Tutorial 4 — Fix an erroneous model¶
The power of GEMs comes from their size — but that size makes errors almost
inevitable, whether you build a model yourself or use someone else's. This
exercise is a systematic round of quality control on a deliberately broken
version of the small yeast model, smallYeastBad.yml.
Model validation is iterative: some errors only surface once others are fixed. The RAVEN philosophy is that it is more valuable to try to make a model do something it should not (e.g. create mass from nothing) than to test only for what it should do.
tutorial4.m leaves several gaps for you to fill; the completed version is in
tutorial4_solutions.m. Many of these checks are also wrapped by the
gapReport function, but doing them step by step is far more instructive.
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. It is assumed that you have already completed Tutorials 2–3 and are somewhat familiar with linear programming.
Step by step¶
1. Can the model make something from nothing?¶
Import the broken model, close all uptake, and maximise the sum of the producing exchange reactions. Any non-zero solution is a problem.
model = readYAMLmodel('smallYeastBad.yml');
model = setParam(model, 'eq', {'glcIN', 'o2IN'}, [0 0]);
model = setParam(model, 'obj', {'acOUT' 'biomassOUT' 'co2OUT' 'ethOUT' 'glyOUT'}, [1 1 1 1 1]);
sol = solveLP(model);
printFluxes(model, sol.x, true); %Nothing produced, good
2. Provoke hidden errors¶
An error may be masked because it costs energy or redox power. Add temporary "free" cofactor reactions (ATP ⇌ ADP + Pᵢ, and similar for NADH/NADPH) to provoke the model, then minimise the sum of fluxes for an interpretable solution:
rxns.rxns = {'FREE_ATP'; 'FREE_NADH'; 'FREE_NADPH'};
rxns.equations = {'ATP <=> ADP + phosphate'; 'NAD(+) <=> NADH'; 'NADP(+) <=> NADPH'};
model = addRxns(model, rxns, 2, 'c');
sol = solveLP(model, 1);
printFluxes(model, sol.x, false, [], [], '%rxnID (%rxnName):%flux\n\t%eqn\n');
Answer to Question 2 — production of ethanol from nothing
Lots of ethanol is produced. ADH1 should only produce one unit of
ethanol. Change its equation with
model = changeRxns(model, 'ADH1', 'acetaldehyde[c] + NADH[c] => ethanol[c] + NAD(+)[c]', 3);
3. Allow every metabolite to be excreted¶
Add a second column to model.b so RAVEN treats it as lower/upper bounds on the
equality constraints — letting anything be excreted. This exposes errors that
need a partner metabolite to be dumped.
model.b = [model.b inf(numel(model.b), 1)];
sol = solveLP(model, 1);
printFluxes(model, sol.x, false, 10^-5, [], '%rxnID (%rxnName):\n\t%eqn\n\t%flux\n');
Answer to Question 3 — two unbalanced reactions
By looking at the reactions which were unbalanced and that were in the flux list, one can see that two reactions should each result in only one unit of product:
FBPshould give one unit of F6P: change it tobeta-D-fructofuranose 1,6-bisphosphate[c] => beta-D-fructofuranose 6-phosphate[c] + phosphate[c].PFKshould give one unit of F16P: change it toATP[c] + beta-D-fructofuranose 6-phosphate[c] => ADP[c] + beta-D-fructofuranose 1,6-bisphosphate[c].
Apply each with changeRxns(model, …, 3).
4. canConsume¶
Set all uptakes and production to 0, drop the excretion column again, and check which metabolites can be consumed without any production:
model = setParam(model, 'eq', getExchangeRxns(model), 0);
model.b = model.b(:, 1);
I = canConsume(model);
disp(model.mets(I)); %These 12 metabolites can be consumed without any production
canConsume reports the metabolites the model can consume even when no
production is allowed. Allow all uptake again, then force the uptake of one of
them — here CO2 — and study the fluxes (a negative output means input):
model.b = [ones(numel(model.b), 1) * -1000 model.b];
model = setParam(model, 'eq', {'co2OUT'}, -1);
sol = solveLP(model);
printFluxes(model, sol.x, false, 10^-5, [], '%rxnID (%rxnName):\n\t%eqn\n\t%flux\n'); %Now it works
Answer to Question 4 — PDC is missing a product
PDC converts pyruvate (3 carbons) to acetaldehyde (2 carbons) without any
other products — CO2 is missing. This would be simpler to change in the
Excel file (or using changeRxns), but as an exercise one can add the
cytosolic CO2 coefficient directly to the stoichiometric matrix:
Irxn = ismember(model.rxns, 'PDC');
Imet = ismember(model.mets, 'CO2_c');
model.S(Imet, Irxn) = 1; %The coefficient is 1.0
constructEquations(model, Irxn)
The solution is now infeasible, meaning it is no longer possible to force uptake of CO2 without any output.
5. Gap-filling against a reference model¶
tutorial4.m also imports a reference model to draw reactions from, allows all
excretion, and reports gaps before filling them:
model = readYAMLmodel('smallYeastBad.yml');
refModel = readYAMLmodel('smallYeast.yml');
model.b = [model.b inf(numel(model.b), 1)];
sol = solveLP(model, 1);
printFluxes(model, sol.x, false, 10^-5, [], '%rxnID (%rxnName):\n\t%eqn\n\t%flux\n');
I = canConsume(model);
disp(model.mets(I));
gapReport(model, {refModel});
[newConnected, cannotConnect, addedRxns, newModel] = fillGaps(model, {refModel}, false);
disp(addedRxns);
disp(newConnected);
disp(cannotConnect);
gapReport automates this whole quality-control workflow, while fillGaps
adds reactions from the reference model (here the Tutorial 3 small yeast model)
to connect the remaining gaps.
Full script¶
% tutorial4
% This script contains the list of functions necessary for running
% Tutorial 4, see Tutorial 4 in "RAVEN tutorials.docx" for more details.
% 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
%Import the model to gap-fill and the reference model to draw reactions from
model=readYAMLmodel('smallYeastBad.yml');
refModel=readYAMLmodel('smallYeast.yml');
model.b=[model.b inf(numel(model.b),1)];
sol=solveLP(model,1);
printFluxes(model,sol.x,false,10^-5,[],'%rxnID (%rxnName):\n\t%eqn\n\t%flux\n');
I=canConsume(model);
disp(model.mets(I));
gapReport(model,{refModel});
[newConnected, cannotConnect, addedRxns, newModel]=fillGaps(model,{refModel},false);
disp(addedRxns);
disp(newConnected);
disp(cannotConnect);