addNewRxnsToEC Add new reactions to an enzyme-constrained model.
+
+
SYNOPSIS
+
function [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, varargin)
+
+
DESCRIPTION
+
addNewRxnsToEC Add new reactions to an enzyme-constrained model.
+
+ Adds new reactions to an enzyme-constrained model. This function is
+ useful to simulate metabolic manipulations done to an organism such as
+ integration of new genes that add a new reaction/pathway.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ newRxns : struct
+ structure with the new reaction information (see Notes for fields).
+
+ Name-Value Arguments
+ --------------------
+ newEnzymes : struct
+ structure with the new enzymes information (see Notes for fields).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ ecModel with new reactions.
+ rxnsAdded : cell
+ cell array with the reactions added.
+ enzAdded : cell
+ cell array with the enzymes added.
+
+ Notes
+ -----
+ The newRxns structure has the following fields:
+
+ - rxns : cell array with unique strings that identifies each reaction.
+ - rxnNames : cell array with the names of each reaction.
+ - equations : cell array with equation strings. Decimal coefficients are expressed as "1.2". Reversibility is indicated by "<=>" or "=>".
+ - grRules : cell array with the gene-reaction relationship for each reaction. For example "(A and B) or (C)" means that the reaction could be catalyzed by a complex between A & B or by C on its own. A rule with brackets is split into its isozymes in disjunctive normal form, so "A and (B or C)" is the two isozymes "A and B" and "A and C". All the genes have to be present in model.genes. Add genes with addGenesRaven before calling this function if needed (opt, default '').
+
+ The newEnzymes structure has the following fields:
+
+ - enzymes : cell array with uniprot id.
+ - genes : cell array with the respective gene.
+ - mw : cell array with the MW.
+
+ Additional usage notes:
+
+ 1. Write equations as follows:
+ 'xylitol[c] + NAD[c] => D-xylulose[c] + NADH[c] + H+[c]'
+ Note that the reversibility defined in the equation will be used to
+ split to construct irreversible reactions (add _REV) and the rules
+ defined will be used to expand the model (add _EXP_n).
+ 2. After adding the new reactions, setKcatForReactions or
+ applyCustomKcats should be run to add the kcat to the reactions, and
+ subsequently applyKcatConstraints.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, newEnzymes, modelAdapter);
+ [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, 'newEnzymes', newEnzymes);
+
+ See also
+ --------
+ applyKcatConstraints, applyCustomKcats
0001 function [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, varargin)
+0002 % addNewRxnsToEC Add new reactions to an enzyme-constrained model.
+0003 %
+0004 % Adds new reactions to an enzyme-constrained model. This function is
+0005 % useful to simulate metabolic manipulations done to an organism such as
+0006 % integration of new genes that add a new reaction/pathway.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % model : struct
+0011 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0012 % newRxns : struct
+0013 % structure with the new reaction information (see Notes for fields).
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % newEnzymes : struct
+0018 % structure with the new enzymes information (see Notes for fields).
+0019 % modelAdapter : ModelAdapter
+0020 % a loaded model adapter (default: the current default model adapter).
+0021 %
+0022 % Returns
+0023 % -------
+0024 % model : struct
+0025 % ecModel with new reactions.
+0026 % rxnsAdded : cell
+0027 % cell array with the reactions added.
+0028 % enzAdded : cell
+0029 % cell array with the enzymes added.
+0030 %
+0031 % Notes
+0032 % -----
+0033 % The newRxns structure has the following fields:
+0034 %
+0035 % - rxns : cell array with unique strings that identifies each reaction.
+0036 % - rxnNames : cell array with the names of each reaction.
+0037 % - equations : cell array with equation strings. Decimal coefficients are expressed as "1.2". Reversibility is indicated by "<=>" or "=>".
+0038 % - grRules : cell array with the gene-reaction relationship for each reaction. For example "(A and B) or (C)" means that the reaction could be catalyzed by a complex between A & B or by C on its own. A rule with brackets is split into its isozymes in disjunctive normal form, so "A and (B or C)" is the two isozymes "A and B" and "A and C". All the genes have to be present in model.genes. Add genes with addGenesRaven before calling this function if needed (opt, default '').
+0039 %
+0040 % The newEnzymes structure has the following fields:
+0041 %
+0042 % - enzymes : cell array with uniprot id.
+0043 % - genes : cell array with the respective gene.
+0044 % - mw : cell array with the MW.
+0045 %
+0046 % Additional usage notes:
+0047 %
+0048 % 1. Write equations as follows:
+0049 % 'xylitol[c] + NAD[c] => D-xylulose[c] + NADH[c] + H+[c]'
+0050 % Note that the reversibility defined in the equation will be used to
+0051 % split to construct irreversible reactions (add _REV) and the rules
+0052 % defined will be used to expand the model (add _EXP_n).
+0053 % 2. After adding the new reactions, setKcatForReactions or
+0054 % applyCustomKcats should be run to add the kcat to the reactions, and
+0055 % subsequently applyKcatConstraints.
+0056 %
+0057 % Examples
+0058 % --------
+0059 % % optional arguments may be given positionally or as name-value pairs:
+0060 % [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, newEnzymes, modelAdapter);
+0061 % [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, 'newEnzymes', newEnzymes);
+0062 %
+0063 % See also
+0064 % --------
+0065 % applyKcatConstraints, applyCustomKcats
+0066
+0067 p = parseGECKOargs(varargin, { ...
+0068 'newEnzymes', []; ...
+0069 'modelAdapter', []});
+0070 newEnzymes = p.newEnzymes;
+0071 modelAdapter = p.modelAdapter;
+0072
+0073 if isempty(modelAdapter)
+0074 modelAdapter = ModelAdapterManager.getDefault();
+0075 if isempty(modelAdapter)
+0076 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0077 end
+0078 end
+0079 params = modelAdapter.getParameters();
+0080
+0081 if isempty(newEnzymes)
+0082 newEnzymes = [];
+0083 end
+0084
+0085 % Validate if GECKO version 3
+0086 if ~isfield(model, 'ec')
+0087 error('Input model is not an ecModel in GECKO 3 format (with ecModel.ec structure)')
+0088 end
+0089
+0090 if model.ec.geckoLight
+0091 error('addNewRxnsToEC only works on non-light ecModels')
+0092 else
+0093 % Check if the new proteins are not already present in the model
+0094 if ~isempty(newEnzymes)
+0095 [toRemove,~] = ismember(newEnzymes.enzymes, model.ec.enzymes);
+0096
+0097 % Remove from the list those enzymes that already present
+0098 if any(toRemove)
+0099 printOrange(['WARNING: Enzymes ' strjoin(newEnzymes.enzymes(toRemove),', ') ' are already present in the model and will not be added.\n']);
+0100 newEnzymes.enzymes(toRemove) = [];
+0101 newEnzymes.genes(toRemove) = [];
+0102 newEnzymes.mw(toRemove) = [];
+0103 end
+0104
+0105 if ~isempty(newEnzymes.enzymes)
+0106
+0107 % 1. Add new genes
+0108 genesToAdd.genes = newEnzymes.genes;
+0109 genesToAdd.geneShortNames = genesToAdd.genes;
+0110 model = addGenesRaven(model, genesToAdd);
+0111
+0112 % 2. Add new Enzyme-usage pseudometabolite
+0113
+0114 % Validate compartment to add protein pseudoreactions
+0115 compartmentID = strcmp(model.compNames,params.enzyme_comp);
+0116 if ~any(compartmentID)
+0117 error(['Compartment ' params.enzyme_comp ' (specified in params.enzyme_comp) '...
+0118 'cannot be found in model.compNames'])
+0119 end
+0120 compartmentID = model.comps(compartmentID);
+0121
+0122 proteinMets.mets = strcat('prot_', newEnzymes.enzymes);
+0123 proteinMets.metNames = proteinMets.mets;
+0124 proteinMets.compartments = compartmentID;
+0125 if isfield(model,'metMiriams')
+0126 proteinMets.metMiriams = repmat({struct('name',{{'sbo'}},'value',{{'SBO:0000252'}})},numel(proteinMets.mets),1);
+0127 end
+0128 if isfield(model,'metCharges')
+0129 proteinMets.metCharges = zeros(numel(proteinMets.mets),1);
+0130 end
+0131 proteinMets.metNotes = repmat({'Enzyme-usage pseudometabolite'},numel(proteinMets.mets),1);
+0132 model = addMets(model,proteinMets);
+0133 end
+0134 enzAdded = newEnzymes.enzymes(:);
+0135 end
+0136
+0137 % 3. Add new reactions
+0138
+0139 % The operators of a gene rule are case insensitive: write them in lower
+0140 % case, as the rest of the function and RAVEN's addRxns expect. Gene ids
+0141 % that contain the letters (Sand1, andA, ORF1) are left as they are.
+0142 newRxns.grRules = regexprep(convertCharArray(newRxns.grRules), ...
+0143 '(?<![^\s()])(and|or)(?![^\s()])', '${lower($0)}', 'ignorecase');
+0144
+0145 % Before add the reactions validate that all the genes in grRules are
+0146 % present in the model.
+0147 genesInRules = strjoin(newRxns.grRules);
+0148 genesInRules = regexp(genesInRules,'[^\s()]+','match'); % Gene ids and the words 'and' and 'or', without spaces and brackets
+0149 genesInRules = genesInRules(~ismember(lower(genesInRules),{'and','or'})); % Only the gene ids
+0150 genesInRules = setdiff(unique(genesInRules),model.genes); % Only keep new genes
+0151
+0152 % If there is any gene which is not present, as to be pass as
+0153 % newEnzymes input
+0154 if ~isempty(genesInRules)
+0155 errorText = 'The following genes from grRules are not present in the model, add them as newEnzymes input:';
+0156 error('%s', ravenList(errorText,genesInRules,false))
+0157 end
+0158
+0159 % Get reversible reactions to split
+0160 toIrrev = find(cellfun(@(x) contains(x,'<=>'),newRxns.equations));
+0161 if ~isempty(toIrrev)
+0162 for i=1:numel(toIrrev)
+0163 idx = toIrrev(i);
+0164 newEquation = split(newRxns.equations(idx),'<=>');
+0165 % Remove directionality
+0166 newRxns.equations(idx) = erase(newRxns.equations(idx), '<');
+0167
+0168 % Add a new reaction to the list to be added
+0169 newRxns.rxns{end+1} = [newRxns.rxns{idx} '_REV'];
+0170 newRxns.rxnNames{end+1} = [newRxns.rxnNames{idx} ' (reversible)'];
+0171 newRxns.equations{end+1} = [strtrim(newEquation{2, 1}) ' => ' strtrim(newEquation{1, 1})];
+0172 newRxns.grRules{end+1} = newRxns.grRules{idx};
+0173 end
+0174 end
+0175
+0176 % Get reactions to separate isozymes
+0177 isozymes = cellfun(@gprToDnf, newRxns.grRules, 'UniformOutput', false);
+0178 toSplit = find(cellfun(@numel,isozymes) > 1);
+0179 if ~isempty(toSplit)
+0180 splitRxns = {}; splitRxnNames = {}; splitEquations = {}; splitGrRules = {};
+0181 for i=1:numel(toSplit)
+0182 idx = toSplit(i);
+0183 % One reaction per AND-clause of the gene rule in disjunctive normal form
+0184 newRules = cellfun(@(x) strjoin(x,' and '), isozymes{idx}, 'UniformOutput', false);
+0185
+0186 for j=1:numel(newRules)
+0187 splitRxns{end+1} = [newRxns.rxns{idx} '_EXP_' num2str(j)];
+0188 splitRxnNames(end+1) = newRxns.rxnNames(idx);
+0189 splitEquations(end+1) = newRxns.equations(idx);
+0190 splitGrRules(end+1) = newRules(j);
+0191 end
+0192 end
+0193
+0194 % Remove every original that was split, in one indexed operation, then append
+0195 % the replacements. Removing (or appending) one at a time while iterating a list
+0196 % of indices computed up front lets each removal shift the indices of everything
+0197 % after it, so a second (or later) original silently gets read from the wrong
+0198 % position once more than one reaction needs splitting in the same call.
+0199 newRxns.rxns(toSplit) = [];
+0200 newRxns.rxnNames(toSplit) = [];
+0201 newRxns.equations(toSplit) = [];
+0202 newRxns.grRules(toSplit) = [];
+0203
+0204 newRxns.rxns = [newRxns.rxns(:); splitRxns(:)];
+0205 newRxns.rxnNames = [newRxns.rxnNames(:); splitRxnNames(:)];
+0206 newRxns.equations = [newRxns.equations(:); splitEquations(:)];
+0207 newRxns.grRules = [newRxns.grRules(:); splitGrRules(:)];
+0208 end
+0209
+0210 newRxns.lb = zeros(length(newRxns.rxns),1);
+0211 newRxns.ub = zeros(length(newRxns.rxns),1) + 1000;
+0212
+0213 model = addRxns(model, newRxns, 3, '', true, true);
+0214
+0215 % 4. Add protein usage reactions.
+0216 usageRxns.rxns = strcat('usage_',proteinMets.mets);
+0217 usageRxns.rxnNames = usageRxns.rxns;
+0218 usageRxns.mets = cell(numel(usageRxns.rxns),1);
+0219 usageRxns.stoichCoeffs = cell(numel(usageRxns.rxns),1);
+0220 for i=1:numel(usageRxns.mets)
+0221 usageRxns.mets{i} = {proteinMets.mets{i}, 'prot_pool'};
+0222 usageRxns.stoichCoeffs{i} = [1,-1];
+0223 end
+0224 usageRxns.lb = zeros(numel(usageRxns.rxns),1);
+0225 usageRxns.ub = zeros(numel(usageRxns.rxns),1) + 1000;
+0226 usageRxns.rev = ones(numel(usageRxns.rxns),1);
+0227 usageRxns.grRules = newEnzymes.genes;
+0228 model = addRxns(model,usageRxns);
+0229
+0230 % 5. Update .ec field with enzyme data
+0231 model.ec.enzymes = [model.ec.enzymes; newEnzymes.enzymes(:)];
+0232 model.ec.genes = [model.ec.genes; newEnzymes.genes(:)];
+0233 model.ec.mw = [model.ec.mw; newEnzymes.mw(:)];
+0234 model.ec.sequence = [model.ec.sequence; repmat({''},numel(newEnzymes.enzymes),1)];
+0235 if isfield(model.ec,'concs')
+0236 model.ec.concs = [model.ec.concs; NaN(numel(newEnzymes.enzymes),1)];
+0237 end
+0238
+0239 % 6. Expand the enzyme rxns matrix
+0240 rxnWithRule = find(~cellfun('isempty',newRxns.grRules));
+0241
+0242 model.ec.rxnEnzMat = [model.ec.rxnEnzMat, zeros(length(model.ec.rxns), length(newEnzymes.enzymes))]; % add n new enzymes
+0243 model.ec.rxnEnzMat = [model.ec.rxnEnzMat; zeros(length(rxnWithRule), length(model.ec.enzymes))]; % add n new rxns
+0244
+0245 % 7. Update .ec field with rxn data
+0246
+0247 % Get reactions to add
+0248 numRxns = length(model.ec.rxns);
+0249 for i = 1:numel(rxnWithRule)
+0250 rxnIdx = rxnWithRule(i);
+0251
+0252 model.ec.rxns(end+1) = newRxns.rxns(rxnIdx);
+0253 model.ec.kcat(end+1) = 0;
+0254 model.ec.source(end+1) = {''};
+0255 model.ec.notes(end+1) = {''};
+0256 model.ec.eccodes(end+1) = {''};
+0257
+0258 % Get rules
+0259 genes = regexp(newRxns.grRules{rxnIdx},'[^\s()]+','match'); % Gene ids and the word 'and', without spaces and brackets
+0260 genes = genes(~ismember(lower(genes),{'and','or'}));
+0261 [~,enzIdx] = ismember(genes, model.ec.genes);
+0262
+0263 % Update the enzyme rxns matrix
+0264 model.ec.rxnEnzMat(numRxns+i, enzIdx) = 1;
+0265 end
+0266
+0267 rxnsAdded = newRxns.rxns(:);
+0268 end
+0269 end
+0270
+0271 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0272 %Function that expands a gene rule into disjunctive normal form: a cell array
+0273 %with one cell per isozyme, each holding the gene ids that are ANDed in it.
+0274 %Gene ids are whole words, so an id that contains 'or' or 'and' is kept
+0275 %intact. An empty rule gives an empty cell array.
+0276 function dnf = gprToDnf(rule)
+0277 tokens = regexp(rule,'\(|\)|[^\s()]+','match');
+0278 isOperator = strcmpi(tokens,'and') | strcmpi(tokens,'or');
+0279 tokens(isOperator) = lower(tokens(isOperator)); % Operators are case insensitive
+0280 if isempty(tokens)
+0281 dnf = {};
+0282 return
+0283 end
+0284 [dnf,pos] = parseOr(tokens,1,rule);
+0285 if pos <= numel(tokens)
+0286 error('Cannot parse the gene rule "%s"',rule)
+0287 end
+0288 end
+0289
+0290 function [dnf,pos] = parseOr(tokens,pos,rule)
+0291 [dnf,pos] = parseAnd(tokens,pos,rule);
+0292 while pos <= numel(tokens) && strcmp(tokens{pos},'or')
+0293 [rhs,pos] = parseAnd(tokens,pos+1,rule);
+0294 dnf = [dnf,rhs];
+0295 end
+0296 end
+0297
+0298 function [dnf,pos] = parseAnd(tokens,pos,rule)
+0299 [dnf,pos] = parseAtom(tokens,pos,rule);
+0300 while pos <= numel(tokens) && strcmp(tokens{pos},'and')
+0301 [rhs,pos] = parseAtom(tokens,pos+1,rule);
+0302 product = cell(1,0);
+0303 for a = 1:numel(dnf)
+0304 for b = 1:numel(rhs)
+0305 product{end+1} = [dnf{a},rhs{b}]; %#ok<AGROW>
+0306 end
+0307 end
+0308 dnf = product;
+0309 end
+0310 end
+0311
+0312 function [dnf,pos] = parseAtom(tokens,pos,rule)
+0313 if pos > numel(tokens) || any(strcmp(tokens{pos},{')','and','or'}))
+0314 error('Cannot parse the gene rule "%s"',rule)
+0315 elseif strcmp(tokens{pos},'(')
+0316 [dnf,pos] = parseOr(tokens,pos+1,rule);
+0317 if pos > numel(tokens) || ~strcmp(tokens{pos},')')
+0318 error('Cannot parse the gene rule "%s"',rule)
+0319 end
+0320 pos = pos + 1;
+0321 else
+0322 dnf = {tokens(pos)};
+0323 pos = pos + 1;
+0324 end
+0325 end
applyComplexData Apply complex subunit stoichiometry to an ecModel.
+
+
SYNOPSIS
+
function [model, foundComplex, proposedComplex] = applyComplexData(model, varargin)
+
+
DESCRIPTION
+
applyComplexData Apply complex subunit stoichiometry to an ecModel.
+
+ Populates model.ec.rxnEnzMat with subunit stochiometries for protein
+ complexes, based on curated data from the EMBL-EBI Complex Portal.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ complexInfo : struct
+ structure as generated by getComplexData. If nothing is provided, an
+ attempt will be made to read data/ComplexPortal.json from the
+ obj.params.path folder specified in the modelAdapter.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ verbose : logical
+ true if a summary should be shown in the Command Window (default
+ true).
+
+ Returns
+ -------
+ model : struct
+ ecModel where model.ec.rxnEnzMat is populated with subunit
+ stochiometries.
+ foundComplex : table
+ complexes that fully matched between the model and the complex data
+ (columns described under Notes).
+ proposedComplex : table
+ complexes where the model contained >75% but <100% of the proteins
+ indicated by Complex Portal, or where the model contained more
+ proteins than indicated for that complex in Complex Portal (same
+ columns as foundComplex, plus a percentage match column).
+
+ Notes
+ -----
+ The subunit stoichiometry is assigned per protein ID: the proteins of a
+ complex need not be listed in the order of model.ec.enzymes.
+
+ foundComplex and proposedComplex share the following columns: rxn
+ (reaction identifier), complexID and name (Complex Portal identifiers),
+ genes (gene names of the complex subunits), protID_model (matched protein
+ IDs from the model), protID_complex (protein IDs listed by Complex
+ Portal), and stochiometry (subunit stoichiometry from Complex Portal).
+ proposedComplex has an additional match column with the percentage match
+ between the model proteins and the proposed complex.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, foundComplex, proposedComplex] = applyComplexData(ecModel, complexInfo, modelAdapter);
+ [model, foundComplex, proposedComplex] = applyComplexData(ecModel, 'complexInfo', complexInfo);
+
+ See also
+ --------
+ getComplexData, makeEcModel
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
getComplexData getComplexData Download curated complex stochiometries from Complex Portal.
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [model, foundComplex, proposedComplex] = applyComplexData(model, varargin)
+0002 % applyComplexData Apply complex subunit stoichiometry to an ecModel.
+0003 %
+0004 % Populates model.ec.rxnEnzMat with subunit stochiometries for protein
+0005 % complexes, based on curated data from the EMBL-EBI Complex Portal.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % model : struct
+0010 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0011 %
+0012 % Name-Value Arguments
+0013 % --------------------
+0014 % complexInfo : struct
+0015 % structure as generated by getComplexData. If nothing is provided, an
+0016 % attempt will be made to read data/ComplexPortal.json from the
+0017 % obj.params.path folder specified in the modelAdapter.
+0018 % modelAdapter : ModelAdapter
+0019 % a loaded model adapter (default: the current default model adapter).
+0020 % verbose : logical
+0021 % true if a summary should be shown in the Command Window (default
+0022 % true).
+0023 %
+0024 % Returns
+0025 % -------
+0026 % model : struct
+0027 % ecModel where model.ec.rxnEnzMat is populated with subunit
+0028 % stochiometries.
+0029 % foundComplex : table
+0030 % complexes that fully matched between the model and the complex data
+0031 % (columns described under Notes).
+0032 % proposedComplex : table
+0033 % complexes where the model contained >75% but <100% of the proteins
+0034 % indicated by Complex Portal, or where the model contained more
+0035 % proteins than indicated for that complex in Complex Portal (same
+0036 % columns as foundComplex, plus a percentage match column).
+0037 %
+0038 % Notes
+0039 % -----
+0040 % The subunit stoichiometry is assigned per protein ID: the proteins of a
+0041 % complex need not be listed in the order of model.ec.enzymes.
+0042 %
+0043 % foundComplex and proposedComplex share the following columns: rxn
+0044 % (reaction identifier), complexID and name (Complex Portal identifiers),
+0045 % genes (gene names of the complex subunits), protID_model (matched protein
+0046 % IDs from the model), protID_complex (protein IDs listed by Complex
+0047 % Portal), and stochiometry (subunit stoichiometry from Complex Portal).
+0048 % proposedComplex has an additional match column with the percentage match
+0049 % between the model proteins and the proposed complex.
+0050 %
+0051 % Examples
+0052 % --------
+0053 % % optional arguments may be given positionally or as name-value pairs:
+0054 % [model, foundComplex, proposedComplex] = applyComplexData(ecModel, complexInfo, modelAdapter);
+0055 % [model, foundComplex, proposedComplex] = applyComplexData(ecModel, 'complexInfo', complexInfo);
+0056 %
+0057 % See also
+0058 % --------
+0059 % getComplexData, makeEcModel
+0060
+0061 p = parseGECKOargs(varargin, { ...
+0062 'complexInfo', []; ...
+0063 'modelAdapter', []; ...
+0064 'verbose', true});
+0065 complexInfo = p.complexInfo;
+0066 modelAdapter = p.modelAdapter;
+0067 verbose = p.verbose;
+0068 if isempty(verbose); verbose = true; end
+0069
+0070 if isempty(modelAdapter)
+0071 modelAdapter = ModelAdapterManager.getDefault();
+0072 if isempty(modelAdapter)
+0073 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0074 end
+0075 end
+0076 params = modelAdapter.params;
+0077
+0078 if isempty(complexInfo)
+0079 complexInfo = fullfile(params.path,'data','ComplexPortal.json');
+0080 if ~isfile(complexInfo)
+0081 complexInfo = getComplexData([], modelAdapter);
+0082 end
+0083 end
+0084
+0085 if ischar(complexInfo) || isstring(complexInfo)
+0086 jsonStr = fileread(complexInfo);
+0087 complexData = jsondecode(jsonStr);
+0088 else
+0089 complexData = complexInfo;
+0090 end
+0091
+0092 %Remove prefixes on rxn names for gecko light
+0093 if ~model.ec.geckoLight
+0094 rxnNames = model.ec.rxns;
+0095 else
+0096 rxnNames = extractAfter(model.ec.rxns,4);
+0097 end
+0098
+0099 foundComplex = cell(numel(rxnNames),7);
+0100 foundComplexCount = 0;
+0101 proposedComplex = cell(numel(rxnNames)*2,8); % Might propose more
+0102 proposedComplexCount = 0;
+0103
+0104 complexProts = repmat({''}, numel({complexData.complexID})*10, 1);
+0105 complexMatrix = zeros(numel({complexData.complexID}),numel(complexProts));
+0106 lastProt = 0;
+0107 for i=1:size(complexMatrix,1)
+0108 % Locate proteins (and add if new)
+0109 protIDs = complexData(i).protID;
+0110 [isOld, protMatIdx] = ismember(protIDs,complexProts);
+0111 newProts = protIDs(~isOld);
+0112 complexProts(lastProt+1:lastProt+numel(newProts))=newProts;
+0113 protMatIdx(~isOld) = lastProt+1 : lastProt+numel(newProts);
+0114 lastProt = lastProt + numel(newProts);
+0115 % Add subunit numbers
+0116 if all(complexData(i).stochiometry == 0)
+0117 complexData(i).stochiometry(:) = 1;
+0118 end
+0119 complexMatrix(i,protMatIdx) = complexData(i).stochiometry;
+0120 end
+0121
+0122 % Remove excess space
+0123 complexMatrix(:,lastProt+1:end) = [];
+0124 complexProts(lastProt+1:end) = [];
+0125 PB = progressReport(numel(rxnNames), 'Assign complexes to reactions');
+0126 for i = 1:numel(rxnNames)
+0127 PB.count;
+0128 % Get the proteins from the model
+0129 modelProts = model.ec.enzymes(find(model.ec.rxnEnzMat(i,:)));
+0130 [protsInMat, protsIdx] = ismember(modelProts,complexProts);
+0131 if ~any(protsInMat)
+0132 continue
+0133 end
+0134 % Find complexes that includes the model proteins
+0135 [potComplex,~] = find(complexMatrix(:,protsIdx(protsInMat)));
+0136 potComplex = unique(potComplex);
+0137 % Some stats on number of subunits, percentage match etc.
+0138 matchComplexUnits = sum(logical(complexMatrix(potComplex,protsIdx(protsInMat))),2);
+0139 totalComplexUnits = sum(logical(complexMatrix(potComplex,:)),2);
+0140 modComplexUnits = numel(modelProts);
+0141 percMatch = matchComplexUnits/modComplexUnits;
+0142 totalMatch = totalComplexUnits/modComplexUnits;
+0143 if any(percMatch == 1 & totalMatch == 1)
+0144 % Exact match
+0145 complexMatch = find(percMatch == 1 & totalMatch == 1);
+0146 if numel(complexMatch)>1 % If more than one match, take first match
+0147 complexMatch = complexMatch(1);
+0148 end
+0149 complexMatch = potComplex(complexMatch);
+0150 foundComplexCount = foundComplexCount + 1;
+0151 foundComplex{foundComplexCount,1} = model.ec.rxns{i};
+0152 foundComplex{foundComplexCount,2} = complexData(complexMatch).complexID;
+0153 foundComplex{foundComplexCount,3} = complexData(complexMatch).name;
+0154 foundComplex{foundComplexCount,4} = complexData(complexMatch).geneName;
+0155 foundComplex{foundComplexCount,5} = modelProts;
+0156 foundComplex{foundComplexCount,6} = complexData(complexMatch).protID;
+0157 foundComplex{foundComplexCount,7} = complexData(complexMatch).stochiometry;
+0158
+0159 % Apply in rxnEnzMat
+0160 % The stoichiometry is taken per protein ID, as the proteins of the
+0161 % complex need not be listed in the order of model.ec.enzymes
+0162 [~, modelProtsIdx] = ismember(modelProts, model.ec.enzymes);
+0163 [~, complexPos] = ismember(modelProts, complexData(complexMatch).protID);
+0164 model.ec.rxnEnzMat(i,modelProtsIdx) = complexData(complexMatch).stochiometry(complexPos);
+0165
+0166 elseif modComplexUnits > 1 % Only suggest potential complex if grRule > 1 protein
+0167 moreUnitsInData = find(percMatch == 1 & totalMatch > 1);
+0168 if any(moreUnitsInData)
+0169 % All ecModel subunits match, but Complex Portal has more subunits
+0170 % Take the complex with the minimum number of extra subunits
+0171 [~,propComplex] = min(totalMatch(moreUnitsInData));
+0172 propComplex = moreUnitsInData(propComplex);
+0173 selectComplex = potComplex(propComplex);
+0174 proposedComplexCount = proposedComplexCount + 1;
+0175 proposedComplex{proposedComplexCount,1} = model.ec.rxns{i};
+0176 proposedComplex{proposedComplexCount,2} = complexData(selectComplex).complexID;
+0177 proposedComplex{proposedComplexCount,3} = complexData(selectComplex).name;
+0178 proposedComplex{proposedComplexCount,4} = complexData(selectComplex).geneName;
+0179 proposedComplex{proposedComplexCount,5} = modelProts;
+0180 proposedComplex{proposedComplexCount,6} = complexData(selectComplex).protID;
+0181 proposedComplex{proposedComplexCount,7} = complexData(selectComplex).stochiometry;
+0182 proposedComplex{proposedComplexCount,8} = totalMatch(propComplex)*100;
+0183 end
+0184 otherUnits = find(percMatch >= 0.75 & percMatch < 1 & totalMatch <= 1);
+0185 if any(otherUnits)
+0186 % At least 75% of ecModels subunits match, Complex Portal maybe have less or different subunits
+0187 % Take the complex with highest % match.
+0188 [~,propComplex] = max(percMatch(otherUnits));
+0189 propComplex = otherUnits(propComplex);
+0190 selectComplex = potComplex(propComplex);
+0191 proposedComplexCount = proposedComplexCount + 1;
+0192 proposedComplex{proposedComplexCount,1} = model.ec.rxns{i};
+0193 proposedComplex{proposedComplexCount,2} = complexData(selectComplex).complexID;
+0194 proposedComplex{proposedComplexCount,3} = complexData(selectComplex).name;
+0195 proposedComplex{proposedComplexCount,4} = complexData(selectComplex).geneName;
+0196 proposedComplex{proposedComplexCount,5} = modelProts;
+0197 proposedComplex{proposedComplexCount,6} = complexData(selectComplex).protID;
+0198 proposedComplex{proposedComplexCount,7} = complexData(selectComplex).stochiometry;
+0199 proposedComplex{proposedComplexCount,8} = percMatch(propComplex)*100;
+0200 end
+0201 end
+0202 end
+0203 PB.done;
+0204
+0205 % Remove empty space
+0206 foundComplex(foundComplexCount+1:end,:) = [];
+0207 proposedComplex(proposedComplexCount+1:end,:) = [];
+0208
+0209 rowHeadings = {'rxn', 'complexID','name','genes','protID_model','protID_complex','stochiometry'};
+0210
+0211 foundComplex = cell2table(foundComplex, 'VariableNames', rowHeadings);
+0212
+0213 proposedComplex = cell2table(proposedComplex, 'VariableNames', [rowHeadings 'match']);
+0214 if verbose
+0215 disp(['A total of ' int2str(numel(foundComplex(:,1))) ' complex have full match, and ' int2str(numel(proposedComplex(:,1))) ' proposed.'])
+0216 end
+0217 end
applyCustomKcats Apply user-defined kcats to an ecModel.
+
+
SYNOPSIS
+
function [model, rxnUpdated, notMatch] = applyCustomKcats(model, varargin)
+
+
DESCRIPTION
+
applyCustomKcats Apply user-defined kcats to an ecModel.
+
+ Reads data/customKcats.tsv in the obj.params.path specified in the model
+ adapter. Alternatively, a customKcats structure can be provided, as
+ specified below.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ customKcats : struct
+ structure with custom kcat information (fields described under Notes).
+ If nothing is provided, an attempt will be made to read
+ data/customKcats.tsv from the obj.params.path folder specified in the
+ modelAdapter.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ ecModel where kcats for defined proteins have been changed.
+ rxnUpdated : cell
+ ids list of updated reactions, where new kcats were applied.
+ notMatch : table
+ reactions for which the custom information provided has a partial
+ match (at least 50%, but not 100%) with the reaction's GPR rule, for
+ the user to curate manually. Columns: rxns and name (reaction
+ identifier and name), custom enzymes (the proteins specified in
+ customKcats), enzymes in model (the enzymes annotated to the reaction
+ in the model), and rules (the reaction's GPR rule).
+
+ Notes
+ -----
+ The customKcats structure has the following fields:
+
+ - proteins : protein identifiers, multiple for the same kcat (in case of a protein complex) are separated by ' + '.
+ - genes : gene identifiers (optional, not used in matching).
+ - gene_name : short gene name (optional, not used in matching).
+ - kcat : new kcat value (one per entry).
+ - rxns : reaction identifiers, multiple for the same kcat are separated by ',' (see further explanation below).
+ - notes : will be appended to model.ec.notes, separated by ', ' from an existing note; an empty note changes nothing (optional).
+ - stoicho : complex stoichiometry, separated by ' + ' (examples: '1' or '3 + 1'), matching the order in proteins field.
+
+ An entry with an empty kcat is skipped. Protein and reaction identifiers
+ are matched exactly, including their letter case. Light ecModels are
+ supported: the rows of model.ec are matched by their ec reaction ids.
+
+ Matching order:
+
+ 1. reactions are identified by .proteins and .rxns.
+ 2. reactions are identified by .proteins only (empty .rxns entry); no
+ additional checks are made: is a reaction annotated with these
+ proteins? => its kcat will be updated, irrespective of the exact
+ reaction, direction, substrate, etc.
+ 3. reactions are identified by .rxns only (empty .proteins entry); no
+ additional checks are made: is a reaction derived from the original
+ reaction identifier => its kcat will be updated, irrespective of the
+ annotated protein.
+
+ The customKcats.rxns field reaction identifiers are from the ORIGINAL
+ model, before _EXP_ suffixes were added by makeEcModel. Reaction
+ directionality IS however specified, with a _REV suffix. Example entries:
+
+ - 'r_0001' will match r_0001, r_0001_EXP_1, r_0001_EXP_2 etc., but not r_0001_REV, r_0001_EXP_1_REV etc.
+ - 'r_0001_REV' will match r_0001_REV, r_0001_EXP_1_REV, r_0001_EXP_2_REV, etc., but not r_0001, r_0001_EXP_1 etc.
+
+ Multiple identifiers should be comma separated (e.g. r_0001, r_0002).
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, rxnUpdated, notMatch] = applyCustomKcats(model, customKcats, modelAdapter);
+ [model, rxnUpdated, notMatch] = applyCustomKcats(model, 'customKcats', customKcats);
+
+ See also
+ --------
+ applyKcatConstraints, makeEcModel
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
applyKcatConstraints applyKcatConstraints Apply kcat-derived enzyme constraints to an ecModel.
0001 function [model, rxnUpdated, notMatch] = applyCustomKcats(model, varargin)
+0002 % applyCustomKcats Apply user-defined kcats to an ecModel.
+0003 %
+0004 % Reads data/customKcats.tsv in the obj.params.path specified in the model
+0005 % adapter. Alternatively, a customKcats structure can be provided, as
+0006 % specified below.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % model : struct
+0011 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0012 %
+0013 % Name-Value Arguments
+0014 % --------------------
+0015 % customKcats : struct
+0016 % structure with custom kcat information (fields described under Notes).
+0017 % If nothing is provided, an attempt will be made to read
+0018 % data/customKcats.tsv from the obj.params.path folder specified in the
+0019 % modelAdapter.
+0020 % modelAdapter : ModelAdapter
+0021 % a loaded model adapter (default: the current default model adapter).
+0022 %
+0023 % Returns
+0024 % -------
+0025 % model : struct
+0026 % ecModel where kcats for defined proteins have been changed.
+0027 % rxnUpdated : cell
+0028 % ids list of updated reactions, where new kcats were applied.
+0029 % notMatch : table
+0030 % reactions for which the custom information provided has a partial
+0031 % match (at least 50%, but not 100%) with the reaction's GPR rule, for
+0032 % the user to curate manually. Columns: rxns and name (reaction
+0033 % identifier and name), custom enzymes (the proteins specified in
+0034 % customKcats), enzymes in model (the enzymes annotated to the reaction
+0035 % in the model), and rules (the reaction's GPR rule).
+0036 %
+0037 % Notes
+0038 % -----
+0039 % The customKcats structure has the following fields:
+0040 %
+0041 % - proteins : protein identifiers, multiple for the same kcat (in case of a protein complex) are separated by ' + '.
+0042 % - genes : gene identifiers (optional, not used in matching).
+0043 % - gene_name : short gene name (optional, not used in matching).
+0044 % - kcat : new kcat value (one per entry).
+0045 % - rxns : reaction identifiers, multiple for the same kcat are separated by ',' (see further explanation below).
+0046 % - notes : will be appended to model.ec.notes, separated by ', ' from an existing note; an empty note changes nothing (optional).
+0047 % - stoicho : complex stoichiometry, separated by ' + ' (examples: '1' or '3 + 1'), matching the order in proteins field.
+0048 %
+0049 % An entry with an empty kcat is skipped. Protein and reaction identifiers
+0050 % are matched exactly, including their letter case. Light ecModels are
+0051 % supported: the rows of model.ec are matched by their ec reaction ids.
+0052 %
+0053 % Matching order:
+0054 %
+0055 % 1. reactions are identified by .proteins and .rxns.
+0056 % 2. reactions are identified by .proteins only (empty .rxns entry); no
+0057 % additional checks are made: is a reaction annotated with these
+0058 % proteins? => its kcat will be updated, irrespective of the exact
+0059 % reaction, direction, substrate, etc.
+0060 % 3. reactions are identified by .rxns only (empty .proteins entry); no
+0061 % additional checks are made: is a reaction derived from the original
+0062 % reaction identifier => its kcat will be updated, irrespective of the
+0063 % annotated protein.
+0064 %
+0065 % The customKcats.rxns field reaction identifiers are from the ORIGINAL
+0066 % model, before _EXP_ suffixes were added by makeEcModel. Reaction
+0067 % directionality IS however specified, with a _REV suffix. Example entries:
+0068 %
+0069 % - 'r_0001' will match r_0001, r_0001_EXP_1, r_0001_EXP_2 etc., but not r_0001_REV, r_0001_EXP_1_REV etc.
+0070 % - 'r_0001_REV' will match r_0001_REV, r_0001_EXP_1_REV, r_0001_EXP_2_REV, etc., but not r_0001, r_0001_EXP_1 etc.
+0071 %
+0072 % Multiple identifiers should be comma separated (e.g. r_0001, r_0002).
+0073 %
+0074 % Examples
+0075 % --------
+0076 % % optional arguments may be given positionally or as name-value pairs:
+0077 % [model, rxnUpdated, notMatch] = applyCustomKcats(model, customKcats, modelAdapter);
+0078 % [model, rxnUpdated, notMatch] = applyCustomKcats(model, 'customKcats', customKcats);
+0079 %
+0080 % See also
+0081 % --------
+0082 % applyKcatConstraints, makeEcModel
+0083
+0084 p = parseGECKOargs(varargin, { ...
+0085 'customKcats', []; ...
+0086 'modelAdapter', []});
+0087 customKcats = p.customKcats;
+0088 modelAdapter = p.modelAdapter;
+0089
+0090 if isempty(modelAdapter)
+0091 modelAdapter = ModelAdapterManager.getDefault();
+0092 if isempty(modelAdapter)
+0093 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0094 end
+0095 end
+0096
+0097 params = modelAdapter.params;
+0098
+0099 if isempty(customKcats)
+0100 customKcats = fullfile(params.path,'data','customKcats.tsv');
+0101 end
+0102 if isstruct(customKcats)
+0103 if ~all(isfield(customKcats,{'proteins','kcat','rxns'}))
+0104 error('The customKcats structure does not have all essential fields.');
+0105 end
+0106 elseif isfile(customKcats)
+0107 fID = fopen(customKcats, 'r');
+0108 fileContent = textscan(fID, '%s %s %s %f %q %s %s', 'Delimiter', '\t', 'HeaderLines', 1);
+0109 fclose(fID);
+0110 clear customKcats
+0111 customKcats.proteins = fileContent{1};
+0112 customKcats.genes = fileContent{2};
+0113 customKcats.gene_name = fileContent{3};
+0114 customKcats.kcat = fileContent{4};
+0115 customKcats.rxns = fileContent{5};
+0116 customKcats.notes = fileContent{6};
+0117 customKcats.stoicho = fileContent{7};
+0118 else
+0119 error(['Cannot find file: ' customKcats]);
+0120 end
+0121
+0122 rxnToUpdate = false(length(model.ec.rxns),1);
+0123 rxnNotMatch = false(length(model.ec.rxns),1);
+0124 evaluatedRule = cell(length(model.ec.rxns),1);
+0125 enzInModel = cell(length(model.ec.rxns),1);
+0126 ecRxnNoSuffix = regexprep(model.ec.rxns,'_EXP_\d+$','');
+0127
+0128 for i = 1:numel(customKcats.proteins)
+0129 if isnan(customKcats.kcat(i))
+0130 printOrange(['WARNING: Entry ' num2str(i) ' has no kcat; it is skipped.']);
+0131 continue
+0132 end
+0133 if isempty(customKcats.proteins{i})
+0134 %If only reaction ID(s) is/are specified (and no proteins),
+0135 %then apply the kcat to all isozymic reactions
+0136 rxns = strtrim(strsplit(customKcats.rxns{i}, ','));
+0137 rxnIdxs = ismember(ecRxnNoSuffix,rxns);
+0138 rxnToUpdate(rxnIdxs) = 1;
+0139 model.ec.kcat(rxnIdxs) = customKcats.kcat(i);
+0140
+0141 % Add note mentioning manual kcat change
+0142 model.ec.source(rxnIdxs) = {'custom'};
+0143 if isfield(customKcats,'notes')
+0144 matchedIdxs = find(rxnIdxs);
+0145 for j = 1:numel(matchedIdxs)
+0146 model.ec.notes{matchedIdxs(j), 1} = appendNote(model.ec.notes{matchedIdxs(j), 1}, customKcats.notes{i});
+0147 end
+0148 end
+0149 else
+0150 prots = strtrim(strsplit(customKcats.proteins{i}, '+'));
+0151
+0152 % Find the index for the enzymes which kcat will be changed. In
+0153 % case the protein is not in the model generate a warning.
+0154 try
+0155 enzIdx = cellfun(@(x) find(strcmp(model.ec.enzymes, x)), prots);
+0156 catch
+0157 enzIdx = [];
+0158 printOrange(['WARNING: Protein(s) ' customKcats.proteins{i} ' were not found in the model.']);
+0159 end
+0160
+0161 % if not specific reactions are defined, find all the reaction
+0162 % index where the enzyme is used
+0163 if isempty(customKcats.rxns{i})
+0164 temp_rxnIdxs = arrayfun(@(x) find(model.ec.rxnEnzMat(:, x)), enzIdx, 'UniformOutput', false);
+0165 % otherwhise, If a set of reactions if defined, only get the index for those
+0166 % but ignore any _EXP_ suffixes
+0167 else
+0168 rxns = strtrim(strsplit(customKcats.rxns{i}, ','));
+0169 temp_rxnIdxs = cellfun(@(x) find(strcmp(ecRxnNoSuffix, x)), rxns, 'UniformOutput', false);
+0170 end
+0171
+0172 if ~isempty(temp_rxnIdxs)
+0173 rxnIdxs = [];
+0174 for j = 1:numel(temp_rxnIdxs)
+0175 rxnIdxs = [rxnIdxs; temp_rxnIdxs{j}];
+0176 end
+0177
+0178 % Check when multiple proteins are involved, since it can return same rxn n times
+0179 rxnIdxs = unique(rxnIdxs); %unique(rxnIdxs{1, :});
+0180
+0181 for j = 1:numel(rxnIdxs)
+0182 % Get all the enzymes involved in the reaction
+0183 allEnzInRxn = find(model.ec.rxnEnzMat(rxnIdxs(j),:));
+0184
+0185 C = intersect(enzIdx, allEnzInRxn);
+0186
+0187 % Determine the match percentage bewteen the rules
+0188 if numel(C) == numel(enzIdx) && numel(C) == numel(allEnzInRxn)
+0189 match = 1;
+0190 else
+0191 if numel(enzIdx) < numel(allEnzInRxn)
+0192 match = numel(C) / numel(allEnzInRxn);
+0193 else
+0194 match = numel(C) / numel(enzIdx);
+0195 end
+0196 end
+0197
+0198 % Update the kcat value stored in the model, if match 100%,
+0199 % otherwhise if >= 0.5 inform to the user to be validated
+0200 if match == 1
+0201 rxnToUpdate(rxnIdxs(j)) = 1;
+0202 model.ec.kcat(rxnIdxs(j)) = customKcats.kcat(i);
+0203
+0204 % Add note mentioning manual kcat change
+0205 model.ec.source{rxnIdxs(j),1} = 'custom';
+0206 if isfield(customKcats,'notes')
+0207 model.ec.notes{rxnIdxs(j), 1} = appendNote(model.ec.notes{rxnIdxs(j), 1}, customKcats.notes{i});
+0208 end
+0209 elseif match >= 0.5 && match < 1
+0210 rxnNotMatch(rxnIdxs(j)) = 1;
+0211 evaluatedRule{rxnIdxs(j), 1} = customKcats.proteins{i};
+0212 enzInModel{rxnIdxs(j), 1} = strjoin(model.ec.enzymes(allEnzInRxn), ' + ');
+0213 end
+0214 end
+0215 end
+0216 end
+0217 end
+0218
+0219 % Apply the new kcat values to the model
+0220 if ~isempty(find(rxnToUpdate, 1))
+0221 if model.ec.geckoLight
+0222 %Light ecModels update by reaction of the model, not by ec row
+0223 updateRxns = ismember(model.rxns, extractAfter(model.ec.rxns(rxnToUpdate), 4));
+0224 else
+0225 updateRxns = rxnToUpdate;
+0226 end
+0227 model = applyKcatConstraints(model, updateRxns);
+0228 else
+0229 printOrange('WARNING: No matches found. Consider checking the IDs or proteins in customKcats.');
+0230 end
+0231
+0232 rxnUpdated = model.ec.rxns(find(rxnToUpdate));
+0233
+0234 % Remove from notMatch those reactions already updated
+0235 remove = and(rxnToUpdate, rxnNotMatch);
+0236 rxnNotMatch(remove) = 0;
+0237 evaluatedRule(remove) = '';
+0238 enzInModel(remove) = '';
+0239
+0240 idRxns = model.ec.rxns(find(rxnNotMatch));
+0241 if model.ec.geckoLight
+0242 modelRxnIds = extractAfter(idRxns, 4);
+0243 else
+0244 modelRxnIds = idRxns;
+0245 end
+0246 fullIdx = cellfun(@(x) find(strcmpi(model.rxns, x), 1), modelRxnIds);
+0247 rxnsNames = model.rxnNames(fullIdx);
+0248 evaluatedRule = evaluatedRule(~cellfun('isempty', evaluatedRule));
+0249 enzInModel = enzInModel(~cellfun('isempty', enzInModel));
+0250 rules = model.grRules(fullIdx);
+0251 notMatch = table(idRxns, rxnsNames, evaluatedRule, enzInModel, rules, ...
+0252 'VariableNames',{'rxns', 'name', 'custom enzymes', 'enzymes in model', 'rules'});
+0253 end
+0254
+0255 function notes = appendNote(notes, note)
+0256 % An empty note changes nothing; a note is separated by ', ' from an existing one.
+0257 note = strtrim(note);
+0258 if isempty(note)
+0259 return
+0260 elseif isempty(notes)
+0261 notes = note;
+0262 else
+0263 notes = [notes ', ' note];
+0264 end
+0265 end
applyKcatConstraints Apply kcat-derived enzyme constraints to an ecModel.
+
+
SYNOPSIS
+
function model = applyKcatConstraints(model,varargin)
+
+
DESCRIPTION
+
applyKcatConstraints Apply kcat-derived enzyme constraints to an ecModel.
+
+ Existing enzyme constraints are first removed (unless updateRxns is
+ provided), and new constraints are defined based on the content of
+ model.ec.kcat. When every kcat is 0, the existing constraints of a full
+ ecModel are removed and no new ones are written; a light ecModel is
+ returned unchanged.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ updateRxns : logical or double or cell or char or string
+ if not all enzyme constraints should be updated, this can be given as
+ either a logical vector of length model.ec.rxns, a vector of
+ model.ec.rxns indices, or a (cell array of) string(s) with
+ model.ec.rxns identifiers. For light models, these reactions should
+ match model.rxns.
+
+ Returns
+ -------
+ model : struct
+ ecModel where reactions are constrained by enzyme usage if a kcat
+ value was provided for the reaction-enzyme pair in model.ec.kcat.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ model = applyKcatConstraints(model, updateRxns);
+ model = applyKcatConstraints(model, 'updateRxns', updateRxns);
+
+ See also
+ --------
+ makeEcModel, getKcatAcrossIsozymes, setKcatForReactions
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
applyCustomKcats applyCustomKcats Apply user-defined kcats to an ecModel.
+
+
+
+
+
SOURCE CODE
+
0001 function model = applyKcatConstraints(model,varargin)
+0002 % applyKcatConstraints Apply kcat-derived enzyme constraints to an ecModel.
+0003 %
+0004 % Existing enzyme constraints are first removed (unless updateRxns is
+0005 % provided), and new constraints are defined based on the content of
+0006 % model.ec.kcat. When every kcat is 0, the existing constraints of a full
+0007 % ecModel are removed and no new ones are written; a light ecModel is
+0008 % returned unchanged.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % model : struct
+0013 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % updateRxns : logical or double or cell or char or string
+0018 % if not all enzyme constraints should be updated, this can be given as
+0019 % either a logical vector of length model.ec.rxns, a vector of
+0020 % model.ec.rxns indices, or a (cell array of) string(s) with
+0021 % model.ec.rxns identifiers. For light models, these reactions should
+0022 % match model.rxns.
+0023 %
+0024 % Returns
+0025 % -------
+0026 % model : struct
+0027 % ecModel where reactions are constrained by enzyme usage if a kcat
+0028 % value was provided for the reaction-enzyme pair in model.ec.kcat.
+0029 %
+0030 % Examples
+0031 % --------
+0032 % % optional arguments may be given positionally or as name-value pairs:
+0033 % model = applyKcatConstraints(model, updateRxns);
+0034 % model = applyKcatConstraints(model, 'updateRxns', updateRxns);
+0035 %
+0036 % See also
+0037 % --------
+0038 % makeEcModel, getKcatAcrossIsozymes, setKcatForReactions
+0039
+0040 p = parseGECKOargs(varargin, { ...
+0041 'updateRxns', []});
+0042 updateRxns = p.updateRxns;
+0043
+0044 %these lines are for the updateRxns handling below only
+0045 if (model.ec.geckoLight)
+0046 rxns = model.rxns;
+0047 else
+0048 rxns = model.ec.rxns;
+0049 end
+0050
+0051 if isempty(updateRxns)
+0052 updateRxns = true(numel(rxns),1);
+0053 elseif isnumeric(updateRxns)
+0054 updateRxnsLog = false(numel(rxns),1);
+0055 updateRxnsLog(updateRxns) = true;
+0056 updateRxns = updateRxnsLog;
+0057 elseif iscellstr(updateRxns) || ischar(updateRxns) || isstring(updateRxns)
+0058 updateRxnsIds = convertCharArray(updateRxns);
+0059 updateRxns = ismember(rxns,updateRxnsIds);
+0060 end
+0061
+0062 if isempty(find(updateRxns, 1)) || isempty(updateRxns)
+0063 error('No reaction to update or updateRxns is logical but without any true value')
+0064 end
+0065
+0066 if ~isfield(model,'ec')
+0067 error(['No model.ec structure could be found: the provided model is'...
+0068 ' not a valid GECKO3 ecModel. First run makeEcModel(model).'])
+0069 end
+0070 if all(model.ec.kcat==0)
+0071 if model.ec.geckoLight
+0072 printOrange('WARNING: No kcat values are provided in model.ec.kcat, model remains unchanged.\n');
+0073 return
+0074 end
+0075 % In a full model the existing enzyme constraints of the selected
+0076 % reactions are still cleared below, and no new ones are written.
+0077 printOrange('WARNING: No kcat values are provided in model.ec.kcat, existing enzyme constraints are cleared and no new ones are written.\n');
+0078 end
+0079
+0080 %Clear existing incorporation of enzyme usage
+0081 if ~model.ec.geckoLight
+0082 protMetIdx = startsWith(model.mets,'prot_') & ~strcmp(model.mets,'prot_pool');
+0083 metabolRxn = unique(model.ec.rxns(updateRxns));
+0084 metabolRxn = ismember(model.rxns,metabolRxn);
+0085 model.S(protMetIdx,metabolRxn) = 0;
+0086 end%no clearing needed for GECKO Light, will be overwritten below
+0087 %For normal GECKO formulation (full model), where each enzyme is explicitly considered
+0088 if ~model.ec.geckoLight
+0089 %Column 1 = rxn idx; 2 = enzyme idx; 3 = subunit copies; 4 = kcat; 5 = MW
+0090 newKcats=zeros(numel(updateRxns)*10,5);
+0091 updateRxns=find(updateRxns);
+0092 kcatFirst=0;
+0093 for i=1:numel(updateRxns)
+0094 j=updateRxns(i);
+0095 if model.ec.kcat(j) ~= 0
+0096 enzymes = find(model.ec.rxnEnzMat(j,:));
+0097 kcatLast = kcatFirst+numel(enzymes);
+0098 kcatFirst = kcatFirst+1;
+0099 newKcats(kcatFirst:kcatLast,1) = j;
+0100 newKcats(kcatFirst:kcatLast,2) = enzymes;
+0101 newKcats(kcatFirst:kcatLast,3) = model.ec.rxnEnzMat(j,enzymes);
+0102 newKcats(kcatFirst:kcatLast,4) = model.ec.kcat(j);
+0103 newKcats(kcatFirst:kcatLast,5) = model.ec.mw(enzymes);
+0104 kcatFirst = kcatLast;
+0105 end
+0106 end
+0107 if exist('kcatLast','var') % If it does not, then no kcats are found
+0108 newKcats(kcatLast+1:end,:)=[];
+0109
+0110 sel = newKcats(:,4) > 0; %Only apply to non-zero kcat
+0111 newKcats(sel,4) = newKcats(sel,4) * 3600; %per second -> per hour
+0112 newKcats(sel,4) = newKcats(sel,5) ./ newKcats(sel,4); %MW/kcat
+0113 newKcats(sel,4) = newKcats(sel,3) .* newKcats(sel,4); %Multicopy subunits.
+0114 newKcats(~sel,4) = 0; %Results in zero-cost
+0115
+0116 %Replace rxns and enzymes with their location in model
+0117 [~,newKcats(:,1)] = ismember(model.ec.rxns(newKcats(:,1)),model.rxns);
+0118 [~,newKcats(:,2)] = ismember(strcat('prot_',model.ec.enzymes(newKcats(:,2))),model.mets);
+0119 linearIndices = sub2ind(size(model.S),newKcats(:,2),newKcats(:,1));
+0120 %Two enzyme rows can map to the same prot_<accession> metabolite (distinct
+0121 %genes sharing one UniProt entry), giving a repeated linear index here. A
+0122 %plain indexed assignment on a repeated index keeps only the last value
+0123 %written, silently dropping the other enzyme's contribution instead of
+0124 %adding it, so accumulate per unique index instead.
+0125 [uniqueIndices,~,linearIndicesToUnique] = unique(linearIndices);
+0126 summedValues = accumarray(linearIndicesToUnique,-newKcats(:,4));
+0127 model.S(uniqueIndices) = summedValues; %Substrate = negative
+0128 end
+0129 else%GECKO light formulation, where prot_pool represents all usages
+0130 prot_pool_idx = find(ismember(model.mets,'prot_pool'));
+0131 %first remove the prefix of all rxns
+0132 modRxns = extractAfter(model.ec.rxns,4);
+0133 % Map ec-reactions to model.rxns
+0134 [hasEc,~] = ismember(model.rxns,modRxns);
+0135 hasEc = find(hasEc & updateRxns);
+0136 [~,rxnIdx] = ismember(modRxns,model.rxns);
+0137 for i = 1:numel(hasEc)
+0138 % Get all isozymes per reaction
+0139 ecIdx = find(rxnIdx == hasEc(i));
+0140 % ecIdx = strcmp(model.rxns(hasEc(i)),modRxns);
+0141 % Multiply enzymes with their MW (they are then automatically
+0142 % summed per reaction), and divide by their kcat, to get a vector
+0143 % of MW/kcat values.
+0144 MWkcat = (model.ec.rxnEnzMat(ecIdx,:) * model.ec.mw) ./ model.ec.kcat(ecIdx);
+0145 % If kcat was zero, MWkcat is Inf (or NaN without enzymes). An
+0146 % isozyme row without any enzyme has MWkcat zero. Drop those
+0147 % isozymes *before* selecting the cheapest one: including them in
+0148 % the min() below would let an isozyme with no kcat or no enzyme
+0149 % always win over a real kcat on another isozyme of the same reaction.
+0150 % A row with a kcat that is not positive or an enzyme mass that is not
+0151 % positive has no usable cost either.
+0152 hasEnzyme = full(sum(model.ec.rxnEnzMat(ecIdx,:) ~= 0,2)) > 0;
+0153 enzMass = full(model.ec.rxnEnzMat(ecIdx,:) * model.ec.mw);
+0154 validKcat = ~isinf(MWkcat) & ~isnan(MWkcat) & hasEnzyme & ...
+0155 model.ec.kcat(ecIdx) > 0 & enzMass > 0;
+0156 if any(validKcat)
+0157 % Select the lowest MW/kcat (= most efficient), and convert to /hour
+0158 model.S(prot_pool_idx, hasEc(i)) = -min(MWkcat(validKcat)/3600);
+0159 else
+0160 % No isozyme of this reaction has a usable kcat; leave it
+0161 % unconstrained by enzyme usage.
+0162 model.S(prot_pool_idx, hasEc(i)) = 0;
+0163 end
+0164 end
+0165 end
+0166 end
findMetSmiles Query PubChem by metabolite names to obtain SMILES.
+
+
SYNOPSIS
+
function [model,noSMILES] = findMetSmiles(model, varargin)
+
+
DESCRIPTION
+
findMetSmiles Query PubChem by metabolite names to obtain SMILES.
+
+ Matches are cached in tutorials/***/data/smilesDB.tsv, which is checked
+ first on subsequent runs so repeated queries are avoided. If the model
+ already has a metSmiles field, non-empty entries are left unchanged.
+
+ Parameters
+ ----------
+ model : struct
+ a model whose metNames field is used to find the relevant SMILES.
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ verbose : logical
+ whether progress should be reported (default true).
+
+ Returns
+ -------
+ model : struct
+ model with model.metSmiles specified.
+ noSMILES : cell
+ metabolite names for which no SMILES could be found.
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [model,noSMILES] = findMetSmiles(model, varargin)
+0002 % findMetSmiles Query PubChem by metabolite names to obtain SMILES.
+0003 %
+0004 % Matches are cached in tutorials/***/data/smilesDB.tsv, which is checked
+0005 % first on subsequent runs so repeated queries are avoided. If the model
+0006 % already has a metSmiles field, non-empty entries are left unchanged.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % model : struct
+0011 % a model whose metNames field is used to find the relevant SMILES.
+0012 %
+0013 % Name-Value Arguments
+0014 % --------------------
+0015 % modelAdapter : ModelAdapter
+0016 % a loaded model adapter (default: the current default model adapter).
+0017 % verbose : logical
+0018 % whether progress should be reported (default true).
+0019 %
+0020 % Returns
+0021 % -------
+0022 % model : struct
+0023 % model with model.metSmiles specified.
+0024 % noSMILES : cell
+0025 % metabolite names for which no SMILES could be found.
+0026 %
+0027 p = parseGECKOargs(varargin, { ...
+0028 'modelAdapter', []; ...
+0029 'verbose', true});
+0030 modelAdapter = p.modelAdapter;
+0031 verbose = p.verbose;
+0032 if isempty(verbose); verbose = true; end
+0033
+0034 if isempty(modelAdapter)
+0035 modelAdapter = ModelAdapterManager.getDefault();
+0036 if isempty(modelAdapter)
+0037 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0038 end
+0039 end
+0040 params = modelAdapter.params;
+0041
+0042 [uniqueNames, ~, uniqueIdx] = unique(regexprep(model.metNames,'^prot_.*',''));
+0043 uniqueSmiles(1:numel(uniqueNames),1) = {''};
+0044 metMatch = false(length(uniqueNames),1);
+0045 metMatch(strcmp(uniqueNames,'')) = 1; % No need trying to match empty fields
+0046 if verbose; fprintf('Check for local SMILES database... '); end
+0047 smilesDBfile = (fullfile(params.path,'data','smilesDB.tsv'));
+0048 if exist(smilesDBfile,'file')==2
+0049 fID = fopen(smilesDBfile,'r');
+0050 raw = textscan(fID,'%s %s','Delimiter','\t','HeaderLines',0);
+0051 fclose(fID);
+0052 smilesDB.names = raw{1};
+0053 smilesDB.smile = raw{2};
+0054 [metMatch, metIdx] = ismember(uniqueNames,smilesDB.names);
+0055 uniqueSmiles(metMatch) = smilesDB.smile(metIdx(metMatch));
+0056 if verbose; fprintf('done.\n'); end
+0057 else
+0058 if verbose; fprintf('not found.\n'); end
+0059 end
+0060
+0061 if any(~metMatch)
+0062 PB = progressReport(numel(uniqueNames), 'Querying PubChem for SMILES by metabolite names');
+0063 webOptions = weboptions('Timeout', 30);
+0064 for i = 1:numel(uniqueNames)
+0065 PB.count;
+0066 if metMatch(i)
+0067 continue;
+0068 end
+0069 retry = 0;
+0070 while retry < 10
+0071 try
+0072 smileResult = webread(['https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/' uniqueNames{i} '/property/CanonicalSMILES/TXT'], webOptions);
+0073 %Sometimes multiple lines are given, with alternative SMILES. Only
+0074 %keep the first suggestion.
+0075 smileResult = regexp(smileResult,'(^\S*)\n','once','tokens');
+0076 uniqueSmiles{i,1} = smileResult{1,1};
+0077 retry = 15; % success: no retry
+0078 catch exception
+0079 %Sometimes the call fails, for example since the server is busy. In those cases
+0080 %we will try 10 times. Some errors however are because the metabolite
+0081 %name does no exist in the database (404) or some other error (the metabolite contains
+0082 %a slash or similar, 400 or 500). In those cases we can
+0083 %immediately give up.
+0084 if (strcmp(exception.identifier, 'MATLAB:webservices:HTTP404StatusCodeError') || ...
+0085 strcmp(exception.identifier, 'MATLAB:webservices:HTTP400StatusCodeError') || ...
+0086 strcmp(exception.identifier, 'MATLAB:webservices:HTTP500StatusCodeError'))
+0087 uniqueSmiles(i) = {''};
+0088 retry = 15;
+0089 else
+0090 retry = retry + 1;
+0091 end
+0092 end
+0093 if retry == 10
+0094 error('Cannot reach PubChem. Check your internet connection and try again.')
+0095 end
+0096 end
+0097 % Append one line each time, in case internet connection is lost
+0098 % halfway. Open & close file each time to avoid leaving the file
+0099 % open when breaking the function.
+0100 out = [uniqueNames(i), uniqueSmiles(i)];
+0101 fID = fopen(smilesDBfile,'a');
+0102 fprintf(fID,'%s\t%s\n',out{:});
+0103 fclose(fID);
+0104 end
+0105 PB.done;
+0106 if verbose
+0107 fprintf('Model-specific SMILES database stored at %s\n',smilesDBfile);
+0108 end
+0109 end
+0110 newSmiles = uniqueSmiles(uniqueIdx);
+0111 noSMILES = cellfun(@isempty,uniqueSmiles);
+0112 successRatio = 1-(numel(find(noSMILES))/numel(uniqueSmiles));
+0113 fprintf('SMILES could be found for %s%% of the unique metabolite names.\n',num2str(successRatio*100,'%.0f'))
+0114 noSMILES = uniqueNames(noSMILES);
+0115
+0116 if ~isfield(model,'metSmiles') || all(cellfun(@isempty,model.metSmiles))
+0117 model.metSmiles = newSmiles;
+0118 else
+0119 emptySmiles = cellfun(@isempty,model.metSmiles);
+0120 model.metSmiles(emptySmiles) = newSmiles(emptySmiles);
+0121 end
+0122 end
getComplexData Download curated complex stochiometries from Complex Portal.
+
+
SYNOPSIS
+
function complexInfo = getComplexData(varargin)
+
+
DESCRIPTION
+
getComplexData Download curated complex stochiometries from Complex Portal.
+
+ Downloads curated complex stochiometries from the EMBL-EBI Complex Portal
+ database. Writes data/ComplexPortal.json in the obj.params.path specified
+ in the model adapter.
+
+ Name-Value Arguments
+ --------------------
+ taxonomicID : double
+ taxonomic identifier for which complex data should be downloaded. Only
+ taxonomic identifiers allowed are those included on Complex Portal:
+ https://www.ebi.ac.uk/complexportal/complex/organisms. If empty, no
+ complex data is downloaded; if 0 (zero), complex data from all
+ organisms in Complex Portal is downloaded.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ complexInfo : struct
+ structure with data downloaded from Complex Portal (fields described
+ under Notes).
+
+ Notes
+ -----
+ The complexInfo structure has the following fields:
+
+ - complexID : id of the complex on Complex Portal.
+ - name : name of the complex on Complex Portal.
+ - species : organism containing the complex.
+ - geneName : names of the genes in the complex.
+ - protID : Uniprot IDs of the proteins in the complex.
+ - stochiometry : the complex stochiometry given in the same order as the genes and proteins in geneName and protID.
+ - defined : 0 if Complex Portal has no defined stochiometry, 1 if defined subunit stochiometry is given, 2 if complex consists of sub-complexes, whose subunit stochiometries are given.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ complexInfo = getComplexData(organism, modelAdapter);
+ complexInfo = getComplexData('taxonomicID', organism);
+
+ See also
+ --------
+ applyComplexData, makeEcModel
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
applyComplexData applyComplexData Apply complex subunit stoichiometry to an ecModel.
+
+
+
+
+
SOURCE CODE
+
0001 function complexInfo = getComplexData(varargin)
+0002 % getComplexData Download curated complex stochiometries from Complex Portal.
+0003 %
+0004 % Downloads curated complex stochiometries from the EMBL-EBI Complex Portal
+0005 % database. Writes data/ComplexPortal.json in the obj.params.path specified
+0006 % in the model adapter.
+0007 %
+0008 % Name-Value Arguments
+0009 % --------------------
+0010 % taxonomicID : double
+0011 % taxonomic identifier for which complex data should be downloaded. Only
+0012 % taxonomic identifiers allowed are those included on Complex Portal:
+0013 % https://www.ebi.ac.uk/complexportal/complex/organisms. If empty, no
+0014 % complex data is downloaded; if 0 (zero), complex data from all
+0015 % organisms in Complex Portal is downloaded.
+0016 % modelAdapter : ModelAdapter
+0017 % a loaded model adapter (default: the current default model adapter).
+0018 %
+0019 % Returns
+0020 % -------
+0021 % complexInfo : struct
+0022 % structure with data downloaded from Complex Portal (fields described
+0023 % under Notes).
+0024 %
+0025 % Notes
+0026 % -----
+0027 % The complexInfo structure has the following fields:
+0028 %
+0029 % - complexID : id of the complex on Complex Portal.
+0030 % - name : name of the complex on Complex Portal.
+0031 % - species : organism containing the complex.
+0032 % - geneName : names of the genes in the complex.
+0033 % - protID : Uniprot IDs of the proteins in the complex.
+0034 % - stochiometry : the complex stochiometry given in the same order as the genes and proteins in geneName and protID.
+0035 % - defined : 0 if Complex Portal has no defined stochiometry, 1 if defined subunit stochiometry is given, 2 if complex consists of sub-complexes, whose subunit stochiometries are given.
+0036 %
+0037 % Examples
+0038 % --------
+0039 % % optional arguments may be given positionally or as name-value pairs:
+0040 % complexInfo = getComplexData(organism, modelAdapter);
+0041 % complexInfo = getComplexData('taxonomicID', organism);
+0042 %
+0043 % See also
+0044 % --------
+0045 % applyComplexData, makeEcModel
+0046
+0047 p = parseGECKOargs(varargin, { ...
+0048 'taxonomicID', []; ...
+0049 'modelAdapter', []});
+0050 taxonomicID = p.taxonomicID;
+0051 modelAdapter = p.modelAdapter;
+0052
+0053 if isempty(modelAdapter)
+0054 modelAdapter = ModelAdapterManager.getDefault();
+0055 if isempty(modelAdapter)
+0056 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0057 end
+0058 end
+0059
+0060 if isempty(taxonomicID)
+0061 taxonomicID = modelAdapter.getParameters().complex.taxonomicID;
+0062 end
+0063
+0064 params = modelAdapter.params;
+0065 if isempty(taxonomicID) % Can be empty when gathered from model adapter
+0066 printOrange('WARNING: No taxonomicID specified.');
+0067 return
+0068 elseif taxonomicID == 0
+0069 taxonomicID = [];
+0070 end
+0071
+0072 webOptions = weboptions('Timeout', 30);
+0073 try
+0074 url1 = 'https://www.ebi.ac.uk/intact/complex-ws/search/*';
+0075 if ~isempty(taxonomicID)
+0076 url1 = [url1 '?facets=species&filters=species:("' num2str(taxonomicID) '")'];
+0077 end
+0078 data = webread(url1,webOptions);
+0079 catch ME
+0080 if (strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError'))
+0081 error('Cannot connect to the Complex Portal, perhaps the server is not responding');
+0082 end
+0083 end
+0084 if data.size == 0
+0085 error('No data could be gathered from Complex Portal for the specified taxonomicID.')
+0086 end
+0087 complexData = cell(data.size,7);
+0088
+0089 PB = progressReport(data.size, 'Retrieving information for complexes');
+0090 for i = 1:data.size
+0091 PB.count;
+0092 url2 = 'https://www.ebi.ac.uk/intact/complex-ws/complex/';
+0093 complexID = data.elements(i,1).complexAC;
+0094 try
+0095 temp = webread([url2 complexID],webOptions);
+0096 catch ME
+0097 if (strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError'))
+0098 printOrange(['WARNING: Cannot retrieve the information for ' complexID '.\n']);
+0099 end
+0100 temp = [];
+0101 end
+0102
+0103 if ~isempty(temp)
+0104 complexData(i,1) = {temp.complexAc};
+0105 complexData(i,2) = {temp.name};
+0106 complexData(i,3) = {temp.species};
+0107
+0108 idxIntType = find(strcmpi({temp.participants.interactorType}, 'protein'));
+0109
+0110 % Some complex reported are 'stable complex', then, save the id
+0111 % complex and but set the genes and protein to a empty string.
+0112 if numel(idxIntType) > 0
+0113 complexData(i,4) = {{temp.participants(idxIntType).name}};
+0114 complexData(i,5) = {{temp.participants(idxIntType).identifier}};
+0115 else
+0116 complexData(i,4) = {{temp.participants.name}};
+0117 complexData(i,5) = {{temp.participants.identifier}};
+0118 end
+0119
+0120 % Portal complex has two stochiometry values, a minimum and
+0121 % maximum value. Only minimum will be store. In some cases,
+0122 % some complex does not have stochiometry coefficient, then, it
+0123 % will be fill with zeros
+0124 if ~cellfun('isempty',{temp.participants.stochiometry})
+0125 % For some reason, if there is only one protein in the complex
+0126 % split function does nor return a cell nx2, instead is 2x1,
+0127 % then assign an incorrect stochiometry
+0128 switch numel(idxIntType)
+0129 case 0 % Contains complexes
+0130 stochiometry = split({temp.participants.stochiometry}.', ',');
+0131 complexData(i,7) = {2};
+0132 case 1 % Contains one protein
+0133 stochiometry = split({temp.participants(idxIntType).stochiometry}.', ',').';
+0134 complexData(i,7) = {1};
+0135 otherwise
+0136 stochiometry = split({temp.participants(idxIntType).stochiometry}.', ',');
+0137 complexData(i,7) = {1};
+0138 end
+0139 values = str2double(erase(stochiometry(:,1),"minValue: ")).';
+0140 complexData(i,6) = {values};
+0141 else
+0142 complexData(i,6) = {repelem(0,numel(complexData{i,4}))};
+0143 complexData(i,7) = {0};
+0144 end
+0145 end
+0146 end
+0147 fprintf('\n');
+0148
+0149 % Expand complexes of complexes
+0150 complexComplex = find([complexData{:,7}]==2);
+0151 if ~isempty(complexComplex)
+0152 for i=1:numel(complexComplex)
+0153 subComplex = complexData{complexComplex(i),5};
+0154 subComplexS = complexData{complexComplex(i),6};
+0155 subComplexIdx = find(ismember(complexData(:,1),subComplex));
+0156 allGenes = horzcat(complexData{subComplexIdx,4});
+0157 allProts = horzcat(complexData{subComplexIdx,5});
+0158 allStoch = {complexData{subComplexIdx,6}};
+0159 for j=1:numel(subComplex)
+0160 allStoch{j}=allStoch{j}*subComplexS(j);
+0161 end
+0162 allStoch = horzcat(allStoch{:});
+0163 [allGenes,ia,ic] = unique(allGenes,'stable');
+0164 allProts = allProts(ia);
+0165 allStoch = splitapply(@sum, allStoch', ic);
+0166 complexData{complexComplex(i),4} = allGenes;
+0167 complexData{complexComplex(i),5} = allProts;
+0168 complexData{complexComplex(i),6} = allStoch;
+0169 end
+0170 end
+0171
+0172 rowHeadings = {'complexID','name','species','geneName','protID','stochiometry','defined'};
+0173
+0174 complexInfo = cell2struct(complexData, rowHeadings, 2);
+0175
+0176 % Convert to a JSON file
+0177 jsontxt = jsonencode(cell2table(complexData, 'VariableNames', rowHeadings));
+0178 % Write to a JSON file
+0179 fid = fopen(fullfile(params.path,'data','ComplexPortal.json'), 'w');
+0180 fprintf(fid, '%s', jsontxt);
+0181 fclose(fid);
+0182 fprintf('Model-specific ComplexPortal database stored at %s\n',fullfile(params.path,'data','ComplexPortal.json'));
+0183 end
getKcatAcrossIsozymes Fill missing kcats from isozymes of the same reaction.
+
+
SYNOPSIS
+
function model = getKcatAcrossIsozymes(model)
+
+
DESCRIPTION
+
getKcatAcrossIsozymes Fill missing kcats from isozymes of the same reaction.
+
+ For reactions without kcat value (0 in model.ec.kcat), isozymes are found
+ (being based on the same reaction in the conventional GEM), that do have a
+ kcat value assigned. The mean kcat value of these isozymes is then used to
+ fill in model.ec.kcat. Only isozymes with a kcat above 0 enter the mean: a
+ NaN or negative kcat of an isozyme is left out, and an entry whose kcat is
+ NaN is not filled.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in full GECKO 3 format (with ecModel.ec structure), not
+ GECKO light.
+
+ Returns
+ -------
+ model : struct
+ an ecModel with kcat values assigned to isozymes in model.ec.kcat.
+
+ Examples
+ --------
+ model = getKcatAcrossIsozymes(model);
+
+ See also
+ --------
+ applyKcatConstraints, setKcatForReactions
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function model = getKcatAcrossIsozymes(model)
+0002 % getKcatAcrossIsozymes Fill missing kcats from isozymes of the same reaction.
+0003 %
+0004 % For reactions without kcat value (0 in model.ec.kcat), isozymes are found
+0005 % (being based on the same reaction in the conventional GEM), that do have a
+0006 % kcat value assigned. The mean kcat value of these isozymes is then used to
+0007 % fill in model.ec.kcat. Only isozymes with a kcat above 0 enter the mean: a
+0008 % NaN or negative kcat of an isozyme is left out, and an entry whose kcat is
+0009 % NaN is not filled.
+0010 %
+0011 % Parameters
+0012 % ----------
+0013 % model : struct
+0014 % an ecModel in full GECKO 3 format (with ecModel.ec structure), not
+0015 % GECKO light.
+0016 %
+0017 % Returns
+0018 % -------
+0019 % model : struct
+0020 % an ecModel with kcat values assigned to isozymes in model.ec.kcat.
+0021 %
+0022 % Examples
+0023 % --------
+0024 % model = getKcatAcrossIsozymes(model);
+0025 %
+0026 % See also
+0027 % --------
+0028 % applyKcatConstraints, setKcatForReactions
+0029
+0030 if model.ec.geckoLight
+0031 error('Provided model is a GECKO light version, this function is not relevant for such models')
+0032 end
+0033 if all(model.ec.kcat==0)
+0034 printOrange('WARNING: No kcat values are provided in model.ec.kcat, model remains unchanged.\n');
+0035 return
+0036 end
+0037
+0038 noKcats = model.ec.kcat==0;
+0039 hasKcat = model.ec.kcat>0; % NaN and negative kcats are not isozyme values
+0040 rxnIDs = regexprep(model.ec.rxns,'_EXP_\d+','');
+0041 noKcatID = rxnIDs(noKcats);
+0042 yesKcatID = rxnIDs(hasKcat);
+0043 yesKcatVal = model.ec.kcat(hasKcat);
+0044
+0045 noKcatVal = cellfun(@(x) strcmp(x, yesKcatID), noKcatID, 'UniformOutput', false);
+0046 noKcatVal = cell2mat(cellfun(@(x) mean(yesKcatVal(x)), noKcatVal, 'UniformOutput', false));
+0047
+0048 newKcat = find(~isnan(noKcatVal));
+0049 newKcatIdx = find(noKcats);
+0050 newKcatIdx = newKcatIdx(newKcat);
+0051 newKcat = noKcatVal(newKcat);
+0052
+0053 model.ec.kcat(newKcatIdx) = newKcat;
+0054 model.ec.source(newKcatIdx) = {'isozymes'};
+0055 end
+0056
getReactionsFromEnzyme Get all reactions annotated to a particular enzyme.
+
+
SYNOPSIS
+
function [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel,proteinId)
+
+
DESCRIPTION
+
getReactionsFromEnzyme Get all reactions annotated to a particular enzyme.
+
+ Parameters
+ ----------
+ ecModel : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ proteinId : char
+ protein identifier, matching ecModel.ec.enzymes case-insensitively.
+
+ Returns
+ -------
+ rxns : cell
+ reactions that are associated with this enzyme.
+ kcat : double
+ kcat values of the corresponding reactions.
+ idx : double
+ index of the reactions in ecModel.ec.rxns.
+ rxnNames : cell
+ names of the reactions.
+ grRules : cell
+ grRules of the reactions.
+
+ Notes
+ -----
+ An unmatched proteinId is not an error: all five outputs are returned
+ empty, with no warning. Matching against ecModel.ec.enzymes is
+ case-insensitive.
+
+ Examples
+ --------
+ [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel, proteinId);
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel,proteinId)
+0002 % getReactionsFromEnzyme Get all reactions annotated to a particular enzyme.
+0003 %
+0004 % Parameters
+0005 % ----------
+0006 % ecModel : struct
+0007 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0008 % proteinId : char
+0009 % protein identifier, matching ecModel.ec.enzymes case-insensitively.
+0010 %
+0011 % Returns
+0012 % -------
+0013 % rxns : cell
+0014 % reactions that are associated with this enzyme.
+0015 % kcat : double
+0016 % kcat values of the corresponding reactions.
+0017 % idx : double
+0018 % index of the reactions in ecModel.ec.rxns.
+0019 % rxnNames : cell
+0020 % names of the reactions.
+0021 % grRules : cell
+0022 % grRules of the reactions.
+0023 %
+0024 % Notes
+0025 % -----
+0026 % An unmatched proteinId is not an error: all five outputs are returned
+0027 % empty, with no warning. Matching against ecModel.ec.enzymes is
+0028 % case-insensitive.
+0029 %
+0030 % Examples
+0031 % --------
+0032 % [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel, proteinId);
+0033
+0034 protIdx = find(strcmpi(ecModel.ec.enzymes,proteinId));
+0035 ecRxnIdx = find(ecModel.ec.rxnEnzMat(:,protIdx));
+0036 rxns = ecModel.ec.rxns(ecRxnIdx);
+0037 kcat = ecModel.ec.kcat(ecRxnIdx);
+0038
+0039 idx = ecRxnIdx;
+0040
+0041 [~,rxnIdx] = ismember(rxns,ecModel.rxns);
+0042 rxnNames = ecModel.rxnNames(rxnIdx);
+0043 grRules = ecModel.grRules(rxnIdx);
+0044 end
getSubsetEcModel Generate a context-specific ecModel from a general one.
+
+
SYNOPSIS
+
function smallEcModel = getSubsetEcModel(bigEcModel,smallGEM)
+
+
DESCRIPTION
+
getSubsetEcModel Generate a context-specific ecModel from a general one.
+
+ Generate a context-specific ecModel (strain/cell-line/tissue) by mapping
+ reactions and genes from a conventional context-specific GEM to a general
+ ecModel (bigEcModel), to yield a context-specific ecModel.
+
+ Both models (bigEcModel and the smallGEM) should have derived from the
+ same starting model. If an ecModel with human-GEM 1.15.0 is made
+ (yielding the bigEcModel), then human-GEM 1.15.0 should also be the basis
+ from which the context-specific GEM (smallGEM) was generated by for
+ instance tINIT.
+
+ Parameters
+ ----------
+ bigEcModel : struct
+ enzyme-constrained version of conventional model of metabolism for a
+ given species.
+ smallGEM : struct
+ reduced model (subset of the general model) for a specific strain
+ (microbes) or cell-line/tissues (mammals).
+
+ Returns
+ -------
+ smallEcModel : struct
+ enzyme-constrained version of the context-specific model.
+
+ Examples
+ --------
+ smallEcModel = getSubsetEcModel(bigEcModel,smallGEM);
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function smallEcModel = getSubsetEcModel(bigEcModel,smallGEM)
+0002 % getSubsetEcModel Generate a context-specific ecModel from a general one.
+0003 %
+0004 % Generate a context-specific ecModel (strain/cell-line/tissue) by mapping
+0005 % reactions and genes from a conventional context-specific GEM to a general
+0006 % ecModel (bigEcModel), to yield a context-specific ecModel.
+0007 %
+0008 % Both models (bigEcModel and the smallGEM) should have derived from the
+0009 % same starting model. If an ecModel with human-GEM 1.15.0 is made
+0010 % (yielding the bigEcModel), then human-GEM 1.15.0 should also be the basis
+0011 % from which the context-specific GEM (smallGEM) was generated by for
+0012 % instance tINIT.
+0013 %
+0014 % Parameters
+0015 % ----------
+0016 % bigEcModel : struct
+0017 % enzyme-constrained version of conventional model of metabolism for a
+0018 % given species.
+0019 % smallGEM : struct
+0020 % reduced model (subset of the general model) for a specific strain
+0021 % (microbes) or cell-line/tissues (mammals).
+0022 %
+0023 % Returns
+0024 % -------
+0025 % smallEcModel : struct
+0026 % enzyme-constrained version of the context-specific model.
+0027 %
+0028 % Examples
+0029 % --------
+0030 % smallEcModel = getSubsetEcModel(bigEcModel,smallGEM);
+0031
+0032 % Check if models were derived from the same starting GEM
+0033 Rxn_format = regexprep(bigEcModel.rxns,'_REV|_EXP_\d+','');
+0034 rxnsDiff = find(~ismember(smallGEM.rxns,Rxn_format));
+0035 if numel(rxnsDiff) > 0
+0036 error('%s', ravenList(['While both models should have derived from the same starting ',...
+0037 'GEM, the following reactions from smallGEM could not be found ',...
+0038 'in bigEcModel:'],smallGEM.rxns(rxnsDiff),false));
+0039 end
+0040
+0041 % Check if original bigEcModel contains context-dependent protein constraints
+0042 if any(bigEcModel.ub(startsWith(bigEcModel.rxns,'usage_prot_')) ~= 1000)
+0043 printOrange(['WARNING: The bigEcModel is constraint by protein concentrations that are\n'...
+0044 'likely not relevant in the constructed smallEcModel.\n']);
+0045 end
+0046
+0047 % Remove genes (and associated reactions) that are absent in smallGEM
+0048 genesToRemove = setdiff(bigEcModel.genes,smallGEM.genes);
+0049 % If genesToRemove contains "standard", then remove this entry
+0050 if ismember('standard', genesToRemove)
+0051 genesToRemove(strcmp(genesToRemove, 'standard')) = [];
+0052 end
+0053 smallEcModel = removeGenes(bigEcModel,genesToRemove,true,true,false);
+0054
+0055 % Remove genes from ec-structure
+0056 enzToRemove = ismember(smallEcModel.ec.genes,genesToRemove);
+0057 smallEcModel.ec.genes(enzToRemove) = [];
+0058 smallEcModel.ec.enzymes(enzToRemove) = [];
+0059 smallEcModel.ec.concs(enzToRemove) = [];
+0060 smallEcModel.ec.mw(enzToRemove) = [];
+0061 smallEcModel.ec.sequence(enzToRemove) = [];
+0062 smallEcModel.ec.rxnEnzMat(:,enzToRemove) = [];
+0063
+0064 % Remove any reaction (except prot_ associated) that is absent from smallGEM
+0065 trimRxns = regexprep(smallEcModel.rxns,'_REV|_EXP_\d+','');
+0066 protRxns = startsWith(smallEcModel.rxns,{'usage_prot_','prot_pool_exchange'});
+0067 keepRxns = ismember(trimRxns,smallGEM.rxns) | protRxns;
+0068 smallEcModel = removeReactions(smallEcModel,~keepRxns, true, true, true);
+0069
+0070 % Remove reactions from ec-structure
+0071 ecRxnsToRemove = ~ismember(smallEcModel.ec.rxns,smallEcModel.rxns);
+0072
+0073 smallEcModel.ec.rxns(ecRxnsToRemove) = [];
+0074 smallEcModel.ec.kcat(ecRxnsToRemove) = [];
+0075 smallEcModel.ec.source(ecRxnsToRemove) = [];
+0076 smallEcModel.ec.notes(ecRxnsToRemove) = [];
+0077 smallEcModel.ec.eccodes(ecRxnsToRemove) = [];
+0078 smallEcModel.ec.rxnEnzMat(ecRxnsToRemove,:) = [];
+0079 end
+0080
makeEcModel Expand a conventional GEM into a basic ecModel.
+
+
SYNOPSIS
+
function [model, noUniprot] = makeEcModel(model, varargin)
+
+
DESCRIPTION
+
makeEcModel Expand a conventional GEM into a basic ecModel.
+
+ Expands a conventional genome-scale model (in RAVEN format) with enzyme
+ information and prepares the reactions for integration of enzyme usage
+ coefficients. This function contains all the steps that need to be done to
+ get a basic ecModel, without incorporating any kcat values or constraints
+ yet. This function should only have to be run once for a model.
+
+ Parameters
+ ----------
+ model : struct
+ a model in RAVEN format.
+
+ Name-Value Arguments
+ --------------------
+ geckoLight : logical
+ true if a simplified GECKO light model should be generated (default
+ false).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ an ecModel in GECKO 3 format, with a model.ec structure where enzyme
+ and kcat information are stored. Protein pseudometabolites and their
+ draw reactions are added to the model, but their usage is not yet
+ implemented (due to absent kcat values at this stage).
+ noUniprot : cell
+ genes for which no information could be found in either the Uniprot
+ or, as fallback, the KEGG database.
+
+ Notes
+ -----
+ The function goes through the following steps:
+
+ 1. Remove gene associations from pseudoreactions.
+ 2. Invert irreversible backwards reactions.
+ 3. Correct 'rev' vector to match lb and ub vectors.
+ 4. Convert to irreversible model (splits reversible reactions).
+ 5. [Skipped with geckoLight:] Expand model to split reactions with 'OR'
+ in grRules (each reaction is then catalyzed by one enzyme (complex)).
+ 6. Make empty model.ec structure, that will contain enzyme and kcat
+ information. One entry per reaction, where isozymes have multiple
+ entries. This model.ec structure will later be populated with kcat
+ values. For geckoLight the structure is different, where each reaction
+ can have multiple isozymes.
+ 7. Add enzyme information fields to model.ec structure: MW, sequence.
+ Data is gathered from UniProt, with KEGG used as a fallback for genes
+ that UniProt does not match.
+ 8. Populate model.ec structure with information from each reaction.
+ 9. [Skipped with geckoLight:] Add proteins as pseudometabolites.
+ 10. Add prot_pool pseudometabolite.
+ 11. [Skipped with geckoLight:] Add usage reactions for the protein
+ pseudometabolites, replenishing from the protein pool (default, can be
+ changed to consider proteomics data at later stage).
+ 12. Add protein pool reaction, without upper bound.
+
+ Note that while protein pseudometabolites, draw & pool reactions might be
+ added to the model, the enzyme usage is not yet incorporated in each
+ metabolic reaction, so enzymes will not be used. applyKcatConstraints
+ incorporates protein pseudometabolites in reactions as enzyme usages by
+ applying the specified kcats as constraints.
+
+ The model.ec structure has the following fields:
+
+ - geckoLight : 0 if full model, 1 if light.
+ - rxns : reaction identifiers that correspond to model.rxns.
+ - kcat : kcat values - not set here.
+ - source : specifies where the kcats come from - not set here.
+ - notes : notes that can be set by downstream functions - not set here.
+ - eccodes : enzyme codes for each enzyme - not set here.
+ - genes : the genes involved in the kcats - not necessarily the same as model.genes, since some genes may not be found in databases etc.
+ - enzymes : Uniprot protein identifiers for the genes; for a gene that
+ was only matched via the KEGG fallback and has no UniProt accession on
+ its KEGG entry, the bare KEGG gene identifier is used instead.
+ - mw : molecular weights of the enzymes.
+ - sequence : sequence of the genes/enzymes.
+ - concs : concentrations of the enzymes - not set here.
+ - rxnEnzMat : matrix of enzymes and rxns.
+
+ The full model is split on all ORs in the GPRs, meaning that the reactions
+ will be duplicated for each isozyme. Only the rxns with genes are added.
+ The fields rxns, eccodes, kcat, source and notes will therefore have one
+ entry per reaction. The fields genes, enzymes, mw, sequence and concs will
+ have one entry per gene. The rxnEnzMat is a matrix with reactions and
+ genes, mapping which genes are connected to which reaction (where isozymes
+ have different reactions).
+
+ The light model works a bit differently. The model has the same number of
+ rxns as the original model, but expanded since it is reversible + one the
+ extra prot maintenance rxn and one extra prot_pool rxn. However, the ec
+ fields rxns, eccodes, kcat, source and notes are duplicated for each
+ isozyme, sorted the same way as model.rxns. So, in model.ec.rxns, the same
+ reaction will appear several times after one another, one entry per
+ izozyme, with corresponding values for that isozyme. These fields
+ therefore have the same length as for the full model. The fields genes,
+ enzymes, mw, sequence and concs are the same here as in the full model.
+ The rxnEnzMat maps the model.ec.rxns entries to genes and is therefore of
+ the same size as for the full model.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, noUniprot] = makeEcModel(model);
+ [model, noUniprot] = makeEcModel(model, 'geckoLight', true);
+
+ See also
+ --------
+ applyKcatConstraints, applyComplexData
0001 function [model, noUniprot] = makeEcModel(model, varargin)
+0002 % makeEcModel Expand a conventional GEM into a basic ecModel.
+0003 %
+0004 % Expands a conventional genome-scale model (in RAVEN format) with enzyme
+0005 % information and prepares the reactions for integration of enzyme usage
+0006 % coefficients. This function contains all the steps that need to be done to
+0007 % get a basic ecModel, without incorporating any kcat values or constraints
+0008 % yet. This function should only have to be run once for a model.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % model : struct
+0013 % a model in RAVEN format.
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % geckoLight : logical
+0018 % true if a simplified GECKO light model should be generated (default
+0019 % false).
+0020 % modelAdapter : ModelAdapter
+0021 % a loaded model adapter (default: the current default model adapter).
+0022 %
+0023 % Returns
+0024 % -------
+0025 % model : struct
+0026 % an ecModel in GECKO 3 format, with a model.ec structure where enzyme
+0027 % and kcat information are stored. Protein pseudometabolites and their
+0028 % draw reactions are added to the model, but their usage is not yet
+0029 % implemented (due to absent kcat values at this stage).
+0030 % noUniprot : cell
+0031 % genes for which no information could be found in either the Uniprot
+0032 % or, as fallback, the KEGG database.
+0033 %
+0034 % Notes
+0035 % -----
+0036 % The function goes through the following steps:
+0037 %
+0038 % 1. Remove gene associations from pseudoreactions.
+0039 % 2. Invert irreversible backwards reactions.
+0040 % 3. Correct 'rev' vector to match lb and ub vectors.
+0041 % 4. Convert to irreversible model (splits reversible reactions).
+0042 % 5. [Skipped with geckoLight:] Expand model to split reactions with 'OR'
+0043 % in grRules (each reaction is then catalyzed by one enzyme (complex)).
+0044 % 6. Make empty model.ec structure, that will contain enzyme and kcat
+0045 % information. One entry per reaction, where isozymes have multiple
+0046 % entries. This model.ec structure will later be populated with kcat
+0047 % values. For geckoLight the structure is different, where each reaction
+0048 % can have multiple isozymes.
+0049 % 7. Add enzyme information fields to model.ec structure: MW, sequence.
+0050 % Data is gathered from UniProt, with KEGG used as a fallback for genes
+0051 % that UniProt does not match.
+0052 % 8. Populate model.ec structure with information from each reaction.
+0053 % 9. [Skipped with geckoLight:] Add proteins as pseudometabolites.
+0054 % 10. Add prot_pool pseudometabolite.
+0055 % 11. [Skipped with geckoLight:] Add usage reactions for the protein
+0056 % pseudometabolites, replenishing from the protein pool (default, can be
+0057 % changed to consider proteomics data at later stage).
+0058 % 12. Add protein pool reaction, without upper bound.
+0059 %
+0060 % Note that while protein pseudometabolites, draw & pool reactions might be
+0061 % added to the model, the enzyme usage is not yet incorporated in each
+0062 % metabolic reaction, so enzymes will not be used. applyKcatConstraints
+0063 % incorporates protein pseudometabolites in reactions as enzyme usages by
+0064 % applying the specified kcats as constraints.
+0065 %
+0066 % The model.ec structure has the following fields:
+0067 %
+0068 % - geckoLight : 0 if full model, 1 if light.
+0069 % - rxns : reaction identifiers that correspond to model.rxns.
+0070 % - kcat : kcat values - not set here.
+0071 % - source : specifies where the kcats come from - not set here.
+0072 % - notes : notes that can be set by downstream functions - not set here.
+0073 % - eccodes : enzyme codes for each enzyme - not set here.
+0074 % - genes : the genes involved in the kcats - not necessarily the same as model.genes, since some genes may not be found in databases etc.
+0075 % - enzymes : Uniprot protein identifiers for the genes; for a gene that
+0076 % was only matched via the KEGG fallback and has no UniProt accession on
+0077 % its KEGG entry, the bare KEGG gene identifier is used instead.
+0078 % - mw : molecular weights of the enzymes.
+0079 % - sequence : sequence of the genes/enzymes.
+0080 % - concs : concentrations of the enzymes - not set here.
+0081 % - rxnEnzMat : matrix of enzymes and rxns.
+0082 %
+0083 % The full model is split on all ORs in the GPRs, meaning that the reactions
+0084 % will be duplicated for each isozyme. Only the rxns with genes are added.
+0085 % The fields rxns, eccodes, kcat, source and notes will therefore have one
+0086 % entry per reaction. The fields genes, enzymes, mw, sequence and concs will
+0087 % have one entry per gene. The rxnEnzMat is a matrix with reactions and
+0088 % genes, mapping which genes are connected to which reaction (where isozymes
+0089 % have different reactions).
+0090 %
+0091 % The light model works a bit differently. The model has the same number of
+0092 % rxns as the original model, but expanded since it is reversible + one the
+0093 % extra prot maintenance rxn and one extra prot_pool rxn. However, the ec
+0094 % fields rxns, eccodes, kcat, source and notes are duplicated for each
+0095 % isozyme, sorted the same way as model.rxns. So, in model.ec.rxns, the same
+0096 % reaction will appear several times after one another, one entry per
+0097 % izozyme, with corresponding values for that isozyme. These fields
+0098 % therefore have the same length as for the full model. The fields genes,
+0099 % enzymes, mw, sequence and concs are the same here as in the full model.
+0100 % The rxnEnzMat maps the model.ec.rxns entries to genes and is therefore of
+0101 % the same size as for the full model.
+0102 %
+0103 % Examples
+0104 % --------
+0105 % % optional arguments may be given positionally or as name-value pairs:
+0106 % [model, noUniprot] = makeEcModel(model);
+0107 % [model, noUniprot] = makeEcModel(model, 'geckoLight', true);
+0108 %
+0109 % See also
+0110 % --------
+0111 % applyKcatConstraints, applyComplexData
+0112
+0113 p = parseGECKOargs(varargin, { ...
+0114 'geckoLight', false; ...
+0115 'modelAdapter', []});
+0116 geckoLight = p.geckoLight;
+0117 modelAdapter = p.modelAdapter;
+0118
+0119 if ~islogical(geckoLight) && ~(geckoLight == 0) && ~(geckoLight == 1)
+0120 error('geckoLight should be either true or false')
+0121 end
+0122
+0123 if isempty(modelAdapter)
+0124 modelAdapter = ModelAdapterManager.getDefault();
+0125 if isempty(modelAdapter)
+0126 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0127 end
+0128 end
+0129 params = modelAdapter.getParameters();
+0130 compartmentID = strcmp(model.compNames,params.enzyme_comp);
+0131 if ~any(compartmentID)
+0132 error(['Compartment ' params.enzyme_comp ' (specified in params.enzyme_comp) '...
+0133 'cannot be found in model.compNames'])
+0134 end
+0135 compartmentID = model.comps(compartmentID);
+0136
+0137 if geckoLight
+0138 ec.geckoLight=true;
+0139 else
+0140 ec.geckoLight=false;
+0141 end
+0142
+0143 %Check if model is in RAVEN format
+0144 if any(isfield(model,{'rules','modelID'}))
+0145 error(['The model is likely loaded using COBRA Toolbox readCbModel(). Instead, use '...
+0146 'RAVEN Toolbox importModel(). Alternatively, you can also convert the ', ...
+0147 'model in MATLAB using ravenCobraWrapper().'])
+0148 end
+0149
+0150 %Check for conflicting reaction and metabolite identifiers
+0151 conflictId = startsWith(model.mets,'prot_');
+0152 if any(conflictId)
+0153 error('The identifiers in model.mets are not allowed to start with ''prot_''.')
+0154 end
+0155 conflictId = startsWith(model.rxns,{'usage_prot_','prot_pool'}) | ...
+0156 endsWith(model.rxns,'_REV') | ...
+0157 ~cellfun(@isempty,regexp(model.rxns,'_EXP_\d+$'));
+0158 if any(conflictId)
+0159 error(['The identifiers in model.rxns are not allowed to start with ''usage_prot'' '...
+0160 'or ''prot_pool'', or end with ''_REV'' or ''_EXP_[digit]''.'])
+0161 end
+0162
+0163 databases = loadDatabases('both', modelAdapter);
+0164 uniprotDB = databases.uniprot;
+0165 keggDB = databases.kegg;
+0166
+0167 %1: Remove gene rules from pseudoreactions (if any):
+0168 if exist(fullfile(params.path,'data','pseudoRxns.tsv'),'file')
+0169 fID = fopen(fullfile(params.path,'data','pseudoRxns.tsv'));
+0170 fileData = textscan(fID,'%s %s','delimiter','\t');
+0171 fclose(fID);
+0172 pseudoRxns = fileData{1};
+0173 pseudoRxns = ismember(model.rxns,pseudoRxns);
+0174 else
+0175 pseudoRxns = false(numel(model.rxns),1);
+0176 end
+0177 pseudoRxns = find(pseudoRxns | contains(model.rxnNames,'pseudoreaction'));
+0178 model.grRules(pseudoRxns) = {''};
+0179 model.rxnGeneMat(pseudoRxns,:) = zeros(numel(pseudoRxns), numel(model.genes));
+0180
+0181 %2: Swap direction of reactions that are defined to only carry negative flux
+0182 to_swap=model.lb < 0 & model.ub == 0;
+0183 model.S(:,to_swap)=-model.S(:,to_swap);
+0184 model.ub(to_swap)=-model.lb(to_swap);
+0185 model.lb(to_swap)=0;
+0186
+0187 %3: Correct rev vector: true if LB < 0 & UB > 0, or it is an exchange reaction:
+0188 model.rev = false(size(model.rxns));
+0189 for i = 1:length(model.rxns)
+0190 if (model.lb(i) < 0 && model.ub(i) > 0) || sum(model.S(:,i) ~= 0) == 1
+0191 model.rev(i) = true;
+0192 end
+0193 end
+0194
+0195 %4: Make irreversible model (appends _REV to reaction IDs to indicate reverse
+0196 %reactions)
+0197 [~,exchRxns] = getExchangeRxns(model);
+0198 nonExchRxns = model.rxns;
+0199 nonExchRxns(exchRxns) = [];
+0200 model=convertToIrrev(model, nonExchRxns);
+0201
+0202 %5: Expand model, to separate isozymes (appends _EXP_* to reaction IDs to
+0203 %indicate duplication)
+0204 if ~geckoLight
+0205 model=expandModel(model);
+0206 end
+0207 % Sort reactions, so that reversible and isozymic reactions are kept near
+0208 if ~geckoLight
+0209 model=sortIdentifiers(model);
+0210 end
+0211
+0212 %6: Make ec-extension structure, one for gene-associated reaction.
+0213 % The structure is different for light and full models
+0214 rxnWithGene = find(sum(model.rxnGeneMat,2));
+0215 if ~geckoLight
+0216 ec.rxns = model.rxns(rxnWithGene);
+0217 emptyCell = cell(numel(rxnWithGene),1);
+0218 emptyCell(:) = {''};
+0219 emptyVect = zeros(numel(rxnWithGene),1);
+0220
+0221 ec.kcat = emptyVect;
+0222 ec.source = emptyCell; % Strings, like 'dlkcat', 'manual', 'brenda', etc.
+0223 ec.notes = emptyCell; % Additional comments
+0224 ec.eccodes = emptyCell;
+0225 ec.concs = emptyVect;
+0226 else
+0227 %Different strategy for GECKO light: Each reaction can exist multiple times in
+0228 %ec.rxns and similar fields - one time per isozyme. The number of copies is
+0229 %the number of AND-clauses of the GPR in disjunctive normal form, so
+0230 %'G1 and (G2 or G3)' gives the isozymes {G1,G2} and {G1,G3}.
+0231 isozymes = cellfun(@gprToDnf, model.grRules(rxnWithGene), 'UniformOutput', false);
+0232 % A reaction with gene entries but an empty rule keeps one isozyme without enzymes
+0233 isozymes(cellfun(@isempty,isozymes)) = {{cell(1,0)}};
+0234 cpys = cellfun(@numel, isozymes);
+0235 prevNumRxns = numel(isozymes);
+0236 cpyIndices = repelem(rxnWithGene, cpys).'; %.' only matter when number of rxns is 1
+0237 %loop through and add a prefix with an isozyme index to the rxns
+0238 %we just give a fixed-length number as prefix, and assume that 999 is enough
+0239 tmpRxns = model.rxns(cpyIndices); %now they have no prefix
+0240 newRxns = tmpRxns;
+0241
+0242 %add the prefix
+0243 nextIndex = 1;
+0244 for i = 1:numel(model.rxns)
+0245 localRxnIndex = 1;
+0246 if nextIndex <= length(tmpRxns) && strcmp(model.rxns(i), tmpRxns(nextIndex))
+0247 while true
+0248 tmp = compose('%03d_',localRxnIndex);
+0249 newRxns{nextIndex} = [tmp{1} tmpRxns{nextIndex}];
+0250 localRxnIndex = localRxnIndex + 1;
+0251 if (localRxnIndex >= 1000)
+0252 error('Increase index size to 10000 - error in the code.'); %this should never happen, we don't have > 999 isozymes
+0253 end
+0254 nextIndex = nextIndex + 1;
+0255 if nextIndex > length(tmpRxns) || ~strcmp(model.rxns(i), tmpRxns(nextIndex))
+0256 break;
+0257 end
+0258 end
+0259 end
+0260 end
+0261
+0262 ec.rxns = newRxns;
+0263
+0264 emptyCell = cell(numel(ec.rxns),1);
+0265 emptyCell(:) = {''};
+0266 emptyVect = zeros(numel(ec.rxns),1);
+0267
+0268 ec.kcat = emptyVect;
+0269 ec.source = emptyCell; % Strings, like 'dlkcat', 'manual', 'brenda', etc.
+0270 ec.notes = emptyCell; % Additional comments
+0271 ec.eccodes = emptyCell;
+0272 ec.concs = emptyVect;
+0273 end
+0274
+0275 %7: Gather enzyme information via UniprotDB (with optional KEGG fallback)
+0276 uniprotCompatibleGenes = modelAdapter.getUniprotCompatibleGenes(model.genes);
+0277 [Lia,Locb] = ismember(uniprotCompatibleGenes,uniprotDB.genes);
+0278
+0279 uniprot = modelAdapter.getUniprotIDsFromTable(uniprotCompatibleGenes);
+0280 if ~isequal(uniprot,uniprotCompatibleGenes)
+0281 uniprot(cellfun(@isempty,uniprot)) = {''};
+0282 [Lia,Locb] = ismember(uniprot,uniprotDB.ID);
+0283 end
+0284
+0285 %KEGG fallback for genes UniProt could not match.
+0286 keggMatched = false(numel(model.genes),1);
+0287 keggIdx = zeros(numel(model.genes),1);
+0288 if ~isempty(keggDB) && any(~Lia)
+0289 notLia = find(~Lia);
+0290 keggCompatibleGenes = modelAdapter.getKeggCompatibleGenes(model.genes(notLia));
+0291 [keggHit, kLocb] = ismember(keggCompatibleGenes,keggDB.genes);
+0292 keggMatched(notLia(keggHit)) = true;
+0293 keggIdx(notLia(keggHit)) = kLocb(keggHit);
+0294 end
+0295
+0296 %noUniprot is now: gene unmatched in both UniProt and KEGG.
+0297 noUniprot = uniprotCompatibleGenes(~Lia & ~keggMatched);
+0298 if all(~Lia & ~keggMatched)
+0299 error('None of the proteins in uniprot.tsv or kegg.tsv match the genes in the model. Changes to the obj.params.uniprot / obj.params.kegg parameters, or a data/uniprotConversion.tsv file are likely required.')
+0300 elseif ~isempty(noUniprot)
+0301 printOrange(['WARNING: The ' num2str(numel(noUniprot)) ' gene(s) reported '...
+0302 'in "noUniprot" cannot be found in data/uniprot.tsv or data/kegg.tsv, these will '...
+0303 'not be enzyme-constrained. If you intend to use different Uniprot '...
+0304 'data (e.g. from a different proteome, make sure you first delete '...
+0305 'the existing data/uniprot.tsv file.\n']);
+0306 end
+0307
+0308 %Build the per-gene enzyme/MW/sequence arrays in two passes: (a)
+0309 %UniProt-matched, (b) KEGG-fallback-matched. Then concatenate and sort
+0310 %alphabetically so ec.genes has a stable order across runs.
+0311 ecGenesUni = model.genes(Lia); %Will often be duplicate of model.genes, but is done here to prevent issues when it is not.
+0312 LocbUni = Locb(Lia);
+0313 enzUni = uniprotDB.ID(LocbUni);
+0314 mwUni = uniprotDB.MW(LocbUni);
+0315 seqUni = uniprotDB.seq(LocbUni);
+0316
+0317 ecGenesKegg = model.genes(keggMatched);
+0318 LocbKegg = keggIdx(keggMatched);
+0319 if isempty(LocbKegg)
+0320 enzKegg = {};
+0321 mwKegg = [];
+0322 seqKegg = {};
+0323 else
+0324 %ec.enzymes gets the UniProt accession stored on the KEGG row; if
+0325 %that is empty, fall back to the bare KEGG gene id.
+0326 enzKegg = keggDB.uniprot(LocbKegg);
+0327 keggFallback = cellfun(@isempty,enzKegg);
+0328 enzKegg(keggFallback) = keggDB.keggGene(LocbKegg(keggFallback));
+0329 mwKegg = keggDB.MW(LocbKegg);
+0330 seqKegg = keggDB.seq(LocbKegg);
+0331 if any(keggFallback)
+0332 fallbackGenes = ecGenesKegg(keggFallback);
+0333 fallbackIDs = enzKegg(keggFallback);
+0334 previewN = min(numel(fallbackGenes),10);
+0335 previewParts = cell(1,previewN);
+0336 for ii = 1:previewN
+0337 previewParts{ii} = [fallbackGenes{ii} '->' fallbackIDs{ii}];
+0338 end
+0339 more = '';
+0340 if numel(fallbackGenes) > 10
+0341 more = [' (and ' num2str(numel(fallbackGenes)-10) ' more)'];
+0342 end
+0343 printOrange(['WARNING: ' num2str(numel(fallbackGenes)) ...
+0344 ' KEGG-resolved gene(s) had no UniProt accession on the KEGG '...
+0345 'row; the bare KEGG gene id is used in ec.enzymes instead: '...
+0346 strjoin(previewParts,', ') more '\n']);
+0347 end
+0348 end
+0349
+0350 allGenes = [ecGenesUni; ecGenesKegg];
+0351 allEnzymes = [enzUni; enzKegg];
+0352 allMW = [mwUni; mwKegg];
+0353 allSeq = [seqUni; seqKegg];
+0354
+0355 [ec.genes,sIdx] = sort(allGenes);
+0356 ec.enzymes = allEnzymes(sIdx);
+0357 ec.mw = allMW(sIdx);
+0358 ec.sequence = allSeq(sIdx);
+0359 %Additional info
+0360 ec.concs = nan(numel(ec.genes),1); % To be filled with proteomics data when available
+0361
+0362 %8: Only parse rxns associated to genes
+0363 if ~geckoLight
+0364 ec.rxnEnzMat = zeros(numel(rxnWithGene),numel(ec.genes)); % Non-zeros will indicate the number of subunits
+0365 for r=1:numel(rxnWithGene)
+0366 rxnGenes = model.genes(find(model.rxnGeneMat(rxnWithGene(r),:)));
+0367 [~,locEnz] = ismember(rxnGenes,ec.genes); % Could also parse directly from rxnGeneMat, but some genes might be missing from Uniprot DB
+0368 locEnz = locEnz(locEnz ~= 0); % Subunits missing from the databases are left out; the others still couple the reaction
+0369 ec.rxnEnzMat(r,locEnz) = 1; %Assume 1 copy per subunit or enzyme, can be modified later
+0370 end
+0371 else
+0372 %For light models, we need to split up all GPRs
+0373 ec.rxnEnzMat = zeros(numel(ec.rxns),numel(ec.genes)); % Non-zeros will indicate the number of subunits
+0374 nextIndex = 1;
+0375 for i=1:prevNumRxns
+0376 %Each isozyme is one AND-clause of the GPR and gets its own row of
+0377 %ec.rxnEnzMat, in the order of the copies of the reaction in ec.rxns.
+0378 for j = 1:numel(isozymes{i})
+0379 ec.rxnEnzMat(nextIndex,ismember(ec.genes,isozymes{i}{j})) = 1;%should be subunit stoichoimetry
+0380 nextIndex = nextIndex + 1;
+0381 end
+0382 end
+0383 end
+0384
+0385 %9: Add proteins as pseudometabolites
+0386 if ~geckoLight
+0387 [proteinMets.mets, uniprotSortId] = unique(ec.enzymes);
+0388 proteinMets.mets = strcat('prot_',proteinMets.mets);
+0389 proteinMets.metNames = proteinMets.mets;
+0390 proteinMets.compartments = compartmentID;
+0391 if isfield(model,'metMiriams')
+0392 proteinMets.metMiriams = repmat({struct('name',{{'sbo'}},'value',{{'SBO:0000252'}})},numel(proteinMets.mets),1);
+0393 end
+0394 if isfield(model,'metCharges')
+0395 proteinMets.metCharges = zeros(numel(proteinMets.mets),1);
+0396 end
+0397 proteinMets.metNotes = repmat({'Enzyme-usage pseudometabolite'},numel(proteinMets.mets),1);
+0398 model = addMets(model,proteinMets);
+0399 end
+0400
+0401 %10: Add protein pool pseudometabolite
+0402 pool.mets = 'prot_pool';
+0403 pool.metNames = pool.mets;
+0404 pool.compartments = compartmentID;
+0405 pool.metNotes = 'Enzyme-usage protein pool';
+0406 model = addMets(model,pool);
+0407
+0408 %11: Add protein usage reactions.
+0409 % Match the model's own subSystems convention (a cell array of chars, or a
+0410 % cell array of cell arrays of chars -- RAVEN's checkModelStruct requires
+0411 % the whole field to be consistently one or the other) rather than always
+0412 % assuming one shape: mixing the two here is what left new ecModels unable
+0413 % to be saved once RAVEN's own reconciliation in addRxns stopped papering
+0414 % over the mismatch.
+0415 if isfield(model,'subSystems') && ~isempty(model.subSystems) && ...
+0416 ~any(cellfun(@iscell,model.subSystems))
+0417 protUsageSubSystem = {'Protein usage'};
+0418 else
+0419 protUsageSubSystem = {{'Protein usage'}};
+0420 end
+0421 if ~geckoLight
+0422 rxnNum = numel(proteinMets.mets);
+0423 usageRxns.rxns = strcat('usage_',proteinMets.mets);
+0424 usageRxns.rxnNames = usageRxns.rxns;
+0425 usageRxns.mets = cell(rxnNum,1);
+0426 usageRxns.stoichCoeffs = cell(rxnNum,1);
+0427 for i=1:numel(usageRxns.mets)
+0428 usageRxns.mets{i} = {proteinMets.mets{i}, 'prot_pool'};
+0429 usageRxns.stoichCoeffs{i} = [1,-1];
+0430 end
+0431 usageRxns.lb = zeros(rxnNum,1);
+0432 usageRxns.ub = zeros(rxnNum,1) + 1000;
+0433 usageRxns.rev = ones(rxnNum,1);
+0434 usageRxns.grRules = ec.genes(uniprotSortId);
+0435 if isfield(model,'subSystems')
+0436 usageRxns.subSystems = repmat(protUsageSubSystem, rxnNum, 1);
+0437 end
+0438 model = addRxns(model,usageRxns);
+0439 end
+0440
+0441 %12: Add protein pool reaction (forward: -> prot_pool; UB set by setProtPoolSize)
+0442 poolRxn.rxns = 'prot_pool_exchange';
+0443 poolRxn.rxnNames = poolRxn.rxns;
+0444 poolRxn.mets = {'prot_pool'};
+0445 poolRxn.stoichCoeffs = {1};
+0446 poolRxn.lb = 0;
+0447 poolRxn.ub = 1000;
+0448 poolRxn.rev = 1;
+0449 if isfield(model,'subSystems')
+0450 poolRxn.subSystems = protUsageSubSystem;
+0451 end
+0452 model = addRxns(model,poolRxn);
+0453
+0454 model.ec=ec;
+0455 end
+0456
+0457 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0458 %Function that expands a gene rule into disjunctive normal form: a cell array
+0459 %with one cell per isozyme, each holding the gene ids that are ANDed in it.
+0460 %Gene ids are whole words, so an id that contains 'or' or 'and' is kept
+0461 %intact. An empty rule gives an empty cell array.
+0462 function dnf = gprToDnf(rule)
+0463 tokens = regexp(rule,'\(|\)|[^\s()]+','match');
+0464 isOperator = strcmpi(tokens,'and') | strcmpi(tokens,'or');
+0465 tokens(isOperator) = lower(tokens(isOperator)); % Operators are case insensitive
+0466 if isempty(tokens)
+0467 dnf = {};
+0468 return
+0469 end
+0470 [dnf,pos] = parseOr(tokens,1,rule);
+0471 if pos <= numel(tokens)
+0472 error('Cannot parse the gene rule "%s"',rule)
+0473 end
+0474 end
+0475
+0476 function [dnf,pos] = parseOr(tokens,pos,rule)
+0477 [dnf,pos] = parseAnd(tokens,pos,rule);
+0478 while pos <= numel(tokens) && strcmp(tokens{pos},'or')
+0479 [rhs,pos] = parseAnd(tokens,pos+1,rule);
+0480 dnf = [dnf,rhs];
+0481 end
+0482 end
+0483
+0484 function [dnf,pos] = parseAnd(tokens,pos,rule)
+0485 [dnf,pos] = parseAtom(tokens,pos,rule);
+0486 while pos <= numel(tokens) && strcmp(tokens{pos},'and')
+0487 [rhs,pos] = parseAtom(tokens,pos+1,rule);
+0488 product = cell(1,0);
+0489 for a = 1:numel(dnf)
+0490 for b = 1:numel(rhs)
+0491 product{end+1} = [dnf{a},rhs{b}]; %#ok<AGROW>
+0492 end
+0493 end
+0494 dnf = product;
+0495 end
+0496 end
+0497
+0498 function [dnf,pos] = parseAtom(tokens,pos,rule)
+0499 if pos > numel(tokens) || any(strcmp(tokens{pos},{')','and','or'}))
+0500 error('Cannot parse the gene rule "%s"',rule)
+0501 elseif strcmp(tokens{pos},'(')
+0502 [dnf,pos] = parseOr(tokens,pos+1,rule);
+0503 if pos > numel(tokens) || ~strcmp(tokens{pos},')')
+0504 error('Cannot parse the gene rule "%s"',rule)
+0505 end
+0506 pos = pos + 1;
+0507 else
+0508 dnf = {tokens(pos)};
+0509 pos = pos + 1;
+0510 end
+0511 end
mapRxnsToConv Map ecModel fluxes back to the conventional starting model.
+
+
SYNOPSIS
+
function [mappedFlux, enzUsageFlux, usageEnz] = mapRxnsToConv(ecModel, model, fluxVect)
+
+
DESCRIPTION
+
mapRxnsToConv Map ecModel fluxes back to the conventional starting model.
+
+ A vector (or matrix) of fluxes is mapped to the reactions in the
+ conventional starting model that was used to construct the ecModel. It is
+ essential that the provided conventional model is indeed the model that
+ was used to initiate ecModel reconstruction.
+
+ Parameters
+ ----------
+ ecModel : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure), that was
+ used to obtain fluxVect.
+ model : struct
+ the starting model for ecModel, to which the reactions should be
+ mapped.
+ fluxVect : double
+ vector or matrix of flux values, matching ecModel.rxns.
+
+ Returns
+ -------
+ mappedFlux : double
+ vector or matrix of flux values, matching model.rxns.
+ enzUsageFlux : double
+ vector or matrix of flux values from enzyme usage reactions, as these
+ are absent from mappedFlux.
+ usageEnz : cell
+ cell array with protein IDs, matching enzUsageFlux.
+
+ Examples
+ --------
+ [mappedFlux, enzUsageFlux, usageEnz] = mapRxnsToConv(ecModel, model, fluxVect);
+
+ See also
+ --------
+ ecFVA
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [mappedFlux, enzUsageFlux, usageEnz] = mapRxnsToConv(ecModel, model, fluxVect)
+0002 % mapRxnsToConv Map ecModel fluxes back to the conventional starting model.
+0003 %
+0004 % A vector (or matrix) of fluxes is mapped to the reactions in the
+0005 % conventional starting model that was used to construct the ecModel. It is
+0006 % essential that the provided conventional model is indeed the model that
+0007 % was used to initiate ecModel reconstruction.
+0008 %
+0009 % Parameters
+0010 % ----------
+0011 % ecModel : struct
+0012 % an ecModel in GECKO 3 format (with ecModel.ec structure), that was
+0013 % used to obtain fluxVect.
+0014 % model : struct
+0015 % the starting model for ecModel, to which the reactions should be
+0016 % mapped.
+0017 % fluxVect : double
+0018 % vector or matrix of flux values, matching ecModel.rxns.
+0019 %
+0020 % Returns
+0021 % -------
+0022 % mappedFlux : double
+0023 % vector or matrix of flux values, matching model.rxns.
+0024 % enzUsageFlux : double
+0025 % vector or matrix of flux values from enzyme usage reactions, as these
+0026 % are absent from mappedFlux.
+0027 % usageEnz : cell
+0028 % cell array with protein IDs, matching enzUsageFlux.
+0029 %
+0030 % Examples
+0031 % --------
+0032 % [mappedFlux, enzUsageFlux, usageEnz] = mapRxnsToConv(ecModel, model, fluxVect);
+0033 %
+0034 % See also
+0035 % --------
+0036 % ecFVA
+0037
+0038 if isempty(fluxVect)
+0039 error('No or empty flux vector provided')
+0040 end
+0041
+0042 fluxes = fluxVect;
+0043 rxnIDs = ecModel.rxns;
+0044
+0045 % Invert flux of _REV reactions
+0046 % The suffixes are added in the order _REV, _EXP_n, so strip _EXP_n first and
+0047 % then _REV, each only at the end of the id (an id like R_REVX is left intact).
+0048 rxnIDs = regexprep(rxnIDs,'_EXP_\d+$','');
+0049 revRxns = endsWith(rxnIDs,'_REV');
+0050 fluxes(revRxns,:) = -fluxes(revRxns,:);
+0051 rxnIDs(revRxns) = regexprep(rxnIDs(revRxns),'_REV$','');
+0052
+0053 % Map and sum fluxes to converted reaction IDs
+0054 [rxnIDmap, convRxnID] = findgroups(rxnIDs);
+0055 newVect = splitapply(@(x){sum(x,1)}, fluxes, rxnIDmap);
+0056 newVect = cell2mat(newVect);
+0057
+0058 % Place in same order as in original model
+0059 [mapCheck,origIdx] = ismember(model.rxns,convRxnID);
+0060 if ~all(mapCheck)
+0061 error('Not all reactions from model.rxns can be found in the ecModel. Are you sure that ecModel is derived from model?')
+0062 end
+0063 mappedFlux=newVect(origIdx,:);
+0064
+0065 % Separately report enzyme usages
+0066 usageEnz = startsWith(ecModel.rxns,{'usage_prot_','prot_pool_exchange'});
+0067 enzUsageFlux = fluxVect(usageEnz,:);
+0068 usageEnz = regexprep(ecModel.rxns(usageEnz),'usage_prot_','');
+0069 usageEnz = regexprep(usageEnz,'prot_pool_exchange','pool');
+0070 end
setKcatForReactions Change the kcat value for selected reactions.
+
+
SYNOPSIS
+
function ecModel = setKcatForReactions(ecModel,rxnIds,kcat)
+
+
DESCRIPTION
+
setKcatForReactions Change the kcat value for selected reactions.
+
+ Changes the kcat value in ecModel.ec.kcat for selected reactions.
+ applyKcatConstraints needs to be run afterwards to transfer the kcat
+ values into the S-matrix.
+
+ Parameters
+ ----------
+ ecModel : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ rxnIds : char or cell
+ reaction identifier matching ecModel.ec.rxns. If the _EXP_ suffix is
+ not included, and there are multiple expanded (isozymic) reactions,
+ then all of those will have their kcat changed. If rxnIds includes a
+ _EXP_ suffix, then only that specific reaction will have its kcat
+ changed. If multiple rxnIds are provided as a cell array, then the
+ above applies to each rxnIds individually.
+ kcat : double
+ the new kcat value.
+
+ Returns
+ -------
+ ecModel : struct
+ ecModel where selected kcat values in ecModel.ec.kcat are changed, but
+ not yet applied to the S-matrix (will require to run
+ applyKcatConstraints). ecModel.ec.source for the changed reactions
+ will read 'setKcatForReactions'.
+
+ Examples
+ --------
+ ecModel = setKcatForReactions(ecModel, rxnIds, kcat);
+
+ See also
+ --------
+ applyKcatConstraints, getKcatAcrossIsozymes
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function ecModel = setKcatForReactions(ecModel,rxnIds,kcat)
+0002 % setKcatForReactions Change the kcat value for selected reactions.
+0003 %
+0004 % Changes the kcat value in ecModel.ec.kcat for selected reactions.
+0005 % applyKcatConstraints needs to be run afterwards to transfer the kcat
+0006 % values into the S-matrix.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % ecModel : struct
+0011 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0012 % rxnIds : char or cell
+0013 % reaction identifier matching ecModel.ec.rxns. If the _EXP_ suffix is
+0014 % not included, and there are multiple expanded (isozymic) reactions,
+0015 % then all of those will have their kcat changed. If rxnIds includes a
+0016 % _EXP_ suffix, then only that specific reaction will have its kcat
+0017 % changed. If multiple rxnIds are provided as a cell array, then the
+0018 % above applies to each rxnIds individually.
+0019 % kcat : double
+0020 % the new kcat value.
+0021 %
+0022 % Returns
+0023 % -------
+0024 % ecModel : struct
+0025 % ecModel where selected kcat values in ecModel.ec.kcat are changed, but
+0026 % not yet applied to the S-matrix (will require to run
+0027 % applyKcatConstraints). ecModel.ec.source for the changed reactions
+0028 % will read 'setKcatForReactions'.
+0029 %
+0030 % Examples
+0031 % --------
+0032 % ecModel = setKcatForReactions(ecModel, rxnIds, kcat);
+0033 %
+0034 % See also
+0035 % --------
+0036 % applyKcatConstraints, getKcatAcrossIsozymes
+0037 rxnIds = convertCharArray(rxnIds);
+0038
+0039 hasExp = ~cellfun(@isempty,regexp(rxnIds,'_EXP_\d+$'));
+0040 nonExpRxns = regexprep(ecModel.ec.rxns,'_EXP_\d+$','');
+0041 rxnsToChange = [];
+0042 for i=1:numel(hasExp)
+0043 if hasExp(i) == 1
+0044 rxnsToChange = [rxnsToChange; find(strcmpi(ecModel.ec.rxns,rxnIds{i}))];
+0045 else
+0046 nonExpRxn = regexprep(rxnIds(i),'_EXP_\d+$','');
+0047 rxnsToChange = [rxnsToChange; find(strcmpi(nonExpRxns,nonExpRxn))];
+0048 end
+0049 end
+0050 if isscalar(rxnsToChange)
+0051 if length(kcat) ~= 1
+0052 error('Found one reaction whose kcat should change, you should provide one kcat value only.')
+0053 end
+0054 else
+0055 if isscalar(kcat)
+0056 % Is fine, all reactions get the same kcat
+0057 elseif length(kcat) ~= length(rxnsToChange)
+0058 error('Found %d reactions whose kcat should change, the new kcat should be either a single value, or a vector of length %d.', length(rxnsToChange), length(rxnsToChange))
+0059 end
+0060 end
+0061 ecModel.ec.kcat(rxnsToChange) = kcat;
+0062 ecModel.ec.source(rxnsToChange) = {'setKcatForReactions'};
+0063 end
setProtPoolSize Set the limit of the total protein usage in the model.
+
+
SYNOPSIS
+
function model = setProtPoolSize(model, varargin)
+
+
DESCRIPTION
+
setProtPoolSize Set the limit of the total protein usage in the model.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ Ptot : double
+ total cellular protein content in g/gDCW. If not specified, the value
+ will be read from the model adapter. If not specified in model
+ adapter, 0.5 g/gDCW is assumed.
+ f : double
+ estimated fraction of enzymes in the model. If not specified, the
+ value will be read from the model adapter. If not specified in model
+ adapter, 0.5 is assumed.
+ sigma : double
+ estimated saturation factor. If not specified, the value will be read
+ from the model adapter. If not specified in model adapter, 0.5 is
+ assumed.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ ecModel with protein pool constraint set.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter);
+ model = setProtPoolSize(model, 'Ptot', Ptot);
+
+ See also
+ --------
+ makeEcModel
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function model = setProtPoolSize(model, varargin)
+0002 % setProtPoolSize Set the limit of the total protein usage in the model.
+0003 %
+0004 % Parameters
+0005 % ----------
+0006 % model : struct
+0007 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0008 %
+0009 % Name-Value Arguments
+0010 % --------------------
+0011 % Ptot : double
+0012 % total cellular protein content in g/gDCW. If not specified, the value
+0013 % will be read from the model adapter. If not specified in model
+0014 % adapter, 0.5 g/gDCW is assumed.
+0015 % f : double
+0016 % estimated fraction of enzymes in the model. If not specified, the
+0017 % value will be read from the model adapter. If not specified in model
+0018 % adapter, 0.5 is assumed.
+0019 % sigma : double
+0020 % estimated saturation factor. If not specified, the value will be read
+0021 % from the model adapter. If not specified in model adapter, 0.5 is
+0022 % assumed.
+0023 % modelAdapter : ModelAdapter
+0024 % a loaded model adapter (default: the current default model adapter).
+0025 %
+0026 % Returns
+0027 % -------
+0028 % model : struct
+0029 % ecModel with protein pool constraint set.
+0030 %
+0031 % Examples
+0032 % --------
+0033 % % optional arguments may be given positionally or as name-value pairs:
+0034 % model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter);
+0035 % model = setProtPoolSize(model, 'Ptot', Ptot);
+0036 %
+0037 % See also
+0038 % --------
+0039 % makeEcModel
+0040
+0041 p = parseGECKOargs(varargin, { ...
+0042 'Ptot', []; ...
+0043 'f', []; ...
+0044 'sigma', []; ...
+0045 'modelAdapter', []});
+0046 Ptot = p.Ptot;
+0047 f = p.f;
+0048 sigma = p.sigma;
+0049 modelAdapter = p.modelAdapter;
+0050
+0051 if isempty(modelAdapter)
+0052 modelAdapter = ModelAdapterManager.getDefault();
+0053 if isempty(modelAdapter) && (isempty(Ptot) || isempty(f) || isempty(sigma))
+0054 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0055 end
+0056 end
+0057
+0058 if isempty(sigma)
+0059 sigma = modelAdapter.getParameters().sigma;
+0060 end
+0061 if isempty(f)
+0062 f = modelAdapter.getParameters().f;
+0063 end
+0064 if isempty(Ptot)
+0065 Ptot = modelAdapter.getParameters().Ptot;
+0066 end
+0067
+0068 model.ub(strcmp(model.rxns, 'prot_pool_exchange')) = Ptot*f*sigma*1000;
+0069 end
+0070
upgradeEcModel Bring an ecModel made with GECKO 3 to the GECKO 4 format.
+
+
SYNOPSIS
+
function [model, changed] = upgradeEcModel(model)
+
+
DESCRIPTION
+
upgradeEcModel Bring an ecModel made with GECKO 3 to the GECKO 4 format.
+
+ GECKO 3 defined the protein usage reactions (usage_prot_<id>, prot_<id>
+ -> prot_pool) and the protein pool exchange (prot_pool_exchange,
+ prot_pool ->) in the reverse direction, used with negative flux. GECKO 4
+ defines them forward (prot_pool -> prot_<id>, -> prot_pool), so that an
+ enzyme's usage is the positive flux of its usage reaction. This turns
+ each reaction that is still reverse: its column of S, its bounds and its
+ objective coefficient change sign. Reactions already forward are left as
+ they are, so calling it again changes nothing. Flux values of the model
+ (fluxes of usage reactions and the pool exchange) change sign with it.
+ The ecModel format (annotation.gecko_format, see saveEcModel) becomes 4
+ when it is missing or lower.
+ geckopy has the same (upgrade_ec_model).
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel.
+
+ Returns
+ -------
+ model : struct
+ the ecModel in the GECKO 4 format.
+ changed : cell
+ the reactions that were turned.
+
+ Examples
+ --------
+ ecModel = upgradeEcModel(readYAMLmodel('ecYeastGEM_GECKO3.yml'));
+
+ See also
+ --------
+ upgradeGECKOproject, makeEcModel, setProtPoolSize
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [model, changed] = upgradeEcModel(model)
+0002 % upgradeEcModel Bring an ecModel made with GECKO 3 to the GECKO 4 format.
+0003 %
+0004 % GECKO 3 defined the protein usage reactions (usage_prot_<id>, prot_<id>
+0005 % -> prot_pool) and the protein pool exchange (prot_pool_exchange,
+0006 % prot_pool ->) in the reverse direction, used with negative flux. GECKO 4
+0007 % defines them forward (prot_pool -> prot_<id>, -> prot_pool), so that an
+0008 % enzyme's usage is the positive flux of its usage reaction. This turns
+0009 % each reaction that is still reverse: its column of S, its bounds and its
+0010 % objective coefficient change sign. Reactions already forward are left as
+0011 % they are, so calling it again changes nothing. Flux values of the model
+0012 % (fluxes of usage reactions and the pool exchange) change sign with it.
+0013 % The ecModel format (annotation.gecko_format, see saveEcModel) becomes 4
+0014 % when it is missing or lower.
+0015 % geckopy has the same (upgrade_ec_model).
+0016 %
+0017 % Parameters
+0018 % ----------
+0019 % model : struct
+0020 % an ecModel.
+0021 %
+0022 % Returns
+0023 % -------
+0024 % model : struct
+0025 % the ecModel in the GECKO 4 format.
+0026 % changed : cell
+0027 % the reactions that were turned.
+0028 %
+0029 % Examples
+0030 % --------
+0031 % ecModel = upgradeEcModel(readYAMLmodel('ecYeastGEM_GECKO3.yml'));
+0032 %
+0033 % See also
+0034 % --------
+0035 % upgradeGECKOproject, makeEcModel, setProtPoolSize
+0036
+0037 pool = find(strcmp(model.mets, 'prot_pool'));
+0038 changed = {};
+0039 if isempty(pool)
+0040 return
+0041 end
+0042 if ~isfield(model, 'annotation') || ~isfield(model.annotation, 'gecko_format') ...
+0043 || str2double(model.annotation.gecko_format) < 4
+0044 model.annotation.gecko_format = '4';
+0045 end
+0046 usage = find(startsWith(model.rxns, 'usage_prot_'));
+0047 reverse = usage(full(model.S(pool, usage)) > 0);
+0048 exchange = find(strcmp(model.rxns, 'prot_pool_exchange'));
+0049 if ~isempty(exchange) && full(model.S(pool, exchange)) < 0
+0050 reverse(end+1, 1) = exchange;
+0051 end
+0052 if isempty(reverse)
+0053 return
+0054 end
+0055 model.S(:, reverse) = -model.S(:, reverse);
+0056 lb = model.lb(reverse);
+0057 model.lb(reverse) = -model.ub(reverse);
+0058 model.ub(reverse) = -lb;
+0059 if isfield(model, 'c')
+0060 model.c(reverse) = -model.c(reverse);
+0061 end
+0062 changed = model.rxns(reverse);
+0063 end
addCarbonNum Deprecated alias for fillCarbonNum.
+
+ Kept for backward compatibility with pre-GECKO4 code. Will be removed
+ in a future release; switch to fillCarbonNum.
+
+ See also
+ --------
+ fillCarbonNum
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function varargout = addCarbonNum(varargin)
+0002 % addCarbonNum Deprecated alias for fillCarbonNum.
+0003 %
+0004 % Kept for backward compatibility with pre-GECKO4 code. Will be removed
+0005 % in a future release; switch to fillCarbonNum.
+0006 %
+0007 % See also
+0008 % --------
+0009 % fillCarbonNum
+0010
+0011 warning('GECKO:deprecatedName', ...
+0012 'addCarbonNum is deprecated; use fillCarbonNum instead. The old name will be removed in a future release.');
+0013 [varargout{1:nargout}] = fillCarbonNum(varargin{:});
+0014 end
copyECtoGEM Deprecated alias for applyECcodes.
+
+ Kept for backward compatibility with pre-GECKO4 code. Will be removed
+ in a future release; switch to applyECcodes.
+
+ See also
+ --------
+ applyECcodes
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function varargout = copyECtoGEM(varargin)
+0002 % copyECtoGEM Deprecated alias for applyECcodes.
+0003 %
+0004 % Kept for backward compatibility with pre-GECKO4 code. Will be removed
+0005 % in a future release; switch to applyECcodes.
+0006 %
+0007 % See also
+0008 % --------
+0009 % applyECcodes
+0010
+0011 warning('GECKO:deprecatedName', ...
+0012 'copyECtoGEM is deprecated; use applyECcodes instead. The old name will be removed in a future release.');
+0013 [varargout{1:nargout}] = applyECcodes(varargin{:});
+0014 end
enzymeUsage Deprecated alias for getEnzymeUsage.
+
+ Kept for backward compatibility with pre-GECKO4 code. Will be removed
+ in a future release; switch to getEnzymeUsage.
+
+ See also
+ --------
+ getEnzymeUsage
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function varargout = enzymeUsage(varargin)
+0002 % enzymeUsage Deprecated alias for getEnzymeUsage.
+0003 %
+0004 % Kept for backward compatibility with pre-GECKO4 code. Will be removed
+0005 % in a future release; switch to getEnzymeUsage.
+0006 %
+0007 % See also
+0008 % --------
+0009 % getEnzymeUsage
+0010
+0011 warning('GECKO:deprecatedName', ...
+0012 'enzymeUsage is deprecated; use getEnzymeUsage instead. The old name will be removed in a future release.');
+0013 [varargout{1:nargout}] = getEnzymeUsage(varargin{:});
+0014 end
pfbaEnzymes Deprecated alias for getPfbaEnzymes.
+
+ Kept for backward compatibility with pre-GECKO4 code. Will be removed
+ in a future release; switch to getPfbaEnzymes.
+
+ See also
+ --------
+ getPfbaEnzymes
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function varargout = pfbaEnzymes(varargin)
+0002 % pfbaEnzymes Deprecated alias for getPfbaEnzymes.
+0003 %
+0004 % Kept for backward compatibility with pre-GECKO4 code. Will be removed
+0005 % in a future release; switch to getPfbaEnzymes.
+0006 %
+0007 % See also
+0008 % --------
+0009 % getPfbaEnzymes
+0010
+0011 warning('GECKO:deprecatedName', ...
+0012 'pfbaEnzymes is deprecated; use getPfbaEnzymes instead. The old name will be removed in a future release.');
+0013 [varargout{1:nargout}] = getPfbaEnzymes(varargin{:});
+0014 end
selectKcatValue Deprecated alias for assignKcatValues.
+
+
SYNOPSIS
+
function varargout = selectKcatValue(varargin)
+
+
DESCRIPTION
+
selectKcatValue Deprecated alias for assignKcatValues.
+
+ Kept for backward compatibility with pre-GECKO4 code. Will be removed
+ in a future release; switch to assignKcatValues.
+
+ See also
+ --------
+ assignKcatValues
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function varargout = selectKcatValue(varargin)
+0002 % selectKcatValue Deprecated alias for assignKcatValues.
+0003 %
+0004 % Kept for backward compatibility with pre-GECKO4 code. Will be removed
+0005 % in a future release; switch to assignKcatValues.
+0006 %
+0007 % See also
+0008 % --------
+0009 % assignKcatValues
+0010
+0011 warning('GECKO:deprecatedName', ...
+0012 'selectKcatValue is deprecated; use assignKcatValues instead. The old name will be removed in a future release.');
+0013 [varargout{1:nargout}] = assignKcatValues(varargin{:});
+0014 end
startGECKOproject Deprecated alias for createGECKOproject.
+
+
SYNOPSIS
+
function varargout = startGECKOproject(varargin)
+
+
DESCRIPTION
+
startGECKOproject Deprecated alias for createGECKOproject.
+
+ Kept for backward compatibility with pre-GECKO4 code. Will be removed
+ in a future release; switch to createGECKOproject.
+
+ See also
+ --------
+ createGECKOproject
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function varargout = startGECKOproject(varargin)
+0002 % startGECKOproject Deprecated alias for createGECKOproject.
+0003 %
+0004 % Kept for backward compatibility with pre-GECKO4 code. Will be removed
+0005 % in a future release; switch to createGECKOproject.
+0006 %
+0007 % See also
+0008 % --------
+0009 % createGECKOproject
+0010
+0011 warning('GECKO:deprecatedName', ...
+0012 'startGECKOproject is deprecated; use createGECKOproject instead. The old name will be removed in a future release.');
+0013 [varargout{1:nargout}] = createGECKOproject(varargin{:});
+0014 end
updateGECKOdoc Deprecated alias for buildGECKOdoc.
+
+
SYNOPSIS
+
function varargout = updateGECKOdoc(varargin)
+
+
DESCRIPTION
+
updateGECKOdoc Deprecated alias for buildGECKOdoc.
+
+ Kept for backward compatibility with pre-GECKO4 code. Will be removed
+ in a future release; switch to buildGECKOdoc.
+
+ See also
+ --------
+ buildGECKOdoc
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function varargout = updateGECKOdoc(varargin)
+0002 % updateGECKOdoc Deprecated alias for buildGECKOdoc.
+0003 %
+0004 % Kept for backward compatibility with pre-GECKO4 code. Will be removed
+0005 % in a future release; switch to buildGECKOdoc.
+0006 %
+0007 % See also
+0008 % --------
+0009 % buildGECKOdoc
+0010
+0011 warning('GECKO:deprecatedName', ...
+0012 'updateGECKOdoc is deprecated; use buildGECKOdoc instead. The old name will be removed in a future release.');
+0013 [varargout{1:nargout}] = buildGECKOdoc(varargin{:});
+0014 end
downloadKEGG Download a KEGG organism's gene data and write a CSV.
+
+
SYNOPSIS
+
function downloadKEGG(keggID, filePath, keggGeneID)
+
+
DESCRIPTION
+
downloadKEGG Download a KEGG organism's gene data and write a CSV.
+
+ Downloads gene information for every gene of a KEGG organism code, and
+ writes the result to a CSV file. Used by loadDatabases when its
+ expected kegg.tsv is missing; can also be called directly to refresh
+ one organism's KEGG snapshot without re-running loadDatabases for
+ everything else.
+
+ Parameters
+ ----------
+ keggID : char
+ KEGG organism code, e.g. 'sce' for Saccharomyces cerevisiae.
+ filePath : char
+ destination CSV path (overwritten if it exists).
+ keggGeneID : char
+ which KEGG gene identifier field becomes the output's gene-name
+ column. 'kegg' (or empty, the default) uses the bare KEGG gene id;
+ any other value is looked up as a cross-reference identifier on
+ each KEGG entry (e.g. 'UniProt').
+
+ Examples
+ --------
+ downloadKEGG('sce', 'kegg.tsv', 'UniProt');
+
+ See also
+ --------
+ loadDatabases, downloadUniProt
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
loadDatabases loadDatabases Load the organism-specific KEGG and UniProt databases.
+
+
+
+
+
SOURCE CODE
+
0001 function downloadKEGG(keggID, filePath, keggGeneID)
+0002 % downloadKEGG Download a KEGG organism's gene data and write a CSV.
+0003 %
+0004 % Downloads gene information for every gene of a KEGG organism code, and
+0005 % writes the result to a CSV file. Used by loadDatabases when its
+0006 % expected kegg.tsv is missing; can also be called directly to refresh
+0007 % one organism's KEGG snapshot without re-running loadDatabases for
+0008 % everything else.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % keggID : char
+0013 % KEGG organism code, e.g. 'sce' for Saccharomyces cerevisiae.
+0014 % filePath : char
+0015 % destination CSV path (overwritten if it exists).
+0016 % keggGeneID : char
+0017 % which KEGG gene identifier field becomes the output's gene-name
+0018 % column. 'kegg' (or empty, the default) uses the bare KEGG gene id;
+0019 % any other value is looked up as a cross-reference identifier on
+0020 % each KEGG entry (e.g. 'UniProt').
+0021 %
+0022 % Examples
+0023 % --------
+0024 % downloadKEGG('sce', 'kegg.tsv', 'UniProt');
+0025 %
+0026 % See also
+0027 % --------
+0028 % loadDatabases, downloadUniProt
+0029
+0030 if nargin < 3 || isempty(keggGeneID)
+0031 keggGeneID = 'kegg';
+0032 end
+0033
+0034 %% Download gene information
+0035 webOptions = weboptions('Timeout',30);
+0036 try
+0037 gene_list = webread(['http://rest.kegg.jp/list/' keggID],webOptions);
+0038 catch ME
+0039 switch ME.identifier
+0040 case'MATLAB:webservices:HTTP400StatusCodeError'
+0041 error(['Unable to download data form KEGG with a potentially invalid ID: ' keggID ])
+0042 otherwise
+0043 rethrow(ME)
+0044 end
+0045 end
+0046 gene_list = regexpi(gene_list, '[^\n]+','match')';
+0047 gene_id = regexpi(gene_list,['(?<=' keggID ':)\S+'],'match');
+0048
+0049 % Retrieve information for every gene in the list, 10 genes per query
+0050 genesPerQuery = 10;
+0051 queries = ceil(numel(gene_id)/genesPerQuery);
+0052 keggData = cell(numel(gene_id),1);
+0053 PB = progressReport(queries, ['Downloading KEGG data for organism code ' keggID]);
+0054 for i = 1:queries
+0055 PB.count;
+0056 % Download batches of genes
+0057 firstIdx = i*genesPerQuery-(genesPerQuery-1);
+0058 lastIdx = i*genesPerQuery;
+0059 if lastIdx > numel(gene_id) % Last query has probably less genes
+0060 lastIdx = numel(gene_id);
+0061 end
+0062 url = ['http://rest.kegg.jp/get/' keggID ':' strjoin([gene_id{firstIdx:lastIdx}],['+' keggID ':'])];
+0063
+0064 % KEGG may be temporarily unresponsive, retry with exponential backoff
+0065 maxRetries = 5;
+0066 for attempt = 1:maxRetries
+0067 try
+0068 out = webread(url,webOptions);
+0069 break
+0070 catch ME
+0071 % A malformed or non-existing query will not succeed on retry
+0072 noRetry = ismember(ME.identifier,{'MATLAB:webservices:HTTP400StatusCodeError', ...
+0073 'MATLAB:webservices:HTTP404StatusCodeError'});
+0074 if noRetry || attempt == maxRetries
+0075 error(['Unable to download KEGG data (attempt %d of %d).\n'...
+0076 'URL: %s\n'...
+0077 'Check your internet connection and the status of the KEGG '...
+0078 'REST API, then try again.\nOriginal error: %s'], ...
+0079 attempt, maxRetries, url, ME.message);
+0080 end
+0081 pause(min(2^attempt,30)) % Wait 2, 4, 8 and 16 seconds before retrying
+0082 end
+0083 end
+0084 outSplit = strsplit(out,['///' 10]); %10 is new line character
+0085 if numel(outSplit) < lastIdx-firstIdx+2
+0086 error('KEGG returns less genes per query') %Reduce genesPerQuery
+0087 end
+0088 keggData(firstIdx:lastIdx) = outSplit(1:end-1);
+0089 end
+0090
+0091 %% Parsing of info to keggDB format
+0092 % Entries returned by KEGG are separated by '///' followed by an empty line,
+0093 % leaving a stray newline at the start of each entry after splitting. This
+0094 % would break the startsWith(...,'ENTRY ') checks below, which detect entries
+0095 % where the regular expression did not match and returned the entry unchanged
+0096 keggData = strtrim(keggData);
+0097
+0098 sequence = regexprep(keggData,'.*AASEQ\s+\d+\s+([A-Z\s])+?\s+NTSEQ.*','$1');
+0099 %No AASEQ -> no protein -> not of interest
+0100 noProt = startsWith(sequence,'ENTRY ');
+0101 uni = regexprep(keggData,'.*UniProt: (\S+?)\s.*','$1');
+0102 noUni = startsWith(uni,'ENTRY ');
+0103 uni(noProt | noUni) = [];
+0104 keggData(noProt | noUni) = [];
+0105 sequence(noProt | noUni) = [];
+0106 sequence = regexprep(sequence,'\s+','');
+0107 keggGene = regexprep(keggData,'ENTRY\s+(\S+?)\s.+','$1');
+0108
+0109 switch keggGeneID
+0110 case {'kegg',''}
+0111 gene_name = keggGene;
+0112 otherwise
+0113 % In case there are special characters:
+0114 keggGeneIDT = regexptranslate('escape',keggGeneID);
+0115 gene_name = regexprep(keggData,['.+' keggGeneIDT ': (\S+?)\n.+'],'$1');
+0116 noID = ~contains(keggData,keggGeneIDT);
+0117 if all(noID)
+0118 error(['None of the KEGG entries are annotated with the gene identifier ' keggGeneID])
+0119 else
+0120 gene_name(noID)= [];
+0121 keggData(noID) = [];
+0122 keggGene(noID) = [];
+0123 sequence(noID) = [];
+0124 uni(noID) = [];
+0125 end
+0126 end
+0127
+0128 EC_names = regexprep(keggData,'.*ORTHOLOGY.*\[EC:(.*?)\].*','$1');
+0129 EC_names(startsWith(EC_names,'ENTRY ')) = {''};
+0130
+0131 MW = cell(numel(sequence),1);
+0132 for i=1:numel(sequence)
+0133 if ~isempty(sequence{i})
+0134 MW{i} = num2str(round(calculateMW(sequence{i})));
+0135 end
+0136 end
+0137
+0138 pathway = regexprep(keggData,'.*PATHWAY\s+(.*?)(BRITE|MODULE).*','$1');
+0139 pathway(startsWith(pathway,'ENTRY ')) = {''};
+0140 pathway = strrep(pathway,[keggID '01100 Metabolic pathways'],'');
+0141 pathway = regexprep(pathway,'\n','');
+0142 pathway = regexprep(pathway,' ','');
+0143
+0144 out = [uni, gene_name, keggGene, EC_names, MW, pathway, sequence];
+0145 out = cell2table(out);
+0146
+0147 writetable(out, filePath, 'FileType', 'text', 'WriteVariableNames',false);
+0148 fprintf('Model-specific KEGG database stored at %s\n',filePath);
+0149 end
downloadUniProt Download UniProt protein data for an organism and write a TSV.
+
+
SYNOPSIS
+
function downloadUniProt(uniprotID, filePath, geneIDfield, type, reviewed)
+
+
DESCRIPTION
+
downloadUniProt Download UniProt protein data for an organism and write a TSV.
+
+ Downloads accession, gene name, EC number, mass and sequence for every
+ protein matching the given organism identifier, and writes the result
+ to a TSV file. Used by loadDatabases when its expected uniprot.tsv is
+ missing; can also be called directly to refresh one organism's UniProt
+ snapshot without re-running loadDatabases for everything else.
+
+ Parameters
+ ----------
+ uniprotID : char
+ UniProt-side identifier whose meaning depends on type, e.g. an NCBI
+ taxonomy ID such as 559292 for Saccharomyces cerevisiae S288C.
+ filePath : char
+ destination TSV path (overwritten if it exists).
+ geneIDfield : char
+ UniProt field that becomes the second column (the gene matching
+ key) (optional, default 'gene_oln', Ordered Locus Names, which
+ works for most prokaryotes and budding yeast).
+ type : char
+ UniProt query field for uniprotID (optional, default
+ 'taxonomy_id'). 'taxonomy' is accepted as an alias.
+ reviewed : logical
+ whether to restrict the query to reviewed (Swiss-Prot) entries
+ (optional, default true).
+
+ Examples
+ --------
+ downloadUniProt(559292, 'uniprot.tsv');
+ downloadUniProt(559292, 'uniprot.tsv', 'gene_oln', 'taxonomy_id', true);
+
+ See also
+ --------
+ loadDatabases, downloadKEGG
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
loadDatabases loadDatabases Load the organism-specific KEGG and UniProt databases.
+
+
+
+
+
SOURCE CODE
+
0001 function downloadUniProt(uniprotID, filePath, geneIDfield, type, reviewed)
+0002 % downloadUniProt Download UniProt protein data for an organism and write a TSV.
+0003 %
+0004 % Downloads accession, gene name, EC number, mass and sequence for every
+0005 % protein matching the given organism identifier, and writes the result
+0006 % to a TSV file. Used by loadDatabases when its expected uniprot.tsv is
+0007 % missing; can also be called directly to refresh one organism's UniProt
+0008 % snapshot without re-running loadDatabases for everything else.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % uniprotID : char
+0013 % UniProt-side identifier whose meaning depends on type, e.g. an NCBI
+0014 % taxonomy ID such as 559292 for Saccharomyces cerevisiae S288C.
+0015 % filePath : char
+0016 % destination TSV path (overwritten if it exists).
+0017 % geneIDfield : char
+0018 % UniProt field that becomes the second column (the gene matching
+0019 % key) (optional, default 'gene_oln', Ordered Locus Names, which
+0020 % works for most prokaryotes and budding yeast).
+0021 % type : char
+0022 % UniProt query field for uniprotID (optional, default
+0023 % 'taxonomy_id'). 'taxonomy' is accepted as an alias.
+0024 % reviewed : logical
+0025 % whether to restrict the query to reviewed (Swiss-Prot) entries
+0026 % (optional, default true).
+0027 %
+0028 % Examples
+0029 % --------
+0030 % downloadUniProt(559292, 'uniprot.tsv');
+0031 % downloadUniProt(559292, 'uniprot.tsv', 'gene_oln', 'taxonomy_id', true);
+0032 %
+0033 % See also
+0034 % --------
+0035 % loadDatabases, downloadKEGG
+0036
+0037 if nargin < 3 || isempty(geneIDfield)
+0038 geneIDfield = 'gene_oln';
+0039 end
+0040 if nargin < 4 || isempty(type)
+0041 type = 'taxonomy_id';
+0042 end
+0043 if nargin < 5 || isempty(reviewed)
+0044 reviewed = true;
+0045 end
+0046 if strcmp(type,'taxonomy')
+0047 type = 'taxonomy_id';
+0048 end
+0049 if reviewed
+0050 uniprotRev = 'reviewed:true+AND+';
+0051 else
+0052 uniprotRev = '';
+0053 end
+0054
+0055 disp(['Downloading Uniprot data for ' type ' ' num2str(uniprotID) '. This can take a few minutes.'])
+0056 url = ['https://rest.uniprot.org/uniprotkb/stream?query=' uniprotRev ...
+0057 type ':' num2str(uniprotID) '&fields=accession%2C' geneIDfield ...
+0058 '%2Cec%2Cmass%2Csequence&format=tsv&compressed=false&sort=protein_name%20asc'];
+0059 try
+0060 urlwrite(url,filePath,'Timeout',30);
+0061 fprintf('Model-specific UniProt database stored at %s\n',filePath);
+0062 catch
+0063 error(['Download failed, check your internet connection and try again, or manually download: ' url ...
+0064 ' After downloading, store the file as ' filePath])
+0065 end
+0066 end
fetchBrendaAsset Download (once) GECKO's BRENDA files and return their folder.
+
+
SYNOPSIS
+
function folder = fetchBrendaAsset(files)
+
+
DESCRIPTION
+
fetchBrendaAsset Download (once) GECKO's BRENDA files and return their folder.
+
+ GECKO does not ship BRENDA data. The files of the toolbox-assets release
+ brenda-2026.1-1 (BRENDA 2026.1, CC BY 4.0) are downloaded on first use
+ with RAVEN's fetchToolboxAsset, which checks them against the checksums
+ of the toolbox-assets manifest, and are unpacked into
+ databases/brenda-2026.1-1 in the GECKO folder. geckopy reads the same
+ release (fetch_brenda_asset). A project can use its own BRENDA files
+ instead, in data/brenda (see ModelAdapter.getBrendaDBFolder).
+
+ Parameters
+ ----------
+ files : cell
+ file names (default: kcat.tsv, sa.tsv, mw.tsv and measurements.tsv);
+ the release also has sequences.fasta, substrates.tsv and
+ kcatCombineParams.tsv.
+
+ Returns
+ -------
+ folder : char
+ the folder with the files, unpacked.
+
+ Examples
+ --------
+ folder = fetchBrendaAsset();
+ folder = fetchBrendaAsset({'kcatCombineParams.tsv'});
+
+ See also
+ --------
+ loadBRENDAdata, loadBrendaMeasurements, ModelAdapter
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function folder = fetchBrendaAsset(files)
+0002 % fetchBrendaAsset Download (once) GECKO's BRENDA files and return their folder.
+0003 %
+0004 % GECKO does not ship BRENDA data. The files of the toolbox-assets release
+0005 % brenda-2026.1-1 (BRENDA 2026.1, CC BY 4.0) are downloaded on first use
+0006 % with RAVEN's fetchToolboxAsset, which checks them against the checksums
+0007 % of the toolbox-assets manifest, and are unpacked into
+0008 % databases/brenda-2026.1-1 in the GECKO folder. geckopy reads the same
+0009 % release (fetch_brenda_asset). A project can use its own BRENDA files
+0010 % instead, in data/brenda (see ModelAdapter.getBrendaDBFolder).
+0011 %
+0012 % Parameters
+0013 % ----------
+0014 % files : cell
+0015 % file names (default: kcat.tsv, sa.tsv, mw.tsv and measurements.tsv);
+0016 % the release also has sequences.fasta, substrates.tsv and
+0017 % kcatCombineParams.tsv.
+0018 %
+0019 % Returns
+0020 % -------
+0021 % folder : char
+0022 % the folder with the files, unpacked.
+0023 %
+0024 % Examples
+0025 % --------
+0026 % folder = fetchBrendaAsset();
+0027 % folder = fetchBrendaAsset({'kcatCombineParams.tsv'});
+0028 %
+0029 % See also
+0030 % --------
+0031 % loadBRENDAdata, loadBrendaMeasurements, ModelAdapter
+0032
+0033 release = 'brenda-2026.1-1';
+0034 if nargin < 1 || isempty(files)
+0035 files = {'kcat.tsv', 'sa.tsv', 'mw.tsv', 'measurements.tsv'};
+0036 end
+0037 files = cellstr(files);
+0038 packed = struct('kcat_tsv', 1, 'sa_tsv', 1, 'mw_tsv', 1, 'measurements_tsv', 1, ...
+0039 'sequences_fasta', 1, 'substrates_tsv', 1, 'kcatCombineParams_tsv', 0);
+0040 folder = fullfile(findGECKOroot(), 'databases', release);
+0041 if exist('fetchToolboxAsset', 'file') ~= 2
+0042 error('fetchBrendaAsset:raven', ['GECKO downloads its BRENDA files with RAVEN''s '...
+0043 'fetchToolboxAsset, which this RAVEN does not have. Update RAVEN to version 3.'])
+0044 end
+0045 for i = 1:numel(files)
+0046 name = files{i};
+0047 key = matlab.lang.makeValidName(name);
+0048 if ~isfield(packed, key)
+0049 error('fetchBrendaAsset:file', '%s is not a file of %s.', name, release)
+0050 end
+0051 target = fullfile(folder, name);
+0052 if isfile(target)
+0053 continue
+0054 end
+0055 if packed.(key)
+0056 gz = fetchToolboxAsset(folder, release, [name '.gz']);
+0057 gunzip(gz, folder);
+0058 else
+0059 fetchToolboxAsset(folder, release, name);
+0060 end
+0061 end
+0062 end
function [KCATcell, SAcell] = loadBRENDAdata(varargin)
+
+
DESCRIPTION
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ loadBRENDAdata Load kcat and specific activity data from BRENDA files.
+
+ Reads the BRENDA data files (kcat.tsv, sa.tsv and mw.tsv) from the BRENDA
+ database folder defined by the model adapter, and returns the kcat values
+ and specific activities (the latter converted to kcat values using the
+ molecular weights).
+
+ kcat.tsv and sa.tsv each carry both a max and a median aggregate per
+ (EC, substrate, organism) triple; kcatAggregation selects which of the
+ two is read. Each file starts with a `#`-prefixed release-version line
+ followed by a tab-delimited column header, both skipped on read.
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ kcatAggregation : char
+ which aggregate to read from kcat.tsv and sa.tsv, 'max' or
+ 'median' (default: params.kcatAggregation from the model adapter,
+ or 'max' if the adapter does not set it). A triple's max is the
+ highest turnover reported for it; its median is the middle one.
+ filterOutlierKcats : logical
+ whether to drop a row whose kcat is implausibly low for its EC
+ (default: true). A row is dropped when its kcat is more than four
+ orders of magnitude below the median kcat of its EC. The reference
+ median of an EC is taken over the positive kcat values of the max and
+ median views of kcat.tsv and of the kcat values derived from sa.tsv
+ and mw.tsv, before any row is dropped, so it does not depend on
+ kcatAggregation. Wildcard ECs (containing '-') do not contribute to a
+ reference. The EC level widens from w.x.y.z to w.x.y, w.x and w while
+ fewer than five values are available. Set to false to keep every row.
+
+ Returns
+ -------
+ KCATcell : cell
+ kcat data extracted from BRENDA.
+ SAcell : cell
+ specific activity data extracted from BRENDA.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [KCATcell, SAcell] = loadBRENDAdata();
+ [KCATcell, SAcell] = loadBRENDAdata('modelAdapter', adapter);
+ [KCATcell, SAcell] = loadBRENDAdata('kcatAggregation', 'median');
+ [KCATcell, SAcell] = loadBRENDAdata('filterOutlierKcats', false);
+
+ See also
+ --------
+ loadDatabases, getECfromDatabase
0001 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0002 function [KCATcell, SAcell] = loadBRENDAdata(varargin)
+0003 % loadBRENDAdata Load kcat and specific activity data from BRENDA files.
+0004 %
+0005 % Reads the BRENDA data files (kcat.tsv, sa.tsv and mw.tsv) from the BRENDA
+0006 % database folder defined by the model adapter, and returns the kcat values
+0007 % and specific activities (the latter converted to kcat values using the
+0008 % molecular weights).
+0009 %
+0010 % kcat.tsv and sa.tsv each carry both a max and a median aggregate per
+0011 % (EC, substrate, organism) triple; kcatAggregation selects which of the
+0012 % two is read. Each file starts with a `#`-prefixed release-version line
+0013 % followed by a tab-delimited column header, both skipped on read.
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % modelAdapter : ModelAdapter
+0018 % a loaded model adapter (default: the current default model adapter).
+0019 % kcatAggregation : char
+0020 % which aggregate to read from kcat.tsv and sa.tsv, 'max' or
+0021 % 'median' (default: params.kcatAggregation from the model adapter,
+0022 % or 'max' if the adapter does not set it). A triple's max is the
+0023 % highest turnover reported for it; its median is the middle one.
+0024 % filterOutlierKcats : logical
+0025 % whether to drop a row whose kcat is implausibly low for its EC
+0026 % (default: true). A row is dropped when its kcat is more than four
+0027 % orders of magnitude below the median kcat of its EC. The reference
+0028 % median of an EC is taken over the positive kcat values of the max and
+0029 % median views of kcat.tsv and of the kcat values derived from sa.tsv
+0030 % and mw.tsv, before any row is dropped, so it does not depend on
+0031 % kcatAggregation. Wildcard ECs (containing '-') do not contribute to a
+0032 % reference. The EC level widens from w.x.y.z to w.x.y, w.x and w while
+0033 % fewer than five values are available. Set to false to keep every row.
+0034 %
+0035 % Returns
+0036 % -------
+0037 % KCATcell : cell
+0038 % kcat data extracted from BRENDA.
+0039 % SAcell : cell
+0040 % specific activity data extracted from BRENDA.
+0041 %
+0042 % Examples
+0043 % --------
+0044 % % optional arguments may be given positionally or as name-value pairs:
+0045 % [KCATcell, SAcell] = loadBRENDAdata();
+0046 % [KCATcell, SAcell] = loadBRENDAdata('modelAdapter', adapter);
+0047 % [KCATcell, SAcell] = loadBRENDAdata('kcatAggregation', 'median');
+0048 % [KCATcell, SAcell] = loadBRENDAdata('filterOutlierKcats', false);
+0049 %
+0050 % See also
+0051 % --------
+0052 % loadDatabases, getECfromDatabase
+0053
+0054 p = parseGECKOargs(varargin, { ...
+0055 'modelAdapter', [], @(x) assert(isempty(x) || isa(x,'ModelAdapter'), ...
+0056 'loadBRENDAdata:invalidModelAdapter', ...
+0057 'modelAdapter must be a ModelAdapter.'); ...
+0058 'kcatAggregation', [], @(x) assert(isempty(x) || any(strcmpi(x, ...
+0059 {'max','median'})), ...
+0060 'loadBRENDAdata:invalidKcatAggregation', ...
+0061 'kcatAggregation must be ''max'' or ''median''.'); ...
+0062 'filterOutlierKcats', true, @(x) assert((islogical(x) || isnumeric(x)) && ...
+0063 isscalar(x), ...
+0064 'loadBRENDAdata:invalidFilterOutlierKcats', ...
+0065 'filterOutlierKcats must be a logical scalar.')});
+0066 modelAdapter = p.modelAdapter;
+0067 kcatAggregation = p.kcatAggregation;
+0068 filterOutlierKcats = logical(p.filterOutlierKcats);
+0069
+0070 if isempty(modelAdapter)
+0071 modelAdapter = ModelAdapterManager.getDefault();
+0072 if isempty(modelAdapter)
+0073 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0074 end
+0075 end
+0076
+0077 kcatAggregation = resolveKcatAggregation(kcatAggregation, modelAdapter);
+0078 %Column 4 holds the per-triple maximum and column 5 the median; both are
+0079 %read, so that the outlier filter can use the max and the median views.
+0080 valueCol = 4 + strcmpi(kcatAggregation,'median');
+0081
+0082 basePath = modelAdapter.getBrendaDBFolder();
+0083 KCAT_file = fullfile(basePath,'kcat.tsv');
+0084 SA_file = fullfile(basePath,'sa.tsv');
+0085 MW_file = fullfile(basePath,'mw.tsv');
+0086
+0087 %Extract BRENDA DATA from files information. kcat.tsv/sa.tsv are seven
+0088 %columns (ec_code, substrate, organism, value_max, value_median, n,
+0089 %references); the data cells hold ec_code, substrate, organism, the max and
+0090 %the median. mw.tsv is six columns (no substrate-level aggregation choice):
+0091 %ec_code, substrate, organism, value, n, references.
+0092 KCAT = openDataFile(KCAT_file,1,'%q %q %q %f %f %f %q',[4 5]);
+0093 scalingFactor = 1/60; %[umol/min/mg] -> [mmol/s/g]
+0094 SA = openDataFile(SA_file,scalingFactor,'%q %q %q %f %f %f %q',[4 5]);
+0095 scalingFactor = 1/1000; %[g/mol] -> [g/mmol]
+0096 MW = openDataFile(MW_file,scalingFactor,'%q %q %q %f %f %q',4);
+0097
+0098 %build an index on MW{1} to speed things up a bit
+0099 %first just extract the genus (i.e. the first part of the name)
+0100 MWECNum = unique(upper(MW{1}));
+0101 MWECNumIndices = cell(length(MWECNum),1);
+0102 MWECNumHashMap = containers.Map(MWECNum,1:length(MWECNum));
+0103 for i = 1:length(MW{1})
+0104 matchInd = cell2mat(values(MWECNumHashMap, upper(MW{1}(i))));
+0105 MWECNumIndices{matchInd} = [MWECNumIndices{matchInd};i];
+0106 end
+0107
+0108 %Match each specific activity to the molecular weight of its EC and
+0109 %organism. The first match is used.
+0110 previousEC = []; EC_indexes = [];
+0111 saRows = zeros(length(SA{1}),1);
+0112 mwVals = zeros(length(SA{1}),1);
+0113 nMatched = 0;
+0114 for i=1:length(SA{1})
+0115 %Gets the indexes of the EC repetitions in the MW cell for every
+0116 %new (different) EC
+0117 if ~strcmpi(SA{1}(i), previousEC)
+0118 key = upper(SA{1}(i));
+0119 if isKey(MWECNumHashMap,key) %annoyingly, this seems to be needed
+0120 matchInd = cell2mat(values(MWECNumHashMap,key));
+0121 EC_indexes = MWECNumIndices{matchInd};
+0122 else
+0123 EC_indexes = [];
+0124 end
+0125 end
+0126 mwEC{1} = MW{3}(EC_indexes); mwEC{2} = MW{4}(EC_indexes);
+0127 % just looks for the first match because just the maximal value for
+0128 % each EC# / Orgaism is reported on the file
+0129 org_index = find(strcmpi(SA{3}(i),mwEC{1}),1);
+0130 if ~isempty(org_index)
+0131 nMatched = nMatched + 1;
+0132 saRows(nMatched) = i;
+0133 mwVals(nMatched) = mwEC{2}(org_index);
+0134 end
+0135 previousEC = SA{1}(i);
+0136 end
+0137 saRows = saRows(1:nMatched);
+0138 mwVals = mwVals(1:nMatched);
+0139
+0140 %Specific activities as kcat values [1/s], for the max and the median
+0141 saEC = SA{1}(saRows);
+0142 saMax = SA{4}(saRows).*mwVals;
+0143 saMedian = SA{5}(saRows).*mwVals;
+0144
+0145 if valueCol == 4
+0146 saSelected = saMax;
+0147 else
+0148 saSelected = saMedian;
+0149 end
+0150
+0151 %Drop kcats that are implausibly low for their EC
+0152 keepKcat = true(size(KCAT{1}));
+0153 keepSA = true(size(saEC));
+0154 if filterOutlierKcats
+0155 ecPool = [KCAT{1}; KCAT{1}; saEC; saEC];
+0156 valPool = [KCAT{4}; KCAT{5}; saMax; saMedian];
+0157 keepKcat = ~isOutlier(KCAT{1},KCAT{valueCol},ecPool,valPool);
+0158 keepSA = ~isOutlier(saEC,saSelected,ecPool,valPool);
+0159 end
+0160
+0161 KCATcell = {KCAT{1}(keepKcat), KCAT{2}(keepKcat), KCAT{3}(keepKcat), ...
+0162 KCAT{valueCol}(keepKcat)};
+0163 if nMatched > 0
+0164 SAcell = {saEC(keepSA), SA{3}(saRows(keepSA)), saSelected(keepSA), ...
+0165 mwVals(keepSA)};
+0166 else
+0167 SAcell = {[],[],[],[]};
+0168 end
+0169 end
+0170
+0171 function data_cell = openDataFile(fileName,scalingFactor,formatSpec,valueCols)
+0172 fID = fopen(fileName);
+0173 raw = textscan(fID,formatSpec,'delimiter','\t','HeaderLines',2);
+0174 fclose(fID);
+0175 data_cell = [raw(1:3), cellfun(@(c) raw{c}*scalingFactor, num2cell(valueCols), ...
+0176 'UniformOutput', false)];
+0177 end
+0178
+0179 function outlier = isOutlier(ecs,vals,ecPool,valPool)
+0180 %A value is an outlier if it is more than 'decades' orders of magnitude below
+0181 %the median kcat of its own EC, taken over the pool of values. The EC widens
+0182 %from the full code to its 3-, 2- and 1-level prefix while fewer than minRef
+0183 %values are available.
+0184 decades = 4;
+0185 minRef = 5;
+0186 usePool = valPool > 0 & ~contains(ecPool,'-');
+0187 ecPool = ecPool(usePool);
+0188 logPool = log10(valPool(usePool));
+0189 refLog = NaN(size(ecs));
+0190 if isempty(ecs) || isempty(logPool)
+0191 outlier = false(size(ecs));
+0192 return
+0193 end
+0194 for level = 4:-1:1
+0195 poolKey = ecPrefix(ecPool,level);
+0196 [grp,keys] = findgroups(poolKey);
+0197 nPerKey = accumarray(grp,1);
+0198 medPerKey = splitapply(@median,logPool,grp);
+0199 [found,loc] = ismember(ecPrefix(ecs,level),keys);
+0200 use = isnan(refLog) & found;
+0201 use(use) = nPerKey(loc(use)) >= minRef;
+0202 refLog(use) = medPerKey(loc(use));
+0203 end
+0204 outlier = vals > 0 & ~isnan(refLog) & log10(vals) < refLog - decades;
+0205 end
+0206
+0207 function prefix = ecPrefix(ecs,level)
+0208 %The first 'level' dot-separated parts of each EC code
+0209 prefix = regexp(ecs,['^[^.]*(?:\.[^.]*){0,' num2str(level-1) '}'],'match','once');
+0210 prefix(cellfun(@isempty,prefix)) = {''};
+0211 prefix = reshape(prefix,size(ecs));
+0212 end
loadDatabases Load the organism-specific KEGG and UniProt databases.
+
+
SYNOPSIS
+
function databases = loadDatabases(varargin)
+
+
DESCRIPTION
+
loadDatabases Load the organism-specific KEGG and UniProt databases.
+
+ Loads (and downloads if necessary) the organism-specific KEGG and UniProt
+ databases that are required to extract protein information. The uniprot.ID
+ and kegg.ID are taken from the ModelAdapter.
+
+ Name-Value Arguments
+ --------------------
+ selectDatabase : char
+ which databases should be loaded, either 'uniprot', 'kegg' or 'both'
+ (default 'both').
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter;
+ send in [] for default).
+
+ Returns
+ -------
+ databases : struct
+ contains .uniprot and .kegg structures, dependent on which databases
+ were selected.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ databases = loadDatabases('both', adapter);
+ databases = loadDatabases('selectDatabase', 'both');
+
+ See also
+ --------
+ getECfromDatabase, loadBRENDAdata, downloadKEGG, downloadUniProt
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
downloadKEGG downloadKEGG Download a KEGG organism's gene data and write a CSV.
downloadUniProt downloadUniProt Download UniProt protein data for an organism and write a TSV.
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function databases = loadDatabases(varargin)
+0002 % loadDatabases Load the organism-specific KEGG and UniProt databases.
+0003 %
+0004 % Loads (and downloads if necessary) the organism-specific KEGG and UniProt
+0005 % databases that are required to extract protein information. The uniprot.ID
+0006 % and kegg.ID are taken from the ModelAdapter.
+0007 %
+0008 % Name-Value Arguments
+0009 % --------------------
+0010 % selectDatabase : char
+0011 % which databases should be loaded, either 'uniprot', 'kegg' or 'both'
+0012 % (default 'both').
+0013 % modelAdapter : ModelAdapter
+0014 % a loaded model adapter (default: the current default model adapter;
+0015 % send in [] for default).
+0016 %
+0017 % Returns
+0018 % -------
+0019 % databases : struct
+0020 % contains .uniprot and .kegg structures, dependent on which databases
+0021 % were selected.
+0022 %
+0023 % Examples
+0024 % --------
+0025 % % optional arguments may be given positionally or as name-value pairs:
+0026 % databases = loadDatabases('both', adapter);
+0027 % databases = loadDatabases('selectDatabase', 'both');
+0028 %
+0029 % See also
+0030 % --------
+0031 % getECfromDatabase, loadBRENDAdata, downloadKEGG, downloadUniProt
+0032
+0033 p = parseGECKOargs(varargin, { ...
+0034 'selectDatabase', 'both'; ...
+0035 'modelAdapter', []});
+0036 selectDatabase = p.selectDatabase;
+0037 modelAdapter = p.modelAdapter;
+0038
+0039 if isempty(modelAdapter)
+0040 modelAdapter = ModelAdapterManager.getDefault();
+0041 if isempty(modelAdapter)
+0042 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0043 end
+0044 end
+0045
+0046 params = modelAdapter.getParameters();
+0047 kegg.ID = params.kegg.ID;
+0048 uniprot.ID = params.uniprot.ID;
+0049 filePath = fullfile(params.path,'data');
+0050 uniprot.geneIDfield = params.uniprot.geneIDfield;
+0051 uniprot.type = params.uniprot.type;
+0052 kegg.geneID = params.kegg.geneID;
+0053
+0054 warning('off', 'MATLAB:MKDIR:DirectoryExists');
+0055
+0056 databases.uniprot = [];
+0057 databases.kegg = [];
+0058
+0059 %% Uniprot
+0060 if any(strcmp(selectDatabase,{'uniprot','both'}))
+0061 uniprotPath = fullfile(filePath,'uniprot.tsv');
+0062 if ~exist(uniprotPath,'file')
+0063 if isempty(uniprot.ID)
+0064 printOrange('WARNING: No uniprot.ID is specified, unable to download UniProt DB.\n');
+0065 end
+0066 downloadUniProt(uniprot.ID, uniprotPath, uniprot.geneIDfield, uniprot.type, params.uniprot.reviewed);
+0067 end
+0068 if exist(uniprotPath,'file')
+0069 fid = fopen(uniprotPath,'r');
+0070 fileContent = textscan(fid,'%q %q %q %q %q','Delimiter','\t','HeaderLines',1);
+0071 fclose(fid);
+0072 databases.uniprot.ID = fileContent{1};
+0073 databases.uniprot.genes = fileContent{2};
+0074 databases.uniprot.eccodes = fileContent{3};
+0075 databases.uniprot.MW = str2double(fileContent{4});
+0076 databases.uniprot.seq = fileContent{5};
+0077 else
+0078 databases.uniprot = [];
+0079 end
+0080 if ~isempty(databases.uniprot)
+0081 [uniqueIDs,uniqueIdx] = unique(databases.uniprot.ID,'stable');
+0082 if numel(uniqueIDs) < numel(databases.uniprot.ID)
+0083 duplID = setdiff(1:numel(databases.uniprot.ID),uniqueIdx);
+0084 error('%s', ravenList(['Duplicate entries are found for the following proteins. '...
+0085 'Manually curate the ''uniprot.tsv'' file, or adjust the uniprot parameters '...
+0086 'in the model adapter:'],databases.uniprot.ID(duplID)));
+0087 end
+0088 end
+0089 end
+0090
+0091 %% KEGG
+0092 if any(strcmp(selectDatabase,{'kegg','both'}))
+0093 keggPath = fullfile(filePath,'kegg.tsv');
+0094 if ~exist(keggPath,'file')
+0095 if isempty(kegg.ID)
+0096 printOrange('WARNING: No kegg.ID is specified, unable to download KEGG DB.\n');
+0097 else
+0098 downloadKEGG(kegg.ID,keggPath,kegg.geneID);
+0099 end
+0100 end
+0101 if exist(keggPath,'file')
+0102 fid = fopen(keggPath,'r');
+0103 fileContent = textscan(fid,'%q %q %q %q %q %q %q','Delimiter',',','HeaderLines',0);
+0104 fclose(fid);
+0105 databases.kegg.uniprot = fileContent{1};
+0106 databases.kegg.genes = fileContent{2};
+0107 databases.kegg.keggGene = fileContent{3};
+0108 databases.kegg.eccodes = fileContent{4};
+0109 databases.kegg.MW = str2double(fileContent{5});
+0110 databases.kegg.pathway = fileContent{6};
+0111 databases.kegg.seq = fileContent{7};
+0112 else
+0113 databases.kegg = [];
+0114 end
+0115 end
+0116 end
function kcatAggregation = resolveKcatAggregation(kcatAggregation, modelAdapter)
+
+
DESCRIPTION
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ resolveKcatAggregation Settle which BRENDA aggregate to read.
+
+ The BRENDA snapshot records both a maximum and a median per
+ (EC, substrate, organism) triple. Which one a run uses can be given per
+ call, or once on the model adapter as params.kcatAggregation; an
+ adapter that sets neither gets 'max', which is what GECKO has always
+ used.
+
+ Input Arguments
+ ---------------
+ kcatAggregation : char
+ the per-call choice, or empty to fall back to the adapter.
+ modelAdapter : ModelAdapter
+ a loaded model adapter.
+
+ Returns
+ -------
+ kcatAggregation : char
+ 'max' or 'median'.
+
+ See also
+ --------
+ loadBRENDAdata, fuzzyKcatMatching
0001 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0002 function kcatAggregation = resolveKcatAggregation(kcatAggregation, modelAdapter)
+0003 % resolveKcatAggregation Settle which BRENDA aggregate to read.
+0004 %
+0005 % The BRENDA snapshot records both a maximum and a median per
+0006 % (EC, substrate, organism) triple. Which one a run uses can be given per
+0007 % call, or once on the model adapter as params.kcatAggregation; an
+0008 % adapter that sets neither gets 'max', which is what GECKO has always
+0009 % used.
+0010 %
+0011 % Input Arguments
+0012 % ---------------
+0013 % kcatAggregation : char
+0014 % the per-call choice, or empty to fall back to the adapter.
+0015 % modelAdapter : ModelAdapter
+0016 % a loaded model adapter.
+0017 %
+0018 % Returns
+0019 % -------
+0020 % kcatAggregation : char
+0021 % 'max' or 'median'.
+0022 %
+0023 % See also
+0024 % --------
+0025 % loadBRENDAdata, fuzzyKcatMatching
+0026
+0027 if isempty(kcatAggregation)
+0028 params = modelAdapter.getParameters();
+0029 if isfield(params,'kcatAggregation') && ~isempty(params.kcatAggregation)
+0030 kcatAggregation = params.kcatAggregation;
+0031 else
+0032 kcatAggregation = 'max';
+0033 end
+0034 end
+0035
+0036 if ~(ischar(kcatAggregation) || isstring(kcatAggregation)) || ...
+0037 ~any(strcmpi(kcatAggregation,{'max','median'}))
+0038 error('resolveKcatAggregation:invalidKcatAggregation', ...
+0039 'kcatAggregation must be ''max'' or ''median''.')
+0040 end
+0041 kcatAggregation = lower(char(kcatAggregation));
+0042 end
getEnzymeBottlenecks Top-N enzymes limiting the current objective.
+
+
SYNOPSIS
+
function bottlenecks = getEnzymeBottlenecks(model, varargin)
+
+
DESCRIPTION
+
getEnzymeBottlenecks Top-N enzymes limiting the current objective.
+
+ Solves the model and ranks every enzyme by the absolute shadow price of
+ its prot_<id> mass-balance constraint --- the enzymes whose extra
+ availability would help the objective the most.
+
+ Parameters
+ ----------
+ model : struct
+ a full ecModel in GECKO 3 format (with ecModel.ec structure). Not
+ supported for gecko-light models, which have no per-enzyme
+ prot_<id> / usage_prot_<id> machinery.
+
+ Name-Value Arguments
+ --------------------
+ top : double
+ number of enzymes to return (default 10).
+
+ Returns
+ -------
+ bottlenecks : table
+ one row per returned enzyme, sorted by descending absolute shadow
+ price, with columns uniprot, gene, shadowPrice, flux, capUsage
+ (abs(flux)/upperBound, NaN if upperBound is 0) and upperBound.
+
+ Examples
+ --------
+ bottlenecks = getEnzymeBottlenecks(ecModel);
+ bottlenecks = getEnzymeBottlenecks(ecModel, 'top', 5);
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function bottlenecks = getEnzymeBottlenecks(model, varargin)
+0002 % getEnzymeBottlenecks Top-N enzymes limiting the current objective.
+0003 %
+0004 % Solves the model and ranks every enzyme by the absolute shadow price of
+0005 % its prot_<id> mass-balance constraint --- the enzymes whose extra
+0006 % availability would help the objective the most.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % model : struct
+0011 % a full ecModel in GECKO 3 format (with ecModel.ec structure). Not
+0012 % supported for gecko-light models, which have no per-enzyme
+0013 % prot_<id> / usage_prot_<id> machinery.
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % top : double
+0018 % number of enzymes to return (default 10).
+0019 %
+0020 % Returns
+0021 % -------
+0022 % bottlenecks : table
+0023 % one row per returned enzyme, sorted by descending absolute shadow
+0024 % price, with columns uniprot, gene, shadowPrice, flux, capUsage
+0025 % (abs(flux)/upperBound, NaN if upperBound is 0) and upperBound.
+0026 %
+0027 % Examples
+0028 % --------
+0029 % bottlenecks = getEnzymeBottlenecks(ecModel);
+0030 % bottlenecks = getEnzymeBottlenecks(ecModel, 'top', 5);
+0031
+0032 p = parseGECKOargs(varargin, {'top', []});
+0033 top = p.top;
+0034 if isempty(top)
+0035 top = 10;
+0036 end
+0037
+0038 if isfield(model.ec, 'geckoLight') && model.ec.geckoLight
+0039 error(['getEnzymeBottlenecks requires a full ecModel, not gecko-light: '...
+0040 'light models have no per-enzyme prot_<id> / usage_prot_<id> machinery.'])
+0041 end
+0042
+0043 sol = solveLP(model);
+0044 if sol.stat ~= 1
+0045 error('getEnzymeBottlenecks: solver status %d; cannot rank bottlenecks.', sol.stat)
+0046 end
+0047
+0048 uniprot = model.ec.enzymes(:);
+0049 gene = model.ec.genes(:);
+0050
+0051 protMets = strcat('prot_', uniprot);
+0052 [found, metIdx] = ismember(protMets, model.mets);
+0053 if ~all(found)
+0054 error('Cannot find prot_<id> metabolites for all enzymes. This is done by makeEcModel.')
+0055 end
+0056 usageRxns = strcat('usage_prot_', uniprot);
+0057 [found, rxnIdx] = ismember(usageRxns, model.rxns);
+0058 if ~all(found)
+0059 error('Usage reactions are not defined for all enzymes. This is done by makeEcModel.')
+0060 end
+0061
+0062 shadowPrice = sol.sPrice(metIdx);
+0063 flux = sol.x(rxnIdx);
+0064 upperBound = model.ub(rxnIdx);
+0065 capUsage = abs(flux) ./ upperBound;
+0066 capUsage(upperBound == 0) = NaN;
+0067
+0068 bottlenecks = table(uniprot, gene, shadowPrice, flux, capUsage, upperBound);
+0069 [~, order] = sort(abs(bottlenecks.shadowPrice), 'descend');
+0070 bottlenecks = bottlenecks(order, :);
+0071 bottlenecks = bottlenecks(1:min(top, height(bottlenecks)), :);
+0072 end
getEnzymeUsage Compute enzyme usages from a provided flux distribution.
+
+
SYNOPSIS
+
function usageData = getEnzymeUsage(ecModel,fluxes,varargin)
+
+
DESCRIPTION
+
getEnzymeUsage Compute enzyme usages from a provided flux distribution.
+
+ Gives enzyme usages based on a provided flux distribution, as obtained
+ from a full GECKO model. It can give:
+
+ 1. absolute usage: the specific enzyme usage in mg/gDCW, which can be
+ given for enzymes with- and without concentration information;
+ 2. capacity usage: the ratio of available enzyme that is used, calculated
+ by (absUsage/UB) (note that capacity usage is 0 if an enzyme
+ concentration was not constrained in the model);
+ 3. UB: the upper bound of each enzyme exchange reaction, which may not be
+ the same as the enzyme concentration, if it has been flexibilized;
+ 4. protID: the protein identifiers for each enzyme, in the same order as
+ ecModel.ec.enzymes.
+
+ Parameters
+ ----------
+ ecModel : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ fluxes : double
+ vector of fluxes, for instance sol.x.
+
+ Name-Value Arguments
+ --------------------
+ zero : logical
+ whether also enzymes with zero absolute usage should be included
+ (default true).
+
+ Returns
+ -------
+ usageData : struct
+ structure with enzyme usage data.
+
+ Notes
+ -----
+ The usageData structure has the following fields:
+
+ - capUsage : vector of enzyme capacity usages.
+ - absUsage : vector of absolute enzyme usages.
+ - UB : vector of enzyme exchange reaction upper bounds.
+ - protID : string array of matching protein IDs.
+ - fluxes : vector of fluxes, copy of input fluxes.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ usageData = getEnzymeUsage(ecModel,fluxes,zero);
+ usageData = getEnzymeUsage(ecModel,fluxes,'zero',false);
+
+ See also
+ --------
+ reportEnzymeUsage
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function usageData = getEnzymeUsage(ecModel,fluxes,varargin)
+0002 % getEnzymeUsage Compute enzyme usages from a provided flux distribution.
+0003 %
+0004 % Gives enzyme usages based on a provided flux distribution, as obtained
+0005 % from a full GECKO model. It can give:
+0006 %
+0007 % 1. absolute usage: the specific enzyme usage in mg/gDCW, which can be
+0008 % given for enzymes with- and without concentration information;
+0009 % 2. capacity usage: the ratio of available enzyme that is used, calculated
+0010 % by (absUsage/UB) (note that capacity usage is 0 if an enzyme
+0011 % concentration was not constrained in the model);
+0012 % 3. UB: the upper bound of each enzyme exchange reaction, which may not be
+0013 % the same as the enzyme concentration, if it has been flexibilized;
+0014 % 4. protID: the protein identifiers for each enzyme, in the same order as
+0015 % ecModel.ec.enzymes.
+0016 %
+0017 % Parameters
+0018 % ----------
+0019 % ecModel : struct
+0020 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0021 % fluxes : double
+0022 % vector of fluxes, for instance sol.x.
+0023 %
+0024 % Name-Value Arguments
+0025 % --------------------
+0026 % zero : logical
+0027 % whether also enzymes with zero absolute usage should be included
+0028 % (default true).
+0029 %
+0030 % Returns
+0031 % -------
+0032 % usageData : struct
+0033 % structure with enzyme usage data.
+0034 %
+0035 % Notes
+0036 % -----
+0037 % The usageData structure has the following fields:
+0038 %
+0039 % - capUsage : vector of enzyme capacity usages.
+0040 % - absUsage : vector of absolute enzyme usages.
+0041 % - UB : vector of enzyme exchange reaction upper bounds.
+0042 % - protID : string array of matching protein IDs.
+0043 % - fluxes : vector of fluxes, copy of input fluxes.
+0044 %
+0045 % Examples
+0046 % --------
+0047 % % optional arguments may be given positionally or as name-value pairs:
+0048 % usageData = getEnzymeUsage(ecModel,fluxes,zero);
+0049 % usageData = getEnzymeUsage(ecModel,fluxes,'zero',false);
+0050 %
+0051 % See also
+0052 % --------
+0053 % reportEnzymeUsage
+0054
+0055 p = parseGECKOargs(varargin, { ...
+0056 'zero', true});
+0057 zero = p.zero;
+0058 if ecModel.ec.geckoLight
+0059 error('This function does not work on GECKO light models.')
+0060 end
+0061 usageData.protID = ecModel.ec.enzymes;
+0062 [~,rxnIdx] = ismember(strcat('usage_prot_',ecModel.ec.enzymes),ecModel.rxns);
+0063
+0064 usageData.UB = ecModel.ub(rxnIdx);
+0065 usageData.absUsage = abs(fluxes(rxnIdx));
+0066 usageData.capUsage = abs(usageData.absUsage./usageData.UB);
+0067 usageData.fluxes = fluxes;
+0068
+0069 if ~zero
+0070 nonzero = usageData.absUsage>0;
+0071 usageData.absUsage = usageData.absUsage(nonzero);
+0072 usageData.capUsage = usageData.capUsage(nonzero);
+0073 usageData.UB = usageData.UB(nonzero);
+0074 usageData.protID = usageData.protID(nonzero);
+0075 end
+0076 end
reportEnzymeUsage Summarize the results from getEnzymeUsage.
+
+
SYNOPSIS
+
function usageReport = reportEnzymeUsage(ecModel, usageData, varargin)
+
+
DESCRIPTION
+
reportEnzymeUsage Summarize the results from getEnzymeUsage.
+
+ Ranks the enzymes from getEnzymeUsage into two tables: those with the
+ highest capacity usage, and those with the highest absolute usage.
+
+ Parameters
+ ----------
+ ecModel : struct
+ a GECKO3 ecModel.
+ usageData : struct
+ output from getEnzymeUsage.
+
+ Name-Value Arguments
+ --------------------
+ highCapUsage : double
+ minimum fraction of enzyme capacity usage to be listed as high usage
+ (default 0.9, i.e. at least 90% capacity usage).
+ topAbsUsage : double
+ how many top enzymes by absolute usage to list (default 10). Use 0
+ or Inf to list all enzymes instead. Enzymes whose reactions carry no
+ flux are never listed here, as they do not contribute to the
+ solution.
+
+ Returns
+ -------
+ usageReport : struct
+ highCapUsage : table
+ enzymes at or above the highCapUsage threshold.
+ topAbsUsage : table
+ the topAbsUsage enzymes with the highest absolute usage.
+ totalUsageFlux : double
+ total protein pool used, i.e. the upper bound of prot_pool_exchange.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ usageReport = reportEnzymeUsage(ecModel, usageData, highCapUsage, topAbsUsage);
+ usageReport = reportEnzymeUsage(ecModel, usageData, 'topAbsUsage', 20);
+
+ See also
+ --------
+ getEnzymeUsage
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function usageReport = reportEnzymeUsage(ecModel, usageData, varargin)
+0002 % reportEnzymeUsage Summarize the results from getEnzymeUsage.
+0003 %
+0004 % Ranks the enzymes from getEnzymeUsage into two tables: those with the
+0005 % highest capacity usage, and those with the highest absolute usage.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % ecModel : struct
+0010 % a GECKO3 ecModel.
+0011 % usageData : struct
+0012 % output from getEnzymeUsage.
+0013 %
+0014 % Name-Value Arguments
+0015 % --------------------
+0016 % highCapUsage : double
+0017 % minimum fraction of enzyme capacity usage to be listed as high usage
+0018 % (default 0.9, i.e. at least 90% capacity usage).
+0019 % topAbsUsage : double
+0020 % how many top enzymes by absolute usage to list (default 10). Use 0
+0021 % or Inf to list all enzymes instead. Enzymes whose reactions carry no
+0022 % flux are never listed here, as they do not contribute to the
+0023 % solution.
+0024 %
+0025 % Returns
+0026 % -------
+0027 % usageReport : struct
+0028 % highCapUsage : table
+0029 % enzymes at or above the highCapUsage threshold.
+0030 % topAbsUsage : table
+0031 % the topAbsUsage enzymes with the highest absolute usage.
+0032 % totalUsageFlux : double
+0033 % total protein pool used, i.e. the upper bound of prot_pool_exchange.
+0034 %
+0035 % Examples
+0036 % --------
+0037 % % optional arguments may be given positionally or as name-value pairs:
+0038 % usageReport = reportEnzymeUsage(ecModel, usageData, highCapUsage, topAbsUsage);
+0039 % usageReport = reportEnzymeUsage(ecModel, usageData, 'topAbsUsage', 20);
+0040 %
+0041 % See also
+0042 % --------
+0043 % getEnzymeUsage
+0044
+0045 p = parseGECKOargs(varargin, { ...
+0046 'highCapUsage', []; ...
+0047 'topAbsUsage', []});
+0048 highCapUsage = p.highCapUsage;
+0049 topAbsUsage = p.topAbsUsage;
+0050 if isempty(highCapUsage); highCapUsage = 0.9; end
+0051 if isempty(topAbsUsage); topAbsUsage = 10; end
+0052 % 0 or Inf means "all enzymes", per the docstring above -- and any topAbsUsage at or
+0053 % above the enzyme count means the same thing in practice, but unlike those two special
+0054 % values it used to reach the indexing below unclamped: usageData.protID(topUse(1:N))
+0055 % with N > numel(topUse) is an out-of-bounds index, so any model with fewer than 10
+0056 % enzymes (ecTestGEM has 5) crashed on the default call.
+0057 if topAbsUsage == 0 || isinf(topAbsUsage) || topAbsUsage > numel(usageData.protID)
+0058 topAbsUsage = numel(usageData.protID);
+0059 end
+0060
+0061 usageReport = {};
+0062
+0063 % Highest capacity usage
+0064 highUsageProt = find(usageData.capUsage > highCapUsage);
+0065 highEnzyme = usageData.protID(highUsageProt);
+0066 [~,enzIdx] = ismember(highEnzyme,ecModel.ec.enzymes);
+0067 geneIDs = ecModel.ec.genes(enzIdx);
+0068
+0069 highUsage.protID = {};
+0070 highUsage.geneID = {};
+0071 highUsage.absUsage = [];
+0072 highUsage.capUsage = [];
+0073 highUsage.kcat = [];
+0074 highUsage.source = {};
+0075 highUsage.rxnID = {};
+0076 highUsage.rxnNames = {};
+0077 highUsage.grRules = {};
+0078
+0079 for i=1:numel(enzIdx)
+0080 [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel,ecModel.ec.enzymes(enzIdx(i)));
+0081 % See if all reactions carried flux
+0082 [~,rIdx] = ismember(rxns,ecModel.rxns);
+0083 carriedFlux = usageData.fluxes(rIdx) > 1e-7;
+0084 if isscalar(find(carriedFlux))
+0085 highUsage.protID(end+1,1) = highEnzyme(i);
+0086 highUsage.geneID(end+1,1) = geneIDs(i);
+0087 highUsage.absUsage(end+1,1) = usageData.absUsage(enzIdx(i));
+0088 highUsage.capUsage(end+1,1) = usageData.capUsage(enzIdx(i));
+0089 highUsage.kcat(end+1,1) = kcat(carriedFlux);
+0090 highUsage.source(end+1,1) = ecModel.ec.source(idx(carriedFlux));
+0091 highUsage.rxnID(end+1,1) = rxns(carriedFlux);
+0092 highUsage.rxnNames(end+1,1) = rxnNames(carriedFlux);
+0093 highUsage.grRules(end+1,1) = grRules(carriedFlux);
+0094 else
+0095 % Add one entry for combined usage
+0096 highUsage.protID(end+1,1) = highEnzyme(i);
+0097 highUsage.geneID(end+1,1) = geneIDs(i);
+0098 highUsage.absUsage(end+1,1) = usageData.absUsage(enzIdx(i));
+0099 highUsage.capUsage(end+1,1) = usageData.capUsage(enzIdx(i));
+0100 highUsage.kcat(end+1,1) = nan;
+0101 highUsage.source{end+1,1} = '===';
+0102 highUsage.rxnID{end+1,1} = '===';
+0103 highUsage.rxnNames{end+1,1} = 'involved in multiple rxns, usage combined, individual rxns below';
+0104 highUsage.grRules{end+1,1} = '===';
+0105 % Recalculate reaction-specific usage
+0106
+0107 rIdx = rIdx(carriedFlux);
+0108 enzFlux = usageData.fluxes(rIdx);
+0109 enzMet = strcat('prot_',highEnzyme{i});
+0110 [~, enzEcIdx] = ismember(enzMet,ecModel.mets);
+0111 indAbsUse = full(transpose(-ecModel.S(enzEcIdx,rIdx)).*enzFlux);
+0112 indCapUse = (indAbsUse /sum(indAbsUse)) * usageData.capUsage(enzIdx(i));
+0113
+0114 rxnNumber = length(rIdx);
+0115 highUsage.protID(end+1:end+rxnNumber,1) = highEnzyme(i);
+0116 highUsage.geneID(end+1:end+rxnNumber,1) = geneIDs(i);
+0117 highUsage.absUsage(end+1:end+rxnNumber,1) = indAbsUse;
+0118 highUsage.capUsage(end+1:end+rxnNumber,1) = indCapUse;
+0119 highUsage.kcat(end+1:end+rxnNumber,1) = kcat(carriedFlux);
+0120 highUsage.source(end+1:end+rxnNumber,1) = ecModel.ec.source(idx(carriedFlux));
+0121 highUsage.rxnID(end+1:end+rxnNumber,1) = rxns(carriedFlux);
+0122 highUsage.rxnNames(end+1:end+rxnNumber,1) = rxnNames(carriedFlux);
+0123 highUsage.grRules(end+1:end+rxnNumber,1) = grRules(carriedFlux);
+0124 end
+0125 end
+0126
+0127 usageReport.highCapUsage = struct2table(highUsage);
+0128
+0129 % Highest absolute usage
+0130 [~,topUse] = sort(usageData.absUsage,'descend');
+0131 topEnzyme = usageData.protID(topUse(1:topAbsUsage));
+0132 [~,b] = ismember(topEnzyme,ecModel.ec.enzymes);
+0133 geneIDs = ecModel.ec.genes(b);
+0134 topUsage.protID = {};
+0135 topUsage.geneID = {};
+0136 topUsage.absUsage = [];
+0137 topUsage.percUsage = [];
+0138 topUsage.kcat = [];
+0139 topUsage.source = {};
+0140 topUsage.rxnID = {};
+0141 topUsage.rxnNames = {};
+0142 topUsage.grRules = {};
+0143
+0144 protPool = ecModel.ub(strcmp(ecModel.rxns,'prot_pool_exchange'));
+0145
+0146 for i=1:numel(topEnzyme)
+0147 [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel,topEnzyme{i});
+0148 % See if all reactions carried flux
+0149 [~,rIdx] = ismember(rxns,ecModel.rxns);
+0150 carriedFlux = usageData.fluxes(rIdx) > 1e-7;
+0151 if ~any(carriedFlux)
+0152 % None of this enzyme's reactions carry flux -- skip it rather than
+0153 % padding the table with a placeholder row for an enzyme that is not
+0154 % actually active.
+0155 continue
+0156 elseif isscalar(find(carriedFlux))
+0157 topUsage.protID(end+1,1) = topEnzyme(i);
+0158 topUsage.geneID(end+1,1) = geneIDs(i);
+0159 topUsage.absUsage(end+1,1) = usageData.absUsage(topUse(i));
+0160 topUsage.percUsage(end+1,1) = topUsage.absUsage(end,1)/protPool*100;
+0161 topUsage.kcat(end+1,1) = kcat(carriedFlux);
+0162 topUsage.source(end+1,1) = ecModel.ec.source(idx(carriedFlux));
+0163 topUsage.rxnID(end+1,1) = rxns(carriedFlux);
+0164 topUsage.rxnNames(end+1,1) = rxnNames(carriedFlux);
+0165 topUsage.grRules(end+1,1) = grRules(carriedFlux);
+0166 else
+0167 % Add one entry for combined usage
+0168 topUsage.protID(end+1,1) = topEnzyme(i);
+0169 topUsage.geneID(end+1,1) = geneIDs(i);
+0170 topUsage.absUsage(end+1,1) = usageData.absUsage(topUse(i));
+0171 topUsage.percUsage(end+1,1) = topUsage.absUsage(end,1)/protPool*100;
+0172 topUsage.kcat(end+1,1) = nan;
+0173 topUsage.source{end+1,1} = '===';
+0174 topUsage.rxnID{end+1,1} = '===';
+0175 topUsage.rxnNames{end+1,1} = 'involved in multiple rxns, usage combined, individual rxns below';
+0176 topUsage.grRules{end+1,1} = '===';
+0177 % Recalculate reaction-specific usage
+0178 rIdx = rIdx(carriedFlux);
+0179 enzFlux = usageData.fluxes(rIdx);
+0180 enzMet = strcat('prot_',topEnzyme{i});
+0181 [~, enzIdx] = ismember(enzMet,ecModel.mets);
+0182 indUse = full(transpose(-ecModel.S(enzIdx,rIdx)).*enzFlux);
+0183 rxnNumber = length(rIdx);
+0184 topUsage.protID(end+1:end+rxnNumber,1) = topEnzyme(i);
+0185 topUsage.geneID(end+1:end+rxnNumber,1) = geneIDs(i);
+0186 topUsage.absUsage(end+1:end+rxnNumber,1) = indUse;
+0187 topUsage.percUsage(end+1:end+rxnNumber,1) = topUsage.absUsage(end-(rxnNumber-1):end,1)/protPool*100;
+0188 topUsage.kcat(end+1:end+rxnNumber,1) = kcat(carriedFlux);
+0189 topUsage.source(end+1:end+rxnNumber,1) = ecModel.ec.source(idx(carriedFlux));
+0190 topUsage.rxnID(end+1:end+rxnNumber,1) = rxns(carriedFlux);
+0191 topUsage.rxnNames(end+1:end+rxnNumber,1) = rxnNames(carriedFlux);
+0192 topUsage.grRules(end+1:end+rxnNumber,1) = grRules(carriedFlux);
+0193 end
+0194 end
+0195 usageReport.topAbsUsage = struct2table(topUsage);
+0196 usageReport.totalUsageFlux = protPool;
+0197 end
ecFSEOF Run Flux-Scanning with Enforced Objective Function for a target.
+
+
SYNOPSIS
+
function fseof = ecFSEOF(model,prodTargetRxn,csRxn,varargin)
+
+
DESCRIPTION
+
ecFSEOF Run Flux-Scanning with Enforced Objective Function for a target.
+
+ Runs Flux-Scanning with Enforced Objective Function (FSEOF) for a
+ specified production target, to identify reaction and gene targets for
+ metabolic engineering. Reactions are classified as targets with the same
+ rule as RAVEN's FSEOF: the correlation between the absolute flux of a
+ reaction and the enforced production flux.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ prodTargetRxn : char
+ rxn ID for the production target reaction, an exchange reaction is
+ recommended.
+ csRxn : char
+ rxn ID for the main carbon source uptake reaction.
+
+ Name-Value Arguments
+ --------------------
+ nSteps : double
+ number of steps for suboptimal objective in FSEOF (default 16).
+ outputFile : logical
+ boolean option to save results in a file (default false).
+ filePath : char
+ file path for results output. It will store two files: at the genes
+ level, ecFSEOF_genes.tsv; and at the reactions level,
+ ecFSEOF_rxns.tsv (default: the 'output' sub-folder taken from
+ modelAdapter, e.g. output/ecFSEOF_rxns.tsv).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ corrThreshold : double
+ minimum absolute Pearson correlation between the absolute flux of a
+ reaction and the enforced production flux for the reaction to be a
+ target (default 0.9).
+
+ Returns
+ -------
+ fseof : struct
+ a structure with all results.
+
+ Notes
+ -----
+ The fseof structure has the following fields:
+
+ - alpha : target production used for enforced objective limits (from minimum to maximum production).
+ - v_matrix : fluxes for each target reaction predicted and each alpha.
+ - rxnTargets : a list with all reactions whose absolute flux is correlated with the enforced production, with an absolute correlation of at least corrThreshold. Contains ID, name, slope, gene rule, and equation. The slope is the absolute slope of the regression of the absolute flux on the enforced production flux. The list is ordered by the product of slope and correlation, highest first.
+ - transportTargets : the same for transport reactions.
+ - geneTargets : a list with all genes of the targets. Contains gene, shortName, slope, action, and essentiality.
+
+ Only reactions with a gene rule are considered, and not those that are
+ assigned to the standard enzyme. The enforced production reaction is
+ included when it has a gene rule, as its flux follows the enforced
+ production by definition. The reactions that draw enzyme usage
+ (usage_prot_*) take part in the classification and are not reported.
+ A reaction is over expressed (OE) when its flux increases with production,
+ knocked out (KO) when it decreases and is at most 1e-8 at the last step,
+ and knocked down (KD) when it decreases otherwise.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ fseof = ecFSEOF(model,prodTargetRxn,csRxn,nSteps,outputFile,filePath,modelAdapter,corrThreshold);
+ fseof = ecFSEOF(model,prodTargetRxn,csRxn,'nSteps',32);
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function fseof = ecFSEOF(model,prodTargetRxn,csRxn,varargin)
+0002 % ecFSEOF Run Flux-Scanning with Enforced Objective Function for a target.
+0003 %
+0004 % Runs Flux-Scanning with Enforced Objective Function (FSEOF) for a
+0005 % specified production target, to identify reaction and gene targets for
+0006 % metabolic engineering. Reactions are classified as targets with the same
+0007 % rule as RAVEN's FSEOF: the correlation between the absolute flux of a
+0008 % reaction and the enforced production flux.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % model : struct
+0013 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0014 % prodTargetRxn : char
+0015 % rxn ID for the production target reaction, an exchange reaction is
+0016 % recommended.
+0017 % csRxn : char
+0018 % rxn ID for the main carbon source uptake reaction.
+0019 %
+0020 % Name-Value Arguments
+0021 % --------------------
+0022 % nSteps : double
+0023 % number of steps for suboptimal objective in FSEOF (default 16).
+0024 % outputFile : logical
+0025 % boolean option to save results in a file (default false).
+0026 % filePath : char
+0027 % file path for results output. It will store two files: at the genes
+0028 % level, ecFSEOF_genes.tsv; and at the reactions level,
+0029 % ecFSEOF_rxns.tsv (default: the 'output' sub-folder taken from
+0030 % modelAdapter, e.g. output/ecFSEOF_rxns.tsv).
+0031 % modelAdapter : ModelAdapter
+0032 % a loaded model adapter (default: the current default model adapter).
+0033 % corrThreshold : double
+0034 % minimum absolute Pearson correlation between the absolute flux of a
+0035 % reaction and the enforced production flux for the reaction to be a
+0036 % target (default 0.9).
+0037 %
+0038 % Returns
+0039 % -------
+0040 % fseof : struct
+0041 % a structure with all results.
+0042 %
+0043 % Notes
+0044 % -----
+0045 % The fseof structure has the following fields:
+0046 %
+0047 % - alpha : target production used for enforced objective limits (from minimum to maximum production).
+0048 % - v_matrix : fluxes for each target reaction predicted and each alpha.
+0049 % - rxnTargets : a list with all reactions whose absolute flux is correlated with the enforced production, with an absolute correlation of at least corrThreshold. Contains ID, name, slope, gene rule, and equation. The slope is the absolute slope of the regression of the absolute flux on the enforced production flux. The list is ordered by the product of slope and correlation, highest first.
+0050 % - transportTargets : the same for transport reactions.
+0051 % - geneTargets : a list with all genes of the targets. Contains gene, shortName, slope, action, and essentiality.
+0052 %
+0053 % Only reactions with a gene rule are considered, and not those that are
+0054 % assigned to the standard enzyme. The enforced production reaction is
+0055 % included when it has a gene rule, as its flux follows the enforced
+0056 % production by definition. The reactions that draw enzyme usage
+0057 % (usage_prot_*) take part in the classification and are not reported.
+0058 % A reaction is over expressed (OE) when its flux increases with production,
+0059 % knocked out (KO) when it decreases and is at most 1e-8 at the last step,
+0060 % and knocked down (KD) when it decreases otherwise.
+0061 %
+0062 % Examples
+0063 % --------
+0064 % % optional arguments may be given positionally or as name-value pairs:
+0065 % fseof = ecFSEOF(model,prodTargetRxn,csRxn,nSteps,outputFile,filePath,modelAdapter,corrThreshold);
+0066 % fseof = ecFSEOF(model,prodTargetRxn,csRxn,'nSteps',32);
+0067
+0068 p = parseGECKOargs(varargin, { ...
+0069 'nSteps', 16; ...
+0070 'outputFile', false; ...
+0071 'filePath', []; ...
+0072 'modelAdapter', []; ...
+0073 'corrThreshold',0.9});
+0074 nSteps = p.nSteps;
+0075 outputFile = p.outputFile;
+0076 filePath = p.filePath;
+0077 modelAdapter = p.modelAdapter;
+0078 corrThreshold = p.corrThreshold;
+0079
+0080 if isempty(modelAdapter)
+0081 modelAdapter = ModelAdapterManager.getDefault();
+0082 if isempty(modelAdapter)
+0083 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0084 end
+0085 end
+0086 params = modelAdapter.getParameters();
+0087
+0088 if isempty(filePath)
+0089 filePath = fullfile(params.path,'output');
+0090 end
+0091
+0092 % Get relevant rxn indexes
+0093 prodTargetRxnIdx = getIndexes(model, prodTargetRxn,'rxns');
+0094 csRxnIdx = getIndexes(model, csRxn,'rxns');
+0095 bioRxnIdx = getIndexes(model, params.bioRxn,'rxns');
+0096
+0097 % Standardize grRules and rxnGeneMat in model
+0098 [grRules,rxnGeneMat] = standardizeGrRules(model,true);
+0099 model.grRules = grRules;
+0100 model.rxnGeneMat = rxnGeneMat;
+0101
+0102 % Check carbon source uptake rate and LB defined
+0103 model = setParam(model, 'obj', params.bioRxn, 1);
+0104 sol = solveLP(model);
+0105 if model.lb(csRxnIdx) < sol.x(csRxnIdx)
+0106 printOrange(['WARNING: Carbon source lower bound was set to ' num2str(model.lb(csRxnIdx)) ...
+0107 ', but the uptake rate after model optimization is ' num2str(sol.x(csRxnIdx)) '.\n']);
+0108 end
+0109
+0110 % run FSEOF analysis
+0111
+0112 % Find out the initial target production.
+0113 iniTarget = sol.x(prodTargetRxnIdx);
+0114
+0115 % Find out the maximum theoretical yield of target reaction.
+0116 model = setParam(model,'obj',prodTargetRxn,1);
+0117 sol = solveLP(model,1);
+0118 % Set to 90%% based on https://doi.org/10.1128/AEM.00115-10
+0119 maxTarget = sol.x(prodTargetRxnIdx) * 0.9;
+0120
+0121 % Define alpha vector for suboptimal enforced objective values between
+0122 % minimal production and 90%% of the maximum theoretical yield, initialize
+0123 % fluxes matriz
+0124 alpha = iniTarget:((maxTarget-iniTarget)/(nSteps-1)):maxTarget;
+0125 v_matrix = zeros(length(model.rxns),length(alpha));
+0126
+0127 % Enforce objective flux iteratively
+0128 PB = progressReport(nSteps, 'Flux Scanning with Enforced Objective Function');
+0129 for i = 1:nSteps
+0130 PB.count;
+0131 % Enforce the objetive flux of product formation
+0132 model = setParam(model,'eq',prodTargetRxnIdx,alpha(i));
+0133
+0134 % Restore minimum biomass lb to zero and set it as objective
+0135 model.lb(bioRxnIdx) = 0;
+0136 model = setParam(model,'obj',params.bioRxn,1);
+0137 sol = solveLP(model,1);
+0138
+0139 % Store flux distribution
+0140 v_matrix(:,i) = sol.x;
+0141
+0142 end
+0143 PB.done;
+0144
+0145 % Take out rxns with no grRule and standard gene
+0146 withGR = ~cellfun(@isempty,model.grRules);
+0147 stdIdx = contains(model.grRules,'standard');
+0148 withGR(stdIdx) = 0;
+0149 target_rxns = model.rxns(withGR);
+0150 v_matrix = v_matrix(withGR,:);
+0151 rxnGeneM = model.rxnGeneMat(withGR,:);
+0152
+0153 % Classify the reactions as in RAVEN's FSEOF. Slope and correlation are taken
+0154 % on the absolute flux against the enforced production flux, so a reversible
+0155 % reaction running backwards counts by its magnitude. A reaction is a target
+0156 % when some absolute flux exceeds 1e-10, the standard deviation of its
+0157 % absolute flux is above 0 and the absolute Pearson correlation reaches
+0158 % corrThreshold. It is suggested for over expression (OE) when the slope is
+0159 % positive, for knock out (KO) when the slope is negative and the absolute
+0160 % flux at the last step is at most 1e-8, and for knock down (KD) for any
+0161 % other negative slope.
+0162 absV = abs(v_matrix);
+0163 slope_rxns = zeros(size(target_rxns));
+0164 corr_rxns = zeros(size(target_rxns));
+0165 target_type = cell(size(target_rxns));
+0166 targets = false(size(target_rxns));
+0167 if numel(alpha) > 1 && std(alpha) > 0
+0168 for i = 1:length(target_rxns)
+0169 track = absV(i,:);
+0170 if any(track > 1e-10) && std(track) > 0
+0171 fit = polyfit(alpha,track,1);
+0172 r = corrcoef(alpha,track);
+0173 if abs(r(1,2)) >= corrThreshold && fit(1) ~= 0
+0174 targets(i) = true;
+0175 slope_rxns(i) = abs(fit(1));
+0176 corr_rxns(i) = abs(r(1,2));
+0177 if fit(1) > 0
+0178 target_type(i) = {'OE'};
+0179 elseif track(end) <= 1e-8
+0180 target_type(i) = {'KO'};
+0181 else
+0182 target_type(i) = {'KD'};
+0183 end
+0184 end
+0185 end
+0186 end
+0187 end
+0188
+0189 % Only keep those reactions that are classified as a target
+0190 target_rxns = target_rxns(targets);
+0191 v_matrix = v_matrix(targets,:);
+0192 rxnGeneM = rxnGeneM(targets,:);
+0193 slope_rxns = slope_rxns(targets);
+0194 corr_rxns = corr_rxns(targets);
+0195 target_type = target_type(targets);
+0196
+0197 % Order from highest to lowest score, the product of slope and correlation
+0198 [~,order] = sort(slope_rxns .* corr_rxns,'descend');
+0199 target_rxns = target_rxns(order);
+0200 v_matrix = v_matrix(order,:);
+0201 rxnGeneM = rxnGeneM(order,:);
+0202 slope_rxns = slope_rxns(order);
+0203 target_type = target_type(order);
+0204
+0205 % Exclude enzyme usage reactions: they are not reported, and they would otherwise
+0206 % enter the gene slopes and the choice of the action per gene
+0207 notUsage = ~startsWith(target_rxns,'usage_prot_');
+0208 target_rxns = target_rxns(notUsage);
+0209 v_matrix = v_matrix(notUsage,:);
+0210 rxnGeneM = rxnGeneM(notUsage,:);
+0211 slope_rxns = slope_rxns(notUsage);
+0212 target_type = target_type(notUsage);
+0213
+0214 % Create gene list of those connected to the remaining rxns
+0215 genes = model.genes(sum(rxnGeneM,1) > 0);
+0216 slope_genes = zeros(size(genes));
+0217 rxnGeneM = rxnGeneM(:,sum(rxnGeneM,1) > 0);
+0218 target_type_genes = cell(size(genes));
+0219 essentiality = cell(size(genes));
+0220
+0221 % Validate for gene essentiality
+0222 PB = progressReport(numel(genes), 'Checking for gene essentiality');
+0223 for i = 1:numel(genes)
+0224 PB.count;
+0225
+0226 % Block protein usage to KO al the reactions associated to it.
+0227 usage_rxn_idx = strcmpi(model.ec.genes, genes{i});
+0228 usage_rxn = strcat('usage_prot_', model.ec.enzymes(usage_rxn_idx));
+0229 tempModel = setParam(model, 'eq', usage_rxn, 0);
+0230 solKO = solveLP(tempModel);
+0231 % Check if no feasible solution was found
+0232 if solKO.stat == -1 || solKO.x(bioRxnIdx) < 1e-8
+0233 essentiality(i) = {'essential'};
+0234 end
+0235
+0236 % Since a gene can be involved in multiple reactions, multiple
+0237 % engineering manipulations can be suggested for the same gene
+0238 % e.g. (OE and KD). So, reconcilie them, and report only one.
+0239
+0240 % Get reaction index, in targets set, for each gene
+0241 rxns_for_gene = find(rxnGeneM(:,i) > 0);
+0242 actions = unique(target_type(rxns_for_gene));
+0243 if numel(actions) > 1
+0244 % If any OE is suggested give the highest priority over KD or KO
+0245 if any(startsWith(actions, 'OE'))
+0246 target_type_genes(i) = {'OE'};
+0247 % Otherwise change to KO if not essential
+0248 elseif ~any(startsWith(actions, 'OE')) && ~isequal(essentiality(i),{'essential'})
+0249 target_type_genes(i) = {'KO'};
+0250 else
+0251 target_type_genes(i) = {'KD'};
+0252 end
+0253 else
+0254 target_type_genes(i) = actions;
+0255 end
+0256 % Extract all the slope (from rxns across alphas) conected to
+0257 % each remaining gene
+0258 slope_set = slope_rxns(rxns_for_gene);
+0259 slope_genes(i) = mean(slope_set);
+0260
+0261 end
+0262 PB.done;
+0263
+0264 % Order genes from highest to lowest slope:
+0265 [~,order] = sort(slope_genes,'descend');
+0266 genes = genes(order,:);
+0267 slope_genes = slope_genes(order,:);
+0268 target_type_genes = target_type_genes(order,:);
+0269 essentiality = essentiality(order,:);
+0270
+0271 % Create output:
+0272
+0273 % Report values used to enforce objective target production
+0274 fseof.alpha = alpha;
+0275
+0276 rxnIdx = getIndexes(model,target_rxns,'rxns');
+0277
+0278 % Report the fluxes at each alpha
+0279 fseof.v_matrix = array2table(v_matrix, ...
+0280 'VariableNames', string(alpha), 'RowNames', model.rxns(rxnIdx));
+0281
+0282 % Report reaction targets
+0283 fseof.rxnTargets(:,1) = model.rxns(rxnIdx);
+0284 fseof.rxnTargets(:,2) = model.rxnNames(rxnIdx);
+0285 fseof.rxnTargets(:,3) = num2cell(slope_rxns);
+0286 fseof.rxnTargets(:,4) = model.grRules(rxnIdx);
+0287 if isempty(rxnIdx)
+0288 fseof.rxnTargets(:,5) = cell(0,1); % constructEquations would give all reactions
+0289 else
+0290 fseof.rxnTargets(:,5) = constructEquations(model,rxnIdx);
+0291 end
+0292
+0293 % Identify transport reactions; defined as reactions involving (at least)
+0294 % one metabolite name in more than one compartment.
+0295 transportRxns = false(numel(rxnIdx),1);
+0296 for i = 1:numel(rxnIdx)
+0297 % Get the involved metabolites in each reaction
+0298 mets = model.metNames(model.S(:,rxnIdx(i))~=0);
+0299 % Remove enzyme metabolite
+0300 mets(startsWith(mets,'prot_')) = [];
+0301 % Validate if it is a transport reaciton
+0302 transportRxns(i) = numel(mets) ~= numel(unique(mets));
+0303 end
+0304
+0305 % Create separate table for transport reactions
+0306 fseof.transportTargets = fseof.rxnTargets(transportRxns,:);
+0307 fseof.rxnTargets(transportRxns,:) = [];
+0308
+0309 % Report gene targets
+0310 geneIdx = cellfun(@(x) find(strcmpi(model.genes,x)),genes);
+0311 fseof.geneTargets(:,1) = genes;
+0312 fseof.geneTargets(:,2) = model.geneShortNames(geneIdx);
+0313 fseof.geneTargets(:,3) = num2cell(slope_genes);
+0314 fseof.geneTargets(:,4) = target_type_genes;
+0315 fseof.geneTargets(:,5) = essentiality;
+0316
+0317 % Save results in a file if defined
+0318 if outputFile
+0319 % Write file with gene targets
+0320 writetable(cell2table(fseof.geneTargets, ...
+0321 'VariableNames', {'gene_IDs''gene_names''slope''action''essentiality'}), ...
+0322 fullfile(filePath, 'ecFSEOF_genes.tsv'), ...
+0323 'FileType', 'text', ...
+0324 'Delimiter', '\t', ...
+0325 'QuoteStrings', false);
+0326
+0327 % Write file with rxn targets
+0328 writetable(cell2table(fseof.rxnTargets, ...
+0329 'VariableNames', {'rxn_IDs''rxnNames''slope''grRules''rxn_formula'}), ...
+0330 fullfile(filePath, 'ecFSEOF_rxns.tsv'), ...
+0331 'FileType', 'text', ...
+0332 'Delimiter', '\t', ...
+0333 'QuoteStrings', false);
+0334
+0335 % Write file with transport targets
+0336 writetable(cell2table(fseof.transportTargets, ...
+0337 'VariableNames', {'rxn_IDs''rxnNames''slope''grRules''rxn_formula'}), ...
+0338 fullfile(filePath, 'ecFSEOF_transporter.tsv'), ...
+0339 'FileType', 'text', ...
+0340 'Delimiter', '\t', ...
+0341 'QuoteStrings', false);
+0342
+0343 disp(['ecFSEOF results stored at: ' newline filePath]);
+0344 end
+0345
+0346 end
ecFVA Run flux variability analysis on an ecModel.
+
+
SYNOPSIS
+
function [minFlux, maxFlux] = ecFVA(ecModel, model, varargin)
+
+
DESCRIPTION
+
ecFVA Run flux variability analysis on an ecModel.
+
+ Flux variability analysis is performed on the ecModel, and isozymic
+ reactions are combined to construct output minFlux and maxFlux vectors,
+ which follow the same order of model.rxns. The output from this function
+ does not include enzyme usage reactions; to observe these, one could
+ consider running flux variability directly on the ecModel.
+
+ Parameters
+ ----------
+ ecModel : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ model : struct
+ non-ecModel variant of the ecModel, to which the minFlux and maxFlux
+ will be mapped.
+
+ Name-Value Arguments
+ --------------------
+ 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).
+
+ Returns
+ -------
+ minFlux : double
+ vector of minimum flux rates, corresponding to model.rxns.
+ maxFlux : double
+ vector of maximum flux rates, corresponding to model.rxns.
+
+ Examples
+ --------
+ [minFlux, maxFlux] = ecFVA(ecModel, model);
+
+ See also
+ --------
+ mapRxnsToConv, plotEcFVA
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [minFlux, maxFlux] = ecFVA(ecModel, model, varargin)
+0002 % ecFVA Run flux variability analysis on an ecModel.
+0003 %
+0004 % Flux variability analysis is performed on the ecModel, and isozymic
+0005 % reactions are combined to construct output minFlux and maxFlux vectors,
+0006 % which follow the same order of model.rxns. The output from this function
+0007 % does not include enzyme usage reactions; to observe these, one could
+0008 % consider running flux variability directly on the ecModel.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % ecModel : struct
+0013 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0014 % model : struct
+0015 % non-ecModel variant of the ecModel, to which the minFlux and maxFlux
+0016 % will be mapped.
+0017 %
+0018 % Name-Value Arguments
+0019 % --------------------
+0020 % runParallel : logical
+0021 % speed up calculations by parallel processing. Requires the MATLAB
+0022 % Parallel Computing Toolbox. If this is not installed, the
+0023 % calculations will not be parallelized, regardless of what is
+0024 % indicated as runParallel (default true).
+0025 %
+0026 % Returns
+0027 % -------
+0028 % minFlux : double
+0029 % vector of minimum flux rates, corresponding to model.rxns.
+0030 % maxFlux : double
+0031 % vector of maximum flux rates, corresponding to model.rxns.
+0032 %
+0033 % Examples
+0034 % --------
+0035 % [minFlux, maxFlux] = ecFVA(ecModel, model);
+0036 %
+0037 % See also
+0038 % --------
+0039 % mapRxnsToConv, plotEcFVA
+0040
+0041 p = parseGECKOargs(varargin, { ...
+0042 'runParallel', true});
+0043 runParallel = p.runParallel;
+0044
+0045 rxnIDs = regexprep(ecModel.rxns,'(_REV)?(_EXP_\d+)?$','');
+0046 [rxnIDmap, convRxnID] = findgroups(rxnIDs);
+0047
+0048 N = numel(convRxnID);
+0049 maxFluxByGroup = nan(N,1);
+0050 minFluxByGroup = nan(N,1);
+0051
+0052 % nW == 0 forces parfor to run as a plain for loop (no pool required or
+0053 % created); nW == Inf lets Parallel Computing Toolbox manage the pool.
+0054 % Falls back to serial automatically when the toolbox isn't installed,
+0055 % rather than erroring the way an unconditional gcp/parpool call would.
+0056 nW = parallelWorkersRAVEN(runParallel);
+0057
+0058 PB = progressReport(N, 'Running ecFVA');
+0059
+0060 parfor (i=1:N, nW)
+0061 tmpModel = ecModel;
+0062 tmpModel.c = zeros(numel(tmpModel.rxns),1);
+0063
+0064 rxnsToOptim = find(rxnIDmap == i);
+0065 rxnsOptIDs = ecModel.rxns(rxnsToOptim);
+0066 rxnsToMin = endsWith(rxnsOptIDs,'_REV') | contains(rxnsOptIDs,'_REV_EXP_');
+0067 rxnsToMax = rxnsToOptim(~rxnsToMin);
+0068 rxnsToMin = rxnsToOptim(rxnsToMin);
+0069
+0070 tmpModel.c(rxnsToMax) = 1;
+0071 tmpModel.c(rxnsToMin) = -1;
+0072 solMax=solveLP(tmpModel);
+0073 if ~isempty(solMax.x)
+0074 % Exact bound for this canonical reaction, read directly off this
+0075 % group's own solve (the "diagonal"): forward minus reverse
+0076 % variants from the one LP that jointly optimised them, not the
+0077 % best value each variant happened to reach in whichever group's
+0078 % solve maximised it -- an "envelope" that can combine values from
+0079 % different, not necessarily jointly feasible, flux distributions.
+0080 maxFluxByGroup(i) = sum(solMax.x(rxnsToMax)) - sum(solMax.x(rxnsToMin));
+0081 end
+0082 tmpModel.c(rxnsToMax) = -1;
+0083 tmpModel.c(rxnsToMin) = 1;
+0084 solMin=solveLP(tmpModel);
+0085 if ~isempty(solMin.x)
+0086 minFluxByGroup(i) = sum(solMin.x(rxnsToMax)) - sum(solMin.x(rxnsToMin));
+0087 end
+0088 count(PB);
+0089 end
+0090 PB.done;
+0091
+0092 % Reorder from canonical-id order to model.rxns order. No min/max swap
+0093 % correction is needed here (unlike a row-reduced envelope): both bounds
+0094 % come from the same group's LP over the same feasible region with only
+0095 % the objective direction flipped, so minFluxByGroup(i) <= maxFluxByGroup(i)
+0096 % always holds whenever both solves succeeded.
+0097 [mapCheck, origIdx] = ismember(model.rxns, convRxnID);
+0098 if ~all(mapCheck)
+0099 error('Not all reactions from model.rxns can be found in the ecModel. Are you sure that ecModel is derived from model?')
+0100 end
+0101 minFlux = minFluxByGroup(origIdx);
+0102 maxFlux = maxFluxByGroup(origIdx);
+0103
+0104 end
getPfbaEnzymes Parsimonious FBA minimising total enzyme usage.
+
+
SYNOPSIS
+
function solution = getPfbaEnzymes(model, varargin)
+
+
DESCRIPTION
+
getPfbaEnzymes Parsimonious FBA minimising total enzyme usage.
+
+ Among all flux distributions at (or within a fraction of) the optimal
+ value of the current objective, finds the one that minimises total
+ enzyme usage: the smallest sum of usage_prot_* fluxes. This is the
+ enzyme-aware analogue of classical pFBA, which minimises total flux
+ instead --- for ecModels this usually picks the most parsimonious
+ *proteome*, not just the most parsimonious fluxes.
+
+ Parameters
+ ----------
+ model : struct
+ a full ecModel in GECKO 3 format (with ecModel.ec structure and
+ usage_prot_<id> reactions). Not supported for gecko-light models.
+
+ Name-Value Arguments
+ --------------------
+ rxnId : char
+ reaction id to use as the objective instead of model.c as given
+ (default: use model.c as given).
+ fractionOfOptimum : double
+ fraction of the optimal objective value to fix before minimising
+ enzyme usage (default 1.0).
+
+ Returns
+ -------
+ solution : struct
+ - x : flux distribution (indexed by model.rxns) at the
+ enzyme-usage-minimising solution.
+ - getEnzymeUsage : the minimised sum of usage_prot_* fluxes.
+ - objectiveValue : the fixed objective's own value at this solution.
+ - stat : solveLP exit flag.
+
+ Examples
+ --------
+ solution = getPfbaEnzymes(ecModel);
+ solution = getPfbaEnzymes(ecModel, 'fractionOfOptimum', 0.9);
+
+ Notes
+ -----
+ usage_prot_<id> reactions must be forward-only (lb=0), the standard
+ GECKO 3 layout; if any has a negative lower bound (reverse flux enabled),
+ getPfbaEnzymes errors instead of building an objective that would fail to
+ minimise |flux| for that reaction.
+
+ See also
+ --------
+ getEnzymeBottlenecks
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function solution = getPfbaEnzymes(model, varargin)
+0002 % getPfbaEnzymes Parsimonious FBA minimising total enzyme usage.
+0003 %
+0004 % Among all flux distributions at (or within a fraction of) the optimal
+0005 % value of the current objective, finds the one that minimises total
+0006 % enzyme usage: the smallest sum of usage_prot_* fluxes. This is the
+0007 % enzyme-aware analogue of classical pFBA, which minimises total flux
+0008 % instead --- for ecModels this usually picks the most parsimonious
+0009 % *proteome*, not just the most parsimonious fluxes.
+0010 %
+0011 % Parameters
+0012 % ----------
+0013 % model : struct
+0014 % a full ecModel in GECKO 3 format (with ecModel.ec structure and
+0015 % usage_prot_<id> reactions). Not supported for gecko-light models.
+0016 %
+0017 % Name-Value Arguments
+0018 % --------------------
+0019 % rxnId : char
+0020 % reaction id to use as the objective instead of model.c as given
+0021 % (default: use model.c as given).
+0022 % fractionOfOptimum : double
+0023 % fraction of the optimal objective value to fix before minimising
+0024 % enzyme usage (default 1.0).
+0025 %
+0026 % Returns
+0027 % -------
+0028 % solution : struct
+0029 % - x : flux distribution (indexed by model.rxns) at the
+0030 % enzyme-usage-minimising solution.
+0031 % - getEnzymeUsage : the minimised sum of usage_prot_* fluxes.
+0032 % - objectiveValue : the fixed objective's own value at this solution.
+0033 % - stat : solveLP exit flag.
+0034 %
+0035 % Examples
+0036 % --------
+0037 % solution = getPfbaEnzymes(ecModel);
+0038 % solution = getPfbaEnzymes(ecModel, 'fractionOfOptimum', 0.9);
+0039 %
+0040 % Notes
+0041 % -----
+0042 % usage_prot_<id> reactions must be forward-only (lb=0), the standard
+0043 % GECKO 3 layout; if any has a negative lower bound (reverse flux enabled),
+0044 % getPfbaEnzymes errors instead of building an objective that would fail to
+0045 % minimise |flux| for that reaction.
+0046 %
+0047 % See also
+0048 % --------
+0049 % getEnzymeBottlenecks
+0050
+0051 p = parseGECKOargs(varargin, {'rxnId', []; 'fractionOfOptimum', []});
+0052 rxnId = p.rxnId;
+0053 fractionOfOptimum = p.fractionOfOptimum;
+0054 if isempty(fractionOfOptimum)
+0055 fractionOfOptimum = 1.0;
+0056 end
+0057
+0058 if isfield(model.ec, 'geckoLight') && model.ec.geckoLight
+0059 error(['getPfbaEnzymes requires a full ecModel, not gecko-light: light models have no '...
+0060 'usage_prot_<id> reactions.'])
+0061 end
+0062
+0063 if ~isempty(rxnId)
+0064 rxnIdx = find(strcmp(model.rxns, rxnId));
+0065 if isempty(rxnIdx)
+0066 error('getPfbaEnzymes: reaction %s not found in model.rxns.', rxnId)
+0067 end
+0068 model.c = zeros(numel(model.rxns), 1);
+0069 model.c(rxnIdx) = 1;
+0070 end
+0071 if ~any(model.c)
+0072 error('getPfbaEnzymes: model.c is empty; nothing to fix before minimising enzyme usage.')
+0073 end
+0074
+0075 usageIdx = find(startsWith(model.rxns, 'usage_prot_'));
+0076 if isempty(usageIdx)
+0077 error('getPfbaEnzymes: no usage_prot_<id> reactions found. This is done by makeEcModel.')
+0078 end
+0079 if any(model.lb(usageIdx) < 0)
+0080 error(['getPfbaEnzymes does not support usage_prot_<id> reactions with a negative lower '...
+0081 'bound (reverse flux enabled): minimising raw flux would not minimise |flux| for '...
+0082 'those reactions.'])
+0083 end
+0084
+0085 solOrig = solveLP(model);
+0086 if solOrig.stat ~= 1
+0087 error('getPfbaEnzymes: solver status %d on the original objective; cannot fix it as a constraint.', solOrig.stat)
+0088 end
+0089
+0090 % Fix the objective as a constraint, same "fake metabolite" technique
+0091 % solveLP's own minFlux=1 option uses (see solveLP.m).
+0092 target = fractionOfOptimum * solOrig.f;
+0093 if target > 0
+0094 target = target * 0.999999;
+0095 else
+0096 target = target * 1.000001;
+0097 end
+0098 fixModel = model;
+0099 fixModel.S(end+1, :) = model.c';
+0100 fixModel.mets{end+1, 1} = 'getPfbaEnzymes_objective';
+0101 if size(fixModel.b, 2) == 1
+0102 fixModel.b = [fixModel.b fixModel.b];
+0103 end
+0104 fixModel.b(end+1, :) = [target inf];
+0105
+0106 fixModel.c = zeros(numel(fixModel.rxns), 1);
+0107 fixModel.c(usageIdx) = -1;
+0108
+0109 solution = solveLP(fixModel);
+0110 if solution.stat ~= 1
+0111 error('getPfbaEnzymes: minimising enzyme usage was infeasible at fractionOfOptimum=%g.', fractionOfOptimum)
+0112 end
+0113
+0114 solution.enzymeUsage = -solution.f;
+0115 solution.objectiveValue = model.c' * solution.x;
+0116 end
plotEcFVA Plot cumulative distribution functions of ecFVA results.
+
+
SYNOPSIS
+
function plotEcFVA(minFlux, maxFlux)
+
+
DESCRIPTION
+
plotEcFVA Plot cumulative distribution functions of ecFVA results.
+
+ Plots cumulative distribution functions of ecFVA results from one or
+ more ecModel(s) and/or GEM(s).
+
+ Parameters
+ ----------
+ minFlux : double
+ vector of minimum flux values, coming from ecFVA. If multiple
+ ecModels/GEMs are to be visualized, then each column represents a
+ separate model.
+ maxFlux : double
+ vector of maximum flux values, matching minFlux.
+
+ See also
+ --------
+ ecFVA
assignKcatValues Select one kcat value per reaction and write to model.ec.
+
+
SYNOPSIS
+
function [model, rxnIdx] = assignKcatValues(model,kcatList,varargin)
+
+
DESCRIPTION
+
assignKcatValues Select one kcat value per reaction and write to model.ec.
+
+ From a kcatList with predicted or suggested kcat values, where each
+ reaction may have multiple entries, one kcat value is selected and written
+ to model.ec.kcat. Zero values are discarded from the start. By default,
+ the maximum value is chosen, but alternatives are available. The kcatList
+ structure is an output of e.g. readDLKcatOutput, readGotEnzymesOutput,
+ readManualKcatList.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ kcatList : struct
+ structure array with separate entries for each kcat value, with the
+ following fields:
+
+ - source : e.g. 'DLKcat' or 'gotenzymes'.
+ - rxns : reaction identifiers, matching model.ec.rxns.
+ - genes : gene identifiers, matching model.ec.genes.
+ - substrates : substrate names, matching model.metNames.
+ - kcats : predicted kcat value in /sec.
+ - kcatSource : optional, the source of each entry; entries with a zero
+ kcat are discarded together with their source.
+
+ Name-Value Arguments
+ --------------------
+ criteria : char
+ which kcat value should be selected if multiple values are provided.
+ Options: 'max', 'min', 'median', 'mean' (default 'max'). The written
+ model.ec.source is taken from the matching entry for 'max'/'min'; for
+ 'median'/'mean', where the selected value need not equal any single
+ input, it is taken from the first entry instead (arbitrary but
+ deterministic, since an aggregate has no single "winning" source).
+ overwrite : char
+ whether existing kcat values should be overwritten. Options: 'true',
+ 'false', 'ifHigher'. The last option will overwrite only if the new
+ kcat value is higher (default 'true').
+
+ Returns
+ -------
+ model : struct
+ ecModel with updated model.ec.kcat and model.ec.source.
+ rxnIdx : double
+ list of reaction indices (matching model.ec.rxns), to indicate which
+ kcat values have been changed.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, rxnIdx] = assignKcatValues(model, kcatList);
+ [model, rxnIdx] = assignKcatValues(model, kcatList, 'criteria', 'mean');
+
+ See also
+ --------
+ readDLKcatOutput, mergeDLKcatAndFuzzyKcats, applyKcatConstraints
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
collectKcats collectKcats Decide one kcat per reaction from the kcat source files.
+
+
+
+
+
SOURCE CODE
+
0001 function [model, rxnIdx] = assignKcatValues(model,kcatList,varargin)
+0002 % assignKcatValues Select one kcat value per reaction and write to model.ec.
+0003 %
+0004 % From a kcatList with predicted or suggested kcat values, where each
+0005 % reaction may have multiple entries, one kcat value is selected and written
+0006 % to model.ec.kcat. Zero values are discarded from the start. By default,
+0007 % the maximum value is chosen, but alternatives are available. The kcatList
+0008 % structure is an output of e.g. readDLKcatOutput, readGotEnzymesOutput,
+0009 % readManualKcatList.
+0010 %
+0011 % Parameters
+0012 % ----------
+0013 % model : struct
+0014 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0015 % kcatList : struct
+0016 % structure array with separate entries for each kcat value, with the
+0017 % following fields:
+0018 %
+0019 % - source : e.g. 'DLKcat' or 'gotenzymes'.
+0020 % - rxns : reaction identifiers, matching model.ec.rxns.
+0021 % - genes : gene identifiers, matching model.ec.genes.
+0022 % - substrates : substrate names, matching model.metNames.
+0023 % - kcats : predicted kcat value in /sec.
+0024 % - kcatSource : optional, the source of each entry; entries with a zero
+0025 % kcat are discarded together with their source.
+0026 %
+0027 % Name-Value Arguments
+0028 % --------------------
+0029 % criteria : char
+0030 % which kcat value should be selected if multiple values are provided.
+0031 % Options: 'max', 'min', 'median', 'mean' (default 'max'). The written
+0032 % model.ec.source is taken from the matching entry for 'max'/'min'; for
+0033 % 'median'/'mean', where the selected value need not equal any single
+0034 % input, it is taken from the first entry instead (arbitrary but
+0035 % deterministic, since an aggregate has no single "winning" source).
+0036 % overwrite : char
+0037 % whether existing kcat values should be overwritten. Options: 'true',
+0038 % 'false', 'ifHigher'. The last option will overwrite only if the new
+0039 % kcat value is higher (default 'true').
+0040 %
+0041 % Returns
+0042 % -------
+0043 % model : struct
+0044 % ecModel with updated model.ec.kcat and model.ec.source.
+0045 % rxnIdx : double
+0046 % list of reaction indices (matching model.ec.rxns), to indicate which
+0047 % kcat values have been changed.
+0048 %
+0049 % Examples
+0050 % --------
+0051 % % optional arguments may be given positionally or as name-value pairs:
+0052 % [model, rxnIdx] = assignKcatValues(model, kcatList);
+0053 % [model, rxnIdx] = assignKcatValues(model, kcatList, 'criteria', 'mean');
+0054 %
+0055 % See also
+0056 % --------
+0057 % readDLKcatOutput, mergeDLKcatAndFuzzyKcats, applyKcatConstraints
+0058
+0059 p = parseGECKOargs(varargin, { ...
+0060 'criteria', []; ...
+0061 'overwrite', []});
+0062 criteria = p.criteria;
+0063 overwrite = p.overwrite;
+0064
+0065 if isempty(overwrite)
+0066 overwrite = 'true';
+0067 elseif islogical(overwrite)
+0068 if overwrite
+0069 overwrite = 'true';
+0070 else
+0071 overwrite = 'false';
+0072 end
+0073 end
+0074 if isempty(criteria)
+0075 criteria = 'max';
+0076 end
+0077
+0078 % Remove zero kcat values. Only adjusting fields that are used later.
+0079 removeZero = kcatList.kcats == 0;
+0080 kcatList.kcats(removeZero) = [];
+0081 kcatList.rxns(removeZero) = [];
+0082 if isfield(kcatList,'kcatSource')
+0083 kcatList.kcatSource(removeZero) = [];
+0084 end
+0085
+0086 % Map to model.ec.rxns
+0087 [sanityCheck,idxInModel] = ismember(kcatList.rxns,model.ec.rxns);
+0088 if ~all(sanityCheck)
+0089 error('Not all reactions in kcatList are found in model.ec.rxns')
+0090 end
+0091 % Make vector with single kcat value per reaction
+0092 idxInModelUnique = unique(idxInModel);
+0093 selectedKcats = zeros(numel(idxInModelUnique),1);
+0094 selectedSource = cell(numel(selectedKcats),1);
+0095 if ~isfield(kcatList,'kcatSource')
+0096 kcatList.kcatSource = cell(numel(kcatList.kcats),1);
+0097 kcatList.kcatSource(:) = {kcatList.source};
+0098 end
+0099 for i=1:numel(idxInModelUnique)
+0100 ind = idxInModelUnique(i);
+0101 idxMatch = find(idxInModel == ind);
+0102 % Choose the maximum number
+0103 switch criteria
+0104 case'max'
+0105 [selectedKcats(i),j] = max(kcatList.kcats(idxMatch));
+0106 case'min'
+0107 [selectedKcats(i),j] = min(kcatList.kcats(idxMatch));
+0108 case'median'
+0109 selectedKcats(i) = median(kcatList.kcats(idxMatch));
+0110 j = 1;
+0111 case'mean'
+0112 selectedKcats(i) = mean(kcatList.kcats(idxMatch));
+0113 j = 1;
+0114 otherwise
+0115 error('Invalid criteria specified')
+0116 end
+0117 selectedSource(i) = kcatList.kcatSource(idxMatch(j));
+0118 end
+0119
+0120 % Populate model.ec.kcat
+0121 switch overwrite
+0122 case'true'
+0123 model.ec.kcat(idxInModelUnique) = selectedKcats;
+0124 model.ec.source(idxInModelUnique) = selectedSource;
+0125 case'false'
+0126 emptyKcats = find(model.ec.kcat == 0);
+0127 [idxInModelUnique,whickKcats] = intersect(idxInModelUnique,emptyKcats,'stable');
+0128 model.ec.kcat(idxInModelUnique) = selectedKcats(whickKcats);
+0129 model.ec.source(idxInModelUnique) = selectedSource(whickKcats);
+0130
+0131 case'ifHigher'
+0132 higherKcats = model.ec.kcat(idxInModelUnique) < selectedKcats;
+0133 selectedKcats(~higherKcats) = [];
+0134 selectedSource(~higherKcats) = [];
+0135 idxInModelUnique(~higherKcats) = [];
+0136 model.ec.kcat(idxInModelUnique) = selectedKcats;
+0137 model.ec.source(idxInModelUnique) = selectedSource;
+0138 otherwise
+0139 error('Invalid overwrite flag specified')
+0140 end
+0141 rxnIdx = idxInModelUnique;
+0142 end
assignKcats Assign kcats from the kcat source files and apply them.
+
+
SYNOPSIS
+
function [model, decisions, report] = assignKcats(model, varargin)
+
+
DESCRIPTION
+
assignKcats Assign kcats from the kcat source files and apply them.
+
+ Decides one kcat per reaction from the kcat source files in data/kcats,
+ as [kcat_assignment] in model_adapter.toml sets (see collectKcats), writes
+ model.ec.kcat and model.ec.source, and applies the kcat constraints
+ (applyKcatConstraints). Any kcats the model had are replaced: the source
+ files are the input, and nothing in the model is written back to them.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ writeReport : logical
+ write the decisions to output/<model id>_kcatDecisions.tsv (default
+ true).
+
+ Returns
+ -------
+ model : struct
+ the ecModel with the assigned kcats applied.
+ decisions, report
+ as returned by collectKcats.
+
+ Examples
+ --------
+ updateKcatSources(ecModel); % gather what the configuration needs
+ ecModel = assignKcats(ecModel);
+
+ See also
+ --------
+ collectKcats, updateKcatSources, applyKcatConstraints
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
collectKcats collectKcats Decide one kcat per reaction from the kcat source files.
0001 function [model, decisions, report] = assignKcats(model, varargin)
+0002 % assignKcats Assign kcats from the kcat source files and apply them.
+0003 %
+0004 % Decides one kcat per reaction from the kcat source files in data/kcats,
+0005 % as [kcat_assignment] in model_adapter.toml sets (see collectKcats), writes
+0006 % model.ec.kcat and model.ec.source, and applies the kcat constraints
+0007 % (applyKcatConstraints). Any kcats the model had are replaced: the source
+0008 % files are the input, and nothing in the model is written back to them.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % model : struct
+0013 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % modelAdapter : ModelAdapter
+0018 % a loaded model adapter (default: the current default model adapter).
+0019 % writeReport : logical
+0020 % write the decisions to output/<model id>_kcatDecisions.tsv (default
+0021 % true).
+0022 %
+0023 % Returns
+0024 % -------
+0025 % model : struct
+0026 % the ecModel with the assigned kcats applied.
+0027 % decisions, report
+0028 % as returned by collectKcats.
+0029 %
+0030 % Examples
+0031 % --------
+0032 % updateKcatSources(ecModel); % gather what the configuration needs
+0033 % ecModel = assignKcats(ecModel);
+0034 %
+0035 % See also
+0036 % --------
+0037 % collectKcats, updateKcatSources, applyKcatConstraints
+0038
+0039 checkOptions(varargin, {'modelAdapter','writeReport'}, 'assignKcats');
+0040 [decisions, report, model] = collectKcats(model, varargin{:});
+0041 model = applyKcatConstraints(model);
+0042 end
+0043
+0044 function checkOptions(args, names, functionName)
+0045 % Options are name-value pairs with known names, so a misspelled option is
+0046 % an error instead of being taken as a positional argument.
+0047 id = [functionName ':arguments'];
+0048 given = args(1:2:end);
+0049 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0050 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0051 end
+0052 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0053 if ~isempty(unknown)
+0054 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0055 strjoin(names, ', '))
+0056 end
+0057 end
collectKcats Decide one kcat per reaction from the kcat source files.
+
+
SYNOPSIS
+
function [decisions, report, ecModel] = collectKcats(model, varargin)
+
+
DESCRIPTION
+
collectKcats Decide one kcat per reaction from the kcat source files.
+
+ Combines the kcat source files in data/kcats (see kcatsFromBrenda,
+ kcatsFromDLKcat, kcatsFromOKP and saveKcatSource) as [kcat_assignment] in
+ model_adapter.toml sets, without changing the model given. assignKcats
+ applies the decisions.
+
+ The order is a preset or a list of stages:
+
+ [kcat_assignment]
+ preset = "brenda_ec_tree" # the default; or ec_tree_homologs, ec_tree_okp, okp,
+ # gecko_median, gecko_classic
+ isozymes = true # fill from isozymes after the stages
+ standard = true # standard kcat for the rest
+
+ # instead of a preset:
+ [[kcat_assignment.stage]]
+ action = "select" # or "combine"
+ sources = ["custom", "database_top", "ec_tree"]
+ criteria = "max" # select: among several values of one source
+ top_origin_limit = 1
+
+ [kcat_sources.okp]
+ methods = ["CataPro", "UniKP"] # the okp source group
+
+ A stage decides the reactions that are still open:
+ - select: the first listed source with a value (see mergeKcats). Sources
+ are the names of source files (custom, ec_tree, dlkcat, okp_catapro,
+ ...) and the BRENDA quality tiers database_exact, database_top and
+ database_bottom, which read brenda.tsv.
+ - combine: all listed sources with a value, each corrected for its bias
+ and weighted by 1 / its error variance, both for the source's evidence
+ for that reaction (the EC tree: measurements of the EC and its EC
+ level; an OKP method: the enzyme's similarity to the method's training
+ sequences). The parameters come with the BRENDA files
+ (kcatCombineParams.tsv). model.ec.source reads 'combined (sd=<log10
+ sd>, n=<sources>)'; sd is the uncertainty for a new measurement.
+ In both, okp stands for the OKP methods of [kcat_sources.okp] methods
+ (default: all methods with combination parameters).
+
+ The presets:
+ - brenda_ec_tree (the default): custom kcats, the BRENDA median of
+ measurements of the organism on the reaction's substrate (origin 1,
+ without EC wildcards), the EC tree (kcatsFromECTree); then the OKP
+ methods combined for the reactions left (without an EC number).
+ - ec_tree_homologs: custom kcats, the organism's own BRENDA value; then
+ the EC tree and homologs (kcatsFromHomologs) combined; then the OKP
+ methods combined.
+ - ec_tree_okp: custom kcats, the organism's own BRENDA value; then the EC
+ tree, homologs and the OKP methods combined; then the OKP methods
+ combined.
+ - okp: custom kcats; then the OKP methods combined.
+ - gecko_median: custom kcats, the BRENDA median of matches without EC
+ wildcards from the organism or other organisms, with or without the
+ substrate (origin 1 to 4), DLKcat, then the other BRENDA matches (EC
+ wildcards, and specific activities, origin 5 and 6).
+ - gecko_classic: as gecko_median with the BRENDA maximum and the
+ specific-activity matches above DLKcat (origin up to 6), as the GECKO 3
+ tutorial.
+ Against measured kcats hidden from the search, the EC tree was closer
+ than the BRENDA median at origins 2 and 4 and with EC wildcards and as
+ close at origins 1 and 3; the EC tree combined with the OKP methods was
+ slightly closer than the EC tree, and the OKP methods combined as close.
+ In ecModels, values combined with predictions set high-turnover enzymes
+ such as ATP synthase too low, which is why the default uses predictions
+ only where there is no value from measurements. The BRENDA aggregation is
+ [kcat_sources.brenda] aggregation (see updateKcatSources).
+
+ After the stages:
+ - custom kcats of data/customKcats.tsv are applied (applyCustomKcats),
+ when a stage lists custom but there is no data/kcats/custom.tsv;
+ - with isozymes, reactions without a kcat take one from their isozymes
+ (getKcatAcrossIsozymes);
+ - with standard, the remaining reactions get the standard kcat
+ (getStandardKcat).
+
+ A source file a stage names that does not exist is reported and left out.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ writeReport : logical
+ write the decisions to output/<model id>_kcatDecisions.tsv (default
+ true).
+
+ Returns
+ -------
+ decisions : table
+ one row per model.ec reaction: ecRxn, stage (the stage number, or
+ custom, isozymes, standard, or empty when no kcat was found), source
+ (as in model.ec.source), kcat, candidates (the number of values the
+ deciding source had for the reaction, or of sources combined),
+ spreadLog10 (their range, in log10 units) and sdLog10 (the
+ uncertainty of a combined value).
+ report : struct
+ - preset: the preset used, or '' with stages;
+ - missing: source files the stages name that do not exist;
+ - sources: per source file loaded, its unmatched, partial and invalid
+ rows (see loadKcatSource).
+ ecModel : struct
+ the model with the decided kcats, before applyKcatConstraints; this
+ is what assignKcats returns, after applying the constraints.
+
+ Examples
+ --------
+ decisions = collectKcats(ecModel);
+ decisions(strcmp(decisions.stage, ''), :) % reactions without a kcat
+
+ See also
+ --------
+ assignKcats, updateKcatSources, loadKcatSource, mergeKcats, assignKcatValues
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
assignKcatValues assignKcatValues Select one kcat value per reaction and write to model.ec.
getStandardKcat getStandardKcat Assign standard kcat and MW to reactions without enzymes.
kcatAssignmentConfig kcatAssignmentConfig The kcat assignment settings of a model adapter.
loadKcatSource loadKcatSource Read a kcat source file and map its rows to an ecModel.
mergeKcats mergeKcats Merge any number of kcatLists, keeping the highest-priority
+This function is called by:
+
+
assignKcats assignKcats Assign kcats from the kcat source files and apply them.
0001 function [decisions, report, ecModel] = collectKcats(model, varargin)
+0002 % collectKcats Decide one kcat per reaction from the kcat source files.
+0003 %
+0004 % Combines the kcat source files in data/kcats (see kcatsFromBrenda,
+0005 % kcatsFromDLKcat, kcatsFromOKP and saveKcatSource) as [kcat_assignment] in
+0006 % model_adapter.toml sets, without changing the model given. assignKcats
+0007 % applies the decisions.
+0008 %
+0009 % The order is a preset or a list of stages:
+0010 %
+0011 % [kcat_assignment]
+0012 % preset = "brenda_ec_tree" # the default; or ec_tree_homologs, ec_tree_okp, okp,
+0013 % # gecko_median, gecko_classic
+0014 % isozymes = true # fill from isozymes after the stages
+0015 % standard = true # standard kcat for the rest
+0016 %
+0017 % # instead of a preset:
+0018 % [[kcat_assignment.stage]]
+0019 % action = "select" # or "combine"
+0020 % sources = ["custom", "database_top", "ec_tree"]
+0021 % criteria = "max" # select: among several values of one source
+0022 % top_origin_limit = 1
+0023 %
+0024 % [kcat_sources.okp]
+0025 % methods = ["CataPro", "UniKP"] # the okp source group
+0026 %
+0027 % A stage decides the reactions that are still open:
+0028 % - select: the first listed source with a value (see mergeKcats). Sources
+0029 % are the names of source files (custom, ec_tree, dlkcat, okp_catapro,
+0030 % ...) and the BRENDA quality tiers database_exact, database_top and
+0031 % database_bottom, which read brenda.tsv.
+0032 % - combine: all listed sources with a value, each corrected for its bias
+0033 % and weighted by 1 / its error variance, both for the source's evidence
+0034 % for that reaction (the EC tree: measurements of the EC and its EC
+0035 % level; an OKP method: the enzyme's similarity to the method's training
+0036 % sequences). The parameters come with the BRENDA files
+0037 % (kcatCombineParams.tsv). model.ec.source reads 'combined (sd=<log10
+0038 % sd>, n=<sources>)'; sd is the uncertainty for a new measurement.
+0039 % In both, okp stands for the OKP methods of [kcat_sources.okp] methods
+0040 % (default: all methods with combination parameters).
+0041 %
+0042 % The presets:
+0043 % - brenda_ec_tree (the default): custom kcats, the BRENDA median of
+0044 % measurements of the organism on the reaction's substrate (origin 1,
+0045 % without EC wildcards), the EC tree (kcatsFromECTree); then the OKP
+0046 % methods combined for the reactions left (without an EC number).
+0047 % - ec_tree_homologs: custom kcats, the organism's own BRENDA value; then
+0048 % the EC tree and homologs (kcatsFromHomologs) combined; then the OKP
+0049 % methods combined.
+0050 % - ec_tree_okp: custom kcats, the organism's own BRENDA value; then the EC
+0051 % tree, homologs and the OKP methods combined; then the OKP methods
+0052 % combined.
+0053 % - okp: custom kcats; then the OKP methods combined.
+0054 % - gecko_median: custom kcats, the BRENDA median of matches without EC
+0055 % wildcards from the organism or other organisms, with or without the
+0056 % substrate (origin 1 to 4), DLKcat, then the other BRENDA matches (EC
+0057 % wildcards, and specific activities, origin 5 and 6).
+0058 % - gecko_classic: as gecko_median with the BRENDA maximum and the
+0059 % specific-activity matches above DLKcat (origin up to 6), as the GECKO 3
+0060 % tutorial.
+0061 % Against measured kcats hidden from the search, the EC tree was closer
+0062 % than the BRENDA median at origins 2 and 4 and with EC wildcards and as
+0063 % close at origins 1 and 3; the EC tree combined with the OKP methods was
+0064 % slightly closer than the EC tree, and the OKP methods combined as close.
+0065 % In ecModels, values combined with predictions set high-turnover enzymes
+0066 % such as ATP synthase too low, which is why the default uses predictions
+0067 % only where there is no value from measurements. The BRENDA aggregation is
+0068 % [kcat_sources.brenda] aggregation (see updateKcatSources).
+0069 %
+0070 % After the stages:
+0071 % - custom kcats of data/customKcats.tsv are applied (applyCustomKcats),
+0072 % when a stage lists custom but there is no data/kcats/custom.tsv;
+0073 % - with isozymes, reactions without a kcat take one from their isozymes
+0074 % (getKcatAcrossIsozymes);
+0075 % - with standard, the remaining reactions get the standard kcat
+0076 % (getStandardKcat).
+0077 %
+0078 % A source file a stage names that does not exist is reported and left out.
+0079 %
+0080 % Parameters
+0081 % ----------
+0082 % model : struct
+0083 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0084 %
+0085 % Name-Value Arguments
+0086 % --------------------
+0087 % modelAdapter : ModelAdapter
+0088 % a loaded model adapter (default: the current default model adapter).
+0089 % writeReport : logical
+0090 % write the decisions to output/<model id>_kcatDecisions.tsv (default
+0091 % true).
+0092 %
+0093 % Returns
+0094 % -------
+0095 % decisions : table
+0096 % one row per model.ec reaction: ecRxn, stage (the stage number, or
+0097 % custom, isozymes, standard, or empty when no kcat was found), source
+0098 % (as in model.ec.source), kcat, candidates (the number of values the
+0099 % deciding source had for the reaction, or of sources combined),
+0100 % spreadLog10 (their range, in log10 units) and sdLog10 (the
+0101 % uncertainty of a combined value).
+0102 % report : struct
+0103 % - preset: the preset used, or '' with stages;
+0104 % - missing: source files the stages name that do not exist;
+0105 % - sources: per source file loaded, its unmatched, partial and invalid
+0106 % rows (see loadKcatSource).
+0107 % ecModel : struct
+0108 % the model with the decided kcats, before applyKcatConstraints; this
+0109 % is what assignKcats returns, after applying the constraints.
+0110 %
+0111 % Examples
+0112 % --------
+0113 % decisions = collectKcats(ecModel);
+0114 % decisions(strcmp(decisions.stage, ''), :) % reactions without a kcat
+0115 %
+0116 % See also
+0117 % --------
+0118 % assignKcats, updateKcatSources, loadKcatSource, mergeKcats, assignKcatValues
+0119
+0120 checkOptions(varargin, {'modelAdapter','writeReport'}, 'collectKcats');
+0121 p = parseGECKOargs(varargin, {'modelAdapter', []; 'writeReport', true});
+0122 modelAdapter = p.modelAdapter;
+0123 if isempty(modelAdapter)
+0124 modelAdapter = ModelAdapterManager.getDefault();
+0125 if isempty(modelAdapter)
+0126 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0127 end
+0128 end
+0129 params = modelAdapter.params;
+0130 config = kcatAssignmentConfig(params);
+0131 kcatDir = fullfile(params.path, 'data', 'kcats');
+0132
+0133 ecModel = model;
+0134 nEc = numel(ecModel.ec.rxns);
+0135 ecModel.ec.kcat(:) = 0;
+0136 ecModel.ec.source(:) = {''};
+0137 stage = repmat({''}, nEc, 1);
+0138 candidates = zeros(nEc, 1);
+0139 spread = nan(nEc, 1);
+0140 sd = nan(nEc, 1);
+0141 combineParams = [];
+0142
+0143 report.preset = config.preset;
+0144 report.missing = {};
+0145 report.sources = struct();
+0146 loaded = struct();
+0147 legacyCustom = false;
+0148
+0149 function list = loadSource(name)
+0150 % The kcat list of a source file, or [] when it does not exist.
+0151 list = [];
+0152 file = fullfile(kcatDir, [name '.tsv']);
+0153 if isfile(file)
+0154 [list, rep] = loadKcatSource(ecModel, file);
+0155 report.sources.(name) = rep;
+0156 elseif strcmp(name, 'custom') && isfile(fullfile(params.path, 'data', 'customKcats.tsv'))
+0157 legacyCustom = true;
+0158 else
+0159 report.missing{end+1} = name;
+0160 printOrange(sprintf(['No kcat source file %s; it is left out. '...
+0161 'updateKcatSources gathers it.\n'], file));
+0162 end
+0163 end
+0164
+0165 for s = 1:numel(config.stages)
+0166 st = config.stages(s);
+0167 if strcmp(st.action, 'combine')
+0168 names = {};
+0169 lists = {};
+0170 for t = 1:numel(st.files)
+0171 name = st.files{t};
+0172 if ~isfield(loaded, name)
+0173 loaded.(name) = loadSource(name);
+0174 end
+0175 if ~isempty(loaded.(name))
+0176 names{end+1} = name; %#ok<AGROW>
+0177 lists{end+1} = loaded.(name); %#ok<AGROW>
+0178 end
+0179 end
+0180 if isempty(names)
+0181 continue
+0182 end
+0183 if isempty(combineParams)
+0184 combineParams = loadCombineParams(modelAdapter);
+0185 end
+0186 openRxns = ecModel.ec.rxns(ecModel.ec.kcat == 0);
+0187 c = combineKcats(names, lists, openRxns, combineParams);
+0188 if isempty(c.rxns)
+0189 continue
+0190 end
+0191 [~, idx] = ismember(c.rxns, ecModel.ec.rxns);
+0192 ecModel.ec.kcat(idx) = c.kcats;
+0193 ecModel.ec.source(idx) = cellstr(compose('combined (sd=%.2f, n=%d)', c.sd, c.n));
+0194 stage(idx) = {sprintf('%d', s)};
+0195 candidates(idx) = c.n;
+0196 spread(idx) = c.spread;
+0197 sd(idx) = c.sd;
+0198 continue
+0199 end
+0200 lists = {};
+0201 priority = {};
+0202 for t = 1:numel(st.files)
+0203 token = st.files{t};
+0204 name = sourceFile(token);
+0205 if ~isfield(loaded, name)
+0206 loaded.(name) = loadSource(name);
+0207 end
+0208 list = loaded.(name);
+0209 if isempty(list)
+0210 continue
+0211 end
+0212 if ~any(cellfun(@(l) isequal(l, list), lists))
+0213 lists{end+1} = list; %#ok<AGROW>
+0214 end
+0215 if startsWith(token, 'database_')
+0216 priority{end+1} = token; %#ok<AGROW>
+0217 else
+0218 % The labels of the file's rows, at the file's place in the order.
+0219 labels = unique(cellfun(@normalizeSource, list.kcatSource, 'UniformOutput', false), 'stable');
+0220 priority = [priority, labels(~ismember(labels, {'brenda','sabio_rk'}))']; %#ok<AGROW>
+0221 end
+0222 end
+0223 if isempty(lists) || isempty(priority)
+0224 continue
+0225 end
+0226 merged = mergeKcats(lists, priority, 'topOriginLimit', st.topOriginLimit, ...
+0227 'bottomOriginLimit', st.bottomOriginLimit, 'wildcardLimit', st.wildcardLimit);
+0228 if isempty(merged.rxns)
+0229 continue
+0230 end
+0231 open = ecModel.ec.kcat == 0;
+0232 [ecModel, idx] = assignKcatValues(ecModel, merged, 'criteria', st.criteria, 'overwrite', 'false');
+0233 idx = idx(open(idx));
+0234 stage(idx) = {sprintf('%d', s)};
+0235 [~, where] = ismember(merged.rxns, ecModel.ec.rxns);
+0236 valid = merged.kcats > 0;
+0237 for i = idx(:)'
+0238 values = merged.kcats(where == i & valid);
+0239 candidates(i) = numel(values);
+0240 spread(i) = log10(max(values)) - log10(min(values));
+0241 end
+0242 end
+0243
+0244 if legacyCustom
+0245 before = ecModel.ec.kcat;
+0246 ecModel = applyCustomKcats(ecModel, 'modelAdapter', modelAdapter);
+0247 changed = ecModel.ec.kcat ~= before;
+0248 stage(changed) = {'custom'};
+0249 candidates(changed) = 1;
+0250 spread(changed) = 0;
+0251 end
+0252 if config.isozymes && ~ecModel.ec.geckoLight
+0253 before = ecModel.ec.kcat == 0;
+0254 ecModel = getKcatAcrossIsozymes(ecModel);
+0255 stage(before & ecModel.ec.kcat > 0) = {'isozymes'};
+0256 end
+0257 if config.standard
+0258 ecModel = getStandardKcat(ecModel, 'modelAdapter', modelAdapter);
+0259 n = numel(ecModel.ec.rxns);
+0260 stage(end+1:n) = {''};
+0261 candidates(end+1:n) = 0;
+0262 spread(end+1:n) = NaN;
+0263 sd(end+1:n) = NaN;
+0264 stage(strcmp(ecModel.ec.source, 'standard') & ecModel.ec.kcat > 0) = {'standard'};
+0265 end
+0266
+0267 decisions = table(ecModel.ec.rxns(:), stage(:), ecModel.ec.source(:), ecModel.ec.kcat(:), ...
+0268 candidates(:), spread(:), sd(:), 'VariableNames', {'ecRxn','stage','source','kcat','candidates', ...
+0269 'spreadLog10','sdLog10'});
+0270 if p.writeReport
+0271 outDir = fullfile(params.path, 'output');
+0272 if ~isfolder(outDir)
+0273 mkdir(outDir);
+0274 end
+0275 id = 'ecModel';
+0276 if isfield(model, 'id') && ~isempty(model.id)
+0277 id = char(model.id);
+0278 end
+0279 writetable(decisions, fullfile(outDir, [id '_kcatDecisions.tsv']), 'FileType', 'text', ...
+0280 'Delimiter', '\t');
+0281 end
+0282 nOpen = sum(strcmp(stage, ''));
+0283 fprintf('Kcats decided for %d of %d reactions.\n', numel(stage) - nOpen, numel(stage));
+0284 end
+0285
+0286 function s = normalizeSource(label)
+0287 % As mergeKcats folds source labels: lower case, other characters as '_'.
+0288 s = lower(strtrim(char(label)));
+0289 s = regexprep(s, '[^0-9a-z]+', '_');
+0290 s = regexprep(s, '^_+|_+$', '');
+0291 end
+0292
+0293 function name = sourceFile(token)
+0294 % The source file a stage token reads: the BRENDA tiers read brenda.tsv.
+0295 if startsWith(token, 'database_')
+0296 name = 'brenda';
+0297 else
+0298 name = token;
+0299 end
+0300 end
+0301
+0302 function checkOptions(args, names, functionName)
+0303 % Options are name-value pairs with known names, so a misspelled option is
+0304 % an error instead of being taken as a positional argument.
+0305 id = [functionName ':arguments'];
+0306 given = args(1:2:end);
+0307 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0308 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0309 end
+0310 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0311 if ~isempty(unknown)
+0312 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0313 strjoin(names, ', '))
+0314 end
+0315 end
+0316
+0317 function params = loadCombineParams(modelAdapter)
+0318 % kcatCombineParams.tsv of the BRENDA folder, or of GECKO's BRENDA release.
+0319 file = fullfile(modelAdapter.getBrendaDBFolder(), 'kcatCombineParams.tsv');
+0320 if ~isfile(file)
+0321 file = fullfile(fetchBrendaAsset({'kcatCombineParams.tsv'}), 'kcatCombineParams.tsv');
+0322 end
+0323 lines = strsplit(fileread(file), {'\r\n', '\n'});
+0324 params.measurementSd = 0.48;
+0325 params.minVariance = 0.01;
+0326 params.scaleKeys = {};
+0327 params.scales = [];
+0328 k = 1;
+0329 while k <= numel(lines) && startsWith(lines{k}, '#')
+0330 tok = regexp(lines{k}, '^#\s*([^:]+):\s*(.*)$', 'tokens', 'once');
+0331 if ~isempty(tok)
+0332 key = strtrim(tok{1});
+0333 value = str2double(strtrim(tok{2}));
+0334 if strcmp(key, 'measurement_sd')
+0335 params.measurementSd = value;
+0336 elseif strcmp(key, 'min_variance')
+0337 params.minVariance = value;
+0338 elseif startsWith(key, 'interval_scale ')
+0339 params.scaleKeys{end+1} = key(numel('interval_scale ')+1:end);
+0340 params.scales(end+1) = value;
+0341 end
+0342 end
+0343 k = k + 1;
+0344 end
+0345 header = strsplit(lines{k}, '\t');
+0346 rows = lines(k+1:end);
+0347 rows = rows(~cellfun(@isempty, strtrim(rows)));
+0348 fields = cellfun(@(r) strsplit(r, '\t', 'CollapseDelimiters', false), rows, 'UniformOutput', false);
+0349 fields = vertcat(fields{:});
+0350 col = @(name) fields(:, strcmp(header, name));
+0351 params.source = col('source');
+0352 params.bin = col('bin');
+0353 params.bias = str2double(col('bias'));
+0354 params.variance = str2double(col('variance'));
+0355 end
+0356
+0357 function c = combineKcats(names, lists, openRxns, params)
+0358 % One kcat per reaction from several sources: each source's value corrected
+0359 % for its bias and weighted by 1 / its error variance, both for the
+0360 % source's evidence bin (see evidenceBin). Several rows of one source for a
+0361 % reaction give their median, with the bin of best evidence; a value
+0362 % without a known bin gets the source's largest variance. geckopy's
+0363 % combine_kcats does the same.
+0364 unknown = setdiff(names, params.source);
+0365 if ~isempty(unknown)
+0366 error('collectKcats:combine', 'No combination parameters for %s; known sources: %s.', ...
+0367 strjoin(unknown, ', '), strjoin(unique(params.source), ', '))
+0368 end
+0369 R = {}; S = {}; X = []; W = []; V = [];
+0370 for k = 1:numel(names)
+0371 name = names{k};
+0372 list = lists{k};
+0373 keep = ismember(list.rxns, openRxns) & list.kcats > 0 & isfinite(list.kcats);
+0374 rxns = list.rxns(keep);
+0375 values = log10(list.kcats(keep));
+0376 details = list.detail(keep);
+0377 mine = strcmp(params.source, name);
+0378 srcBins = params.bin(mine);
+0379 srcBias = params.bias(mine);
+0380 srcVar = params.variance(mine);
+0381 [~, worst] = max(srcVar);
+0382 [u, ~, g] = unique(rxns, 'stable');
+0383 for r = 1:numel(u)
+0384 rows = find(g == r);
+0385 bins = cellfun(@(d) evidenceBin(name, d), details(rows), 'UniformOutput', false);
+0386 [found, where] = ismember(bins, srcBins);
+0387 if any(found)
+0388 [~, best] = min(srcVar(where(found)));
+0389 pick = where(found);
+0390 pick = pick(best);
+0391 else
+0392 pick = worst;
+0393 end
+0394 R{end+1, 1} = u{r}; %#ok<AGROW>
+0395 S{end+1, 1} = name; %#ok<AGROW>
+0396 V(end+1, 1) = median(values(rows)); %#ok<AGROW>
+0397 X(end+1, 1) = median(values(rows)) - srcBias(pick); %#ok<AGROW>
+0398 W(end+1, 1) = 1 / max(srcVar(pick), params.minVariance); %#ok<AGROW>
+0399 end
+0400 end
+0401 [u, ~, g] = unique(R, 'stable');
+0402 c.rxns = u;
+0403 n = numel(u);
+0404 c.kcats = zeros(n, 1); c.sd = zeros(n, 1); c.n = zeros(n, 1); c.spread = zeros(n, 1);
+0405 for r = 1:n
+0406 rows = g == r;
+0407 w = W(rows);
+0408 c.kcats(r) = 10 ^ (sum(w .* X(rows)) / sum(w));
+0409 srcs = S(rows);
+0410 hasOkp = any(startsWith(srcs, 'okp_'));
+0411 hasTree = any(strcmp(srcs, 'ec_tree'));
+0412 if hasTree && hasOkp
+0413 key = 'ec_tree+okp';
+0414 elseif hasOkp
+0415 key = 'okp';
+0416 elseif hasTree
+0417 key = 'ec_tree+homologs';
+0418 else
+0419 key = '';
+0420 end
+0421 scale = 1;
+0422 hit = strcmp(params.scaleKeys, key);
+0423 if any(hit)
+0424 scale = params.scales(find(hit, 1));
+0425 end
+0426 c.sd(r) = sqrt(1 / sum(w) + params.measurementSd ^ 2) * scale;
+0427 c.n(r) = sum(rows);
+0428 c.spread(r) = max(V(rows)) - min(V(rows));
+0429 end
+0430 end
+0431
+0432 function b = evidenceBin(source, detail)
+0433 % The evidence bin of a source's value, from its detail column.
+0434 b = '';
+0435 if strcmp(source, 'ec_tree')
+0436 n = regexp(detail, 'n=(\d+)', 'tokens', 'once');
+0437 depth = regexp(detail, 'depth=(\d)', 'tokens', 'once');
+0438 if ~isempty(depth) && strcmp(depth{1}, '4') && ~isempty(n)
+0439 v = str2double(n{1});
+0440 edges = [2 5 20 100];
+0441 labels = {'1-2', '3-5', '6-20', '21-100', '>100'};
+0442 b = ['ec4:' labels{find([v <= edges, true], 1)}];
+0443 elseif ~isempty(depth)
+0444 b = ['ec' depth{1}];
+0445 else
+0446 b = 'ec0';
+0447 end
+0448 elseif strcmp(source, 'homologs')
+0449 t = regexp(detail, 'identity=([\d.]+)', 'tokens', 'once');
+0450 if ~isempty(t) && str2double(t{1}) >= 0.95
+0451 b = 'exact'; % the same protein: an assumed bin, see kcatCombineParams.tsv
+0452 elseif ~isempty(t)
+0453 labels = {'40', '50', '60', '70', '80', '90'};
+0454 b = labels{find([str2double(t{1}) <= [0.5 0.6 0.7 0.8 0.9], true], 1)};
+0455 end
+0456 elseif startsWith(source, 'okp_')
+0457 t = regexp(detail, 'similarity=([\d.]+)', 'tokens', 'once');
+0458 if ~isempty(t)
+0459 labels = {'<40', '40-60', '60-80', '>80'};
+0460 b = labels{find([str2double(t{1}) <= [40 60 80], true], 1)};
+0461 end
+0462 end
+0463 end
convertCustomKcats Convert data/customKcats.tsv into data/kcats/custom.tsv.
+
+
SYNOPSIS
+
function [file, report] = convertCustomKcats(model, varargin)
+
+
DESCRIPTION
+
convertCustomKcats Convert data/customKcats.tsv into data/kcats/custom.tsv.
+
+ Writes the custom kcats of a GECKO 3 project as the curated kcat source
+ file data/kcats/custom.tsv, which collectKcats reads first. The kcats are
+ those applyCustomKcats gives the ecModel, including which of two entries
+ for the same reaction wins: one row per reaction, keyed by conventional
+ reaction and enzyme set (see getEcRxnKeys), with the entry's notes in the
+ note column. The stoicho column is left out: complex stoichiometry comes
+ from applyComplexData. Unlike applyCustomKcats, custom.tsv also applies to
+ light ecModels. geckopy has the same (convert_custom_kcats).
+
+ Parameters
+ ----------
+ model : struct
+ the full ecModel the custom kcats are for (with EC data).
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ customKcats : char
+ the file to convert (default: data/customKcats.tsv in the project).
+ outFile : char
+ the file to write (default: data/kcats/custom.tsv in the project). An
+ existing file is not overwritten.
+
+ Returns
+ -------
+ file : char
+ the file written.
+ report : struct
+ - rows: number of rows written;
+ - partial: reactions whose enzymes only partly match an entry, which
+ applyCustomKcats does not change either (its notMatch).
+
+ Examples
+ --------
+ convertCustomKcats(ecModel); % then collectKcats/assignKcats read custom.tsv
+
+ See also
+ --------
+ collectKcats, applyCustomKcats, getEcRxnKeys, upgradeGECKOproject
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
getEcRxnKeys getEcRxnKeys Build-independent keys of the model.ec reactions.
0001 function [file, report] = convertCustomKcats(model, varargin)
+0002 % convertCustomKcats Convert data/customKcats.tsv into data/kcats/custom.tsv.
+0003 %
+0004 % Writes the custom kcats of a GECKO 3 project as the curated kcat source
+0005 % file data/kcats/custom.tsv, which collectKcats reads first. The kcats are
+0006 % those applyCustomKcats gives the ecModel, including which of two entries
+0007 % for the same reaction wins: one row per reaction, keyed by conventional
+0008 % reaction and enzyme set (see getEcRxnKeys), with the entry's notes in the
+0009 % note column. The stoicho column is left out: complex stoichiometry comes
+0010 % from applyComplexData. Unlike applyCustomKcats, custom.tsv also applies to
+0011 % light ecModels. geckopy has the same (convert_custom_kcats).
+0012 %
+0013 % Parameters
+0014 % ----------
+0015 % model : struct
+0016 % the full ecModel the custom kcats are for (with EC data).
+0017 %
+0018 % Name-Value Arguments
+0019 % --------------------
+0020 % modelAdapter : ModelAdapter
+0021 % a loaded model adapter (default: the current default model adapter).
+0022 % customKcats : char
+0023 % the file to convert (default: data/customKcats.tsv in the project).
+0024 % outFile : char
+0025 % the file to write (default: data/kcats/custom.tsv in the project). An
+0026 % existing file is not overwritten.
+0027 %
+0028 % Returns
+0029 % -------
+0030 % file : char
+0031 % the file written.
+0032 % report : struct
+0033 % - rows: number of rows written;
+0034 % - partial: reactions whose enzymes only partly match an entry, which
+0035 % applyCustomKcats does not change either (its notMatch).
+0036 %
+0037 % Examples
+0038 % --------
+0039 % convertCustomKcats(ecModel); % then collectKcats/assignKcats read custom.tsv
+0040 %
+0041 % See also
+0042 % --------
+0043 % collectKcats, applyCustomKcats, getEcRxnKeys, upgradeGECKOproject
+0044
+0045 checkOptions(varargin, {'modelAdapter','customKcats','outFile'}, 'convertCustomKcats');
+0046 p = parseGECKOargs(varargin, {'modelAdapter', []; 'customKcats', []; 'outFile', []});
+0047 modelAdapter = p.modelAdapter;
+0048 if isempty(modelAdapter)
+0049 modelAdapter = ModelAdapterManager.getDefault();
+0050 if isempty(modelAdapter)
+0051 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0052 end
+0053 end
+0054 if model.ec.geckoLight
+0055 error('convertCustomKcats:light', ['applyCustomKcats does not work on light ecModels; give '...
+0056 'the full ecModel. The custom.tsv written applies to light ecModels too.'])
+0057 end
+0058 inFile = p.customKcats;
+0059 if isempty(inFile)
+0060 inFile = fullfile(modelAdapter.params.path, 'data', 'customKcats.tsv');
+0061 end
+0062 file = p.outFile;
+0063 if isempty(file)
+0064 file = fullfile(modelAdapter.params.path, 'data', 'kcats', 'custom.tsv');
+0065 end
+0066 if ~isfile(inFile)
+0067 error('convertCustomKcats:missing', 'No custom kcats file %s.', inFile)
+0068 end
+0069 if isfile(file)
+0070 error('convertCustomKcats:exists', '%s exists and is not overwritten.', file)
+0071 end
+0072
+0073 base = model;
+0074 base.ec.notes(:) = {''};
+0075 [applied, rxnUpdated, notMatch] = applyCustomKcats(base, 'customKcats', inFile, 'modelAdapter', modelAdapter);
+0076 idx = find(ismember(applied.ec.rxns, rxnUpdated));
+0077 [baseRxns, enzymeSets] = getEcRxnKeys(applied);
+0078 rows = strings(numel(idx), 9);
+0079 for k = 1:numel(idx)
+0080 i = idx(k);
+0081 note = regexprep(applied.ec.notes{i}, '[\t\r\n]', ' ');
+0082 rows(k, :) = [string(baseRxns{i}), string(enzymeSets{i}), "", formatNumber(applied.ec.kcat(i)), ...
+0083 "", "custom", "", "", string(note)];
+0084 end
+0085 % Isozymic copies of one conventional reaction share a key and a value.
+0086 if ~isempty(rows)
+0087 [~, first] = unique(join(rows(:, 1:2), char(1), 2), 'stable');
+0088 rows = rows(sort(first), :);
+0089 [~, order] = sort(join(rows(:, [1 2 3 6 4 5 7 8 9]), char(1), 2));
+0090 rows = rows(order, :);
+0091 end
+0092
+0093 folder = fileparts(file);
+0094 if ~isfolder(folder)
+0095 mkdir(folder);
+0096 end
+0097 gecko = strtrim(fileread(fullfile(findGECKOroot(), 'version.txt')));
+0098 [~, name, ext] = fileparts(inFile);
+0099 fid = fopen(file, 'w', 'n', 'UTF-8');
+0100 fprintf(fid, '# source: custom\n');
+0101 fprintf(fid, '# generator: convertCustomKcats (GECKO %s)\n', gecko);
+0102 fprintf(fid, '# parameters: {}\n');
+0103 fprintf(fid, '# data: %s\n', [name ext]);
+0104 fprintf(fid, '# kind: curated\n');
+0105 fprintf(fid, '# created: %s\n', char(datetime('now', 'TimeZone', 'UTC', 'Format', 'yyyy-MM-dd''T''HH:mm:ss''Z''')));
+0106 fprintf(fid, 'rxn\tenzymes\tsubstrate\tkcat\tlog10_sd\tsource\tdetail\tec_code\tnote\n');
+0107 if ~isempty(rows)
+0108 fprintf(fid, '%s\n', join(rows, sprintf('\t'), 2));
+0109 end
+0110 fclose(fid);
+0111 report.rows = size(rows, 1);
+0112 report.partial = notMatch;
+0113 fprintf('%d custom kcats written to %s.\n', report.rows, file);
+0114 end
+0115
+0116 function t = formatNumber(x)
+0117 % The shortest text that reads back as x, as saveKcatSource writes numbers.
+0118 if x == round(x) && abs(x) < 1e15
+0119 t = string(sprintf('%d', x));
+0120 return
+0121 end
+0122 for digits = 1:17
+0123 t = string(sprintf(sprintf('%%.%dg', digits), x));
+0124 if str2double(t) == x
+0125 return
+0126 end
+0127 end
+0128 end
+0129
+0130 function checkOptions(args, names, functionName)
+0131 % Options are name-value pairs with known names, so a misspelled option is
+0132 % an error instead of being taken as a positional argument.
+0133 id = [functionName ':arguments'];
+0134 given = args(1:2:end);
+0135 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0136 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0137 end
+0138 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0139 if ~isempty(unknown)
+0140 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0141 strjoin(names, ', '))
+0142 end
+0143 end
ecStatsFromBrenda Summarise loadBRENDAdata's output per EC number, for
+
+
SYNOPSIS
+
function ecStats = ecStatsFromBrenda(KCATcell,SAcell)
+
+
DESCRIPTION
+
ecStatsFromBrenda Summarise loadBRENDAdata's output per EC number, for
+ reviewKcatAssignment's ecStats argument.
+
+ TODO: verify against a real BRENDA pull before relying on this. Ported
+ from geckopy's ec_stats_from_brenda without being able to run it
+ against real BRENDA data in this environment. In particular,
+ loadBRENDAdata's KCATcell only carries the BRENDA *max* aggregate per
+ (EC, substrate, organism) triple (column 4 of kcat.tsv) -- the median
+ column is read internally but discarded before KCATcell is returned --
+ so kcatMedian below is always NaN, and reviewKcatAssignment's
+ "ec-maximum" check (which needs a finite kcatMedian) will never fire
+ until loadBRENDAdata is extended to also expose it.
+
+ Parameters
+ ----------
+ KCATcell : cell
+ as returned by loadBRENDAdata's first output: {ecCode, substrate,
+ organism, kcatMax}.
+ SAcell : cell
+ as returned by loadBRENDAdata's second output: {ecCode, organism,
+ kcatFromSA, mw}.
+
+ Returns
+ -------
+ ecStats : containers.Map
+ EC code -> struct(nKcat,kcatMax,kcatMedian,values,nSa), keyed by
+ the same (uppercased) EC strings BRENDA uses.
+
+ See also
+ --------
+ reviewKcatAssignment, loadBRENDAdata
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function ecStats = ecStatsFromBrenda(KCATcell,SAcell)
+0002 % ecStatsFromBrenda Summarise loadBRENDAdata's output per EC number, for
+0003 % reviewKcatAssignment's ecStats argument.
+0004 %
+0005 % TODO: verify against a real BRENDA pull before relying on this. Ported
+0006 % from geckopy's ec_stats_from_brenda without being able to run it
+0007 % against real BRENDA data in this environment. In particular,
+0008 % loadBRENDAdata's KCATcell only carries the BRENDA *max* aggregate per
+0009 % (EC, substrate, organism) triple (column 4 of kcat.tsv) -- the median
+0010 % column is read internally but discarded before KCATcell is returned --
+0011 % so kcatMedian below is always NaN, and reviewKcatAssignment's
+0012 % "ec-maximum" check (which needs a finite kcatMedian) will never fire
+0013 % until loadBRENDAdata is extended to also expose it.
+0014 %
+0015 % Parameters
+0016 % ----------
+0017 % KCATcell : cell
+0018 % as returned by loadBRENDAdata's first output: {ecCode, substrate,
+0019 % organism, kcatMax}.
+0020 % SAcell : cell
+0021 % as returned by loadBRENDAdata's second output: {ecCode, organism,
+0022 % kcatFromSA, mw}.
+0023 %
+0024 % Returns
+0025 % -------
+0026 % ecStats : containers.Map
+0027 % EC code -> struct(nKcat,kcatMax,kcatMedian,values,nSa), keyed by
+0028 % the same (uppercased) EC strings BRENDA uses.
+0029 %
+0030 % See also
+0031 % --------
+0032 % reviewKcatAssignment, loadBRENDAdata
+0033 ecCol = upper(KCATcell{1});
+0034 maxCol = KCATcell{4};
+0035
+0036 saEcCol = {};
+0037 if ~isempty(SAcell) && numel(SAcell) >= 1 && ~isempty(SAcell{1})
+0038 saEcCol = upper(SAcell{1});
+0039 end
+0040
+0041 ecList = unique([ecCol(:); saEcCol(:)]);
+0042 ecStats = containers.Map('KeyType','char','ValueType','any');
+0043 for i = 1:numel(ecList)
+0044 ec = char(ecList{i});
+0045 rows = strcmp(ecCol,ec);
+0046 nKcat = sum(rows);
+0047 if nKcat > 0
+0048 kcatMax = max(maxCol(rows));
+0049 values = unique(round(maxCol(rows),6));
+0050 else
+0051 kcatMax = NaN;
+0052 values = [];
+0053 end
+0054 nSa = sum(strcmp(saEcCol,ec));
+0055 ecStats(ec) = struct('nKcat',nKcat,'kcatMax',kcatMax,'kcatMedian',NaN,'values',values,'nSa',nSa);
+0056 end
+0057 end
ecTreeEstimate EC-tree kcat estimates for EC numbers.
+
+
SYNOPSIS
+
function [log10kcat, n, depth, ecUsed] = ecTreeEstimate(eccodes, measurements, k)
+
+
DESCRIPTION
+
ecTreeEstimate EC-tree kcat estimates for EC numbers.
+
+ For each EC number, the median log10 kcat of its measurements is pulled
+ toward the estimate of its parent class with weight n / (n + k), where n
+ is the number of measurements: the global median, then the 2-digit
+ class, the 3-digit class and the full EC number, in that order. A
+ well-measured EC keeps its own median; a sparsely measured one leans on
+ its class. geckopy has the same (geckopy.gather_kcats.ec_tree.ECTree).
+
+ Parameters
+ ----------
+ eccodes : cell
+ per query one or more EC numbers separated by ';'. The one with most
+ measurements of its full EC is used (the first on a tie).
+ measurements : struct
+ BRENDA measurement table with fields ec and log10kcat (see
+ loadBrendaMeasurements).
+ k : double
+ shrinkage constant (default 5): the number of measurements at which
+ an EC's own median and its class count equally.
+
+ Returns
+ -------
+ log10kcat : double
+ the estimates; the global median for queries without measurements.
+ n : double
+ number of measurements of the full EC number used.
+ depth : double
+ deepest EC level with measurements: 4, 3, 2, or 0 when there are
+ none (or no EC number).
+ ecUsed : cell
+ the EC number used.
+
+ Examples
+ --------
+ m = loadBrendaMeasurements();
+ [v, n, depth] = ecTreeEstimate({'1.1.1.1'; '2.7.1.1;2.7.1.2'}, m);
+ kcat = 10.^v;
+
+ See also
+ --------
+ kcatsFromECTree, loadBrendaMeasurements
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
kcatsFromECTree kcatsFromECTree Estimate kcats from the EC tree and keep them as a source file.
0001 function [log10kcat, n, depth, ecUsed] = ecTreeEstimate(eccodes, measurements, k)
+0002 % ecTreeEstimate EC-tree kcat estimates for EC numbers.
+0003 %
+0004 % For each EC number, the median log10 kcat of its measurements is pulled
+0005 % toward the estimate of its parent class with weight n / (n + k), where n
+0006 % is the number of measurements: the global median, then the 2-digit
+0007 % class, the 3-digit class and the full EC number, in that order. A
+0008 % well-measured EC keeps its own median; a sparsely measured one leans on
+0009 % its class. geckopy has the same (geckopy.gather_kcats.ec_tree.ECTree).
+0010 %
+0011 % Parameters
+0012 % ----------
+0013 % eccodes : cell
+0014 % per query one or more EC numbers separated by ';'. The one with most
+0015 % measurements of its full EC is used (the first on a tie).
+0016 % measurements : struct
+0017 % BRENDA measurement table with fields ec and log10kcat (see
+0018 % loadBrendaMeasurements).
+0019 % k : double
+0020 % shrinkage constant (default 5): the number of measurements at which
+0021 % an EC's own median and its class count equally.
+0022 %
+0023 % Returns
+0024 % -------
+0025 % log10kcat : double
+0026 % the estimates; the global median for queries without measurements.
+0027 % n : double
+0028 % number of measurements of the full EC number used.
+0029 % depth : double
+0030 % deepest EC level with measurements: 4, 3, 2, or 0 when there are
+0031 % none (or no EC number).
+0032 % ecUsed : cell
+0033 % the EC number used.
+0034 %
+0035 % Examples
+0036 % --------
+0037 % m = loadBrendaMeasurements();
+0038 % [v, n, depth] = ecTreeEstimate({'1.1.1.1'; '2.7.1.1;2.7.1.2'}, m);
+0039 % kcat = 10.^v;
+0040 %
+0041 % See also
+0042 % --------
+0043 % kcatsFromECTree, loadBrendaMeasurements
+0044
+0045 if nargin < 3 || isempty(k)
+0046 k = 5;
+0047 end
+0048 if ~(isnumeric(k) && isscalar(k) && k > 0)
+0049 error('ecTreeEstimate:k', 'k must be a positive number.')
+0050 end
+0051 if ischar(eccodes) || isstring(eccodes)
+0052 eccodes = cellstr(eccodes);
+0053 end
+0054 ec = measurements.ec(:);
+0055 v = measurements.log10kcat(:);
+0056 globalMedian = median(v);
+0057 lv = cell(4, 1);
+0058 for d = [2 3 4]
+0059 if d == 4
+0060 key = ec;
+0061 else
+0062 key = cellfun(@(e) parentEC(e, d), ec, 'UniformOutput', false);
+0063 end
+0064 [keys, ~, g] = unique(key);
+0065 lv{d} = struct('keys', {keys}, 'median', accumarray(g, v, [], @median), ...
+0066 'n', accumarray(g, 1));
+0067 end
+0068
+0069 nq = numel(eccodes);
+0070 log10kcat = repmat(globalMedian, nq, 1);
+0071 n = zeros(nq, 1);
+0072 depth = zeros(nq, 1);
+0073 ecUsed = repmat({''}, nq, 1);
+0074 for q = 1:nq
+0075 ecs = strtrim(strsplit(char(eccodes{q}), ';'));
+0076 ecs = ecs(~cellfun(@isempty, ecs));
+0077 if isempty(ecs)
+0078 continue
+0079 end
+0080 best = ecs{1};
+0081 bestN = -1;
+0082 for e = 1:numel(ecs)
+0083 c = countOf(lv{4}, ecs{e});
+0084 if c > bestN
+0085 best = ecs{e};
+0086 bestN = c;
+0087 end
+0088 end
+0089 value = globalMedian;
+0090 for d = [2 3 4]
+0091 if d == 4
+0092 key = best;
+0093 else
+0094 key = parentEC(best, d);
+0095 end
+0096 [c, med] = countOf(lv{d}, key);
+0097 if c > 0
+0098 w = c / (c + k);
+0099 value = w * med + (1 - w) * value;
+0100 depth(q) = d;
+0101 end
+0102 end
+0103 log10kcat(q) = value;
+0104 n(q) = bestN;
+0105 ecUsed{q} = best;
+0106 end
+0107 end
+0108
+0109 function p = parentEC(ec, d)
+0110 parts = strsplit(ec, '.');
+0111 p = strjoin(parts(1:min(d, numel(parts))), '.');
+0112 end
+0113
+0114 function [c, med] = countOf(level, key)
+0115 [found, i] = ismember(key, level.keys);
+0116 c = 0;
+0117 med = NaN;
+0118 if found
+0119 c = level.n(i);
+0120 med = level.median(i);
+0121 end
+0122 end
extractEnzymeSubstratePairs The enzyme-substrate pairs of an ecModel's reactions.
+
+
SYNOPSIS
+
function pairs = extractEnzymeSubstratePairs(model, varargin)
+
+
DESCRIPTION
+
extractEnzymeSubstratePairs The enzyme-substrate pairs of an ecModel's reactions.
+
+ Lists, for each model.ec reaction, its enzymes and its substrates as
+ DLKcat and the homolog search use them: the reactants of the reaction,
+ without the metabolites of DLKcatIgnoreMets.tsv (metal ions, small
+ molecules), without protein pseudometabolites, and without currency
+ metabolites of DLKcatCurrencyMets.tsv where both members of a pair take
+ part (unless no other substrate remains). Both files are read from the
+ data folder of the project, or else from the databases folder of GECKO.
+ geckopy has the same (extract_enzyme_substrate_pairs).
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ ecRxns : logical
+ which model.ec.rxns to list (default: all).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ pairs : struct
+ ecRxn (index into model.ec.rxns), protein (index into model.ec.genes,
+ model.ec.enzymes and model.ec.sequence) and met (index into
+ model.mets), one entry per pair, reaction by reaction.
+
+ Examples
+ --------
+ pairs = extractEnzymeSubstratePairs(ecModel);
+ model.metNames(pairs.met(1:5))
+
+ See also
+ --------
+ writeDLKcatInput, kcatsFromHomologs
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
kcatsFromHomologs kcatsFromHomologs Estimate kcats from measured homologs and keep them as a source file.
writeDLKcatInput writeDLKcatInput Prepare and write the input file for DLKcat.
+
+
+
+
+
SOURCE CODE
+
0001 function pairs = extractEnzymeSubstratePairs(model, varargin)
+0002 % extractEnzymeSubstratePairs The enzyme-substrate pairs of an ecModel's reactions.
+0003 %
+0004 % Lists, for each model.ec reaction, its enzymes and its substrates as
+0005 % DLKcat and the homolog search use them: the reactants of the reaction,
+0006 % without the metabolites of DLKcatIgnoreMets.tsv (metal ions, small
+0007 % molecules), without protein pseudometabolites, and without currency
+0008 % metabolites of DLKcatCurrencyMets.tsv where both members of a pair take
+0009 % part (unless no other substrate remains). Both files are read from the
+0010 % data folder of the project, or else from the databases folder of GECKO.
+0011 % geckopy has the same (extract_enzyme_substrate_pairs).
+0012 %
+0013 % Parameters
+0014 % ----------
+0015 % model : struct
+0016 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0017 %
+0018 % Name-Value Arguments
+0019 % --------------------
+0020 % ecRxns : logical
+0021 % which model.ec.rxns to list (default: all).
+0022 % modelAdapter : ModelAdapter
+0023 % a loaded model adapter (default: the current default model adapter).
+0024 %
+0025 % Returns
+0026 % -------
+0027 % pairs : struct
+0028 % ecRxn (index into model.ec.rxns), protein (index into model.ec.genes,
+0029 % model.ec.enzymes and model.ec.sequence) and met (index into
+0030 % model.mets), one entry per pair, reaction by reaction.
+0031 %
+0032 % Examples
+0033 % --------
+0034 % pairs = extractEnzymeSubstratePairs(ecModel);
+0035 % model.metNames(pairs.met(1:5))
+0036 %
+0037 % See also
+0038 % --------
+0039 % writeDLKcatInput, kcatsFromHomologs
+0040
+0041 [geckoPath, ~] = findGECKOroot();
+0042 p = parseGECKOargs(varargin, {'ecRxns', []; 'modelAdapter', []});
+0043 ecRxnsSel = p.ecRxns;
+0044 if isempty(ecRxnsSel)
+0045 ecRxnsSel = true(numel(model.ec.rxns),1);
+0046 elseif ~islogical(ecRxnsSel)
+0047 error('ecRxns should be provided as logical vector')
+0048 elseif numel(ecRxnsSel)~=numel(model.ec.rxns)
+0049 error('Length of ecRxns is not the same as model.ec.rxns')
+0050 end
+0051 ecRxnsSel = find(ecRxnsSel);
+0052 modelAdapter = p.modelAdapter;
+0053 if isempty(modelAdapter)
+0054 modelAdapter = ModelAdapterManager.getDefault();
+0055 if isempty(modelAdapter)
+0056 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0057 end
+0058 end
+0059 params = modelAdapter.params;
+0060
+0061 if ~model.ec.geckoLight
+0062 origRxns = model.ec.rxns;
+0063 else
+0064 origRxns = extractAfter(model.ec.rxns,4);
+0065 end
+0066 origRxnsToInclude = origRxns(ecRxnsSel);
+0067
+0068 % Map back to original reactions, to extract substrates
+0069 [sanityCheck,origRxnIdxs] = ismember(origRxnsToInclude,model.rxns);
+0070 if ~all(sanityCheck)
+0071 error('Not all reactions in model.ec.rxns are found in model.rxns')
+0072 end
+0073
+0074 % Ignore selected metabolites (metal ions, proteins etc.). First check by
+0075 % name (case insensitive, without white spaces and special characters),
+0076 % then also try to match with metSmiles (if available).
+0077 metsNoSpecialChars = lower(regexprep(model.metNames,'[^0-9a-zA-Z]+',''));
+0078 if exist(fullfile(params.path,'data','DLKcatIgnoreMets.tsv'),'file')
+0079 fID = fopen(fullfile(params.path,'data','DLKcatIgnoreMets.tsv'));
+0080 else
+0081 fID = fopen(fullfile(geckoPath,'databases','DLKcatIgnoreMets.tsv'));
+0082 end
+0083 fileData = textscan(fID,'%s %s','delimiter','\t');
+0084 fclose(fID);
+0085 [ignoreMets, ignoreSmiles] = deal(fileData{[1,2]});
+0086 ignoreMets = lower(regexprep(ignoreMets,'[^0-9a-zA-Z]+',''));
+0087 ignoreSmiles(cellfun(@isempty,ignoreSmiles)) = [];
+0088
+0089 ignoreMetsIdx = logical(ismember(metsNoSpecialChars,ignoreMets));
+0090 if isfield(model,'metSmiles')
+0091 ignoreMetsIdx = ignoreMetsIdx | logical(ismember(model.metSmiles,ignoreSmiles));
+0092 end
+0093 % Also leave out protein-usage pseudometabolites
+0094 ignoreMetsIdx = ignoreMetsIdx | startsWith(model.mets,'prot_');
+0095 reducedS = model.S;
+0096 reducedS(ignoreMetsIdx,:) = 0;
+0097
+0098 % Ignore currency metabolites if they occur in pairs. First check by
+0099 % name (case insensitive, without white spaces and special characters),
+0100 % then also try to match with metSmiles (if available).
+0101 if exist(fullfile(params.path,'data','DLKcatCurrencyMets.tsv'),'file')
+0102 fID = fopen(fullfile(params.path,'data','DLKcatCurrencyMets.tsv'));
+0103 else
+0104 fID = fopen(fullfile(geckoPath,'databases','DLKcatCurrencyMets.tsv'));
+0105 end
+0106 fileData = textscan(fID,'%s %s','delimiter','\t');
+0107 fclose(fID);
+0108 [currencyMets(:,1), currencyMets(:,2)] = deal(fileData{[1,2]});
+0109 currencyMets = lower(regexprep(currencyMets,'[^0-9a-zA-Z]+',''));
+0110
+0111 for i=1:size(currencyMets,1)
+0112 subs = strcmp(currencyMets(i,1),metsNoSpecialChars);
+0113 prod = strcmp(currencyMets(i,2),metsNoSpecialChars);
+0114 [~,subsRxns]=find(reducedS(subs,:));
+0115 [~,prodRxns]=find(reducedS(prod,:));
+0116 pairRxns = intersect(subsRxns,prodRxns);
+0117 tempRedS=reducedS;
+0118 tempRedS([find(subs);find(prod)],pairRxns) = 0;
+0119 % Do not remove currency mets if no substrate remains
+0120 rxnsWithRemainingSubstrates = any(tempRedS(:,pairRxns) < 0,1);
+0121 reducedS([find(subs);find(prod)],intersect(pairRxns,pairRxns(rxnsWithRemainingSubstrates))) = 0;
+0122 end
+0123
+0124 % origRxnIdxs already selects exactly the model.rxns columns for the requested
+0125 % ecRxns subset (one column per requested reaction, via origRxns(ecRxns) above), so
+0126 % clearedRedS needs no further filtering here.
+0127 clearedRedS = reducedS(:,origRxnIdxs);
+0128
+0129 % Enumerate all substrates for each reaction. clearedRedS has one column per entry
+0130 % of ecRxnsSel, in that same order, so `reactions` here indexes *that subset*, not
+0131 % model.ec.rxns directly --- translate back to a real model.ec.rxns index before
+0132 % using it against model.ec.rxns / model.ec.rxnEnzMat, which are always full-length.
+0133 [substrates, reactions] = find(clearedRedS<0);
+0134 globalEcRxns = ecRxnsSel(reactions);
+0135
+0136 % Enumerate all proteins for each reaction
+0137 [proteins, enzRows] = find(transpose(model.ec.rxnEnzMat(globalEcRxns,:)));
+0138 pairs.ecRxn = reshape(globalEcRxns(enzRows), [], 1);
+0139 pairs.protein = reshape(proteins, [], 1);
+0140 pairs.met = reshape(substrates(enzRows), [], 1);
+0141 end
fetchOpenKineticsPredictor Fetch and parse an OpenKineticsPredictor job.
+
+
SYNOPSIS
+
function [done, kcatList] = fetchOpenKineticsPredictor(model, varargin)
+
+
DESCRIPTION
+
fetchOpenKineticsPredictor Fetch and parse an OpenKineticsPredictor job.
+
+ Checks the status of an OpenKineticsPredictor job and, when it has
+ finished, downloads the result to data/OKP_output.csv and parses it (via
+ readOpenKineticsPredictorOutput) into a kcatList consumable by
+ assignKcatValues.
+
+ The API key (only needed when useStored=false) is resolved from the
+ OKP_API_KEY environment variable or data/okpApiKey.txt; see
+ submitOpenKineticsPredictor for how to obtain and store a key.
+
+ Parameters
+ ----------
+ model : struct
+ ecModel in GECKO 3 format (with ecModel.ec structure; model.metSmiles
+ is required to map results back).
+
+ Name-Value Arguments
+ --------------------
+ useStored : logical
+ if true, skip the API entirely and parse the already-downloaded
+ data/OKP_output.csv. If false, query the API (default false).
+ jobId : char
+ OKP job id (default: read from data/OKP_job.txt, written by
+ submitOpenKineticsPredictor).
+ wait : logical
+ if true, poll until the job finishes; if false, report once and
+ return (default false).
+ pollInterval : double
+ seconds between polls when wait=true (default 30).
+ outFile : char
+ where the result is stored, and read from with useStored=true
+ (default: data/OKP_output.csv).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ done : logical
+ true if a result was obtained (downloaded or loaded from disk).
+ kcatList : struct
+ structure with kcat values (empty when not done); see
+ readOpenKineticsPredictorOutput for its fields (source, rxns, genes,
+ substrates, kcats, kcatSource).
+
+ Examples
+ --------
+ [done, kcatList] = fetchOpenKineticsPredictor(model); % check once
+ [done, kcatList] = fetchOpenKineticsPredictor(model, 'wait', true); % poll
+ [done, kcatList] = fetchOpenKineticsPredictor(model, 'useStored', true); % parse stored file
+
+ See also
+ --------
+ submitOpenKineticsPredictor, readOpenKineticsPredictorOutput
fuzzyKcatMatching Match model EC numbers and substrates to BRENDA kcats.
+
+
SYNOPSIS
+
function kcatList = fuzzyKcatMatching(model, varargin)
+
+
DESCRIPTION
+
fuzzyKcatMatching Match model EC numbers and substrates to BRENDA kcats.
+
+ Matches the model EC numbers and substrates to the BRENDA database, to
+ return the corresponding kcats for each reaction. If no exact match is
+ found, less specific kcat values are found from (a) evolutionary closely
+ related organism; (b) different substrate; (c) calculated from specific
+ activities; (d) wildcards in the EC number. The model organism is provided
+ in the model adapter as obj.params.org_name, and evolutionary distance to
+ other organisms is determined via KEGG phylogeny. If an organism name
+ occurs multiple times in KEGG, the first instance will be used when
+ determining evolutionary distance.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ ecRxns : logical or double
+ for which reactions (from model.ec.rxns) kcat values should be found,
+ provided as a logical vector with the same length as model.ec.rxns
+ (the reactions are returned in model order), or as indices into
+ model.ec.rxns (the reactions are returned in the order given, and an
+ index given twice is returned twice). The literal [] selects all
+ reactions, which is the default; any other empty selection, such as
+ find(mask) without true entries, selects none and returns an empty
+ kcatList.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ forceWClvl : double
+ number of extra levels, from the right, replaced by a wildcard (on top
+ of any wildcard already in the code; default 0).
+
+ Returns
+ -------
+ kcatList : struct
+ structure array with list of BRENDA derived kcat values, with separate
+ entries for each kcat value.
+
+ Notes
+ -----
+ The kcatList structure has the following fields:
+
+ - source : 'brenda'.
+ - rxns : reaction identifiers.
+ - substrates : substrate names.
+ - kcats : proposed kcat value in /sec.
+ - eccodes : as used to query BRENDA.
+ - wildcardLvl : which level of EC wild-card was necessary to find a match
+ (0: w.x.y.z; 1: w.x.y.-; 2: w.x.-.-; 3: w.-.-.-; 4: -.-.-.-, which
+ matches every EC number in BRENDA).
+ - origin : which level of specificity was necessary to find a match
+ (1: correct organism, correct substrate, kcat; 2: any organism, correct
+ substrate, kcat; 3: correct organism, any substrate, kcat; 4: any
+ organism, any substrate, kcat; 5: correct organism, specific activity;
+ 6: any organism, specific activity). Levels are tried in the order
+ 1, 2, 3, 5, 4, 6: a specific activity from the correct organism is
+ preferred over a kcat from any organism with any substrate. When a
+ reaction has multiple EC numbers, the EC with the fewest wildcards and
+ the earliest-tried level is selected, and among those the highest kcat
+ (the first one when several EC numbers share it); the wildcardLvl and
+ origin are those of the selected EC.
+
+ If a wildcard is used, origin levels 1 and 2 are ignored. The last digits
+ in the E.C. number indicate the substrate specificity, so if this should
+ be ignored, then correct substrate matches should not be prioritized.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ kcatList = fuzzyKcatMatching(model);
+ kcatList = fuzzyKcatMatching(model, 'forceWClvl', 1);
+
+ See also
+ --------
+ mergeDLKcatAndFuzzyKcats, assignKcatValues
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
kcatsFromBrenda kcatsFromBrenda Gather kcats from BRENDA and keep them as a source file.
0001 function kcatList = fuzzyKcatMatching(model, varargin)
+0002 % fuzzyKcatMatching Match model EC numbers and substrates to BRENDA kcats.
+0003 %
+0004 % Matches the model EC numbers and substrates to the BRENDA database, to
+0005 % return the corresponding kcats for each reaction. If no exact match is
+0006 % found, less specific kcat values are found from (a) evolutionary closely
+0007 % related organism; (b) different substrate; (c) calculated from specific
+0008 % activities; (d) wildcards in the EC number. The model organism is provided
+0009 % in the model adapter as obj.params.org_name, and evolutionary distance to
+0010 % other organisms is determined via KEGG phylogeny. If an organism name
+0011 % occurs multiple times in KEGG, the first instance will be used when
+0012 % determining evolutionary distance.
+0013 %
+0014 % Parameters
+0015 % ----------
+0016 % model : struct
+0017 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0018 %
+0019 % Name-Value Arguments
+0020 % --------------------
+0021 % ecRxns : logical or double
+0022 % for which reactions (from model.ec.rxns) kcat values should be found,
+0023 % provided as a logical vector with the same length as model.ec.rxns
+0024 % (the reactions are returned in model order), or as indices into
+0025 % model.ec.rxns (the reactions are returned in the order given, and an
+0026 % index given twice is returned twice). The literal [] selects all
+0027 % reactions, which is the default; any other empty selection, such as
+0028 % find(mask) without true entries, selects none and returns an empty
+0029 % kcatList.
+0030 % modelAdapter : ModelAdapter
+0031 % a loaded model adapter (default: the current default model adapter).
+0032 % forceWClvl : double
+0033 % number of extra levels, from the right, replaced by a wildcard (on top
+0034 % of any wildcard already in the code; default 0).
+0035 %
+0036 % Returns
+0037 % -------
+0038 % kcatList : struct
+0039 % structure array with list of BRENDA derived kcat values, with separate
+0040 % entries for each kcat value.
+0041 %
+0042 % Notes
+0043 % -----
+0044 % The kcatList structure has the following fields:
+0045 %
+0046 % - source : 'brenda'.
+0047 % - rxns : reaction identifiers.
+0048 % - substrates : substrate names.
+0049 % - kcats : proposed kcat value in /sec.
+0050 % - eccodes : as used to query BRENDA.
+0051 % - wildcardLvl : which level of EC wild-card was necessary to find a match
+0052 % (0: w.x.y.z; 1: w.x.y.-; 2: w.x.-.-; 3: w.-.-.-; 4: -.-.-.-, which
+0053 % matches every EC number in BRENDA).
+0054 % - origin : which level of specificity was necessary to find a match
+0055 % (1: correct organism, correct substrate, kcat; 2: any organism, correct
+0056 % substrate, kcat; 3: correct organism, any substrate, kcat; 4: any
+0057 % organism, any substrate, kcat; 5: correct organism, specific activity;
+0058 % 6: any organism, specific activity). Levels are tried in the order
+0059 % 1, 2, 3, 5, 4, 6: a specific activity from the correct organism is
+0060 % preferred over a kcat from any organism with any substrate. When a
+0061 % reaction has multiple EC numbers, the EC with the fewest wildcards and
+0062 % the earliest-tried level is selected, and among those the highest kcat
+0063 % (the first one when several EC numbers share it); the wildcardLvl and
+0064 % origin are those of the selected EC.
+0065 %
+0066 % If a wildcard is used, origin levels 1 and 2 are ignored. The last digits
+0067 % in the E.C. number indicate the substrate specificity, so if this should
+0068 % be ignored, then correct substrate matches should not be prioritized.
+0069 %
+0070 % Examples
+0071 % --------
+0072 % % optional arguments may be given positionally or as name-value pairs:
+0073 % kcatList = fuzzyKcatMatching(model);
+0074 % kcatList = fuzzyKcatMatching(model, 'forceWClvl', 1);
+0075 %
+0076 % See also
+0077 % --------
+0078 % mergeDLKcatAndFuzzyKcats, assignKcatValues
+0079
+0080 % Validators reject a name that landed in a positional slot, which
+0081 % happens when positional and name-value arguments are mixed. Without
+0082 % them `while forceWClvl > 0` on a char array is all-true and silently
+0083 % wildcards every EC number.
+0084 p = parseGECKOargs(varargin, { ...
+0085 'ecRxns', [], @(x) assert(isempty(x) || isnumeric(x) || ...
+0086 islogical(x), ...
+0087 'fuzzyKcatMatching:invalidEcRxns', ...
+0088 'ecRxns must be numeric or logical.'); ...
+0089 'modelAdapter', [], @(x) assert(isempty(x) || isa(x,'ModelAdapter'), ...
+0090 'fuzzyKcatMatching:invalidModelAdapter', ...
+0091 'modelAdapter must be a ModelAdapter.'); ...
+0092 'forceWClvl', [], @(x) assert(isempty(x) || (isnumeric(x) && ...
+0093 isscalar(x) && x >= 0), ...
+0094 'fuzzyKcatMatching:invalidForceWClvl', ...
+0095 'forceWClvl must be a nonnegative scalar.'); ...
+0096 'kcatAggregation', [], @(x) assert(isempty(x) || any(strcmpi(x, ...
+0097 {'max','median'})), ...
+0098 'fuzzyKcatMatching:invalidKcatAggregation', ...
+0099 'kcatAggregation must be ''max'' or ''median''.')});
+0100 ecRxns = p.ecRxns;
+0101 modelAdapter = p.modelAdapter;
+0102 forceWClvl = p.forceWClvl;
+0103 kcatAggregation = p.kcatAggregation;
+0104
+0105 % Get indices: [] selects all reactions, a logical vector the true entries (in
+0106 % model order) and indices are used as given (order and repeats kept).
+0107 if isnumeric(ecRxns) && isequal(size(ecRxns),[0 0])
+0108 ecRxns = (1:numel(model.ec.rxns)).';
+0109 elseif islogical(ecRxns)
+0110 ecRxns = find(ecRxns(:));
+0111 else
+0112 ecRxns = ecRxns(:);
+0113 if any(ecRxns < 1 | ecRxns > numel(model.ec.rxns) | ecRxns ~= round(ecRxns))
+0114 error('fuzzyKcatMatching:invalidEcRxns', ...
+0115 'ecRxns must hold indices of model.ec.rxns.');
+0116 end
+0117 end
+0118
+0119 if isempty(modelAdapter)
+0120 modelAdapter = ModelAdapterManager.getDefault();
+0121 if isempty(modelAdapter)
+0122 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0123 end
+0124 end
+0125 params = modelAdapter.params;
+0126
+0127 if isempty(forceWClvl)
+0128 forceWClvl = 0;
+0129 end
+0130
+0131 if ~isfield(model.ec,'eccodes')
+0132 error('No EC codes defined in model.ec.eccodes. First run getECfromGEM() and/or getECfromDatabase().')
+0133 end
+0134 eccodes = model.ec.eccodes(ecRxns);
+0135 if isempty(ecRxns)
+0136 %No reactions selected: nothing to match, and BRENDA is not read
+0137 kcatList.source = 'brenda';
+0138 kcatList.rxns = cell(0,1);
+0139 kcatList.substrates = cell(0,1);
+0140 kcatList.kcats = zeros(0,1);
+0141 kcatList.eccodes = cell(0,1);
+0142 kcatList.wildcardLvl = NaN(0,1);
+0143 kcatList.origin = NaN(0,1);
+0144 return
+0145 end
+0146 substrates = cell(numel(ecRxns),1);
+0147 substrCoeffs = cell(numel(ecRxns),1);
+0148
+0149 %Need to remove the prefix of GECKO light rxn names in the ec structure
+0150 if ~model.ec.geckoLight
+0151 rxnNames = model.ec.rxns;
+0152 else
+0153 rxnNames = extractAfter(model.ec.rxns, 4);
+0154 end
+0155 [~,originalRxns] = ismember(rxnNames(ecRxns),model.rxns);
+0156 for i = 1:length(ecRxns)
+0157 sel = find(model.S(:,originalRxns(i)) < 0);
+0158 substrates{i} = model.metNames(sel);
+0159 substrCoeffs{i} = -model.S(sel,originalRxns(i));
+0160 end
+0161
+0162 %Load BRENDA data:
+0163 kcatAggregation = resolveKcatAggregation(kcatAggregation, modelAdapter);
+0164 [KCATcell, SAcell] = loadBRENDAdata('modelAdapter', modelAdapter, ...
+0165 'kcatAggregation', kcatAggregation);
+0166
+0167 %Creates a Structure with KEGG codes for organisms, names and taxonomical
+0168 %distance matrix and extract the organism index in the KEGG struct
+0169 phylDistStruct = KEGG_struct(modelAdapter.getPhylDistStructPath());
+0170 %Get the KEGG code for the model's organism
+0171 org_name = params.org_name;
+0172 org_index = find_inKEGG(org_name,phylDistStruct.names);
+0173 %build an index for genus in the phyl dist struct
+0174 %first just extract the genus (i.e. the first part of the name)
+0175 phylDistStruct.genus = lower(regexprep(phylDistStruct.names,'\s.*',''));
+0176 %create a map for the genuses
+0177 phylDistStruct.uniqueGenusList = unique(phylDistStruct.genus);
+0178 phylDistStruct.genusHashMap = containers.Map(phylDistStruct.uniqueGenusList,1:length(phylDistStruct.uniqueGenusList));
+0179 phylDistStruct.uniqueGenusIndices = cell(length(phylDistStruct.uniqueGenusList),1);
+0180
+0181 %Then for each genus create a list with indices to the names
+0182 for i = 1:length(phylDistStruct.genus)
+0183 matchInd = cell2mat(values(phylDistStruct.genusHashMap,phylDistStruct.genus(i)));
+0184 phylDistStruct.uniqueGenusIndices{matchInd} = [phylDistStruct.uniqueGenusIndices{matchInd};i];
+0185 end
+0186
+0187 %Allocate output
+0188 kcats = zeros(length(eccodes),1);
+0189 mM = length(eccodes);
+0190
+0191 %Create empty kcatInfo
+0192 %Not returned as an output; used only internally to construct
+0193 %kcatList.wildcardLvl and kcatList.origin.
+0194 kcatInfo.info.org_s = zeros(mM,1);
+0195 kcatInfo.info.rest_s = zeros(mM,1);
+0196 kcatInfo.info.org_ns = zeros(mM,1);
+0197 kcatInfo.info.rest_ns = zeros(mM,1);
+0198 kcatInfo.info.org_sa = zeros(mM,1);
+0199 kcatInfo.info.rest_sa = zeros(mM,1);
+0200 kcatInfo.info.wcLevel = NaN(mM,1);
+0201 kcatInfo.stats.queries = 0;
+0202 kcatInfo.stats.org_s = 0;
+0203 kcatInfo.stats.rest_s = 0;
+0204 kcatInfo.stats.org_ns = 0;
+0205 kcatInfo.stats.rest_ns = 0;
+0206 kcatInfo.stats.org_sa = 0;
+0207 kcatInfo.stats.rest_sa = 0;
+0208 kcatInfo.stats.wc0 = 0;
+0209 kcatInfo.stats.wc1 = 0;
+0210 kcatInfo.stats.wc2 = 0;
+0211 kcatInfo.stats.wc3 = 0;
+0212 kcatInfo.stats.wc4 = 0;
+0213 kcatInfo.stats.matrix = zeros(6,5);
+0214
+0215 %build an EC index to speed things up a bit - many of the ECs appear
+0216 %many times - unnecessary to compare them all
+0217 %so, here, each EC string appears only once, and you get a vector with
+0218 %indices to the rows in KCATcell
+0219 [ECIndexIds,~,ic] = unique(KCATcell{1});
+0220 EcIndexIndices = cell(length(ECIndexIds),1);
+0221 for i = 1:length(EcIndexIndices)
+0222 EcIndexIndices{i} = find(ic == i).';
+0223 end
+0224
+0225 %Apply force wildcard level
+0226 eccodes = forceWildcards(eccodes,forceWClvl);
+0227
+0228 PB = progressReport(mM, 'Gathering kcat values by fuzzy matching to BRENDA database');
+0229 %Main loop:
+0230 for i = 1:mM
+0231 PB.count;
+0232 %Match:
+0233 EC = eccodes{i};
+0234 if ~isempty(EC)
+0235 EC = strsplit(EC,';');
+0236 %Try to match direct reaction:
+0237 if ~isempty(substrates{i})
+0238 [kcats(i), kcatInfo.info,kcatInfo.stats] = iterativeMatch(EC,substrates{i},substrCoeffs{i},i,KCATcell,...
+0239 kcatInfo.info,kcatInfo.stats,org_name,...
+0240 phylDistStruct,org_index,SAcell,ECIndexIds,EcIndexIndices,kcatAggregation);
+0241 end
+0242 end
+0243 end
+0244
+0245 kcatList.source = 'brenda';
+0246 kcatList.rxns = model.ec.rxns(ecRxns);
+0247 kcatList.substrates = substrates;
+0248 kcatList.kcats = kcats;
+0249 kcatList.eccodes = eccodes;
+0250 kcatList.wildcardLvl = kcatInfo.info.wcLevel;
+0251 kcatList.origin = NaN(numel(model.ec.rxns(ecRxns)),1);
+0252 % This can be refactored, iterativeMatch and their nested functions can
+0253 % just directly report the origin number.
+0254 origin = [kcatInfo.info.org_s kcatInfo.info.rest_s kcatInfo.info.org_ns kcatInfo.info.rest_ns kcatInfo.info.org_sa kcatInfo.info.rest_sa];
+0255 for i=1:6
+0256 kcatList.origin(find(origin(:,i))) = i;
+0257 end
+0258 end
+0259 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0260 %Replaces the rightmost level of each EC code that is not a wildcard yet with
+0261 %a wildcard, forceWClvl times, so that level 4 gives -.-.-.- . The codes of one
+0262 %entry are separated by ';'. A code that does not have four levels is kept.
+0263 function eccodes = forceWildcards(eccodes,forceWClvl)
+0264 if forceWClvl <= 0
+0265 return
+0266 end
+0267 for i = 1:numel(eccodes)
+0268 if isempty(eccodes{i})
+0269 continue
+0270 end
+0271 codes = strsplit(eccodes{i},';');
+0272 for j = 1:numel(codes)
+0273 parts = strsplit(codes{j},'.');
+0274 if numel(parts) ~= 4
+0275 continue
+0276 end
+0277 for k = 1:forceWClvl
+0278 last = find(~strcmp(parts,'-'),1,'last');
+0279 if isempty(last)
+0280 break
+0281 end
+0282 parts{last} = '-';
+0283 end
+0284 codes{j} = strjoin(parts,'.');
+0285 end
+0286 eccodes{i} = strjoin(codes,';');
+0287 end
+0288 end
+0289 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0290 function [kcat,dir,tot] =iterativeMatch(EC,subs,substrCoeff,i,KCATcell,dir,tot,...
+0291 name,phylDist,org_index,SAcell,ECIndexIds,EcIndexIndices,kcatAggregation)
+0292 %Will iteratively try to match the EC number to some registry in BRENDA,
+0293 %using each time one additional wildcard.
+0294
+0295 kcat = zeros(size(EC));
+0296 origin = zeros(size(EC));
+0297 matches = zeros(size(EC));
+0298 wc_num = ones(size(EC)).*1000;
+0299 for k = 1:length(EC)
+0300 success = false;
+0301 while ~success
+0302 %Atempt match:
+0303 [kcat(k),origin(k),matches(k)] = mainMatch(EC{k},subs,substrCoeff,KCATcell,...
+0304 name,phylDist,...
+0305 org_index,SAcell,ECIndexIds,EcIndexIndices,kcatAggregation);
+0306 %If any match found, ends. If not, introduces one extra wild card and
+0307 %tries again:
+0308 if origin(k) > 0
+0309 success = true;
+0310 wc_num(k) = sum(EC{k}=='-');
+0311 else
+0312 wild_num = sum(EC{k}=='-');
+0313 if numel(strsplit(EC{k},'.')) ~= 4
+0314 %An EC code without four levels (for example '1.1.1') cannot be
+0315 %escalated level by level: stop with the no-match values, as
+0316 %geckopy does.
+0317 success = true;
+0318 elseif wild_num >= 4
+0319 %Already fully wildcarded ('-.-.-.-') and still no match --
+0320 %stop here instead of computing dot_pos(4-wild_num) with
+0321 %wild_num==4, an out-of-range index. kcat(k)/origin(k) keep
+0322 %their zero-initialised "no match" values.
+0323 success = true;
+0324 elseif wild_num == 3
+0325 %Last escalation: '-.-.-.-' matches every EC number in BRENDA
+0326 EC{k} = '-.-.-.-';
+0327 else
+0328 dot_pos = [2 strfind(EC{k},'.')];
+0329 wc_text = '-.-.-.-';
+0330 EC{k} = [EC{k}(1:dot_pos(4-wild_num)) wc_text(1:2*wild_num+1)];
+0331 end
+0332 end
+0333 end
+0334 end
+0335
+0336 if sum(origin) > 0
+0337 %For more than one EC: Choose the maximum value among the ones with the
+0338 %less amount of wildcards and the better origin:
+0339 best_pos = (wc_num == min(wc_num));
+0340 new_origin = origin(best_pos);
+0341 best_origin = min(new_origin(new_origin~=0));
+0342 %Intersect with the wildcard filter above -- comparing origin==best_origin
+0343 %against the full (unfiltered) origin vector would let an EC with a worse
+0344 %(higher) wildcard count back in whenever it happens to share best_origin,
+0345 %defeating the wildcard-count filter entirely.
+0346 best_pos = best_pos & (origin == best_origin);
+0347 %Highest kcat among those, the first one if several share it
+0348 best_idx = find(best_pos);
+0349 [~,j] = max(kcat(best_idx));
+0350 max_pos = best_idx(j);
+0351 wc_num = wc_num(max_pos);
+0352 origin = origin(max_pos);
+0353 matches = matches(max_pos);
+0354 kcat = kcat(max_pos);
+0355
+0356 %Update dir and tot:
+0357 dir.org_s(i) = matches*(origin == 1);
+0358 dir.rest_s(i) = matches*(origin == 2);
+0359 dir.org_ns(i) = matches*(origin == 3);
+0360 dir.org_sa(i) = matches*(origin == 4);
+0361 dir.rest_ns(i) = matches*(origin == 5);
+0362 dir.rest_sa(i) = matches*(origin == 6);
+0363 dir.wcLevel(i) = wc_num;
+0364 tot.org_s = tot.org_s + (origin == 1);
+0365 tot.rest_s = tot.rest_s + (origin == 2);
+0366 tot.org_ns = tot.org_ns + (origin == 3);
+0367 tot.org_sa = tot.org_sa + (origin == 4);
+0368 tot.rest_ns = tot.rest_ns + (origin == 5);
+0369 tot.rest_sa = tot.rest_sa + (origin == 6);
+0370 tot.wc0 = tot.wc0 + (wc_num == 0);
+0371 tot.wc1 = tot.wc1 + (wc_num == 1);
+0372 tot.wc2 = tot.wc2 + (wc_num == 2);
+0373 tot.wc3 = tot.wc3 + (wc_num == 3);
+0374 tot.wc4 = tot.wc4 + (wc_num == 4);
+0375 tot.queries = tot.queries + 1;
+0376 tot.matrix(origin,wc_num+1) = tot.matrix(origin,wc_num+1) + 1;
+0377 end
+0378
+0379 end
+0380 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0381
+0382 function [kcat,origin,matches] = mainMatch(EC,subs,substrCoeff,KCATcell,...
+0383 name,phylDist,org_index,SAcell,ECIndexIds,EcIndexIndices,kcatAggregation)
+0384
+0385 %First make the string matching. This takes time, so we only want to do
+0386 %this once:
+0387 %Relaxes matching if wild cards are present:
+0388 wild = false;
+0389 wild_pos = strfind(EC,'-');
+0390 if ~isempty(wild_pos)
+0391 EC = EC(1:wild_pos(1)-1);
+0392 wild = true;
+0393 end
+0394 stringMatchesEC_cell = extract_string_matches(EC,KCATcell{1},wild,ECIndexIds,EcIndexIndices);
+0395
+0396 % Matching function prioritizing organism and substrate specificity when
+0397 % available.
+0398
+0399 origin = 0;
+0400 %First try to match organism and substrate:
+0401 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,name,true,false,...
+0402 phylDist,org_index,SAcell,stringMatchesEC_cell,[],kcatAggregation);
+0403 if matches > 0 && ~wild % If wildcard, ignore substrate match
+0404 origin = 1;
+0405 %If no match, try the closest organism but match the substrate:
+0406 else
+0407 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,'',true,false,...
+0408 phylDist,org_index,SAcell,stringMatchesEC_cell,[],kcatAggregation);
+0409 if matches > 0 && ~wild % If wildcard, ignore substrate match
+0410 origin = 2;
+0411 %If no match, try to match organism but with any substrate:
+0412 else
+0413 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,name,false,false,...
+0414 phylDist,org_index,SAcell,stringMatchesEC_cell,[],kcatAggregation);
+0415 if matches > 0
+0416 origin = 3;
+0417 %If no match, try to match organism but for any substrate (SA*MW):
+0418 else
+0419 %create matching index for SA, has not been needed until now
+0420 stringMatchesSA = extract_string_matches(EC,SAcell{1},wild,[],[]);
+0421
+0422 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,name,false,...
+0423 true,phylDist,org_index,...
+0424 SAcell,stringMatchesEC_cell,stringMatchesSA,kcatAggregation);
+0425 if matches > 0
+0426 origin = 4;
+0427 %If no match, try any organism and any substrate:
+0428 else
+0429 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,'',false,...
+0430 false,phylDist,...
+0431 org_index,SAcell,stringMatchesEC_cell,stringMatchesSA,kcatAggregation);
+0432 if matches > 0
+0433 origin = 5;
+0434 %Again if no match, look for any org and SA*MW
+0435 else
+0436 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,'',...
+0437 false,true,phylDist,...
+0438 org_index,SAcell,stringMatchesEC_cell,stringMatchesSA,kcatAggregation);
+0439 if matches > 0
+0440 origin = 6;
+0441 end
+0442 end
+0443
+0444 end
+0445 end
+0446 end
+0447 end
+0448 end
+0449 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0450 function [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,organism,...
+0451 substrate,SA,phylDist,...
+0452 org_index,SAcell,KCATcellMatches,SAcellMatches,kcatAggregation)
+0453
+0454 %Will go through BRENDA and will record any match. Afterwards, it will
+0455 %return the maximum value and the number of matches attained.
+0456 kcat = [];
+0457 matches = 0;
+0458
+0459 if SA
+0460 %SAcell{1},wild,[],[]
+0461 EC_indexes = extract_indexes(SAcellMatches,[],SAcell{2},subs,substrate,...
+0462 organism,org_index,phylDist);
+0463
+0464 kcat = SAcell{3}(EC_indexes);
+0465 org_cell = SAcell{2}(EC_indexes);
+0466 MW_BRENDA = SAcell{4}(EC_indexes);
+0467
+0468 else
+0469 %KCATcell{1},wild,ECIndexIds,EcIndexIndices
+0470 EC_indexes = extract_indexes(KCATcellMatches,KCATcell{2},KCATcell{3},...
+0471 subs,substrate,organism,org_index,...
+0472 phylDist);
+0473 if substrate
+0474 for j = 1:length(EC_indexes)
+0475 indx = EC_indexes(j);
+0476 for k = 1:length(subs)
+0477 if (isempty(subs{k}))
+0478 break;
+0479 end
+0480 %l = logical(strcmpi(model.metNames,subs{k}).*(model.S(:,i)~=0)); %I don't understand the .* (model.S(:,i)~=0) part, it shouldn't be needed?/JG;
+0481 if ~isempty(subs{k}) && strcmpi(subs{k},KCATcell{2}(indx))
+0482 if KCATcell{4}(indx) > 0
+0483 coeff = substrCoeff(k);
+0484 kCatTmp = KCATcell{4}(indx);
+0485 kcat = [kcat;kCatTmp/coeff];
+0486 end
+0487 end
+0488 end
+0489 end
+0490 else
+0491 kcat = KCATcell{4}(EC_indexes);
+0492 end
+0493 end
+0494 %Collapse the matched entries to one value, by the chosen aggregation:
+0495 if isempty(kcat)
+0496 kcat = 0;
+0497 else
+0498 matches = length(kcat);
+0499 if strcmpi(kcatAggregation,'median')
+0500 kcat = median(kcat);
+0501 else
+0502 kcat = max(kcat);
+0503 end
+0504 end
+0505 %Avoid SA*Mw values over the diffusion limit rate [Bar-Even et al. 2011]
+0506 if kcat>(1E7)
+0507 kcat = 1E7;
+0508 end
+0509 end
+0510
+0511 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0512 %Make the string matches of the ECs. This is heavy, so only do it once!
+0513 %
+0514 function EC_indexes = extract_string_matches(EC,EC_cell,wild,ECIndexIds,EcIndexIndices)
+0515 EC_indexes = [];
+0516 EC_indexesOld = [];
+0517 if wild
+0518 if (~isempty(ECIndexIds)) %In some cases the EC_cell is not from KCatCell
+0519 % startsWith, not contains: EC is a truncated prefix like '1.' (everything
+0520 % before the first wildcard dash), and an anchored match is what the
+0521 % non-optimized branch below already does (strfind(...)==1). contains()
+0522 % would match that same '1.' substring anywhere in the string, not just at
+0523 % the start -- '4.2.1.1' contains '1.' between its third and fourth levels,
+0524 % so a '1.-.-.-' wildcard query for enzyme class 1 would also match entries
+0525 % from unrelated classes whose EC code happens to contain the same two
+0526 % characters.
+0527 X = find(startsWith(ECIndexIds, EC));
+0528 for j = 1:length(X)
+0529 EC_indexes = [EC_indexes,EcIndexIndices{X(j)}];
+0530 end
+0531 elseif isempty(EC) %Not optimized, fully wildcarded: every entry matches
+0532 EC_indexes = 1:length(EC_cell);
+0533 else%Not optimized
+0534 for j=1:length(EC_cell)
+0535 if strfind(EC_cell{j},EC)==1
+0536 EC_indexes = [EC_indexes,j];
+0537 end
+0538 end
+0539 end
+0540 else
+0541 if (~isempty(ECIndexIds)) %In some cases the EC_cell is not from KCatCell
+0542 mtch = find(strcmpi(EC,ECIndexIds));
+0543 if (~isempty(mtch))
+0544 EC_indexes = EcIndexIndices{mtch};
+0545 end
+0546 else%%Not optimized
+0547 if ~isempty(EC_cell)
+0548 EC_indexes = transpose(find(strcmpi(EC,EC_cell)));
+0549 end
+0550 end
+0551 end
+0552
+0553 end
+0554 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0555 %Extract the indexes of the entries in the BRENDA data that meet the
+0556 %conditions specified by the search criteria
+0557 function EC_indexes = extract_indexes(EC_indCellStringMatches,subs_cell,orgs_cell,subs,...
+0558 substrate,organism, org_index,...
+0559 phylDist)
+0560
+0561 EC_indexes = EC_indCellStringMatches;%reuse so the string comparisons are not run many times
+0562
+0563 %If substrate=true then it will extract only the substrates appereances
+0564 %indexes in the EC subset from the BRENDA cell array
+0565
+0566 if substrate
+0567 if (~isempty(EC_indexes)) %optimization
+0568 Subs_indexes = [];
+0569 for l = 1:length(subs)
+0570 if (isempty(subs{l}))
+0571 break;
+0572 end
+0573 Subs_indexes = horzcat(Subs_indexes,EC_indexes(strcmpi(subs(l),...
+0574 subs_cell(EC_indexes))));
+0575 end
+0576 EC_indexes = Subs_indexes;
+0577 end
+0578 end
+0579
+0580 EC_orgs = orgs_cell(EC_indexes);
+0581 %If specific organism values are requested looks for all the organism
+0582 %repetitions on the subset BRENDA cell array(EC_indexes)
+0583 if string(organism) ~= ''
+0584 EC_indexes = EC_indexes(strcmpi(string(organism),EC_orgs));
+0585
+0586 %If KEGG code was assigned to the organism (model) then it will look for
+0587 %the Kcat value for the closest organism
+0588 elseif org_index~='*'%&& org_index~=''
+0589 KEGG_indexes = [];temp = [];
+0590
+0591 %For relating a phyl dist between the modelled organism and the organisms
+0592 %on the BRENDA cell array it should search for a KEGG code for each of
+0593 %these
+0594 for j=1:length(EC_indexes)
+0595 %Assigns a KEGG index for those found on the KEGG struct
+0596 orgs_index = find(strcmpi(orgs_cell(EC_indexes(j)),phylDist.names),1);
+0597 if ~isempty(orgs_index)
+0598 KEGG_indexes = [KEGG_indexes; orgs_index];
+0599 temp = [temp;EC_indexes(j)];
+0600 %For values related to organisms without KEGG code, then it will
+0601 %look for KEGG code for the first organism with the same genus
+0602 else
+0603 org = orgs_cell{EC_indexes(j)};
+0604 orgGenus = lower(regexprep(org,'\s.*',''));
+0605 if isKey(phylDist.genusHashMap,orgGenus) %annoyingly, this seems to be needed
+0606 matchInd = cell2mat(values(phylDist.genusHashMap,{orgGenus}));
+0607 matches = phylDist.uniqueGenusIndices{matchInd};
+0608 k = matches(1);
+0609 KEGG_indexes = [KEGG_indexes;k];
+0610 temp = [temp;EC_indexes(j)];
+0611 end
+0612 end
+0613 end
+0614 %Update the EC_indexes cell array
+0615 EC_indexes = temp;
+0616 %Looks for the taxonomically closest organism and saves the index of
+0617 %its appearences in the BRENDA cell
+0618 if ~isempty(EC_indexes)
+0619 distances = phylDist.distMat(org_index,KEGG_indexes);
+0620 EC_indexes = EC_indexes(distances == min(distances));
+0621 end
+0622 end
+0623 end
+0624 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0625 function org_index = find_inKEGG(org_name,names)
+0626 org_index = find(strcmpi(org_name,names));
+0627 if numel(org_index)>1
+0628 org_index = org_index(1);
+0629 elseif isempty(org_index)
+0630 org_name = regexprep(org_name,'\s.*','');
+0631 org_index = find(strcmpi(org_name,names));
+0632 if numel(org_index)>1
+0633 org_index = org_index(1);
+0634 elseif isempty(org_index)
+0635 org_index = '*';
+0636 end
+0637 end
+0638 end
+0639 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0640 function phylDistStruct = KEGG_struct(phylpath)
+0641 load(phylpath)
+0642 phylDistStruct.ids = transpose(phylDistStruct.ids);
+0643 phylDistStruct.names = transpose(phylDistStruct.names);
+0644 phylDistStruct.names = regexprep(phylDistStruct.names,'\s*\(.*','');
+0645 end
getEcRxnKeys Build-independent keys of the model.ec reactions.
+
+
SYNOPSIS
+
function [baseRxns, enzymeSets] = getEcRxnKeys(model)
+
+
DESCRIPTION
+
getEcRxnKeys Build-independent keys of the model.ec reactions.
+
+ A kcat source file (see saveKcatSource) identifies a reaction by its
+ conventional reaction and its enzyme set, not by model.ec.rxns, whose
+ identifiers depend on how the ecModel was built. This function gives
+ both keys for every model.ec row.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Returns
+ -------
+ baseRxns : cell
+ per model.ec.rxns entry, the conventional reaction: without a
+ trailing _EXP_<n> (full model) or a leading <nnn>_ (light model). A
+ reverse direction keeps its _REV.
+ enzymeSets : cell
+ per model.ec.rxns entry, the UniProt accessions of its enzymes
+ (model.ec.rxnEnzMat), sorted and joined by ' + '; empty without
+ enzymes.
+
+ Examples
+ --------
+ [baseRxns, enzymeSets] = getEcRxnKeys(ecModel);
+
+ See also
+ --------
+ saveKcatSource, loadKcatSource
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
convertCustomKcats convertCustomKcats Convert data/customKcats.tsv into data/kcats/custom.tsv.
loadKcatSource loadKcatSource Read a kcat source file and map its rows to an ecModel.
saveKcatSource saveKcatSource Write a kcatList to a kcat source file of the project.
+
+
+
+
+
SOURCE CODE
+
0001 function [baseRxns, enzymeSets] = getEcRxnKeys(model)
+0002 % getEcRxnKeys Build-independent keys of the model.ec reactions.
+0003 %
+0004 % A kcat source file (see saveKcatSource) identifies a reaction by its
+0005 % conventional reaction and its enzyme set, not by model.ec.rxns, whose
+0006 % identifiers depend on how the ecModel was built. This function gives
+0007 % both keys for every model.ec row.
+0008 %
+0009 % Parameters
+0010 % ----------
+0011 % model : struct
+0012 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0013 %
+0014 % Returns
+0015 % -------
+0016 % baseRxns : cell
+0017 % per model.ec.rxns entry, the conventional reaction: without a
+0018 % trailing _EXP_<n> (full model) or a leading <nnn>_ (light model). A
+0019 % reverse direction keeps its _REV.
+0020 % enzymeSets : cell
+0021 % per model.ec.rxns entry, the UniProt accessions of its enzymes
+0022 % (model.ec.rxnEnzMat), sorted and joined by ' + '; empty without
+0023 % enzymes.
+0024 %
+0025 % Examples
+0026 % --------
+0027 % [baseRxns, enzymeSets] = getEcRxnKeys(ecModel);
+0028 %
+0029 % See also
+0030 % --------
+0031 % saveKcatSource, loadKcatSource
+0032
+0033 if model.ec.geckoLight
+0034 baseRxns = regexprep(model.ec.rxns, '^\d+_', '');
+0035 else
+0036 baseRxns = regexprep(model.ec.rxns, '_EXP_\d+$', '');
+0037 end
+0038 baseRxns = baseRxns(:);
+0039
+0040 n = numel(model.ec.rxns);
+0041 enzymeSets = repmat({''}, n, 1);
+0042 [rows, cols] = find(model.ec.rxnEnzMat);
+0043 if ~isempty(rows)
+0044 % Enzymes per reaction in one pass, rather than a search per reaction.
+0045 perRxn = accumarray(rows(:), cols(:), [n 1], @(c) {c});
+0046 for i = find(~cellfun(@isempty, perRxn))'
+0047 enzymes = unique(model.ec.enzymes(perRxn{i}));
+0048 enzymeSets{i} = strjoin(enzymes(:)', ' + ');
+0049 end
+0050 end
+0051 end
getStandardKcat Assign standard kcat and MW to reactions without enzymes.
+
+
SYNOPSIS
+
function [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model, varargin)
+
+
DESCRIPTION
+
getStandardKcat Assign standard kcat and MW to reactions without enzymes.
+
+ Calculates a standard kcat and standard molecular weight (MW) that can be
+ used to apply enzyme constraints to reactions without any associated
+ enzymes. Such reactions have either an empty model.grRules field, or they
+ have no match in model.ec.rxns (which can be the case if the genes in the
+ model.grRules field could not be mapped to enzymes). This is done by
+ adding those reactions to model.ec, assigning a "standard" pseudoenzyme
+ with the standard MW (median of all proteins in the organism) and standard
+ kcat (median from all kcat, or subsystem specific kcat).
+
+ A reaction is assigned a subSystem specific kcat value if the model has a
+ subSystems field and the reaction is annotated with a subSystem. Only the
+ first subSystem will be considered if multiple are annotated to the same
+ reaction.
+
+ Exchange, transport and pseudoreactions are filtered out, plus any
+ reaction identifiers specified in /data/pseudoRxns.tsv in the model
+ adapter folder.
+
+ In addition, reactions that are annotated with an enzyme (and therefore
+ already in model.ec), but not assigned any reaction-specific kcat value
+ (their model.ec.kcat entry is either 0 or NaN), can be assigned standard
+ kcat values by a similar approach. However, those reactions will not be
+ linked to the "standard" pseudoenzyme, but will use the enzyme that they
+ had already been associated with.
+
+ Any pre-existing standard kcat assignments (identified by 'standard'
+ entries in model.ec.source) are removed when applying this function.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ threshold : double
+ a threshold to determine whether to use a kcat value based on the
+ median kcat of the reactions in the same subSystem or based on the
+ median value of all the kcat in the model. The second option is used
+ when the number of reactions in a determined subSystem with a real
+ (nonzero, non-NaN) kcat value is < threshold (default 10).
+ fillZeroKcat : logical
+ whether zero kcat values should be replaced with standard kcat values
+ (default true).
+
+ Returns
+ -------
+ model : struct
+ ecModel where model.ec is expanded with a standard protein with
+ standard kcat and standard MW, assigned to reactions without gene
+ associations.
+ rxnsMissingGPR : cell
+ a list of updated rxns identifiers with a standard value.
+ standardMW : double
+ the standard MW value calculated.
+ standardKcat : double
+ the standard kcat value calculated.
+ rxnsNoKcat : cell
+ a list of rxns identifiers whose zero kcat has been replaced.
+
+ Notes
+ -----
+ While model.ec.kcat is populated, applyKcatConstraints would still need to
+ be run to apply the new constraints to the S-matrix.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model);
+ [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model, 'threshold', 20);
+
+ See also
+ --------
+ removeStandardKcat, applyKcatConstraints
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
collectKcats collectKcats Decide one kcat per reaction from the kcat source files.
0001 function [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model, varargin)
+0002 % getStandardKcat Assign standard kcat and MW to reactions without enzymes.
+0003 %
+0004 % Calculates a standard kcat and standard molecular weight (MW) that can be
+0005 % used to apply enzyme constraints to reactions without any associated
+0006 % enzymes. Such reactions have either an empty model.grRules field, or they
+0007 % have no match in model.ec.rxns (which can be the case if the genes in the
+0008 % model.grRules field could not be mapped to enzymes). This is done by
+0009 % adding those reactions to model.ec, assigning a "standard" pseudoenzyme
+0010 % with the standard MW (median of all proteins in the organism) and standard
+0011 % kcat (median from all kcat, or subsystem specific kcat).
+0012 %
+0013 % A reaction is assigned a subSystem specific kcat value if the model has a
+0014 % subSystems field and the reaction is annotated with a subSystem. Only the
+0015 % first subSystem will be considered if multiple are annotated to the same
+0016 % reaction.
+0017 %
+0018 % Exchange, transport and pseudoreactions are filtered out, plus any
+0019 % reaction identifiers specified in /data/pseudoRxns.tsv in the model
+0020 % adapter folder.
+0021 %
+0022 % In addition, reactions that are annotated with an enzyme (and therefore
+0023 % already in model.ec), but not assigned any reaction-specific kcat value
+0024 % (their model.ec.kcat entry is either 0 or NaN), can be assigned standard
+0025 % kcat values by a similar approach. However, those reactions will not be
+0026 % linked to the "standard" pseudoenzyme, but will use the enzyme that they
+0027 % had already been associated with.
+0028 %
+0029 % Any pre-existing standard kcat assignments (identified by 'standard'
+0030 % entries in model.ec.source) are removed when applying this function.
+0031 %
+0032 % Parameters
+0033 % ----------
+0034 % model : struct
+0035 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0036 %
+0037 % Name-Value Arguments
+0038 % --------------------
+0039 % modelAdapter : ModelAdapter
+0040 % a loaded model adapter (default: the current default model adapter).
+0041 % threshold : double
+0042 % a threshold to determine whether to use a kcat value based on the
+0043 % median kcat of the reactions in the same subSystem or based on the
+0044 % median value of all the kcat in the model. The second option is used
+0045 % when the number of reactions in a determined subSystem with a real
+0046 % (nonzero, non-NaN) kcat value is < threshold (default 10).
+0047 % fillZeroKcat : logical
+0048 % whether zero kcat values should be replaced with standard kcat values
+0049 % (default true).
+0050 %
+0051 % Returns
+0052 % -------
+0053 % model : struct
+0054 % ecModel where model.ec is expanded with a standard protein with
+0055 % standard kcat and standard MW, assigned to reactions without gene
+0056 % associations.
+0057 % rxnsMissingGPR : cell
+0058 % a list of updated rxns identifiers with a standard value.
+0059 % standardMW : double
+0060 % the standard MW value calculated.
+0061 % standardKcat : double
+0062 % the standard kcat value calculated.
+0063 % rxnsNoKcat : cell
+0064 % a list of rxns identifiers whose zero kcat has been replaced.
+0065 %
+0066 % Notes
+0067 % -----
+0068 % While model.ec.kcat is populated, applyKcatConstraints would still need to
+0069 % be run to apply the new constraints to the S-matrix.
+0070 %
+0071 % Examples
+0072 % --------
+0073 % % optional arguments may be given positionally or as name-value pairs:
+0074 % [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model);
+0075 % [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model, 'threshold', 20);
+0076 %
+0077 % See also
+0078 % --------
+0079 % removeStandardKcat, applyKcatConstraints
+0080
+0081 p = parseGECKOargs(varargin, { ...
+0082 'modelAdapter', []; ...
+0083 'threshold', []; ...
+0084 'fillZeroKcat', []});
+0085 modelAdapter = p.modelAdapter;
+0086 threshold = p.threshold;
+0087 fillZeroKcat = p.fillZeroKcat;
+0088
+0089 if isempty(modelAdapter)
+0090 modelAdapter = ModelAdapterManager.getDefault();
+0091 if isempty(modelAdapter)
+0092 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0093 end
+0094 end
+0095 params = modelAdapter.getParameters();
+0096
+0097 if isempty(threshold)
+0098 threshold = 10;
+0099 end
+0100
+0101 if isempty(fillZeroKcat)
+0102 fillZeroKcat = true;
+0103 end
+0104
+0105 databases = loadDatabases('uniprot', modelAdapter);
+0106
+0107 % An standard MW is defined for all the rxns which does not have a GPR
+0108 % rule defined. This is based in all the proteins reported for the specific
+0109 % organism in uniprot
+0110 standardMW = median(databases.uniprot.MW, 'omitnan');
+0111
+0112 % An standard Kcat is defined for all the rxns which does not have a GPR
+0113 % rule defined. In this case, the kcat value for a particular reaction is
+0114 % defined as the median of the kcat values of the reactions (with a real,
+0115 % nonzero, non-NaN kcat) involved in the same subsystem in which the given
+0116 % reaction is involved. Nevertheless, if a subSystem have a number of such
+0117 % reactions lower than a treshold, the kcat value will be the median of the
+0118 % kcat in all the reactions of the model.
+0119
+0120 % Remove from the list those with kcat zero
+0121 rxnsKcatZero = model.ec.kcat > 0;
+0122
+0123 % Get the kcat value based on all the kcats in the model
+0124 standardKcat = median(model.ec.kcat(rxnsKcatZero), 'omitnan');
+0125
+0126 % If the model have subSystems assigned calculate kcat based on subSystem
+0127 if isfield(model,'subSystems') && ~all(cellfun(@isempty, model.subSystems))
+0128 standard = false;
+0129 if model.ec.geckoLight
+0130 modRxns = extractAfter(model.ec.rxns,4);
+0131 else
+0132 modRxns = model.ec.rxns;
+0133 end
+0134 % Map ec-rxns to model.rxns
+0135 [~,rxnIdx] = ismember(modRxns,model.rxns);
+0136 % Choose first subSystem
+0137 enzSubSystems = flattenCell(model.subSystems(rxnIdx));
+0138 enzSubSystems = enzSubSystems(:,1);
+0139 if ~all(cellfun(@isempty, enzSubSystems))
+0140
+0141 % Make list of unique subsystems, and which rxns are linked to them
+0142 [enzSubSystem_names, ~, rxnToSub] = unique(enzSubSystems);
+0143 % Calculate the median kcat per subSystem, counting only reactions
+0144 % with a real (nonzero, non-NaN) kcat toward both the median itself
+0145 % and the threshold that decides whether a subSystem-specific value
+0146 % is used at all.
+0147 numSubSystem = numel(enzSubSystem_names);
+0148 kcatsPerSubSystem = zeros(numSubSystem,1);
+0149 kcatSubSystem = zeros(numSubSystem,1);
+0150 for i = 1:numSubSystem
+0151 subKcat = model.ec.kcat(rxnToSub == i);
+0152 subKcat = subKcat(subKcat ~= 0 & ~isnan(subKcat));
+0153 kcatsPerSubSystem(i) = numel(subKcat);
+0154 if kcatsPerSubSystem(i) >= threshold
+0155 kcatSubSystem(i) = median(subKcat);
+0156 else
+0157 kcatSubSystem(i) = standardKcat;
+0158 end
+0159 end
+0160 else
+0161 standard = true;
+0162 printOrange('WARNING: No subSystem-specific kcat values can be calculated');
+0163 end
+0164 else
+0165 standard = true;
+0166 printOrange('WARNING: No subSystem-specific kcat values can be calculated');
+0167 end
+0168
+0169 % Find reactions without GPR
+0170 rxnsMissingGPR = find(cellfun(@isempty, model.grRules));
+0171
+0172 % Find reactions with GPR but without model.ec entry (for instance due to
+0173 % no protein matching)
+0174 rxnsMissingEnzyme = find(~cellfun(@isempty, model.grRules));
+0175 if model.ec.geckoLight
+0176 ecRxnsList = unique(extractAfter(model.ec.rxns,4));
+0177 else
+0178 ecRxnsList = model.ec.rxns;
+0179 end
+0180 rxnsMissingEnzyme = find(and(~ismember(model.rxns(rxnsMissingEnzyme),ecRxnsList), ~contains(model.rxns(rxnsMissingEnzyme),'usage_prot_')));
+0181 rxnsMissingGPR = [rxnsMissingGPR;rxnsMissingEnzyme];
+0182
+0183 % Get custom list of reaction IDs to ignore, if existing. First column
+0184 % contains reaction IDs, second column contains reaction names for
+0185 % reference only.
+0186 if exist(fullfile(params.path,'data','pseudoRxns.tsv'),'file')
+0187 fID = fopen(fullfile(params.path,'data','pseudoRxns.tsv'));
+0188 fileData = textscan(fID,'%s %s','delimiter','\t');
+0189 fclose(fID);
+0190 customRxns = fileData{1};
+0191 customRxns = find(ismember(model.rxns,customRxns));
+0192 else
+0193 customRxns = [];
+0194 end
+0195 % Get and remove exchange, transport, spontaneous and pseudo reactions
+0196 [~, exchangeRxns] = getExchangeRxns(model);
+0197 transportRxns = getTransportRxns(model);
+0198 [spontaneousRxns, ~] = modelAdapter.getSpontaneousReactions(model);
+0199 pseudoRxns = contains(model.rxnNames,'pseudoreaction');
+0200 slimeRxns = contains(model.rxnNames,'SLIME rxn');
+0201 rxnsToIgnore = [customRxns; exchangeRxns; find(transportRxns); ...
+0202 find(spontaneousRxns); find(pseudoRxns); find(slimeRxns)];
+0203 rxnsMissingGPR(ismember(rxnsMissingGPR, rxnsToIgnore)) = [];
+0204
+0205 % Only add if not geckoLight & getStandardKcat was not run earlier
+0206 if ~any(strcmp(model.mets,'prot_standard'))
+0207 % Add a new gene to be consistent with ec field named standard
+0208 proteinStdGenes.genes = 'standard';
+0209 if isfield(model,'geneShortNames')
+0210 proteinStdGenes.geneShortNames = 'std';
+0211 end
+0212 model = addGenesRaven(model, proteinStdGenes);
+0213
+0214 if ~model.ec.geckoLight
+0215 % Add a new metabolite named prot_standard
+0216 proteinStdMets.mets = 'prot_standard';
+0217 proteinStdMets.metNames = proteinStdMets.mets;
+0218 % Validate compartment
+0219 compartmentID = strcmp(model.compNames,params.enzyme_comp);
+0220 if ~any(compartmentID)
+0221 error(['Compartment ' params.enzyme_comp ' (specified in params.enzyme_comp) '...
+0222 'cannot be found in model.compNames'])
+0223 end
+0224 proteinStdMets.compartments = model.comps(compartmentID);
+0225
+0226 if isfield(model,'metNotes')
+0227 proteinStdMets.metNotes = 'Standard enzyme-usage pseudometabolite';
+0228 end
+0229 model = addMets(model, proteinStdMets);
+0230
+0231 % Add a protein usage reaction if not a light version
+0232 proteinStdUsageRxn.rxns = {'usage_prot_standard'};
+0233 proteinStdUsageRxn.rxnNames = proteinStdUsageRxn.rxns;
+0234 proteinStdUsageRxn.mets = {proteinStdMets.mets, 'prot_pool'};
+0235 proteinStdUsageRxn.stoichCoeffs = [1, -1];
+0236 proteinStdUsageRxn.lb = 0;
+0237 proteinStdUsageRxn.ub = 1000;
+0238 proteinStdUsageRxn.grRules = proteinStdGenes.genes;
+0239
+0240 model = addRxns(model, proteinStdUsageRxn);
+0241 end
+0242 % Update .ec structure in model
+0243 model.ec.genes(end+1) = {'standard'};
+0244 model.ec.enzymes(end+1) = {'standard'};
+0245 model.ec.mw(end+1) = standardMW;
+0246 model.ec.sequence(end+1) = {''};
+0247 % Additional info
+0248 if isfield(model.ec,'concs')
+0249 model.ec.concs(end+1) = nan();
+0250 end
+0251
+0252 % Expand the enzyme rxns matrix
+0253 model.ec.rxnEnzMat = [model.ec.rxnEnzMat, zeros(length(model.ec.rxns), 1)]; % 1 new enzyme
+0254 model.ec.rxnEnzMat = [model.ec.rxnEnzMat; zeros(length(rxnsMissingGPR), length(model.ec.enzymes))]; % new rxns
+0255 end
+0256 stdMetIdx = find(strcmpi(model.ec.enzymes, 'standard'));
+0257
+0258 % Remove previous standard kcat assignment
+0259 oldStandardEnz = find(strcmp(model.ec.source,'standard'));
+0260 if ~isempty(oldStandardEnz)
+0261 oldStandardProt = logical(model.ec.rxnEnzMat(oldStandardEnz,stdMetIdx));
+0262 % If annotated with real enzyme => set kcat to zero
+0263 model.ec.kcat(oldStandardEnz(~oldStandardProt)) = 0;
+0264 model.ec.source(oldStandardEnz(~oldStandardProt)) = {''};
+0265 % If annotated with standard protein => remove entry
+0266 model.ec.rxns(oldStandardEnz(oldStandardProt)) = [];
+0267 model.ec.kcat(oldStandardEnz(oldStandardProt)) = [];
+0268 model.ec.source(oldStandardEnz(oldStandardProt)) = [];
+0269 model.ec.notes(oldStandardEnz(oldStandardProt)) = [];
+0270 model.ec.eccodes(oldStandardEnz(oldStandardProt)) = [];
+0271 model.ec.rxnEnzMat(oldStandardEnz(oldStandardProt),:) = [];
+0272 end
+0273
+0274 numRxns = length(model.ec.rxns);
+0275 for i = 1:numel(rxnsMissingGPR)
+0276 rxnIdx = rxnsMissingGPR(i);
+0277
+0278 % Update .ec structure in model
+0279 if ~model.ec.geckoLight
+0280 model.ec.rxns(end+1) = model.rxns(rxnIdx);
+0281 % Add prefix in case is light version
+0282 else
+0283 model.ec.rxns{end+1} = ['001_' model.rxns{rxnIdx}];
+0284 end
+0285
+0286 if ~standard
+0287 % kcatSubSystemIdx is a one-hot vector (the reaction's first subSystem
+0288 % matched against every subSystem that has a computed kcat); any(), not
+0289 % all(), is the right test -- all() only ever succeeds when the model
+0290 % has exactly one subSystem in total, silently falling back to the
+0291 % model-wide standardKcat for every reaction otherwise.
+0292 kcatSubSystemIdx = strcmpi(enzSubSystem_names, model.subSystems{rxnIdx}(1));
+0293 if any(kcatSubSystemIdx)
+0294 model.ec.kcat(end+1) = kcatSubSystem(kcatSubSystemIdx);
+0295 else
+0296 model.ec.kcat(end+1) = standardKcat;
+0297 end
+0298 else
+0299 model.ec.kcat(end+1) = standardKcat;
+0300 end
+0301
+0302 model.ec.source(end+1) = {'standard'};
+0303 model.ec.notes(end+1) = {''};
+0304 model.ec.eccodes(end+1) = {''};
+0305
+0306 % Update the enzyme rxns matrix
+0307 model.ec.rxnEnzMat(numRxns+i, stdMetIdx) = 1;
+0308 end
+0309 % Get the rxns identifiers of the updated rxns
+0310 rxnsMissingGPR = model.rxns(rxnsMissingGPR);
+0311
+0312 if fillZeroKcat
+0313 zeroKcat = model.ec.kcat == 0 | isnan(model.ec.kcat);
+0314 model.ec.kcat(zeroKcat) = standardKcat;
+0315 model.ec.source(zeroKcat) = {'standard'};
+0316 rxnsNoKcat = model.ec.rxns(zeroKcat);
+0317 else
+0318 rxnsNoKcat = [];
+0319 end
+0320 end
+0321
+0322 function Cflat = flattenCell(C,strFlag)
+0323 %FLATTENCELL Flatten a nested column cell array into a matrix cell array.
+0324 %
+0325 % CFLAT = FLATTENCELL(C) takes a column cell array in which one or more
+0326 % entries is a nested cell array, and flattens it into a 2D matrix cell
+0327 % array, where the nested entries are spread across new columns.
+0328 %
+0329 % CFLAT = FLATTENCELL(C,STRFLAG) if STRFLAG is TRUE, empty entries in the
+0330 % resulting CFLAT will be replaced with empty strings {''}. Default = FALSE
+0331 if nargin < 2
+0332 strFlag = false;
+0333 end
+0334
+0335 % determine which entries are cells
+0336 cells = cellfun(@iscell,C);
+0337
+0338 % determine number of elements in each nested cell
+0339 cellsizes = cellfun(@numel,C);
+0340 cellsizes(~cells) = 1; % ignore non-cell entries
+0341
+0342 % flatten single-entry cells
+0343 Cflat = C;
+0344 Cflat(cells & (cellsizes == 1)) = cellfun(@(x) x{1},Cflat(cells & (cellsizes == 1)),'UniformOutput',false);
+0345
+0346 % iterate through multi-entry cells
+0347 multiCells = find(cellsizes > 1);
+0348 for i = 1:length(multiCells)
+0349 cellContents = Cflat{multiCells(i)};
+0350 Cflat(multiCells(i),1:length(cellContents)) = cellContents;
+0351 end
+0352
+0353 % change empty elements to strings, if specified
+0354 if ( strFlag )
+0355 Cflat(cellfun(@isempty,Cflat)) = {''};
+0356 end
+0357 end
kcatAssignmentConfig The kcat assignment settings of a model adapter.
+
+
SYNOPSIS
+
function config = kcatAssignmentConfig(params)
+
+
DESCRIPTION
+
kcatAssignmentConfig The kcat assignment settings of a model adapter.
+
+ Reads [kcat_assignment] and [kcat_sources.*] of model_adapter.toml (or
+ params.kcat_assignment and params.kcat_sources of an adapter class), and
+ expands a preset into its stages, as collectKcats and updateKcatSources
+ use them. Without a preset or stages the preset is brenda_ec_tree.
+ geckopy's kcat_assignment_config gives the same settings.
+
+ Parameters
+ ----------
+ params : struct
+ the parameters of a model adapter (modelAdapter.params).
+
+ Returns
+ -------
+ config : struct
+ - preset: the preset name, or '' when stages are given;
+ - isozymes, standard: logical;
+ - stages: struct array with action ('select' or 'combine'), sources
+ (cell, as given), files (the source files they read, with okp
+ replaced by the files of its methods), criteria, topOriginLimit,
+ bottomOriginLimit and wildcardLimit;
+ - okpMethods: the OKP methods of the okp source group ([kcat_sources.okp]
+ methods; default all methods with combination parameters);
+ - sources: per source name its options (from [kcat_sources] and the
+ preset).
+
+ Examples
+ --------
+ config = kcatAssignmentConfig(modelAdapter.params);
+ config.stages(1).sources % the order of the sources
+
+ See also
+ --------
+ collectKcats, assignKcats, updateKcatSources
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
collectKcats collectKcats Decide one kcat per reaction from the kcat source files.
updateKcatSources updateKcatSources Gather the kcat sources that the configuration needs.
kcatsFromBrenda Gather kcats from BRENDA and keep them as a source file.
+
+
SYNOPSIS
+
function kcatList = kcatsFromBrenda(model, varargin)
+
+
DESCRIPTION
+
kcatsFromBrenda Gather kcats from BRENDA and keep them as a source file.
+
+ Matches the model's EC numbers and substrates to BRENDA by fuzzy
+ matching (see fuzzyKcatMatching), and writes the result to
+ data/kcats/brenda.tsv in the project folder (see saveKcatSource). The
+ matching runs locally on the BRENDA files of GECKO.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure), with EC
+ numbers (getECfromDatabase or getECfromGEM).
+
+ Name-Value Arguments
+ --------------------
+ aggregation : char
+ which BRENDA value per EC number, substrate and organism is used:
+ 'max' or 'median' (default: params.kcatAggregation of the model
+ adapter, or 'max').
+ forceWClvl : double
+ number of extra levels, from the right, replaced by a wildcard in the
+ EC numbers (on top of any wildcard already in the code; default 0).
+ ecRxns : logical or double
+ for which model.ec.rxns kcats are gathered (default: all). A logical
+ vector or indices of model.ec.rxns, see fuzzyKcatMatching; [] selects
+ all reactions and any other empty selection is an error.
+ save : logical
+ write the source file (default true).
+ saveAs : char
+ name of the source file, without .tsv (default 'brenda'). With
+ ecRxns, the file then only holds those reactions.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ kcatList : struct
+ the kcat values, as returned by fuzzyKcatMatching.
+
+ Examples
+ --------
+ kcatsFromBrenda(ecModel);
+ kcatList = kcatsFromBrenda(ecModel, 'aggregation', 'median');
+
+ See also
+ --------
+ fuzzyKcatMatching, kcatsFromDLKcat, kcatsFromOKP, saveKcatSource, loadKcatSource
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
fuzzyKcatMatching fuzzyKcatMatching Match model EC numbers and substrates to BRENDA kcats.
saveKcatSource saveKcatSource Write a kcatList to a kcat source file of the project.
+This function is called by:
+
+
updateKcatSources updateKcatSources Gather the kcat sources that the configuration needs.
0001 function kcatList = kcatsFromBrenda(model, varargin)
+0002 % kcatsFromBrenda Gather kcats from BRENDA and keep them as a source file.
+0003 %
+0004 % Matches the model's EC numbers and substrates to BRENDA by fuzzy
+0005 % matching (see fuzzyKcatMatching), and writes the result to
+0006 % data/kcats/brenda.tsv in the project folder (see saveKcatSource). The
+0007 % matching runs locally on the BRENDA files of GECKO.
+0008 %
+0009 % Parameters
+0010 % ----------
+0011 % model : struct
+0012 % an ecModel in GECKO 3 format (with ecModel.ec structure), with EC
+0013 % numbers (getECfromDatabase or getECfromGEM).
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % aggregation : char
+0018 % which BRENDA value per EC number, substrate and organism is used:
+0019 % 'max' or 'median' (default: params.kcatAggregation of the model
+0020 % adapter, or 'max').
+0021 % forceWClvl : double
+0022 % number of extra levels, from the right, replaced by a wildcard in the
+0023 % EC numbers (on top of any wildcard already in the code; default 0).
+0024 % ecRxns : logical or double
+0025 % for which model.ec.rxns kcats are gathered (default: all). A logical
+0026 % vector or indices of model.ec.rxns, see fuzzyKcatMatching; [] selects
+0027 % all reactions and any other empty selection is an error.
+0028 % save : logical
+0029 % write the source file (default true).
+0030 % saveAs : char
+0031 % name of the source file, without .tsv (default 'brenda'). With
+0032 % ecRxns, the file then only holds those reactions.
+0033 % modelAdapter : ModelAdapter
+0034 % a loaded model adapter (default: the current default model adapter).
+0035 %
+0036 % Returns
+0037 % -------
+0038 % kcatList : struct
+0039 % the kcat values, as returned by fuzzyKcatMatching.
+0040 %
+0041 % Examples
+0042 % --------
+0043 % kcatsFromBrenda(ecModel);
+0044 % kcatList = kcatsFromBrenda(ecModel, 'aggregation', 'median');
+0045 %
+0046 % See also
+0047 % --------
+0048 % fuzzyKcatMatching, kcatsFromDLKcat, kcatsFromOKP, saveKcatSource, loadKcatSource
+0049
+0050 checkOptions(varargin, {'aggregation','forceWClvl','ecRxns','save','saveAs','modelAdapter'}, 'kcatsFromBrenda');
+0051 p = parseGECKOargs(varargin, { ...
+0052 'aggregation', []; ...
+0053 'forceWClvl', 0; ...
+0054 'ecRxns', []; ...
+0055 'save', true; ...
+0056 'saveAs', 'brenda'; ...
+0057 'modelAdapter', []});
+0058 modelAdapter = p.modelAdapter;
+0059 if isempty(modelAdapter)
+0060 modelAdapter = ModelAdapterManager.getDefault();
+0061 if isempty(modelAdapter)
+0062 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0063 end
+0064 end
+0065 aggregation = resolveKcatAggregation(p.aggregation, modelAdapter);
+0066 ifisSubset(p.ecRxns) && countSelected(p.ecRxns) == 0
+0067 error('kcatsFromBrenda:emptySubset', ...
+0068 'ecRxns selects no reactions; leave it out to gather for all reactions.');
+0069 end
+0070 if p.save
+0071 file = fullfile(modelAdapter.params.path, 'data', 'kcats', [char(p.saveAs) '.tsv']);
+0072 warnSubset(p.ecRxns, numel(model.ec.rxns), file);
+0073 end
+0074
+0075 kcatList = fuzzyKcatMatching(model, 'ecRxns', p.ecRxns, 'modelAdapter', modelAdapter, ...
+0076 'forceWClvl', p.forceWClvl, 'kcatAggregation', aggregation);
+0077
+0078 if p.save
+0079 parameters = struct('aggregation', aggregation, 'force_wildcard_level', p.forceWClvl);
+0080 ifisSubset(p.ecRxns)
+0081 parameters.ec_rxns = numel(kcatList.rxns);
+0082 end
+0083 saveKcatSource(model, kcatList, p.saveAs, 'modelAdapter', modelAdapter, ...
+0084 'generator', 'kcatsFromBrenda', 'parameters', parameters, ...
+0085 'data', brendaRelease(modelAdapter));
+0086 fprintf('BRENDA kcats for %d reactions written to %s\n', sum(kcatList.kcats > 0), file);
+0087 end
+0088 end
+0089
+0090 function release = brendaRelease(modelAdapter)
+0091 % The release line at the top of kcat.tsv, e.g. 'BRENDA release 2026.1'.
+0092 release = '';
+0093 file = fullfile(modelAdapter.getBrendaDBFolder(), 'kcat.tsv');
+0094 fid = fopen(file, 'r');
+0095 if fid < 0
+0096 return
+0097 end
+0098 line = fgetl(fid);
+0099 fclose(fid);
+0100 tok = regexp(line, '^#\s*(BRENDA release [^ ]+)', 'tokens', 'once');
+0101 if ~isempty(tok)
+0102 release = tok{1};
+0103 end
+0104 end
+0105
+0106 function tf = isSubset(ecRxns)
+0107 % [] selects all reactions; any other value selects the reactions it names.
+0108 tf = ~(isnumeric(ecRxns) && isequal(size(ecRxns),[0 0]));
+0109 end
+0110
+0111 function n = countSelected(ecRxns)
+0112 if islogical(ecRxns)
+0113 n = sum(ecRxns);
+0114 else
+0115 n = numel(ecRxns);
+0116 end
+0117 end
+0118
+0119 function n = countRxns(ecRxns)
+0120 if islogical(ecRxns)
+0121 n = sum(ecRxns);
+0122 else
+0123 n = numel(unique(ecRxns));
+0124 end
+0125 end
+0126
+0127 function warnSubset(ecRxns, nRxns, file)
+0128 % A subset written under the default name replaces the full file.
+0129 ifisSubset(ecRxns) && countRxns(ecRxns) < nRxns && isfile(file)
+0130 printOrange(sprintf(['%s is replaced by kcats for %d reactions only. Give saveAs '...
+0131 'to keep it.\n'], file, countRxns(ecRxns)));
+0132 end
+0133 end
+0134
+0135 function checkOptions(args, names, functionName)
+0136 % Options are name-value pairs with known names, so a misspelled option is
+0137 % an error instead of being taken as a positional argument.
+0138 id = [functionName ':arguments'];
+0139 given = args(1:2:end);
+0140 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0141 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0142 end
+0143 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0144 if ~isempty(unknown)
+0145 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0146 strjoin(names, ', '))
+0147 end
+0148 end
kcatsFromDLKcat Gather kcats from DLKcat and keep them as a source file.
+
+
SYNOPSIS
+
function kcatList = kcatsFromDLKcat(model, varargin)
+
+
DESCRIPTION
+
kcatsFromDLKcat Gather kcats from DLKcat and keep them as a source file.
+
+ Predicts kcats with DLKcat and writes them to data/kcats/dlkcat.tsv in
+ the project folder (see saveKcatSource). With 'run', true it writes the
+ DLKcat input (writeDLKcatInput) and runs DLKcat through its Docker image
+ (runDLKcat), which needs Docker; otherwise it reads the output of an
+ earlier run (readDLKcatOutput). A run records how many reactions it
+ covered in data/DLKcat_run.txt, so that the source file says so also when
+ the output is read later.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure), with
+ metabolite SMILES (findMetSmiles) and protein sequences.
+
+ Name-Value Arguments
+ --------------------
+ run : logical
+ write the input and run DLKcat; false reads the existing output in
+ data/DLKcat.tsv (default false).
+ ecRxns : logical
+ for which model.ec.rxns DLKcat is run (default: all). Only used with
+ 'run', true.
+ save : logical
+ write the source file (default true).
+ saveAs : char
+ name of the source file, without .tsv (default 'dlkcat').
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ kcatList : struct
+ the kcat values, as returned by readDLKcatOutput.
+
+ Examples
+ --------
+ kcatsFromDLKcat(ecModel, 'run', true);
+ kcatList = kcatsFromDLKcat(ecModel);
+
+ See also
+ --------
+ writeDLKcatInput, runDLKcat, readDLKcatOutput, kcatsFromBrenda, saveKcatSource
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
readDLKcatOutput readDLKcatOutput Read the DLKcat output file into a kcatList structure.
runDLKcat runDLKcat Run DLKcat to predict kcat values from a Docker image.
saveKcatSource saveKcatSource Write a kcatList to a kcat source file of the project.
writeDLKcatInput writeDLKcatInput Prepare and write the input file for DLKcat.
+This function is called by:
+
+
updateKcatSources updateKcatSources Gather the kcat sources that the configuration needs.
0001 function kcatList = kcatsFromDLKcat(model, varargin)
+0002 % kcatsFromDLKcat Gather kcats from DLKcat and keep them as a source file.
+0003 %
+0004 % Predicts kcats with DLKcat and writes them to data/kcats/dlkcat.tsv in
+0005 % the project folder (see saveKcatSource). With 'run', true it writes the
+0006 % DLKcat input (writeDLKcatInput) and runs DLKcat through its Docker image
+0007 % (runDLKcat), which needs Docker; otherwise it reads the output of an
+0008 % earlier run (readDLKcatOutput). A run records how many reactions it
+0009 % covered in data/DLKcat_run.txt, so that the source file says so also when
+0010 % the output is read later.
+0011 %
+0012 % Parameters
+0013 % ----------
+0014 % model : struct
+0015 % an ecModel in GECKO 3 format (with ecModel.ec structure), with
+0016 % metabolite SMILES (findMetSmiles) and protein sequences.
+0017 %
+0018 % Name-Value Arguments
+0019 % --------------------
+0020 % run : logical
+0021 % write the input and run DLKcat; false reads the existing output in
+0022 % data/DLKcat.tsv (default false).
+0023 % ecRxns : logical
+0024 % for which model.ec.rxns DLKcat is run (default: all). Only used with
+0025 % 'run', true.
+0026 % save : logical
+0027 % write the source file (default true).
+0028 % saveAs : char
+0029 % name of the source file, without .tsv (default 'dlkcat').
+0030 % modelAdapter : ModelAdapter
+0031 % a loaded model adapter (default: the current default model adapter).
+0032 %
+0033 % Returns
+0034 % -------
+0035 % kcatList : struct
+0036 % the kcat values, as returned by readDLKcatOutput.
+0037 %
+0038 % Examples
+0039 % --------
+0040 % kcatsFromDLKcat(ecModel, 'run', true);
+0041 % kcatList = kcatsFromDLKcat(ecModel);
+0042 %
+0043 % See also
+0044 % --------
+0045 % writeDLKcatInput, runDLKcat, readDLKcatOutput, kcatsFromBrenda, saveKcatSource
+0046
+0047 checkOptions(varargin, {'run','ecRxns','save','saveAs','modelAdapter'}, 'kcatsFromDLKcat');
+0048 p = parseGECKOargs(varargin, { ...
+0049 'run', false; ...
+0050 'ecRxns', []; ...
+0051 'save', true; ...
+0052 'saveAs', 'dlkcat'; ...
+0053 'modelAdapter', []});
+0054 modelAdapter = p.modelAdapter;
+0055 if isempty(modelAdapter)
+0056 modelAdapter = ModelAdapterManager.getDefault();
+0057 if isempty(modelAdapter)
+0058 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0059 end
+0060 end
+0061 if ~p.run && ~isempty(p.ecRxns)
+0062 error('kcatsFromDLKcat:ecRxns', ['ecRxns selects the reactions DLKcat is run for, so '...
+0063 'it needs ''run'', true.'])
+0064 end
+0065 if p.save
+0066 file = fullfile(modelAdapter.params.path, 'data', 'kcats', [char(p.saveAs) '.tsv']);
+0067 if p.run
+0068 warnSubset(p.ecRxns, numel(model.ec.rxns), file);
+0069 end
+0070 end
+0071
+0072 runFile = fullfile(modelAdapter.params.path, 'data', 'DLKcat_run.txt');
+0073 if p.run
+0074 % As runDLKcat: on macOS, MATLAB started from the launcher may not
+0075 % have Docker on its path.
+0076 if ismac
+0077 setenv('PATH', strcat('/usr/local/bin', ':', getenv("PATH")));
+0078 end
+0079 [status, ~] = system('docker --version');
+0080 if status ~= 0
+0081 error('kcatsFromDLKcat:noDocker', ['DLKcat runs in a Docker image, but Docker was '...
+0082 'not found. Install it from https://www.docker.com, or call kcatsFromDLKcat '...
+0083 'without ''run'' to read an earlier DLKcat output.'])
+0084 end
+0085 writeDLKcatInput(model, 'ecRxns', p.ecRxns, 'modelAdapter', modelAdapter, 'overwrite', true);
+0086 runDLKcat('modelAdapter', modelAdapter);
+0087 fid = fopen(runFile, 'w');
+0088 if ~isempty(p.ecRxns)
+0089 fprintf(fid, 'ecRxns: %d\n', countRxns(p.ecRxns));
+0090 end
+0091 fclose(fid);
+0092 else
+0093 outFile = fullfile(modelAdapter.params.path, 'data', 'DLKcat.tsv');
+0094 if ~isfile(outFile)
+0095 error('kcatsFromDLKcat:noOutput', ['No DLKcat output at %s. Run '...
+0096 'kcatsFromDLKcat(model, ''run'', true) first.'], outFile)
+0097 end
+0098 end
+0099 kcatList = readDLKcatOutput(model, 'modelAdapter', modelAdapter);
+0100
+0101 if p.save
+0102 parameters = struct('run', logical(p.run));
+0103 if isfile(runFile)
+0104 tok = regexp(fileread(runFile), 'ecRxns:\s*(\d+)', 'tokens', 'once');
+0105 if ~isempty(tok)
+0106 parameters.ec_rxns = str2double(tok{1});
+0107 end
+0108 end
+0109 saveKcatSource(model, kcatList, p.saveAs, 'modelAdapter', modelAdapter, ...
+0110 'generator', 'kcatsFromDLKcat', 'parameters', parameters);
+0111 fprintf('DLKcat kcats for %d reactions written to %s\n', ...
+0112 numel(unique(kcatList.rxns(kcatList.kcats > 0))), file);
+0113 end
+0114 end
+0115
+0116 function n = countRxns(ecRxns)
+0117 if islogical(ecRxns)
+0118 n = sum(ecRxns);
+0119 else
+0120 n = numel(unique(ecRxns));
+0121 end
+0122 end
+0123
+0124 function warnSubset(ecRxns, nRxns, file)
+0125 % A subset written under the default name replaces the full file.
+0126 if ~isempty(ecRxns) && countRxns(ecRxns) < nRxns && isfile(file)
+0127 printOrange(sprintf(['%s is replaced by kcats for %d reactions only. Give saveAs '...
+0128 'to keep it.\n'], file, countRxns(ecRxns)));
+0129 end
+0130 end
+0131
+0132 function checkOptions(args, names, functionName)
+0133 % Options are name-value pairs with known names, so a misspelled option is
+0134 % an error instead of being taken as a positional argument.
+0135 id = [functionName ':arguments'];
+0136 given = args(1:2:end);
+0137 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0138 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0139 end
+0140 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0141 if ~isempty(unknown)
+0142 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0143 strjoin(names, ', '))
+0144 end
+0145 end
kcatsFromECTree Estimate kcats from the EC tree and keep them as a source file.
+
+
SYNOPSIS
+
function kcatList = kcatsFromECTree(model, varargin)
+
+
DESCRIPTION
+
kcatsFromECTree Estimate kcats from the EC tree and keep them as a source file.
+
+ Each reaction gets the median kcat of all wild-type BRENDA measurements
+ of its EC number, from any organism and substrate, shrunk toward the
+ median of the EC's 3-digit and 2-digit classes with weight n / (n + k)
+ for n measurements (see ecTreeEstimate). A reaction with several EC
+ numbers uses the one with most measurements. Reactions without an EC
+ number, or whose EC classes have no measurements, get no value. The
+ result is written to data/kcats/ec_tree.tsv in the project folder (see
+ saveKcatSource).
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure), with EC
+ numbers (getECfromDatabase or getECfromGEM).
+
+ Name-Value Arguments
+ --------------------
+ k : double
+ shrinkage constant (default 5): the number of measurements at which
+ an EC's own median and its class count equally.
+ ecRxns : logical
+ for which model.ec.rxns kcats are estimated (default: all).
+ measurements : struct
+ BRENDA measurement table (default: loadBrendaMeasurements with the
+ model adapter).
+ save : logical
+ write the source file (default true).
+ saveAs : char
+ name of the source file, without .tsv (default 'ec_tree'). With
+ ecRxns, the file then only holds those reactions.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ kcatList : struct
+ the kcat values, with fields source, rxns, eccodes, kcats and detail
+ (the number of measurements of the EC, n, and the deepest EC level
+ with measurements, depth).
+
+ Examples
+ --------
+ kcatsFromECTree(ecModel);
+ kcatList = kcatsFromECTree(ecModel, 'k', 10, 'save', false);
+
+ See also
+ --------
+ ecTreeEstimate, loadBrendaMeasurements, kcatsFromBrenda, saveKcatSource, assignKcats
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
ecTreeEstimate ecTreeEstimate EC-tree kcat estimates for EC numbers.
0001 function kcatList = kcatsFromECTree(model, varargin)
+0002 % kcatsFromECTree Estimate kcats from the EC tree and keep them as a source file.
+0003 %
+0004 % Each reaction gets the median kcat of all wild-type BRENDA measurements
+0005 % of its EC number, from any organism and substrate, shrunk toward the
+0006 % median of the EC's 3-digit and 2-digit classes with weight n / (n + k)
+0007 % for n measurements (see ecTreeEstimate). A reaction with several EC
+0008 % numbers uses the one with most measurements. Reactions without an EC
+0009 % number, or whose EC classes have no measurements, get no value. The
+0010 % result is written to data/kcats/ec_tree.tsv in the project folder (see
+0011 % saveKcatSource).
+0012 %
+0013 % Parameters
+0014 % ----------
+0015 % model : struct
+0016 % an ecModel in GECKO 3 format (with ecModel.ec structure), with EC
+0017 % numbers (getECfromDatabase or getECfromGEM).
+0018 %
+0019 % Name-Value Arguments
+0020 % --------------------
+0021 % k : double
+0022 % shrinkage constant (default 5): the number of measurements at which
+0023 % an EC's own median and its class count equally.
+0024 % ecRxns : logical
+0025 % for which model.ec.rxns kcats are estimated (default: all).
+0026 % measurements : struct
+0027 % BRENDA measurement table (default: loadBrendaMeasurements with the
+0028 % model adapter).
+0029 % save : logical
+0030 % write the source file (default true).
+0031 % saveAs : char
+0032 % name of the source file, without .tsv (default 'ec_tree'). With
+0033 % ecRxns, the file then only holds those reactions.
+0034 % modelAdapter : ModelAdapter
+0035 % a loaded model adapter (default: the current default model adapter).
+0036 %
+0037 % Returns
+0038 % -------
+0039 % kcatList : struct
+0040 % the kcat values, with fields source, rxns, eccodes, kcats and detail
+0041 % (the number of measurements of the EC, n, and the deepest EC level
+0042 % with measurements, depth).
+0043 %
+0044 % Examples
+0045 % --------
+0046 % kcatsFromECTree(ecModel);
+0047 % kcatList = kcatsFromECTree(ecModel, 'k', 10, 'save', false);
+0048 %
+0049 % See also
+0050 % --------
+0051 % ecTreeEstimate, loadBrendaMeasurements, kcatsFromBrenda, saveKcatSource, assignKcats
+0052
+0053 checkOptions(varargin, {'k','ecRxns','measurements','save','saveAs','modelAdapter'}, 'kcatsFromECTree');
+0054 p = parseGECKOargs(varargin, { ...
+0055 'k', 5; ...
+0056 'ecRxns', []; ...
+0057 'measurements', []; ...
+0058 'save', true; ...
+0059 'saveAs', 'ec_tree'; ...
+0060 'modelAdapter', []});
+0061 modelAdapter = p.modelAdapter;
+0062 if isempty(modelAdapter)
+0063 modelAdapter = ModelAdapterManager.getDefault();
+0064 if isempty(modelAdapter)
+0065 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0066 end
+0067 end
+0068 if ~isfield(model.ec, 'eccodes')
+0069 error('No EC codes defined in model.ec.eccodes. First run getECfromGEM() and/or getECfromDatabase().')
+0070 end
+0071 if p.save
+0072 file = fullfile(modelAdapter.params.path, 'data', 'kcats', [char(p.saveAs) '.tsv']);
+0073 warnSubset(p.ecRxns, numel(model.ec.rxns), file);
+0074 end
+0075 measurements = p.measurements;
+0076 if isempty(measurements)
+0077 measurements = loadBrendaMeasurements('modelAdapter', modelAdapter);
+0078 end
+0079
+0080 idx = (1:numel(model.ec.rxns))';
+0081 if ~isempty(p.ecRxns)
+0082 idx = idx(p.ecRxns(:));
+0083 end
+0084 eccodes = model.ec.eccodes(idx);
+0085 eccodes(cellfun(@(e) ~ischar(e) && ~isstring(e), eccodes)) = {''};
+0086 [values, n, depth, ecUsed] = ecTreeEstimate(eccodes, measurements, p.k);
+0087 found = depth > 0;
+0088 idx = idx(found);
+0089 kcatList.source = 'ec_tree';
+0090 kcatList.rxns = model.ec.rxns(idx);
+0091 kcatList.eccodes = ecUsed(found);
+0092 kcatList.kcats = 10 .^ values(found);
+0093 kcatList.detail = cellstr("n=" + string(n(found)) + "; depth=" + string(depth(found)));
+0094
+0095 if p.save
+0096 parameters = struct('k', p.k);
+0097 if ~isempty(p.ecRxns)
+0098 parameters.ec_rxns = countRxns(p.ecRxns);
+0099 end
+0100 saveKcatSource(model, kcatList, p.saveAs, 'modelAdapter', modelAdapter, ...
+0101 'generator', 'kcatsFromECTree', 'parameters', parameters, ...
+0102 'data', measurements.release);
+0103 fprintf('EC-tree kcats for %d reactions written to %s\n', numel(kcatList.rxns), file);
+0104 end
+0105 end
+0106
+0107 function n = countRxns(ecRxns)
+0108 if islogical(ecRxns)
+0109 n = sum(ecRxns);
+0110 else
+0111 n = numel(unique(ecRxns));
+0112 end
+0113 end
+0114
+0115 function warnSubset(ecRxns, nRxns, file)
+0116 % A subset written under the default name replaces the full file.
+0117 if ~isempty(ecRxns) && countRxns(ecRxns) < nRxns && isfile(file)
+0118 printOrange(sprintf(['%s is replaced by kcats for %d reactions only. Give saveAs '...
+0119 'to keep it.\n'], file, countRxns(ecRxns)));
+0120 end
+0121 end
+0122
+0123 function checkOptions(args, names, functionName)
+0124 % Options are name-value pairs with known names, so a misspelled option is
+0125 % an error instead of being taken as a positional argument.
+0126 id = [functionName ':arguments'];
+0127 given = args(1:2:end);
+0128 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0129 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0130 end
+0131 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0132 if ~isempty(unknown)
+0133 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0134 strjoin(names, ', '))
+0135 end
+0136 end
kcatsFromHomologs Estimate kcats from measured homologs and keep them as a source file.
+
+
SYNOPSIS
+
function kcatList = kcatsFromHomologs(model, varargin)
+
+
DESCRIPTION
+
kcatsFromHomologs Estimate kcats from measured homologs and keep them as a source file.
+
+ For each reaction, enzyme and substrate (the substrates DLKcat would use,
+ see extractEnzymeSubstratePairs), the enzyme's sequence is searched with
+ DIAMOND against the sequences of the BRENDA measurements (at least 40%
+ identity, 80% coverage of both sequences). Each homolog measured on the
+ same substrate (same InChIKey, without the protonation flag) contributes
+ the median of its measurements; the estimate is the mean of the k most
+ identical such homologs, weighted by identity. Homologs at 95% identity or
+ more are measurements of (nearly) the same protein: when there are any,
+ the estimate is the median of those alone. For an enzyme complex the
+ estimate is the median over its subunits. Model metabolites are matched to
+ InChIKeys through substrates.tsv of the BRENDA files: their ChEBI
+ annotations, then MetaNetX annotations (model.metMiriams), then their
+ names. Reactions without a homolog measured on one of their substrates get
+ no value. The result is written to data/kcats/homologs.tsv (see
+ saveKcatSource). DIAMOND is RAVEN's (getDiamond; downloaded when
+ missing). geckopy has the same (kcats_from_homologs).
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure), with enzyme
+ sequences (model.ec.sequence).
+
+ Name-Value Arguments
+ --------------------
+ k : double
+ the number of most identical homologs averaged (default 10).
+ minIdentity : double
+ DIAMOND identity threshold, as a fraction (default 0.4).
+ coverage : double
+ DIAMOND coverage threshold of both sequences, as a fraction (default 0.8).
+ threads : double
+ DIAMOND threads (default: DIAMOND's).
+ save : logical
+ write the source file (default true).
+ saveAs : char
+ name of the source file, without .tsv (default 'homologs').
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ kcatList : struct
+ the kcat values, with fields source, rxns, substrates, kcats and
+ detail (the identity of the nearest homolog, identity, and the number
+ of homologs used, n).
+
+ Examples
+ --------
+ kcatsFromHomologs(ecModel);
+
+ See also
+ --------
+ kcatsFromECTree, extractEnzymeSubstratePairs, saveKcatSource, collectKcats
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
extractEnzymeSubstratePairs extractEnzymeSubstratePairs The enzyme-substrate pairs of an ecModel's reactions.
kcatsFromOKP Gather kcats from OpenKineticsPredictor and keep them as a source file.
+
+
SYNOPSIS
+
function [kcatList, done] = kcatsFromOKP(model, varargin)
+
+
DESCRIPTION
+
kcatsFromOKP Gather kcats from OpenKineticsPredictor and keep them as a source file.
+
+ Predicts kcats with one OpenKineticsPredictor (OKP) method and writes
+ them to data/kcats/okp_<method>.tsv in the project folder (see
+ saveKcatSource). OKP is a web service with a job queue, so this function
+ can be called repeatedly:
+
+ - without a job for this method, it submits one
+ (submitOpenKineticsPredictor);
+ - with a job, it fetches the result (fetchOpenKineticsPredictor). When
+ the job is done the file is written; otherwise it reports the job's
+ status and writes nothing.
+
+ Each method keeps its own job in data/OKP_job_<method>.txt and its own
+ result in data/OKP_output_<method>.csv, so jobs of several methods can
+ run at the same time. The job file records which job the stored result
+ came from; useStored only uses a result of the current job. An API key
+ is needed (see resolveOkpApiKey).
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure), with
+ metabolite SMILES (findMetSmiles) and protein sequences.
+
+ Name-Value Arguments
+ --------------------
+ method : char
+ OKP method, e.g. 'CataPro', 'CatPred', 'CatRange', 'DLKcat',
+ 'EITLEM', 'KinForm-H', 'KinForm-L', 'OmniESI', 'TurNup', 'UniKP'
+ (default: params.okp.method of the model adapter, or 'CataPro').
+ wait : logical
+ wait until the job is done (default false).
+ resubmit : logical
+ submit a new job even if one exists for this method; its earlier
+ result is removed (default false).
+ useStored : logical
+ read the stored result of this method's job, without contacting OKP
+ (default false).
+ ecRxns : logical
+ for which model.ec.rxns kcats are predicted (default: all). Only
+ used when a job is submitted.
+ save : logical
+ write the source file (default true).
+ saveAs : char
+ name of the source file, without .tsv (default 'okp_<method>', in
+ lower case, with other characters than letters and digits as '_').
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ kcatList : struct
+ the kcat values, as returned by readOpenKineticsPredictorOutput;
+ empty while the job is not done.
+ done : logical
+ whether the result was available.
+
+ Examples
+ --------
+ kcatsFromOKP(ecModel, 'method', 'CataPro'); % submits
+ kcatsFromOKP(ecModel, 'method', 'CataPro'); % later: fetches and writes
+ kcatsFromOKP(ecModel, 'method', 'UniKP', 'wait', true);
+
+ See also
+ --------
+ submitOpenKineticsPredictor, fetchOpenKineticsPredictor, kcatsFromBrenda, saveKcatSource
0001 function [kcatList, done] = kcatsFromOKP(model, varargin)
+0002 % kcatsFromOKP Gather kcats from OpenKineticsPredictor and keep them as a source file.
+0003 %
+0004 % Predicts kcats with one OpenKineticsPredictor (OKP) method and writes
+0005 % them to data/kcats/okp_<method>.tsv in the project folder (see
+0006 % saveKcatSource). OKP is a web service with a job queue, so this function
+0007 % can be called repeatedly:
+0008 %
+0009 % - without a job for this method, it submits one
+0010 % (submitOpenKineticsPredictor);
+0011 % - with a job, it fetches the result (fetchOpenKineticsPredictor). When
+0012 % the job is done the file is written; otherwise it reports the job's
+0013 % status and writes nothing.
+0014 %
+0015 % Each method keeps its own job in data/OKP_job_<method>.txt and its own
+0016 % result in data/OKP_output_<method>.csv, so jobs of several methods can
+0017 % run at the same time. The job file records which job the stored result
+0018 % came from; useStored only uses a result of the current job. An API key
+0019 % is needed (see resolveOkpApiKey).
+0020 %
+0021 % Parameters
+0022 % ----------
+0023 % model : struct
+0024 % an ecModel in GECKO 3 format (with ecModel.ec structure), with
+0025 % metabolite SMILES (findMetSmiles) and protein sequences.
+0026 %
+0027 % Name-Value Arguments
+0028 % --------------------
+0029 % method : char
+0030 % OKP method, e.g. 'CataPro', 'CatPred', 'CatRange', 'DLKcat',
+0031 % 'EITLEM', 'KinForm-H', 'KinForm-L', 'OmniESI', 'TurNup', 'UniKP'
+0032 % (default: params.okp.method of the model adapter, or 'CataPro').
+0033 % wait : logical
+0034 % wait until the job is done (default false).
+0035 % resubmit : logical
+0036 % submit a new job even if one exists for this method; its earlier
+0037 % result is removed (default false).
+0038 % useStored : logical
+0039 % read the stored result of this method's job, without contacting OKP
+0040 % (default false).
+0041 % ecRxns : logical
+0042 % for which model.ec.rxns kcats are predicted (default: all). Only
+0043 % used when a job is submitted.
+0044 % save : logical
+0045 % write the source file (default true).
+0046 % saveAs : char
+0047 % name of the source file, without .tsv (default 'okp_<method>', in
+0048 % lower case, with other characters than letters and digits as '_').
+0049 % modelAdapter : ModelAdapter
+0050 % a loaded model adapter (default: the current default model adapter).
+0051 %
+0052 % Returns
+0053 % -------
+0054 % kcatList : struct
+0055 % the kcat values, as returned by readOpenKineticsPredictorOutput;
+0056 % empty while the job is not done.
+0057 % done : logical
+0058 % whether the result was available.
+0059 %
+0060 % Examples
+0061 % --------
+0062 % kcatsFromOKP(ecModel, 'method', 'CataPro'); % submits
+0063 % kcatsFromOKP(ecModel, 'method', 'CataPro'); % later: fetches and writes
+0064 % kcatsFromOKP(ecModel, 'method', 'UniKP', 'wait', true);
+0065 %
+0066 % See also
+0067 % --------
+0068 % submitOpenKineticsPredictor, fetchOpenKineticsPredictor, kcatsFromBrenda, saveKcatSource
+0069
+0070 checkOptions(varargin, {'method','wait','resubmit','useStored','ecRxns','save','saveAs','modelAdapter'}, 'kcatsFromOKP');
+0071 p = parseGECKOargs(varargin, { ...
+0072 'method', []; ...
+0073 'wait', false; ...
+0074 'resubmit', false; ...
+0075 'useStored', false; ...
+0076 'ecRxns', []; ...
+0077 'save', true; ...
+0078 'saveAs', []; ...
+0079 'modelAdapter', []});
+0080 modelAdapter = p.modelAdapter;
+0081 if isempty(modelAdapter)
+0082 modelAdapter = ModelAdapterManager.getDefault();
+0083 if isempty(modelAdapter)
+0084 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0085 end
+0086 end
+0087 params = modelAdapter.params;
+0088 method = char(p.method);
+0089 if isempty(method)
+0090 if isfield(params, 'okp') && isfield(params.okp, 'method') && ~isempty(params.okp.method)
+0091 method = char(params.okp.method);
+0092 else
+0093 method = 'CataPro';
+0094 end
+0095 end
+0096 if any(strcmp(method, {'CatPred', 'TurNup'}))
+0097 error('kcatsFromOKP:method', ['%s reads whole reactions (its substrate set, and products '...
+0098 'for TurNup); kcatsFromOKP submits enzyme-substrate pairs only.'], method)
+0099 end
+0100 tag = lower(regexprep(method, '[^A-Za-z0-9]+', '_'));
+0101 dataDir = fullfile(params.path, 'data');
+0102 jobFile = fullfile(dataDir, ['OKP_job_' tag '.txt']);
+0103 outFile = fullfile(dataDir, ['OKP_output_' tag '.csv']);
+0104 name = char(p.saveAs);
+0105 if isempty(name)
+0106 name = ['okp_' tag];
+0107 end
+0108 job = readJobFile(jobFile);
+0109 submitting = ~p.useStored && (p.resubmit || ~isfield(job, 'jobId'));
+0110 if ~isempty(p.ecRxns) && ~submitting
+0111 error('kcatsFromOKP:ecRxns', ['ecRxns selects the reactions of a new job, but this call '...
+0112 'submits none (a job for %s exists; use ''resubmit'', true for a new one).'], method)
+0113 end
+0114 if p.save
+0115 file = fullfile(modelAdapter.params.path, 'data', 'kcats', [char(name) '.tsv']);
+0116 if submitting
+0117 warnSubset(p.ecRxns, numel(model.ec.rxns), file);
+0118 end
+0119 end
+0120
+0121 if p.useStored
+0122 if ~isfield(job, 'jobId')
+0123 adoptSharedResult(model, modelAdapter, dataDir, method, jobFile, outFile);
+0124 job = readJobFile(jobFile);
+0125 end
+0126 if ~isfile(outFile) || ~isfield(job, 'resultOf') || ~strcmp(job.resultOf, job.jobId)
+0127 error('kcatsFromOKP:noStored', ['No stored result of the current %s job (%s). Call '...
+0128 'kcatsFromOKP without ''useStored'' to fetch it.'], method, jobFile)
+0129 end
+0130 kcatList = readOpenKineticsPredictorOutput(model, 'outFile', outFile, 'modelAdapter', modelAdapter);
+0131 done = true;
+0132 else
+0133 if submitting
+0134 if isfile(outFile)
+0135 delete(outFile);
+0136 end
+0137 jobId = submitOpenKineticsPredictor(model, 'method', method, 'ecRxns', p.ecRxns, ...
+0138 'overwrite', true, 'modelAdapter', modelAdapter);
+0139 job = struct('jobId', jobId, 'method', method, 'submittedAt', ...
+0140 char(datetime('now', 'TimeZone', 'UTC', 'Format', 'yyyy-MM-dd''T''HH:mm:ss''Z''')));
+0141 if ~isempty(p.ecRxns)
+0142 job.ecRxns = sprintf('%d', countRxns(p.ecRxns));
+0143 end
+0144 writeJobFile(jobFile, job);
+0145 end
+0146 [done, kcatList] = fetchOpenKineticsPredictor(model, 'jobId', job.jobId, 'wait', p.wait, ...
+0147 'outFile', outFile, 'modelAdapter', modelAdapter);
+0148 if done
+0149 job.resultOf = job.jobId;
+0150 writeJobFile(jobFile, job);
+0151 end
+0152 end
+0153
+0154 if ~done
+0155 fprintf('Call kcatsFromOKP(model, ''method'', ''%s'') again later to collect the result.\n', method);
+0156 return
+0157 end
+0158 if p.save
+0159 parameters = struct('method', method);
+0160 if isfield(job, 'ecRxns')
+0161 parameters.ec_rxns = str2double(job.ecRxns);
+0162 end
+0163 if isfield(kcatList, 'similarity')
+0164 kcatList.detail = repmat({''}, numel(kcatList.rxns), 1);
+0165 has = ~isnan(kcatList.similarity);
+0166 kcatList.detail(has) = cellstr("similarity=" + string(kcatList.similarity(has)));
+0167 end
+0168 saveKcatSource(model, kcatList, name, 'modelAdapter', modelAdapter, ...
+0169 'generator', 'kcatsFromOKP', 'parameters', parameters, 'data', ['OKP job ' job.jobId]);
+0170 fprintf('OKP (%s) kcats for %d reactions written to %s\n', method, ...
+0171 numel(unique(kcatList.rxns(kcatList.kcats > 0))), file);
+0172 end
+0173 end
+0174
+0175 function adoptSharedResult(model, modelAdapter, dataDir, method, jobFile, outFile)
+0176 % A result that fetchOpenKineticsPredictor stored by itself, in
+0177 % data/OKP_output.csv with data/OKP_job.txt, is taken over for this method
+0178 % only when its job is for this method and its predictions are labelled
+0179 % with this method.
+0180 shared = fullfile(dataDir, 'OKP_output.csv');
+0181 sharedJob = readJobFile(fullfile(dataDir, 'OKP_job.txt'));
+0182 ok = isfile(shared) && isfield(sharedJob, 'jobId') && isfield(sharedJob, 'method') && ...
+0183 strcmpi(sharedJob.method, method);
+0184 if ok
+0185 list = readOpenKineticsPredictorOutput(model, 'outFile', shared, 'modelAdapter', modelAdapter);
+0186 predicted = list.kcatSource(startsWith(list.kcatSource, 'OKP-') & ...
+0187 ~ismember(lower(list.kcatSource), {'okp-brenda','okp-sabio-rk','okp-uniprot'}));
+0188 ok = ~isempty(predicted) && all(strcmpi(predicted, ['OKP-' method]));
+0189 end
+0190 if ~ok
+0191 error('kcatsFromOKP:noStored', ['No stored OKP result for %s (%s). Call kcatsFromOKP '...
+0192 'without ''useStored'' to submit or fetch a job.'], method, outFile)
+0193 end
+0194 copyfile(shared, outFile);
+0195 sharedJob.method = method;
+0196 sharedJob.resultOf = sharedJob.jobId;
+0197 writeJobFile(jobFile, sharedJob);
+0198 end
+0199
+0200 function job = readJobFile(file)
+0201 % Fields of an OKP job file ('key: value' per line), as a struct.
+0202 job = struct();
+0203 if ~isfile(file)
+0204 return
+0205 end
+0206 lines = strsplit(fileread(file), newline);
+0207 for i = 1:numel(lines)
+0208 tok = regexp(lines{i}, '^([A-Za-z]\w*):\s*(.*?)\s*$', 'tokens', 'once');
+0209 if ~isempty(tok)
+0210 job.(tok{1}) = tok{2};
+0211 end
+0212 end
+0213 end
+0214
+0215 function writeJobFile(file, job)
+0216 fid = fopen(file, 'w');
+0217 for f = {'jobId','method','submittedAt','ecRxns','resultOf'}
+0218 if isfield(job, f{1})
+0219 fprintf(fid, '%s: %s\n', f{1}, job.(f{1}));
+0220 end
+0221 end
+0222 fclose(fid);
+0223 end
+0224
+0225 function n = countRxns(ecRxns)
+0226 if islogical(ecRxns)
+0227 n = sum(ecRxns);
+0228 else
+0229 n = numel(unique(ecRxns));
+0230 end
+0231 end
+0232
+0233 function warnSubset(ecRxns, nRxns, file)
+0234 % A subset written under the default name replaces the full file.
+0235 if ~isempty(ecRxns) && countRxns(ecRxns) < nRxns && isfile(file)
+0236 printOrange(sprintf(['%s is replaced by kcats for %d reactions only. Give saveAs '...
+0237 'to keep it.\n'], file, countRxns(ecRxns)));
+0238 end
+0239 end
+0240
+0241 function checkOptions(args, names, functionName)
+0242 % Options are name-value pairs with known names, so a misspelled option is
+0243 % an error instead of being taken as a positional argument.
+0244 id = [functionName ':arguments'];
+0245 given = args(1:2:end);
+0246 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0247 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0248 end
+0249 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0250 if ~isempty(unknown)
+0251 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0252 strjoin(names, ', '))
+0253 end
+0254 end
loadBrendaMeasurements Load the BRENDA measurement table.
+
+
SYNOPSIS
+
function measurements = loadBrendaMeasurements(varargin)
+
+
DESCRIPTION
+
loadBrendaMeasurements Load the BRENDA measurement table.
+
+ Reads measurements.tsv from the BRENDA database folder of the model
+ adapter (next to kcat.tsv, sa.tsv and mw.tsv): one row per wild-type
+ BRENDA kcat with an EC number, after the outlier screen, with the
+ substrate, organism, UniProt accession and sequence id of each
+ measurement. geckopy reads the same file (load_brenda_measurements).
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ folder : char
+ folder with measurements.tsv, instead of the adapter's BRENDA folder.
+
+ Returns
+ -------
+ measurements : struct
+ one field per column of the file (ec, substrate, substrate_inchikey,
+ organism, taxid, uniprot, seq_id, temperature, ph and pmid as cell
+ arrays of char; kcat as double), plus log10kcat and release (the
+ release line of the file, e.g. 'BRENDA release 2026.1').
+
+ Examples
+ --------
+ measurements = loadBrendaMeasurements();
+
+ See also
+ --------
+ kcatsFromECTree, ecTreeEstimate, loadBRENDAdata
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
kcatsFromECTree kcatsFromECTree Estimate kcats from the EC tree and keep them as a source file.
kcatsFromHomologs kcatsFromHomologs Estimate kcats from measured homologs and keep them as a source file.
0001 function measurements = loadBrendaMeasurements(varargin)
+0002 % loadBrendaMeasurements Load the BRENDA measurement table.
+0003 %
+0004 % Reads measurements.tsv from the BRENDA database folder of the model
+0005 % adapter (next to kcat.tsv, sa.tsv and mw.tsv): one row per wild-type
+0006 % BRENDA kcat with an EC number, after the outlier screen, with the
+0007 % substrate, organism, UniProt accession and sequence id of each
+0008 % measurement. geckopy reads the same file (load_brenda_measurements).
+0009 %
+0010 % Name-Value Arguments
+0011 % --------------------
+0012 % modelAdapter : ModelAdapter
+0013 % a loaded model adapter (default: the current default model adapter).
+0014 % folder : char
+0015 % folder with measurements.tsv, instead of the adapter's BRENDA folder.
+0016 %
+0017 % Returns
+0018 % -------
+0019 % measurements : struct
+0020 % one field per column of the file (ec, substrate, substrate_inchikey,
+0021 % organism, taxid, uniprot, seq_id, temperature, ph and pmid as cell
+0022 % arrays of char; kcat as double), plus log10kcat and release (the
+0023 % release line of the file, e.g. 'BRENDA release 2026.1').
+0024 %
+0025 % Examples
+0026 % --------
+0027 % measurements = loadBrendaMeasurements();
+0028 %
+0029 % See also
+0030 % --------
+0031 % kcatsFromECTree, ecTreeEstimate, loadBRENDAdata
+0032
+0033 checkOptions(varargin, {'modelAdapter','folder'}, 'loadBrendaMeasurements');
+0034 p = parseGECKOargs(varargin, {'modelAdapter', []; 'folder', []});
+0035 folder = p.folder;
+0036 if isempty(folder)
+0037 modelAdapter = p.modelAdapter;
+0038 if isempty(modelAdapter)
+0039 modelAdapter = ModelAdapterManager.getDefault();
+0040 if isempty(modelAdapter)
+0041 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0042 end
+0043 end
+0044 folder = modelAdapter.getBrendaDBFolder();
+0045 end
+0046 file = fullfile(folder, 'measurements.tsv');
+0047 if ~isfile(file)
+0048 error('loadBrendaMeasurements:missing', ['No BRENDA measurement table (measurements.tsv) in %s. '...
+0049 'It comes with the BRENDA files of GECKO (databases folder).'], folder)
+0050 end
+0051 fid = fopen(file, 'r', 'n', 'UTF-8');
+0052 first = fgetl(fid);
+0053 header = strsplit(fgetl(fid), '\t');
+0054 data = textscan(fid, repmat('%s', 1, numel(header)), 'Delimiter', '\t', 'Whitespace', '', ...
+0055 'ReturnOnError', false);
+0056 fclose(fid);
+0057 if ~all(ismember({'ec', 'kcat'}, header))
+0058 error('loadBrendaMeasurements:columns', '%s lacks the columns ec and kcat.', file)
+0059 end
+0060 measurements = struct();
+0061 for c = 1:numel(header)
+0062 measurements.(matlab.lang.makeValidName(header{c})) = data{c};
+0063 end
+0064 measurements.kcat = str2double(measurements.kcat);
+0065 keep = measurements.kcat > 0 & ~cellfun(@isempty, measurements.ec);
+0066 for f = fieldnames(measurements)'
+0067 measurements.(f{1}) = measurements.(f{1})(keep);
+0068 end
+0069 measurements.ec = strtrim(measurements.ec);
+0070 measurements.log10kcat = log10(measurements.kcat);
+0071 tok = regexp(first, '^#\s*(BRENDA release [^ ]+)', 'tokens', 'once');
+0072 measurements.release = '';
+0073 if ~isempty(tok)
+0074 measurements.release = tok{1};
+0075 end
+0076 end
+0077
+0078 function checkOptions(args, names, functionName)
+0079 % Options are name-value pairs with known names, so a misspelled option is
+0080 % an error instead of being taken as a positional argument.
+0081 id = [functionName ':arguments'];
+0082 given = args(1:2:end);
+0083 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0084 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0085 end
+0086 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0087 if ~isempty(unknown)
+0088 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0089 strjoin(names, ', '))
+0090 end
+0091 end
loadKcatSource Read a kcat source file and map its rows to an ecModel.
+
+
SYNOPSIS
+
function [kcatList, report] = loadKcatSource(model, name, varargin)
+
+
DESCRIPTION
+
loadKcatSource Read a kcat source file and map its rows to an ecModel.
+
+ Reads data/kcats/<name>.tsv (see saveKcatSource) and maps every row to
+ the model.ec reactions it applies to, by conventional reaction and
+ enzyme set (see getEcRxnKeys):
+
+ 1. A row with rxn and enzymes applies to the model.ec rows with that
+ conventional reaction and that enzyme set.
+ 2. A row with only enzymes applies to every model.ec row with that
+ enzyme set; a row with only rxn to every model.ec row of that
+ conventional reaction (curated rows; saveKcatSource always writes
+ both).
+ 3. A row whose kcat is missing, not a number, infinite, or not above 0
+ is reported as invalid and not applied.
+ 4. A row that matches nothing is reported, never dropped silently. If a
+ model.ec row has at least half of the row's enzymes, that partial
+ match is reported as well, but not applied.
+
+ The enzymes of a row may be listed in any order.
+
+ The file is read as UTF-8 (a leading byte-order mark is ignored). The
+ '#' lines before the column header are the header; '#' lines and blank
+ lines further down are skipped. Fields wrapped in double quotes, as a
+ spreadsheet may save them, are unwrapped. kcat and log10_sd must be plain
+ numbers (12, 0.5, 1e-05); other text, such as '5,5' with a decimal comma,
+ is reported as invalid. A row with more filled fields than there are
+ columns is an error.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ name : char
+ name of the source (data/kcats/<name>.tsv in the project folder), or
+ the path to a kcat source file.
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ kcatList : struct
+ kcat values per model.ec reaction, as used by mergeKcats and
+ assignKcatValues: source (the source name), rxns, kcats,
+ substrates, eccodes, wildcardLvl and origin (from the detail
+ column), kcatSource (the per-row source), log10sd, detail (the detail
+ column), and line (the line of the file each entry comes from).
+ report : struct
+ - invalid: table of the file rows with a missing or invalid kcat;
+ - unmatched: table of the file rows that apply to no model.ec row;
+ - partial: table of rows that only partly match an enzyme set, with
+ the model.ec reaction and its enzymes;
+ - header: the '#' header lines of the file, as field: value (for
+ instance header.source, header.parameters);
+ - records: the rows of the file as read, one field per column (kcat
+ and log10_sd as double, other columns as cell of char) and line,
+ the line number of each row.
+
+ Examples
+ --------
+ kcatList = loadKcatSource(ecModel, 'brenda');
+ [kcatList, report] = loadKcatSource(ecModel, 'custom');
+
+ See also
+ --------
+ saveKcatSource, kcatsFromBrenda, getEcRxnKeys
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
getEcRxnKeys getEcRxnKeys Build-independent keys of the model.ec reactions.
+This function is called by:
+
+
collectKcats collectKcats Decide one kcat per reaction from the kcat source files.
0001 function [kcatList, report] = loadKcatSource(model, name, varargin)
+0002 % loadKcatSource Read a kcat source file and map its rows to an ecModel.
+0003 %
+0004 % Reads data/kcats/<name>.tsv (see saveKcatSource) and maps every row to
+0005 % the model.ec reactions it applies to, by conventional reaction and
+0006 % enzyme set (see getEcRxnKeys):
+0007 %
+0008 % 1. A row with rxn and enzymes applies to the model.ec rows with that
+0009 % conventional reaction and that enzyme set.
+0010 % 2. A row with only enzymes applies to every model.ec row with that
+0011 % enzyme set; a row with only rxn to every model.ec row of that
+0012 % conventional reaction (curated rows; saveKcatSource always writes
+0013 % both).
+0014 % 3. A row whose kcat is missing, not a number, infinite, or not above 0
+0015 % is reported as invalid and not applied.
+0016 % 4. A row that matches nothing is reported, never dropped silently. If a
+0017 % model.ec row has at least half of the row's enzymes, that partial
+0018 % match is reported as well, but not applied.
+0019 %
+0020 % The enzymes of a row may be listed in any order.
+0021 %
+0022 % The file is read as UTF-8 (a leading byte-order mark is ignored). The
+0023 % '#' lines before the column header are the header; '#' lines and blank
+0024 % lines further down are skipped. Fields wrapped in double quotes, as a
+0025 % spreadsheet may save them, are unwrapped. kcat and log10_sd must be plain
+0026 % numbers (12, 0.5, 1e-05); other text, such as '5,5' with a decimal comma,
+0027 % is reported as invalid. A row with more filled fields than there are
+0028 % columns is an error.
+0029 %
+0030 % Parameters
+0031 % ----------
+0032 % model : struct
+0033 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0034 % name : char
+0035 % name of the source (data/kcats/<name>.tsv in the project folder), or
+0036 % the path to a kcat source file.
+0037 %
+0038 % Name-Value Arguments
+0039 % --------------------
+0040 % modelAdapter : ModelAdapter
+0041 % a loaded model adapter (default: the current default model adapter).
+0042 %
+0043 % Returns
+0044 % -------
+0045 % kcatList : struct
+0046 % kcat values per model.ec reaction, as used by mergeKcats and
+0047 % assignKcatValues: source (the source name), rxns, kcats,
+0048 % substrates, eccodes, wildcardLvl and origin (from the detail
+0049 % column), kcatSource (the per-row source), log10sd, detail (the detail
+0050 % column), and line (the line of the file each entry comes from).
+0051 % report : struct
+0052 % - invalid: table of the file rows with a missing or invalid kcat;
+0053 % - unmatched: table of the file rows that apply to no model.ec row;
+0054 % - partial: table of rows that only partly match an enzyme set, with
+0055 % the model.ec reaction and its enzymes;
+0056 % - header: the '#' header lines of the file, as field: value (for
+0057 % instance header.source, header.parameters);
+0058 % - records: the rows of the file as read, one field per column (kcat
+0059 % and log10_sd as double, other columns as cell of char) and line,
+0060 % the line number of each row.
+0061 %
+0062 % Examples
+0063 % --------
+0064 % kcatList = loadKcatSource(ecModel, 'brenda');
+0065 % [kcatList, report] = loadKcatSource(ecModel, 'custom');
+0066 %
+0067 % See also
+0068 % --------
+0069 % saveKcatSource, kcatsFromBrenda, getEcRxnKeys
+0070
+0071 checkOptions(varargin, {'modelAdapter'}, 'loadKcatSource');
+0072 p = parseGECKOargs(varargin, {'modelAdapter', []});
+0073
+0074 if isfile(name)
+0075 filename = name;
+0076 else
+0077 modelAdapter = p.modelAdapter;
+0078 if isempty(modelAdapter)
+0079 modelAdapter = ModelAdapterManager.getDefault();
+0080 if isempty(modelAdapter)
+0081 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0082 end
+0083 end
+0084 filename = fullfile(modelAdapter.params.path, 'data', 'kcats', [name '.tsv']);
+0085 if ~isfile(filename)
+0086 error('loadKcatSource:missing', ['No kcat source file %s. Make it with the '...
+0087 'kcatsFrom<Source> function of this source (kcatsFromBrenda, kcatsFromDLKcat, '...
+0088 'kcatsFromOKP), or with saveKcatSource.'], filename)
+0089 end
+0090 end
+0091 [records, meta] = readSourceFile(filename);
+0092 sourceName = '';
+0093 if isfield(meta, 'source')
+0094 sourceName = meta.source;
+0095 end
+0096
+0097 [baseRxns, enzymeSets] = getEcRxnKeys(model);
+0098 nEc = numel(baseRxns);
+0099 nRec = numel(records.kcat);
+0100
+0101 % Lookup tables: key -> model.ec rows.
+0102 byPair = groupIndex(strcat(baseRxns, '|', enzymeSets));
+0103 byBase = groupIndex(baseRxns);
+0104 byEnz = groupIndex(enzymeSets);
+0105 % accession -> model.ec rows containing it, for partial matches.
+0106 accRows = cell(nEc, 1);
+0107 for i = 1:nEc
+0108 if ~isempty(enzymeSets{i})
+0109 accRows{i} = strsplit(enzymeSets{i}, ' + ');
+0110 end
+0111 end
+0112 accIdx = repelem((1:nEc)', cellfun(@numel, accRows));
+0113 byAcc = groupIndex([accRows{:}]', accIdx);
+0114
+0115 invalid = ~(records.kcat > 0) | ~isfinite(records.kcat);
+0116 matchRec = cell(nRec, 1);
+0117 matchEc = cell(nRec, 1);
+0118 unmatched = false(nRec, 1);
+0119 partialRec = zeros(0, 1); partialEc = zeros(0, 1);
+0120 for r = find(~invalid)'
+0121 rxn = records.rxn{r};
+0122 enz = normalizeEnzymes(records.enzymes{r});
+0123 if ~isempty(rxn) && ~isempty(enz)
+0124 hit = lookup(byPair, [rxn '|' enz]);
+0125 elseif ~isempty(enz)
+0126 hit = lookup(byEnz, enz);
+0127 elseif ~isempty(rxn)
+0128 hit = lookup(byBase, rxn);
+0129 else
+0130 hit = [];
+0131 end
+0132 if isempty(hit)
+0133 unmatched(r) = true;
+0134 if ~isempty(enz)
+0135 wanted = strsplit(enz, ' + ');
+0136 candidates = unique(cell2mat(cellfun(@(a) lookup(byAcc, a), wanted(:), 'UniformOutput', false)));
+0137 if ~isempty(rxn)
+0138 candidates = intersect(candidates, lookup(byBase, rxn));
+0139 end
+0140 for c = candidates(:)'
+0141 if mean(ismember(wanted, accRows{c})) >= 0.5
+0142 partialRec(end+1, 1) = r; %#ok<AGROW>
+0143 partialEc(end+1, 1) = c; %#ok<AGROW>
+0144 end
+0145 end
+0146 end
+0147 else
+0148 matchRec{r} = repmat(r, numel(hit), 1);
+0149 matchEc{r} = hit(:);
+0150 end
+0151 end
+0152 rec = vertcat(matchRec{:}, zeros(0, 1));
+0153 ec = vertcat(matchEc{:}, zeros(0, 1));
+0154
+0155 kcatList.source = sourceName;
+0156 kcatList.rxns = reshape(model.ec.rxns(ec), [], 1);
+0157 kcatList.kcats = records.kcat(rec);
+0158 kcatList.substrates = records.substrate(rec);
+0159 kcatList.eccodes = records.ec_code(rec);
+0160 kcatList.kcatSource = records.source(rec);
+0161 kcatList.log10sd = records.log10_sd(rec);
+0162 kcatList.detail = records.detail(rec);
+0163 [kcatList.wildcardLvl, kcatList.origin] = parseDetail(records.detail(rec));
+0164 kcatList.line = records.line(rec);
+0165
+0166 report.invalid = table(records.line(invalid), records.rxn(invalid), records.enzymes(invalid), ...
+0167 records.kcat(invalid), 'VariableNames', {'line','rxn','enzymes','kcat'});
+0168 report.unmatched = table(records.line(unmatched), records.rxn(unmatched), records.enzymes(unmatched), ...
+0169 records.kcat(unmatched), 'VariableNames', {'line','rxn','enzymes','kcat'});
+0170 report.partial = table(records.line(partialRec), records.rxn(partialRec), records.enzymes(partialRec), ...
+0171 reshape(model.ec.rxns(partialEc), [], 1), enzymeSets(partialEc), ...
+0172 'VariableNames', {'line','rxn','enzymes','ecRxn','ecEnzymes'});
+0173 if any(invalid)
+0174 printOrange(sprintf('%d of %d rows of %s have no valid kcat; see report.invalid.\n', ...
+0175 sum(invalid), nRec, filename));
+0176 end
+0177 if any(unmatched)
+0178 printOrange(sprintf('%d of %d rows of %s apply to no reaction of this model; see report.unmatched.\n', ...
+0179 sum(unmatched), nRec, filename));
+0180 end
+0181 report.header = meta;
+0182 report.records = records;
+0183 end
+0184
+0185 function map = groupIndex(keys, rows)
+0186 % containers.Map from each key to the rows (default 1:n) that have it.
+0187 if nargin < 2
+0188 rows = (1:numel(keys))';
+0189 end
+0190 map = containers.Map('KeyType', 'char', 'ValueType', 'any');
+0191 if isempty(keys)
+0192 return
+0193 end
+0194 [u, ~, g] = unique(keys(:));
+0195 groups = accumarray(g, rows(:), [], @(x) {sort(x)});
+0196 for i = 1:numel(u)
+0197 if ~isempty(u{i})
+0198 map(u{i}) = groups{i};
+0199 end
+0200 end
+0201 end
+0202
+0203 function rows = lookup(map, key)
+0204 if isKey(map, key)
+0205 rows = map(key);
+0206 else
+0207 rows = zeros(0, 1);
+0208 end
+0209 end
+0210
+0211 function enz = normalizeEnzymes(enz)
+0212 % Sorted and joined as getEcRxnKeys does, so hand-written rows match too.
+0213 if isempty(enz)
+0214 return
+0215 end
+0216 parts = strtrim(strsplit(enz, '+'));
+0217 parts = unique(parts(~cellfun(@isempty, parts)));
+0218 enz = strjoin(sort(parts(:)'), ' + ');
+0219 end
+0220
+0221 function [wc, origin] = parseDetail(detail)
+0222 wc = nan(numel(detail), 1);
+0223 origin = nan(numel(detail), 1);
+0224 for i = 1:numel(detail)
+0225 t = regexp(detail{i}, 'wc=(\d+)', 'tokens', 'once');
+0226 if ~isempty(t)
+0227 wc(i) = str2double(t{1});
+0228 end
+0229 t = regexp(detail{i}, 'origin=(\d+)', 'tokens', 'once');
+0230 if ~isempty(t)
+0231 origin(i) = str2double(t{1});
+0232 end
+0233 end
+0234 end
+0235
+0236 function [records, meta] = readSourceFile(filename)
+0237 % The rows and '#' header lines of a kcat source file, as they are.
+0238 text = readUtf8(filename);
+0239 lines = regexp(text, '\r?\n', 'split');
+0240
+0241 meta = struct();
+0242 k = 1;
+0243 while k <= numel(lines) && (isempty(strtrim(lines{k})) || startsWith(unquoteField(lines{k}), '#'))
+0244 [key, value] = parseHeaderLine(lines{k});
+0245 if ~isempty(key)
+0246 meta.(key) = value;
+0247 end
+0248 k = k + 1;
+0249 end
+0250 if k > numel(lines)
+0251 error('loadKcatSource:noHeader', '%s has no column header.', filename)
+0252 end
+0253 columns = cellfun(@unquoteField, strsplit(lines{k}, sprintf('\t'), ...
+0254 'CollapseDelimiters', false), 'UniformOutput', false);
+0255 if ~all(ismember({'kcat','source'}, columns)) || ~any(ismember({'rxn','enzymes'}, columns))
+0256 error('loadKcatSource:columns', ['%s needs the columns kcat and source, and rxn '...
+0257 'and/or enzymes.'], filename)
+0258 end
+0259 named = columns(~cellfun(@isempty, columns));
+0260 if numel(unique(named)) < numel(named) || any(strcmp(columns, 'line'))
+0261 error('loadKcatSource:columns', '%s has a column name twice, or a column named line.', filename)
+0262 end
+0263 nCol = numel(columns);
+0264
+0265 trimmed = strtrim(lines);
+0266 skip = cellfun(@isempty, trimmed) | startsWith(trimmed, '#') | startsWith(trimmed, '"#');
+0267 skip(1:k) = true;
+0268 kept = find(~skip);
+0269 L = string(lines(kept));
+0270 L = L(:);
+0271 F = strings(numel(kept), nCol);
+0272 nTabs = count(L, sprintf('\t'));
+0273 regular = nTabs == nCol - 1;
+0274 if any(regular)
+0275 % Rows with exactly one field per column, split in one go.
+0276 F(regular, :) = reshape(split(L(regular), sprintf('\t'), 2), [], nCol);
+0277 end
+0278 for r = find(~regular)'
+0279 fields = strsplit(char(L(r)), sprintf('\t'), 'CollapseDelimiters', false);
+0280 if numel(fields) > nCol
+0281 if any(~cellfun(@isempty, strtrim(fields(nCol+1:end))))
+0282 error('loadKcatSource:fields', '%s, line %d has more fields than the %d columns.', ...
+0283 filename, kept(r), nCol)
+0284 end
+0285 fields = fields(1:nCol);
+0286 end
+0287 fields(end+1:nCol) = {''};
+0288 F(r, :) = string(fields);
+0289 end
+0290 F = strtrim(F);
+0291 quoted = find(startsWith(F, '"'));
+0292 for q = quoted(:)'
+0293 F(q) = unquoteField(char(F(q)));
+0294 end
+0295 filled = any(F ~= "", 2); % a row of tabs only is skipped
+0296 values = cellstr(F(filled, :));
+0297 if isempty(values)
+0298 values = cell(0, nCol);
+0299 end
+0300 lineNo = kept(filled);
+0301 lineNo = lineNo(:);
+0302 n = numel(lineNo);
+0303
+0304 records = struct();
+0305 for c = 1:nCol
+0306 if isempty(columns{c})
+0307 continue
+0308 end
+0309 name = matlab.lang.makeValidName(columns{c});
+0310 v = values(:, c);
+0311 if any(strcmp(columns{c}, {'kcat','log10_sd'}))
+0312 v = parseKcatNumber(v);
+0313 end
+0314 records.(name) = v;
+0315 end
+0316 for c = {'rxn','enzymes','substrate','source','detail','ec_code','note'}
+0317 if ~isfield(records, c{1})
+0318 records.(c{1}) = repmat({''}, n, 1);
+0319 end
+0320 end
+0321 if ~isfield(records, 'log10_sd')
+0322 records.log10_sd = nan(n, 1);
+0323 end
+0324 records.line = lineNo;
+0325 end
+0326
+0327 function text = readUtf8(filename)
+0328 fid = fopen(filename, 'r', 'n', 'UTF-8');
+0329 if fid < 0
+0330 error('loadKcatSource:read', 'Cannot read %s', filename)
+0331 end
+0332 cleanup = onCleanup(@() fclose(fid));
+0333 text = fread(fid, [1 Inf], '*char');
+0334 text = regexprep(text, ['^' char(65279)], '');
+0335 end
+0336
+0337 function x = parseKcatNumber(texts)
+0338 % Plain numbers only: '5,5' or '12 /s' read as NaN.
+0339 x = nan(numel(texts), 1);
+0340 ok = ~cellfun(@isempty, regexp(texts(:), '^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$|^[-+]?Inf$', 'once'));
+0341 x(ok) = str2double(texts(ok));
+0342 end
+0343
+0344 function [key, value] = parseHeaderLine(line)
+0345 key = ''; value = '';
+0346 tok = regexp(unquoteField(line), '^\s*#\s*([A-Za-z][A-Za-z0-9_]{0,62})\s*:\s?(.*)$', 'tokens', 'once');
+0347 if ~isempty(tok)
+0348 key = lower(tok{1});
+0349 value = strtrim(tok{2});
+0350 end
+0351 end
+0352
+0353 function t = unquoteField(t)
+0354 t = strtrim(t);
+0355 if numel(t) >= 2 && t(1) == '"' && t(end) == '"'
+0356 t = strtrim(strrep(t(2:end-1), '""', '"'));
+0357 end
+0358 end
+0359
+0360 function checkOptions(args, names, functionName)
+0361 % Options are name-value pairs with known names, so a misspelled option is
+0362 % an error instead of being taken as a positional argument.
+0363 id = [functionName ':arguments'];
+0364 given = args(1:2:end);
+0365 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0366 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0367 end
+0368 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0369 if ~isempty(unknown)
+0370 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0371 strjoin(names, ', '))
+0372 end
+0373 end
mergeDLKcatAndFuzzyKcats Merge DLKcat and BRENDA fuzzy matching results.
+
+
SYNOPSIS
+
function mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy, varargin)
+
+
DESCRIPTION
+
mergeDLKcatAndFuzzyKcats Merge DLKcat and BRENDA fuzzy matching results.
+
+ Deprecated thin wrapper around mergeKcats, kept for the common DLKcat +
+ fuzzy-BRENDA case. New code should call mergeKcats directly, which also
+ handles a single list that mixes several sources (e.g. an
+ OpenKineticsPredictor result with BRENDA/Sabio-RK/CataPro rows).
+
+ Merges the results from DLKcat and fuzzy matching to the BRENDA database.
+ Order of preference:
+
+ 1. BRENDA match with correct E.C. number, with origin (see below) not
+ lower than the specified topOriginLimit.
+ 2. DLKcat match.
+ 3. BRENDA match with correct E.C. number, with origin below topOriginLimit
+ but not lower than the bottomOriginLimit.
+ 4. BRENDA match with wildcards in the E.C. number, with not more wildcards
+ than wildcardLimit, and origin not lower than the bottomOriginLimit.
+
+ Parameters
+ ----------
+ kcatListDLKcat : struct
+ kcatList derived from readDLKcatOutput.
+ kcatListFuzzy : struct
+ kcatList derived from fuzzyKcatMatching.
+
+ Name-Value Arguments
+ --------------------
+ topOriginLimit : double
+ origin limit for prioritized BRENDA matches. Origin is explained in
+ more detail below (default 4: BRENDA matches of origin 5 and 6, from
+ specific activities, rank after DLKcat; use 6 to rank them above).
+ bottomOriginLimit : double
+ origin limit for low priority BRENDA matches. Origin is explained in
+ more detail below (default 6).
+ wildcardLimit : double
+ maximum number of wildcards in E.C. number of BRENDA matches
+ (default 3).
+
+ Returns
+ -------
+ mergedKcatList : struct
+ merged kcatList with the same field layout produced by mergeKcats
+ (source, kcatSource, rxns, genes, substrates, kcats, eccodes,
+ wildcardLvl, origin), keeping only the rows selected by the order of
+ preference above.
+
+ Notes
+ -----
+ The origin parameter:
+
+ - 1 : correct organism, correct substrate, kcat.
+ - 2 : any organism, correct substrate, kcat.
+ - 3 : correct organism, any substrate, kcat.
+ - 4 : any organism, any substrate, kcat.
+ - 5 : correct organism, specific activity.
+ - 6 : any organism, specific activity.
+
+ Example of wildcards in E.C. number:
+
+ - 0 : 1.1.1.3 glycerol-3-phosphate dehydrogenase (NAD+).
+ - 1 : 1.1.1.- oxidoreductase, acting on the CH-OH group of donors, with
+ NAD+ or NADP+ as acceptor.
+ - 2 : 1.1.-.- oxidoreductase, acting on the CH-OH group of donors.
+ - 3 : 1.-.-.- oxidoreductase.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy);
+ mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy, 'wildcardLimit', 2);
+
+ See also
+ --------
+ mergeKcats, readDLKcatOutput, fuzzyKcatMatching, assignKcatValues
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
mergeKcats mergeKcats Merge any number of kcatLists, keeping the highest-priority
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy, varargin)
+0002 % mergeDLKcatAndFuzzyKcats Merge DLKcat and BRENDA fuzzy matching results.
+0003 %
+0004 % Deprecated thin wrapper around mergeKcats, kept for the common DLKcat +
+0005 % fuzzy-BRENDA case. New code should call mergeKcats directly, which also
+0006 % handles a single list that mixes several sources (e.g. an
+0007 % OpenKineticsPredictor result with BRENDA/Sabio-RK/CataPro rows).
+0008 %
+0009 % Merges the results from DLKcat and fuzzy matching to the BRENDA database.
+0010 % Order of preference:
+0011 %
+0012 % 1. BRENDA match with correct E.C. number, with origin (see below) not
+0013 % lower than the specified topOriginLimit.
+0014 % 2. DLKcat match.
+0015 % 3. BRENDA match with correct E.C. number, with origin below topOriginLimit
+0016 % but not lower than the bottomOriginLimit.
+0017 % 4. BRENDA match with wildcards in the E.C. number, with not more wildcards
+0018 % than wildcardLimit, and origin not lower than the bottomOriginLimit.
+0019 %
+0020 % Parameters
+0021 % ----------
+0022 % kcatListDLKcat : struct
+0023 % kcatList derived from readDLKcatOutput.
+0024 % kcatListFuzzy : struct
+0025 % kcatList derived from fuzzyKcatMatching.
+0026 %
+0027 % Name-Value Arguments
+0028 % --------------------
+0029 % topOriginLimit : double
+0030 % origin limit for prioritized BRENDA matches. Origin is explained in
+0031 % more detail below (default 4: BRENDA matches of origin 5 and 6, from
+0032 % specific activities, rank after DLKcat; use 6 to rank them above).
+0033 % bottomOriginLimit : double
+0034 % origin limit for low priority BRENDA matches. Origin is explained in
+0035 % more detail below (default 6).
+0036 % wildcardLimit : double
+0037 % maximum number of wildcards in E.C. number of BRENDA matches
+0038 % (default 3).
+0039 %
+0040 % Returns
+0041 % -------
+0042 % mergedKcatList : struct
+0043 % merged kcatList with the same field layout produced by mergeKcats
+0044 % (source, kcatSource, rxns, genes, substrates, kcats, eccodes,
+0045 % wildcardLvl, origin), keeping only the rows selected by the order of
+0046 % preference above.
+0047 %
+0048 % Notes
+0049 % -----
+0050 % The origin parameter:
+0051 %
+0052 % - 1 : correct organism, correct substrate, kcat.
+0053 % - 2 : any organism, correct substrate, kcat.
+0054 % - 3 : correct organism, any substrate, kcat.
+0055 % - 4 : any organism, any substrate, kcat.
+0056 % - 5 : correct organism, specific activity.
+0057 % - 6 : any organism, specific activity.
+0058 %
+0059 % Example of wildcards in E.C. number:
+0060 %
+0061 % - 0 : 1.1.1.3 glycerol-3-phosphate dehydrogenase (NAD+).
+0062 % - 1 : 1.1.1.- oxidoreductase, acting on the CH-OH group of donors, with
+0063 % NAD+ or NADP+ as acceptor.
+0064 % - 2 : 1.1.-.- oxidoreductase, acting on the CH-OH group of donors.
+0065 % - 3 : 1.-.-.- oxidoreductase.
+0066 %
+0067 % Examples
+0068 % --------
+0069 % % optional arguments may be given positionally or as name-value pairs:
+0070 % mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy);
+0071 % mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy, 'wildcardLimit', 2);
+0072 %
+0073 % See also
+0074 % --------
+0075 % mergeKcats, readDLKcatOutput, fuzzyKcatMatching, assignKcatValues
+0076
+0077 p = parseGECKOargs(varargin, { ...
+0078 'topOriginLimit', 4; ...
+0079 'bottomOriginLimit', 6; ...
+0080 'wildcardLimit', 3});
+0081 topOriginLimit = p.topOriginLimit;
+0082 bottomOriginLimit = p.bottomOriginLimit;
+0083 wildcardLimit = p.wildcardLimit;
+0084
+0085 warning(['mergeDLKcatAndFuzzyKcats is deprecated; use mergeKcats instead. '...
+0086 'The old name will be removed in a future release.']);
+0087
+0088 % Fuzzy list first, then DLKcat, so surviving rows keep the legacy order
+0089 % (fuzzy block followed by DLKcat block).
+0090 mergedKcatList = mergeKcats({kcatListFuzzy, kcatListDLKcat}, ...
+0091 {'database_top', 'dlkcat', 'database_bottom'}, ...
+0092 'topOriginLimit', topOriginLimit, ...
+0093 'bottomOriginLimit', bottomOriginLimit, ...
+0094 'wildcardLimit', wildcardLimit);
+0095 end
mergeKcats Merge any number of kcatLists, keeping the highest-priority
+
+
SYNOPSIS
+
function mergedKcatList = mergeKcats(kcatLists, sourcePriority, varargin)
+
+
DESCRIPTION
+
mergeKcats Merge any number of kcatLists, keeping the highest-priority
+ source for each reaction.
+
+ Generalizes mergeDLKcatAndFuzzyKcats: instead of two fixed inputs (DLKcat
+ + fuzzy BRENDA) it accepts a cell array of kcatLists, each of which may
+ itself mix several sources (for example the output of
+ fetchOpenKineticsPredictor, carrying BRENDA, Sabio-RK and CataPro values
+ in its kcatSource field). A caller-supplied sourcePriority decides which
+ source wins for each reaction.
+
+ Parameters
+ ----------
+ kcatLists : cell
+ cell array of kcatList structs (as produced by fuzzyKcatMatching,
+ readDLKcatOutput, fetchOpenKineticsPredictor, ...).
+ sourcePriority : cell
+ cell array of tier tokens / source labels, best first. Three
+ reserved tier tokens may be used instead of a literal source label:
+
+ - 'database_exact' an exact experimental-database measurement
+ (a BRENDA/Sabio-RK row with no fuzzy metadata, i.e. a direct OKP
+ hit, no wildcards).
+ - 'database_top' a fuzzy BRENDA match with wildcardLvl==0 and
+ origin<=topOriginLimit.
+ - 'database_bottom' weaker fuzzy BRENDA matches within
+ wildcardLimit / bottomOriginLimit.
+
+ Any other token is matched against each row's source after folding
+ to lowercase snake_case (e.g. 'dlkcat', 'catapro'). Sources not
+ listed are dropped.
+
+ Name-Value Arguments
+ --------------------
+ topOriginLimit : double
+ origin limit for database_top (default 4). Origins 5 and 6 are specific
+ activity times molecular weight kcats, which do not always beat a
+ prediction; they rank after the other sources and are used where
+ nothing else exists, or above DLKcat with topOriginLimit 6.
+ bottomOriginLimit : double
+ origin limit for database_bottom (default 6).
+ wildcardLimit : double
+ maximum wildcards for database_bottom's wildcard branch (default 3).
+ databaseSources : cell
+ source labels treated as experimental databases, i.e. routed to the
+ database_* tiers (default {'brenda','sabio_rk'}).
+
+ Returns
+ -------
+ mergedKcatList : struct
+ merged kcatList, keeping only the winning rows per reaction; see
+ Notes below for its fields. Row order follows the order of the input
+ lists (each list's surviving rows, in turn), so a two-list call
+ behaves like the legacy mergeDLKcatAndFuzzyKcats.
+
+ Notes
+ -----
+ The mergedKcatList structure has the following fields:
+
+ - source : 'merged'.
+ - kcatSource : per-row provenance, preserved verbatim from the input
+ lists.
+ - rxns : reaction identifiers.
+ - genes : gene identifiers (where provided by the input lists).
+ - substrates : substrate names (where provided by the input lists).
+ - kcats : selected kcat value in /sec.
+ - eccodes : as used to query BRENDA (where applicable).
+ - wildcardLvl : wildcard level of the source match (where applicable).
+ - origin : specificity level of the source match (where applicable).
+
+ The origin parameter:
+
+ - 1 : correct organism, correct substrate, kcat.
+ - 2 : any organism, correct substrate, kcat.
+ - 3 : correct organism, any substrate, kcat.
+ - 4 : any organism, any substrate, kcat.
+ - 5 : correct organism, specific activity.
+ - 6 : any organism, specific activity.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ mergedKcatList = mergeKcats({kcatListFuzzy, kcatListDLKcat}, ...
+ {'database_top', 'dlkcat', 'database_bottom'});
+ mergedKcatList = mergeKcats({kcatListFuzzy, kcatListOKP}, ...
+ {'database_exact', 'database_top', 'database_bottom'}, 'wildcardLimit', 2);
+
+ See also
+ --------
+ mergeDLKcatAndFuzzyKcats, readDLKcatOutput, fuzzyKcatMatching, assignKcatValues
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
collectKcats collectKcats Decide one kcat per reaction from the kcat source files.
mergeDLKcatAndFuzzyKcats mergeDLKcatAndFuzzyKcats Merge DLKcat and BRENDA fuzzy matching results.
readDLKcatOutput Read the DLKcat output file into a kcatList structure.
+
+
SYNOPSIS
+
function kcatList = readDLKcatOutput(model, varargin)
+
+
DESCRIPTION
+
readDLKcatOutput Read the DLKcat output file into a kcatList structure.
+
+ Reads the DLKcat output file and constructs a kcatList structure, that can
+ be used by assignKcatValues() to populate the ecModel with kcat values.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ outFile : char
+ name and path of the DLKcat output file (default: data/DLKcat.tsv from
+ the obj.params.path folder specified in the modelAdapter).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ kcatList : struct
+ structure array with list of DLKcat derived kcat values, with separate
+ entries for each kcat value.
+
+ Notes
+ -----
+ The kcatList structure has the following fields:
+
+ - source : 'DLKcat'.
+ - rxns : reaction identifiers.
+ - genes : gene identifiers.
+ - substrates : substrate names.
+ - kcats : predicted kcat value in /sec.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ kcatList = readDLKcatOutput(model);
+ kcatList = readDLKcatOutput(model, 'outFile', 'myDLKcat.tsv');
+
+ See also
+ --------
+ runDLKcat, writeDLKcatInput, assignKcatValues
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
kcatsFromDLKcat kcatsFromDLKcat Gather kcats from DLKcat and keep them as a source file.
+
+
+
+
+
SOURCE CODE
+
0001 function kcatList = readDLKcatOutput(model, varargin)
+0002 % readDLKcatOutput Read the DLKcat output file into a kcatList structure.
+0003 %
+0004 % Reads the DLKcat output file and constructs a kcatList structure, that can
+0005 % be used by assignKcatValues() to populate the ecModel with kcat values.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % model : struct
+0010 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0011 %
+0012 % Name-Value Arguments
+0013 % --------------------
+0014 % outFile : char
+0015 % name and path of the DLKcat output file (default: data/DLKcat.tsv from
+0016 % the obj.params.path folder specified in the modelAdapter).
+0017 % modelAdapter : ModelAdapter
+0018 % a loaded model adapter (default: the current default model adapter).
+0019 %
+0020 % Returns
+0021 % -------
+0022 % kcatList : struct
+0023 % structure array with list of DLKcat derived kcat values, with separate
+0024 % entries for each kcat value.
+0025 %
+0026 % Notes
+0027 % -----
+0028 % The kcatList structure has the following fields:
+0029 %
+0030 % - source : 'DLKcat'.
+0031 % - rxns : reaction identifiers.
+0032 % - genes : gene identifiers.
+0033 % - substrates : substrate names.
+0034 % - kcats : predicted kcat value in /sec.
+0035 %
+0036 % Examples
+0037 % --------
+0038 % % optional arguments may be given positionally or as name-value pairs:
+0039 % kcatList = readDLKcatOutput(model);
+0040 % kcatList = readDLKcatOutput(model, 'outFile', 'myDLKcat.tsv');
+0041 %
+0042 % See also
+0043 % --------
+0044 % runDLKcat, writeDLKcatInput, assignKcatValues
+0045
+0046 p = parseGECKOargs(varargin, { ...
+0047 'outFile', []; ...
+0048 'modelAdapter', []});
+0049 outFile = p.outFile;
+0050 modelAdapter = p.modelAdapter;
+0051
+0052 if isempty(modelAdapter)
+0053 modelAdapter = ModelAdapterManager.getDefault();
+0054 if isempty(modelAdapter)
+0055 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0056 end
+0057 end
+0058 params = modelAdapter.params;
+0059
+0060 if isempty(outFile)
+0061 fID = fopen(fullfile(params.path,'data','DLKcat.tsv'),'r');
+0062 else
+0063 fID = fopen(outFile);
+0064 end
+0065 DLKcatOutput = textscan(fID,'%s %s %s %s %s %s','Delimiter','\t','HeaderLines',1);
+0066 fclose(fID);
+0067
+0068 % Check that DLKcat output file and model match (not fool proof, but good enough)
+0069 [rxns, genes, subs, kcats] = deal(DLKcatOutput{[1,2,3,6]});
+0070
+0071 % Check if it contains any kcat values
+0072 if all(cellfun(@isempty,kcats)) || all(strcmpi(kcats,'NA'))
+0073 error('DLKcat file does not contain any kcat values, please run runDLKcat() first.')
+0074 end
+0075
+0076 % Check that all substrates are in the model (case-insensitive: an SBML loader can
+0077 % normalise metabolite-name capitalisation differently than whatever produced the
+0078 % DLKcat input, and that must not read as a genuinely missing substrate).
+0079 matchMets = ismember(lower(subs),lower(model.metNames));
+0080 if ~all(matchMets)
+0081 errorText = 'DLKcat was likely run with an input file that was generated from another ecModel, as the following substrates from DLKcat output cannot be found in model.metNames:';
+0082 error('%s', ravenList(errorText,subs(~matchMets),false))
+0083 end
+0084
+0085 % Check that all reactions are in model.ec.rxns
+0086 matchRxns = ismember(rxns,model.ec.rxns);
+0087 if ~all(matchRxns)
+0088 errorText = 'DLKcat was likely run with an input file that was generated from another ecModel, as the following reactions from DLKcat output cannot be found in model.metNames:';
+0089 error('%s', ravenList(errorText,rxns(~matchRxns),false))
+0090 end
+0091
+0092 % Filter out entries with no numeric value
+0093 noOutput = cellfun(@isempty,regexpi(kcats,'[0-9]'));
+0094 kcats = str2double(kcats(~noOutput));
+0095 rxns(noOutput) = [];
+0096 genes(noOutput) = [];
+0097 subs(noOutput) = [];
+0098
+0099 % Make kcatList structure
+0100 kcatList.source = 'DLKcat';
+0101 kcatList.rxns = rxns;
+0102 kcatList.genes = genes;
+0103 kcatList.substrates = subs;
+0104 kcatList.kcats = kcats;
+0105 end
readOpenKineticsPredictorOutput Read OpenKineticsPredictor output to kcatList.
+
+
SYNOPSIS
+
function kcatList = readOpenKineticsPredictorOutput(model, varargin)
+
+
DESCRIPTION
+
readOpenKineticsPredictorOutput Read OpenKineticsPredictor output to kcatList.
+
+ Reads the OpenKineticsPredictor output file (job-*-output.csv with columns:
+ kcat (1/s), Source kcat, Extra Info kcat, Protein Sequence, Substrate) and
+ constructs a kcatList structure, that can be used by assignKcatValues() to
+ populate the ecModel with kcat values.
+
+ CatRange predicts a class of kcat values, which OKP returns as its range
+ (for instance '10 to 100 s^-1'). It is read as the geometric midpoint of
+ the range, or half the upper bound for the class starting at 0.
+
+ The per-entry `Source kcat` column (e.g. 'Prediction from CatPred',
+ 'BRENDA', 'Sabio-RK', 'UniProt') is preserved in kcatList.kcatSource so
+ that assignKcatValues() records the actual provenance of each value.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ outFile : char
+ name and path of the OpenKineticsPredictor output CSV. If omitted, a
+ file selection dialog is opened, starting in the data/ folder specified
+ by the modelAdapter.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ kcatList : struct
+ structure array with list of kcat values from OpenKineticsPredictor,
+ with separate entries for each kcat value.
+
+ Notes
+ -----
+ The kcatList structure has the following fields:
+
+ - source : 'OpenKineticsPredictor'.
+ - rxns : reaction identifiers, matching model.ec.rxns.
+ - genes : gene identifiers, matching model.ec.genes.
+ - substrates : substrate names, matching model.metNames.
+ - kcats : kcat values in /sec.
+ - similarity : the enzyme's highest sequence similarity to the method's
+ training data, in percent (NaN without the similarity column)
+ - kcatSource : per-entry source as reported by OKP, prefixed with 'OKP-'
+ (e.g. 'OKP-CatPred', 'OKP-BRENDA', 'OKP-Sabio-RK', 'OKP-UniProt').
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ kcatList = readOpenKineticsPredictorOutput(model);
+ kcatList = readOpenKineticsPredictorOutput(model, 'outFile', 'job-output.csv');
+
+ See also
+ --------
+ writeOpenKineticsPredictorInput, assignKcatValues
0001 function kcatList = readOpenKineticsPredictorOutput(model, varargin)
+0002 % readOpenKineticsPredictorOutput Read OpenKineticsPredictor output to kcatList.
+0003 %
+0004 % Reads the OpenKineticsPredictor output file (job-*-output.csv with columns:
+0005 % kcat (1/s), Source kcat, Extra Info kcat, Protein Sequence, Substrate) and
+0006 % constructs a kcatList structure, that can be used by assignKcatValues() to
+0007 % populate the ecModel with kcat values.
+0008 %
+0009 % CatRange predicts a class of kcat values, which OKP returns as its range
+0010 % (for instance '10 to 100 s^-1'). It is read as the geometric midpoint of
+0011 % the range, or half the upper bound for the class starting at 0.
+0012 %
+0013 % The per-entry `Source kcat` column (e.g. 'Prediction from CatPred',
+0014 % 'BRENDA', 'Sabio-RK', 'UniProt') is preserved in kcatList.kcatSource so
+0015 % that assignKcatValues() records the actual provenance of each value.
+0016 %
+0017 % Parameters
+0018 % ----------
+0019 % model : struct
+0020 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0021 %
+0022 % Name-Value Arguments
+0023 % --------------------
+0024 % outFile : char
+0025 % name and path of the OpenKineticsPredictor output CSV. If omitted, a
+0026 % file selection dialog is opened, starting in the data/ folder specified
+0027 % by the modelAdapter.
+0028 % modelAdapter : ModelAdapter
+0029 % a loaded model adapter (default: the current default model adapter).
+0030 %
+0031 % Returns
+0032 % -------
+0033 % kcatList : struct
+0034 % structure array with list of kcat values from OpenKineticsPredictor,
+0035 % with separate entries for each kcat value.
+0036 %
+0037 % Notes
+0038 % -----
+0039 % The kcatList structure has the following fields:
+0040 %
+0041 % - source : 'OpenKineticsPredictor'.
+0042 % - rxns : reaction identifiers, matching model.ec.rxns.
+0043 % - genes : gene identifiers, matching model.ec.genes.
+0044 % - substrates : substrate names, matching model.metNames.
+0045 % - kcats : kcat values in /sec.
+0046 % - similarity : the enzyme's highest sequence similarity to the method's
+0047 % training data, in percent (NaN without the similarity column)
+0048 % - kcatSource : per-entry source as reported by OKP, prefixed with 'OKP-'
+0049 % (e.g. 'OKP-CatPred', 'OKP-BRENDA', 'OKP-Sabio-RK', 'OKP-UniProt').
+0050 %
+0051 % Examples
+0052 % --------
+0053 % % optional arguments may be given positionally or as name-value pairs:
+0054 % kcatList = readOpenKineticsPredictorOutput(model);
+0055 % kcatList = readOpenKineticsPredictorOutput(model, 'outFile', 'job-output.csv');
+0056 %
+0057 % See also
+0058 % --------
+0059 % writeOpenKineticsPredictorInput, assignKcatValues
+0060
+0061 p = parseGECKOargs(varargin, { ...
+0062 'outFile', []; ...
+0063 'modelAdapter', []});
+0064 outFile = p.outFile;
+0065 modelAdapter = p.modelAdapter;
+0066
+0067 if isempty(modelAdapter)
+0068 modelAdapter = ModelAdapterManager.getDefault();
+0069 if isempty(modelAdapter)
+0070 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0071 end
+0072 end
+0073 params = modelAdapter.params;
+0074
+0075 if isempty(outFile)
+0076 startDir = fullfile(params.path,'data');
+0077 if ~exist(startDir,'dir')
+0078 startDir = params.path;
+0079 end
+0080 [fName, fPath] = uigetfile({'*.csv','OpenKineticsPredictor output (*.csv)'; ...
+0081 '*.*','All files (*.*)'}, ...
+0082 'Select OpenKineticsPredictor output file', ...
+0083 fullfile(startDir, filesep));
+0084 if isequal(fName,0)
+0085 error('No file selected.')
+0086 end
+0087 outFile = fullfile(fPath, fName);
+0088 end
+0089
+0090 %% Read OKP output file using readcell to correctly handle quoted fields
+0091 % that may contain commas in the 'Extra Info kcat' column.
+0092 if ~exist(outFile,'file')
+0093 error('Cannot find file: %s', outFile)
+0094 end
+0095 raw = readcell(outFile, 'Delimiter', ',', 'TextType', 'char');
+0096
+0097 if size(raw,2) < 5
+0098 error('OKP output file does not have the expected 5 columns (kcat, Source kcat, Extra Info kcat, Protein Sequence, Substrate): %s', outFile)
+0099 end
+0100
+0101 % Drop header row; the 'max similarity to <method> training data' column,
+0102 % when present, gives the similarity.
+0103 header = cellfun(@(h) char(string(h)), raw(1,:), 'UniformOutput', false);
+0104 simCol = find(startsWith(header, 'max similarity to'), 1);
+0105 raw = raw(2:end, :);
+0106 similarity = nan(size(raw, 1), 1);
+0107 if ~isempty(simCol)
+0108 for i = 1:size(raw, 1)
+0109 v = raw{i, simCol};
+0110 if isnumeric(v) && ~isempty(v)
+0111 similarity(i) = v;
+0112 elseif ischar(v) || isstring(v)
+0113 similarity(i) = str2double(v);
+0114 end
+0115 end
+0116 end
+0117
+0118 kcatsCol = raw(:,1);
+0119 sourceKcatCol = raw(:,2);
+0120 sequences = raw(:,4);
+0121 smiles = raw(:,5);
+0122
+0123 % Normalize types: readcell returns doubles for numeric cells, char for
+0124 % strings, and `missing` for empty fields.
+0125 kcats = nan(size(kcatsCol));
+0126 for i = 1:numel(kcatsCol)
+0127 v = kcatsCol{i};
+0128 if isnumeric(v)
+0129 kcats(i) = v;
+0130 elseif ischar(v) || isstring(v)
+0131 kcats(i) = kcatFromText(char(v));
+0132 end
+0133 end
+0134
+0135 sourceKcatCol = normalizeCellStrings(sourceKcatCol);
+0136 sequences = normalizeCellStrings(sequences);
+0137 smiles = normalizeCellStrings(smiles);
+0138
+0139 %% Filter out entries with no numeric kcat value (NaN). Zero values are
+0140 % retained here and filtered downstream by assignKcatValues.
+0141 valid = ~isnan(kcats);
+0142 kcats = kcats(valid);
+0143 sequences = sequences(valid);
+0144 smiles = smiles(valid);
+0145 sourceKcatCol = sourceKcatCol(valid);
+0146 similarity = similarity(valid);
+0147
+0148 if isempty(kcats)
+0149 error('OKP file does not contain any numeric kcat values. Run the job at https://predictor.openkinetics.org/ first.')
+0150 end
+0151
+0152 if ~isfield(model,'metSmiles')
+0153 error('model.metSmiles is required to map SMILES back to metabolites.')
+0154 end
+0155
+0156 %% Build ec.rxns -> model.rxns index mapping (inverse of the mapping done
+0157 % in writeOpenKineticsPredictorInput)
+0158 if model.ec.geckoLight
+0159 origRxns = extractAfter(model.ec.rxns, 4);
+0160 else
+0161 origRxns = model.ec.rxns;
+0162 end
+0163 [foundEcRxn, ecRxnToModelRxn] = ismember(origRxns, model.rxns);
+0164 if ~all(foundEcRxn)
+0165 error('Not all entries in model.ec.rxns could be matched to model.rxns.')
+0166 end
+0167
+0168 %% Resolve each row to matching ec.rxns indices
+0169 rxnsOut = cell(0,1);
+0170 genesOut = cell(0,1);
+0171 substratesOut = cell(0,1);
+0172 kcatsOut = zeros(0,1);
+0173 sourceOut = cell(0,1);
+0174 similarityOut = zeros(0,1);
+0175 unmatched = 0;
+0176
+0177 for i = 1:numel(sequences)
+0178 % Map protein sequence -> ec.genes indices (columns of rxnEnzMat)
+0179 proteinIdxs = find(strcmp(model.ec.sequence, sequences{i}));
+0180 % Map SMILES -> metabolite indices (rows of model.S)
+0181 metIdxs = find(strcmp(model.metSmiles, smiles{i}));
+0182
+0183 if isempty(proteinIdxs) || isempty(metIdxs)
+0184 unmatched = unmatched + 1;
+0185 continue
+0186 end
+0187
+0188 % model.rxns columns where any of these mets are substrates (S < 0)
+0189 modelRxnsUsingSub = any(model.S(metIdxs, :) < 0, 1);
+0190
+0191 % Lift to ec.rxns: does the underlying model.rxn consume this metabolite?
+0192 ecRxnUsesSub = modelRxnsUsingSub(ecRxnToModelRxn).';
+0193
+0194 % ec.rxns catalyzed by any of these protein(s)
+0195 ecRxnCatalyzed = any(model.ec.rxnEnzMat(:, proteinIdxs) ~= 0, 2);
+0196
+0197 candidateEcRxns = find(ecRxnUsesSub & ecRxnCatalyzed);
+0198
+0199 if isempty(candidateEcRxns)
+0200 unmatched = unmatched + 1;
+0201 continue
+0202 end
+0203
+0204 for k = 1:numel(candidateEcRxns)
+0205 ecIdx = candidateEcRxns(k);
+0206
+0207 % Pick the gene whose protein both matches this sequence and
+0208 % catalyzes this ec.rxn
+0209 catalysts = find(model.ec.rxnEnzMat(ecIdx, :) ~= 0);
+0210 matchingProtein = intersect(proteinIdxs, catalysts, 'stable');
+0211 if ~isempty(matchingProtein)
+0212 geneName = model.ec.genes{matchingProtein(1)};
+0213 else
+0214 geneName = '';
+0215 end
+0216
+0217 % Pick a metabolite name whose SMILES matches and that is a
+0218 % substrate of the underlying model.rxn
+0219 origRxnIdx = ecRxnToModelRxn(ecIdx);
+0220 metIsSub = model.S(metIdxs, origRxnIdx) < 0;
+0221 substrateMetIdxs = metIdxs(metIsSub);
+0222 if ~isempty(substrateMetIdxs)
+0223 subName = model.metNames{substrateMetIdxs(1)};
+0224 else
+0225 subName = '';
+0226 end
+0227
+0228 rxnsOut{end+1,1} = model.ec.rxns{ecIdx};
+0229 genesOut{end+1,1} = geneName;
+0230 substratesOut{end+1,1} = subName;
+0231 kcatsOut(end+1,1) = kcats(i);
+0232 sourceOut{end+1,1} = sourceKcatCol{i};
+0233 similarityOut(end+1,1) = similarity(i);
+0234 end
+0235 end
+0236
+0237 if unmatched > 0
+0238 warning('%d of %d OKP entries could not be matched to an ec.rxn (protein sequence and/or SMILES not found in model, or no enzyme+substrate pair matches).', ...
+0239 unmatched, numel(sequences));
+0240 end
+0241
+0242 if isempty(rxnsOut)
+0243 error('No OKP entries could be mapped to model.ec.rxns.')
+0244 end
+0245
+0246 %% Build per-entry source labels
+0247 % Strip leading 'Prediction from ' to keep labels compact, then prefix all
+0248 % with 'OKP-' so the overall pipeline stage is still identifiable in
+0249 % model.ec.source.
+0250 kcatSource = regexprep(sourceOut, '^Prediction from\s+', '');
+0251 kcatSource = strcat('OKP-', kcatSource);
+0252
+0253 %% Make kcatList structure
+0254 kcatList.source = 'OpenKineticsPredictor';
+0255 kcatList.rxns = rxnsOut;
+0256 kcatList.genes = genesOut;
+0257 kcatList.substrates = substratesOut;
+0258 kcatList.kcats = kcatsOut;
+0259 kcatList.kcatSource = kcatSource;
+0260 kcatList.similarity = similarityOut;
+0261 end
+0262
+0263 function c = normalizeCellStrings(c)
+0264 % Convert each cell entry to a char row vector; missing/empty -> ''.
+0265 for i = 1:numel(c)
+0266 v = c{i};
+0267 if ismissing(v)
+0268 c{i} = '';
+0269 elseif isstring(v)
+0270 c{i} = char(v);
+0271 elseif isnumeric(v)
+0272 if isnan(v)
+0273 c{i} = '';
+0274 else
+0275 c{i} = num2str(v);
+0276 end
+0277 elseif ~ischar(v)
+0278 c{i} = char(string(v));
+0279 end
+0280 end
+0281 end
+0282
+0283 function kcat = kcatFromText(v)
+0284 % A number, or a CatRange class range '<low> to <high> s^-1' as its
+0285 % geometric midpoint (half the upper bound when the class starts at 0).
+0286 kcat = str2double(v);
+0287 if ~isnan(kcat)
+0288 return
+0289 end
+0290 bounds = regexp(strtrim(v), '^([0-9.eE+-]+)\s+to\s+([0-9.eE+-]+)(\s*s\^-1)?$', 'tokens', 'once');
+0291 if isempty(bounds)
+0292 return
+0293 end
+0294 low = str2double(bounds{1});
+0295 high = str2double(bounds{2});
+0296 if isnan(low) || isnan(high)
+0297 return
+0298 elseif low > 0
+0299 kcat = sqrt(low * high);
+0300 else
+0301 kcat = high / 2;
+0302 end
+0303 end
removeStandardKcat Remove the standard pseudoenzyme and standard kcats.
+
+
SYNOPSIS
+
function model = removeStandardKcat(model)
+
+
DESCRIPTION
+
removeStandardKcat Remove the standard pseudoenzyme and standard kcats.
+
+ Removes the "standard" pseudoenzyme and standard kcat and MW values, as
+ they were introduced by getStandardKcat. Also standard kcat values that
+ were assigned by getStandardKcat if fillZeroKcat was set to true are
+ removed from model.ec.kcat. Both the model.ec and model.S structures are
+ modified.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure) that has
+ standard kcat values implemented by getStandardKcat.
+
+ Returns
+ -------
+ model : struct
+ ecModel without standard pseudoprotein and standard kcat values.
+
+ Examples
+ --------
+ model = removeStandardKcat(model);
+
+ See also
+ --------
+ getStandardKcat, applyKcatConstraints
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function model = removeStandardKcat(model)
+0002 % removeStandardKcat Remove the standard pseudoenzyme and standard kcats.
+0003 %
+0004 % Removes the "standard" pseudoenzyme and standard kcat and MW values, as
+0005 % they were introduced by getStandardKcat. Also standard kcat values that
+0006 % were assigned by getStandardKcat if fillZeroKcat was set to true are
+0007 % removed from model.ec.kcat. Both the model.ec and model.S structures are
+0008 % modified.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % model : struct
+0013 % an ecModel in GECKO 3 format (with ecModel.ec structure) that has
+0014 % standard kcat values implemented by getStandardKcat.
+0015 %
+0016 % Returns
+0017 % -------
+0018 % model : struct
+0019 % ecModel without standard pseudoprotein and standard kcat values.
+0020 %
+0021 % Examples
+0022 % --------
+0023 % model = removeStandardKcat(model);
+0024 %
+0025 % See also
+0026 % --------
+0027 % getStandardKcat, applyKcatConstraints
+0028
+0029 % Remove standard enzyme from ec structure
+0030 stdEnzIdx = find(strcmpi(model.ec.enzymes, 'standard'));
+0031 if ~isempty(stdEnzIdx)
+0032 model.ec.genes(stdEnzIdx) = [];
+0033 model.ec.enzymes(stdEnzIdx) = [];
+0034 model.ec.mw(stdEnzIdx) = [];
+0035 model.ec.sequence(stdEnzIdx) = [];
+0036 if isfield(model.ec,'concs')
+0037 model.ec.concs(stdEnzIdx) = [];
+0038 end
+0039 rxnEnzIdx = find(model.ec.rxnEnzMat(:,stdEnzIdx));
+0040 model.ec.rxns(rxnEnzIdx) = [];
+0041 model.ec.kcat(rxnEnzIdx) = [];
+0042 model.ec.source(rxnEnzIdx) = [];
+0043 model.ec.notes(rxnEnzIdx) = [];
+0044 model.ec.eccodes(rxnEnzIdx) = [];
+0045 model.ec.rxnEnzMat(:,stdEnzIdx) = [];
+0046 model.ec.rxnEnzMat(rxnEnzIdx,:) = [];
+0047 end
+0048
+0049 % Remove standard kcat values and reapply kcat constraints for those
+0050 % specific reactions
+0051 stdKcatIdx = find(strcmpi(model.ec.source, 'standard'));
+0052 if ~isempty(stdKcatIdx)
+0053 model.ec.source(stdKcatIdx) = {''};
+0054 model.ec.kcat(stdKcatIdx) = 0;
+0055 model = applyKcatConstraints(model,stdKcatIdx);
+0056 end
+0057
+0058 % Remove standard protein, usage and gene from model structure
+0059 stdMetIdx = find(strcmpi(model.mets, 'prot_standard'));
+0060 if ~isempty(stdMetIdx)
+0061 model = removeMets(model,stdMetIdx,false,false,false,false);
+0062 end
+0063 stdProtEx = find(strcmpi(model.rxns, 'usage_prot_standard'));
+0064 if ~isempty(stdProtEx)
+0065 model = removeReactions(model,stdProtEx,false,false,false);
+0066 end
+0067 stdProtGene = find(strcmpi(model.genes, 'standard'));
+0068 if ~isempty(stdProtGene)
+0069 model = removeGenes(model,stdProtGene,false,false,false);
+0070 end
+0071 end
resolveOkpApiKey Resolve the OpenKineticsPredictor API key.
+
+
SYNOPSIS
+
function key = resolveOkpApiKey(argKey, basePath)
+
+
DESCRIPTION
+
resolveOkpApiKey Resolve the OpenKineticsPredictor API key.
+
+ Internal helper shared by submitOpenKineticsPredictor and
+ fetchOpenKineticsPredictor; not intended to be called directly.
+
+ Checked in order: the argKey input, the OKP_API_KEY environment variable,
+ then a plain-text file data/okpApiKey.txt under basePath (this name is
+ git-ignored by GECKO). The key is a secret and must never be read from the
+ model adapter, which is shared/committed.
+
+ Parameters
+ ----------
+ argKey : char
+ an explicitly provided key, or empty to fall through to the
+ environment variable / file.
+ basePath : char
+ the model adapter's params.path, under which data/okpApiKey.txt is
+ looked up.
+
+ Returns
+ -------
+ key : char
+ the resolved API key.
+
+ See also
+ --------
+ submitOpenKineticsPredictor, fetchOpenKineticsPredictor
submitOpenKineticsPredictor submitOpenKineticsPredictor Submit a kcat prediction job to OpenKineticsPredictor.
updateKcatSources updateKcatSources Gather the kcat sources that the configuration needs.
+
+
+
+
+
SOURCE CODE
+
0001 function key = resolveOkpApiKey(argKey, basePath)
+0002 % resolveOkpApiKey Resolve the OpenKineticsPredictor API key.
+0003 %
+0004 % Internal helper shared by submitOpenKineticsPredictor and
+0005 % fetchOpenKineticsPredictor; not intended to be called directly.
+0006 %
+0007 % Checked in order: the argKey input, the OKP_API_KEY environment variable,
+0008 % then a plain-text file data/okpApiKey.txt under basePath (this name is
+0009 % git-ignored by GECKO). The key is a secret and must never be read from the
+0010 % model adapter, which is shared/committed.
+0011 %
+0012 % Parameters
+0013 % ----------
+0014 % argKey : char
+0015 % an explicitly provided key, or empty to fall through to the
+0016 % environment variable / file.
+0017 % basePath : char
+0018 % the model adapter's params.path, under which data/okpApiKey.txt is
+0019 % looked up.
+0020 %
+0021 % Returns
+0022 % -------
+0023 % key : char
+0024 % the resolved API key.
+0025 %
+0026 % See also
+0027 % --------
+0028 % submitOpenKineticsPredictor, fetchOpenKineticsPredictor
+0029
+0030 if ~isempty(argKey)
+0031 key = strtrim(char(argKey));
+0032 return
+0033 end
+0034 envKey = getenv('OKP_API_KEY');
+0035 if ~isempty(envKey)
+0036 key = strtrim(envKey);
+0037 return
+0038 end
+0039 keyFile = fullfile(basePath,'data','okpApiKey.txt');
+0040 if exist(keyFile,'file')
+0041 key = strtrim(fileread(keyFile));
+0042 return
+0043 end
+0044 error(['No OpenKineticsPredictor API key found. Provide it as the apiKey '...
+0045 'argument, set the OKP_API_KEY environment variable, or place it in '...
+0046 '%s. Generate a key at https://predictor.openkinetics.org/.'], keyFile);
+0047 end
reviewKcatAssignment Flag kcat assignments that deserve a second look.
+
+
SYNOPSIS
+
function findings = reviewKcatAssignment(rxnIds,kcats,sources,ecCodes,varargin)
+
+
DESCRIPTION
+
reviewKcatAssignment Flag kcat assignments that deserve a second look.
+
+ A tuner's largest corrections are rarely discoveries about biology.
+ They are places the assignment went wrong: a value derived from one
+ specific-activity measurement, a database maximum standing in for a
+ distribution with a two-hundred-fold spread, one fallback number reused
+ across a whole family of reactions. Finding those directly is cheaper
+ than inferring them from a fit, and a corrected prior propagates to
+ every model built from the same databases, where a tuned kcat stays in
+ the model it was tuned on.
+
+ Two design choices, both from measurement rather than taste (see
+ geckopy's gather_kcats/review.py, which this ports):
+
+ Rank by leverage, and cut the report there. Tightening the checks
+ themselves does not shorten a report nearly as well, because looking
+ odd and mattering are unrelated properties. Filtering by leverage keeps
+ the list short and keeps the entries worth a curator's time.
+
+ Report a parameter once. Isozyme copies the assignment could not
+ separate share a value and a source, so they share a finding; listing
+ them separately fills the report with duplicates of one decision.
+
+ Parameters
+ ----------
+ rxnIds, kcats, sources, ecCodes : cell/double
+ ec.rxns, ec.kcat, ec.source, ec.eccodes (or the equivalent
+ subset), all the same length.
+
+ Name-Value Arguments
+ --------------------
+ ecStats : containers.Map
+ EC code -> struct(nKcat,kcatMax,kcatMedian,values,nSa), as returned
+ by ecStatsFromBrenda. Checks needing it are skipped when it is
+ absent, so this still runs without a database.
+ enzymes : cell
+ protein ids per kcat (cell of cell arrays), used to count how many
+ other reactions share an enzyme.
+ names : cell
+ reaction names, same length as rxnIds (default: not shown).
+ leverage : double
+ per-kcat effect on the distance from a one-at-a-time screen (e.g.
+ screenKcatLeverage's leverage column, expanded back to
+ ecModel.ec.rxns positions). Without it findings cannot be ranked
+ and are returned in input order.
+ slow, fast : double
+ magnitude check bounds (default 1e-2, 1e4).
+ ecMaxGap : double
+ how many times the EC median a value must exceed, while sitting
+ exactly at the EC max, to flag as ec-maximum (default 3.0).
+ repeatMin : double
+ how many distinct reactions must share one kcat value to flag as
+ repeated-value (default 5).
+ include : cell
+ names from {'standard-fallback','tied-isozymes'} to report as well
+ (default: neither -- both are deliberate choices, not mistakes, so
+ stay quiet unless asked for).
+ coverage : double
+ truncate the report to the rows carrying this share of the
+ *flagged* leverage (default: no truncation).
+ top : double
+ truncate the report to this many rows (default: no truncation).
+
+ Returns
+ -------
+ findings : table
+ one row per free parameter: rxnId, name, ecCode, source, kcat,
+ leverage, nIsozymes, otherReactions, checks (comma-joined string),
+ detail. Write to TSV with e.g.
+ writetable(findings,'findings.tsv','FileType','text','Delimiter','\t')
+
+ See also
+ --------
+ ecStatsFromBrenda, isozymeTieMap, screenKcatLeverage
0001 function findings = reviewKcatAssignment(rxnIds,kcats,sources,ecCodes,varargin)
+0002 % reviewKcatAssignment Flag kcat assignments that deserve a second look.
+0003 %
+0004 % A tuner's largest corrections are rarely discoveries about biology.
+0005 % They are places the assignment went wrong: a value derived from one
+0006 % specific-activity measurement, a database maximum standing in for a
+0007 % distribution with a two-hundred-fold spread, one fallback number reused
+0008 % across a whole family of reactions. Finding those directly is cheaper
+0009 % than inferring them from a fit, and a corrected prior propagates to
+0010 % every model built from the same databases, where a tuned kcat stays in
+0011 % the model it was tuned on.
+0012 %
+0013 % Two design choices, both from measurement rather than taste (see
+0014 % geckopy's gather_kcats/review.py, which this ports):
+0015 %
+0016 % Rank by leverage, and cut the report there. Tightening the checks
+0017 % themselves does not shorten a report nearly as well, because looking
+0018 % odd and mattering are unrelated properties. Filtering by leverage keeps
+0019 % the list short and keeps the entries worth a curator's time.
+0020 %
+0021 % Report a parameter once. Isozyme copies the assignment could not
+0022 % separate share a value and a source, so they share a finding; listing
+0023 % them separately fills the report with duplicates of one decision.
+0024 %
+0025 % Parameters
+0026 % ----------
+0027 % rxnIds, kcats, sources, ecCodes : cell/double
+0028 % ec.rxns, ec.kcat, ec.source, ec.eccodes (or the equivalent
+0029 % subset), all the same length.
+0030 %
+0031 % Name-Value Arguments
+0032 % --------------------
+0033 % ecStats : containers.Map
+0034 % EC code -> struct(nKcat,kcatMax,kcatMedian,values,nSa), as returned
+0035 % by ecStatsFromBrenda. Checks needing it are skipped when it is
+0036 % absent, so this still runs without a database.
+0037 % enzymes : cell
+0038 % protein ids per kcat (cell of cell arrays), used to count how many
+0039 % other reactions share an enzyme.
+0040 % names : cell
+0041 % reaction names, same length as rxnIds (default: not shown).
+0042 % leverage : double
+0043 % per-kcat effect on the distance from a one-at-a-time screen (e.g.
+0044 % screenKcatLeverage's leverage column, expanded back to
+0045 % ecModel.ec.rxns positions). Without it findings cannot be ranked
+0046 % and are returned in input order.
+0047 % slow, fast : double
+0048 % magnitude check bounds (default 1e-2, 1e4).
+0049 % ecMaxGap : double
+0050 % how many times the EC median a value must exceed, while sitting
+0051 % exactly at the EC max, to flag as ec-maximum (default 3.0).
+0052 % repeatMin : double
+0053 % how many distinct reactions must share one kcat value to flag as
+0054 % repeated-value (default 5).
+0055 % include : cell
+0056 % names from {'standard-fallback','tied-isozymes'} to report as well
+0057 % (default: neither -- both are deliberate choices, not mistakes, so
+0058 % stay quiet unless asked for).
+0059 % coverage : double
+0060 % truncate the report to the rows carrying this share of the
+0061 % *flagged* leverage (default: no truncation).
+0062 % top : double
+0063 % truncate the report to this many rows (default: no truncation).
+0064 %
+0065 % Returns
+0066 % -------
+0067 % findings : table
+0068 % one row per free parameter: rxnId, name, ecCode, source, kcat,
+0069 % leverage, nIsozymes, otherReactions, checks (comma-joined string),
+0070 % detail. Write to TSV with e.g.
+0071 % writetable(findings,'findings.tsv','FileType','text','Delimiter','\t')
+0072 %
+0073 % See also
+0074 % --------
+0075 % ecStatsFromBrenda, isozymeTieMap, screenKcatLeverage
+0076 p = parseGECKOargs(varargin, { ...
+0077 'ecStats', containers.Map('KeyType','char','ValueType','any'); ...
+0078 'enzymes', {}; ...
+0079 'names', {}; ...
+0080 'leverage', []; ...
+0081 'slow', 1e-2; ...
+0082 'fast', 1e4; ...
+0083 'ecMaxGap', 3.0; ...
+0084 'repeatMin', 5; ...
+0085 'include', {}; ...
+0086 'coverage', []; ...
+0087 'top', []});
+0088 ecStats = p.ecStats;
+0089 enzymes = p.enzymes;
+0090 names = p.names;
+0091 leverage = p.leverage;
+0092 slow = p.slow;
+0093 fast = p.fast;
+0094 ecMaxGap = p.ecMaxGap;
+0095 repeatMin = p.repeatMin;
+0096 include = p.include;
+0097 coverage = p.coverage;
+0098 top = p.top;
+0099
+0100 kcats = kcats(:);
+0101 n = numel(kcats);
+0102 if numel(rxnIds) ~= n || numel(sources) ~= n || numel(ecCodes) ~= n
+0103 error('rxnIds, sources and ecCodes must all have %d entries, matching kcats.', n)
+0104 end
+0105 if isempty(leverage)
+0106 lev = zeros(n,1);
+0107 else
+0108 lev = leverage(:);
+0109 end
+0110
+0111 tie = isozymeTieMap(rxnIds,kcats,'sources',sources);
+0112 reps = unique(tie);
+0113 memberLists = cell(numel(reps),1);
+0114 for r = 1:numel(reps)
+0115 memberLists{r} = find(tie == reps(r));
+0116 end
+0117
+0118 % A value reused across many distinct reactions is a fallback wearing a
+0119 % measurement's label, whatever its source says.
+0120 reuseMap = containers.Map('KeyType','char','ValueType','any');
+0121 for i = 1:n
+0122 key = reuseKey(kcats(i));
+0123 base = baseReactionLocal(rxnIds{i});
+0124 if isKey(reuseMap,key)
+0125 reuseMap(key) = union(reuseMap(key), {base});
+0126 else
+0127 reuseMap(key) = {base};
+0128 end
+0129 end
+0130
+0131 enzymeRxns = containers.Map('KeyType','char','ValueType','any');
+0132 if ~isempty(enzymes)
+0133 for i = 1:n
+0134 for e = 1:numel(enzymes{i})
+0135 prot = char(enzymes{i}{e});
+0136 base = baseReactionLocal(rxnIds{i});
+0137 if isKey(enzymeRxns,prot)
+0138 enzymeRxns(prot) = union(enzymeRxns(prot), {base});
+0139 else
+0140 enzymeRxns(prot) = {base};
+0141 end
+0142 end
+0143 end
+0144 end
+0145
+0146 rxnIdOut=[]; nameOut=[]; ecOut=[]; sourceOut=[]; kcatOut=[]; leverageOut=[];
+0147 nIsoOut=[]; otherOut=[]; checksOut=[]; detailOut=[];
+0148
+0149 for r = 1:numel(reps)
+0150 idx = memberLists{r};
+0151 i = idx(1);
+0152 ec = firstEc(ecCodes{i});
+0153 st = [];
+0154 if isKey(ecStats,ec)
+0155 st = ecStats(ec);
+0156 end
+0157 checks = {}; detail = {};
+0158
+0159 if ~isempty(st) && st.nKcat == 0
+0160 checks{end+1} = 'no-ec-evidence'; %#ok<AGROW>
+0161 if st.nSa > 0
+0162 detail{end+1} = sprintf('no kcat rows for EC %s; %d specific-activity rows instead', ec, st.nSa); %#ok<AGROW>
+0163 else
+0164 detail{end+1} = sprintf('no kcat rows for EC %s', ec); %#ok<AGROW>
+0165 end
+0166 end
+0167 key = reuseKey(kcats(i));
+0168 if isKey(reuseMap,key) && numel(reuseMap(key)) >= repeatMin
+0169 checks{end+1} = 'repeated-value'; %#ok<AGROW>
+0170 detail{end+1} = sprintf('%g 1/s reused across %d reactions', kcats(i), numel(reuseMap(key))); %#ok<AGROW>
+0171 end
+0172 if ~isempty(st) && st.nKcat > 0 && isfinite(st.kcatMedian) && st.kcatMedian > 0 ...
+0173 && abs(kcats(i)-st.kcatMax) <= 1e-9*max(kcats(i),1) && st.kcatMax/st.kcatMedian > ecMaxGap
+0174 checks{end+1} = 'ec-maximum'; %#ok<AGROW>
+0175 detail{end+1} = sprintf('took the EC maximum, %.0fx its median over %d rows', st.kcatMax/st.kcatMedian, st.nKcat); %#ok<AGROW>
+0176 end
+0177 if kcats(i) < slow || kcats(i) > fast
+0178 checks{end+1} = 'magnitude'; %#ok<AGROW>
+0179 detail{end+1} = sprintf('%g 1/s is outside %g-%g', kcats(i), slow, fast); %#ok<AGROW>
+0180 end
+0181 if strcmpi(sources{i},'custom') && ~isempty(st) && ismember(round(kcats(i),6), st.values)
+0182 checks{end+1} = 'custom-duplicate'; %#ok<AGROW>
+0183 detail{end+1} = 'custom value equals a database value for its own EC'; %#ok<AGROW>
+0184 end
+0185 if numel(idx) > 1
+0186 if ismember('tied-isozymes',include)
+0187 checks{end+1} = 'tied-isozymes'; %#ok<AGROW>
+0188 end
+0189 detail{end+1} = sprintf('%d isozyme copies share this value', numel(idx)); %#ok<AGROW>
+0190 end
+0191 if strcmpi(sources{i},'standard')
+0192 if ~ismember('standard-fallback',include)
+0193 continue
+0194 end
+0195 checks{end+1} = 'standard-fallback'; %#ok<AGROW>
+0196 detail{end+1} = 'the model''s fallback value, not a measurement'; %#ok<AGROW>
+0197 end
+0198
+0199 if isempty(checks)
+0200 continue
+0201 end
+0202 others = {};
+0203 if ~isempty(enzymes)
+0204 for e = 1:numel(enzymes{i})
+0205 prot = char(enzymes{i}{e});
+0206 if isKey(enzymeRxns,prot)
+0207 others = union(others, enzymeRxns(prot));
+0208 end
+0209 end
+0210 end
+0211 others = setdiff(others, {baseReactionLocal(rxnIds{i})});
+0212
+0213 rxnIdOut{end+1,1} = rxnIds{i}; %#ok<AGROW>
+0214 if isempty(names)
+0215 nameOut{end+1,1} = ''; %#ok<AGROW>
+0216 else
+0217 nameOut{end+1,1} = names{i}; %#ok<AGROW>
+0218 end
+0219 ecOut{end+1,1} = ec; %#ok<AGROW>
+0220 sourceOut{end+1,1} = sources{i}; %#ok<AGROW>
+0221 kcatOut(end+1,1) = kcats(i); %#ok<AGROW>
+0222 leverageOut(end+1,1) = max(lev(idx)); %#ok<AGROW>
+0223 nIsoOut(end+1,1) = numel(idx); %#ok<AGROW>
+0224 otherOut(end+1,1) = numel(others); %#ok<AGROW>
+0225 checksOut{end+1,1} = strjoin(checks,','); %#ok<AGROW>
+0226 detailOut{end+1,1} = strjoin(detail,'; '); %#ok<AGROW>
+0227 end
+0228
+0229 if isempty(rxnIdOut)
+0230 findings = table(cell(0,1),cell(0,1),cell(0,1),cell(0,1),zeros(0,1),zeros(0,1), ...
+0231 zeros(0,1),zeros(0,1),cell(0,1),cell(0,1), 'VariableNames', ...
+0232 {'rxnId','name','ecCode','source','kcat','leverage','nIsozymes','otherReactions','checks','detail'});
+0233 return
+0234 end
+0235
+0236 findings = table(rxnIdOut,nameOut,ecOut,sourceOut,kcatOut,leverageOut,nIsoOut,otherOut,checksOut,detailOut, ...
+0237 'VariableNames',{'rxnId','name','ecCode','source','kcat','leverage','nIsozymes','otherReactions','checks','detail'});
+0238 findings = sortrows(findings,'leverage','descend');
+0239
+0240 flaggedTotal = sum(findings.leverage);
+0241 if ~isempty(coverage) && flaggedTotal > 0
+0242 acc = cumsum(findings.leverage);
+0243 keepN = find(acc/flaggedTotal >= coverage, 1, 'first');
+0244 if isempty(keepN)
+0245 keepN = height(findings);
+0246 end
+0247 findings = findings(1:keepN,:);
+0248 end
+0249 if ~isempty(top)
+0250 findings = findings(1:min(top,height(findings)),:);
+0251 end
+0252 end
+0253
+0254 function key = reuseKey(kcat)
+0255 if kcat > 0
+0256 key = sprintf('%.9f', round(log(kcat),9));
+0257 else
+0258 key = 'NONE';
+0259 end
+0260 end
+0261
+0262 function ec = firstEc(ecCode)
+0263 parts = strsplit(char(ecCode), ';');
+0264 ec = strtrim(parts{1});
+0265 end
+0266
+0267 function base = baseReactionLocal(rxnId)
+0268 % Same one-line split as isozymeTieMap's local baseReaction. Kept as a
+0269 % local duplicate rather than exported from isozymeTieMap.m, since
+0270 % MATLAB only exposes a file's first function to other files and this is
+0271 % its only caller here.
+0272 parts = strsplit(rxnId, '_EXP_');
+0273 base = parts{1};
+0274 end
runDLKcat Run DLKcat to predict kcat values from a Docker image.
+
+
SYNOPSIS
+
function runDLKcat(varargin)
+
+
DESCRIPTION
+
runDLKcat Run DLKcat to predict kcat values from a Docker image.
+
+ Runs DLKcat via a Docker image; once it completes successfully, the
+ DLKcat file is overwritten with the predicted output, in the model-
+ specific 'data' sub-folder taken from modelAdapter (e.g.
+ GECKO/tutorials/tutorial_yeast-GEM/data/DLKcat.tsv).
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ filePath : char
+ path to the DLKcat.tsv file (default: data/DLKcat.tsv).
+
+ Notes
+ -----
+ 1. Requires Docker to be installed, and Docker Desktop running. Visit
+ "https://www.docker.com".
+ 2. Runtime will depend on whether the image is to be downloaded or not.
+
+ See also
+ --------
+ writeDLKcatInput, readDLKcatOutput
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
kcatsFromDLKcat kcatsFromDLKcat Gather kcats from DLKcat and keep them as a source file.
+
+
+
+
+
SOURCE CODE
+
0001 function runDLKcat(varargin)
+0002 % runDLKcat Run DLKcat to predict kcat values from a Docker image.
+0003 %
+0004 % Runs DLKcat via a Docker image; once it completes successfully, the
+0005 % DLKcat file is overwritten with the predicted output, in the model-
+0006 % specific 'data' sub-folder taken from modelAdapter (e.g.
+0007 % GECKO/tutorials/tutorial_yeast-GEM/data/DLKcat.tsv).
+0008 %
+0009 % Name-Value Arguments
+0010 % --------------------
+0011 % modelAdapter : ModelAdapter
+0012 % a loaded model adapter (default: the current default model adapter).
+0013 % filePath : char
+0014 % path to the DLKcat.tsv file (default: data/DLKcat.tsv).
+0015 %
+0016 % Notes
+0017 % -----
+0018 % 1. Requires Docker to be installed, and Docker Desktop running. Visit
+0019 % "https://www.docker.com".
+0020 % 2. Runtime will depend on whether the image is to be downloaded or not.
+0021 %
+0022 % See also
+0023 % --------
+0024 % writeDLKcatInput, readDLKcatOutput
+0025
+0026 p = parseGECKOargs(varargin, { ...
+0027 'modelAdapter', []; ...
+0028 'filePath', []});
+0029 modelAdapter = p.modelAdapter;
+0030 filePath = p.filePath;
+0031
+0032 if isempty(modelAdapter)
+0033 modelAdapter = ModelAdapterManager.getDefault();
+0034 if isempty(modelAdapter)
+0035 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0036 end
+0037 end
+0038 params = modelAdapter.params;
+0039 % Make sure path is full, not relative
+0040 [~, params.path] = fileattrib(params.path);
+0041 params.path=params.path.Name;
+0042
+0043 if isempty(filePath)
+0044 filePath = fullfile(params.path,'data','DLKcat.tsv');
+0045 elseif strcmp(filePath(end),{'\','/'})
+0046 filePath = fullfile(filePath,'DLKcat.tsv');
+0047 end
+0048 filePath = checkFileExistence(filePath,1);
+0049
+0050 copyfile(filePath, fullfile(params.path,'data','tempDLKcat.tsv'));
+0051
+0052
+0053 %% Check and install requirements
+0054 % On macOS, Docker might not be properly loaded if MATLAB is started via
+0055 % launcher and not terminal.
+0056 if ismac
+0057 setenv('PATH', strcat('/usr/local/bin', ':', getenv("PATH")));
+0058 end
+0059
+0060 % Check if Docker is installed
+0061 [checks.docker.status, checks.docker.out] = system('docker --version');
+0062 if checks.docker.status ~= 0
+0063 error('Cannot find Docker, make sure it is installed. If it is, it might be required to start Matlab from the command-line instead of the launcher in order for Docker to be detected and used.')
+0064 end
+0065
+0066 disp('Running DLKcat prediction, this may take many minutes, especially the first time.')
+0067 status = system(['docker run --rm -v "' fullfile(params.path,'/data') '":/data ghcr.io/sysbiochalmers/dlkcat-gecko:0.1 /bin/bash -c "python DLKcat.py /data/tempDLKcat.tsv /data/tempDLKcatOutput.tsv"']);
+0068 delete(fullfile(params.path,'/data/tempDLKcat.tsv'));
+0069
+0070 if status == 0 && exist(fullfile(params.path,'data/tempDLKcatOutput.tsv'))
+0071 movefile(fullfile(params.path,'/data/tempDLKcatOutput.tsv'), filePath);
+0072 disp('DKLcat prediction completed.');
+0073 else
+0074 error('DLKcat encountered an error or it did not create any output file.')
+0075 end
+0076
saveKcatSource Write a kcatList to a kcat source file of the project.
+
+
SYNOPSIS
+
function filename = saveKcatSource(model, kcatList, name, varargin)
+
+
DESCRIPTION
+
saveKcatSource Write a kcatList to a kcat source file of the project.
+
+ Writes the kcat values of kcatList to data/kcats/<name>.tsv in the
+ project folder, the file that loadKcatSource reads back. Rows are keyed
+ by their conventional reaction and enzyme set (see getEcRxnKeys), not
+ by model.ec.rxns, so the file stays valid when the ecModel is rebuilt.
+ kcatsFromBrenda, kcatsFromDLKcat and kcatsFromOKP write these files for
+ their sources; use this function for a kcatList made in another way.
+
+ The file is UTF-8 text. It starts with '#' header lines (source,
+ generator, parameters, data, kind, created), then a tab-separated table
+ with the columns rxn, enzymes, substrate, kcat, log10_sd, source,
+ detail, ec_code and note, sorted by all columns from rxn onwards.
+ Left out are:
+ - rows whose kcat is not a positive finite number (0 means no value
+ was found);
+ - rows of model.ec reactions without enzymes, which have no enzyme
+ constraint and could not be told apart from their isozymes.
+
+ Parameters
+ ----------
+ model : struct
+ the ecModel the kcatList refers to.
+ kcatList : struct
+ kcat values as returned by kcatsFromBrenda, fuzzyKcatMatching,
+ readDLKcatOutput, readOpenKineticsPredictorOutput or mergeKcats:
+ fields rxns and kcats, optionally source, substrates, eccodes,
+ wildcardLvl, origin, kcatSource, log10sd and detail (further
+ key=value pairs for the detail column).
+ name : char
+ name of the source; the file is <name>.tsv.
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ folder : char
+ folder to write to (default: data/kcats in the project folder).
+ generator : char
+ what produced the values (default: 'saveKcatSource').
+ parameters : struct
+ settings used to produce the values, written as compact JSON with
+ sorted keys (default: none).
+ data : char
+ version of the data the values come from, e.g. a BRENDA release
+ (default: empty).
+ kind : char
+ 'gathered' (default) or 'curated'.
+
+ A file whose header says 'kind: curated' or 'source: custom', or named
+ custom.tsv, is never written.
+
+ Returns
+ -------
+ filename : char
+ the file written.
+
+ Examples
+ --------
+ kcatList = fuzzyKcatMatching(ecModel);
+ saveKcatSource(ecModel, kcatList, 'mybrenda');
+
+ See also
+ --------
+ kcatsFromBrenda, loadKcatSource, getEcRxnKeys
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
getEcRxnKeys getEcRxnKeys Build-independent keys of the model.ec reactions.
+This function is called by:
+
+
kcatsFromBrenda kcatsFromBrenda Gather kcats from BRENDA and keep them as a source file.
kcatsFromDLKcat kcatsFromDLKcat Gather kcats from DLKcat and keep them as a source file.
kcatsFromECTree kcatsFromECTree Estimate kcats from the EC tree and keep them as a source file.
kcatsFromHomologs kcatsFromHomologs Estimate kcats from measured homologs and keep them as a source file.
kcatsFromOKP kcatsFromOKP Gather kcats from OpenKineticsPredictor and keep them as a source file.
0001 function filename = saveKcatSource(model, kcatList, name, varargin)
+0002 % saveKcatSource Write a kcatList to a kcat source file of the project.
+0003 %
+0004 % Writes the kcat values of kcatList to data/kcats/<name>.tsv in the
+0005 % project folder, the file that loadKcatSource reads back. Rows are keyed
+0006 % by their conventional reaction and enzyme set (see getEcRxnKeys), not
+0007 % by model.ec.rxns, so the file stays valid when the ecModel is rebuilt.
+0008 % kcatsFromBrenda, kcatsFromDLKcat and kcatsFromOKP write these files for
+0009 % their sources; use this function for a kcatList made in another way.
+0010 %
+0011 % The file is UTF-8 text. It starts with '#' header lines (source,
+0012 % generator, parameters, data, kind, created), then a tab-separated table
+0013 % with the columns rxn, enzymes, substrate, kcat, log10_sd, source,
+0014 % detail, ec_code and note, sorted by all columns from rxn onwards.
+0015 % Left out are:
+0016 % - rows whose kcat is not a positive finite number (0 means no value
+0017 % was found);
+0018 % - rows of model.ec reactions without enzymes, which have no enzyme
+0019 % constraint and could not be told apart from their isozymes.
+0020 %
+0021 % Parameters
+0022 % ----------
+0023 % model : struct
+0024 % the ecModel the kcatList refers to.
+0025 % kcatList : struct
+0026 % kcat values as returned by kcatsFromBrenda, fuzzyKcatMatching,
+0027 % readDLKcatOutput, readOpenKineticsPredictorOutput or mergeKcats:
+0028 % fields rxns and kcats, optionally source, substrates, eccodes,
+0029 % wildcardLvl, origin, kcatSource, log10sd and detail (further
+0030 % key=value pairs for the detail column).
+0031 % name : char
+0032 % name of the source; the file is <name>.tsv.
+0033 %
+0034 % Name-Value Arguments
+0035 % --------------------
+0036 % modelAdapter : ModelAdapter
+0037 % a loaded model adapter (default: the current default model adapter).
+0038 % folder : char
+0039 % folder to write to (default: data/kcats in the project folder).
+0040 % generator : char
+0041 % what produced the values (default: 'saveKcatSource').
+0042 % parameters : struct
+0043 % settings used to produce the values, written as compact JSON with
+0044 % sorted keys (default: none).
+0045 % data : char
+0046 % version of the data the values come from, e.g. a BRENDA release
+0047 % (default: empty).
+0048 % kind : char
+0049 % 'gathered' (default) or 'curated'.
+0050 %
+0051 % A file whose header says 'kind: curated' or 'source: custom', or named
+0052 % custom.tsv, is never written.
+0053 %
+0054 % Returns
+0055 % -------
+0056 % filename : char
+0057 % the file written.
+0058 %
+0059 % Examples
+0060 % --------
+0061 % kcatList = fuzzyKcatMatching(ecModel);
+0062 % saveKcatSource(ecModel, kcatList, 'mybrenda');
+0063 %
+0064 % See also
+0065 % --------
+0066 % kcatsFromBrenda, loadKcatSource, getEcRxnKeys
+0067
+0068 names = {'modelAdapter','folder','generator','parameters','data','kind'};
+0069 checkOptions(varargin, names, 'saveKcatSource');
+0070 p = parseGECKOargs(varargin, { ...
+0071 'modelAdapter', []; ...
+0072 'folder', []; ...
+0073 'generator', 'saveKcatSource'; ...
+0074 'parameters', struct(); ...
+0075 'data', ''; ...
+0076 'kind', 'gathered'});
+0077 kind = validatestring(p.kind, {'gathered','curated'});
+0078 name = char(name);
+0079 filename = kcatSourceFile(name, p.folder, p.modelAdapter);
+0080
+0081 [baseRxns, enzymeSets] = getEcRxnKeys(model);
+0082 [found, idx] = ismember(kcatList.rxns(:), model.ec.rxns);
+0083 if ~all(found)
+0084 error('saveKcatSource:unknownRxns', '%s', ravenList( ...
+0085 'These kcatList reactions are not in model.ec.rxns:', kcatList.rxns(~found), false));
+0086 end
+0087
+0088 n = numel(kcatList.rxns);
+0089 kcats = double(kcatList.kcats(:));
+0090 if numel(kcats) ~= n
+0091 error('saveKcatSource:fields', 'kcatList.kcats and kcatList.rxns differ in length.')
+0092 end
+0093 substrate = textColumn(kcatList, 'substrates', n);
+0094 ecCode = textColumn(kcatList, 'eccodes', n);
+0095 extra = textColumn(kcatList, 'detail', n);
+0096 if isfield(kcatList, 'kcatSource')
+0097 source = textColumn(kcatList, 'kcatSource', n);
+0098 elseif isfield(kcatList, 'source')
+0099 source = repmat({char(kcatList.source)}, n, 1);
+0100 else
+0101 source = repmat({name}, n, 1);
+0102 end
+0103 wc = numColumn(kcatList, 'wildcardLvl', n);
+0104 origin = numColumn(kcatList, 'origin', n);
+0105 sd = numColumn(kcatList, 'log10sd', n);
+0106
+0107 keep = kcats > 0 & isfinite(kcats);
+0108 noEnzymes = keep & cellfun(@isempty, enzymeSets(idx));
+0109 if any(noEnzymes)
+0110 fprintf('%d kcats of reactions without enzymes are not written to %s.\n', sum(noEnzymes), filename);
+0111 end
+0112 keep = find(keep & ~noEnzymes);
+0113
+0114 clean = @(c) regexprep(string(c(:)), '[\t\r\n]', ' ');
+0115 rows = strings(numel(keep), 9);
+0116 if ~isempty(keep)
+0117 wcText = strings(numel(keep), 1);
+0118 hasWc = ~isnan(wc(keep));
+0119 wcText(hasWc) = "wc=" + string(fix(wc(keep(hasWc))));
+0120 originText = strings(numel(keep), 1);
+0121 hasOrigin = ~isnan(origin(keep));
+0122 originText(hasOrigin) = "origin=" + string(fix(origin(keep(hasOrigin))));
+0123 detail = join([wcText, originText, strtrim(clean(extra(keep)))], "; ", 2);
+0124 detail = regexprep(detail, '^(; )+|(; )+$', '');
+0125 detail = regexprep(detail, '(; ){2,}', '; ');
+0126 rows(:, 1) = clean(baseRxns(idx(keep)));
+0127 rows(:, 2) = clean(enzymeSets(idx(keep)));
+0128 rows(:, 3) = clean(substrate(keep));
+0129 rows(:, 4) = formatNumbers(kcats(keep));
+0130 rows(:, 5) = formatNumbers(sd(keep));
+0131 rows(:, 6) = clean(source(keep));
+0132 rows(:, 7) = detail;
+0133 rows(:, 8) = clean(ecCode(keep));
+0134 % Sorted by the columns in this order, as geckopy sorts tuples: one key
+0135 % per row, the fields joined by a character below any in the text.
+0136 [~, order] = sort(join(rows(:, [1 2 3 6 4 5 7 8 9]), char(1), 2));
+0137 rows = rows(order, :);
+0138 end
+0139
+0140 folder = fileparts(filename);
+0141 if ~isfolder(folder)
+0142 mkdir(folder);
+0143 end
+0144 fid = fopen(filename, 'w', 'n', 'UTF-8');
+0145 if fid < 0
+0146 error('saveKcatSource:write', 'Cannot write %s', filename)
+0147 end
+0148 cleanup = onCleanup(@() fclose(fid));
+0149 fprintf(fid, '# source: %s\n', name);
+0150 fprintf(fid, '# generator: %s (GECKO %s)\n', cleanText(p.generator), geckoVersion());
+0151 fprintf(fid, '# parameters: %s\n', kcatSourceJson(p.parameters));
+0152 fprintf(fid, '# data: %s\n', cleanText(p.data));
+0153 fprintf(fid, '# kind: %s\n', kind);
+0154 fprintf(fid, '# created: %s\n', char(datetime('now', 'TimeZone', 'UTC', 'Format', 'yyyy-MM-dd''T''HH:mm:ss''Z''')));
+0155 fprintf(fid, 'rxn\tenzymes\tsubstrate\tkcat\tlog10_sd\tsource\tdetail\tec_code\tnote\n');
+0156 if ~isempty(rows)
+0157 fprintf(fid, '%s\n', join(rows, sprintf('\t'), 2));
+0158 end
+0159 end
+0160
+0161 function col = textColumn(s, field, n)
+0162 % A kcatList field as an n x 1 cell of char; a cell of several entries
+0163 % (e.g. all substrates of a reaction) is joined by '; '.
+0164 col = repmat({''}, n, 1);
+0165 if ~isfield(s, field) || isempty(s.(field))
+0166 return
+0167 end
+0168 v = s.(field);
+0169 if ischar(v) || isstring(v)
+0170 v = cellstr(v);
+0171 end
+0172 if numel(v) ~= n
+0173 error('saveKcatSource:fields', 'kcatList.%s and kcatList.rxns differ in length.', field)
+0174 end
+0175 for i = 1:n
+0176 e = v{i};
+0177 if iscell(e)
+0178 e = strjoin(cellfun(@char, e(:)', 'UniformOutput', false), '; ');
+0179 elseif isstring(e)
+0180 e = char(e);
+0181 elseif ~ischar(e)
+0182 e = '';
+0183 end
+0184 col{i} = e;
+0185 end
+0186 end
+0187
+0188 function col = numColumn(s, field, n)
+0189 col = nan(n, 1);
+0190 if isfield(s, field) && ~isempty(s.(field))
+0191 if numel(s.(field)) ~= n
+0192 error('saveKcatSource:fields', 'kcatList.%s and kcatList.rxns differ in length.', field)
+0193 end
+0194 col = double(s.(field)(:));
+0195 end
+0196 end
+0197
+0198 function t = formatNumbers(x)
+0199 % formatKcatNumber for a vector, in a few vectorized passes.
+0200 x = x(:);
+0201 t = strings(numel(x), 1);
+0202 whole = isfinite(x) & x == round(x) & abs(x) < 1e15;
+0203 t(whole) = compose('%d', x(whole));
+0204 t(isinf(x) & x > 0) = "Inf";
+0205 t(isinf(x) & x < 0) = "-Inf";
+0206 open = find(isfinite(x) & ~whole);
+0207 for digits = 1:17
+0208 if isempty(open)
+0209 break
+0210 end
+0211 candidate = compose(sprintf('%%.%dg', digits), x(open));
+0212 hit = str2double(candidate) == x(open);
+0213 t(open(hit)) = candidate(hit);
+0214 open = open(~hit);
+0215 end
+0216 end
+0217
+0218 function t = cleanText(t)
+0219 t = regexprep(char(t), '[\t\r\n]', ' ');
+0220 end
+0221
+0222 function v = geckoVersion()
+0223 v = '';
+0224 file = fullfile(findGECKOroot, 'version.txt');
+0225 if isfile(file)
+0226 v = strtrim(fileread(file));
+0227 end
+0228 end
+0229
+0230 function filename = kcatSourceFile(name, folder, modelAdapter)
+0231 name = char(name);
+0232 if isempty(regexp(name, '^[A-Za-z0-9_.-]+$', 'once'))
+0233 error('saveKcatSource:invalidName', ...
+0234 'The source name "%s" may only contain letters, digits, ''_'', ''-'' and ''.''.', name)
+0235 end
+0236 if isempty(folder)
+0237 if isempty(modelAdapter)
+0238 modelAdapter = ModelAdapterManager.getDefault();
+0239 if isempty(modelAdapter)
+0240 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0241 end
+0242 end
+0243 folder = fullfile(modelAdapter.params.path, 'data', 'kcats');
+0244 end
+0245 filename = fullfile(char(folder), [name '.tsv']);
+0246 if strcmpi(name, 'custom')
+0247 error('saveKcatSource:curated', ['custom.tsv holds curated kcats and is not '...
+0248 'written by GECKO; edit it by hand.'])
+0249 end
+0250 if isfile(filename)
+0251 meta = readHeaderLines(filename);
+0252 if (isfield(meta, 'kind') && strcmpi(meta.kind, 'curated')) || ...
+0253 (isfield(meta, 'source') && strcmpi(meta.source, 'custom'))
+0254 error('saveKcatSource:curated', '%s is curated by hand and is not overwritten.', filename)
+0255 end
+0256 end
+0257 end
+0258
+0259 function meta = readHeaderLines(filename)
+0260 % Only the header lines at the top, so that a damaged table does not stop
+0261 % a gathered file from being written again.
+0262 meta = struct();
+0263 fid = fopen(filename, 'r', 'n', 'UTF-8');
+0264 if fid < 0
+0265 return
+0266 end
+0267 cleanup = onCleanup(@() fclose(fid));
+0268 line = fgetl(fid);
+0269 if ischar(line)
+0270 line = regexprep(line, ['^' char(65279)], '');
+0271 end
+0272 while ischar(line) && (isempty(strtrim(line)) || startsWith(unquoteField(line), '#'))
+0273 [key, value] = parseHeaderLine(line);
+0274 if ~isempty(key)
+0275 meta.(key) = value;
+0276 end
+0277 line = fgetl(fid);
+0278 end
+0279 end
+0280
+0281 function j = kcatSourceJson(v)
+0282 if isstruct(v) && isscalar(v)
+0283 f = sort(fieldnames(v));
+0284 parts = cell(1, numel(f));
+0285 for k = 1:numel(f)
+0286 parts{k} = [jsonString(f{k}) ':'kcatSourceJson(v.(f{k}))];
+0287 end
+0288 j = ['{' strjoin(parts, ',') '}'];
+0289 elseif ischar(v) || (isstring(v) && isscalar(v))
+0290 j = jsonString(char(v));
+0291 elseif iscell(v)
+0292 j = ['[' strjoin(cellfun(@kcatSourceJson, v(:)', 'UniformOutput', false), ',') ']'];
+0293 elseif (isnumeric(v) || islogical(v)) && numel(v) ~= 1
+0294 j = ['[' strjoin(arrayfun(@kcatSourceJson, v(:)', 'UniformOutput', false), ',') ']'];
+0295 elseif islogical(v)
+0296 j = 'false';
+0297 if v
+0298 j = 'true';
+0299 end
+0300 elseif isnumeric(v)
+0301 if ~isfinite(v)
+0302 j = 'null';
+0303 else
+0304 j = formatKcatNumber(double(v));
+0305 end
+0306 elseif isempty(v)
+0307 j = 'null';
+0308 else
+0309 error('kcatSourceJson:type', 'Parameters of class %s cannot be written.', class(v))
+0310 end
+0311 end
+0312
+0313 function s = jsonString(s)
+0314 % JSON escapes, as Python's json.dumps(ensure_ascii=False).
+0315 out = '';
+0316 for c = s
+0317 switch c
+0318 case'"', out = [out '\"']; %#ok<AGROW>
+0319 case'\', out = [out '\\']; %#ok<AGROW>
+0320 case char(8), out = [out '\b']; %#ok<AGROW>
+0321 case char(9), out = [out '\t']; %#ok<AGROW>
+0322 case char(10), out = [out '\n']; %#ok<AGROW>
+0323 case char(12), out = [out '\f']; %#ok<AGROW>
+0324 case char(13), out = [out '\r']; %#ok<AGROW>
+0325 otherwise
+0326 if double(c) < 32
+0327 out = [out sprintf('\\u%04x', double(c))]; %#ok<AGROW>
+0328 else
+0329 out = [out c]; %#ok<AGROW>
+0330 end
+0331 end
+0332 end
+0333 s = ['"' out '"'];
+0334 end
+0335
+0336 function t = formatKcatNumber(x)
+0337 if isnan(x)
+0338 t = '';
+0339 elseif isinf(x)
+0340 t = 'Inf';
+0341 if x < 0
+0342 t = '-Inf';
+0343 end
+0344 elseif x == round(x) && abs(x) < 1e15
+0345 t = sprintf('%d', x);
+0346 else
+0347 for digits = 1:17
+0348 t = sprintf('%.*g', digits, x);
+0349 if str2double(t) == x
+0350 break
+0351 end
+0352 end
+0353 end
+0354 end
+0355
+0356 function [key, value] = parseHeaderLine(line)
+0357 key = ''; value = '';
+0358 tok = regexp(unquoteField(line), '^\s*#\s*([A-Za-z][A-Za-z0-9_]{0,62})\s*:\s?(.*)$', 'tokens', 'once');
+0359 if ~isempty(tok)
+0360 key = lower(tok{1});
+0361 value = strtrim(tok{2});
+0362 end
+0363 end
+0364
+0365 function t = unquoteField(t)
+0366 t = strtrim(t);
+0367 if numel(t) >= 2 && t(1) == '"' && t(end) == '"'
+0368 t = strtrim(strrep(t(2:end-1), '""', '"'));
+0369 end
+0370 end
+0371
+0372 function checkOptions(args, names, functionName)
+0373 % Options are name-value pairs with known names, so a misspelled option is
+0374 % an error instead of being taken as a positional argument.
+0375 id = [functionName ':arguments'];
+0376 given = args(1:2:end);
+0377 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0378 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0379 end
+0380 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0381 if ~isempty(unknown)
+0382 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0383 strjoin(names, ', '))
+0384 end
+0385 end
submitOpenKineticsPredictor Submit a kcat prediction job to OpenKineticsPredictor.
+
+
SYNOPSIS
+
function jobId = submitOpenKineticsPredictor(model, varargin)
+
+
DESCRIPTION
+
submitOpenKineticsPredictor Submit a kcat prediction job to OpenKineticsPredictor.
+
+ Builds the OpenKineticsPredictor input (protein sequences + single-substrate
+ SMILES, via writeOpenKineticsPredictorInput) and submits a kcat prediction
+ job to the OKP REST API (https://predictor.openkinetics.org/). The returned
+ job id is also stored in data/OKP_job.txt, so fetchOpenKineticsPredictor can
+ pick it up later.
+
+ Predictors available via OKP: CataPro, CatPred, CatRange, DLKcat, EITLEM,
+ KinForm-H, KinForm-L, OmniESI, OmniESI-O2DENet, TurNup, UniKP (see GET
+ /api/v1/methods/). CatRange predicts a class of kcat values;
+ readOpenKineticsPredictorOutput uses its geometric midpoint.
+
+ Obtaining an API key:
+ The OKP API requires a personal Bearer key (looks like 'ak_...'). Keys
+ are free and need no registration:
+ 1. Open https://predictor.openkinetics.org/api-docs in a browser.
+ 2. In the "API key generator" section, click "Generate".
+ 3. Copy the key shown (it is displayed only once). If you lose it,
+ revoke it on the same page and generate a new one.
+ Keys are tied to your IP and carry a daily prediction quota that resets
+ at midnight UTC.
+
+ The key is a secret; do NOT put it in the model adapter (which is
+ shared/committed). Provide it in one of three ways, checked in this
+ order: the apiKey argument below; the OKP_API_KEY environment variable
+ (setenv('OKP_API_KEY',...)); or a plain-text file data/okpApiKey.txt in
+ the model's data folder (this name is git-ignored by GECKO).
+
+ Parameters
+ ----------
+ model : struct
+ ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ ecRxns : logical
+ logical vector indicating which reactions to include (default: all
+ reactions).
+ overwrite : logical
+ rebuild data/OKP.csv even if it exists (default false; an existing
+ file is reused).
+ apiKey : char
+ OKP API key (default: resolved from OKP_API_KEY or data/okpApiKey.txt,
+ see above).
+ method : char
+ predictor to use (default: params.okp.method, or 'CataPro').
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ jobId : char
+ the OKP job identifier (also written to data/OKP_job.txt).
+
+ Examples
+ --------
+ jobId = submitOpenKineticsPredictor(model);
+ jobId = submitOpenKineticsPredictor(model, 'overwrite', true, 'apiKey', 'ak_...', 'method', 'DLKcat');
+
+ See also
+ --------
+ fetchOpenKineticsPredictor, writeOpenKineticsPredictorInput
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
resolveOkpApiKey resolveOkpApiKey Resolve the OpenKineticsPredictor API key.
0001 function jobId = submitOpenKineticsPredictor(model, varargin)
+0002 % submitOpenKineticsPredictor Submit a kcat prediction job to OpenKineticsPredictor.
+0003 %
+0004 % Builds the OpenKineticsPredictor input (protein sequences + single-substrate
+0005 % SMILES, via writeOpenKineticsPredictorInput) and submits a kcat prediction
+0006 % job to the OKP REST API (https://predictor.openkinetics.org/). The returned
+0007 % job id is also stored in data/OKP_job.txt, so fetchOpenKineticsPredictor can
+0008 % pick it up later.
+0009 %
+0010 % Predictors available via OKP: CataPro, CatPred, CatRange, DLKcat, EITLEM,
+0011 % KinForm-H, KinForm-L, OmniESI, OmniESI-O2DENet, TurNup, UniKP (see GET
+0012 % /api/v1/methods/). CatRange predicts a class of kcat values;
+0013 % readOpenKineticsPredictorOutput uses its geometric midpoint.
+0014 %
+0015 % Obtaining an API key:
+0016 % The OKP API requires a personal Bearer key (looks like 'ak_...'). Keys
+0017 % are free and need no registration:
+0018 % 1. Open https://predictor.openkinetics.org/api-docs in a browser.
+0019 % 2. In the "API key generator" section, click "Generate".
+0020 % 3. Copy the key shown (it is displayed only once). If you lose it,
+0021 % revoke it on the same page and generate a new one.
+0022 % Keys are tied to your IP and carry a daily prediction quota that resets
+0023 % at midnight UTC.
+0024 %
+0025 % The key is a secret; do NOT put it in the model adapter (which is
+0026 % shared/committed). Provide it in one of three ways, checked in this
+0027 % order: the apiKey argument below; the OKP_API_KEY environment variable
+0028 % (setenv('OKP_API_KEY',...)); or a plain-text file data/okpApiKey.txt in
+0029 % the model's data folder (this name is git-ignored by GECKO).
+0030 %
+0031 % Parameters
+0032 % ----------
+0033 % model : struct
+0034 % ecModel in GECKO 3 format (with ecModel.ec structure).
+0035 %
+0036 % Name-Value Arguments
+0037 % --------------------
+0038 % ecRxns : logical
+0039 % logical vector indicating which reactions to include (default: all
+0040 % reactions).
+0041 % overwrite : logical
+0042 % rebuild data/OKP.csv even if it exists (default false; an existing
+0043 % file is reused).
+0044 % apiKey : char
+0045 % OKP API key (default: resolved from OKP_API_KEY or data/okpApiKey.txt,
+0046 % see above).
+0047 % method : char
+0048 % predictor to use (default: params.okp.method, or 'CataPro').
+0049 % modelAdapter : ModelAdapter
+0050 % a loaded model adapter (default: the current default model adapter).
+0051 %
+0052 % Returns
+0053 % -------
+0054 % jobId : char
+0055 % the OKP job identifier (also written to data/OKP_job.txt).
+0056 %
+0057 % Examples
+0058 % --------
+0059 % jobId = submitOpenKineticsPredictor(model);
+0060 % jobId = submitOpenKineticsPredictor(model, 'overwrite', true, 'apiKey', 'ak_...', 'method', 'DLKcat');
+0061 %
+0062 % See also
+0063 % --------
+0064 % fetchOpenKineticsPredictor, writeOpenKineticsPredictorInput
+0065
+0066 p = parseGECKOargs(varargin, { ...
+0067 'ecRxns', []; ...
+0068 'overwrite', []; ...
+0069 'apiKey', []; ...
+0070 'method', []; ...
+0071 'modelAdapter', []});
+0072 ecRxns = p.ecRxns;
+0073 overwrite = p.overwrite;
+0074 apiKey = p.apiKey;
+0075 method = p.method;
+0076 modelAdapter = p.modelAdapter;
+0077
+0078 if isempty(modelAdapter)
+0079 modelAdapter = ModelAdapterManager.getDefault();
+0080 if isempty(modelAdapter)
+0081 error('Either provide a modelAdapter or set the default in ModelAdapterManager.')
+0082 end
+0083 end
+0084 params = modelAdapter.params;
+0085
+0086 if isempty(overwrite)
+0087 overwrite = false;
+0088 end
+0089 if isempty(ecRxns)
+0090 ecRxns = true(numel(model.ec.rxns),1);
+0091 elseif ~islogical(ecRxns) || numel(ecRxns) ~= numel(model.ec.rxns)
+0092 error('ecRxns should be a logical vector with length equal to model.ec.rxns')
+0093 end
+0094
+0095 apiKey = resolveOkpApiKey(apiKey, params.path);
+0096
+0097 % Resolve predictor method and request options: defaults -> params.okp.* ->
+0098 % the method argument (highest priority, applied last).
+0099 okp = struct('method','CataPro', ...
+0100 'handleLongSequences','truncate', ...
+0101 'includeSimilarityColumns',true,'canonicalizeSubstrates',true);
+0102 if isfield(params,'okp')
+0103 fn = fieldnames(params.okp);
+0104 for i=1:numel(fn); okp.(fn{i}) = params.okp.(fn{i}); end
+0105 end
+0106 if ~isempty(method)
+0107 okp.method = method;
+0108 end
+0109
+0110 %% Build (or reuse) the input CSV --- the same function, writing the same
+0111 % file, as the file-based workflow (writeOpenKineticsPredictorInput /
+0112 % readOpenKineticsPredictorOutput), rather than a separate reimplementation.
+0113 filename = fullfile(params.path,'data','OKP.csv');
+0114 if exist(filename,'file') && ~overwrite
+0115 fprintf('Using existing %s (set overwrite=true to rebuild).\n', filename);
+0116 else
+0117 writeOpenKineticsPredictorInput(model, 'ecRxns', ecRxns, 'modelAdapter', modelAdapter, ...
+0118 'onlyWithSmiles', true, 'overwrite', true);
+0119 end
+0120
+0121 %% Submit via the OKP REST API
+0122 % GECKO only ever predicts kcat.
+0123 url = 'https://predictor.openkinetics.org/api/v1/submit/';
+0124
+0125 targetsJson = '["kcat"]';
+0126 methodsJson = ['{"kcat":"' okp.method '"}'];
+0127
+0128 import matlab.net.http.*
+0129 import matlab.net.http.io.*
+0130 provider = MultipartFormProvider( ...
+0131 'file', FileProvider(filename), ...
+0132 'targets', targetsJson, ...
+0133 'methods', methodsJson, ...
+0134 'handleLongSequences', okp.handleLongSequences, ...
+0135 'includeSimilarityColumns', boolToStr(okp.includeSimilarityColumns), ...
+0136 'canonicalizeSubstrates', boolToStr(okp.canonicalizeSubstrates));
+0137 header = matlab.net.http.HeaderField('Authorization', ['Bearer ' apiKey]);
+0138 req = RequestMessage('POST', header, provider);
+0139 resp = req.send(url);
+0140
+0141 data = resp.Body.Data;
+0142 if resp.StatusCode ~= matlab.net.http.StatusCode.OK && ...
+0143 resp.StatusCode ~= matlab.net.http.StatusCode.Created
+0144 error('OKP submit failed (HTTP %d): %s', double(resp.StatusCode), okpErrorText(data));
+0145 end
+0146 if ~isstruct(data) || ~isfield(data,'jobId')
+0147 error('OKP submit returned an unexpected response (no jobId).');
+0148 end
+0149 jobId = data.jobId;
+0150
+0151 %% Persist job metadata (plain text, one field per line)
+0152 metaFile = fullfile(params.path,'data','OKP_job.txt');
+0153 fID = fopen(metaFile,'w');
+0154 fprintf(fID,'jobId: %s\n', jobId);
+0155 fprintf(fID,'method: %s\n', okp.method);
+0156 fprintf(fID,'submittedAt: %s\n', char(datetime('now','TimeZone','UTC','Format','yyyy-MM-dd''T''HH:mm:ss''Z''')));
+0157 fclose(fID);
+0158
+0159 fprintf('Submitted OKP job %s (method: %s). Metadata stored at %s\n', jobId, okp.method, metaFile);
+0160 fprintf('Check progress later with fetchOpenKineticsPredictor, or at https://predictor.openkinetics.org/\n');
+0161 end
+0162
+0163 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0164 function s = boolToStr(b)
+0165 if islogical(b) || isnumeric(b)
+0166 if b; s = 'true'; else; s = 'false'; end
+0167 else
+0168 s = char(b);
+0169 end
+0170 end
+0171
+0172 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0173 function txt = okpErrorText(data)
+0174 if isstruct(data) && isfield(data,'error')
+0175 txt = data.error;
+0176 elseif ischar(data) || isstring(data)
+0177 txt = char(data);
+0178 else
+0179 txt = 'unknown error';
+0180 end
+0181 end
updateKcatSources Gather the kcat sources that the configuration needs.
+
+
SYNOPSIS
+
function report = updateKcatSources(model, varargin)
+
+
DESCRIPTION
+
updateKcatSources Gather the kcat sources that the configuration needs.
+
+ Lists the kcat source files that [kcat_assignment] in model_adapter.toml
+ uses (see collectKcats), and gathers those that are missing or outdated
+ with kcatsFromBrenda, kcatsFromECTree, kcatsFromDLKcat and kcatsFromOKP:
+
+ - a file is outdated when its header records other settings than
+ [kcat_sources.<name>] asks for (presets set these for brenda and
+ ec_tree), or, for brenda and ec_tree, another BRENDA release than the
+ BRENDA files in use;
+ - brenda and ec_tree are made again; dlkcat reads data/DLKcat.tsv if it exists and
+ runs DLKcat (Docker) otherwise; okp_<method> submits or fetches a job,
+ and reports it as pending while the job runs;
+ - custom.tsv and other files it cannot gather are left as they are.
+
+ Requirements are checked first: Docker for a DLKcat run and an
+ OpenKineticsPredictor API key for OKP. Calling it again is safe: files
+ that are current are not gathered again.
+
+ Per-source settings are read from model_adapter.toml, for instance:
+
+ [kcat_sources.brenda]
+ aggregation = "median"
+
+ [kcat_sources.okp_catapro]
+ method = "CataPro"
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ dryRun : logical
+ only report what would be gathered (default false).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ report : table
+ one row per source: source, status (current, gathered, pending,
+ outdated, missing, failed or skipped) and note.
+
+ Examples
+ --------
+ updateKcatSources(ecModel, 'dryRun', true)
+ updateKcatSources(ecModel);
+
+ See also
+ --------
+ collectKcats, assignKcats, kcatsFromBrenda, kcatsFromDLKcat, kcatsFromOKP
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
kcatAssignmentConfig kcatAssignmentConfig The kcat assignment settings of a model adapter.
kcatsFromBrenda kcatsFromBrenda Gather kcats from BRENDA and keep them as a source file.
kcatsFromDLKcat kcatsFromDLKcat Gather kcats from DLKcat and keep them as a source file.
kcatsFromECTree kcatsFromECTree Estimate kcats from the EC tree and keep them as a source file.
kcatsFromHomologs kcatsFromHomologs Estimate kcats from measured homologs and keep them as a source file.
kcatsFromOKP kcatsFromOKP Gather kcats from OpenKineticsPredictor and keep them as a source file.
resolveOkpApiKey resolveOkpApiKey Resolve the OpenKineticsPredictor API key.
0001 function report = updateKcatSources(model, varargin)
+0002 % updateKcatSources Gather the kcat sources that the configuration needs.
+0003 %
+0004 % Lists the kcat source files that [kcat_assignment] in model_adapter.toml
+0005 % uses (see collectKcats), and gathers those that are missing or outdated
+0006 % with kcatsFromBrenda, kcatsFromECTree, kcatsFromDLKcat and kcatsFromOKP:
+0007 %
+0008 % - a file is outdated when its header records other settings than
+0009 % [kcat_sources.<name>] asks for (presets set these for brenda and
+0010 % ec_tree), or, for brenda and ec_tree, another BRENDA release than the
+0011 % BRENDA files in use;
+0012 % - brenda and ec_tree are made again; dlkcat reads data/DLKcat.tsv if it exists and
+0013 % runs DLKcat (Docker) otherwise; okp_<method> submits or fetches a job,
+0014 % and reports it as pending while the job runs;
+0015 % - custom.tsv and other files it cannot gather are left as they are.
+0016 %
+0017 % Requirements are checked first: Docker for a DLKcat run and an
+0018 % OpenKineticsPredictor API key for OKP. Calling it again is safe: files
+0019 % that are current are not gathered again.
+0020 %
+0021 % Per-source settings are read from model_adapter.toml, for instance:
+0022 %
+0023 % [kcat_sources.brenda]
+0024 % aggregation = "median"
+0025 %
+0026 % [kcat_sources.okp_catapro]
+0027 % method = "CataPro"
+0028 %
+0029 % Parameters
+0030 % ----------
+0031 % model : struct
+0032 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0033 %
+0034 % Name-Value Arguments
+0035 % --------------------
+0036 % dryRun : logical
+0037 % only report what would be gathered (default false).
+0038 % modelAdapter : ModelAdapter
+0039 % a loaded model adapter (default: the current default model adapter).
+0040 %
+0041 % Returns
+0042 % -------
+0043 % report : table
+0044 % one row per source: source, status (current, gathered, pending,
+0045 % outdated, missing, failed or skipped) and note.
+0046 %
+0047 % Examples
+0048 % --------
+0049 % updateKcatSources(ecModel, 'dryRun', true)
+0050 % updateKcatSources(ecModel);
+0051 %
+0052 % See also
+0053 % --------
+0054 % collectKcats, assignKcats, kcatsFromBrenda, kcatsFromDLKcat, kcatsFromOKP
+0055
+0056 checkOptions(varargin, {'dryRun','modelAdapter'}, 'updateKcatSources');
+0057 p = parseGECKOargs(varargin, {'dryRun', false; 'modelAdapter', []});
+0058 modelAdapter = p.modelAdapter;
+0059 if isempty(modelAdapter)
+0060 modelAdapter = ModelAdapterManager.getDefault();
+0061 if isempty(modelAdapter)
+0062 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0063 end
+0064 end
+0065 params = modelAdapter.params;
+0066 config = kcatAssignmentConfig(params);
+0067 kcatDir = fullfile(params.path, 'data', 'kcats');
+0068
+0069 names = {};
+0070 for s = 1:numel(config.stages)
+0071 for t = 1:numel(config.stages(s).files)
+0072 token = config.stages(s).files{t};
+0073 if startsWith(token, 'database_')
+0074 token = 'brenda';
+0075 end
+0076 if ~any(strcmp(names, token))
+0077 names{end+1} = token; %#ok<AGROW>
+0078 end
+0079 end
+0080 end
+0081
+0082 status = cell(numel(names), 1);
+0083 note = cell(numel(names), 1);
+0084 for k = 1:numel(names)
+0085 name = names{k};
+0086 file = fullfile(kcatDir, [name '.tsv']);
+0087 wanted = struct();
+0088 if isfield(config.sources, name)
+0089 wanted = config.sources.(name);
+0090 end
+0091 [status{k}, note{k}] = check(name, file, wanted);
+0092 if any(strcmp(status{k}, {'missing','outdated'})) && ~p.dryRun
+0093 [status{k}, note{k}] = gather(model, name, wanted, modelAdapter);
+0094 end
+0095 end
+0096 report = table(names(:), status, note, 'VariableNames', {'source','status','note'});
+0097 disp(report)
+0098
+0099 function [st, nt] = check(name, file, wanted)
+0100 st = 'current'; nt = '';
+0101 if strcmp(name, 'custom')
+0102 st = 'skipped'; nt = 'curated by hand';
+0103 return
+0104 elseif ~any(strcmp(name, {'brenda','ec_tree','homologs','dlkcat'})) && ~startsWith(name, 'okp_')
+0105 st = 'skipped'; nt = 'not a source GECKO gathers';
+0106 return
+0107 end
+0108 if ~isfile(file)
+0109 st = 'missing';
+0110 return
+0111 end
+0112 meta = readHeaderLines(file);
+0113 recorded = struct();
+0114 if isfield(meta, 'parameters') && ~isempty(meta.parameters)
+0115 recorded = jsondecode(meta.parameters);
+0116 end
+0117 f = fieldnames(wanted);
+0118 for i = 1:numel(f)
+0119 if ~isfield(recorded, f{i}) || ~isequal(normalizeValue(recorded.(f{i})), normalizeValue(wanted.(f{i})))
+0120 st = 'outdated';
+0121 nt = sprintf('%s differs from model_adapter.toml', f{i});
+0122 return
+0123 end
+0124 end
+0125 if any(strcmp(name, {'brenda','ec_tree','homologs'}))
+0126 release = brendaRelease(modelAdapter, name);
+0127 if isfield(meta, 'data') && ~isempty(release) && ~strcmp(meta.data, release)
+0128 st = 'outdated';
+0129 nt = sprintf('made from %s; GECKO has %s', meta.data, release);
+0130 end
+0131 end
+0132 end
+0133 end
+0134
+0135 function [st, nt] = gather(model, name, wanted, modelAdapter)
+0136 st = 'gathered'; nt = '';
+0137 try
+0138 if strcmp(name, 'brenda')
+0139 args = {};
+0140 if isfield(wanted, 'aggregation'), args = [args, {'aggregation', wanted.aggregation}]; end
+0141 if isfield(wanted, 'force_wildcard_level'), args = [args, {'forceWClvl', wanted.force_wildcard_level}]; end
+0142 kcatsFromBrenda(model, args{:}, 'modelAdapter', modelAdapter);
+0143 elseif strcmp(name, 'ec_tree')
+0144 args = {};
+0145 if isfield(wanted, 'k'), args = {'k', double(wanted.k)}; end
+0146 kcatsFromECTree(model, args{:}, 'modelAdapter', modelAdapter);
+0147 elseif strcmp(name, 'homologs')
+0148 args = {};
+0149 if isfield(wanted, 'k'), args = [args, {'k', double(wanted.k)}]; end
+0150 if isfield(wanted, 'min_identity'), args = [args, {'minIdentity', double(wanted.min_identity)}]; end
+0151 if isfield(wanted, 'coverage'), args = [args, {'coverage', double(wanted.coverage)}]; end
+0152 kcatsFromHomologs(model, args{:}, 'modelAdapter', modelAdapter);
+0153 elseif strcmp(name, 'dlkcat')
+0154 run = ~isfile(fullfile(modelAdapter.params.path, 'data', 'DLKcat.tsv'));
+0155 if run
+0156 [status, ~] = system('docker --version');
+0157 if status ~= 0
+0158 st = 'failed'; nt = 'DLKcat needs Docker to run';
+0159 return
+0160 end
+0161 end
+0162 kcatsFromDLKcat(model, 'run', run, 'modelAdapter', modelAdapter);
+0163 else% okp_<method>
+0164 method = okpMethod(name, wanted);
+0165 resolveOkpApiKey('', modelAdapter.params.path); % errors without a key
+0166 [~, done] = kcatsFromOKP(model, 'method', method, 'saveAs', name, 'modelAdapter', modelAdapter);
+0167 if ~done
+0168 st = 'pending'; nt = sprintf('OKP job for %s still running; call again later', method);
+0169 end
+0170 end
+0171 catch err
+0172 st = 'failed'; nt = err.message;
+0173 end
+0174 end
+0175
+0176 function method = okpMethod(name, wanted)
+0177 % The OKP method of okp_<method>: from [kcat_sources.okp_<method>] method,
+0178 % or the known method whose name gives this file name.
+0179 if isfield(wanted, 'method')
+0180 method = char(wanted.method);
+0181 return
+0182 end
+0183 known = {'CataPro','CatPred','CatRange','DLKcat','EITLEM','KinForm-H','KinForm-L', ...
+0184 'OmniESI','OmniESI-O2DENet','TurNup','UniKP'};
+0185 tags = cellfun(@(m) ['okp_' lower(regexprep(m, '[^A-Za-z0-9]+', '_'))], known, 'UniformOutput', false);
+0186 hit = strcmp(tags, name);
+0187 if ~any(hit)
+0188 error('updateKcatSources:okpMethod', ['Set the OKP method of %s in model_adapter.toml: '...
+0189 '[kcat_sources.%s] method = "..."'], name, name)
+0190 end
+0191 method = known{hit};
+0192 end
+0193
+0194 function v = normalizeValue(v)
+0195 if isstring(v)
+0196 v = char(v);
+0197 end
+0198 if ischar(v)
+0199 v = lower(v);
+0200 elseif islogical(v)
+0201 v = double(v);
+0202 end
+0203 end
+0204
+0205 function release = brendaRelease(modelAdapter, name)
+0206 % The release line of the BRENDA file a source is made from.
+0207 release = '';
+0208 files = struct('brenda', 'kcat.tsv', 'ec_tree', 'measurements.tsv', 'homologs', 'measurements.tsv');
+0209 fid = fopen(fullfile(modelAdapter.getBrendaDBFolder(), files.(name)), 'r');
+0210 if fid < 0
+0211 return
+0212 end
+0213 line = fgetl(fid);
+0214 fclose(fid);
+0215 tok = regexp(line, '^#\s*(BRENDA release [^ ]+)', 'tokens', 'once');
+0216 if ~isempty(tok)
+0217 release = tok{1};
+0218 end
+0219 end
+0220
+0221 function meta = readHeaderLines(filename)
+0222 % Only the header lines at the top, so that a damaged table does not stop
+0223 % a gathered file from being written again.
+0224 meta = struct();
+0225 fid = fopen(filename, 'r', 'n', 'UTF-8');
+0226 if fid < 0
+0227 return
+0228 end
+0229 cleanup = onCleanup(@() fclose(fid));
+0230 line = fgetl(fid);
+0231 if ischar(line)
+0232 line = regexprep(line, ['^' char(65279)], '');
+0233 end
+0234 while ischar(line) && (isempty(strtrim(line)) || startsWith(unquoteField(line), '#'))
+0235 [key, value] = parseHeaderLine(line);
+0236 if ~isempty(key)
+0237 meta.(key) = value;
+0238 end
+0239 line = fgetl(fid);
+0240 end
+0241 end
+0242
+0243 function [key, value] = parseHeaderLine(line)
+0244 key = ''; value = '';
+0245 tok = regexp(unquoteField(line), '^\s*#\s*([A-Za-z][A-Za-z0-9_]{0,62})\s*:\s?(.*)$', 'tokens', 'once');
+0246 if ~isempty(tok)
+0247 key = lower(tok{1});
+0248 value = strtrim(tok{2});
+0249 end
+0250 end
+0251
+0252 function t = unquoteField(t)
+0253 t = strtrim(t);
+0254 if numel(t) >= 2 && t(1) == '"' && t(end) == '"'
+0255 t = strtrim(strrep(t(2:end-1), '""', '"'));
+0256 end
+0257 end
+0258
+0259 function checkOptions(args, names, functionName)
+0260 % Options are name-value pairs with known names, so a misspelled option is
+0261 % an error instead of being taken as a positional argument.
+0262 id = [functionName ':arguments'];
+0263 given = args(1:2:end);
+0264 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0265 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0266 end
+0267 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0268 if ~isempty(unknown)
+0269 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0270 strjoin(names, ', '))
+0271 end
+0272 end
writeDLKcatInput Prepare and write the input file for DLKcat.
+
+
SYNOPSIS
+
function writtenTable = writeDLKcatInput(model, varargin)
+
+
DESCRIPTION
+
writeDLKcatInput Prepare and write the input file for DLKcat.
+
+ Prepares the input for DLKcat, and writes it to data/DLKcat.tsv in the
+ obj.params.path specified in the ModelAdapter.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ ecRxns : logical
+ for which reactions (from model.ec.rxns) DLKcat should predict kcat
+ values, provided as logical vector with same length as model.ec.rxns
+ (default: all reactions).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ onlyWithSmiles : logical
+ whether to only include metabolites with SMILES (default true).
+ filename : char
+ path to the input file, including the filename and .tsv extension
+ (default: data/DLKcat.tsv from the obj.params.path folder specified in
+ the modelAdapter).
+ overwrite : logical
+ whether existing file should be overwritten (default false, to prevent
+ overwriting a file that already contains DLKcat-predicted kcat values).
+
+ Returns
+ -------
+ writtenTable : cell
+ the table written, mainly to be used for testing purposes.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ writtenTable = writeDLKcatInput(model);
+ writtenTable = writeDLKcatInput(model, 'onlyWithSmiles', false);
+
+ See also
+ --------
+ runDLKcat, readDLKcatOutput, extractEnzymeSubstratePairs
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
extractEnzymeSubstratePairs extractEnzymeSubstratePairs The enzyme-substrate pairs of an ecModel's reactions.
+This function is called by:
+
+
kcatsFromDLKcat kcatsFromDLKcat Gather kcats from DLKcat and keep them as a source file.
+
+
+
+
+
SOURCE CODE
+
0001 function writtenTable = writeDLKcatInput(model, varargin)
+0002 % writeDLKcatInput Prepare and write the input file for DLKcat.
+0003 %
+0004 % Prepares the input for DLKcat, and writes it to data/DLKcat.tsv in the
+0005 % obj.params.path specified in the ModelAdapter.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % model : struct
+0010 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0011 %
+0012 % Name-Value Arguments
+0013 % --------------------
+0014 % ecRxns : logical
+0015 % for which reactions (from model.ec.rxns) DLKcat should predict kcat
+0016 % values, provided as logical vector with same length as model.ec.rxns
+0017 % (default: all reactions).
+0018 % modelAdapter : ModelAdapter
+0019 % a loaded model adapter (default: the current default model adapter).
+0020 % onlyWithSmiles : logical
+0021 % whether to only include metabolites with SMILES (default true).
+0022 % filename : char
+0023 % path to the input file, including the filename and .tsv extension
+0024 % (default: data/DLKcat.tsv from the obj.params.path folder specified in
+0025 % the modelAdapter).
+0026 % overwrite : logical
+0027 % whether existing file should be overwritten (default false, to prevent
+0028 % overwriting a file that already contains DLKcat-predicted kcat values).
+0029 %
+0030 % Returns
+0031 % -------
+0032 % writtenTable : cell
+0033 % the table written, mainly to be used for testing purposes.
+0034 %
+0035 % Examples
+0036 % --------
+0037 % % optional arguments may be given positionally or as name-value pairs:
+0038 % writtenTable = writeDLKcatInput(model);
+0039 % writtenTable = writeDLKcatInput(model, 'onlyWithSmiles', false);
+0040 %
+0041 % See also
+0042 % --------
+0043 % runDLKcat, readDLKcatOutput, extractEnzymeSubstratePairs
+0044
+0045 p = parseGECKOargs(varargin, { ...
+0046 'ecRxns', []; ...
+0047 'modelAdapter', []; ...
+0048 'onlyWithSmiles', []; ...
+0049 'filename', []; ...
+0050 'overwrite', []});
+0051 ecRxnsSel = p.ecRxns;
+0052 modelAdapter = p.modelAdapter;
+0053 onlyWithSmiles = p.onlyWithSmiles;
+0054 filename = p.filename;
+0055 overwrite = p.overwrite;
+0056
+0057 if isempty(ecRxnsSel)
+0058 ecRxnsSel = true(numel(model.ec.rxns),1);
+0059 elseif ~logical(ecRxnsSel)
+0060 error('ecRxns should be provided as logical vector')
+0061 elseif numel(ecRxnsSel)~=numel(model.ec.rxns)
+0062 error('Length of ecRxns is not the same as model.ec.rxns')
+0063 end
+0064 ecRxnsSel = find(ecRxnsSel); % Change to indices, into the full model.ec.rxns
+0065
+0066 if isempty(modelAdapter)
+0067 modelAdapter = ModelAdapterManager.getDefault();
+0068 if isempty(modelAdapter)
+0069 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0070 end
+0071 end
+0072 params = modelAdapter.params;
+0073
+0074 if isempty(onlyWithSmiles)
+0075 onlyWithSmiles=true;
+0076 end
+0077
+0078 if isempty(filename)
+0079 filename = fullfile(params.path,'data','DLKcat.tsv');
+0080 elseif ~endsWith(filename,'.tsv')
+0081 error('If filename is provided, it should include the .tsv extension.')
+0082 end
+0083
+0084 if isempty(overwrite) || ~overwrite % If is true
+0085 if exist(filename,'file')
+0086 error([filename ' already exists, either delete it first, or set the overwrite input argument as true'])
+0087 end
+0088 end
+0089
+0090 pairs = extractEnzymeSubstratePairs(model, 'ecRxns', ismember((1:numel(model.ec.rxns))', ecRxnsSel), ...
+0091 'modelAdapter', modelAdapter);
+0092
+0093 % Prepare output
+0094 out(1,:) = model.ec.rxns(pairs.ecRxn);
+0095 out(2,:) = model.ec.genes(pairs.protein);
+0096 out(3,:) = model.metNames(pairs.met);
+0097 if isfield(model,'metSmiles')
+0098 out(4,:) = model.metSmiles(pairs.met);
+0099 else
+0100 out(4,:) = cell(numel(pairs.met),1);
+0101 end
+0102
+0103 out(5,:) = model.ec.sequence(pairs.protein);
+0104 if onlyWithSmiles
+0105 out(:,cellfun(@isempty,out(4,:))) = [];
+0106 else
+0107 out(4,cellfun(@isempty,out(4,:))) = {'None'};
+0108 end
+0109 out(6,:) = cell(numel(out(1,:)),1);
+0110 out(6,:) = {'NA'};
+0111
+0112 % Write file
+0113 fID = fopen(filename,'w');
+0114 fprintf(fID,'%s\t%s\t%s\t%s\t%s\t%s\n',out{:});
+0115 fclose(fID);
+0116 fprintf('Model-specific DLKcat input stored at %s\n',filename);
+0117
+0118 writtenTable = out;
+0119 end
writeOpenKineticsPredictorInput Write input file for OpenKineticsPredictor.
+
+
SYNOPSIS
+
function writeOpenKineticsPredictorInput(model, varargin)
+
+
DESCRIPTION
+
writeOpenKineticsPredictorInput Write input file for OpenKineticsPredictor.
+
+ Prepares input for OpenKineticsPredictor: protein sequences and single-
+ substrate SMILES, which allows the use of the following predictors: DLKcat;
+ EITLEM; UniKP; KinForm-H; KinForm-L; CataPro; CatPred.
+
+ The function writes OKP.csv, that can be used as input file at
+ https://predictor.openkinetics.org/
+
+ The OpenKineticsPredictor output file can be used to generate a kcatList
+ through readOpenKineticsPredictorOutput.
+
+ Parameters
+ ----------
+ model : struct
+ ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ ecRxns : logical
+ logical vector indicating which reactions to include (default: all
+ reactions).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ onlyWithSmiles : logical
+ only include entries with SMILES (default true).
+ overwrite : logical
+ overwrite existing file (default false).
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ writeOpenKineticsPredictorInput(model, ecRxns);
+ writeOpenKineticsPredictorInput(model, 'onlyWithSmiles', false);
+
+ See also
+ --------
+ readOpenKineticsPredictorOutput, writeDLKcatInput
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
submitOpenKineticsPredictor submitOpenKineticsPredictor Submit a kcat prediction job to OpenKineticsPredictor.
+
+
+
+
+
SOURCE CODE
+
0001 function writeOpenKineticsPredictorInput(model, varargin)
+0002 % writeOpenKineticsPredictorInput Write input file for OpenKineticsPredictor.
+0003 %
+0004 % Prepares input for OpenKineticsPredictor: protein sequences and single-
+0005 % substrate SMILES, which allows the use of the following predictors: DLKcat;
+0006 % EITLEM; UniKP; KinForm-H; KinForm-L; CataPro; CatPred.
+0007 %
+0008 % The function writes OKP.csv, that can be used as input file at
+0009 % https://predictor.openkinetics.org/
+0010 %
+0011 % The OpenKineticsPredictor output file can be used to generate a kcatList
+0012 % through readOpenKineticsPredictorOutput.
+0013 %
+0014 % Parameters
+0015 % ----------
+0016 % model : struct
+0017 % ecModel in GECKO 3 format (with ecModel.ec structure).
+0018 %
+0019 % Name-Value Arguments
+0020 % --------------------
+0021 % ecRxns : logical
+0022 % logical vector indicating which reactions to include (default: all
+0023 % reactions).
+0024 % modelAdapter : ModelAdapter
+0025 % a loaded model adapter (default: the current default model adapter).
+0026 % onlyWithSmiles : logical
+0027 % only include entries with SMILES (default true).
+0028 % overwrite : logical
+0029 % overwrite existing file (default false).
+0030 %
+0031 % Examples
+0032 % --------
+0033 % % optional arguments may be given positionally or as name-value pairs:
+0034 % writeOpenKineticsPredictorInput(model, ecRxns);
+0035 % writeOpenKineticsPredictorInput(model, 'onlyWithSmiles', false);
+0036 %
+0037 % See also
+0038 % --------
+0039 % readOpenKineticsPredictorOutput, writeDLKcatInput
+0040
+0041 [geckoPath, ~] = findGECKOroot();
+0042
+0043 %% Parse inputs
+0044 p = parseGECKOargs(varargin, { ...
+0045 'ecRxns', []; ...
+0046 'modelAdapter', []; ...
+0047 'onlyWithSmiles', []; ...
+0048 'overwrite', []});
+0049 ecRxns = p.ecRxns;
+0050 modelAdapter = p.modelAdapter;
+0051 onlyWithSmiles = p.onlyWithSmiles;
+0052 overwrite = p.overwrite;
+0053
+0054 if isempty(ecRxns)
+0055 ecRxns = true(numel(model.ec.rxns),1);
+0056 elseif ~islogical(ecRxns) || numel(ecRxns) ~= numel(model.ec.rxns)
+0057 error('ecRxns should be a logical vector with length equal to model.ec.rxns')
+0058 end
+0059
+0060 if isempty(modelAdapter)
+0061 modelAdapter = ModelAdapterManager.getDefault();
+0062 if isempty(modelAdapter)
+0063 error('Either provide a modelAdapter or set the default in ModelAdapterManager.')
+0064 end
+0065 end
+0066 params = modelAdapter.params;
+0067
+0068 if isempty(onlyWithSmiles)
+0069 onlyWithSmiles = true;
+0070 end
+0071
+0072 if isempty(overwrite)
+0073 overwrite = false;
+0074 end
+0075
+0076 %% Output files
+0077 filename = fullfile(params.path,'data','OKP.csv');
+0078 if ~overwrite && exist(filename,'file')
+0079 error('%s already exists. Set overwrite=true to replace it.', filename)
+0080 end
+0081
+0082 %% Map ec.rxns to model.rxns
+0083 if model.ec.geckoLight
+0084 origRxns = extractAfter(model.ec.rxns, 4);
+0085 else
+0086 origRxns = model.ec.rxns;
+0087 end
+0088
+0089 ecRxnsIdx = find(ecRxns);
+0090 [found, origRxnIdxs] = ismember(origRxns(ecRxnsIdx), model.rxns);
+0091 if ~all(found)
+0092 error('Not all reactions in model.ec.rxns found in model.rxns')
+0093 end
+0094
+0095 %% Build reduced stoichiometric matrix (ignore certain metabolites)
+0096 % Load metabolites to ignore (reuses DLKcat files)
+0097 if exist(fullfile(params.path,'data','DLKcatIgnoreMets.tsv'),'file')
+0098 ignoreFile = fullfile(params.path,'data','DLKcatIgnoreMets.tsv');
+0099 else
+0100 ignoreFile = fullfile(geckoPath,'databases','DLKcatIgnoreMets.tsv');
+0101 end
+0102 fID = fopen(ignoreFile);
+0103 fileData = textscan(fID,'%s %s','delimiter','\t');
+0104 fclose(fID);
+0105 [ignoreMets, ignoreSmiles] = deal(fileData{[1,2]});
+0106
+0107 % Normalize metabolite names (remove special characters, lowercase)
+0108 metsNorm = lower(regexprep(model.metNames,'[^0-9a-zA-Z]+',''));
+0109 ignoreMetsNorm = lower(regexprep(ignoreMets,'[^0-9a-zA-Z]+',''));
+0110 ignoreSmiles(cellfun(@isempty,ignoreSmiles)) = [];
+0111
+0112 % Mark metabolites to ignore
+0113 ignoreMetsIdx = ismember(metsNorm, ignoreMetsNorm);
+0114 if isfield(model,'metSmiles')
+0115 ignoreMetsIdx = ignoreMetsIdx | ismember(model.metSmiles, ignoreSmiles);
+0116 end
+0117 ignoreMetsIdx = ignoreMetsIdx | startsWith(model.mets,'prot_');
+0118
+0119 reducedS = model.S;
+0120 reducedS(ignoreMetsIdx,:) = 0;
+0121
+0122 %% Remove currency metabolite pairs (reuses DLKcat files)
+0123 if exist(fullfile(params.path,'data','DLKcatCurrencyMets.tsv'),'file')
+0124 currencyFile = fullfile(params.path,'data','DLKcatCurrencyMets.tsv');
+0125 else
+0126 currencyFile = fullfile(geckoPath,'databases','DLKcatCurrencyMets.tsv');
+0127 end
+0128 fID = fopen(currencyFile);
+0129 fileData = textscan(fID,'%s %s','delimiter','\t');
+0130 fclose(fID);
+0131 [currencyMets(:,1), currencyMets(:,2)] = deal(fileData{[1,2]});
+0132 currencyMets = lower(regexprep(currencyMets,'[^0-9a-zA-Z]+',''));
+0133 for i=1:size(currencyMets,1)
+0134 subs = strcmp(currencyMets(i,1),metsNorm);
+0135 prod = strcmp(currencyMets(i,2),metsNorm);
+0136 [~,subsRxns]=find(reducedS(subs,:));
+0137 [~,prodRxns]=find(reducedS(prod,:));
+0138 pairRxns = intersect(subsRxns,prodRxns);
+0139 tempRedS=reducedS;
+0140 tempRedS([find(subs);find(prod)],pairRxns) = 0;
+0141 % Do not remove currency mets if no substrate remains
+0142 rxnsWithRemainingSubstrates = any(tempRedS(:,pairRxns) < 0,1);
+0143 reducedS([find(subs);find(prod)],intersect(pairRxns,pairRxns(rxnsWithRemainingSubstrates))) = 0;
+0144 end
+0145
+0146
+0147 %% Extract substrates for selected reactions only
+0148 clearedS = reducedS(:, origRxnIdxs);
+0149
+0150 % Find all substrates (negative stoichiometry)
+0151 [substrateIdxs, reactionIdxs] = find(clearedS < 0);
+0152
+0153 % Find proteins catalyzing each reaction. reactionIdxs indexes clearedS's
+0154 % columns, i.e. positions within the requested ecRxnsIdx subset -- map back
+0155 % through ecRxnsIdx to the absolute model.ec.rxnEnzMat row before indexing it.
+0156 [proteinIdxs, rxnEnzIdxs] = find(model.ec.rxnEnzMat(ecRxnsIdx(reactionIdxs),:)');
+0157
+0158 %% Build output: sequence and SMILES
+0159 sequences = model.ec.sequence(proteinIdxs);
+0160
+0161 if isfield(model,'metSmiles')
+0162 smiles = model.metSmiles(substrateIdxs(rxnEnzIdxs));
+0163 else
+0164 smiles = cell(size(sequences));
+0165 end
+0166
+0167 %% Filter empty entries
+0168 if onlyWithSmiles
+0169 valid = ~cellfun(@isempty, sequences) & ~cellfun(@isempty, smiles);
+0170 else
+0171 valid = ~cellfun(@isempty, sequences);
+0172 smiles(cellfun(@isempty, smiles)) = {'None'};
+0173 end
+0174
+0175 sequences = sequences(valid);
+0176 smiles = smiles(valid);
+0177
+0178 %% Write output file
+0179 % Sequence and SMILES
+0180 outTable = [sequences(:)'; smiles(:)'];
+0181
+0182 fID = fopen(filename,'w');
+0183 fprintf(fID,'Protein Sequence,Substrate\n');
+0184 fprintf(fID,'%s,%s\n', outTable{:});
+0185 fclose(fID);
+0186 fprintf('OpenKineticsPredictor input stored at %s\n', filename);
+0187 end
function [model, foundComplex, proposedComplex] = applyComplexData(model, complexInfo, modelAdapter, verbose)
-
-
DESCRIPTION
-
applyComplexData
- Apply stochiometry for complex in an ecModel
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- complexInfo structure as generated by getComplexData. If nothing
- is provided, an attempt will be made to read
- data/ComplexPortal.json from the obj.params.path folder
- specified in the modelAdapter.
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
- verbose logical if a summary should be shown in the Command
- Window (Optional, default true)
-
- Output:
- model ecModel where model.ec.rxnEnzMat is populated with
- subunit stochiometries
- foundComplex complexes that fully matched between the model and the
- complex data
- proposedComplex complexes where the model contained >75% but <100% of
- the proteins indicated by Complex Portal, or where the
- model contained more proteins than indicated for that
- complex in Complex Portal.
-
- Usage:
- [model, foundComplex, proposedComplex] = applyComplexData(ecModel, complexInfo, modelAdapter);
function [model, rxnUpdated, notMatch] = applyCustomKcats(model, customKcats, modelAdapter)
-
-
DESCRIPTION
-
applyCustomKcats
- Apply user defined kcats. Reads data/customKcats.tsv in the obj.params.path
- specified in the model adapter. Alternatively, a customKcats structure can
- provided, as specified below.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- customKcats structure with custom kcat information. If nothing
- is provided, an attempt will be made to read
- data/customKcats.tsv from the obj.params.path folder
- specified in the modelAdapter.
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- model ecModel where kcats for defined proteins have been
- changed
- rxnUpdated ids list of updated reactions, new kcats were applied
- notMatch table with the list of reactions which the custom
- information provided does not have full match (> 50%)
- based on GPR rules. Then, they are suggested to be
- curated by the user
-
- customKcats structure:
- - proteins protein identifiers, multiple for the same kcat (in case
- of a protein complex) are separated by ' + '
- - genes gene identifiers (optional, not used in matching)
- - gene_name short gene name (optional, not used in matching)
- - kcat new kcat value (one per entry)
- - rxns reaction identifiers, multiple for the same kcat are
- separated by ',' (see further explanation below)
- - notes will be appended to model.ec.notes (optional)
- - stoicho complex stoichiometry, separated by ' + ' (examples: '1'
- or '3 + 1'), matching the order in proteins field
-
- Matching order:
- (1) reactions are identified by .proteins and .rxns
- (2) reactions are identified by .proteins only (empty .rxns entry)
- no additional checks are made: is a reaction annotated with these
- proteins? => its kcat will be updated, irrespective of the exact
- reaction, direction, substrate, etc.
- (3) reactions are identified by .rxns only (empty .proteins entry)
- no additional checks are made: is a reaction derived from the
- original reaction identifier => its kcat will be updated,
- irrespective of the annotated protein
-
- customKcats.rxns field:
- The reaction identifiers are from the ORIGINAL model, before _EXP_
- suffixes were added by makeEcModel. Reaction directionality IS however
- specified, with a _REV suffix.
- Example entries:
- 'r_0001' will match r_0001, r_0001_EXP_1, r_0001_EXP_2 etc., but
- not r_0001_REV, r_0001_EXP_1_REV etc.
- 'r_0001_REV' will match r_0001_REV, r_0001_EXP_1_REV, r_0001_EXP_2_REV,
- etc., but not r_0001, r_0001_EXP_1 etc.
- Multiple identifiers should be comma separated (e.g. r_0001, r_0002)
-
- Usage:
- [model, rxnUpdated, notMatch] = applyCustomKcats(model, customKcats, modelAdapter);
0001 function [model, rxnUpdated, notMatch] = applyCustomKcats(model, customKcats, modelAdapter)
-0002 % applyCustomKcats
-0003 % Apply user defined kcats. Reads data/customKcats.tsv in the obj.params.path
-0004 % specified in the model adapter. Alternatively, a customKcats structure can
-0005 % provided, as specified below.
-0006 %
-0007 % Input:
-0008 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0009 % customKcats structure with custom kcat information. If nothing
-0010 % is provided, an attempt will be made to read
-0011 % data/customKcats.tsv from the obj.params.path folder
-0012 % specified in the modelAdapter.
-0013 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0014 % default model adapter).
-0015 %
-0016 % Output:
-0017 % model ecModel where kcats for defined proteins have been
-0018 % changed
-0019 % rxnUpdated ids list of updated reactions, new kcats were applied
-0020 % notMatch table with the list of reactions which the custom
-0021 % information provided does not have full match (> 50%)
-0022 % based on GPR rules. Then, they are suggested to be
-0023 % curated by the user
-0024 %
-0025 % customKcats structure:
-0026 % - proteins protein identifiers, multiple for the same kcat (in case
-0027 % of a protein complex) are separated by ' + '
-0028 % - genes gene identifiers (optional, not used in matching)
-0029 % - gene_name short gene name (optional, not used in matching)
-0030 % - kcat new kcat value (one per entry)
-0031 % - rxns reaction identifiers, multiple for the same kcat are
-0032 % separated by ',' (see further explanation below)
-0033 % - notes will be appended to model.ec.notes (optional)
-0034 % - stoicho complex stoichiometry, separated by ' + ' (examples: '1'
-0035 % or '3 + 1'), matching the order in proteins field
-0036 %
-0037 % Matching order:
-0038 % (1) reactions are identified by .proteins and .rxns
-0039 % (2) reactions are identified by .proteins only (empty .rxns entry)
-0040 % no additional checks are made: is a reaction annotated with these
-0041 % proteins? => its kcat will be updated, irrespective of the exact
-0042 % reaction, direction, substrate, etc.
-0043 % (3) reactions are identified by .rxns only (empty .proteins entry)
-0044 % no additional checks are made: is a reaction derived from the
-0045 % original reaction identifier => its kcat will be updated,
-0046 % irrespective of the annotated protein
-0047 %
-0048 % customKcats.rxns field:
-0049 % The reaction identifiers are from the ORIGINAL model, before _EXP_
-0050 % suffixes were added by makeEcModel. Reaction directionality IS however
-0051 % specified, with a _REV suffix.
-0052 % Example entries:
-0053 % 'r_0001' will match r_0001, r_0001_EXP_1, r_0001_EXP_2 etc., but
-0054 % not r_0001_REV, r_0001_EXP_1_REV etc.
-0055 % 'r_0001_REV' will match r_0001_REV, r_0001_EXP_1_REV, r_0001_EXP_2_REV,
-0056 % etc., but not r_0001, r_0001_EXP_1 etc.
-0057 % Multiple identifiers should be comma separated (e.g. r_0001, r_0002)
-0058 %
-0059 % Usage:
-0060 % [model, rxnUpdated, notMatch] = applyCustomKcats(model, customKcats, modelAdapter);
-0061
-0062 if nargin < 3 || isempty(modelAdapter)
-0063 modelAdapter = ModelAdapterManager.getDefault();
-0064 if isempty(modelAdapter)
-0065 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0066 end
-0067 end
-0068
-0069 params = modelAdapter.params;
-0070
-0071 if nargin<2 || isempty(customKcats)
-0072 customKcats = fullfile(params.path,'data','customKcats.tsv');
-0073 end
-0074 if isstruct(customKcats)
-0075 if ~all(isfield(customKcats,{'proteins','kcat','rxns'}))
-0076 error('The customKcats structure does not have all essential fields.');
-0077 end
-0078 elseif isfile(customKcats)
-0079 fID = fopen(customKcats, 'r');
-0080 fileContent = textscan(fID, '%s %s %s %f %q %s %s', 'Delimiter', '\t', 'HeaderLines', 1);
-0081 fclose(fID);
-0082 clear customKcats
-0083 customKcats.proteins = fileContent{1};
-0084 customKcats.genes = fileContent{2};
-0085 customKcats.gene_name = fileContent{3};
-0086 customKcats.kcat = fileContent{4};
-0087 customKcats.rxns = fileContent{5};
-0088 customKcats.notes = fileContent{6};
-0089 customKcats.stoicho = fileContent{7};
-0090 else
-0091 error(['Cannot find file: ' customKcats]);
-0092 end
-0093
-0094 rxnToUpdate = false(length(model.ec.rxns),1);
-0095 rxnNotMatch = false(length(model.ec.rxns),1);
-0096 evaluatedRule = cell(length(model.ec.rxns),1);
-0097 enzInModel = cell(length(model.ec.rxns),1);
-0098 ecRxnNoSuffix = regexprep(model.ec.rxns,'_EXP_\d+$','');
-0099
-0100 % Implementation for full GECKO formulation
-0101 if ~model.ec.geckoLight
-0102 for i = 1:numel(customKcats.proteins)
-0103 if isempty(customKcats.proteins{i})
-0104 %If only reaction ID(s) is/are specified (and no proteins),
-0105 %then apply the kcat to all isozymic reactions
-0106 rxns = strtrim(strsplit(customKcats.rxns{i}, ','));
-0107 rxnIdxs = ismember(ecRxnNoSuffix,rxns);
-0108 rxnToUpdate(rxnIdxs) = 1;
-0109 model.ec.kcat(rxnIdxs) = customKcats.kcat(i);
-0110 else
-0111 prots = strtrim(strsplit(customKcats.proteins{i}, '+'));
-0112
-0113 % Find the index for the enzymes which kcat will be changed. In
-0114 % case the protein is not in the model generate a warning.
-0115 try
-0116 enzIdx = cellfun(@(x) find(strcmpi(model.ec.enzymes, x)), prots);
-0117 catch
-0118 enzIdx = [];
-0119 printOrange(['WARNING: Protein(s) ' customKcats.proteins{i} ' were not found in the model.']);
-0120 end
-0121
-0122 % if not specific reactions are defined, find all the reaction
-0123 % index where the enzyme is used
-0124 if isempty(customKcats.rxns{i})
-0125 temp_rxnIdxs = arrayfun(@(x) find(model.ec.rxnEnzMat(:, x)), enzIdx, 'UniformOutput', false);
-0126 % otherwhise, If a set of reactions if defined, only get the index for those
-0127 % but ignore any _EXP_ suffixes
-0128 else
-0129 rxns = strtrim(strsplit(customKcats.rxns{i}, ','));
-0130 temp_rxnIdxs = arrayfun(@(x) find(strcmpi(ecRxnNoSuffix, x)), rxns, 'UniformOutput', false);
-0131 end
-0132
-0133 if ~isempty(temp_rxnIdxs)
-0134 rxnIdxs = [];
-0135 for j = 1:numel(temp_rxnIdxs)
-0136 rxnIdxs = [rxnIdxs; temp_rxnIdxs{j}];
-0137 end
-0138
-0139 % Check when multiple proteins are involved, since it can return same rxn n times
-0140 rxnIdxs = unique(rxnIdxs); %unique(rxnIdxs{1, :});
-0141
-0142 for j = 1:numel(rxnIdxs)
-0143 % Get all the enzymes involved in the reaction
-0144 allEnzInRxn = find(model.ec.rxnEnzMat(rxnIdxs(j),:));
-0145
-0146 C = intersect(enzIdx, allEnzInRxn);
-0147
-0148 % Determine the match percentage bewteen the rules
-0149 if numel(C) == numel(enzIdx) && numel(C) == numel(allEnzInRxn)
-0150 match = 1;
-0151 else
-0152 if numel(enzIdx) < numel(allEnzInRxn)
-0153 match = numel(C) / numel(allEnzInRxn);
-0154 else
-0155 match = numel(C) / numel(enzIdx);
-0156 end
-0157 end
-0158
-0159 % Update the kcat value stored in the model, if match 100%,
-0160 % otherwhise if >= 0.5 inform to the user to be validated
-0161 if match == 1
-0162 rxnToUpdate(rxnIdxs(j)) = 1;
-0163 model.ec.kcat(rxnIdxs(j)) = customKcats.kcat(i);
-0164
-0165 % Add note mentioning manual kcat change
-0166 model.ec.source{rxnIdxs(j),1} = 'custom';
-0167 if isfield(customKcats,'notes')
-0168 if isempty(model.ec.notes{rxnIdxs(j), 1}) && ~isempty(customKcats.notes{i})
-0169 model.ec.notes{rxnIdxs(j), 1} = customKcats.notes{i};
-0170 else
-0171 model.ec.notes{rxnIdxs(j), 1} = [model.ec.notes{rxnIdxs(j), 1} ', ' customKcats.notes{i}];
-0172 end
-0173 end
-0174 elseif match >= 0.5 && match < 1
-0175 rxnNotMatch(rxnIdxs(j)) = 1;
-0176 evaluatedRule{rxnIdxs(j), 1} = customKcats.proteins{i};
-0177 enzInModel{rxnIdxs(j), 1} = strjoin(model.ec.enzymes(allEnzInRxn), ' + ');
-0178 end
-0179 end
-0180 end
-0181 end
-0182 end
-0183
-0184 % Apply the new kcat values to the model
-0185 if ~isempty(find(rxnToUpdate, 1))
-0186 model = applyKcatConstraints(model, rxnToUpdate);
-0187 else
-0188 printOrange('WARNING: No matches found. Consider checking the IDs or proteins in customKcats.');
-0189 end
-0190
-0191 rxnUpdated = model.ec.rxns(find(rxnToUpdate));
-0192
-0193 % Remove from notMatch those reactions already updated
-0194 remove = and(rxnToUpdate, rxnNotMatch);
-0195 rxnNotMatch(remove) = 0;
-0196 evaluatedRule(remove) = '';
-0197 enzInModel(remove) = '';
-0198
-0199 idRxns = model.ec.rxns(find(rxnNotMatch));
-0200 fullIdx = cellfun(@(x) find(strcmpi(model.rxns, x)), idRxns);
-0201 rxnsNames = model.rxnNames(fullIdx);
-0202 evaluatedRule = evaluatedRule(~cellfun('isempty', evaluatedRule));
-0203 enzInModel = enzInModel(~cellfun('isempty', enzInModel));
-0204 rules = model.grRules(fullIdx);
-0205 notMatch = table(idRxns, rxnsNames, evaluatedRule, enzInModel, rules, ...
-0206 'VariableNames',{'rxns', 'name', 'custom enzymes', 'enzymes in model', 'rules'});
-0207 end
-0208 end
function model = applyKcatConstraints(model,updateRxns)
-
-
DESCRIPTION
-
applyKcatConstraints
- Applies kcat-derived enzyme constraints to an ecModel. Existing enzyme
- constraints are first removed (unless updateRxns is provided), and new
- constraints are defined based on the content of model.ec.kcat.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- updateRxns if not all enzyme constraints should be updated, this can
- be given as either a logical vector of length
- model.ec.rxns, a vector of model.ec.rxns indices, or a
- (cell array of) string(s) with model.ec.rxns identifiers.
- For light models, these reactions should match model.rxns.
-
- Output:
- model ecModel where reactions are constrained by enzyme usage
- if a kcat value was provided for the reaction-enzyme pair
- in model.ec.kcat
-
- Usage: model = applyKcatConstraints(model,updateRxns);
0001 function model = applyKcatConstraints(model,updateRxns)
-0002 % applyKcatConstraints
-0003 % Applies kcat-derived enzyme constraints to an ecModel. Existing enzyme
-0004 % constraints are first removed (unless updateRxns is provided), and new
-0005 % constraints are defined based on the content of model.ec.kcat.
-0006 %
-0007 % Input:
-0008 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0009 % updateRxns if not all enzyme constraints should be updated, this can
-0010 % be given as either a logical vector of length
-0011 % model.ec.rxns, a vector of model.ec.rxns indices, or a
-0012 % (cell array of) string(s) with model.ec.rxns identifiers.
-0013 % For light models, these reactions should match model.rxns.
-0014 %
-0015 % Output:
-0016 % model ecModel where reactions are constrained by enzyme usage
-0017 % if a kcat value was provided for the reaction-enzyme pair
-0018 % in model.ec.kcat
-0019 %
-0020 % Usage: model = applyKcatConstraints(model,updateRxns);
-0021
-0022 %these lines are for the nargin lines below only
-0023 if (model.ec.geckoLight)
-0024 rxns = model.rxns;
-0025 else
-0026 rxns = model.ec.rxns;
-0027 end
-0028
-0029 if nargin<2
-0030 updateRxns = true(numel(rxns),1);
-0031 elseif isnumeric(updateRxns)
-0032 updateRxnsLog = false(numel(rxns),1);
-0033 updateRxnsLog(updateRxns) = true;
-0034 updateRxns = updateRxnsLog;
-0035 elseif iscellstr(updateRxns) || ischar(updateRxns) || isstring(updateRxns)
-0036 updateRxnsIds = convertCharArray(updateRxns);
-0037 updateRxns = ismember(rxns,updateRxnsIds);
-0038 end
-0039
-0040 if isempty(find(updateRxns, 1)) || isempty(updateRxns)
-0041 error('No reaction to update or updateRxns is logical but without any true value')
-0042 end
-0043
-0044 if ~isfield(model,'ec')
-0045 error(['No model.ec structure could be found: the provided model is'...
-0046 ' not a valid GECKO3 ecModel. First run makeEcModel(model).'])
-0047 end
-0048 if all(model.ec.kcat==0)
-0049 printOrange('WARNING: No kcat values are provided in model.ec.kcat, model remains unchanged.\n');
-0050 return
-0051 end
-0052
-0053 %Clear existing incorporation of enzyme usage
-0054 if ~model.ec.geckoLight
-0055 protMetIdx = startsWith(model.mets,'prot_') & ~strcmp(model.mets,'prot_pool');
-0056 metabolRxn = unique(model.ec.rxns(updateRxns));
-0057 metabolRxn = ismember(model.rxns,metabolRxn);
-0058 model.S(protMetIdx,metabolRxn) = 0;
-0059 end%no clearing needed for GECKO Light, will be overwritten below
-0060 %For normal GECKO formulation (full model), where each enzyme is explicitly considered
-0061 if ~model.ec.geckoLight
-0062 %Column 1 = rxn idx; 2 = enzyme idx; 3 = subunit copies; 4 = kcat; 5 = MW
-0063 newKcats=zeros(numel(updateRxns)*10,5);
-0064 updateRxns=find(updateRxns);
-0065 kcatFirst=0;
-0066 for i=1:numel(updateRxns)
-0067 j=updateRxns(i);
-0068 if model.ec.kcat(j) ~= 0
-0069 enzymes = find(model.ec.rxnEnzMat(j,:));
-0070 kcatLast = kcatFirst+numel(enzymes);
-0071 kcatFirst = kcatFirst+1;
-0072 newKcats(kcatFirst:kcatLast,1) = j;
-0073 newKcats(kcatFirst:kcatLast,2) = enzymes;
-0074 newKcats(kcatFirst:kcatLast,3) = model.ec.rxnEnzMat(j,enzymes);
-0075 newKcats(kcatFirst:kcatLast,4) = model.ec.kcat(j);
-0076 newKcats(kcatFirst:kcatLast,5) = model.ec.mw(enzymes);
-0077 kcatFirst = kcatLast;
-0078 end
-0079 end
-0080 if exist('kcatLast','var') % If it does not, then no kcats are found
-0081 newKcats(kcatLast+1:end,:)=[];
-0082
-0083 sel = newKcats(:,4) > 0; %Only apply to non-zero kcat
-0084 newKcats(sel,4) = newKcats(sel,4) * 3600; %per second -> per hour
-0085 newKcats(sel,4) = newKcats(sel,5) ./ newKcats(sel,4); %MW/kcat
-0086 newKcats(sel,4) = newKcats(sel,3) .* newKcats(sel,4); %Multicopy subunits.
-0087 newKcats(~sel,4) = 0; %Results in zero-cost
-0088
-0089 %Replace rxns and enzymes with their location in model
-0090 [~,newKcats(:,1)] = ismember(model.ec.rxns(newKcats(:,1)),model.rxns);
-0091 [~,newKcats(:,2)] = ismember(strcat('prot_',model.ec.enzymes(newKcats(:,2))),model.mets);
-0092 linearIndices = sub2ind(size(model.S),newKcats(:,2),newKcats(:,1));
-0093 model.S(linearIndices) = -newKcats(:,4); %Substrate = negative
-0094 end
-0095 else%GECKO light formulation, where prot_pool represents all usages
-0096 prot_pool_idx = find(ismember(model.mets,'prot_pool'));
-0097 %first remove the prefix of all rxns
-0098 modRxns = extractAfter(model.ec.rxns,4);
-0099 % Map ec-reactions to model.rxns
-0100 [hasEc,~] = ismember(model.rxns,modRxns);
-0101 hasEc = find(hasEc & updateRxns);
-0102 [~,rxnIdx] = ismember(modRxns,model.rxns);
-0103 for i = 1:numel(hasEc)
-0104 % Get all isozymes per reaction
-0105 ecIdx = find(rxnIdx == hasEc(i));
-0106 % ecIdx = strcmp(model.rxns(hasEc(i)),modRxns);
-0107 % Multiply enzymes with their MW (they are then automatically
-0108 % summed per reaction), and divide by their kcat, to get a vector
-0109 % of MW/kcat values.
-0110 MWkcat = (model.ec.rxnEnzMat(ecIdx,:) * model.ec.mw) ./ model.ec.kcat(ecIdx);
-0111 % If kcat was zero, MWkcat is Inf. If no enzyme info was found,
-0112 % MWkcat is NaN. Correct both back to zero
-0113 MWkcat(isinf(MWkcat) | isnan(MWkcat)) = 0;
-0114 % Select the lowest MW/kcat (= most efficient), and convert to /hour
-0115 model.S(prot_pool_idx, hasEc(i)) = -min(MWkcat/3600);
-0116 end
-0117 end
-0118 end
function [model,noSMILES] = findMetSmiles(model, modelAdapter, verbose)
-
-
DESCRIPTION
-
findMetSMILES
- Queries PubChem by metabolite names to obtain SMILES. Matches will also
- be stored in tutorials/***/data/smilesDB.tsv, that will also be queried
- first next time the function is run. If the model already has a
- metSmiles field, then non-empty entries will not be overwritten.
-
- Input:
- model a model whose metNames field is used to find the relevant SMILES
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
- verbose logical whether progress should be reported (Optional,
- default true)
- Ouput:
- model model with model.metSmiles specified.
- noSMILES metabolite names for which no SMILES could be found.
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function [model,noSMILES] = findMetSmiles(model, modelAdapter, verbose)
-0002 % findMetSMILES
-0003 % Queries PubChem by metabolite names to obtain SMILES. Matches will also
-0004 % be stored in tutorials/***/data/smilesDB.tsv, that will also be queried
-0005 % first next time the function is run. If the model already has a
-0006 % metSmiles field, then non-empty entries will not be overwritten.
-0007 %
-0008 % Input:
-0009 % model a model whose metNames field is used to find the relevant SMILES
-0010 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0011 % default model adapter).
-0012 % verbose logical whether progress should be reported (Optional,
-0013 % default true)
-0014 % Ouput:
-0015 % model model with model.metSmiles specified.
-0016 % noSMILES metabolite names for which no SMILES could be found.
-0017 %
-0018 if nargin < 3 || isempty(verbose)
-0019 verbose = true;
-0020 end
-0021 if nargin < 2 || isempty(modelAdapter)
-0022 modelAdapter = ModelAdapterManager.getDefault();
-0023 if isempty(modelAdapter)
-0024 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0025 end
-0026 end
-0027 params = modelAdapter.params;
-0028
-0029 [uniqueNames, ~, uniqueIdx] = unique(regexprep(model.metNames,'^prot_.*',''));
-0030 uniqueSmiles(1:numel(uniqueNames),1) = {''};
-0031 metMatch = false(length(uniqueNames),1);
-0032 metMatch(strcmp(uniqueNames,'')) = 1; % No need trying to match empty fields
-0033 if verbose; fprintf('Check for local SMILES database... '); end
-0034 smilesDBfile = (fullfile(params.path,'data','smilesDB.tsv'));
-0035 if exist(smilesDBfile,'file')==2
-0036 fID = fopen(smilesDBfile,'r');
-0037 raw = textscan(fID,'%s %s','Delimiter','\t','HeaderLines',0);
-0038 fclose(fID);
-0039 smilesDB.names = raw{1};
-0040 smilesDB.smile = raw{2};
-0041 [metMatch, metIdx] = ismember(uniqueNames,smilesDB.names);
-0042 uniqueSmiles(metMatch) = smilesDB.smile(metIdx(metMatch));
-0043 if verbose; fprintf('done.\n'); end
-0044 else
-0045 if verbose; fprintf('not found.\n'); end
-0046 end
-0047
-0048 if any(~metMatch)
-0049 progressbar('Querying PubChem for SMILES by metabolite names')
-0050 webOptions = weboptions('Timeout', 30);
-0051 for i = 1:numel(uniqueNames)
-0052 if metMatch(i)
-0053 continue;
-0054 end
-0055 retry = 0;
-0056 while retry < 10
-0057 try
-0058 smileResult = webread(['https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/' uniqueNames{i} '/property/CanonicalSMILES/TXT'], webOptions);
-0059 %Sometimes multiple lines are given, with alternative SMILES. Only
-0060 %keep the first suggestion.
-0061 smileResult = regexp(smileResult,'(^\S*)\n','once','tokens');
-0062 uniqueSmiles{i,1} = smileResult{1,1};
-0063 retry = 15; % success: no retry
-0064 catch exception
-0065 %Sometimes the call fails, for example since the server is busy. In those cases
-0066 %we will try 10 times. Some errors however are because the metabolite
-0067 %name does no exist in the database (404) or some other error (the metabolite contains
-0068 %a slash or similar, 400 or 500). In those cases we can
-0069 %immediately give up.
-0070 if (strcmp(exception.identifier, 'MATLAB:webservices:HTTP404StatusCodeError') || ...
-0071 strcmp(exception.identifier, 'MATLAB:webservices:HTTP400StatusCodeError') || ...
-0072 strcmp(exception.identifier, 'MATLAB:webservices:HTTP500StatusCodeError'))
-0073 uniqueSmiles(i) = {''};
-0074 retry = 15;
-0075 else
-0076 retry = retry + 1;
-0077 end
-0078 end
-0079 if retry == 10
-0080 error('Cannot reach PubChem. Check your internet connection and try again.')
-0081 end
-0082 end
-0083 % Append one line each time, in case internet connection is lost
-0084 % halfway. Open & close file each time to avoid leaving the file
-0085 % open when breaking the function.
-0086 out = [uniqueNames(i), uniqueSmiles(i)];
-0087 fID = fopen(smilesDBfile,'a');
-0088 fprintf(fID,'%s\t%s\n',out{:});
-0089 fclose(fID);
-0090 progressbar(i/numel(uniqueNames))
-0091 end
-0092 if verbose
-0093 fprintf('Model-specific SMILES database stored at %s\n',smilesDBfile);
-0094 end
-0095 end
-0096 newSmiles = uniqueSmiles(uniqueIdx);
-0097 noSMILES = cellfun(@isempty,uniqueSmiles);
-0098 successRatio = 1-(numel(find(noSMILES))/numel(uniqueSmiles));
-0099 fprintf('SMILES could be found for %s%% of the unique metabolite names.\n',num2str(successRatio*100,'%.0f'))
-0100 noSMILES = uniqueNames(noSMILES);
-0101
-0102 if ~isfield(model,'metSmiles') || all(cellfun(@isempty,model.metSmiles))
-0103 model.metSmiles = newSmiles;
-0104 else
-0105 emptySmiles = cellfun(@isempty,model.metSmiles);
-0106 model.metSmiles(emptySmiles) = newSmiles(emptySmiles);
-0107 end
-0108 progressbar(1) % Make sure it closes
-0109 end
function complexInfo = getComplexData(taxonomicID, modelAdapter)
-
-
DESCRIPTION
-
getComplexData
- Download curated complex stochiometries from the EMBL-EBI Complex
- Portal database. Writes data/ComplexPortal.json in the obj.params.path
- specified in the model adapter.
-
- Input:
- taxonomicID taxonomic identifier for which complex data should be
- downloaded. Only taxonomic identifiers allowed are
- those included on Complex Portal:
- https://www.ebi.ac.uk/complexportal/complex/organisms
- If empty, no complex data is downloaded, if 0 (zero),
- complex data from all organisms in Complex Portal is
- downloaded.
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
- Output:
- complexInfo structure with data downloaded from Complex Portal.
- Contains the following fields:
- - complexID: id of the complex on Complex Portal
- - name: name of the complex on Complex Portal
- - species: organism containing the complex
- - geneName: names of the genes in the complex
- - protID: Uniprot IDs of the proteins in the complex
- - stochiometry: the complex stochiometry given in the same
- order as the genes and proteins in geneName and protID
- - defined: 0 if Complex Portal has no defined stochiometry
- 1 if defined subunit stochiometry is given
- 2 if complex consists of sub-complexes, whose
- subunit stochiometries are given
- Usage
- complexInfo = getComplexData(organism, modelAdapter);
0001 function complexInfo = getComplexData(taxonomicID, modelAdapter)
-0002 % getComplexData
-0003 % Download curated complex stochiometries from the EMBL-EBI Complex
-0004 % Portal database. Writes data/ComplexPortal.json in the obj.params.path
-0005 % specified in the model adapter.
-0006 %
-0007 % Input:
-0008 % taxonomicID taxonomic identifier for which complex data should be
-0009 % downloaded. Only taxonomic identifiers allowed are
-0010 % those included on Complex Portal:
-0011 % https://www.ebi.ac.uk/complexportal/complex/organisms
-0012 % If empty, no complex data is downloaded, if 0 (zero),
-0013 % complex data from all organisms in Complex Portal is
-0014 % downloaded.
-0015 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0016 % default model adapter).
-0017 % Output:
-0018 % complexInfo structure with data downloaded from Complex Portal.
-0019 % Contains the following fields:
-0020 % - complexID: id of the complex on Complex Portal
-0021 % - name: name of the complex on Complex Portal
-0022 % - species: organism containing the complex
-0023 % - geneName: names of the genes in the complex
-0024 % - protID: Uniprot IDs of the proteins in the complex
-0025 % - stochiometry: the complex stochiometry given in the same
-0026 % order as the genes and proteins in geneName and protID
-0027 % - defined: 0 if Complex Portal has no defined stochiometry
-0028 % 1 if defined subunit stochiometry is given
-0029 % 2 if complex consists of sub-complexes, whose
-0030 % subunit stochiometries are given
-0031 % Usage
-0032 % complexInfo = getComplexData(organism, modelAdapter);
-0033
-0034 if nargin < 2 || isempty(modelAdapter)
-0035 modelAdapter = ModelAdapterManager.getDefault();
-0036 if isempty(modelAdapter)
-0037 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0038 end
-0039 end
-0040
-0041 if nargin<1 || isempty(taxonomicID)
-0042 taxonomicID = modelAdapter.getParameters().complex.taxonomicID;
-0043 end
-0044
-0045 params = modelAdapter.params;
-0046 if isempty(taxonomicID) % Can be empty when gathered from model adapter
-0047 printOrange('WARNING: No taxonomicID specified.');
-0048 return
-0049 elseif taxonomicID == 0
-0050 taxonomicID = [];
-0051 end
-0052
-0053 webOptions = weboptions('Timeout', 30);
-0054 try
-0055 url1 = 'https://www.ebi.ac.uk/intact/complex-ws/search/*';
-0056 if ~isempty(taxonomicID)
-0057 url1 = [url1 '?facets=species&filters=species:("' num2str(taxonomicID) '")'];
-0058 end
-0059 data = webread(url1,webOptions);
-0060 catch ME
-0061 if (strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError'))
-0062 error('Cannot connect to the Complex Portal, perhaps the server is not responding');
-0063 end
-0064 end
-0065 if data.size == 0
-0066 error('No data could be gathered from Complex Portal for the specified taxonomicID.')
-0067 end
-0068 complexData = cell(data.size,7);
-0069
-0070 progressbar('Retrieving information for complexes');
-0071 for i = 1:data.size
-0072 progressbar(i/data.size) % Update progress bar
-0073 url2 = 'https://www.ebi.ac.uk/intact/complex-ws/complex/';
-0074 complexID = data.elements(i,1).complexAC;
-0075 try
-0076 temp = webread([url2 complexID],webOptions);
-0077 catch ME
-0078 if (strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError'))
-0079 printOrange(['WARNING: Cannot retrieve the information for ' complexID '.\n']);
-0080 end
-0081 temp = [];
-0082 end
-0083
-0084 if ~isempty(temp)
-0085 complexData(i,1) = {temp.complexAc};
-0086 complexData(i,2) = {temp.name};
-0087 complexData(i,3) = {temp.species};
-0088
-0089 idxIntType = find(strcmpi({temp.participants.interactorType}, 'protein'));
-0090
-0091 % Some complex reported are 'stable complex', then, save the id
-0092 % complex and but set the genes and protein to a empty string.
-0093 if numel(idxIntType) > 0
-0094 complexData(i,4) = {{temp.participants(idxIntType).name}};
-0095 complexData(i,5) = {{temp.participants(idxIntType).identifier}};
-0096 else
-0097 complexData(i,4) = {{temp.participants.name}};
-0098 complexData(i,5) = {{temp.participants.identifier}};
-0099 end
-0100
-0101 % Portal complex has two stochiometry values, a minimum and
-0102 % maximum value. Only minimum will be store. In some cases,
-0103 % some complex does not have stochiometry coefficient, then, it
-0104 % will be fill with zeros
-0105 if ~cellfun('isempty',{temp.participants.stochiometry})
-0106 % For some reason, if there is only one protein in the complex
-0107 % split function does nor return a cell nx2, instead is 2x1,
-0108 % then assign an incorrect stochiometry
-0109 switch numel(idxIntType)
-0110 case 0 % Contains complexes
-0111 stochiometry = split({temp.participants.stochiometry}.', ',');
-0112 complexData(i,7) = {2};
-0113 case 1 % Contains one protein
-0114 stochiometry = split({temp.participants(idxIntType).stochiometry}.', ',').';
-0115 complexData(i,7) = {1};
-0116 otherwise
-0117 stochiometry = split({temp.participants(idxIntType).stochiometry}.', ',');
-0118 complexData(i,7) = {1};
-0119 end
-0120 values = str2double(erase(stochiometry(:,1),"minValue: ")).';
-0121 complexData(i,6) = {values};
-0122 else
-0123 complexData(i,6) = {repelem(0,numel(complexData{i,4}))};
-0124 complexData(i,7) = {0};
-0125 end
-0126 end
-0127 end
-0128 fprintf('\n');
-0129
-0130 % Expand complexes of complexes
-0131 complexComplex = find([complexData{:,7}]==2);
-0132 if ~isempty(complexComplex)
-0133 for i=1:numel(complexComplex)
-0134 subComplex = complexData{complexComplex(i),5};
-0135 subComplexS = complexData{complexComplex(i),6};
-0136 subComplexIdx = find(ismember(complexData(:,1),subComplex));
-0137 allGenes = horzcat(complexData{subComplexIdx,4});
-0138 allProts = horzcat(complexData{subComplexIdx,5});
-0139 allStoch = {complexData{subComplexIdx,6}};
-0140 for j=1:numel(subComplex)
-0141 allStoch{j}=allStoch{j}*subComplexS(j);
-0142 end
-0143 allStoch = horzcat(allStoch{:});
-0144 [allGenes,ia,ic] = unique(allGenes,'stable');
-0145 allProts = allProts(ia);
-0146 allStoch = splitapply(@sum, allStoch', ic);
-0147 complexData{complexComplex(i),4} = allGenes;
-0148 complexData{complexComplex(i),5} = allProts;
-0149 complexData{complexComplex(i),6} = allStoch;
-0150 end
-0151 end
-0152
-0153 rowHeadings = {'complexID','name','species','geneName','protID','stochiometry','defined'};
-0154
-0155 complexInfo = cell2struct(complexData, rowHeadings, 2);
-0156
-0157 % Convert to a JSON file
-0158 jsontxt = jsonencode(cell2table(complexData, 'VariableNames', rowHeadings));
-0159 % Write to a JSON file
-0160 fid = fopen(fullfile(params.path,'data','ComplexPortal.json'), 'w');
-0161 fprintf(fid, '%s', jsontxt);
-0162 fclose(fid);
-0163 fprintf('Model-specific ComplexPortal database stored at %s\n',fullfile(params.path,'data','ComplexPortal.json'));
-0164 end
getKcatAcrossIsozymes
- For reactions without kcat value (0 in model.ec.kcat), isozymes are
- found (being based on the same reaction in the conventional GEM), that
- do have a kcat value assigned. The mean kcat value of these isozymes
- is then used to fill in model.ec.kcat.
-
- Input:
- model an ecModel in full GECKO 3 format (with ecModel.ec structure),
- not GECKO light
-
- Output:
- model an ecModel with kcat values assigned to isozymes in model.ec.kcat
-
- Usage: model = getKcatAcrossIsozymes(model);
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function model = getKcatAcrossIsozymes(model)
-0002 % getKcatAcrossIsozymes
-0003 % For reactions without kcat value (0 in model.ec.kcat), isozymes are
-0004 % found (being based on the same reaction in the conventional GEM), that
-0005 % do have a kcat value assigned. The mean kcat value of these isozymes
-0006 % is then used to fill in model.ec.kcat.
-0007 %
-0008 % Input:
-0009 % model an ecModel in full GECKO 3 format (with ecModel.ec structure),
-0010 % not GECKO light
-0011 %
-0012 % Output:
-0013 % model an ecModel with kcat values assigned to isozymes in model.ec.kcat
-0014 %
-0015 % Usage: model = getKcatAcrossIsozymes(model);
-0016
-0017 if model.ec.geckoLight
-0018 error('Provided model is a GECKO light version, this function is not relevant for such models')
-0019 end
-0020 if all(model.ec.kcat==0)
-0021 printOrange('WARNING: No kcat values are provided in model.ec.kcat, model remains unchanged.\n');
-0022 return
-0023 end
-0024
-0025 noKcats = model.ec.kcat==0;
-0026 rxnIDs = regexprep(model.ec.rxns,'_EXP_\d+','');
-0027 noKcatID = rxnIDs(noKcats);
-0028 yesKcatID = rxnIDs(~noKcats);
-0029 yesKcatVal = model.ec.kcat(~noKcats);
-0030
-0031 noKcatVal = cellfun(@(x) strcmp(x, yesKcatID), noKcatID, 'UniformOutput', false);
-0032 noKcatVal = cell2mat(cellfun(@(x) mean(yesKcatVal(x)), noKcatVal, 'UniformOutput', false));
-0033
-0034 newKcat = find(~isnan(noKcatVal));
-0035 newKcatIdx = find(noKcats);
-0036 newKcatIdx = newKcatIdx(newKcat);
-0037 newKcat = noKcatVal(newKcat);
-0038
-0039 model.ec.kcat(newKcatIdx) = newKcat;
-0040 model.ec.source(newKcatIdx) = {'isozymes'};
-0041 end
-0042
function [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel,proteinId)
-
-
DESCRIPTION
-
getReactionsFromEnzyme
- Get all reactions that are annotated to a particular enzyme.
-
- Input:
- ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
- proteinId protein identifier, matching ecModel.ec.enzymes.
-
- Output:
- rxns reactions that are associated with this enzyme
- kcat kcat values of the corresponding reactions
- idx index of the reactions in ecModel.ec.rxns
- rxnNames names of the reactions
- grRules grRules of the reactions
-
- Usage: [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel,proteinId)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel,proteinId)
-0002 % getReactionsFromEnzyme
-0003 % Get all reactions that are annotated to a particular enzyme.
-0004 %
-0005 % Input:
-0006 % ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
-0007 % proteinId protein identifier, matching ecModel.ec.enzymes.
-0008 %
-0009 % Output:
-0010 % rxns reactions that are associated with this enzyme
-0011 % kcat kcat values of the corresponding reactions
-0012 % idx index of the reactions in ecModel.ec.rxns
-0013 % rxnNames names of the reactions
-0014 % grRules grRules of the reactions
-0015 %
-0016 % Usage: [rxns, kcat, idx, rxnNames, grRules] = getReactionsFromEnzyme(ecModel,proteinId)
-0017
-0018 protIdx = find(strcmpi(ecModel.ec.enzymes,proteinId));
-0019 ecRxnIdx = find(ecModel.ec.rxnEnzMat(:,protIdx));
-0020 rxns = ecModel.ec.rxns(ecRxnIdx);
-0021 kcat = ecModel.ec.kcat(ecRxnIdx);
-0022
-0023 idx = ecRxnIdx;
-0024
-0025 [~,rxnIdx] = ismember(rxns,ecModel.rxns);
-0026 rxnNames = ecModel.rxnNames(rxnIdx);
-0027 grRules = ecModel.grRules(rxnIdx);
-0028 end
function [model, noUniprot] = makeEcModel(model, geckoLight, modelAdapter)
-
-
DESCRIPTION
-
makeEcModel
- Expands a conventional genome-scale model (in RAVEN format) with enzyme
- information and prepares the reactions for integration of enzyme usage
- coefficients. This function contains all the steps that need to be done
- to get a basic ecModel, without incorporating any kcat values or
- constraints yet. This function should only have to be run once for a
- model.
-
- Input:
- model a model in RAVEN format
- geckoLight true if a simplified GECKO light model should be generated.
- (Optional, default is false).
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Ouput:
- model an ecModel in GECKO 3 format, with a model.ec structure where
- enzyme and kcat information are stored. Protein pseudo-
- metabolites and their draw reactions are added to the model,
- but their usage is not yet implemented (due to absent kcat
- values at this stage).
- noUniprot genes for which no information could be found in the
- Uniprot database
-
- The function goes through the following steps:
- 1. Remove gene associations from pseudoreactions.
- 2. Invert irreversible backwards reactions.
- 3. Correct 'rev' vector to match lb and ub vectors.
- 4. Convert to irreversible model (splits reversible reactions).
- 5. [Skipped with geckoLight:] Expand model to split reactions with
- 'OR' in grRules (each reaction is then catalyzed by one enzyme
- (complex).
- 6. Make empty model.ec structure, that will contain enzyme and kcat
- information. One entry per reaction, where isozymes have multiple
- entries. This model.ec structure will later be populated with kcat
- values. For geckoLight the structure is different, where each
- reaction can have multiple isozymes.
- 7. Add enzyme information fields to model.ec structure: MW, sequence.
- 8. Populate model.ec structure with information from each reaction.
- 9. [Skipped with geckoLight:] Add proteins as pseudometabolites.
- 10. Add prot_pool pseudometabolite.
- 11. [Skipped with geckoLight:] Add usage reactions for the protein
- pseudometabolites, replenishing from the protein pool (default, can
- be changed to consider proteomics data at later stage)
- 12. Add protein pool reaction, without upper bound.
-
- Note that while protein pseudometabolites, draw & pool reactions might
- be added to the model, the enzyme usage is not yet incorporated in each
- metabolic reaction, so enzymes will not be used. applyKcatConstraints
- incorporates protein pseudometabolites in reactions as enzyme usages by
- applying the specified kcats as constraints.
-
-The EC structure looks as follows
- Attributes:
- geckoLight: 0 if full model, 1 if light
- rxns: reaction identifiers that correspond to model.rxns
- kcat: kcat values - not set here
- source: specifies where the kcats come from - not set here
- notes: notes that can be set by downstream functions - not set
- here
- eccodes: enzyme codes for each enzyme - not set here
- genes: the genes involved in the kcats - not necessarily the
- same as model.genes, since some genes may not be found in
- databases etc.
- enzymes: Uniprot protein identifiers for the genes
- mw: molecular weights of the enzymes
- sequence: sequence of the genes/enzymes
- concs: concentrations of the enzymes - not set here
- rxnEnzMat: matrix of enzymes and rxns
-
- The full model is split on all ORs in the GPRs, meaning that the
- reactions will be duplicated for each isozyme. Only the rxns with genes
- are added. The fields rxns, eccodes, kcat, source and notes will
- therefore have one entry per reaction. The fields genes, enzymes, mw,
- sequence and concs will have one entry per gene. The rxnEnzMat is a
- matrix with reactions and genes, mapping which genes are connected to
- which reaction (where isozymes have different reactions).
-
- The light model works a bit differently. The model has the same number of
- rxns as the original model, but expanded since it is reversible + one the
- extra prot maintenance rxn and one extra prot_pool rxn. However, the ec
- fields rxns, eccodes, kcat, source and notes are duplicated for each
- isozyme, sorted the same way as model.rxns. So, in model.ec.rxns, the
- same reaction will appear several times after one another, one entry per
- izozyme, with corresponding values for that isozyme. These fields
- therefore have the same length as for the full model. The fields genes,
- enzymes, mw, sequence and concs are the same here as in the full model.
- The rxnEnzMat maps the model.ec.rxns entries to genes and is therefore of
- the same size as for the full model.
-
- Usage:
- [model, noUniprot] = makeEcModel(model, geckoLight, modelAdapter)
0001 function [model, noUniprot] = makeEcModel(model, geckoLight, modelAdapter)
-0002 % makeEcModel
-0003 % Expands a conventional genome-scale model (in RAVEN format) with enzyme
-0004 % information and prepares the reactions for integration of enzyme usage
-0005 % coefficients. This function contains all the steps that need to be done
-0006 % to get a basic ecModel, without incorporating any kcat values or
-0007 % constraints yet. This function should only have to be run once for a
-0008 % model.
-0009 %
-0010 % Input:
-0011 % model a model in RAVEN format
-0012 % geckoLight true if a simplified GECKO light model should be generated.
-0013 % (Optional, default is false).
-0014 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0015 % default model adapter).
-0016 %
-0017 % Ouput:
-0018 % model an ecModel in GECKO 3 format, with a model.ec structure where
-0019 % enzyme and kcat information are stored. Protein pseudo-
-0020 % metabolites and their draw reactions are added to the model,
-0021 % but their usage is not yet implemented (due to absent kcat
-0022 % values at this stage).
-0023 % noUniprot genes for which no information could be found in the
-0024 % Uniprot database
-0025 %
-0026 % The function goes through the following steps:
-0027 % 1. Remove gene associations from pseudoreactions.
-0028 % 2. Invert irreversible backwards reactions.
-0029 % 3. Correct 'rev' vector to match lb and ub vectors.
-0030 % 4. Convert to irreversible model (splits reversible reactions).
-0031 % 5. [Skipped with geckoLight:] Expand model to split reactions with
-0032 % 'OR' in grRules (each reaction is then catalyzed by one enzyme
-0033 % (complex).
-0034 % 6. Make empty model.ec structure, that will contain enzyme and kcat
-0035 % information. One entry per reaction, where isozymes have multiple
-0036 % entries. This model.ec structure will later be populated with kcat
-0037 % values. For geckoLight the structure is different, where each
-0038 % reaction can have multiple isozymes.
-0039 % 7. Add enzyme information fields to model.ec structure: MW, sequence.
-0040 % 8. Populate model.ec structure with information from each reaction.
-0041 % 9. [Skipped with geckoLight:] Add proteins as pseudometabolites.
-0042 % 10. Add prot_pool pseudometabolite.
-0043 % 11. [Skipped with geckoLight:] Add usage reactions for the protein
-0044 % pseudometabolites, replenishing from the protein pool (default, can
-0045 % be changed to consider proteomics data at later stage)
-0046 % 12. Add protein pool reaction, without upper bound.
-0047 %
-0048 % Note that while protein pseudometabolites, draw & pool reactions might
-0049 % be added to the model, the enzyme usage is not yet incorporated in each
-0050 % metabolic reaction, so enzymes will not be used. applyKcatConstraints
-0051 % incorporates protein pseudometabolites in reactions as enzyme usages by
-0052 % applying the specified kcats as constraints.
-0053 %
-0054 %The EC structure looks as follows
-0055 % Attributes:
-0056 % geckoLight: 0 if full model, 1 if light
-0057 % rxns: reaction identifiers that correspond to model.rxns
-0058 % kcat: kcat values - not set here
-0059 % source: specifies where the kcats come from - not set here
-0060 % notes: notes that can be set by downstream functions - not set
-0061 % here
-0062 % eccodes: enzyme codes for each enzyme - not set here
-0063 % genes: the genes involved in the kcats - not necessarily the
-0064 % same as model.genes, since some genes may not be found in
-0065 % databases etc.
-0066 % enzymes: Uniprot protein identifiers for the genes
-0067 % mw: molecular weights of the enzymes
-0068 % sequence: sequence of the genes/enzymes
-0069 % concs: concentrations of the enzymes - not set here
-0070 % rxnEnzMat: matrix of enzymes and rxns
-0071 %
-0072 % The full model is split on all ORs in the GPRs, meaning that the
-0073 % reactions will be duplicated for each isozyme. Only the rxns with genes
-0074 % are added. The fields rxns, eccodes, kcat, source and notes will
-0075 % therefore have one entry per reaction. The fields genes, enzymes, mw,
-0076 % sequence and concs will have one entry per gene. The rxnEnzMat is a
-0077 % matrix with reactions and genes, mapping which genes are connected to
-0078 % which reaction (where isozymes have different reactions).
-0079 %
-0080 % The light model works a bit differently. The model has the same number of
-0081 % rxns as the original model, but expanded since it is reversible + one the
-0082 % extra prot maintenance rxn and one extra prot_pool rxn. However, the ec
-0083 % fields rxns, eccodes, kcat, source and notes are duplicated for each
-0084 % isozyme, sorted the same way as model.rxns. So, in model.ec.rxns, the
-0085 % same reaction will appear several times after one another, one entry per
-0086 % izozyme, with corresponding values for that isozyme. These fields
-0087 % therefore have the same length as for the full model. The fields genes,
-0088 % enzymes, mw, sequence and concs are the same here as in the full model.
-0089 % The rxnEnzMat maps the model.ec.rxns entries to genes and is therefore of
-0090 % the same size as for the full model.
-0091 %
-0092 % Usage:
-0093 % [model, noUniprot] = makeEcModel(model, geckoLight, modelAdapter)
-0094
-0095 if nargin<2
-0096 geckoLight=false;
-0097 elseif ~islogical(geckoLight) && ~(geckoLight == 0) && ~(geckoLight == 1)
-0098 error('geckoLight should be either true or false')
-0099 end
-0100
-0101 if nargin < 3 || isempty(modelAdapter)
-0102 modelAdapter = ModelAdapterManager.getDefault();
-0103 if isempty(modelAdapter)
-0104 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0105 end
-0106 end
-0107 params = modelAdapter.getParameters();
-0108 compartmentID = strcmp(model.compNames,params.enzyme_comp);
-0109 if ~any(compartmentID)
-0110 error(['Compartment ' params.enzyme_comp ' (specified in params.enzyme_comp) '...
-0111 'cannot be found in model.compNames'])
-0112 end
-0113 compartmentID = model.comps(compartmentID);
-0114
-0115 if geckoLight
-0116 ec.geckoLight=true;
-0117 else
-0118 ec.geckoLight=false;
-0119 end
-0120
-0121 %Check if model is in RAVEN format
-0122 if any(isfield(model,{'rules','modelID'}))
-0123 error(['The model is likely loaded using COBRA Toolbox readCbModel(). Instead, use '...
-0124 'RAVEN Toolbox importModel(). Alternatively, you can also convert the ', ...
-0125 'model in MATLAB using ravenCobraWrapper().'])
-0126 end
-0127
-0128 %Check for conflicting reaction and metabolite identifiers
-0129 conflictId = startsWith(model.mets,'prot_');
-0130 if any(conflictId)
-0131 error('The identifiers in model.mets are not allowed to start with ''prot_''.')
-0132 end
-0133 conflictId = startsWith(model.rxns,{'usage_prot_','prot_pool'}) | ...
-0134 endsWith(model.rxns,'_REV') | ...
-0135 ~cellfun(@isempty,regexp(model.rxns,'_EXP_\d+$'));
-0136 if any(conflictId)
-0137 error(['The identifiers in model.rxns are not allowed to start with ''usage_prot'' '...
-0138 'or ''prot_pool'', or end with ''_REV'' or ''_EXP_[digit]''.'])
-0139 end
-0140
-0141 uniprotDB = loadDatabases('both', modelAdapter);
-0142 uniprotDB = uniprotDB.uniprot;
-0143
-0144 %1: Remove gene rules from pseudoreactions (if any):
-0145 if exist(fullfile(params.path,'data','pseudoRxns.tsv'),'file')
-0146 fID = fopen(fullfile(params.path,'data','pseudoRxns.tsv'));
-0147 fileData = textscan(fID,'%s %s','delimiter','\t');
-0148 fclose(fID);
-0149 pseudoRxns = fileData{1};
-0150 pseudoRxns = ismember(model.rxns,pseudoRxns);
-0151 else
-0152 pseudoRxns = false(numel(model.rxns),1);
-0153 end
-0154 pseudoRxns = find(pseudoRxns | contains(model.rxnNames,'pseudoreaction'));
-0155 model.grRules(pseudoRxns) = {''};
-0156 model.rxnGeneMat(pseudoRxns,:) = zeros(numel(pseudoRxns), numel(model.genes));
-0157
-0158 %2: Swap direction of reactions that are defined to only carry negative flux
-0159 to_swap=model.lb < 0 & model.ub == 0;
-0160 model.S(:,to_swap)=-model.S(:,to_swap);
-0161 model.ub(to_swap)=-model.lb(to_swap);
-0162 model.lb(to_swap)=0;
-0163
-0164 %3: Correct rev vector: true if LB < 0 & UB > 0, or it is an exchange reaction:
-0165 model.rev = false(size(model.rxns));
-0166 for i = 1:length(model.rxns)
-0167 if (model.lb(i) < 0 && model.ub(i) > 0) || sum(model.S(:,i) ~= 0) == 1
-0168 model.rev(i) = true;
-0169 end
-0170 end
-0171
-0172 %4: Make irreversible model (appends _REV to reaction IDs to indicate reverse
-0173 %reactions)
-0174 [~,exchRxns] = getExchangeRxns(model);
-0175 nonExchRxns = model.rxns;
-0176 nonExchRxns(exchRxns) = [];
-0177 model=convertToIrrev(model, nonExchRxns);
-0178
-0179 %5: Expand model, to separate isozymes (appends _EXP_* to reaction IDs to
-0180 %indicate duplication)
-0181 if ~geckoLight
-0182 model=expandModel(model);
-0183 end
-0184 % Sort reactions, so that reversible and isozymic reactions are kept near
-0185 if ~geckoLight
-0186 model=sortIdentifiers(model);
-0187 end
-0188
-0189 %6: Make ec-extension structure, one for gene-associated reaction.
-0190 % The structure is different for light and full models
-0191 rxnWithGene = find(sum(model.rxnGeneMat,2));
-0192 if ~geckoLight
-0193 ec.rxns = model.rxns(rxnWithGene);
-0194 emptyCell = cell(numel(rxnWithGene),1);
-0195 emptyCell(:) = {''};
-0196 emptyVect = zeros(numel(rxnWithGene),1);
-0197
-0198 ec.kcat = emptyVect;
-0199 ec.source = emptyCell; % Strings, like 'dlkcat', 'manual', 'brenda', etc.
-0200 ec.notes = emptyCell; % Additional comments
-0201 ec.eccodes = emptyCell;
-0202 ec.concs = emptyVect;
-0203 else
-0204 %Different strategy for GECKO light: Each reaction can exist multiple times in
-0205 %ec.rxns and similar fields - one time per isozyme. The number of copies is
-0206 %the number of ORs in the GPR + 1
-0207 numOrs = count(model.grRules(rxnWithGene), ' or ');
-0208 cpys = numOrs + 1;
-0209 prevNumRxns = length(numOrs);
-0210 cpyIndices = repelem(rxnWithGene, cpys).'; %.' only matter when number of rxns is 1
-0211 %loop through and add a prefix with an isozyme index to the rxns
-0212 %we just give a fixed-length number as prefix, and assume that 999 is enough
-0213 tmpRxns = model.rxns(cpyIndices); %now they have no prefix
-0214 newRxns = tmpRxns;
-0215
-0216 %add the prefix
-0217 nextIndex = 1;
-0218 for i = 1:numel(model.rxns)
-0219 localRxnIndex = 1;
-0220 if nextIndex <= length(tmpRxns) && strcmp(model.rxns(i), tmpRxns(nextIndex))
-0221 while true
-0222 tmp = compose('%03d_',localRxnIndex);
-0223 newRxns{nextIndex} = [tmp{1} tmpRxns{nextIndex}];
-0224 localRxnIndex = localRxnIndex + 1;
-0225 if (localRxnIndex >= 1000)
-0226 error('Increase index size to 10000 - error in the code.'); %this should never happen, we don't have > 999 isozymes
-0227 end
-0228 nextIndex = nextIndex + 1;
-0229 if nextIndex > length(tmpRxns) || ~strcmp(model.rxns(i), tmpRxns(nextIndex))
-0230 break;
-0231 end
-0232 end
-0233 end
-0234 end
-0235
-0236 ec.rxns = newRxns;
-0237
-0238 emptyCell = cell(numel(ec.rxns),1);
-0239 emptyCell(:) = {''};
-0240 emptyVect = zeros(numel(ec.rxns),1);
-0241
-0242 ec.kcat = emptyVect;
-0243 ec.source = emptyCell; % Strings, like 'dlkcat', 'manual', 'brenda', etc.
-0244 ec.notes = emptyCell; % Additional comments
-0245 ec.eccodes = emptyCell;
-0246 ec.concs = emptyVect;
-0247 end
-0248
-0249 %7: Gather enzyme information via UniprotDB
-0250 uniprotCompatibleGenes = modelAdapter.getUniprotCompatibleGenes(model.genes);
-0251 [Lia,Locb] = ismember(uniprotCompatibleGenes,uniprotDB.genes);
-0252
-0253 uniprot = modelAdapter.getUniprotIDsFromTable(uniprotCompatibleGenes);
-0254 if ~isequal(uniprot,uniprotCompatibleGenes)
-0255 uniprot(cellfun(@isempty,uniprot)) = {''};
-0256 [Lia,Locb] = ismember(uniprot,uniprotDB.ID);
-0257 end
-0258 noUniprot = uniprotCompatibleGenes(~Lia);
-0259 if all(~Lia)
-0260 error('None of the proteins in uniprot.tsv match the genes in the model. Changes to the obj.params.uniprot parameters, or a data/uniprotConversion.tsv file are likely required.')
-0261 elseif ~isempty(noUniprot)
-0262 printOrange(['WARNING: The ' num2str(numel(noUniprot)) ' gene(s) reported '...
-0263 'in "noUniprot" cannot be found in data/uniprot.tsv, these will '...
-0264 'not be enzyme-constrained. If you intend to use different Uniprot '...
-0265 'data (e.g. from a different proteome, make sure you first delete '...
-0266 'the existing data/uniprot.tsv file.\n']);
-0267 end
-0268 ec.genes = model.genes(Lia); %Will often be duplicate of model.genes, but is done here to prevent issues when it is not.
-0269 ec.enzymes = uniprotDB.ID(Locb(Lia));
-0270 ec.mw = uniprotDB.MW(Locb(Lia));
-0271 ec.sequence = uniprotDB.seq(Locb(Lia));
-0272 %Additional info
-0273 ec.concs = nan(numel(ec.genes),1); % To be filled with proteomics data when available
-0274
-0275 %8: Only parse rxns associated to genes
-0276 if ~geckoLight
-0277 ec.rxnEnzMat = zeros(numel(rxnWithGene),numel(ec.genes)); % Non-zeros will indicate the number of subunits
-0278 for r=1:numel(rxnWithGene)
-0279 rxnGenes = model.genes(find(model.rxnGeneMat(rxnWithGene(r),:)));
-0280 [~,locEnz] = ismember(rxnGenes,ec.genes); % Could also parse directly from rxnGeneMat, but some genes might be missing from Uniprot DB
-0281 if locEnz ~= 0
-0282 ec.rxnEnzMat(r,locEnz) = 1; %Assume 1 copy per subunit or enzyme, can be modified later
-0283 end
-0284 end
-0285 else
-0286 %For light models, we need to split up all GPRs
-0287 ec.rxnEnzMat = zeros(numel(ec.rxns),numel(ec.genes)); % Non-zeros will indicate the number of subunits
-0288 nextIndex = 1;
-0289 %For full model generation, the GPRs are controlled in expandModel, but
-0290 %here we need to make an explicit format check
-0291 indexes2check = findPotentialErrors(model.grRules,model);
-0292 if ~isempty(indexes2check)
-0293 printOrange('Run standardizeGrRules(model) for a more detailed warning.\n');
-0294 printOrange('For Human-GEM, these reactions can be corrected using simplifyGrRules.\n');
-0295 end
-0296
-0297 for i=1:prevNumRxns
-0298 %ind is the index in the model, not to confuse with the index in the ec struct (i),
-0299 %which only contains reactions with GPRs.
-0300 ind = rxnWithGene(i);
-0301 %Get rid of all '(' and ')' since I'm not looking at complex stuff
-0302 %anyways
-0303 geneString=model.grRules{ind};
-0304 geneString=strrep(geneString,'(','');
-0305 geneString=strrep(geneString,')','');
-0306 geneString=strrep(geneString,' or ',';');
-0307
-0308 if (numOrs(i) == 0)
-0309 geneNames = {geneString};
-0310 else
-0311 %Split the string into gene names
-0312 geneNames=regexp(geneString,';','split');
-0313 end
-0314
-0315 %Now loop through the isozymes and set the rxnGeneMat
-0316 for j = 1:length(geneNames)
-0317 %Find the gene in the gene list If ' and ' relationship, first
-0318 %split the genes
-0319 fnd = strfind(geneNames{j},' and ');
-0320 if ~isempty(fnd)
-0321 andGenes=regexp(geneNames{j},' and ','split');
-0322 ec.rxnEnzMat(nextIndex,ismember(ec.genes,andGenes)) = 1; %should be subunit stoichoimetry
-0323 else
-0324 ec.rxnEnzMat(nextIndex,ismember(ec.genes,geneNames(j)))=1;%should be subunit stoichoimetry
-0325 end
-0326 nextIndex = nextIndex + 1;
-0327 end
-0328 end
-0329 end
-0330
-0331 %9: Add proteins as pseudometabolites
-0332 if ~geckoLight
-0333 [proteinMets.mets, uniprotSortId] = unique(ec.enzymes);
-0334 proteinMets.mets = strcat('prot_',proteinMets.mets);
-0335 proteinMets.metNames = proteinMets.mets;
-0336 proteinMets.compartments = compartmentID;
-0337 if isfield(model,'metMiriams')
-0338 proteinMets.metMiriams = repmat({struct('name',{{'sbo'}},'value',{{'SBO:0000252'}})},numel(proteinMets.mets),1);
-0339 end
-0340 if isfield(model,'metCharges')
-0341 proteinMets.metCharges = zeros(numel(proteinMets.mets),1);
-0342 end
-0343 proteinMets.metNotes = repmat({'Enzyme-usage pseudometabolite'},numel(proteinMets.mets),1);
-0344 model = addMets(model,proteinMets);
-0345 end
-0346
-0347 %10: Add protein pool pseudometabolite
-0348 pool.mets = 'prot_pool';
-0349 pool.metNames = pool.mets;
-0350 pool.compartments = compartmentID;
-0351 pool.metNotes = 'Enzyme-usage protein pool';
-0352 model = addMets(model,pool);
-0353
-0354 %11: Add protein usage reactions.
-0355 if ~geckoLight
-0356 usageRxns.rxns = strcat('usage_',proteinMets.mets);
-0357 usageRxns.rxnNames = usageRxns.rxns;
-0358 usageRxns.mets = cell(numel(usageRxns.rxns),1);
-0359 usageRxns.stoichCoeffs = cell(numel(usageRxns.rxns),1);
-0360 for i=1:numel(usageRxns.mets)
-0361 usageRxns.mets{i} = {proteinMets.mets{i}, 'prot_pool'};
-0362 usageRxns.stoichCoeffs{i} = [-1,1];
-0363 end
-0364 usageRxns.lb = zeros(numel(usageRxns.rxns),1) - 1000;
-0365 usageRxns.ub = zeros(numel(usageRxns.rxns),1);
-0366 usageRxns.rev = ones(numel(usageRxns.rxns),1);
-0367 usageRxns.grRules = ec.genes(uniprotSortId);
-0368 model = addRxns(model,usageRxns);
-0369 end
-0370
-0371 %12: Add protein pool reaction (with open UB)
-0372 poolRxn.rxns = 'prot_pool_exchange';
-0373 poolRxn.rxnNames = poolRxn.rxns;
-0374 poolRxn.mets = {'prot_pool'};
-0375 poolRxn.stoichCoeffs = {-1};
-0376 poolRxn.lb = -1000;
-0377 poolRxn.ub = 0;
-0378 poolRxn.rev = 1;
-0379 model = addRxns(model,poolRxn);
-0380
-0381 model.ec=ec;
-0382 end
-0383
-0384 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0385 %Function that gets the model field grRules and returns the indexes of the
-0386 %rules in which the pattern ") and (" is present.
-0387 %Copied from standardizeGrRules
-0388 % TODO: Make this an accessible function in a separate file in RAVEN and remove this
-0389 %implementation.
-0390 function indexes2check = findPotentialErrors(grRules,model)
-0391 indxs_l = find(~cellfun(@isempty,strfind(grRules,') and (')));
-0392 indxs_l_L = find(~cellfun(@isempty,strfind(grRules,') and')));
-0393 indxs_l_R = find(~cellfun(@isempty,strfind(grRules,'and (')));
-0394 indexes2check = vertcat(indxs_l,indxs_l_L,indxs_l_R);
-0395 indexes2check = unique(indexes2check);
-0396
-0397 if ~isempty(indexes2check)
-0398 textToPrint = 'WARNING: Potentially problematic ") AND (" in the grRules for reaction(s):\n';
-0399 for i=1:numel(indexes2check)
-0400 textToPrint=[textToPrint '\t' model.rxns{indexes2check(i)} '\n'];
-0401 end
-0402 printOrange(textToPrint);
-0403 end
-0404 end
function ecModel = setKcatForReactions(ecModel,rxnIds,kcat)
-
-
DESCRIPTION
-
setKcatForReactions
- Change the kcat value in ecModel.ec.kcat for selected reactions.
- applyKcatConstraints needs to be run afterwards to transfer the kcat
- values into the S-matrix.
-
- Input:
- ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
- rxnIds reaction identifier matching ecModel.ec.rxns. If the _EXP_.
- suffix is not included, and there are multiple expanded
- (isozymic) reactions, then all off those will have their
- kcat changed. If rxnIds includes a _EXP_ suffix, then only
- that specific reaction will have its kcat changed. If
- multiple rxnIds are provided as a cell array, then the
- above applies to each rxnIds individual.
- kcat the new kcat value
-
- Output:
- ecModel ecModel where selected kcat values in ecModel.ec.kcat are
- changed, but not yet applied to the S-matrix (will require
- to run applyKcatConstraints). ecModel.ec.source for the
- changed reactions will read 'from setKcatForReactions'
-
- Usage: ecModel = setKcatForReactions(ecModel,rxnIds,kcat)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function ecModel = setKcatForReactions(ecModel,rxnIds,kcat)
-0002 % setKcatForReactions
-0003 % Change the kcat value in ecModel.ec.kcat for selected reactions.
-0004 % applyKcatConstraints needs to be run afterwards to transfer the kcat
-0005 % values into the S-matrix.
-0006 %
-0007 % Input:
-0008 % ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
-0009 % rxnIds reaction identifier matching ecModel.ec.rxns. If the _EXP_.
-0010 % suffix is not included, and there are multiple expanded
-0011 % (isozymic) reactions, then all off those will have their
-0012 % kcat changed. If rxnIds includes a _EXP_ suffix, then only
-0013 % that specific reaction will have its kcat changed. If
-0014 % multiple rxnIds are provided as a cell array, then the
-0015 % above applies to each rxnIds individual.
-0016 % kcat the new kcat value
-0017 %
-0018 % Output:
-0019 % ecModel ecModel where selected kcat values in ecModel.ec.kcat are
-0020 % changed, but not yet applied to the S-matrix (will require
-0021 % to run applyKcatConstraints). ecModel.ec.source for the
-0022 % changed reactions will read 'from setKcatForReactions'
-0023 %
-0024 % Usage: ecModel = setKcatForReactions(ecModel,rxnIds,kcat)
-0025 rxnIds = convertCharArray(rxnIds);
-0026
-0027 hasExp = ~cellfun(@isempty,regexp(rxnIds,'_EXP_\d+$'));
-0028 nonExpRxns = regexprep(ecModel.ec.rxns,'_EXP_\d+$','');
-0029 rxnsToChange = [];
-0030 for i=1:numel(hasExp)
-0031 if hasExp(i) == 1
-0032 rxnsToChange = [rxnsToChange; find(strcmpi(ecModel.ec.rxns,rxnIds{i}))];
-0033 else
-0034 nonExpRxn = regexprep(rxnIds(i),'_EXP_\d+$','');
-0035 rxnsToChange = [rxnsToChange; find(strcmpi(nonExpRxns,nonExpRxn))];
-0036 end
-0037 end
-0038 if isscalar(rxnsToChange)
-0039 if length(kcat) ~= 1
-0040 error('Found one reaction whose kcat should change, you should provide one kcat value only.')
-0041 end
-0042 else
-0043 if isscalar(kcat)
-0044 % Is fine, all reactions get the same kcat
-0045 elseif length(kcat) ~= length(rxnsToChange)
-0046 error('Found %d reactions whose kcat should change, the new kcat should be either a single value, or a vector of length %d.', length(rxnsToChange), length(rxnsToChange))
-0047 end
-0048 end
-0049 ecModel.ec.kcat(rxnsToChange) = kcat;
-0050 ecModel.ec.source(rxnsToChange) = {'setKcatForReactions'};
-0051 end
function model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter)
-
-
DESCRIPTION
-
setProtPoolSize
- Sets the limit of the total protein usage in the model.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- Ptot Total cellular protein content in g/gDCW. If not
- specified, the value will be read from the model
- adapter. If not specified in model adapter, 0.5 g/gDCW
- is assumed.
- f Estimated fraction of enzymes in the model. If not
- specified, the value will be read from the model
- adapter. If not specified in model adapter, 0.5 is
- assumed.
- sigma Estimated saturation factor. If not specified, the
- value will be read from the model adapter. If not
- specified in model adapter, 0.5 is assumed.
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- model ecModel with protein pool constraint set
-
- Usage: model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter)
-0002 % setProtPoolSize
-0003 % Sets the limit of the total protein usage in the model.
-0004 %
-0005 % Input:
-0006 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0007 % Ptot Total cellular protein content in g/gDCW. If not
-0008 % specified, the value will be read from the model
-0009 % adapter. If not specified in model adapter, 0.5 g/gDCW
-0010 % is assumed.
-0011 % f Estimated fraction of enzymes in the model. If not
-0012 % specified, the value will be read from the model
-0013 % adapter. If not specified in model adapter, 0.5 is
-0014 % assumed.
-0015 % sigma Estimated saturation factor. If not specified, the
-0016 % value will be read from the model adapter. If not
-0017 % specified in model adapter, 0.5 is assumed.
-0018 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0019 % default model adapter).
-0020 %
-0021 % Output:
-0022 % model ecModel with protein pool constraint set
-0023 %
-0024 % Usage: model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter)
-0025
-0026 if nargin < 5 || isempty(modelAdapter)
-0027 modelAdapter = ModelAdapterManager.getDefault();
-0028 if isempty(modelAdapter) && (nargin<2 || isempty(Ptot) || isempty(f) || isempty(sigma))
-0029 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0030 end
-0031 end
-0032
-0033 if nargin<4 || isempty(sigma)
-0034 sigma = modelAdapter.getParameters().sigma;
-0035 end
-0036 if nargin<3 || isempty(f)
-0037 f = modelAdapter.getParameters().f;
-0038 end
-0039 if nargin<2 || isempty(Ptot)
-0040 Ptot = modelAdapter.getParameters().Ptot;
-0041 end
-0042
-0043 model.lb(strcmp(model.rxns, 'prot_pool_exchange')) = -(Ptot*f*sigma*1000);
-0044 end
-0045
function kcatList = fuzzyKcatMatching(model, ecRxns, modelAdapter, forceWClvl)
-
-
DESCRIPTION
-
fuzzyKcatMatching
- Matchs the model EC numbers and substrates to the BRENDA database, to
- return the corresponding kcats for each reaction. If no exact match is
- found, less specific kcat values are found from (a) evolutionary
- closely related organism; (b) different substrate; (c) calculated from
- specific activities; (d) wildcards in the EC number. The model organism
- is provided in the model adapter as obj.params.org_name, and
- evolutionary distance to other organisms is determined via KEGG
- phylogeny. If an organism name occurs multiple times in KEGG, the first
- instance will be used when determining evolutionary distance.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- ecRxns for which reactions (from model.ec.rxns) kcat values should
- be found, provided as logical vector with same length as
- model.ec.rxns. (Opt, default is all reactions)
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
- forceWClvl force a minimum wildcard level (Optional, default 0).
-
- Output:
- kcatList structure array with list of BRENDA derived kcat values,
- with separate entries for each kcat value
- source 'brenda'
- rxns reaction identifiers
- substrate substrate names
- kcat proposed kcat value in /sec
- eccodes as used to query BRENDA
- wildCardLvl which level of EC wild-card was necessary to
- find a match
- 0: w.x.y.z
- 1: w.x.y.-
- 2: w.x.-.-
- 3: w.-.-.-
- origin which level of specificity was necessary to
- find a match
- 1: correct organism, correct substrate, kcat
- 2: any organism, correct substrate, kcat
- 3: correct organism, any substrate, kcat
- 4: any organism, any substrate, kcat
- 5: correct organism, specific activity
- 6: any organism, specific activity
-
- Note: If a wildcard is used, origin levels 1 and 2 are ignored. The
- last digits in the E.C. number indicate the substrate specificity, so
- if this should be ignored, then correct substrate matches should not be
- prioritized.
-
- Usage:
- kcatList = fuzzyKcatMatching(model, ecRxns, modelAdapter, forceWClvl)
0001 function kcatList = fuzzyKcatMatching(model, ecRxns, modelAdapter, forceWClvl)
-0002 % fuzzyKcatMatching
-0003 % Matchs the model EC numbers and substrates to the BRENDA database, to
-0004 % return the corresponding kcats for each reaction. If no exact match is
-0005 % found, less specific kcat values are found from (a) evolutionary
-0006 % closely related organism; (b) different substrate; (c) calculated from
-0007 % specific activities; (d) wildcards in the EC number. The model organism
-0008 % is provided in the model adapter as obj.params.org_name, and
-0009 % evolutionary distance to other organisms is determined via KEGG
-0010 % phylogeny. If an organism name occurs multiple times in KEGG, the first
-0011 % instance will be used when determining evolutionary distance.
-0012 %
-0013 % Input:
-0014 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0015 % ecRxns for which reactions (from model.ec.rxns) kcat values should
-0016 % be found, provided as logical vector with same length as
-0017 % model.ec.rxns. (Opt, default is all reactions)
-0018 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0019 % default model adapter).
-0020 % forceWClvl force a minimum wildcard level (Optional, default 0).
-0021 %
-0022 % Output:
-0023 % kcatList structure array with list of BRENDA derived kcat values,
-0024 % with separate entries for each kcat value
-0025 % source 'brenda'
-0026 % rxns reaction identifiers
-0027 % substrate substrate names
-0028 % kcat proposed kcat value in /sec
-0029 % eccodes as used to query BRENDA
-0030 % wildCardLvl which level of EC wild-card was necessary to
-0031 % find a match
-0032 % 0: w.x.y.z
-0033 % 1: w.x.y.-
-0034 % 2: w.x.-.-
-0035 % 3: w.-.-.-
-0036 % origin which level of specificity was necessary to
-0037 % find a match
-0038 % 1: correct organism, correct substrate, kcat
-0039 % 2: any organism, correct substrate, kcat
-0040 % 3: correct organism, any substrate, kcat
-0041 % 4: any organism, any substrate, kcat
-0042 % 5: correct organism, specific activity
-0043 % 6: any organism, specific activity
-0044 %
-0045 % Note: If a wildcard is used, origin levels 1 and 2 are ignored. The
-0046 % last digits in the E.C. number indicate the substrate specificity, so
-0047 % if this should be ignored, then correct substrate matches should not be
-0048 % prioritized.
-0049 %
-0050 % Usage:
-0051 % kcatList = fuzzyKcatMatching(model, ecRxns, modelAdapter, forceWClvl)
-0052
-0053 if nargin<2 || isempty(ecRxns)
-0054 ecRxns = true(numel(model.ec.rxns),1);
-0055 elseif isnumeric(ecRxns)
-0056 ecRxnsVec = false(numel(model.ec.rxns),1);
-0057 ecRxnsVec(ecRxns) = true;
-0058 ecRxns = ecRxnsVec;
-0059 end
-0060 ecRxns=find(ecRxns); % Get indices instead of logical
-0061
-0062 if nargin < 3 || isempty(modelAdapter)
-0063 modelAdapter = ModelAdapterManager.getDefault();
-0064 if isempty(modelAdapter)
-0065 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0066 end
-0067 end
-0068 params = modelAdapter.params;
-0069
-0070 if nargin < 4 || isempty(forceWClvl)
-0071 forceWClvl = 0;
-0072 end
-0073
-0074 if ~isfield(model.ec,'eccodes')
-0075 error('No EC codes defined in model.ec.eccodes. First run getECfromGEM() and/or getECfromDatabase().')
-0076 end
-0077 eccodes = model.ec.eccodes(ecRxns);
-0078 substrates = cell(numel(ecRxns),1);
-0079 substrCoeffs = cell(numel(ecRxns),1);
-0080
-0081 %Need to remove the prefix of GECKO light rxn names in the ec structure
-0082 if ~model.ec.geckoLight
-0083 rxnNames = model.ec.rxns;
-0084 else
-0085 rxnNames = extractAfter(model.ec.rxns, 4);
-0086 end
-0087 [~,originalRxns] = ismember(rxnNames(ecRxns),model.rxns);
-0088 for i = 1:length(ecRxns)
-0089 sel = find(model.S(:,originalRxns(i)) < 0);
-0090 substrates{i} = model.metNames(sel);
-0091 substrCoeffs{i} = -model.S(sel,originalRxns(i));
-0092 end
-0093
-0094 %Load BRENDA data:
-0095 [KCATcell, SAcell] = loadBRENDAdata(modelAdapter);
-0096
-0097 %Creates a Structure with KEGG codes for organisms, names and taxonomical
-0098 %distance matrix and extract the organism index in the KEGG struct
-0099 phylDistStruct = KEGG_struct(modelAdapter.getPhylDistStructPath());
-0100 %Get the KEGG code for the model's organism
-0101 org_name = params.org_name;
-0102 org_index = find_inKEGG(org_name,phylDistStruct.names);
-0103 %build an index for genus in the phyl dist struct
-0104 %first just extract the genus (i.e. the first part of the name)
-0105 phylDistStruct.genus = lower(regexprep(phylDistStruct.names,'\s.*',''));
-0106 %create a map for the genuses
-0107 phylDistStruct.uniqueGenusList = unique(phylDistStruct.genus);
-0108 phylDistStruct.genusHashMap = containers.Map(phylDistStruct.uniqueGenusList,1:length(phylDistStruct.uniqueGenusList));
-0109 phylDistStruct.uniqueGenusIndices = cell(length(phylDistStruct.uniqueGenusList),1);
-0110
-0111 %Then for each genus create a list with indices to the names
-0112 for i = 1:length(phylDistStruct.genus)
-0113 matchInd = cell2mat(values(phylDistStruct.genusHashMap,phylDistStruct.genus(i)));
-0114 phylDistStruct.uniqueGenusIndices{matchInd} = [phylDistStruct.uniqueGenusIndices{matchInd};i];
-0115 end
-0116
-0117 %Allocate output
-0118 kcats = zeros(length(eccodes),1);
-0119 mM = length(eccodes);
-0120
-0121 %Create empty kcatInfo
-0122 %Legacy, no longer given as output, rather used to construct
-0123 %kcatList.wildcardLvl and kcatList.origin.
-0124 kcatInfo.info.org_s = zeros(mM,1);
-0125 kcatInfo.info.rest_s = zeros(mM,1);
-0126 kcatInfo.info.org_ns = zeros(mM,1);
-0127 kcatInfo.info.rest_ns = zeros(mM,1);
-0128 kcatInfo.info.org_sa = zeros(mM,1);
-0129 kcatInfo.info.rest_sa = zeros(mM,1);
-0130 kcatInfo.info.wcLevel = NaN(mM,1);
-0131 kcatInfo.stats.queries = 0;
-0132 kcatInfo.stats.org_s = 0;
-0133 kcatInfo.stats.rest_s = 0;
-0134 kcatInfo.stats.org_ns = 0;
-0135 kcatInfo.stats.rest_ns = 0;
-0136 kcatInfo.stats.org_sa = 0;
-0137 kcatInfo.stats.rest_sa = 0;
-0138 kcatInfo.stats.wc0 = 0;
-0139 kcatInfo.stats.wc1 = 0;
-0140 kcatInfo.stats.wc2 = 0;
-0141 kcatInfo.stats.wc3 = 0;
-0142 kcatInfo.stats.wc4 = 0;
-0143 kcatInfo.stats.matrix = zeros(6,5);
-0144
-0145 %build an EC index to speed things up a bit - many of the ECs appear
-0146 %many times - unnecessary to compare them all
-0147 %so, here, each EC string appears only once, and you get a vector with
-0148 %indices to the rows in KCATcell
-0149 [ECIndexIds,~,ic] = unique(KCATcell{1});
-0150 EcIndexIndices = cell(length(ECIndexIds),1);
-0151 for i = 1:length(EcIndexIndices)
-0152 EcIndexIndices{i} = find(ic == i).';
-0153 end
-0154
-0155 %Apply force wildcard level
-0156 while forceWClvl > 0
-0157 eccodes=regexprep(eccodes,'(.)*(\.\d+)(\.-)*$','$1\.-$3');
-0158 forceWClvl = forceWClvl - 1;
-0159 end
-0160 if forceWClvl == 1
-0161 eccodes = regexprep(eccodes,'.*','-\.-\.-\.-');
-0162 end
-0163
-0164 progressbar('Gathering kcat values by fuzzy matching to BRENDA database')
-0165 %Main loop:
-0166 for i = 1:mM
-0167 %Match:
-0168 EC = eccodes{i};
-0169 if ~isempty(EC)
-0170 EC = strsplit(EC,';');
-0171 %Try to match direct reaction:
-0172 if ~isempty(substrates{i})
-0173 [kcats(i), kcatInfo.info,kcatInfo.stats] = iterativeMatch(EC,substrates{i},substrCoeffs{i},i,KCATcell,...
-0174 kcatInfo.info,kcatInfo.stats,org_name,...
-0175 phylDistStruct,org_index,SAcell,ECIndexIds,EcIndexIndices);
-0176 end
-0177 end
-0178 progressbar(i/mM)
-0179 end
-0180
-0181 kcatList.source = 'brenda';
-0182 kcatList.rxns = model.ec.rxns(ecRxns);
-0183 kcatList.substrates = substrates;
-0184 kcatList.kcats = kcats;
-0185 kcatList.eccodes = eccodes;
-0186 kcatList.wildcardLvl = kcatInfo.info.wcLevel;
-0187 kcatList.origin = NaN(numel(model.ec.rxns(ecRxns)),1);
-0188 % This can be refactored, iterativeMatch and their nested functions can
-0189 % just directly report the origin number.
-0190 origin = [kcatInfo.info.org_s kcatInfo.info.rest_s kcatInfo.info.org_ns kcatInfo.info.rest_ns kcatInfo.info.org_sa kcatInfo.info.rest_sa];
-0191 for i=1:6
-0192 kcatList.origin(find(origin(:,i))) = i;
-0193 end
-0194 end
-0195 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0196 function [kcat,dir,tot] =iterativeMatch(EC,subs,substrCoeff,i,KCATcell,dir,tot,...
-0197 name,phylDist,org_index,SAcell,ECIndexIds,EcIndexIndices)
-0198 %Will iteratively try to match the EC number to some registry in BRENDA,
-0199 %using each time one additional wildcard.
-0200
-0201 kcat = zeros(size(EC));
-0202 origin = zeros(size(EC));
-0203 matches = zeros(size(EC));
-0204 wc_num = ones(size(EC)).*1000;
-0205 for k = 1:length(EC)
-0206 success = false;
-0207 while ~success
-0208 %Atempt match:
-0209 [kcat(k),origin(k),matches(k)] = mainMatch(EC{k},subs,substrCoeff,KCATcell,...
-0210 name,phylDist,...
-0211 org_index,SAcell,ECIndexIds,EcIndexIndices);
-0212 %If any match found, ends. If not, introduces one extra wild card and
-0213 %tries again:
-0214 if origin(k) > 0
-0215 success = true;
-0216 wc_num(k) = sum(EC{k}=='-');
-0217 else
-0218 dot_pos = [2 strfind(EC{k},'.')];
-0219 wild_num = sum(EC{k}=='-');
-0220 wc_text = '-.-.-.-';
-0221 EC{k} = [EC{k}(1:dot_pos(4-wild_num)) wc_text(1:2*wild_num+1)];
-0222 end
-0223 end
-0224 end
-0225
-0226 if sum(origin) > 0
-0227 %For more than one EC: Choose the maximum value among the ones with the
-0228 %less amount of wildcards and the better origin:
-0229 best_pos = (wc_num == min(wc_num));
-0230 new_origin = origin(best_pos);
-0231 best_pos = (origin == min(new_origin(new_origin~=0)));
-0232 max_pos = find(kcat == max(kcat(best_pos)));
-0233 wc_num = wc_num(max_pos(1));
-0234 origin = origin(max_pos(1));
-0235 matches = matches(max_pos(1));
-0236 kcat = kcat(max_pos(1));
-0237
-0238 %Update dir and tot:
-0239 dir.org_s(i) = matches*(origin == 1);
-0240 dir.rest_s(i) = matches*(origin == 2);
-0241 dir.org_ns(i) = matches*(origin == 3);
-0242 dir.org_sa(i) = matches*(origin == 4);
-0243 dir.rest_ns(i) = matches*(origin == 5);
-0244 dir.rest_sa(i) = matches*(origin == 6);
-0245 dir.wcLevel(i) = wc_num;
-0246 tot.org_s = tot.org_s + (origin == 1);
-0247 tot.rest_s = tot.rest_s + (origin == 2);
-0248 tot.org_ns = tot.org_ns + (origin == 3);
-0249 tot.org_sa = tot.org_sa + (origin == 4);
-0250 tot.rest_ns = tot.rest_ns + (origin == 5);
-0251 tot.rest_sa = tot.rest_sa + (origin == 6);
-0252 tot.wc0 = tot.wc0 + (wc_num == 0);
-0253 tot.wc1 = tot.wc1 + (wc_num == 1);
-0254 tot.wc2 = tot.wc2 + (wc_num == 2);
-0255 tot.wc3 = tot.wc3 + (wc_num == 3);
-0256 tot.wc4 = tot.wc4 + (wc_num == 4);
-0257 tot.queries = tot.queries + 1;
-0258 tot.matrix(origin,wc_num+1) = tot.matrix(origin,wc_num+1) + 1;
-0259 end
-0260
-0261 end
-0262 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0263
-0264 function [kcat,origin,matches] = mainMatch(EC,subs,substrCoeff,KCATcell,...
-0265 name,phylDist,org_index,SAcell,ECIndexIds,EcIndexIndices)
-0266
-0267 %First make the string matching. This takes time, so we only want to do
-0268 %this once:
-0269 %Relaxes matching if wild cards are present:
-0270 wild = false;
-0271 wild_pos = strfind(EC,'-');
-0272 if ~isempty(wild_pos)
-0273 EC = EC(1:wild_pos(1)-1);
-0274 wild = true;
-0275 end
-0276 stringMatchesEC_cell = extract_string_matches(EC,KCATcell{1},wild,ECIndexIds,EcIndexIndices);
-0277
-0278 % Matching function prioritizing organism and substrate specificity when
-0279 % available.
-0280
-0281 origin = 0;
-0282 %First try to match organism and substrate:
-0283 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,name,true,false,...
-0284 phylDist,org_index,SAcell,stringMatchesEC_cell,[]);
-0285 if matches > 0 && ~wild % If wildcard, ignore substrate match
-0286 origin = 1;
-0287 %If no match, try the closest organism but match the substrate:
-0288 else
-0289 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,'',true,false,...
-0290 phylDist,org_index,SAcell,stringMatchesEC_cell,[]);
-0291 if matches > 0 && ~wild % If wildcard, ignore substrate match
-0292 origin = 2;
-0293 %If no match, try to match organism but with any substrate:
-0294 else
-0295 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,name,false,false,...
-0296 phylDist,org_index,SAcell,stringMatchesEC_cell,[]);
-0297 if matches > 0
-0298 origin = 3;
-0299 %If no match, try to match organism but for any substrate (SA*MW):
-0300 else
-0301 %create matching index for SA, has not been needed until now
-0302 stringMatchesSA = extract_string_matches(EC,SAcell{1},wild,[],[]);
-0303
-0304 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,name,false,...
-0305 true,phylDist,org_index,...
-0306 SAcell,stringMatchesEC_cell,stringMatchesSA);
-0307 if matches > 0
-0308 origin = 4;
-0309 %If no match, try any organism and any substrate:
-0310 else
-0311 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,'',false,...
-0312 false,phylDist,...
-0313 org_index,SAcell,stringMatchesEC_cell,stringMatchesSA);
-0314 if matches > 0
-0315 origin = 5;
-0316 %Again if no match, look for any org and SA*MW
-0317 else
-0318 [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,'',...
-0319 false,true,phylDist,...
-0320 org_index,SAcell,stringMatchesEC_cell,stringMatchesSA);
-0321 if matches > 0
-0322 origin = 6;
-0323 end
-0324 end
-0325
-0326 end
-0327 end
-0328 end
-0329 end
-0330 end
-0331 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0332 function [kcat,matches] = matchKcat(EC,subs,substrCoeff,KCATcell,organism,...
-0333 substrate,SA,phylDist,...
-0334 org_index,SAcell,KCATcellMatches,SAcellMatches)
-0335
-0336 %Will go through BRENDA and will record any match. Afterwards, it will
-0337 %return the average value and the number of matches attained.
-0338 kcat = [];
-0339 matches = 0;
-0340
-0341 if SA
-0342 %SAcell{1},wild,[],[]
-0343 EC_indexes = extract_indexes(SAcellMatches,[],SAcell{2},subs,substrate,...
-0344 organism,org_index,phylDist);
-0345
-0346 kcat = SAcell{3}(EC_indexes);
-0347 org_cell = SAcell{2}(EC_indexes);
-0348 MW_BRENDA = SAcell{4}(EC_indexes);
-0349
-0350 else
-0351 %KCATcell{1},wild,ECIndexIds,EcIndexIndices
-0352 EC_indexes = extract_indexes(KCATcellMatches,KCATcell{2},KCATcell{3},...
-0353 subs,substrate,organism,org_index,...
-0354 phylDist);
-0355 if substrate
-0356 for j = 1:length(EC_indexes)
-0357 indx = EC_indexes(j);
-0358 for k = 1:length(subs)
-0359 if (isempty(subs{k}))
-0360 break;
-0361 end
-0362 %l = logical(strcmpi(model.metNames,subs{k}).*(model.S(:,i)~=0)); %I don't understand the .* (model.S(:,i)~=0) part, it shouldn't be needed?/JG;
-0363 if ~isempty(subs{k}) && strcmpi(subs{k},KCATcell{2}(indx))
-0364 if KCATcell{4}(indx) > 0
-0365 coeff = min(substrCoeff);
-0366 kCatTmp = KCATcell{4}(indx);
-0367 kcat = [kcat;kCatTmp/coeff];
-0368 end
-0369 end
-0370 end
-0371 end
-0372 else
-0373 kcat = KCATcell{4}(EC_indexes);
-0374 end
-0375 end
-0376 %Return maximum value:
-0377 if isempty(kcat)
-0378 kcat = 0;
-0379 else
-0380 matches = length(kcat);
-0381 [kcat,MaxIndx] = max(kcat);
-0382 end
-0383 %Avoid SA*Mw values over the diffusion limit rate [Bar-Even et al. 2011]
-0384 if kcat>(1E7)
-0385 kcat = 1E7;
-0386 end
-0387 end
-0388
-0389 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0390 %Make the string matches of the ECs. This is heavy, so only do it once!
-0391 %
-0392 function EC_indexes = extract_string_matches(EC,EC_cell,wild,ECIndexIds,EcIndexIndices)
-0393 EC_indexes = [];
-0394 EC_indexesOld = [];
-0395 if wild
-0396 if (~isempty(ECIndexIds)) %In some cases the EC_cell is not from KCatCell
-0397 X = find(contains(ECIndexIds, EC));
-0398 for j = 1:length(X)
-0399 EC_indexes = [EC_indexes,EcIndexIndices{X(j)}];
-0400 end
-0401 else%Not optimized
-0402 for j=1:length(EC_cell)
-0403 if strfind(EC_cell{j},EC)==1
-0404 EC_indexes = [EC_indexes,j];
-0405 end
-0406 end
-0407 end
-0408 else
-0409 if (~isempty(ECIndexIds)) %In some cases the EC_cell is not from KCatCell
-0410 mtch = find(strcmpi(EC,ECIndexIds));
-0411 if (~isempty(mtch))
-0412 EC_indexes = EcIndexIndices{mtch};
-0413 end
-0414 else%%Not optimized
-0415 if ~isempty(EC_cell)
-0416 EC_indexes = transpose(find(strcmpi(EC,EC_cell)));
-0417 end
-0418 end
-0419 end
-0420
-0421 end
-0422 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0423 %Extract the indexes of the entries in the BRENDA data that meet the
-0424 %conditions specified by the search criteria
-0425 function EC_indexes = extract_indexes(EC_indCellStringMatches,subs_cell,orgs_cell,subs,...
-0426 substrate,organism, org_index,...
-0427 phylDist)
-0428
-0429 EC_indexes = EC_indCellStringMatches;%reuse so the string comparisons are not run many times
-0430
-0431 %If substrate=true then it will extract only the substrates appereances
-0432 %indexes in the EC subset from the BRENDA cell array
-0433
-0434 if substrate
-0435 if (~isempty(EC_indexes)) %optimization
-0436 Subs_indexes = [];
-0437 for l = 1:length(subs)
-0438 if (isempty(subs{l}))
-0439 break;
-0440 end
-0441 Subs_indexes = horzcat(Subs_indexes,EC_indexes(strcmpi(subs(l),...
-0442 subs_cell(EC_indexes))));
-0443 end
-0444 EC_indexes = Subs_indexes;
-0445 end
-0446 end
-0447
-0448 EC_orgs = orgs_cell(EC_indexes);
-0449 %If specific organism values are requested looks for all the organism
-0450 %repetitions on the subset BRENDA cell array(EC_indexes)
-0451 if string(organism) ~= ''
-0452 EC_indexes = EC_indexes(strcmpi(string(organism),EC_orgs));
-0453
-0454 %If KEGG code was assigned to the organism (model) then it will look for
-0455 %the Kcat value for the closest organism
-0456 elseif org_index~='*'%&& org_index~=''
-0457 KEGG_indexes = [];temp = [];
-0458
-0459 %For relating a phyl dist between the modelled organism and the organisms
-0460 %on the BRENDA cell array it should search for a KEGG code for each of
-0461 %these
-0462 for j=1:length(EC_indexes)
-0463 %Assigns a KEGG index for those found on the KEGG struct
-0464 orgs_index = find(strcmpi(orgs_cell(EC_indexes(j)),phylDist.names),1);
-0465 if ~isempty(orgs_index)
-0466 KEGG_indexes = [KEGG_indexes; orgs_index];
-0467 temp = [temp;EC_indexes(j)];
-0468 %For values related to organisms without KEGG code, then it will
-0469 %look for KEGG code for the first organism with the same genus
-0470 else
-0471 org = orgs_cell{EC_indexes(j)};
-0472 orgGenus = lower(regexprep(org,'\s.*',''));
-0473 if isKey(phylDist.genusHashMap,orgGenus) %annoyingly, this seems to be needed
-0474 matchInd = cell2mat(values(phylDist.genusHashMap,{orgGenus}));
-0475 matches = phylDist.uniqueGenusIndices{matchInd};
-0476 k = matches(1);
-0477 KEGG_indexes = [KEGG_indexes;k];
-0478 temp = [temp;EC_indexes(j)];
-0479 end
-0480 end
-0481 end
-0482 %Update the EC_indexes cell array
-0483 EC_indexes = temp;
-0484 %Looks for the taxonomically closest organism and saves the index of
-0485 %its appearences in the BRENDA cell
-0486 if ~isempty(EC_indexes)
-0487 distances = phylDist.distMat(org_index,KEGG_indexes);
-0488 EC_indexes = EC_indexes(distances == min(distances));
-0489 end
-0490 end
-0491 end
-0492 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0493 function org_index = find_inKEGG(org_name,names)
-0494 org_index = find(strcmpi(org_name,names));
-0495 if numel(org_index)>1
-0496 org_index = org_index(1);
-0497 elseif isempty(org_index)
-0498 org_name = regexprep(org_name,'\s.*','');
-0499 org_index = find(strcmpi(org_name,names));
-0500 if numel(org_index)>1
-0501 org_index = org_index(1);
-0502 elseif isempty(org_index)
-0503 org_index = '*';
-0504 end
-0505 end
-0506 end
-0507 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0508 function phylDistStruct = KEGG_struct(phylpath)
-0509 load(phylpath)
-0510 phylDistStruct.ids = transpose(phylDistStruct.ids);
-0511 phylDistStruct.names = transpose(phylDistStruct.names);
-0512 phylDistStruct.names = regexprep(phylDistStruct.names,'\s*\(.*','');
-0513 end
getStandardKcat
- Calculate an standard kcat and standard molecular weight (MW) that can
- be used to apply enzyme constraints to reactions without any associated
- enzymes. Such reactions have either an empty model.grRules field, or
- they have no match in model.ec.rxns (which can be the case if the genes
- in the model.grRules field could not be mapped to enzymes). This is
- done by adding those reactions to model.ec, assign a "standard"
- pseudoenzyme with the standard MW (median of all proteins in the
- organism) and standard kcat (median from all kcat, or subsystem
- specific kcat).
-
- A reaction is assigned a subSystem specific kcat values if the model
- has a subSystems field and the reaction is annotated with a subSystem.
- Only the first subSystem will be considered if multiple are annotated
- to the same reaction.
-
- Exchange, transport and pseudoreactions are filtered out, plus any
- reaction identifiers specified in /data/pseudoRxns.tsv in the model
- adapter folder.
-
- In addition, reactions that are annotated with an enzyme (and therefore
- already in model.ec), but not assigned any reaction-specific kcat value
- (their model.ec.kcat entry is either 0 or NaN), can be assigned
- standard kcat values by a similar approach. However, those reactions
- will not be linked to the "standard" pseudoenzyme, but will use the
- enzyme that they had already been associated with.
-
- Any pre-existing standard kcat assignments (identified by 'standard'
- entires in model.ec.source) are removed when applying this function.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- modelAdapter a loaded model adapter (Optional, will otherwise use
- the default model adapter).
- threshold a threshold to determine when use a kcat value based on
- the mean kcat of the reactions in the same subSystem or
- based on the median value of all the kcat in the model.
- Second option is used when the number of reactions in a
- determined subSystem is < threshold. (Optional, default
- = 10)
- fillZeroKcat logical whether zero kcat values should be replaced
- with standard kcat values. (Optional, default = true).
-
- Output:
- model ecModel where model.ec is expanded with a standard
- protein with standard kcat and standard MW, assigned to
- reactions without gene associations.
- rxnsMissingGPR a list of updated rxns identifiers with a standard value
- standardMW the standard MW value calculated
- standardKcat the standard Kcat value calculated
- rxnsNoKcat a list of rxns identifiers whose zero kcat has been replaced
-
- While model.ec.kcat is populated, applyKcatConstraints would still need
- to be run to apply the new constraints to the S-matrix.
-
- Usage:
- [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model, modelAdapter, threshold, fillZeroKcat);
0001 function [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model, modelAdapter, threshold, fillZeroKcat)
-0002 % getStandardKcat
-0003 % Calculate an standard kcat and standard molecular weight (MW) that can
-0004 % be used to apply enzyme constraints to reactions without any associated
-0005 % enzymes. Such reactions have either an empty model.grRules field, or
-0006 % they have no match in model.ec.rxns (which can be the case if the genes
-0007 % in the model.grRules field could not be mapped to enzymes). This is
-0008 % done by adding those reactions to model.ec, assign a "standard"
-0009 % pseudoenzyme with the standard MW (median of all proteins in the
-0010 % organism) and standard kcat (median from all kcat, or subsystem
-0011 % specific kcat).
-0012 %
-0013 % A reaction is assigned a subSystem specific kcat values if the model
-0014 % has a subSystems field and the reaction is annotated with a subSystem.
-0015 % Only the first subSystem will be considered if multiple are annotated
-0016 % to the same reaction.
-0017 %
-0018 % Exchange, transport and pseudoreactions are filtered out, plus any
-0019 % reaction identifiers specified in /data/pseudoRxns.tsv in the model
-0020 % adapter folder.
-0021 %
-0022 % In addition, reactions that are annotated with an enzyme (and therefore
-0023 % already in model.ec), but not assigned any reaction-specific kcat value
-0024 % (their model.ec.kcat entry is either 0 or NaN), can be assigned
-0025 % standard kcat values by a similar approach. However, those reactions
-0026 % will not be linked to the "standard" pseudoenzyme, but will use the
-0027 % enzyme that they had already been associated with.
-0028 %
-0029 % Any pre-existing standard kcat assignments (identified by 'standard'
-0030 % entires in model.ec.source) are removed when applying this function.
-0031 %
-0032 % Input:
-0033 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0034 % modelAdapter a loaded model adapter (Optional, will otherwise use
-0035 % the default model adapter).
-0036 % threshold a threshold to determine when use a kcat value based on
-0037 % the mean kcat of the reactions in the same subSystem or
-0038 % based on the median value of all the kcat in the model.
-0039 % Second option is used when the number of reactions in a
-0040 % determined subSystem is < threshold. (Optional, default
-0041 % = 10)
-0042 % fillZeroKcat logical whether zero kcat values should be replaced
-0043 % with standard kcat values. (Optional, default = true).
-0044 %
-0045 % Output:
-0046 % model ecModel where model.ec is expanded with a standard
-0047 % protein with standard kcat and standard MW, assigned to
-0048 % reactions without gene associations.
-0049 % rxnsMissingGPR a list of updated rxns identifiers with a standard value
-0050 % standardMW the standard MW value calculated
-0051 % standardKcat the standard Kcat value calculated
-0052 % rxnsNoKcat a list of rxns identifiers whose zero kcat has been replaced
-0053 %
-0054 % While model.ec.kcat is populated, applyKcatConstraints would still need
-0055 % to be run to apply the new constraints to the S-matrix.
-0056 %
-0057 % Usage:
-0058 % [model, rxnsMissingGPR, standardMW, standardKcat, rxnsNoKcat] = getStandardKcat(model, modelAdapter, threshold, fillZeroKcat);
-0059
-0060 if nargin < 2 || isempty(modelAdapter)
-0061 modelAdapter = ModelAdapterManager.getDefault();
-0062 if isempty(modelAdapter)
-0063 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0064 end
-0065 end
-0066 params = modelAdapter.getParameters();
-0067
-0068 if nargin < 3 || isempty(threshold)
-0069 threshold = 10;
-0070 end
-0071
-0072 if nargin < 4 || isempty(fillZeroKcat)
-0073 fillZeroKcat = true;
-0074 end
-0075
-0076 databases = loadDatabases('uniprot', modelAdapter);
-0077
-0078 % An standard MW is defined for all the rxns which does not have a GPR
-0079 % rule defined. This is based in all the proteins reported for the specific
-0080 % organism in uniprot
-0081 standardMW = median(databases.uniprot.MW, 'omitnan');
-0082
-0083 % An standard Kcat is defined for all the rxns which does not have a GPR
-0084 % rule defined. In this case, the kcat value for a particular reaction is
-0085 % defined as the mean of the kcat values of the reactions involved in the
-0086 % same subsystem in which the given reaction is involved. Nevertheless, if
-0087 % a subSystem have a number of reactions lower than a treshold, the kcat
-0088 % value will be the median of the kcat in all the reactions of the model.
-0089
-0090 % Remove from the list those with kcat zero
-0091 rxnsKcatZero = model.ec.kcat > 0;
-0092
-0093 % Get the kcat value based on all the kcats in the model
-0094 standardKcat = median(model.ec.kcat(rxnsKcatZero), 'omitnan');
-0095
-0096 % If the model have subSystems assigned calculate kcat based on subSystem
-0097 if isfield(model,'subSystems') && ~all(cellfun(@isempty, model.subSystems))
-0098 standard = false;
-0099 if model.ec.geckoLight
-0100 modRxns = extractAfter(model.ec.rxns,4);
-0101 else
-0102 modRxns = model.ec.rxns;
-0103 end
-0104 % Map ec-rxns to model.rxns
-0105 [~,rxnIdx] = ismember(modRxns,model.rxns);
-0106 % Choose first subSystem
-0107 enzSubSystems = flattenCell(model.subSystems(rxnIdx));
-0108 enzSubSystems = enzSubSystems(:,1);
-0109 if ~all(cellfun(@isempty, enzSubSystems))
-0110
-0111 % Make list of unique subsystems, and which rxns are linked to them
-0112 [enzSubSystem_names, ~, rxnToSub] = unique(enzSubSystems);
-0113 % Make matrix of ec-rxns vs. unique subsystem index
-0114 ind = sub2ind([numel(enzSubSystem_names) numel(enzSubSystems)],rxnToSub',1:numel(rxnToSub));
-0115 kcatSubSystem = false([numel(enzSubSystem_names) numel(enzSubSystems)]);
-0116 kcatSubSystem(ind) = true;
-0117 % Number of kcats per subSystem
-0118 kcatsPerSubSystem = sum(kcatSubSystem,2);
-0119 % Calculate average kcat values per subSystem
-0120 kcatSubSystem = (kcatSubSystem*model.ec.kcat)./kcatsPerSubSystem;
-0121 kcatSubSystem(kcatsPerSubSystem < threshold) = standardKcat;
-0122 else
-0123 standard = true;
-0124 printOrange('WARNING: No subSystem-specific kcat values can be calculated');
-0125 end
-0126 else
-0127 standard = true;
-0128 printOrange('WARNING: No subSystem-specific kcat values can be calculated');
-0129 end
-0130
-0131 % Find reactions without GPR
-0132 rxnsMissingGPR = find(cellfun(@isempty, model.grRules));
-0133
-0134 % Find reactions with GPR but without model.ec entry (for instance due to
-0135 % no protein matching)
-0136 rxnsMissingEnzyme = find(~cellfun(@isempty, model.grRules));
-0137 if model.ec.geckoLight
-0138 ecRxnsList = unique(extractAfter(model.ec.rxns,4));
-0139 else
-0140 ecRxnsList = model.ec.rxns;
-0141 end
-0142 rxnsMissingEnzyme = find(and(~ismember(model.rxns(rxnsMissingEnzyme),ecRxnsList), ~contains(model.rxns(rxnsMissingEnzyme),'usage_prot_')));
-0143 rxnsMissingGPR = [rxnsMissingGPR;rxnsMissingEnzyme];
-0144
-0145 % Get custom list of reaction IDs to ignore, if existing. First column
-0146 % contains reaction IDs, second column contains reaction names for
-0147 % reference only.
-0148 if exist(fullfile(params.path,'data','pseudoRxns.tsv'),'file')
-0149 fID = fopen(fullfile(params.path,'data','pseudoRxns.tsv'));
-0150 fileData = textscan(fID,'%s %s','delimiter','\t');
-0151 fclose(fID);
-0152 customRxns = fileData{1};
-0153 customRxns = find(ismember(model.rxns,customRxns));
-0154 else
-0155 customRxns = [];
-0156 end
-0157 % Get and remove exchange, transport, spontaneous and pseudo reactions
-0158 [~, exchangeRxns] = getExchangeRxns(model);
-0159 transportRxns = getTransportRxns(model);
-0160 [spontaneousRxns, ~] = modelAdapter.getSpontaneousReactions(model);
-0161 pseudoRxns = contains(model.rxnNames,'pseudoreaction');
-0162 slimeRxns = contains(model.rxnNames,'SLIME rxn');
-0163 rxnsToIgnore = [customRxns; exchangeRxns; find(transportRxns); ...
-0164 find(spontaneousRxns); find(pseudoRxns); find(slimeRxns)];
-0165 rxnsMissingGPR(ismember(rxnsMissingGPR, rxnsToIgnore)) = [];
-0166
-0167 % Only add if not geckoLight & getStandardKcat was not run earlier
-0168 if ~any(strcmp(model.mets,'prot_standard'))
-0169 % Add a new gene to be consistent with ec field named standard
-0170 proteinStdGenes.genes = 'standard';
-0171 if isfield(model,'geneShortNames')
-0172 proteinStdGenes.geneShortNames = 'std';
-0173 end
-0174 model = addGenesRaven(model, proteinStdGenes);
-0175
-0176 if ~model.ec.geckoLight
-0177 % Add a new metabolite named prot_standard
-0178 proteinStdMets.mets = 'prot_standard';
-0179 proteinStdMets.metNames = proteinStdMets.mets;
-0180 % Validate compartment
-0181 compartmentID = strcmp(model.compNames,params.enzyme_comp);
-0182 if ~any(compartmentID)
-0183 error(['Compartment ' params.enzyme_comp ' (specified in params.enzyme_comp) '...
-0184 'cannot be found in model.compNames'])
-0185 end
-0186 proteinStdMets.compartments = model.comps(compartmentID);
-0187
-0188 if isfield(model,'metNotes')
-0189 proteinStdMets.metNotes = 'Standard enzyme-usage pseudometabolite';
-0190 end
-0191 model = addMets(model, proteinStdMets);
-0192
-0193 % Add a protein usage reaction if not a light version
-0194 proteinStdUsageRxn.rxns = {'usage_prot_standard'};
-0195 proteinStdUsageRxn.rxnNames = proteinStdUsageRxn.rxns;
-0196 proteinStdUsageRxn.mets = {proteinStdMets.mets, 'prot_pool'};
-0197 proteinStdUsageRxn.stoichCoeffs = [-1, 1];
-0198 proteinStdUsageRxn.lb = -1000;
-0199 proteinStdUsageRxn.ub = 0;
-0200 proteinStdUsageRxn.grRules = proteinStdGenes.genes;
-0201
-0202 model = addRxns(model, proteinStdUsageRxn);
-0203 end
-0204 % Update .ec structure in model
-0205 model.ec.genes(end+1) = {'standard'};
-0206 model.ec.enzymes(end+1) = {'standard'};
-0207 model.ec.mw(end+1) = standardMW;
-0208 model.ec.sequence(end+1) = {''};
-0209 % Additional info
-0210 if isfield(model.ec,'concs')
-0211 model.ec.concs(end+1) = nan();
-0212 end
-0213
-0214 % Expand the enzyme rxns matrix
-0215 model.ec.rxnEnzMat = [model.ec.rxnEnzMat, zeros(length(model.ec.rxns), 1)]; % 1 new enzyme
-0216 model.ec.rxnEnzMat = [model.ec.rxnEnzMat; zeros(length(rxnsMissingGPR), length(model.ec.enzymes))]; % new rxns
-0217 end
-0218 stdMetIdx = find(strcmpi(model.ec.enzymes, 'standard'));
-0219
-0220 % Remove previous standard kcat assignment
-0221 oldStandardEnz = find(strcmp(model.ec.source,'standard'));
-0222 if ~isempty(oldStandardEnz)
-0223 oldStandardProt = logical(model.ec.rxnEnzMat(oldStandardEnz,stdMetIdx));
-0224 % If annotated with real enzyme => set kcat to zero
-0225 model.ec.kcat(oldStandardEnz(~oldStandardProt)) = 0;
-0226 model.ec.source(oldStandardEnz(~oldStandardProt)) = {''};
-0227 % If annotated with standard protein => remove entry
-0228 model.ec.rxns(oldStandardEnz(oldStandardProt)) = [];
-0229 model.ec.kcat(oldStandardEnz(oldStandardProt)) = [];
-0230 model.ec.source(oldStandardEnz(oldStandardProt)) = [];
-0231 model.ec.notes(oldStandardEnz(oldStandardProt)) = [];
-0232 model.ec.eccodes(oldStandardEnz(oldStandardProt)) = [];
-0233 model.ec.rxnEnzMat(oldStandardEnz(oldStandardProt),:) = [];
-0234 end
-0235
-0236 numRxns = length(model.ec.rxns);
-0237 for i = 1:numel(rxnsMissingGPR)
-0238 rxnIdx = rxnsMissingGPR(i);
-0239
-0240 % Update .ec structure in model
-0241 if ~model.ec.geckoLight
-0242 model.ec.rxns(end+1) = model.rxns(rxnIdx);
-0243 % Add prefix in case is light version
-0244 else
-0245 model.ec.rxns{end+1} = ['001_' model.rxns{rxnIdx}];
-0246 end
-0247
-0248 if ~standard
-0249 kcatSubSystemIdx = strcmpi(enzSubSystem_names, model.subSystems{rxnIdx}(1));
-0250 if all(kcatSubSystemIdx)
-0251 model.ec.kcat(end+1) = kcatSubSystem(kcatSubSystemIdx);
-0252 else
-0253 model.ec.kcat(end+1) = standardKcat;
-0254 end
-0255 else
-0256 model.ec.kcat(end+1) = standardKcat;
-0257 end
-0258
-0259 model.ec.source(end+1) = {'standard'};
-0260 model.ec.notes(end+1) = {''};
-0261 model.ec.eccodes(end+1) = {''};
-0262
-0263 % Update the enzyme rxns matrix
-0264 model.ec.rxnEnzMat(numRxns+i, stdMetIdx) = 1;
-0265 end
-0266 % Get the rxns identifiers of the updated rxns
-0267 rxnsMissingGPR = model.rxns(rxnsMissingGPR);
-0268
-0269 if fillZeroKcat
-0270 zeroKcat = model.ec.kcat == 0 | isnan(model.ec.kcat);
-0271 model.ec.kcat(zeroKcat) = standardKcat;
-0272 model.ec.source(zeroKcat) = {'standard'};
-0273 rxnsNoKcat = model.ec.rxns(zeroKcat);
-0274 else
-0275 rxnsNoKcat = [];
-0276 end
-0277 end
-0278
-0279 function Cflat = flattenCell(C,strFlag)
-0280 %FLATTENCELL Flatten a nested column cell array into a matrix cell array.
-0281 %
-0282 % CFLAT = FLATTENCELL(C) takes a column cell array in which one or more
-0283 % entries is a nested cell array, and flattens it into a 2D matrix cell
-0284 % array, where the nested entries are spread across new columns.
-0285 %
-0286 % CFLAT = FLATTENCELL(C,STRFLAG) if STRFLAG is TRUE, empty entries in the
-0287 % resulting CFLAT will be replaced with empty strings {''}. Default = FALSE
-0288 if nargin < 2
-0289 strFlag = false;
-0290 end
-0291
-0292 % determine which entries are cells
-0293 cells = cellfun(@iscell,C);
-0294
-0295 % determine number of elements in each nested cell
-0296 cellsizes = cellfun(@numel,C);
-0297 cellsizes(~cells) = 1; % ignore non-cell entries
-0298
-0299 % flatten single-entry cells
-0300 Cflat = C;
-0301 Cflat(cells & (cellsizes == 1)) = cellfun(@(x) x{1},Cflat(cells & (cellsizes == 1)),'UniformOutput',false);
-0302
-0303 % iterate through multi-entry cells
-0304 multiCells = find(cellsizes > 1);
-0305 for i = 1:length(multiCells)
-0306 cellContents = Cflat{multiCells(i)};
-0307 Cflat(multiCells(i),1:length(cellContents)) = cellContents;
-0308 end
-0309
-0310 % change empty elements to strings, if specified
-0311 if ( strFlag )
-0312 Cflat(cellfun(@isempty,Cflat)) = {''};
-0313 end
-0314 end
function mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy, topOriginLimit, bottomOriginLimit, wildcardLimit)
-
-
DESCRIPTION
-
mergeDlkcatAndFuzzyKcats
- Merges the results from DLKcat and fuzzy matching to BRENDA database.
- Order of preference:
- 1: BRENDA match with correct E.C. number, with origin (see below) not
- lower than the specified topOriginLimit
- 2: DLKcat match
- 3: BRENDA match with correct E.C. number, with origin below
- topOriginLimit but not lower than the bottomOriginLimit
- 4: BRENDA match with wildcards in the E.C. number, with not more
- wildcards than wildcardLimit, and origin not lower than the
- bottomOriginLimit
-
- Input:
- kcatListDLKcat kcatList derived from readDLKcatOutput
- kcatListFuzzy kcatList derived from fuzzyKcatMatching
- topOriginLimit origin limit for prioritized BRENDA matches. Origin
- is explained in more detail below. (Optional,
- default 6)
- bottomOriginLimit origin limit for low priority BRENDA matches.
- Origin is explained in more detail below.
- (Optional, default 6)
- wildcardLimit maximum number of wildcards in E.C. number of
- BRENDA matches (Optional, default 3)
-
- Output:
- mergedKcatList merged list of kcats
-
- The origin parameter:
- 1: correct organism, correct substrate, kcat
- 2: any organism, correct substrate, kcat
- 3: correct organism, any substrate, kcat
- 4: any organism, any substrate, kcat
- 5: correct organism, specific activity
- 6: any organism, specific activity
-
- Example of wildcards in E.C. number:
- 0: 1.1.1.3 glycerol-3-phosphate dehydrogenase (NAD+)
- 1: 1.1.1.- oxidoreductase, acting on the CH-OH group of donors,
- with NAD+ or NADP+ as acceptor
- 2: 1.1.-.- oxidoreductase, acting on the CH-OH group of donors
- 3: 1.-.-.- oxidoreductase
-
- Usage:
- mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy, topOriginLimit, bottomOriginLimit, wildcardLimit)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy, topOriginLimit, bottomOriginLimit, wildcardLimit)
-0002 % mergeDlkcatAndFuzzyKcats
-0003 % Merges the results from DLKcat and fuzzy matching to BRENDA database.
-0004 % Order of preference:
-0005 % 1: BRENDA match with correct E.C. number, with origin (see below) not
-0006 % lower than the specified topOriginLimit
-0007 % 2: DLKcat match
-0008 % 3: BRENDA match with correct E.C. number, with origin below
-0009 % topOriginLimit but not lower than the bottomOriginLimit
-0010 % 4: BRENDA match with wildcards in the E.C. number, with not more
-0011 % wildcards than wildcardLimit, and origin not lower than the
-0012 % bottomOriginLimit
-0013 %
-0014 % Input:
-0015 % kcatListDLKcat kcatList derived from readDLKcatOutput
-0016 % kcatListFuzzy kcatList derived from fuzzyKcatMatching
-0017 % topOriginLimit origin limit for prioritized BRENDA matches. Origin
-0018 % is explained in more detail below. (Optional,
-0019 % default 6)
-0020 % bottomOriginLimit origin limit for low priority BRENDA matches.
-0021 % Origin is explained in more detail below.
-0022 % (Optional, default 6)
-0023 % wildcardLimit maximum number of wildcards in E.C. number of
-0024 % BRENDA matches (Optional, default 3)
-0025 %
-0026 % Output:
-0027 % mergedKcatList merged list of kcats
-0028 %
-0029 % The origin parameter:
-0030 % 1: correct organism, correct substrate, kcat
-0031 % 2: any organism, correct substrate, kcat
-0032 % 3: correct organism, any substrate, kcat
-0033 % 4: any organism, any substrate, kcat
-0034 % 5: correct organism, specific activity
-0035 % 6: any organism, specific activity
-0036 %
-0037 % Example of wildcards in E.C. number:
-0038 % 0: 1.1.1.3 glycerol-3-phosphate dehydrogenase (NAD+)
-0039 % 1: 1.1.1.- oxidoreductase, acting on the CH-OH group of donors,
-0040 % with NAD+ or NADP+ as acceptor
-0041 % 2: 1.1.-.- oxidoreductase, acting on the CH-OH group of donors
-0042 % 3: 1.-.-.- oxidoreductase
-0043 %
-0044 % Usage:
-0045 % mergedKcatList = mergeDLKcatAndFuzzyKcats(kcatListDLKcat, kcatListFuzzy, topOriginLimit, bottomOriginLimit, wildcardLimit)
-0046
-0047 if nargin < 5
-0048 wildcardLimit = 3;
-0049 end
-0050
-0051 if nargin < 4
-0052 bottomOriginLimit = 6;
-0053 end
-0054
-0055 if nargin < 3
-0056 topOriginLimit = 6;
-0057 end
-0058
-0059 if (topOriginLimit < 1) || (topOriginLimit > 6)
-0060 error('topPrioOriginLimit should be between 1 and 6.');
-0061 end
-0062
-0063 if (bottomOriginLimit < 1) || (bottomOriginLimit > 6)
-0064 error('originCutLevel should be between 1 and 6.');
-0065 end
-0066
-0067 if (wildcardLimit < 0) || (wildcardLimit > 3)
-0068 error('wildcardCutLevel should be between 0 and 3.');
-0069 end
-0070
-0071 wc = kcatListFuzzy.wildcardLvl;
-0072 wc(isnan(wc)) = 1000; %large wildcard
-0073
-0074 origin = kcatListFuzzy.origin;
-0075 origin(isnan(origin)) = 1000; %large origin
-0076
-0077 prio1 = (wc == 0) & (origin <= topOriginLimit);
-0078
-0079 rxnsWithPrio1 = unique(kcatListFuzzy.rxns(prio1));
-0080
-0081 %Things get a bit complicated since not all reactions are in the kcatLists and
-0082 %some reactions may appear multiple times
-0083 prio2 = true(length(kcatListDLKcat.rxns),1);
-0084 prio2(ismember(kcatListDLKcat.rxns, rxnsWithPrio1)) = false;
-0085 prio2Rxns = unique(kcatListDLKcat.rxns(prio2));
-0086
-0087 %The prioritization between wildcards and origin is already done in fuzzy matching,
-0088 %so we can join them here
-0089 prio3 = ((wc == 0) & (origin > topOriginLimit) & (origin <= bottomOriginLimit)) | ...
-0090 ((wc > 0) & (wc <= wildcardLimit) & (origin <= bottomOriginLimit));
-0091 prio3(ismember(kcatListFuzzy.rxns, prio2Rxns)) = false;
-0092
-0093 fuzzyRxns = prio1 | prio3;
-0094
-0095 %Now build the merged list, fuzzy followed by dlkcat
-0096 %The order of the reactions is therefore not preserved.
-0097 mergedKcatList = struct();
-0098 mergedKcatList.source = 'Merged DLKcat and fuzzy';
-0099 [fuzzySrc{1:sum(fuzzyRxns)}] = deal(kcatListFuzzy.source);
-0100 [dlkcatSrc{1:sum(prio2)}] = deal(kcatListDLKcat.source);
-0101 mergedKcatList.kcatSource = [fuzzySrc.';dlkcatSrc.'];
-0102 mergedKcatList.rxns = [kcatListFuzzy.rxns(fuzzyRxns);kcatListDLKcat.rxns(prio2)];
-0103 mergedKcatList.genes = [cell(sum(fuzzyRxns),1);kcatListDLKcat.genes(prio2)];
-0104 mergedKcatList.substrates = [kcatListFuzzy.substrates(fuzzyRxns);kcatListDLKcat.substrates(prio2)];
-0105 mergedKcatList.kcats = [kcatListFuzzy.kcats(fuzzyRxns);kcatListDLKcat.kcats(prio2)];
-0106 mergedKcatList.eccodes = [kcatListFuzzy.eccodes(fuzzyRxns);cell(sum(prio2),1)];
-0107 mergedKcatList.wildcardLvl = [kcatListFuzzy.wildcardLvl(fuzzyRxns);nan(sum(prio2),1)];
-0108 mergedKcatList.origin = [kcatListFuzzy.origin(fuzzyRxns);nan(sum(prio2),1)];
-0109 end
function kcatList = readDLKcatOutput(model, outFile, modelAdapter)
-
-
DESCRIPTION
-
readDLKcatOutput
- Reads the DLKcat output file and constructs a kcatList structure, that
- can be used by selectKcatValue() to populate the ecModel with kcat
- values.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- outFile name and path of the DLKcat output file. (Optional,
- default is data/DLKcat.tsv from the obj.params.path
- folder specified in the modelAdapter)
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- kcatList structure array with list of DLKcat derived kcat values,
- with separate entries for each kcat value
- source 'DLKcat'
- rxns reaction identifiers
- genes gene identifiers
- substrate substrate names
- kcat predicted kcat value in /sec
-
- Usage:
- kcatList = readDLKcatOutput(model, outFile, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function kcatList = readDLKcatOutput(model, outFile, modelAdapter)
-0002 % readDLKcatOutput
-0003 % Reads the DLKcat output file and constructs a kcatList structure, that
-0004 % can be used by selectKcatValue() to populate the ecModel with kcat
-0005 % values.
-0006 %
-0007 % Input:
-0008 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0009 % outFile name and path of the DLKcat output file. (Optional,
-0010 % default is data/DLKcat.tsv from the obj.params.path
-0011 % folder specified in the modelAdapter)
-0012 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0013 % default model adapter).
-0014 %
-0015 % Output:
-0016 % kcatList structure array with list of DLKcat derived kcat values,
-0017 % with separate entries for each kcat value
-0018 % source 'DLKcat'
-0019 % rxns reaction identifiers
-0020 % genes gene identifiers
-0021 % substrate substrate names
-0022 % kcat predicted kcat value in /sec
-0023 %
-0024 % Usage:
-0025 % kcatList = readDLKcatOutput(model, outFile, modelAdapter)
-0026
-0027 if nargin < 3 || isempty(modelAdapter)
-0028 modelAdapter = ModelAdapterManager.getDefault();
-0029 if isempty(modelAdapter)
-0030 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0031 end
-0032 end
-0033 params = modelAdapter.params;
-0034
-0035 if nargin<2 || isempty(outFile)
-0036 fID = fopen(fullfile(params.path,'data','DLKcat.tsv'),'r');
-0037 else
-0038 fID = fopen(outFile);
-0039 end
-0040 DLKcatOutput = textscan(fID,'%s %s %s %s %s %s','Delimiter','\t','HeaderLines',1);
-0041 fclose(fID);
-0042
-0043 % Check that DLKcat output file and model match (not fool proof, but good enough)
-0044 [rxns, genes, subs, kcats] = deal(DLKcatOutput{[1,2,3,6]});
-0045
-0046 % Check if it contains any kcat values
-0047 if all(cellfun(@isempty,kcats)) || all(strcmpi(kcats,'NA'))
-0048 error('DLKcat file does not contain any kcat values, please run runDLKcat() first.')
-0049 end
-0050
-0051 % Check that all substrates are in the model
-0052 matchMets = ismember(subs,model.metNames);
-0053 if ~all(matchMets)
-0054 errorText = 'DLKcat was likely run with an input file that was generated from another ecModel, as the following substrates from DLKcat output cannot be found in model.metNames:';
-0055 dispEM(errorText,true,subs(~matchMets),false)
-0056 end
-0057
-0058 % Check that all reactions are in model.ec.rxns
-0059 matchRxns = ismember(rxns,model.ec.rxns);
-0060 if ~all(matchRxns)
-0061 errorText = 'DLKcat was likely run with an input file that was generated from another ecModel, as the following reactions from DLKcat output cannot be found in model.metNames:';
-0062 dispEM(errorText,true,rxns(~matchRxns),false)
-0063 end
-0064
-0065 % Filter out entries with no numeric value
-0066 noOutput = cellfun(@isempty,regexpi(kcats,'[0-9]'));
-0067 kcats = str2double(kcats(~noOutput));
-0068 rxns(noOutput) = [];
-0069 genes(noOutput) = [];
-0070 subs(noOutput) = [];
-0071
-0072 % Make kcatList structure
-0073 kcatList.source = 'DLKcat';
-0074 kcatList.rxns = rxns;
-0075 kcatList.genes = genes;
-0076 kcatList.substrates = subs;
-0077 kcatList.kcats = kcats;
-0078 end
removeStandardKcat
- Remove the "standard" pseudoenzyme and standard kcat and MW values, as
- they were introduced by getStandardKcat. Also standard kcat values that
- were assigned by getStandardKcat if fillZeroKcat was set to true are
- removed from model.ec.kcat. Both the model.ec and model.S structures
- are modified.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- that has standard kcat values implemented by
- getStandardKcat.
-
- Output:
- model ecModel without standard pseudoprotein and standard
- kcat values
-
- Usage:
- model = removeStandardKcat(model);
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function model = removeStandardKcat(model)
-0002 % removeStandardKcat
-0003 % Remove the "standard" pseudoenzyme and standard kcat and MW values, as
-0004 % they were introduced by getStandardKcat. Also standard kcat values that
-0005 % were assigned by getStandardKcat if fillZeroKcat was set to true are
-0006 % removed from model.ec.kcat. Both the model.ec and model.S structures
-0007 % are modified.
-0008 %
-0009 % Input:
-0010 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0011 % that has standard kcat values implemented by
-0012 % getStandardKcat.
-0013 %
-0014 % Output:
-0015 % model ecModel without standard pseudoprotein and standard
-0016 % kcat values
-0017 %
-0018 % Usage:
-0019 % model = removeStandardKcat(model);
-0020
-0021 % Remove standard enzyme from ec structure
-0022 stdEnzIdx = find(strcmpi(model.ec.enzymes, 'standard'));
-0023 if ~isempty(stdEnzIdx)
-0024 model.ec.genes(stdEnzIdx) = [];
-0025 model.ec.enzymes(stdEnzIdx) = [];
-0026 model.ec.mw(stdEnzIdx) = [];
-0027 model.ec.sequence(stdEnzIdx) = [];
-0028 if isfield(model.ec,'concs')
-0029 model.ec.concs(stdEnzIdx) = [];
-0030 end
-0031 rxnEnzIdx = find(model.ec.rxnEnzMat(:,stdEnzIdx));
-0032 model.ec.rxns(rxnEnzIdx) = [];
-0033 model.ec.kcat(rxnEnzIdx) = [];
-0034 model.ec.source(rxnEnzIdx) = [];
-0035 model.ec.notes(rxnEnzIdx) = [];
-0036 model.ec.eccodes(rxnEnzIdx) = [];
-0037 model.ec.rxnEnzMat(:,stdEnzIdx) = [];
-0038 model.ec.rxnEnzMat(rxnEnzIdx,:) = [];
-0039 end
-0040
-0041 % Remove standard kcat values and reapply kcat constraints for those
-0042 % specific reactions
-0043 stdKcatIdx = find(strcmpi(model.ec.source, 'standard'));
-0044 if ~isempty(stdKcatIdx)
-0045 model.ec.source(stdKcatIdx) = {''};
-0046 model.ec.kcat(stdKcatIdx) = 0;
-0047 model = applyKcatConstraints(model,stdKcatIdx);
-0048 end
-0049
-0050 % Remove standard protein, usage and gene from model structure
-0051 stdMetIdx = find(strcmpi(model.mets, 'prot_standard'));
-0052 if ~isempty(stdMetIdx)
-0053 model = removeMets(model,stdMetIdx,false,false,false,false);
-0054 end
-0055 stdProtEx = find(strcmpi(model.rxns, 'usage_prot_standard'));
-0056 if ~isempty(stdProtEx)
-0057 model = removeReactions(model,stdProtEx,false,false,false);
-0058 end
-0059 stdProtGene = find(strcmpi(model.genes, 'standard'));
-0060 if ~isempty(stdProtGene)
-0061 model = removeGenes(model,stdProtGene,false,false,false);
-0062 end
-0063 end
runDLKcat
- Runs DLKcat to predict kcat values from a Docker image. Once DLKcat is succesfully
- run, the DLKcatFile will be overwritten with the DLKcat
- output in the model-specific 'data' sub-folder taken from modelAdapter
- (e.g. GECKO/tutorials/tutorial_yeast-GEM/data/DLKcat.tsv)
-
- Input
- modelAdapter a loaded model adapter. (Optional, will otherwise use
- the default model adapter)
- filePath path to the DLKcat.tsv file. (Optional, will otherwise
- assume data/DLKcat.tsv)
-
- NOTE: 1. Requires Docker to be installed, and Docker Desktop running. Visit "https://www.docker.com"
- 2. Runtime will depend on whether the image is to be downloaded or not.
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function runDLKcat(modelAdapter,filePath)
-0002 % runDLKcat
-0003 % Runs DLKcat to predict kcat values from a Docker image. Once DLKcat is succesfully
-0004 % run, the DLKcatFile will be overwritten with the DLKcat
-0005 % output in the model-specific 'data' sub-folder taken from modelAdapter
-0006 % (e.g. GECKO/tutorials/tutorial_yeast-GEM/data/DLKcat.tsv)
-0007 %
-0008 % Input
-0009 % modelAdapter a loaded model adapter. (Optional, will otherwise use
-0010 % the default model adapter)
-0011 % filePath path to the DLKcat.tsv file. (Optional, will otherwise
-0012 % assume data/DLKcat.tsv)
-0013 %
-0014 % NOTE: 1. Requires Docker to be installed, and Docker Desktop running. Visit "https://www.docker.com"
-0015 % 2. Runtime will depend on whether the image is to be downloaded or not.
-0016
-0017 if nargin < 1 || isempty(modelAdapter)
-0018 modelAdapter = ModelAdapterManager.getDefault();
-0019 if isempty(modelAdapter)
-0020 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0021 end
-0022 end
-0023 params = modelAdapter.params;
-0024 % Make sure path is full, not relative
-0025 [~, params.path] = fileattrib(params.path);
-0026 params.path=params.path.Name;
-0027
-0028 if nargin < 2 || isempty(filePath)
-0029 filePath = fullfile(params.path,'data','DLKcat.tsv');
-0030 elseif strcmp(filePath(end),{'\','/'})
-0031 filePath = fullfile(filePath,'DLKcat.tsv');
-0032 end
-0033 filePath = checkFileExistence(filePath,1);
-0034
-0035 copyfile(filePath, fullfile(params.path,'data','tempDLKcat.tsv'));
-0036
-0037
-0038 %% Check and install requirements
-0039 % On macOS, Docker might not be properly loaded if MATLAB is started via
-0040 % launcher and not terminal.
-0041 if ismac
-0042 setenv('PATH', strcat('/usr/local/bin', ':', getenv("PATH")));
-0043 end
-0044
-0045 % Check if Docker is installed
-0046 [checks.docker.status, checks.docker.out] = system('docker --version');
-0047 if checks.docker.status ~= 0
-0048 error('Cannot find Docker, make sure it is installed. If it is, it might be required to start Matlab from the command-line instead of the launcher in order for Docker to be detected and used.')
-0049 end
-0050
-0051 disp('Running DLKcat prediction, this may take many minutes, especially the first time.')
-0052 status = system(['docker run --rm -v "' fullfile(params.path,'/data') '":/data ghcr.io/sysbiochalmers/dlkcat-gecko:0.1 /bin/bash -c "python DLKcat.py /data/tempDLKcat.tsv /data/tempDLKcatOutput.tsv"']);
-0053 delete(fullfile(params.path,'/data/tempDLKcat.tsv'));
-0054
-0055 if status == 0 && exist(fullfile(params.path,'data/tempDLKcatOutput.tsv'))
-0056 movefile(fullfile(params.path,'/data/tempDLKcatOutput.tsv'), filePath);
-0057 disp('DKLcat prediction completed.');
-0058 else
-0059 error('DLKcat encountered an error or it did not create any output file.')
-0060 end
-0061
function [model, rxnIdx] = selectKcatValue(model,kcatList,criteria,overwrite)
-
-
DESCRIPTION
-
selectKcatValue
- From a kcatList with predicted or suggested kcat values, where each
- reaction may have multiple entries, one kcat value is selected and
- written to model.ec.kcat. Zero values are discarded from the start. By
- default, the maximum value is chosen, but alternatives are available.
- The kcatList structure is an output of e.g. readDLKcatOutput,
- readGotEnzymesOutput, readManualKcatList.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- kcatList structure array with separate entries for each kcat value
- source e.g. 'DLKcat' or 'gotenzymes'
- rxns reaction identifiers, matching model.rxns
- genes gene identifiers, matching model.genes
- substrate substrates, matching model.mets
- kcat predicted kcat value in /sec
- criteria which kcat value should be selected if multiple values are
- provided. Options: 'max', 'min', 'median', 'mean'. (Opt,
- default 'max')
- overwrite whether existing kcat values should be overwritten.
- Options: 'true', 'false', 'ifHigher'. The last option will
- overwrite only if the new kcat value is higher. (Opt,
- default 'true')
-
- Output:
- model ecModel with updated model.ec.kcat and model.ec.source
- rxnIdx list of reaction indices (matching model.ec.rxns), to
- indicate which kcat values have been changed.
- Usage:
- [model, rxnIdx] = selectKcatValue(model,kcatList,criteria,overwrite)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function [model, rxnIdx] = selectKcatValue(model,kcatList,criteria,overwrite)
-0002 % selectKcatValue
-0003 % From a kcatList with predicted or suggested kcat values, where each
-0004 % reaction may have multiple entries, one kcat value is selected and
-0005 % written to model.ec.kcat. Zero values are discarded from the start. By
-0006 % default, the maximum value is chosen, but alternatives are available.
-0007 % The kcatList structure is an output of e.g. readDLKcatOutput,
-0008 % readGotEnzymesOutput, readManualKcatList.
-0009 %
-0010 % Input:
-0011 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0012 % kcatList structure array with separate entries for each kcat value
-0013 % source e.g. 'DLKcat' or 'gotenzymes'
-0014 % rxns reaction identifiers, matching model.rxns
-0015 % genes gene identifiers, matching model.genes
-0016 % substrate substrates, matching model.mets
-0017 % kcat predicted kcat value in /sec
-0018 % criteria which kcat value should be selected if multiple values are
-0019 % provided. Options: 'max', 'min', 'median', 'mean'. (Opt,
-0020 % default 'max')
-0021 % overwrite whether existing kcat values should be overwritten.
-0022 % Options: 'true', 'false', 'ifHigher'. The last option will
-0023 % overwrite only if the new kcat value is higher. (Opt,
-0024 % default 'true')
-0025 %
-0026 % Output:
-0027 % model ecModel with updated model.ec.kcat and model.ec.source
-0028 % rxnIdx list of reaction indices (matching model.ec.rxns), to
-0029 % indicate which kcat values have been changed.
-0030 % Usage:
-0031 % [model, rxnIdx] = selectKcatValue(model,kcatList,criteria,overwrite)
-0032
-0033 if nargin < 4
-0034 overwrite = 'true';
-0035 elseif islogical(overwrite)
-0036 if overwrite
-0037 overwrite = 'true';
-0038 else
-0039 overwrite = 'false';
-0040 end
-0041 end
-0042 if nargin < 3
-0043 criteria = 'max';
-0044 end
-0045
-0046 % Remove zero kcat values. Only adjusting fields that are used later.
-0047 removeZero = kcatList.kcats == 0;
-0048 kcatList.kcats(removeZero) = [];
-0049 kcatList.rxns(removeZero) = [];
-0050
-0051 % Map to model.ec.rxns
-0052 [sanityCheck,idxInModel] = ismember(kcatList.rxns,model.ec.rxns);
-0053 if ~all(sanityCheck)
-0054 error('Not all reactions in kcatList are found in model.ec.rxns')
-0055 end
-0056 % Make vector with single kcat value per reaction
-0057 idxInModelUnique = unique(idxInModel);
-0058 selectedKcats = zeros(numel(idxInModelUnique),1);
-0059 selectedSource = cell(numel(selectedKcats),1);
-0060 if ~isfield(kcatList,'kcatSource')
-0061 kcatList.kcatSource = cell(numel(kcatList.kcats),1);
-0062 kcatList.kcatSource(:) = {kcatList.source};
-0063 end
-0064 for i=1:numel(idxInModelUnique)
-0065 ind = idxInModelUnique(i);
-0066 idxMatch = find(idxInModel == ind);
-0067 % Choose the maximum number
-0068 switch criteria
-0069 case'max'
-0070 [selectedKcats(i),j] = max(kcatList.kcats(idxMatch));
-0071 case'min'
-0072 [selectedKcats(i),j] = min(kcatList.kcats(idxMatch));
-0073 case'median'
-0074 [selectedKcats(i),j] = median(kcatList.kcats(idxMatch));
-0075 case'mean'
-0076 [selectedKcats(i),j] = mean(kcatList.kcats(idxMatch));
-0077 otherwise
-0078 error('Invalid criteria specified')
-0079 end
-0080 selectedSource(i) = kcatList.kcatSource(idxMatch(j));
-0081 end
-0082
-0083 % Populate model.ec.kcat
-0084 switch overwrite
-0085 case'true'
-0086 model.ec.kcat(idxInModelUnique) = selectedKcats;
-0087 model.ec.source(idxInModelUnique) = selectedSource;
-0088 case'false'
-0089 emptyKcats = find(model.ec.kcat == 0);
-0090 [idxInModelUnique,whickKcats] = intersect(idxInModelUnique,emptyKcats,'stable');
-0091 model.ec.kcat(idxInModelUnique) = selectedKcats(whickKcats);
-0092 model.ec.source(idxInModelUnique) = selectedSource(whickKcats);
-0093
-0094 case'ifHigher'
-0095 higherKcats = model.ec.kcat(idxInModelUnique) < selectedKcats;
-0096 selectedKcats(~higherKcats) = [];
-0097 selectedSource(~higherKcats) = [];
-0098 idxInModelUnique(~higherKcats) = [];
-0099 model.ec.kcat(idxInModelUnique) = selectedKcats;
-0100 model.ec.source(idxInModelUnique) = selectedSource;
-0101 otherwise
-0102 error('Invalid overwrite flag specified')
-0103 end
-0104 rxnIdx = idxInModelUnique;
-0105 end
function writtenTable = writeDLKcatInput(model, ecRxns, modelAdapter, onlyWithSmiles, filename, overwrite)
-
-
DESCRIPTION
-
writeDLKcatInput
- Prepares the input for DLKcat, and writes it to data/DLKcat.tsv
- in the obj.params.path specified in the ModelAdapter.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- ecRxns for which reactions (from model.ec.rxns) DLKcat should
- predict kcat values, provided as logical vector with
- same length as model.ec.rxns. (Opt, default is all
- reactions)
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
- onlyWithSmiles logical whether to only include metabolites with SMILES
- (optional, default true)
- filename path to the input file, including the filename and .tsv
- extension (Optional, default is data/DLKcat.tsv from
- the obj.params.path folder specified in the modelAdapter)
- overwrite logical whether existing file should be overwritten.
- (Optional, default false, to prevent overwriting file
- that already contains DLKcat-predicted kcat values).
-
- Output:
- writtenTable The table written, mainly to be used for testing purposes.
-
- Usage:
- writtenTable = writeDLKcatInput(model, ecRxns, modelAdapter, onlyWithSmiles, filename, overwrite)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function writtenTable = writeDLKcatInput(model, ecRxns, modelAdapter, onlyWithSmiles, filename, overwrite)
-0002 % writeDLKcatInput
-0003 % Prepares the input for DLKcat, and writes it to data/DLKcat.tsv
-0004 % in the obj.params.path specified in the ModelAdapter.
-0005 %
-0006 % Input:
-0007 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0008 % ecRxns for which reactions (from model.ec.rxns) DLKcat should
-0009 % predict kcat values, provided as logical vector with
-0010 % same length as model.ec.rxns. (Opt, default is all
-0011 % reactions)
-0012 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0013 % default model adapter).
-0014 % onlyWithSmiles logical whether to only include metabolites with SMILES
-0015 % (optional, default true)
-0016 % filename path to the input file, including the filename and .tsv
-0017 % extension (Optional, default is data/DLKcat.tsv from
-0018 % the obj.params.path folder specified in the modelAdapter)
-0019 % overwrite logical whether existing file should be overwritten.
-0020 % (Optional, default false, to prevent overwriting file
-0021 % that already contains DLKcat-predicted kcat values).
-0022 %
-0023 % Output:
-0024 % writtenTable The table written, mainly to be used for testing purposes.
-0025 %
-0026 % Usage:
-0027 % writtenTable = writeDLKcatInput(model, ecRxns, modelAdapter, onlyWithSmiles, filename, overwrite)
-0028
-0029 [geckoPath, ~] = findGECKOroot();
-0030
-0031 if nargin<2 || isempty(ecRxns)
-0032 ecRxns = true(numel(model.ec.rxns),1);
-0033 elseif ~logical(ecRxns)
-0034 error('ecRxns should be provided as logical vector')
-0035 elseif numel(ecRxns)~=numel(model.ec.rxns)
-0036 error('Length of ecRxns is not the same as model.ec.rxns')
-0037 end
-0038 ecRxns = find(ecRxns); % Change to indices
-0039
-0040 if nargin < 3 || isempty(modelAdapter)
-0041 modelAdapter = ModelAdapterManager.getDefault();
-0042 if isempty(modelAdapter)
-0043 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0044 end
-0045 end
-0046 params = modelAdapter.params;
-0047
-0048 if nargin<4 || isempty(onlyWithSmiles)
-0049 onlyWithSmiles=true;
-0050 end
-0051
-0052 if nargin<5 || isempty(filename)
-0053 filename = fullfile(params.path,'data','DLKcat.tsv');
-0054 elseif ~endsWith(filename,'.tsv')
-0055 error('If filename is provided, it should include the .tsv extension.')
-0056 end
-0057
-0058 if nargin<6 || isempty(overwrite) || ~overwrite % If is true
-0059 if exist(filename,'file')
-0060 error([filename ' already exists, either delete it first, or set the overwrite input argument as true'])
-0061 end
-0062 end
-0063
-0064 if ~model.ec.geckoLight
-0065 origRxns = model.ec.rxns;
-0066 else
-0067 origRxns = extractAfter(model.ec.rxns,4);
-0068 end
-0069 origRxnsToInclude = origRxns(ecRxns);
-0070
-0071 % Map back to original reactions, to extract substrates
-0072 [sanityCheck,origRxnIdxs] = ismember(origRxnsToInclude,model.rxns);
-0073 if ~all(sanityCheck)
-0074 error('Not all reactions in model.ec.rxns are found in model.rxns')
-0075 end
-0076
-0077 % Ignore selected metabolites (metal ions, proteins etc.). First check by
-0078 % name (case insensitive, without white spaces and special characters),
-0079 % then also try to match with metSmiles (if available).
-0080 metsNoSpecialChars = lower(regexprep(model.metNames,'[^0-9a-zA-Z]+',''));
-0081 if exist(fullfile(params.path,'data','DLKcatIgnoreMets.tsv'),'file')
-0082 fID = fopen(fullfile(params.path,'data','DLKcatIgnoreMets.tsv'));
-0083 else
-0084 fID = fopen(fullfile(geckoPath,'databases','DLKcatIgnoreMets.tsv'));
-0085 end
-0086 fileData = textscan(fID,'%s %s','delimiter','\t');
-0087 fclose(fID);
-0088 [ignoreMets, ignoreSmiles] = deal(fileData{[1,2]});
-0089 ignoreMets = lower(regexprep(ignoreMets,'[^0-9a-zA-Z]+',''));
-0090 ignoreSmiles(cellfun(@isempty,ignoreSmiles)) = [];
-0091
-0092 ignoreMetsIdx = logical(ismember(metsNoSpecialChars,ignoreMets));
-0093 if isfield(model,'metSmiles')
-0094 ignoreMetsIdx = ignoreMetsIdx | logical(ismember(model.metSmiles,ignoreSmiles));
-0095 end
-0096 % Also leave out protein-usage pseudometabolites
-0097 ignoreMetsIdx = ignoreMetsIdx | startsWith(model.mets,'prot_');
-0098 reducedS = model.S;
-0099 reducedS(ignoreMetsIdx,:) = 0;
-0100
-0101 % Ignore currency metabolites if they occur in pairs. First check by
-0102 % name (case insensitive, without white spaces and special characters),
-0103 % then also try to match with metSmiles (if available).
-0104 if exist(fullfile(params.path,'data','DLKcatCurrencyMets.tsv'),'file')
-0105 fID = fopen(fullfile(params.path,'data','DLKcatCurrencyMets.tsv'));
-0106 else
-0107 fID = fopen(fullfile(geckoPath,'databases','DLKcatCurrencyMets.tsv'));
-0108 end
-0109 fileData = textscan(fID,'%s %s','delimiter','\t');
-0110 fclose(fID);
-0111 [currencyMets(:,1), currencyMets(:,2)] = deal(fileData{[1,2]});
-0112 currencyMets = lower(regexprep(currencyMets,'[^0-9a-zA-Z]+',''));
-0113
-0114 for i=1:size(currencyMets,1)
-0115 subs = strcmp(currencyMets(i,1),metsNoSpecialChars);
-0116 prod = strcmp(currencyMets(i,2),metsNoSpecialChars);
-0117 [~,subsRxns]=find(reducedS(subs,:));
-0118 [~,prodRxns]=find(reducedS(prod,:));
-0119 pairRxns = intersect(subsRxns,prodRxns);
-0120 tempRedS=reducedS;
-0121 tempRedS([find(subs);find(prod)],pairRxns) = 0;
-0122 % Do not remove currency mets if no substrate remains
-0123 rxnsWithRemainingSubstrates = any(tempRedS(:,pairRxns) < 0,1);
-0124 reducedS([find(subs);find(prod)],intersect(pairRxns,pairRxns(rxnsWithRemainingSubstrates))) = 0;
-0125 end
-0126
-0127 %filter out the reactions we're not interested in - will solve the problem for both full and light
-0128 clearedRedS = reducedS(:,origRxnIdxs);
-0129 rxnsToClear = true(length(origRxnIdxs),1);
-0130 rxnsToClear(ecRxns) = false;
-0131 clearedRedS(:,rxnsToClear) = 0;
-0132
-0133 % Enumerate all substrates for each reaction
-0134 [substrates, reactions] = find(clearedRedS<0); %the reactions here are in model.ec.rxns space
-0135
-0136 % Enumerate all proteins for each reaction
-0137 [proteins, ecRxns] = find(transpose(model.ec.rxnEnzMat(reactions,:)));
-0138
-0139 % Prepare output
-0140 out(1,:) = model.ec.rxns(reactions(ecRxns));
-0141 out(2,:) = model.ec.genes(proteins);
-0142 out(3,:) = model.metNames(substrates(ecRxns));
-0143 if isfield(model,'metSmiles')
-0144 out(4,:) = model.metSmiles(substrates(ecRxns));
-0145 else
-0146 out(4,:) = cell(numel(substrates(ecRxns)),1);
-0147 end
-0148
-0149 out(5,:) = model.ec.sequence(proteins);
-0150 if onlyWithSmiles
-0151 out(:,cellfun(@isempty,out(4,:))) = [];
-0152 else
-0153 out(4,cellfun(@isempty,out(4,:))) = {'None'};
-0154 end
-0155 out(6,:) = cell(numel(out(1,:)),1);
-0156 out(6,:) = {'NA'};
-0157
-0158 % Write file
-0159 fID = fopen(filename,'w');
-0160 fprintf(fID,'%s\t%s\t%s\t%s\t%s\t%s\n',out{:});
-0161 fclose(fID);
-0162 fprintf('Model-specific DLKcat input stored at %s\n',filename);
-0163
-0164 writtenTable = out;
-0165 end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- MW = calculateMW(sequence)
- Calculates de molecular weight of a protein.
-
- INPUT: Sequence (can include extra things such as spaces or numbers).
- OUTPUT: Molecular weight number
-
- BenjamÃn Sánchez. Last edited: 2015-04-13
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [EC,conflicts] = findECInDB(gene_set, DBecNum, DBMW, geneIndex, geneHashMap)
-
-
DESCRIPTION
-
findECInDB
- Collects enzyme codes for genes from the UniProt or KEGGdatabase.
-
- Input:
- gene_set genes from a given metabolic reaction
- DBecNum array of EC numbers
- DBMW array of molecular weights in Da
- geneIndex as generated by getECfromDatabase
- geneHashMap as generated by getECfromDatabase
-
- Output:
- EC all EC numbers found for the genes in gene_set
- conflicts genes with multiple protein matches
-
- Usage:
- [EC,conflicts] = findECInDB(gene_set, DBecNum, DBMW, geneIndex, geneHashMap)
0001 function [EC,conflicts] = findECInDB(gene_set, DBecNum, DBMW, geneIndex, geneHashMap)
-0002 % findECInDB
-0003 % Collects enzyme codes for genes from the UniProt or KEGGdatabase.
-0004 %
-0005 % Input:
-0006 % gene_set genes from a given metabolic reaction
-0007 % DBecNum array of EC numbers
-0008 % DBMW array of molecular weights in Da
-0009 % geneIndex as generated by getECfromDatabase
-0010 % geneHashMap as generated by getECfromDatabase
-0011 %
-0012 % Output:
-0013 % EC all EC numbers found for the genes in gene_set
-0014 % conflicts genes with multiple protein matches
-0015 %
-0016 % Usage:
-0017 % [EC,conflicts] = findECInDB(gene_set, DBecNum, DBMW, geneIndex, geneHashMap)
-0018
-0019 %Preallocate function variables
-0020 conflicts = cell(1,2);
-0021
-0022 %Split the gene set and match each gene:
-0023 %gene_set = strsplit(grRule,' and '); %this string splitting together with getSimpleGeneSets takes 2.6 s, that is ok.
-0024 EC_set = cell(size(gene_set));
-0025
-0026 %find index indices of all genes in one call
-0027
-0028 %this loop is slow
-0029 for j = 1:length(gene_set)
-0030 gene = gene_set{j};
-0031 matches = [];
-0032 %Get the indexes for all of the proteins related to gene
-0033 if isKey(geneHashMap,gene) %annoyingly, this seems to be needed
-0034 matchInd = cell2mat(values(geneHashMap,gene_set(j)));
-0035 matches = geneIndex{matchInd};
-0036 end
-0037 if ~isempty(matches)
-0038 %Get the indexes of the matched protein(s) with non-empty EC#s
-0039 nonEmpty = matches(~cellfun(@isempty,DBecNum(matches)));
-0040 if ~isempty(nonEmpty)
-0041 [geneECs,ia] = unique(DBecNum(nonEmpty),'stable');
-0042 %For genes with multiple proteins associated to several
-0043 %non-empty ecNumbers
-0044 if length(geneECs)>1
-0045 indexes = nonEmpty(ia);
-0046 ecNum = DBecNum(indexes);
-0047 %Save first match
-0048 EC_set{j} = getECstring(EC_set{j},ecNum{1});
-0049 %Save additional matches as potential conflicts
-0050 if ~ismember(gene,conflicts{1})
-0051 conflicts{1} = [conflicts{1}; {gene}];
-0052 conflicts{2} = [conflicts{2}; {indexes}];
-0053 end
-0054 else
-0055 %If there is a single unique ec number for the matched
-0056 %protein(s), then choose the lightest protein
-0057 [~,minW] = min(DBMW(nonEmpty));
-0058 matches = nonEmpty(minW);
-0059 EC_set{j} = getECstring(EC_set{j},DBecNum{matches});
-0060 end
-0061 end
-0062 end
-0063
-0064 if isempty(EC_set{j})
-0065 EC_set{j} = '';
-0066 end
-0067 end
-0068
-0069 %EC: Find union and intersection between all units (only applies for
-0070 %complexes, i.e. length(EC_set) > 1):
-0071 uni_EC = strsplit(EC_set{1},' ');
-0072 int_EC = uni_EC;
-0073 for j = 2:length(EC_set)
-0074 other_EC = strsplit(EC_set{j},' ');
-0075 if isempty(uni_EC)
-0076 uni_EC = other_EC;
-0077 int_EC = other_EC;
-0078 elseif ~isempty(other_EC)
-0079 uni_EC = compare_wild([uni_EC other_EC]);
-0080 int_EC = intersection(int_EC,other_EC);
-0081 end
-0082 end
-0083 %Use the intersection found, if any. If not, use the union:
-0084 if isempty(int_EC)
-0085 EC_set = uni_EC;
-0086 else
-0087 EC_set = int_EC;
-0088 end
-0089
-0090 %Add new codes as new possible isozymes and remove the initial "EC" text:
-0091
-0092 for i = 1:length(EC_set)
-0093 if ~isempty(EC_set{i})
-0094 EC_set{i} = extractAfter(EC_set{i},2);%remove 2 first chars
-0095 end
-0096 end
-0097
-0098 %EC_set = cellfun(@(x) extractAfter(x,2),EC_set,'UniformOutput',false);%remove 2 first chars
-0099 EC = strjoin(EC_set,';');
-0100
-0101
-0102 %Delete repeated Uniprots (and the corresponding ECs):
-0103 %EC(deleted) = []; %Not sure if this needs to be done somehow, don't think so
-0104
-0105 end
-0106
-0107 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0108 function int_EC = intersection(prev_EC,new_EC)
-0109 %Finds the common elements between two cell arrays, if any. Also considers
-0110 %wildcards (e.g. if 'EC1.1.1.1' is in one array and 'EC1.1.1.-' is in the
-0111 %other one, then 'EC1.1.1.1' is added to the intersection).
-0112 int_EC = {};
-0113 for i = 1:length(prev_EC)
-0114 for j = 1:length(new_EC)
-0115 new_int = compare_wild({prev_EC{i} new_EC{j}});
-0116 if length(new_int) == 1
-0117 int_EC = [int_EC new_int];
-0118 end
-0119 end
-0120 end
-0121 end
-0122 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0123 function int_EC = compare_wild(EC)
-0124 %Goes through a cell array of EC numbers, and erases any repetitions,
-0125 %considering also wildcards (e.g. will erase 'EC1.2.3.-' if 'EC1.2.3.4' is
-0126 %already present).
-0127
-0128 %Trim all EC numbers of wild cards (e.g. 'EC1.2.-.-' -> 'EC1.2.'):
-0129 EC_trimmed = EC;
-0130 for i = 1:length(EC)
-0131 %Add a final dot for avoiding issues (e.g. 'EC1.1.1.1' & 'EC1.1.1.12'):
-0132 ECi = [EC{i} '.'];
-0133 pos = strfind(ECi,'-');
-0134 if ~isempty(pos)
-0135 ECi = ECi(1:pos(1)-1);
-0136 end
-0137 EC_trimmed{i} = ECi;
-0138 end
-0139
-0140 %Compare all EC numbers between them to find if 1 fits in the other:
-0141 non_repeated = true(1,length(EC));
-0142 for i = 1:length(EC)-1
-0143 for j = i+1:length(EC)
-0144 ECi = EC_trimmed{i};
-0145 ECj = EC_trimmed{j};
-0146 %If ECj fits in ECi then ECj can be disregarded:
-0147 if strfind(ECi,ECj)
-0148 non_repeated(j) = false;
-0149 %Else, if ECi fits in ECj then ECi can be disregarded:
-0150 elseif strfind(ECj,ECi)
-0151 non_repeated(i) = false;
-0152 end
-0153 end
-0154 end
-0155
-0156 int_EC = EC(non_repeated);
-0157
-0158 end
function model = getECfromDatabase(model, ecRxns, action, modelAdapter)
-
-
DESCRIPTION
-
getECfromDatabase
- Populates the model.ec.eccodes field with enzyme codes that are
- extracted from UniProt and KEGG databases, as assigned to the proteins
- that catalyze the specific reactions.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- ecRxns logical of length model.ec.rxns that specifies which
- model.ec.eccodes entries should be queried. Exiting
- values in model.ec.eccodes will be wiped. Entries that
- are indicated by false will be kept and not modified by
- this function (optional, by default all model.ec.eccodes
- entries are populated by this function)
- action response action if multiple proteins with different EC
- numbers are found for a given gene in a metabolic
- reaction (optional, default 'display')
- - 'display' displays all found multiplicities
- - 'ignore' ignore multiplicities and use the protein
- with the lowest index in the database.
- - 'add' adds all the multiple proteins as
- isozymes for the given reaction
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- model ecModel with populated model.ec.eccodes
-
- Usage:
- model = getECfromDatabase(model, ecRxns, action, modelAdapter)
0001 function model = getECfromDatabase(model, ecRxns, action, modelAdapter)
-0002 % getECfromDatabase
-0003 % Populates the model.ec.eccodes field with enzyme codes that are
-0004 % extracted from UniProt and KEGG databases, as assigned to the proteins
-0005 % that catalyze the specific reactions.
-0006 %
-0007 % Input:
-0008 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0009 % ecRxns logical of length model.ec.rxns that specifies which
-0010 % model.ec.eccodes entries should be queried. Exiting
-0011 % values in model.ec.eccodes will be wiped. Entries that
-0012 % are indicated by false will be kept and not modified by
-0013 % this function (optional, by default all model.ec.eccodes
-0014 % entries are populated by this function)
-0015 % action response action if multiple proteins with different EC
-0016 % numbers are found for a given gene in a metabolic
-0017 % reaction (optional, default 'display')
-0018 % - 'display' displays all found multiplicities
-0019 % - 'ignore' ignore multiplicities and use the protein
-0020 % with the lowest index in the database.
-0021 % - 'add' adds all the multiple proteins as
-0022 % isozymes for the given reaction
-0023 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0024 % default model adapter).
-0025 %
-0026 % Output:
-0027 % model ecModel with populated model.ec.eccodes
-0028 %
-0029 % Usage:
-0030 % model = getECfromDatabase(model, ecRxns, action, modelAdapter)
-0031
-0032 if nargin < 2 || isempty(ecRxns)
-0033 ecRnxs = true(numel(model.ec.rxns),1);
-0034 end
-0035
-0036 if nargin < 3 || isempty(action)
-0037 action = 'display';
-0038 end
-0039
-0040 if nargin < 4 || isempty(modelAdapter)
-0041 modelAdapter = ModelAdapterManager.getDefault();
-0042 if isempty(modelAdapter)
-0043 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0044 end
-0045 end
-0046 params = modelAdapter.getParameters();
-0047
-0048 rxnEnzMat = model.ec.rxnEnzMat;
-0049 genes = modelAdapter.getUniprotCompatibleGenes(model.ec.genes);
-0050
-0051 data = loadDatabases('both', modelAdapter);
-0052 uniprot = data.uniprot;
-0053 kegg = data.kegg;
-0054
-0055 modelGenes = modelAdapter.getUniprotIDsFromTable(model.genes);
-0056 DBgenesUniprot = data.uniprot.genes;
-0057 if ~isequal(modelGenes,model.genes)
-0058 [Lia,Locb] = ismember(modelGenes,uniprot.ID);
-0059 DBgenesUniprot(Locb(Lia)) = model.genes(Lia);
-0060 keepEntry = unique(Locb(Lia));
-0061 DBgenesUniprot = DBgenesUniprot(keepEntry);
-0062 else
-0063 keepEntry = true(numel(DBgenesUniprot),1);
-0064 end
-0065 DBecNumUniprot = data.uniprot.eccodes(keepEntry);
-0066 DBMWUniprot = data.uniprot.MW(keepEntry);
-0067 %Build an index from gene to prot for faster processing later
-0068 [geneIndexUniprot,geneHashMapUniprot] = hashGeneToProt(DBgenesUniprot);
-0069
-0070 if ~isempty(kegg)
-0071 DBgenesKEGG = data.kegg.genes;
-0072 DBecNumKEGG = data.kegg.eccodes;
-0073 DBMWKEGG = data.kegg.MW;
-0074 [geneIndexKEGG,geneHashMapKEGG] = hashGeneToProt(DBgenesKEGG);
-0075 end
-0076 n = size(rxnEnzMat,1);
-0077
-0078 eccodes = cell(n,1);
-0079 eccodes(:)= {''};
-0080 conflicts = cell(1,4);
-0081
-0082 rxnEnzMat = logical(rxnEnzMat);
-0083 progressbar('Assigning EC numbers from database')
-0084 for i = 1:n
-0085 gns = genes(rxnEnzMat(i,:).');
-0086 if ~isempty(gns)
-0087 %Find match in Uniprot:
-0088 [new_EC,multGenes] = findECInDB(gns,DBecNumUniprot,DBMWUniprot,geneIndexUniprot,geneHashMapUniprot);
-0089 if ~isempty(new_EC)
-0090 DBase = 'uniprot';
-0091 if ~isempty(multGenes{1})
-0092 multGenes{3} = DBase;
-0093 end
-0094 end
-0095 if ~isempty(kegg) && (isempty(new_EC) || endsWith(new_EC,'-'))
-0096 %Find match in KEGG
-0097 [new_EC_kegg,multGenes] = findECInDB(gns,DBecNumKEGG,DBMWKEGG,geneIndexKEGG,geneHashMapKEGG);
-0098 if ~isempty(new_EC_kegg)
-0099 DBase = 'kegg';
-0100 if ~isempty(multGenes{1})
-0101 multGenes{3} = DBase;
-0102 end
-0103 new_EC=new_EC_kegg;
-0104 end
-0105 end
-0106 eccodes{i} = new_EC;
-0107
-0108 if ~isempty(multGenes{1})
-0109 %Rxn index
-0110 conflicts{1} = [conflicts{1};i];
-0111 %Gene IDs
-0112 conflicts{2} = [conflicts{2};multGenes{1}];
-0113 %Indexes in DB
-0114 conflicts{3} = [conflicts{3};multGenes{2}];
-0115 %DB name
-0116 conflicts{4} = [conflicts{4};{multGenes{3}}];
-0117
-0118 %{ I don't understand the purpose of this, let's skip it for now
-0119 %if strcmpi(action,'add')
-0120 % if strcmpi(DBase,'swissprot')
-0121 % [uni,EC,MW,Genes] = addMultipleMatches(uni,EC,MW,Genes,multGenes,swissprot);
-0122 % elseif strcmpi(DBase,'KEGG')
-0123 % [uni,EC,MW,Genes] = addMultipleMatches(uni,EC,MW,Genes,multGenes,kegg);
-0124 % end
-0125 %end
-0126 %}
-0127 end
-0128 end
-0129 progressbar(i/n)
-0130 end
-0131
-0132 %Display error message with the multiple gene-protein matches found
-0133 if strcmpi(action,'display') && ~isempty(conflicts{1})
-0134 displayErrorMessage(conflicts,uniprot,kegg)
-0135 end
-0136
-0137 if nargin < 2 || isempty(ecRxns) || all(ecRxns)
-0138 model.ec.eccodes = eccodes;
-0139 else
-0140 if ~isfield(model.ec,'eccodes')
-0141 model.ec.eccodes(1:numel(model.ec.rxns),1) = {''};
-0142 end
-0143 %Probably faster to subset with ecRxns in the beginning of the script,
-0144 %but this was at the moment simpler to implement.
-0145 model.ec.eccodes(ecRxns) = eccodes(ecRxns);
-0146 end
-0147
-0148 function displayErrorMessage(conflicts,uniprot,kegg)
-0149 STR = ['\n ' num2str(length(conflicts{1})) ' genes with multiple associated proteins were found, please'];
-0150 STR = [STR, ' revise case by case in the uniprot and kegg files:\n\n'];
-0151 for i=1:length(conflicts{1})
-0152 if strcmpi(conflicts{4}{i},'uniprot')
-0153 DB = uniprot.ID;
-0154 else
-0155 DB = kegg.uniprot;
-0156 end
-0157 proteins = DB(conflicts{3}{i});
-0158 STR = [STR, '- gene: ' conflicts{2}{i} ' Proteins: ' strjoin(proteins) '\n'];
-0159 end
-0160 STR = [STR, '\nIf a wrongly annotated case was found then call the '];
-0161 STR = [STR, 'getECfromDatabase.m function again with the option action'];
-0162 STR = [STR, '= ignore\n\n'];
-0163 STR = [STR, 'If the conflicting proteins are desired to be kept as isozymes'];
-0164 STR = [STR, ' then call the getECfromDatabase.m function'];
-0165 STR = [STR, ' again with the option action = add\n'];
-0166 error(sprintf(STR))
-0167 end
-0168
-0169 function [geneIndex,geneHashMap]=hashGeneToProt(proteinDB)
-0170
-0171 [x,y] = size(proteinDB);
-0172 genesForIndex = reshape(proteinDB, x*y, 1);
-0173 genesForIndex = genesForIndex(~cellfun(@isempty, genesForIndex));
-0174 genesForIndex = unique(genesForIndex);
-0175 geneIndex = cell(length(genesForIndex),1);
-0176 geneHashMap = containers.Map(genesForIndex,1:length(genesForIndex));
-0177 protIndices = 1:length(proteinDB(:,1));
-0178 for i = 1:y
-0179 tmp1 = proteinDB(:,i);
-0180 sel = ~cellfun(@isempty, tmp1);
-0181 indices = cell2mat(values(geneHashMap,tmp1(sel)));
-0182 protIndicesSel = protIndices(sel);
-0183 for j = 1:length(indices)
-0184 geneIndex{indices(j)} = [geneIndex{indices(j)};protIndicesSel(j)];
-0185 end
-0186 end
-0187 end
-0188 end
function [model, invalidEC, invalidECpos] = getECfromGEM(model, ecRxns)
-
-
DESCRIPTION
-
getECfromGEM
- Use the model.eccodes to populates the model.ec.eccodes field. EC
- numbers that are not formatted as four numbers separated by periods,
- possibly with trailing wildcards. Examples: 1.2.3.4 or 1.2.3.- while
- invalid EC numbers are 1.2.3 or 1_2_3_4. Multiple EC numbers are
- separated by ; for instance 1.2.3.4;1.2.3.5 not 1.2.3.4|1.2.3.5.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- ecRxns logical of length model.ec.rxns that specifies for
- which reactions the existing model.ec.eccodes entry
- should be kept and not modified by this function
- (optional, by default all model.ec.eccodes entries
- are populated by this function)
-
- Output:
- model ecModel with populated model.ec.eccodes
- invalidEC incorrectly formatted EC numbers
- invalidECpos position of invalidEC in model.eccodes
-
- Usage:
- [model, invalidEC, invalidECpos] = getECfromGEM(model, ecRxns)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function [model, invalidEC, invalidECpos] = getECfromGEM(model, ecRxns)
-0002 % getECfromGEM
-0003 % Use the model.eccodes to populates the model.ec.eccodes field. EC
-0004 % numbers that are not formatted as four numbers separated by periods,
-0005 % possibly with trailing wildcards. Examples: 1.2.3.4 or 1.2.3.- while
-0006 % invalid EC numbers are 1.2.3 or 1_2_3_4. Multiple EC numbers are
-0007 % separated by ; for instance 1.2.3.4;1.2.3.5 not 1.2.3.4|1.2.3.5.
-0008 %
-0009 % Input:
-0010 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0011 % ecRxns logical of length model.ec.rxns that specifies for
-0012 % which reactions the existing model.ec.eccodes entry
-0013 % should be kept and not modified by this function
-0014 % (optional, by default all model.ec.eccodes entries
-0015 % are populated by this function)
-0016 %
-0017 % Output:
-0018 % model ecModel with populated model.ec.eccodes
-0019 % invalidEC incorrectly formatted EC numbers
-0020 % invalidECpos position of invalidEC in model.eccodes
-0021 %
-0022 % Usage:
-0023 % [model, invalidEC, invalidECpos] = getECfromGEM(model, ecRxns)
-0024
-0025 if ~isfield(model,'eccodes')
-0026 error('The model has no model.eccodes field.')
-0027 end
-0028
-0029 %Need to remove the prefix of GECKO light rxn names in the ec structure
-0030 if ~model.ec.geckoLight
-0031 rxnNames = model.ec.rxns;
-0032 else
-0033 rxnNames = extractAfter(model.ec.rxns, 4);
-0034 end
-0035
-0036 [~,rxnIdxs] = ismember(rxnNames,model.rxns);
-0037
-0038 % Check if eccodes are valid
-0039 eccodes = model.eccodes;
-0040 invalidEC = regexprep(eccodes,'(\d\.(\w|-)+\.(\w|-)+\.(\w|-)+)(;\w+\.(\w|-)+\.(\w|-)+\.(\w|-)+)*(.*)','$3');
-0041 invalidEC = ~cellfun(@isempty,invalidEC);
-0042 invalidECpos = find(invalidEC);
-0043 if any(invalidECpos)
-0044 invalidEC = model.eccodes(invalidEC);
-0045 if nargout<2
-0046 fprintf('Skipped incorrectly formatted EC numbers, rerun getECfromGEM with all outputs to get a list.\n')
-0047 else
-0048 fprintf('Skipped incorrectly formatted EC numbers.\n')
-0049 end
-0050 eccodes(invalidECpos)={''};
-0051 else
-0052 invalidEC = [];
-0053 end
-0054 if nargin<2 || all(ecRxns)
-0055 model.ec.eccodes = eccodes(rxnIdxs);
-0056 else
-0057 if ~isfield(model.ec,'eccodes')
-0058 model.ec.eccodes(1:numel(model.ec.rxns),1) = {''};
-0059 end
-0060 model.ec.eccodes(ecRxns) = eccodes(rxnIdxs(ecRxns));
-0061 end
-0062 end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- EC_set = getECstring(EC_set,ecNumbers)
-
- Provides a single string with all the EC numbers associated to a given
- protein with according to the next format: "ECX.X.X.X ECX.X.X.X ECX.X.X.X"
-
- Ivan Domenzain. Last edited: 2018-09-06
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-In case of several ec numbers for the same protein
0001 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0002 % EC_set = getECstring(EC_set,ecNumbers)
-0003 %
-0004 % Provides a single string with all the EC numbers associated to a given
-0005 % protein with according to the next format: "ECX.X.X.X ECX.X.X.X ECX.X.X.X"
-0006 %
-0007 % Ivan Domenzain. Last edited: 2018-09-06
-0008 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0009 function EC_set = getECstring(EC_set,ecNumbers)
-0010 %In case of several ec numbers for the same protein
-0011 new_EC_set = replace(strsplit(ecNumbers,' '),';','');
-0012 for l = 1:length(new_EC_set)
-0013 EC_set = [EC_set 'EC' new_EC_set{l}];
-0014 if l<length(new_EC_set)
-0015 EC_set = [EC_set ' '];
-0016 end
-0017 end
-0018 end
0001 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0002 function [KCATcell, SAcell] = loadBRENDAdata(modelAdapter)
-0003
-0004 if nargin < 1 || isempty(modelAdapter)
-0005 modelAdapter = ModelAdapterManager.getDefault();
-0006 if isempty(modelAdapter)
-0007 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0008 end
-0009 end
-0010
-0011 basePath = modelAdapter.getBrendaDBFolder();
-0012 KCAT_file = fullfile(basePath,'max_KCAT.txt');
-0013 SA_file = fullfile(basePath,'max_SA.txt');
-0014 MW_file = fullfile(basePath,'max_MW.txt');
-0015
-0016 %Extract BRENDA DATA from files information
-0017 KCATcell = openDataFile(KCAT_file,1);
-0018 scalingFactor = 1/60; %[umol/min/mg] -> [mmol/s/g] Old: 60 [umol/min/mg] -> [mmol/h/g]
-0019 SA = openDataFile(SA_file,scalingFactor);
-0020 scalingFactor = 1/1000; %[g/mol] -> [g/mmol]
-0021 MW = openDataFile(MW_file,scalingFactor);
-0022
-0023 for i=1:4
-0024 SAcell{i} = [];
-0025 end
-0026 previousEC = []; EC_indexes = [];
-0027
-0028 %build an index on MW{1} to speed things up a bit
-0029 %first just extract the genus (i.e. the first part of the name)
-0030 MWECNum = upper(unique(MW{1}));
-0031 MWECNumIndices = cell(length(MWECNum),1);
-0032 MWECNumHashMap = containers.Map(MWECNum,1:length(MWECNum));
-0033 for i = 1:length(MW{1})
-0034 matchInd = cell2mat(values(MWECNumHashMap, MW{1}(i)));
-0035 MWECNumIndices{matchInd} = [MWECNumIndices{matchInd};i];
-0036 end
-0037
-0038
-0039 for i=1:length(SA{1})
-0040 %Gets the indexes of the EC repetitions in the MW cell for every
-0041 %new (different) EC
-0042 if ~strcmpi(SA{1}(i), previousEC)
-0043 key = upper(SA{1}(i));
-0044 if isKey(MWECNumHashMap,key) %annoyingly, this seems to be needed
-0045 matchInd = cell2mat(values(MWECNumHashMap,key));
-0046 EC_indexes = MWECNumIndices{matchInd};
-0047 else
-0048 EC_indexes = [];
-0049 end
-0050 end
-0051 mwEC{1} = MW{3}(EC_indexes); mwEC{2} = MW{4}(EC_indexes);
-0052 % just looks for the first match because just the maximal value for
-0053 % each EC# / Orgaism is reported on the file
-0054 org_index = find(strcmpi(SA{3}(i),mwEC{1}),1);
-0055 if ~isempty(org_index)
-0056 SAcell{1} = [SAcell{1};SA{1}(i)];
-0057 SAcell{2} = [SAcell{2};SA{3}(i)];
-0058 SAcell{3} = [SAcell{3}; SA{4}(i)*mwEC{2}(org_index)]; %[1/hr]
-0059 SAcell{4} = [SAcell{4}; mwEC{2}(org_index)];
-0060 end
-0061 previousEC = SA{1}(i);
-0062 end
-0063
-0064 %remove EC in front of all the EC numbers
-0065 if ~isempty(KCATcell{1})
-0066 KCATcell{1} = extractAfter(KCATcell{1},2);
-0067 end
-0068 if ~isempty(SAcell{1})
-0069 SAcell{1} = extractAfter(SAcell{1},2);
-0070 end
-0071
-0072 function data_cell = openDataFile(fileName,scalingFactor)
-0073 fID = fopen(fileName);
-0074 data_cell = textscan(fID,'%q %q %q %f %q','delimiter','\t');
-0075 fclose(fID);
-0076 data_cell{4} = data_cell{4}*scalingFactor;
-0077 %Split string for each organism in the BRENDA data
-0078 %{name, taxonomy, KEGG code}
-0079 data_cell{3} = regexprep(data_cell{3},'\/\/.*','');
-0080 end
-0081 end
function databases = loadDatabases(selectDatabase,modelAdapter)
-
-
DESCRIPTION
-
loadDatabases
- Loads (and downloads if necessary) the organism-specific KEGG and
- UniProt databases that are required to extract protein information. The
- uniprot.ID and kegg.ID are taken from the ModelAdapter.
-
- Input:
- selectDatabase which databases should be loaded, either 'uniprot',
- 'kegg' or 'both' (optional, default 'both')
- modelAdapter Model adapter. Optional, default will use the default
- model adapter (send in [] for default).
-
- Output:
- databases contains .uniprot and .kegg structures, dependent on
- which databases were selected.
-
- Usage:
- databases = loadDatabases(selectDatabase,modelAdapter)
function [rmse,rmseList] = abc_max(ecModel,fluxData,maxGrowth,rxn2block,modelAdapter)
-
-
DESCRIPTION
-
abc_max
- Internal function in BayesianSensitivityTuning. Gets the average RMSE for
- a certain set of growth data and kcats.
-
- Input:
- ecModel ecModel that was generated by makeEcModel, or loaded from
- an earlier run. Not compatible with ecModels generated by
- earlier GECKO versions (pre 3.0).
- fluxData Growth data loaded from file.
- maxGrowth Max growth data loaded from file.
- rxn2block List of reactions to block, could be read from file.
-
- Output:
- rmse The average RMSE
0001 function [rmse,rmseList] = abc_max(ecModel,fluxData,maxGrowth,rxn2block,modelAdapter)
-0002 % abc_max
-0003 % Internal function in BayesianSensitivityTuning. Gets the average RMSE for
-0004 % a certain set of growth data and kcats.
-0005 %
-0006 % Input:
-0007 % ecModel ecModel that was generated by makeEcModel, or loaded from
-0008 % an earlier run. Not compatible with ecModels generated by
-0009 % earlier GECKO versions (pre 3.0).
-0010 % fluxData Growth data loaded from file.
-0011 % maxGrowth Max growth data loaded from file.
-0012 % rxn2block List of reactions to block, could be read from file.
-0013 %
-0014 % Output:
-0015 % rmse The average RMSE
-0016
-0017 if nargin < 5 || isempty(modelAdapter)
-0018 modelAdapter = ModelAdapterManager.getDefault();
-0019 if isempty(modelAdapter)
-0020 error('Either send in a modelAdapter or set the default ecModel adapter in the ModelAdapterManager.')
-0021 end
-0022 end
-0023 rmse_1 = []; rmseList1 = []; rmse_2 = []; rmseList2 = [];
-0024
-0025 if ~isfield(ecModel,'excarbon')
-0026 ecModel = addCarbonNum(ecModel);
-0027 ecModel.excarbon(ecModel.excarbon == 0) = 1;
-0028 end
-0029
-0030 %% First test with flux data
-0031 if ~isempty(fluxData)
-0032 [rmse_1, rmseList1] = rmsecal(ecModel,fluxData,true,rxn2block,modelAdapter);
-0033 rmseList1 = [cellstr("fluxData_" + string(1:numel(rmseList1)))',num2cell(rmseList1)];
-0034 end
-0035 %% Second test with maximum growth on various carbon sources
-0036 if ~isempty(maxGrowth) % simulate the maximal growth rate
-0037 [rmse_2, rmseList2] = rmsecal(ecModel,maxGrowth,false,rxn2block,modelAdapter);
-0038 rmseList2 = [cellstr("maxGrowth_" + string(1:numel(rmseList2)))',num2cell(rmseList2)];
-0039 end
-0040
-0041 %% Combine results
-0042 rmse = mean([rmse_1,rmse_2],'omitnan');
-0043 rmseList = [rmseList1;rmseList2];
-0044 end
-0045
-0046 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0047 function [rmse, rmseList] = rmsecal(ecModel,data,constrain,rxn2block,modelAdapter)
-0048
-0049 rmseList = zeros(length(data.conds),1);
-0050
-0051 for i = 1:length(data.conds)
-0052
-0053 % Set carbon source
-0054 [~,exchIdx] = ismember(data.conds,data.exchMets);
-0055 if any(exchIdx == 0)
-0056 missingMets = strjoin(unique(data.conds(exchIdx == 0 )), '; ');
-0057 error('Carbon source(s) "%s" in the provided fluxData or maxGrowth cannot be matched by name with an exchange reaction.', missingMets);
-0058 end
-0059 % Set all other carbon sources to zero
-0060 model_tmp = setParam(ecModel,'lb',data.exchRxnIDs(unique(exchIdx)),0);
-0061 % Only allow condition-specific carbon source
-0062 if constrain % If RMSE is calculated from flux data, then constrain carbon uptake
-0063 model_tmp = setParam(model_tmp,'lb',data.exchRxnIDs(exchIdx(i)),data.exchFluxes(i,exchIdx(i)));
-0064 else% If RMSE is calculated from max growth, then do not constrain carbon uptake
-0065 model_tmp = setParam(model_tmp,'lb',data.exchRxnIDs(exchIdx(i)),-1000);
-0066 end
-0067
-0068 % Remove carbon source exchange from rxn2block
-0069 rxn2blockIter = rxn2block;
-0070 rxn2blockIter(strcmp(rxn2blockIter,data.exchRxnIDs(exchIdx(i)))) = [];
-0071
-0072 % Make anaerobic if relevant
-0073 o2Flux = data.exchFluxes(i,strcmp('oxygen',data.exchMets));
-0074 if o2Flux == 0
-0075 model_tmp = modelAdapter.makeModelAnaerobic(model_tmp);
-0076 end
-0077
-0078 sol = solveLP(model_tmp);
-0079 if checkSolution(sol)
-0080 bioRxn = getIndexes(model_tmp,data.biomass,'rxns');
-0081 % Assume that biomass contains 41 Cmmol/gDCW
-0082 bioMeas = data.grRate(i) * 41;
-0083 bioSim = sol.x(bioRxn) * 41;
-0084
-0085 if constrain
-0086 fluxToCheck = ~isnan(data.exchFluxes(i,:));
-0087 fluxRxnIdx = getIndexes(model_tmp,data.exchRxnIDs(fluxToCheck),'rxns');
-0088 cNormMeasFlux = model_tmp.excarbon(fluxRxnIdx) .* transpose(data.exchFluxes(i,fluxToCheck));
-0089 cNormSimFlux = model_tmp.excarbon(fluxRxnIdx) .* sol.x(fluxRxnIdx);
-0090
-0091 % Make sure that selected exchange reactions have zero flux
-0092 blockRxnIdx = getIndexes(model_tmp,rxn2blockIter,'rxns');
-0093 blockSim = sol.x(blockRxnIdx) .* model_tmp.excarbon(blockRxnIdx);
-0094 zeroFlux = blockSim == 0;
-0095 blockSim(zeroFlux) = [];
-0096 blockMeas = zeros(numel(blockSim),1);
-0097
-0098 measured = [cNormMeasFlux; bioMeas; blockMeas];
-0099 simulated = [cNormSimFlux; bioSim; blockSim];
-0100 else
-0101 measured = bioMeas;
-0102 simulated = bioSim;
-0103 end
-0104 rmseList(i) = sqrt(mse(measured,simulated));
-0105 else
-0106 rmseList(i) = NaN;
-0107 end
-0108 end
-0109 rmseList(isnan(rmseList)) = 99; % High penalty for models that failed
-0110 if isfield(data,'bayesianRMSEweight')
-0111 rmseList = rmseList .* data.bayesianRMSEweight;
-0112 end
-0113 rmse = mean(rmseList);
-0114 end
addCarbonNum
- Adds a carbon number field to the model.
-
- Input:
- model ecModel that was generated by makeEcModel, or loaded from
- an earlier run. Not compatible with ecModels generated by
- earlier GECKO versions (pre 3.0).
- Output:
- model ecModel
0001 function model = addCarbonNum(model)
-0002 % addCarbonNum
-0003 % Adds a carbon number field to the model.
-0004 %
-0005 % Input:
-0006 % model ecModel that was generated by makeEcModel, or loaded from
-0007 % an earlier run. Not compatible with ecModels generated by
-0008 % earlier GECKO versions (pre 3.0).
-0009 % Output:
-0010 % model ecModel
-0011 %
-0012
-0013 % get carbonnum for each exchange rxn to further calculation of error
-0014 model.excarbon = zeros(length(model.rxns),1);
-0015 [EXrxn, EXrxnIdx] = getExchangeRxns(model);
-0016 [CarbonNum,EXfors] = getcarbonnum(model,EXrxn);
-0017 model.excarbon(EXrxnIdx) = CarbonNum;
-0018 model.excarbon(strcmp(model.rxnNames,'growth')) = 41;
-0019
-0020 function [CarbonNum,EXfors,changedmets] = getcarbonnum(model,exrxn)
-0021 % getcarbonnum
-0022 % Gets carbon numbers matching the model.
-0023 %
-0024 % Input:
-0025 % model ecModel that was generated by makeEcModel, or loaded from
-0026 % an earlier run. Not compatible with ecModels generated by
-0027 % earlier GECKO versions (pre 3.0).
-0028 % exrxn Exchange reaction to use for counting
-0029 % Output:
-0030 % model ecModel
-0031 %
-0032
-0033 % exrxn must be an exchange rxn and only contain one mets in the equation
-0034 [~,idx] = ismember(exrxn,model.rxns);
-0035 EXmets = model.S(:,idx);
-0036 % find all exchange mets
-0037 EXmetsIdx = zeros(length(exrxn),1);
-0038 for k = 1:length(EXmets(1,:))
-0039 EXmetsIdx(k) = find(EXmets(:,k)); %
-0040 end
-0041 EXfors = model.metFormulas(EXmetsIdx);
-0042 [Ematrix, elements] = getElementalComposition(EXfors,{'C'});
-0043 Ematrix = Ematrix(:,1);
-0044 changedmets = exrxn(isnan(Ematrix));
-0045 Ematrix(isnan(Ematrix)) = 1;
-0046 CarbonNum = Ematrix;
-0047
-0048 function [Ematrix, elements] = getElementalComposition(formulae, elements, chargeInFormula)
-0049 % Get the complete elemental composition matrix. It supports formulae with
-0050 % generic elements, parentheses and decimal places
-0051 %
-0052 % USAGE:
-0053 % [Ematrix, elements] = getElementalComposition(formulae, elements, chargeInFormula)
-0054 %
-0055 % INPUT:
-0056 % formulae: cell array of strings of chemical formulae. Can contain any generic elements starting
-0057 % with a capital letter followed by lowercase letters or '_', followed by a non-negative number.
-0058 % Also support '()', '[]', '{}'. E.g. {'H2O'; '[H2O]2(CuSO4)Generic_element0.5'}
-0059 % OPTIONAL INPUTS:
-0060 % elements: elements from previous call to preserve the order (default {})
-0061 % chargeInFormula: true to accept formulae containing the generic element 'Charge' representing the charges,
-0062 % followed by a real number, e.g., 'HCharge1', 'SO4Charge-2' (default false).
-0063 %
-0064 % OUTPUTS:
-0065 % Ematrix: elemental composition matrix (#formulae x #elements)
-0066 % elements: cell array of elements corresponding to the columns of Ematrix
-0067 %
-0068 % E.g., [Ematrix, elements] = getElementalComposition({'H2O'; '[H2O]2(CuSO4)Generic_element0.5'}) would return:
-0069 % elements = {'H', 'O', 'Cu', 'S', 'Generic_element'}
-0070 % Ematrix = [ 2, 1, 0, 0, 0;
-0071 % 4, 6, 1, 1, 0.5]
-0072 %
-0073 % Siu Hung Joshua Chan May 2017
-0074
-0075 if nargin < 3 || isempty(chargeInFormula)
-0076 chargeInFormula = false;
-0077 else
-0078 chargeInFormula = logical(chargeInFormula);
-0079 end
-0080 % for recalling the original formula at the top level if there are parentheses
-0081 % in the formula leading to iterative calling
-0082 persistent formTopLv
-0083 persistent formCurLv
-0084 % for storing error message during iterative calling
-0085 persistent errMsg
-0086 persistent errMsgInThisLoop
-0087 persistent topLvJ
-0088 persistent selfCall
-0089 if isempty(selfCall)
-0090 selfCall = 0;
-0091 end
-0092 if ~selfCall
-0093 [formTopLv, errMsg] = deal('');
-0094 end
-0095
-0096 if ~isstruct(formulae)
-0097 if ~iscell(formulae)
-0098 % make sure it is a cell array of strings
-0099 formulae = {formulae};
-0100 end
-0101 else
-0102 % also accept COBRA model as input
-0103 if isfield(formulae, 'metFormulas')
-0104 formulae = formulae.metFormulas;
-0105 else
-0106 error('The 1st input ''formulae'' should be a cell array of strings of formulae or a COBRA model with *.metFormulas')
-0107 end
-0108 end
-0109
-0110 if nargin < 2 || isempty(elements)
-0111 elements = {};
-0112 elseif numel(unique(elements)) < numel(elements)
-0113 error('Repeated elements in the input ''elements'' array.')
-0114 else
-0115 elements = elements(:)'; % make sure it is a row vector
-0116 end
-0117
-0118 % the Ematrix
-0119 Ematrix = zeros(numel(formulae), numel(elements));
-0120 % replace all brackets and braces by parentheses
-0121 formulae = regexprep(formulae, '[\[\{]', '\(');
-0122 formulae = regexprep(formulae, '[\]\}]', '\)');
-0123 digit = floor(log10(numel(formulae))) + 1;
-0124 for j = 1:numel(formulae)
-0125 if ~selfCall
-0126 % reset top level information for each formula
-0127 [formTopLv, topLvJ, errMsgInThisLoop] = deal(formulae{j}, j, false);
-0128 end
-0129 formulae{j} = strtrim(formulae{j});
-0130 if ~isempty(formulae{j})
-0131 % get all outer parentheses
-0132 parenthesis = [];
-0133 stP = [];
-0134 stPpos = [];
-0135 lv = 0;
-0136 k = 1;
-0137 while k <= length(formulae{j})
-0138 if strcmp(formulae{j}(k),'(')
-0139 if lv == 0
-0140 pStart = k;
-0141 end
-0142 lv = lv + 1;
-0143 elseif strcmp(formulae{j}(k),')')
-0144 if lv == 1
-0145 parenthesis = [parenthesis; [pStart k]];
-0146 % right parenthesis, get the following stoichiometry if any
-0147 stPre = regexp(formulae{j}(k+1:end), '^(\+|\-)?\d*\.?\d*', 'match');
-0148 if isempty(stPre)
-0149 stP = [stP; 1];
-0150 stPpos = [stPpos; k k];
-0151 else
-0152 stCheck = str2double(stPre{1});
-0153 s = '';
-0154 if isnan(stCheck)
-0155 s = 'Invalid';
-0156 elseif stCheck < 0
-0157 s = 'Negative';
-0158 end
-0159 if ~isempty(s) % error if not convertible to number or negative
-0160 f = [formulae{j}(parenthesis(end,1):parenthesis(end,2)), stPre{1}];
-0161 addErrorMessage(sprintf(' %s stoichiometry in ''%s''\n', s, f))
-0162 end
-0163 % stoichiometry
-0164 stP = [stP; stCheck];
-0165 % position of the stoichiometry in the text
-0166 stPpos = [stPpos; k + 1, k + length(stPre{1})];
-0167 k = k + length(stPre{1});
-0168 end
-0169 end
-0170 lv = lv - 1;
-0171 end
-0172 k = k + 1;
-0173 end
-0174 if isempty(parenthesis)
-0175 % No parenthesis, parse the formula
-0176 re = regexp(formulae{j}, '([A-Z][a-z_]*)((?:\+|\-)?\d*\.?\d*)', 'tokens');
-0177 s = strjoin(cellfun(@(x) strjoin(x,''), re, 'UniformOutput', false), '');
-0178 errorFlag = 0;
-0179 if ~strcmp(s, formulae{j}) % check if the entire formula is retrieved
-0180 errorFlag = 1;
-0181 else
-0182 % check the stoichiometry
-0183 eleCheck = cellfun(@(x) x{1}, re, 'UniformOutput', false);
-0184 stCheck = cellfun(@(x) str2double(x{2}), re);
-0185 stCheck(cellfun(@(x) isempty(x{2}), re)) = 1; % empty value => stoichiometry = 1
-0186 if any(isnan(stCheck))
-0187 f = strjoin(cellfun(@(x) strjoin(x, ''), re(isnan(stCheck)), 'UniformOutput', false), ''', ''');
-0188 [errorFlag, errMsgKey] = deal(2, 'Invalid');
-0189 else
-0190 % if chargeInFormula is true, detect negative stoich for non-charge elements, else any negative stoich
-0191 negSt = (~chargeInFormula | ~strcmp(eleCheck, 'Charge')) & stCheck < 0;
-0192 if any(negSt)
-0193 f = strjoin(cellfun(@(x) strjoin(x, ''), re(negSt), 'UniformOutput', false), ''', ''');
-0194 [errorFlag, errMsgKey] = deal(2, 'Negative');
-0195 end
-0196 end
-0197 end
-0198 if errorFlag > 0
-0199 if selfCall <= 1
-0200 s2 = sprintf('from the part ''%s''', formulae{j});
-0201 elseif selfCall == 2
-0202 s2 = sprintf('from the part ''(%s)''', formCurLv);
-0203 elseif selfCall == 3
-0204 s2 = sprintf('from the part ''(%s)''', formulae{j});
-0205 end
-0206 if errorFlag == 1
-0207 s2 = sprintf([' Only ''%s'' can be recognized %s.\n'...
-0208 ' Each element should start with a capital letter followed by lower case letters'...
-0209 ' or ''_'' with indefinite length and followed by a number.\n'], s, s2);
-0210 elseif errorFlag == 2
-0211 s2 = sprintf(' %s stoichiometry in ''%s'' %s\n', errMsgKey, f, s2);
-0212 end
-0213 addErrorMessage(s2);
-0214 end
-0215 elementJ = repmat({''}, 1, numel(re));
-0216 nEj = 0;
-0217 stoichJ = zeros(numel(re),1);
-0218 for k = 1:numel(re)
-0219 [ynK,idK] = ismember(re{k}(1), elementJ(1:nEj));
-0220 if ynK
-0221 k2 = idK;
-0222 else
-0223 nEj = nEj + 1;
-0224 elementJ{nEj} = re{k}{1};
-0225 k2 = nEj;
-0226 end
-0227 if isempty(re{k}{2})
-0228 stoichJ(k2) = stoichJ(k2) + 1;
-0229 else
-0230 stoichJ(k2) = stoichJ(k2) + str2double(re{k}{2});
-0231 end
-0232 end
-0233 elementJ = elementJ(1:nEj);
-0234 stoichJ = stoichJ(1:nEj);
-0235 [ynE, idE] = ismember(elementJ, elements); % map to existing elements
-0236 if any(~ynE)
-0237 idE(~ynE) = (numel(elements) + 1):(numel(elements) + sum(~ynE));
-0238 Ematrix(:, (numel(elements) + 1):(numel(elements) + sum(~ynE))) = 0;
-0239 elements = [elements, elementJ(~ynE)];
-0240 end
-0241 Ematrix(j, idE) = stoichJ;
-0242 else
-0243 % parentheses found. Iteratively get the formula inside parentheses
-0244 rest = true(length(formulae{j}),1);
-0245 for k = 1:size(parenthesis,1)
-0246 selfCallCur = selfCall;
-0247 selfCall = 3;
-0248 [EmatrixK, elements] = getElementalComposition(formulae{j}(...
-0249 (parenthesis(k,1)+1):(parenthesis(k,2)-1)), elements, chargeInFormula);
-0250 if numel(elements) > size(Ematrix, 2)
-0251 Ematrix(:, (size(Ematrix, 2) + 1):numel(elements)) = 0;
-0252 end
-0253 selfCall = selfCallCur;
-0254 Ematrix(j, 1:numel(elements)) = Ematrix(j, 1:numel(elements)) + EmatrixK * stP(k);
-0255 rest(parenthesis(k,1):stPpos(k,2)) = false;
-0256 end
-0257 if any(rest)
-0258 formCurLv = formulae{j};
-0259 selfCallCur = selfCall;
-0260 selfCall = 1 + (selfCall >= 1);
-0261 while any(rest)
-0262 % get the consecutive part of the formula that is not in parentheses
-0263 [pStart, pEnd] = deal(find(rest, 1), find(~rest));
-0264 pEnd = min(pEnd(pEnd > pStart));
-0265 if isempty(pEnd)
-0266 pEnd = numel(rest);
-0267 else
-0268 pEnd = pEnd - 1;
-0269 end
-0270 [EmatrixK, elements] = getElementalComposition(formulae{j}(pStart:pEnd), elements, chargeInFormula);
-0271 if numel(elements) > size(Ematrix, 2)
-0272 Ematrix(:, (size(Ematrix, 2) + 1):numel(elements)) = 0;
-0273 end
-0274 Ematrix(j, 1:numel(elements)) = Ematrix(j, 1:numel(elements)) + EmatrixK;
-0275 rest(pStart:pEnd) = false;
-0276 end
-0277 selfCall = selfCallCur;
-0278 end
-0279 end
-0280 else
-0281 Ematrix(j,:) = NaN;
-0282 end
-0283 end
-0284 Ematrix = Ematrix(:, 1:numel(elements));
-0285 if ~selfCall
-0286 selfCall = [];
-0287 if ~isempty(errMsg)
-0288 error(['%s\n', errMsg], 'Invalid formula input:')
-0289 end
-0290 end
-0291
-0292 % nested function for adding error messages
-0293 function addErrorMessage(s)
-0294 if ~errMsgInThisLoop
-0295 errMsg = [errMsg, sprintf(['#%0' num2str(digit) 'd: %s\n'], topLvJ, formTopLv)];
-0296 errMsgInThisLoop = true;
-0297 end
-0298 errMsg = [errMsg, s];
-0299 end
-0300 end
-0301
-0302 end
-0303
-0304 end
function [ecModel, rmseTrace, kcatTrace, stdTrace] = bayesianSensitivityTuning(ecModel,kcatStd,modelAdapter)
-
-
DESCRIPTION
-
bayesianSensitivityTuning
- Modifies kcats to better fit experimental data. Currently only works with
- light models.
-
- Input:
- ecModel ecModel that was generated by makeEcModel, or loaded from
- an earlier run. Not compatible with ecModels generated by
- earlier GECKO versions (pre 3.0).
- kcatStd vector of same length as ecModel.ec.kcat, with the
- initial standard deviations (SD) for the kcat values.
- By default, kcatStd is set by multiplying kcat values
- with params.bayesian.initSDmultiplDef, or by source-
- specific multipliers specified in params.bayesian.
- kcatSources and params.bayesian.initSDmultipl. Instead,
- it is also possible to provide a custom kcatStd vector.
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- model ecModel with new kcats, not applied.
getrSample
- Sample random kcat values from a distribution.
-
- Input:
- mu mean of distribution
- sigma standard deviation of distribution
- n number of kcat values to sample
- method shape of distribution: 'lognormal' or 'uniform'. (optional,
- default is 'lognormal'). If 'lognormal' is selected, mu and
- sigma are automatically converted into log-space
-
- Output:
- r sample of kcat values
0001 function r = getrSample(mu,sigma,n,method)
-0002 % getrSample
-0003 % Sample random kcat values from a distribution.
-0004 %
-0005 % Input:
-0006 % mu mean of distribution
-0007 % sigma standard deviation of distribution
-0008 % n number of kcat values to sample
-0009 % method shape of distribution: 'lognormal' or 'uniform'. (optional,
-0010 % default is 'lognormal'). If 'lognormal' is selected, mu and
-0011 % sigma are automatically converted into log-space
-0012 %
-0013 % Output:
-0014 % r sample of kcat values
-0015
-0016 if nargin < 4
-0017 method = 'lognormal';
-0018 end
-0019 if mu == 0
-0020 r = zeros(1,n);
-0021 elseif strcmp(method,'lognormal')
-0022 % Convert to log-space
-0023 varLog = log(1 + (sigma/mu)^2);
-0024 sigmaLog = sqrt(varLog);
-0025 muLog = log(mu) - 0.5 * varLog;
-0026
-0027 % Take random samples from lognormal distribution
-0028 r = lognrnd(muLog,sigmaLog,[1,n]);
-0029 elseif strcmp(method,'uniform')
-0030 pd = makedist('uniform','lower',mu - sigma,'upper',mu + sigma);
-0031 t = truncate(pd,0,inf);
-0032 r = random(t,1,n);
-0033 end
-0034 end
function [value,organism,parameter] = findMaxValue(EC_cell,BRENDA,SA_cell)
-
-
DESCRIPTION
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-function [value,organism,parameter] = findMaxValue(EC_cell,BRENDA, SA_cell)
-
- Function that gets the maximum kinetic parameter (Kcat or S.A.*Mw) from
- the BRENDA files for the specified set of EC numbers. The algorithm also
- returns the organism and the parameter type (Kcat or S.A.) of the query.
-
- Ivan Domenzain Last edited. 2018-02-06
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-Looks for the maximum turnover number available for the EC# associated
-with the uniprot code
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0002 %function [value,organism,parameter] = findMaxValue(EC_cell,BRENDA, SA_cell)
-0003 %
-0004 % Function that gets the maximum kinetic parameter (Kcat or S.A.*Mw) from
-0005 % the BRENDA files for the specified set of EC numbers. The algorithm also
-0006 % returns the organism and the parameter type (Kcat or S.A.) of the query.
-0007 %
-0008 % Ivan Domenzain Last edited. 2018-02-06
-0009 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0010 function [value,organism,parameter] = findMaxValue(EC_cell,BRENDA,SA_cell)
-0011 %Looks for the maximum turnover number available for the EC# associated
-0012 %with the uniprot code
-0013 EC_cell = strsplit(EC_cell,' ');
-0014 value = [];
-0015 organism = [];
-0016 parameter = [];
-0017 for i=1:length(EC_cell)
-0018 find_flag = false;
-0019 %In case that wild cards are present in the EC number the search on
-0020 %the BRENDA file will be relaxed.
-0021 if ~isempty(strfind(EC_cell{i},'-'))
-0022 EC_cell{i} = EC_cell{i}(strfind(EC_cell{i},'-')-1:end);
-0023 find_flag = true;
-0024 end
-0025 ECnumber = ['EC' EC_cell{i}];
-0026 Kcat = 0; orgK = '';
-0027 if find_flag == true
-0028 matching = find(~cellfun(@isempty,strfind(BRENDA{1},ECnumber)));
-0029 else
-0030 % If no wild cards are present the EC number search in the
-0031 % BRENDA file will look for an exact match
-0032 matching = find(strcmpi(ECnumber,BRENDA{1}));
-0033 end
-0034 %Gets the maximum Kcat value for the queried EC#
-0035 if ~isempty(matching)
-0036 [Kcat, maxIndx] = max(BRENDA{4}(matching));
-0037 orgK = BRENDA{3}(matching(maxIndx));
-0038 end
-0039 % Looks for the maximum SA*Mw value available for the EC number
-0040 SA_Mw = 0; orgS = '';
-0041 if find_flag == true
-0042 matching = find(~cellfun(@isempty,strfind(SA_cell{1},ECnumber)));
-0043 else
-0044 matching = find(strcmpi(ECnumber,SA_cell{1}));
-0045 end
-0046 %Gets the maximum SA*Mw value for the queried EC#
-0047 if ~isempty(matching)
-0048 [SA_Mw, maxIndx] = max(SA_cell{3}(matching));
-0049 SA_Mw = SA_Mw; %[1/hr]
-0050 orgS = SA_cell{2}(matching(maxIndx));
-0051 end
-0052 %Choose the maximal available value as a turnover number for the EC
-0053 value = [value; max(Kcat,SA_Mw)];
-0054
-0055 if Kcat>SA_Mw
-0056 organism = [organism; {orgK}];
-0057 parameter = [parameter; {'K_cat'}];
-0058 else
-0059 organism = [organism; {orgS}];
-0060 parameter = [parameter; {'SA*Mw'}];
-0061 end
-0062 end
-0063 [value, maxIndx] = max(value);
-0064 organism = organism(maxIndx);
-0065 parameter = parameter(maxIndx);
-0066 end
-0067 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [model, tunedKcats] = sensitivityTuning(model, desiredGrowthRate, modelAdapter, foldChange, protToIgnore, verbose)
-
-
DESCRIPTION
-
sensitivityTuning
- Function that relaxes the most limiting kcats until a certain growth rate
- is reached. The function will update kcats in model.ec.kcat.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- desiredGrowthRate kcats will be relaxed until this growth rate is reached
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
- foldChange kcat values will be increased by this fold-change.
- (Opt, default 10)
- protToIgnore vector of protein ids to be ignore in tuned kcats.
- e.g. {'P38122', 'Q99271'} (Optional, default = [])
- verbose logical whether progress should be reported (Optional,
- default true)
-
- Output:
- model ecModel with updated model.ec.kcat
- tunedKcats structure with information on tuned kcat values
- rxns identifiers of reactions with tuned kcat
- values
- rxnNames names of the reactions in tunedKcats.rxns
- enzymes enzymes that catalyze the reactions in
- tunedKcats.rxns, whose kcat value has been
- tuned.
- oldKcat kcat values in the input model
- newKcat kcat values in the output model, after tuning
-
- Note: The model.ec.notes field will contain the original kcat value and
- source, unless the kcat has previously been set by sensitivityTuning, in
- which case the notes field remains unchanged.
-
- Usage:
- [model, tunedKcats] = sensitivityTuning(model, desiredGrowthRate, modelAdapter, foldChange, protToIgnore, verbose)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function [model, tunedKcats] = sensitivityTuning(model, desiredGrowthRate, modelAdapter, foldChange, protToIgnore, verbose)
-0002 % sensitivityTuning
-0003 % Function that relaxes the most limiting kcats until a certain growth rate
-0004 % is reached. The function will update kcats in model.ec.kcat.
-0005 %
-0006 % Input:
-0007 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0008 % desiredGrowthRate kcats will be relaxed until this growth rate is reached
-0009 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0010 % default model adapter).
-0011 % foldChange kcat values will be increased by this fold-change.
-0012 % (Opt, default 10)
-0013 % protToIgnore vector of protein ids to be ignore in tuned kcats.
-0014 % e.g. {'P38122', 'Q99271'} (Optional, default = [])
-0015 % verbose logical whether progress should be reported (Optional,
-0016 % default true)
-0017 %
-0018 % Output:
-0019 % model ecModel with updated model.ec.kcat
-0020 % tunedKcats structure with information on tuned kcat values
-0021 % rxns identifiers of reactions with tuned kcat
-0022 % values
-0023 % rxnNames names of the reactions in tunedKcats.rxns
-0024 % enzymes enzymes that catalyze the reactions in
-0025 % tunedKcats.rxns, whose kcat value has been
-0026 % tuned.
-0027 % oldKcat kcat values in the input model
-0028 % newKcat kcat values in the output model, after tuning
-0029 %
-0030 % Note: The model.ec.notes field will contain the original kcat value and
-0031 % source, unless the kcat has previously been set by sensitivityTuning, in
-0032 % which case the notes field remains unchanged.
-0033 %
-0034 % Usage:
-0035 % [model, tunedKcats] = sensitivityTuning(model, desiredGrowthRate, modelAdapter, foldChange, protToIgnore, verbose)
-0036
-0037 if nargin < 6 || isempty(verbose)
-0038 verbose = true;
-0039 end
-0040 if nargin < 5 || isempty(protToIgnore)
-0041 protToIgnore = {};
-0042 end
-0043 if nargin < 4 || isempty(foldChange)
-0044 foldChange = 10;
-0045 end
-0046 if nargin < 3 || isempty(modelAdapter)
-0047 modelAdapter = ModelAdapterManager.getDefault();
-0048 if isempty(modelAdapter)
-0049 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0050 end
-0051 end
-0052 params = modelAdapter.params;
-0053 if nargin < 2 || isempty(desiredGrowthRate)
-0054 desiredGrowthRate = params.gR_exp;
-0055 end
-0056
-0057 kcatList = [];
-0058 m = model;
-0059 m.c = double(strcmp(m.rxns, params.bioRxn));% Make sure that growth is maximized
-0060
-0061 [res,hs] = solveLP(m);
-0062 if isempty(res.x)
-0063 error('FBA of input model gives no valid result. Reduce protein pool constraint with setProtPoolSize and/or check if exchange constraints are correctly defined.')
-0064 end
-0065 lastGrowth = 0;
-0066 if ~m.ec.geckoLight
-0067 %for the full model, we first find the draw reaction with the most flux
-0068 drawRxns = startsWith(m.rxns, 'usage_prot_');
-0069 idxToIgnore = cellfun(@(x) find(strcmpi(model.rxns, ['usage_prot_' x])), protToIgnore);
-0070 iteration = 1;
-0071 while true
-0072 [res,hs] = solveLP(m,0,[],hs); %skip parsimonius, only takes time
-0073 if (lastGrowth == res.f)
-0074 printOrange('WARNING: No growth increase from increased kcats - check if the constraints on the uptake reactions are too tight.\n');
-0075 break;
-0076 end
-0077 lastGrowth = res.f;
-0078 if verbose; disp(['Iteration ' num2str(iteration) ': Growth: ' num2str(lastGrowth)]); end
-0079 if (lastGrowth >= desiredGrowthRate)
-0080 break;
-0081 end
-0082 %If you get an error here, it is likely due to numerical issues in the solver
-0083 %The trick where we don't allow low kcats is to fix that, but maybe
-0084 %it is not enough.
-0085 iteration = iteration + 1;
-0086 %find the highest draw_prot rxn flux
-0087 drawFluxes = zeros(length(drawRxns),1);
-0088 drawFluxes(drawRxns) = res.x(drawRxns);
-0089 % Remove from the list user defined proteins
-0090 drawFluxes(idxToIgnore) = 0;
-0091 [~,sel] = min(drawFluxes); % since bounds -1000 to 0
-0092 %Now get the metabolite
-0093 metSel = m.S(:,sel) < 0; % negative coeff
-0094 %now find the reaction with the largest consumption of this protein
-0095 protFluxes = m.S(metSel,:).' .* res.x; %negative
-0096 [~,rxnSel] = min(protFluxes);
-0097 kcatList = [kcatList, rxnSel];
-0098 rxn = m.rxns(rxnSel);
-0099 targetSubRxn = strcmp(m.ec.rxns, rxn);
-0100 if ~strcmp(m.ec.source(targetSubRxn),'sensitivityTuning')
-0101 oldNote = m.ec.notes{targetSubRxn};
-0102 newNote = ['preTuneKcat=' num2str(m.ec.kcat(targetSubRxn)) ' | source:' m.ec.source{targetSubRxn}];
-0103 if ~isempty(oldNote)
-0104 newNote = [oldNote '; ' newNote];
-0105 end
-0106 m.ec.notes{targetSubRxn} = newNote;
-0107 end
-0108 m.ec.kcat(targetSubRxn) = m.ec.kcat(targetSubRxn) .* foldChange;
-0109 m.ec.source(targetSubRxn) = {'sensitivityTuning'};
-0110 m = applyKcatConstraints(m,targetSubRxn);
-0111 end
-0112
-0113 else
-0114 origRxns = extractAfter(m.ec.rxns,4);
-0115 %find the reactions involved in proteins to be ignored
-0116 idxToIgnore = cellfun(@(x) find(m.ec.rxnEnzMat(:, strcmpi(m.ec.enzymes, x))), protToIgnore, 'UniformOutput', false);
-0117 %create an unique vector
-0118 idxToIgnore = unique(cat(1, idxToIgnore{:}));
-0119 %get the correct idx in model.rxns
-0120 idxToIgnore = cellfun(@(x) find(strcmpi(m.rxns, x)), origRxns(idxToIgnore));
-0121 iteration = 1;
-0122 while true
-0123 res = solveLP(m,0); %skip parsimonius, only takes time
-0124 if (lastGrowth == res.f)
-0125 printOrange('No growth increase from increased kcats - check if the constraints on the uptake reactions are too tight.\n');
-0126 break;
-0127 end
-0128 lastGrowth = res.f;
-0129 if (lastGrowth >= desiredGrowthRate)
-0130 break;
-0131 end
-0132 %If you get an error here, it is likely due to numerical issues in the solver
-0133 %The trick where we don't allow low kcats is to fix that, but maybe
-0134 %it is not enough.
-0135 if verbose; disp(['Iteration ' num2str(iteration) ': Growth: ' num2str(lastGrowth)]); end
-0136 iteration = iteration + 1;
-0137 %find the highest protein usage flux
-0138 protPoolStoich = m.S(strcmp(m.mets, 'prot_pool'),:).';
-0139 protPoolStoich(idxToIgnore) = 0;
-0140 [~,sel] = min(res.x .* protPoolStoich); %max consumption
-0141 kcatList = [kcatList, sel];
-0142 rxn = m.rxns(sel.');
-0143 targetSubRxns = strcmp(origRxns, rxn);
-0144 m.ec.kcat(targetSubRxns) = m.ec.kcat(targetSubRxns) .* foldChange;
-0145 m = applyKcatConstraints(m,rxn);
-0146 end
-0147 end
-0148
-0149 kcatList = unique(kcatList);
-0150 tunedKcats.rxns = m.rxns(kcatList);
-0151 tunedKcats.rxnNames = m.rxnNames(kcatList);
-0152 if ~model.ec.geckoLight
-0153 [~, rxnIdx] = ismember(tunedKcats.rxns,m.ec.rxns);
-0154 else
-0155 [~, rxnIdx] = ismember(tunedKcats.rxns,origRxns);
-0156 end
-0157 tunedKcats.enzymes = cell(numel(kcatList),1);
-0158 for i=1:numel(rxnIdx)
-0159 [~, metIdx] = find(m.ec.rxnEnzMat(rxnIdx(i),:));
-0160 tunedKcats.enzymes{i} = strjoin(m.ec.enzymes(metIdx),';');
-0161 end
-0162 tunedKcats.oldKcat = model.ec.kcat(rxnIdx);
-0163 tunedKcats.newKcat = m.ec.kcat(rxnIdx);
-0164 tunedKcats.source = model.ec.source(rxnIdx);
-0165
-0166 model = m;
-0167 end
function [model, sigma] = sigmaFitter(model, growthRate, Ptot, f, makePlot, modelAdapter)
-
-
DESCRIPTION
-
sigmaFitter
- Function that fits the average enzyme saturation factor in an ecModel
- according to a provided experimentally measured value for the objective
- function (i.e. growth rate at specified conditions)
-
- INPUTS:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- growthRate growth rate that should be reached. If not
- specified, the value will be read from the model
- adapter.
- Ptot Total cellular protein content in g/gDCW. If not
- specified, the value will be read from the model
- adapter. If not specified in model adapter, 0.5 g/gDCW
- is assumed.
- f Estimated fraction of enzymes in the model. If not
- specified, the value will be read from the model
- adapter. If not specified in model adapter, 0.5 is
- assumed.
- makePlot Logical whether a plot should be made. Default true.
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- model ecModel with protein pool exchange upper bound adapted
- to the optimal sigma-factor
- sigma optimal sigma-factor
-
- Usage:
- [model, sigma] = sigmaFitter(model, growthRate, Ptot, f, makePlot, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function [model, sigma] = sigmaFitter(model, growthRate, Ptot, f, makePlot, modelAdapter)
-0002 % sigmaFitter
-0003 % Function that fits the average enzyme saturation factor in an ecModel
-0004 % according to a provided experimentally measured value for the objective
-0005 % function (i.e. growth rate at specified conditions)
-0006 %
-0007 % INPUTS:
-0008 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0009 % growthRate growth rate that should be reached. If not
-0010 % specified, the value will be read from the model
-0011 % adapter.
-0012 % Ptot Total cellular protein content in g/gDCW. If not
-0013 % specified, the value will be read from the model
-0014 % adapter. If not specified in model adapter, 0.5 g/gDCW
-0015 % is assumed.
-0016 % f Estimated fraction of enzymes in the model. If not
-0017 % specified, the value will be read from the model
-0018 % adapter. If not specified in model adapter, 0.5 is
-0019 % assumed.
-0020 % makePlot Logical whether a plot should be made. Default true.
-0021 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0022 % default model adapter).
-0023 %
-0024 % Output:
-0025 % model ecModel with protein pool exchange upper bound adapted
-0026 % to the optimal sigma-factor
-0027 % sigma optimal sigma-factor
-0028 %
-0029 % Usage:
-0030 % [model, sigma] = sigmaFitter(model, growthRate, Ptot, f, makePlot, modelAdapter)
-0031
-0032 if nargin < 6 || isempty(modelAdapter)
-0033 modelAdapter = ModelAdapterManager.getDefault();
-0034 if isempty(modelAdapter)
-0035 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0036 end
-0037 end
-0038
-0039 if nargin<5 || isempty(makePlot)
-0040 makePlot = true;
-0041 end
-0042 if nargin<4 || isempty(f)
-0043 f = modelAdapter.getParameters().f;
-0044 end
-0045 if nargin<3 || isempty(Ptot)
-0046 Ptot = modelAdapter.getParameters().Ptot;
-0047 end
-0048 if nargin<2 || isempty(growthRate)
-0049 growthRate = modelAdapter.getParameters().gR_exp;
-0050 end
-0051
-0052 objValues = [];
-0053 errors = [];
-0054 sigParam = [];
-0055 objPos = find(model.c);
-0056 %Relax bounds for the objective function
-0057 model.lb(objPos) = 0;
-0058 model.ub(objPos) = 1000;
-0059 hsSol=[];
-0060 for i=1:100
-0061 %Constrains the ecModel with the i-th sigma factor
-0062 sigma = i/100;
-0063 model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter);
-0064 [solution, hsSol] = solveLP(model,0,[],hsSol);
-0065 if isempty(solution.x)
-0066 solution.x=zeros(length(model.rxns),1);
-0067 end
-0068 objValues = [objValues; solution.x(objPos)];
-0069 error = abs(((growthRate-solution.x(objPos))/growthRate)*100);
-0070 errors = [errors; error];
-0071 error = num2str(((growthRate-solution.x(objPos))/growthRate)*100);
-0072 sigParam = [sigParam; sigma];
-0073 end
-0074 [~, minIndx] = min(errors);
-0075 sigma = sigParam(minIndx);
-0076 if makePlot
-0077 figure
-0078 plot(sigParam,errors,'LineWidth',5)
-0079 title('Sigma fitting')
-0080 xlabel('Average enzyme saturation [-]')
-0081 ylabel('Absolute relative error [%]')
-0082 end
-0083 end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- table = truncateValues(table,cols)
- table cell array or table where some columns have values that should
- be truncated
- cols index or indices of columns with values to be truncated
-
- Benjamin Sanchez Last edited: 2018-05-25
- Eduard Kerkhoven Last edited: 2018-12-05
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0002 % table = truncateValues(table,cols)
-0003 % table cell array or table where some columns have values that should
-0004 % be truncated
-0005 % cols index or indices of columns with values to be truncated
-0006 %
-0007 % Benjamin Sanchez Last edited: 2018-05-25
-0008 % Eduard Kerkhoven Last edited: 2018-12-05
-0009 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-0010 function table = truncateValues(table,cols)
-0011
-0012 [m,n] = size(table);
-0013 for i = 1:m
-0014 for j = cols
-0015 orderMagn = max([ceil(log10(abs(table{i,j}))),0]);
-0016 table{i,j} = round(table{i,j},6-orderMagn);
-0017 end
-0018 end
-0019 end
-0020
-0021 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function f = calculateFfactor(model, protData, enzymes, modelAdapter)
-
-
DESCRIPTION
-
calculateFfactor
- Computes the f factor, as a proxy to the mass fraction of proteins
- accounted for in an ecModel out of the total protein content in cells.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- protData structure with proteome data, from loadProtData (Optional,
- by default it instead attempts to load data/paxDB.tsv)
- enzymes list of enzymes (Optional, default model.ec.enzymes)
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- f f-factor
-
- Usage:
- f = calculateFfactor(model, protData, enzymes, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function f = calculateFfactor(model, protData, enzymes, modelAdapter)
-0002 % calculateFfactor
-0003 % Computes the f factor, as a proxy to the mass fraction of proteins
-0004 % accounted for in an ecModel out of the total protein content in cells.
-0005 %
-0006 % Input:
-0007 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0008 % protData structure with proteome data, from loadProtData (Optional,
-0009 % by default it instead attempts to load data/paxDB.tsv)
-0010 % enzymes list of enzymes (Optional, default model.ec.enzymes)
-0011 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0012 % default model adapter).
-0013 %
-0014 % Output:
-0015 % f f-factor
-0016 %
-0017 % Usage:
-0018 % f = calculateFfactor(model, protData, enzymes, modelAdapter)
-0019
-0020 if nargin < 4 || isempty(modelAdapter)
-0021 modelAdapter = ModelAdapterManager.getDefault();
-0022 if isempty(modelAdapter)
-0023 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0024 end
-0025 end
-0026 params = modelAdapter.getParameters();
-0027
-0028 if nargin < 3 || isempty(enzymes)
-0029 enzymes = model.ec.enzymes;
-0030 end
-0031
-0032 % Gather proteome data in protData structure
-0033 if nargin < 2 || isempty(protData)
-0034 if exist(fullfile(params.path,'data','paxDB.tsv'),'file')
-0035 protData = fullfile(params.path,'data','paxDB.tsv');
-0036 else
-0037 printOrange('WARNING: No proteomics data is provided or can be found. Default f value of 0.5 is returned.\n');
-0038 f = 0.5;
-0039 end
-0040 end
-0041
-0042 % Gather Uniprot database for finding MW
-0043 uniprotDB = loadDatabases('uniprot', modelAdapter);
-0044 uniprotDB = uniprotDB.uniprot;
-0045
-0046 if ischar(protData) && endsWith(protData,'paxDB.tsv')
-0047 fID = fopen(fullfile(protData),'r');
-0048 fileContent = textscan(fID,'%s','delimiter','\n');
-0049 headerLines = find(startsWith(fileContent{1},'#'),1,'last');
-0050 fclose(fID);
-0051
-0052 %Read data file, excluding headerlines
-0053 fID = fopen(fullfile(protData),'r');
-0054 fileContent = textscan(fID,'%s %s %f','delimiter','\t','HeaderLines',headerLines);
-0055 genes = fileContent{2};
-0056 %Remove internal geneIDs modifiers
-0057 genes = regexprep(genes,'^\d+\.','');
-0058 level = fileContent{3};
-0059 fclose(fID);
-0060 [a,b] = ismember(genes,uniprotDB.genes);
-0061 uniprot = uniprotDB.ID(b(a));
-0062 level(~a) = [];
-0063 clear protData
-0064 protData.uniprotIDs = uniprot;
-0065 protData.level = level;
-0066 % Get MW and abundance (unit does not matter, f is fraction)
-0067 [~,idx] = ismember(protData.uniprotIDs,uniprotDB.ID);
-0068 protData.MW = uniprotDB.MW(idx);
-0069 protData.abundances = protData.level .* protData.MW;
-0070 end
-0071
-0072 avgAbundances = mean(protData.abundances,2);
-0073 totalProt = sum(avgAbundances,'omitnan');
-0074
-0075 % Get enzymes in model
-0076 enzymesInModel = ismember(protData.uniprotIDs,enzymes);
-0077 totalEnz = sum(avgAbundances(enzymesInModel),'omitnan');
-0078
-0079 f = totalEnz/totalProt;
-0080 end
function model = constrainEnzConcs(model, removeConstraints)
-
-
DESCRIPTION
-
constrainEnzConcs
- Constrain enzyme usages by their concentration as provided in
- model.ec.concs. For enzymes with non-NaN entries in model.ec.concs,
- their enzyme usage reaction will no longer draw from the protein pool,
- but is rather constraint by the measured protein abundance.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec
- structure) with enzyme concentrations in
- model.ec.concs
- removeConstraints logical, whether enzyme concentration
- constraints should be removed (model.ec.concs
- will remain unchanged). (optional, default false)
-
- Output:
- model an ecModel constraint with available enzyme concentrations
-
- Note: To populate model.ec.concs you should run fillEnzConcs.
-
- Usage:
- model = constrainEnzConcs(model, removeConstraints)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function model = constrainEnzConcs(model, removeConstraints)
-0002 % constrainEnzConcs
-0003 % Constrain enzyme usages by their concentration as provided in
-0004 % model.ec.concs. For enzymes with non-NaN entries in model.ec.concs,
-0005 % their enzyme usage reaction will no longer draw from the protein pool,
-0006 % but is rather constraint by the measured protein abundance.
-0007 %
-0008 % Input:
-0009 % model an ecModel in GECKO 3 format (with ecModel.ec
-0010 % structure) with enzyme concentrations in
-0011 % model.ec.concs
-0012 % removeConstraints logical, whether enzyme concentration
-0013 % constraints should be removed (model.ec.concs
-0014 % will remain unchanged). (optional, default false)
-0015 %
-0016 % Output:
-0017 % model an ecModel constraint with available enzyme concentrations
-0018 %
-0019 % Note: To populate model.ec.concs you should run fillEnzConcs.
-0020 %
-0021 % Usage:
-0022 % model = constrainEnzConcs(model, removeConstraints)
-0023
-0024 %Enzyme with NaN entry in model.ec.concs => draw from prot_pool
-0025 %Enzyme with numeric entry in model.ec.concs => exchange reaction with
-0026 %enzyme level as UB
-0027
-0028 if nargin<2
-0029 removeConstraints = false;
-0030 end
-0031
-0032 %Get indices of usage reactions
-0033 usageRxns = strcat('usage_prot_',model.ec.enzymes);
-0034 [~, usageRxnsIdx] = ismember(usageRxns, model.rxns);
-0035
-0036 if any(usageRxnsIdx == 0)
-0037 error('Usage reactions are not defined for all enzymes. This is done by makeEcModel.')
-0038 end
-0039 %Get index of protein pool metabolite
-0040 protPoolIdx = find(ismember(model.mets,'prot_pool'));
-0041 if ~any(protPoolIdx)
-0042 error('Cannot find protein pool pseudometabolite.')
-0043 end
-0044
-0045 %Protein that should be constraint by UB
-0046 if removeConstraints
-0047 protCons = [];
-0048 else
-0049 protCons = ~isnan(model.ec.concs);
-0050 end
-0051
-0052 %Set all reactions to draw from prot_pool
-0053 model.S(protPoolIdx, usageRxnsIdx) = 1;
-0054 model.lb(usageRxnsIdx) = -1000;
-0055
-0056 %If non-NaN in model.ec.concs, then constrain by UB
-0057 if any(protCons)
-0058 % model.S(protPoolIdx, usageRxnsIdx(protCons)) = 0; % Since GECKO 3.2.0
-0059 model.lb(usageRxnsIdx(protCons)) = -model.ec.concs(protCons);
-0060 end
-0061 end
function model = constrainFluxData(model, fluxData, condition, maxMinGrowth, looseStrictFlux, modelAdapter)
-
-
DESCRIPTION
-
constrainFluxData
- Constrains fluxes to the data that is provided in the fluxData
- structure, which itself is read by loadFluxData from data/fluxData.tsv.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- fluxData structure with flux data
- conds sampling condition
- Ptot total protein (g/gDCW)
- grRate growth rate (1/h)
- exchFluxes exchange fluxes (mmol/gDCWh)
- exchMets exchanged metabolites, matching exchFluxes
- exchRxnIDs exchange reaction IDs, matching exchMets
- condition either index number or name of the sample condition in
- fluxData.conds (Optional, default = 1)
- maxMinGrowth 'max' if the provided growth rate should be set as
- maximum growth rate (= upper bound), or 'min' if it
- should be set as minimum growth rate (= lower bound).
- The latter option is suitable if minimization of
- prot_pool_exchange is used as objective function. (Opt,
- default = 'max')
- looseStrictFlux how strictly constrained the exchange fluxes should be,
- optional, default = 'loose'
- 'loose' if the exchange fluxes should be constraint
- only by the "outer bounds". If exchFluxes(i)
- > 0, LB = 0 and UB = exchFluxes(i). If
- exchFluxes(i) < 0, LB = exchFluxes(i) and
- UB = 0
- 0-100 LB and UB constraints are set with a specified
- percentage of variance around exchFluxes. If 10
- is specified, LB = exchFluxes*0.95 and UB =
- exchFluxes*1.05. This allows for 10% variance
- around the exchFluxes values, but strictly
- forces a flux through the exchRxns.
- modelAdapter a loaded model adapter (Optional, will otherwise use
- the default model adapter)
-
- Output:
- model an ecModel where fluxes are constraint
-
- Note: If a provided constraint is either -1000 or 1000, then the function
- will update the reaction lower and upper bound to either allow uptake or
- excretion, irrespective of what option is given as the looseStrictFlux
- parameter.
-
- Usage:
- model = constrainFluxData(model, fluxData, condition, maxMinGrowth, looseStrictFlux, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function model = constrainFluxData(model, fluxData, condition, maxMinGrowth, looseStrictFlux, modelAdapter)
-0002 % constrainFluxData
-0003 % Constrains fluxes to the data that is provided in the fluxData
-0004 % structure, which itself is read by loadFluxData from data/fluxData.tsv.
-0005 %
-0006 % Input:
-0007 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0008 % fluxData structure with flux data
-0009 % conds sampling condition
-0010 % Ptot total protein (g/gDCW)
-0011 % grRate growth rate (1/h)
-0012 % exchFluxes exchange fluxes (mmol/gDCWh)
-0013 % exchMets exchanged metabolites, matching exchFluxes
-0014 % exchRxnIDs exchange reaction IDs, matching exchMets
-0015 % condition either index number or name of the sample condition in
-0016 % fluxData.conds (Optional, default = 1)
-0017 % maxMinGrowth 'max' if the provided growth rate should be set as
-0018 % maximum growth rate (= upper bound), or 'min' if it
-0019 % should be set as minimum growth rate (= lower bound).
-0020 % The latter option is suitable if minimization of
-0021 % prot_pool_exchange is used as objective function. (Opt,
-0022 % default = 'max')
-0023 % looseStrictFlux how strictly constrained the exchange fluxes should be,
-0024 % optional, default = 'loose'
-0025 % 'loose' if the exchange fluxes should be constraint
-0026 % only by the "outer bounds". If exchFluxes(i)
-0027 % > 0, LB = 0 and UB = exchFluxes(i). If
-0028 % exchFluxes(i) < 0, LB = exchFluxes(i) and
-0029 % UB = 0
-0030 % 0-100 LB and UB constraints are set with a specified
-0031 % percentage of variance around exchFluxes. If 10
-0032 % is specified, LB = exchFluxes*0.95 and UB =
-0033 % exchFluxes*1.05. This allows for 10% variance
-0034 % around the exchFluxes values, but strictly
-0035 % forces a flux through the exchRxns.
-0036 % modelAdapter a loaded model adapter (Optional, will otherwise use
-0037 % the default model adapter)
-0038 %
-0039 % Output:
-0040 % model an ecModel where fluxes are constraint
-0041 %
-0042 % Note: If a provided constraint is either -1000 or 1000, then the function
-0043 % will update the reaction lower and upper bound to either allow uptake or
-0044 % excretion, irrespective of what option is given as the looseStrictFlux
-0045 % parameter.
-0046 %
-0047 % Usage:
-0048 % model = constrainFluxData(model, fluxData, condition, maxMinGrowth, looseStrictFlux, modelAdapter)
-0049
-0050 if nargin < 6 || isempty(modelAdapter)
-0051 modelAdapter = ModelAdapterManager.getDefault();
-0052 if isempty(modelAdapter)
-0053 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0054 end
-0055 end
-0056 params = modelAdapter.getParameters();
-0057
-0058 if nargin < 5 || isempty(looseStrictFlux)
-0059 looseStrictFlux = 'loose';
-0060 end
-0061
-0062 if nargin < 4 || isempty(maxMinGrowth)
-0063 maxMinGrowth = 'max';
-0064 end
-0065
-0066 if nargin < 2 || isempty(fluxData)
-0067 fluxData = loadFluxData(fullfile(params.path,'data','fluxData.tsv'),modelAdapter);
-0068 end
-0069
-0070 if nargin < 3 || isempty(condition)
-0071 condition = 1;
-0072 elseif ~isnumeric(condition)
-0073 idx = find(strcmp(fluxData.conds,condition));
-0074 if isempty(condition)
-0075 error(['Condition ' condition ' cannot be found in fluxData'])
-0076 else
-0077 condition = idx;
-0078 end
-0079 end
-0080
-0081 % Select the exchange fluxes of specified condition
-0082 fluxData.exchFluxes = fluxData.exchFluxes(condition,:);
-0083
-0084 %Set original c-source to zero
-0085 model = setParam(model,'eq',params.c_source,0);
-0086 %Set growth
-0087 switch maxMinGrowth
-0088 case'max'
-0089 model = setParam(model,'lb',params.bioRxn,0);
-0090 model = setParam(model,'ub',params.bioRxn,fluxData.grRate(condition));
-0091 case'min'
-0092 model = setParam(model,'lb',params.bioRxn,fluxData.grRate(condition));
-0093 model = setParam(model,'ub',params.bioRxn,1000);
-0094 end
-0095
-0096 naFlux = isnan(fluxData.exchFluxes);
-0097 negFlux = lt(fluxData.exchFluxes,0); % less than 0
-0098 ub = fluxData.exchFluxes(~(negFlux | naFlux));
-0099 posFlux = fluxData.exchRxnIDs(~(negFlux | naFlux));
-0100 lb = fluxData.exchFluxes(negFlux & ~naFlux);
-0101 negFlux = fluxData.exchRxnIDs(negFlux & ~naFlux);
-0102
-0103 switch looseStrictFlux
-0104 case'loose'
-0105 model = setParam(model,'lb',negFlux,lb);
-0106 model = setParam(model,'ub',negFlux,0);
-0107 model = setParam(model,'lb',posFlux,0);
-0108 model = setParam(model,'ub',posFlux,ub);
-0109 otherwise
-0110 model = setParam(model,'var',fluxData.exchRxnIDs,fluxData.exchFluxes,looseStrictFlux);
-0111 end
-0112 extremeFlux = find(abs(fluxData.exchFluxes)==1000);
-0113 if any(extremeFlux)
-0114 exchFlux = fluxData.exchFluxes(extremeFlux);
-0115 if any(exchFlux==-1000)
-0116 model = setParam(model,'lb',fluxData.exchRxnIDs(extremeFlux(exchFlux==-1000)),-1000);
-0117 model = setParam(model,'ub',fluxData.exchRxnIDs(extremeFlux(exchFlux==-1000)),0);
-0118 end
-0119 if any(exchFlux==1000)
-0120 model = setParam(model,'lb',fluxData.exchRxnIDs(extremeFlux(exchFlux==1000)),0);
-0121 model = setParam(model,'ub',fluxData.exchRxnIDs(extremeFlux(exchFlux==1000)),1000);
-0122 end
-0123 end
-0124 end
function model = fillEnzConcs(model, protData, dataCol)
-
-
DESCRIPTION
-
fillEnzConcs
- Uses the protein concentrations from protData to fill model.ec.concs.
- Protein levels should be provided in mg/gDCW. If no data is provided
- a particular protein, its level is NaN. Existing entries in
- model.ec.concs are overwritten.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- protData structure with proteome data, from loadProtFluxData
- uniprotIDs cell arrray with Uniprot IDs matching
- protData.abundances
- abundances matrix of proteomics data
- dataCol number indicating the column in protData.abundances that
- contains the relevant protein concentrations (protData may
- contain data from multiple conditions/samples/experiments,
- each with their own column in protData.abundances.
- Optional, default = 1.
-
- Output:
- model an ecModel where model.ec.concs is populated with protein
- concentrations.
-
- Note: to also constrain the model with the content of model.ec.concs, you
- should run constrainEnzConcs.
-
- Usage:
- model = fillEnzConcs(model, protData)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function model = fillEnzConcs(model, protData, dataCol)
-0002 % fillEnzConcs
-0003 % Uses the protein concentrations from protData to fill model.ec.concs.
-0004 % Protein levels should be provided in mg/gDCW. If no data is provided
-0005 % a particular protein, its level is NaN. Existing entries in
-0006 % model.ec.concs are overwritten.
-0007 %
-0008 % Input:
-0009 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0010 % protData structure with proteome data, from loadProtFluxData
-0011 % uniprotIDs cell arrray with Uniprot IDs matching
-0012 % protData.abundances
-0013 % abundances matrix of proteomics data
-0014 % dataCol number indicating the column in protData.abundances that
-0015 % contains the relevant protein concentrations (protData may
-0016 % contain data from multiple conditions/samples/experiments,
-0017 % each with their own column in protData.abundances.
-0018 % Optional, default = 1.
-0019 %
-0020 % Output:
-0021 % model an ecModel where model.ec.concs is populated with protein
-0022 % concentrations.
-0023 %
-0024 % Note: to also constrain the model with the content of model.ec.concs, you
-0025 % should run constrainEnzConcs.
-0026 %
-0027 % Usage:
-0028 % model = fillEnzConcs(model, protData)
-0029
-0030 if nargin < 3 || isempty(dataCol)
-0031 dataCol = 1;
-0032 end
-0033
-0034 uniprotIDs = protData.uniprotIDs;
-0035 abundances = protData.abundances(:,dataCol);
-0036
-0037 %Redefine an empty model.ec.concs vector
-0038 model.ec.concs=nan(numel(model.ec.enzymes),1);
-0039
-0040 [a,b] = ismember(uniprotIDs, model.ec.enzymes);
-0041 model.ec.concs(b(a)) = abundances(a);
-0042 end
function [model, flexEnz] = flexibilizeEnzConcs(model, expGrowth, foldChange, iterPerEnzyme, modelAdapter, verbose)
-
-
DESCRIPTION
-
flexibilizeEnzConcs
- Flexibilize enzyme concentration of an ecModel with constrained with
- proteomics data. The upper bound of the protein usage reaction is
- changed, while the concentrations in ecModel.ec.concs remain unchanged.
-
- If no (more) limiting enzyme concentrations can be found, it might be
- the protein pool exchange that is limiting growth. In that case, an
- attempt will be made to relax the protein pool exchange reaction, and
- if the growth rate indeed increases, it is suggested to set the protein
- pool exchange unconstrained (lb=-1000) before again running
- flexibilizeEnzConcs. Such situations, where a proteomics integrated
- ecModel is overconstrained, may occur if the ecModel should be able to
- simulate maximum growth rate (from e.g. batch cultivation).
-
- If relaxing the protein pool exchange does not increase the growth
- rate, then this is not due to enzyme constraints, but rather an issue
- with the metabolic network itself, or the set nutrient exchange is not
- sufficient.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- expGrowth estimated experimental growth rate. If not specified,
- the value will be read from the model adapter
- foldChange a value how much increase the enzyme concentration
- (Optional, default = 2)
- iterPerEnzyme the number of iterations that an enzyme can be increased.
- A zero number can be defined. if zero is defined no limit
- will be set, and it will increase the enzyme concentration
- until reach de defined growth rate (Optional, default = 5)
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter)
- verbose logical whether progress should be reported (Optional,
- default true)
-
- Output:
- model ecModel where the constraint of measured enzyme
- concentrations have been flexibilized (=relaxed) in the
- S-matrix, to allow the model to reach the growth rate
- defined in expGrowth, while the values in
- ecModel.ec.concs have remained untouched.
- flexEnz array with information about flexibilized proteins
- uniprotIDs enzymes whose usage UB was flexibilized
- oldConcs original concentrations, from mode.ec.concs
- flexConcs flexibilized concentrations, new UB in
- model
- ratioIncr ratio by which the concentration increased,
- the enzymes will be sorted by this field
- frequence numeric how often the enzyme has been
- step-wise flexibilized
-
- Usage:
- [model, flexEnz] = flexibilizeEnzConcs(model, expGrowth, foldChange, iterPerEnzyme, modelAdapter, verbose)
0001 function [model, flexEnz] = flexibilizeEnzConcs(model, expGrowth, foldChange, iterPerEnzyme, modelAdapter, verbose)
-0002 % flexibilizeEnzConcs
-0003 % Flexibilize enzyme concentration of an ecModel with constrained with
-0004 % proteomics data. The upper bound of the protein usage reaction is
-0005 % changed, while the concentrations in ecModel.ec.concs remain unchanged.
-0006 %
-0007 % If no (more) limiting enzyme concentrations can be found, it might be
-0008 % the protein pool exchange that is limiting growth. In that case, an
-0009 % attempt will be made to relax the protein pool exchange reaction, and
-0010 % if the growth rate indeed increases, it is suggested to set the protein
-0011 % pool exchange unconstrained (lb=-1000) before again running
-0012 % flexibilizeEnzConcs. Such situations, where a proteomics integrated
-0013 % ecModel is overconstrained, may occur if the ecModel should be able to
-0014 % simulate maximum growth rate (from e.g. batch cultivation).
-0015 %
-0016 % If relaxing the protein pool exchange does not increase the growth
-0017 % rate, then this is not due to enzyme constraints, but rather an issue
-0018 % with the metabolic network itself, or the set nutrient exchange is not
-0019 % sufficient.
-0020 %
-0021 % Input:
-0022 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0023 % expGrowth estimated experimental growth rate. If not specified,
-0024 % the value will be read from the model adapter
-0025 % foldChange a value how much increase the enzyme concentration
-0026 % (Optional, default = 2)
-0027 % iterPerEnzyme the number of iterations that an enzyme can be increased.
-0028 % A zero number can be defined. if zero is defined no limit
-0029 % will be set, and it will increase the enzyme concentration
-0030 % until reach de defined growth rate (Optional, default = 5)
-0031 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0032 % default model adapter)
-0033 % verbose logical whether progress should be reported (Optional,
-0034 % default true)
-0035 %
-0036 % Output:
-0037 % model ecModel where the constraint of measured enzyme
-0038 % concentrations have been flexibilized (=relaxed) in the
-0039 % S-matrix, to allow the model to reach the growth rate
-0040 % defined in expGrowth, while the values in
-0041 % ecModel.ec.concs have remained untouched.
-0042 % flexEnz array with information about flexibilized proteins
-0043 % uniprotIDs enzymes whose usage UB was flexibilized
-0044 % oldConcs original concentrations, from mode.ec.concs
-0045 % flexConcs flexibilized concentrations, new UB in
-0046 % model
-0047 % ratioIncr ratio by which the concentration increased,
-0048 % the enzymes will be sorted by this field
-0049 % frequence numeric how often the enzyme has been
-0050 % step-wise flexibilized
-0051 %
-0052 % Usage:
-0053 % [model, flexEnz] = flexibilizeEnzConcs(model, expGrowth, foldChange, iterPerEnzyme, modelAdapter, verbose)
-0054
-0055 if nargin < 6 || isempty(verbose)
-0056 verbose = true;
-0057 end
-0058 if nargin < 5 || isempty(modelAdapter)
-0059 modelAdapter = ModelAdapterManager.getDefault();
-0060 if isempty(modelAdapter)
-0061 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0062 end
-0063 end
-0064 params = modelAdapter.getParameters();
-0065
-0066 if nargin < 4 || isempty(iterPerEnzyme)
-0067 iterPerEnzyme = 5;
-0068 end
-0069
-0070 if nargin < 3 || isempty(foldChange)
-0071 foldChange = 2;
-0072 end
-0073
-0074 if nargin < 2 || isempty(expGrowth)
-0075 expGrowth = modelAdapter.getParameters().gR_exp;
-0076 end
-0077
-0078 % If a zero value is defined, not iteration limit will be set
-0079 if iterPerEnzyme == 0
-0080 iterPerEnzyme = inf;
-0081 end
-0082
-0083 bioRxnIdx = getIndexes(model,params.bioRxn,'rxns');
-0084 if model.ub(bioRxnIdx) < expGrowth
-0085 fprintf(['The upper bound of the biomass reaction was %s, while expGrowth '...
-0086 'is %s. The upper bound has been set to expGrowth.'],...
-0087 num2str(model.ub(bioRxnIdx)), num2str(expGrowth))
-0088 model.ub(bioRxnIdx) = expGrowth;
-0089 end
-0090
-0091 % Keep track if protein pool will be flexibilized
-0092 changedProtPool = false;
-0093
-0094 % In case the model have not been protein constrained
-0095 % model = constrainEnzConcs(model);
-0096
-0097 [sol,hs] = solveLP(model);
-0098 predGrowth = sol.f;
-0099
-0100 % Get those proteins with a concentration defined
-0101 protConcs = find(~isnan(model.ec.concs));
-0102
-0103 % Get enzymes names with a concentration value
-0104 proteins = model.ec.enzymes(protConcs);
-0105
-0106 % Store how many time is predicted a enzyme with the highest coeff
-0107 frequence = zeros(numel(proteins),1);
-0108
-0109 flexBreak=false;
-0110 if any(protConcs)
-0111 while predGrowth < expGrowth
-0112
-0113 % Get the control coefficients
-0114 [~, controlCoeffs] = getConcControlCoeffs(model, proteins, foldChange, 0.75);
-0115
-0116 % Stop looking for limiting proteins all coeffs are zero
-0117 if any(controlCoeffs)
-0118
-0119 % Find the maximum coefficient index
-0120 [~,maxIdx] = max(controlCoeffs);
-0121
-0122 % Increase +1 the frequence
-0123 frequence(maxIdx) = frequence(maxIdx) + 1;
-0124
-0125 % Allow to increase the UB maximum five times
-0126 if frequence(maxIdx) <= iterPerEnzyme
-0127
-0128 % Set how much will increase the UB
-0129 increase = foldChange*frequence(maxIdx);
-0130
-0131 % Get usage rxn for the highest coeff
-0132 protUsageIdx = strcmpi(model.rxns, strcat('usage_prot_', proteins(maxIdx)));
-0133
-0134 % replace the UB
-0135 model.lb(protUsageIdx) = -(model.ec.concs(protConcs(maxIdx)) * (1+increase));
-0136
-0137 % Get the new growth rate
-0138 sol = solveLP(model,0,[],hs);
-0139 predGrowth = sol.f;
-0140
-0141 if verbose
-0142 disp(['Protein ' proteins{maxIdx} ' LB adjusted. Grow: ' num2str(predGrowth)])
-0143 end
-0144 else
-0145 printOrange(['WARNING: Limit has been reached. Protein ' proteins{maxIdx} ' seems to be problematic. Consider changing the kcat.\n']);
-0146 flexBreak=true;
-0147 break
-0148 end
-0149 else
-0150 disp('No (more) limiting enzymes have been found. Attempting to increase protein pool exchange...')
-0151 protPoolIdx = getIndexes(model,'prot_pool_exchange','rxns');
-0152 oldProtPool = -model.lb(protPoolIdx);
-0153 tempModel = setParam(model,'lb',protPoolIdx,-1000);
-0154 tempModel = setParam(tempModel,'ub',bioRxnIdx,expGrowth);
-0155 sol = solveLP(tempModel);
-0156 if (sol.f-predGrowth)>1e-10 % There is improvement in growth rate
-0157 % Find new protein pool constraint
-0158 predGrowth = sol.f;
-0159 tempModel = setParam(tempModel,'lb',bioRxnIdx,predGrowth);
-0160 tempModel = setParam(tempModel,'obj',protPoolIdx,1);
-0161 sol = solveLP(tempModel);
-0162 newProtPool = sol.f;
-0163 model.lb(protPoolIdx) = -newProtPool;
-0164 fprintf(['Changing the lower bound of protein pool exchange from %s ',...
-0165 'to %s enabled a\ngrowth rate of %s. It can be helpful to set ', ...
-0166 'the lower bound of protein\npool exchange to -1000 before ',...
-0167 'running flexibilizeEnzConcs.\n'],num2str(-oldProtPool), ...
-0168 num2str(-newProtPool),num2str(predGrowth));
-0169 changedProtPool = true;
-0170 else
-0171 fprintf(['Protein pool exchange was also not limiting. Inability to reach growth ',...
-0172 'rate is not related to\nenzyme constraints. Maximum growth rate ',...
-0173 'is %s.\n'], num2str(predGrowth))
-0174 end
-0175 % To return an usable model with these conditions, set the
-0176 % highest growth rate reached as the experimental value
-0177 expGrowth = predGrowth;
-0178 break
-0179 end
-0180 end
-0181 else
-0182 error('Protein concentrations have not been defined. Please run readProteomics and constrainEnzConcs')
-0183 end
-0184
-0185 protFlex = proteins(frequence>0);
-0186 [~,protUsageIdx] = ismember(strcat('usage_prot_', protFlex), model.rxns);
-0187 [~,ecProtId] = ismember(protFlex, model.ec.enzymes);
-0188 oldConcs = model.ec.concs(ecProtId);
-0189 %frequence = frequence(ecProtId);
-0190
-0191 if flexBreak == false && ~isempty(protFlex)
-0192 % Not all flexibilized proteins require flexibilization in the end
-0193 % Test flux distribution with minimum prot_pool usage
-0194 modelTemp = setParam(model,'var',params.bioRxn,expGrowth,0.5);
-0195 modelTemp = setParam(modelTemp,'obj','prot_pool_exchange',1);
-0196 sol = solveLP(modelTemp,1);
-0197 % Check which concentrations can remain unchanged
-0198 newConcs = abs(sol.x(protUsageIdx));
-0199 keepOld = newConcs<oldConcs;
-0200 % Set UBs
-0201 model.lb(protUsageIdx(keepOld)) = -oldConcs(keepOld);
-0202 model.lb(protUsageIdx(~keepOld)) = -newConcs(~keepOld);
-0203 % Output structure
-0204 protFlex(keepOld) = [];
-0205 oldConcs(keepOld) = [];
-0206 newConcs(keepOld) = [];
-0207 frequence=frequence(frequence>0);
-0208 frequence(keepOld) = [];
-0209
-0210 flexEnz.uniprotIDs = protFlex;
-0211 flexEnz.oldConcs = oldConcs;
-0212 flexEnz.flexConcs = newConcs;
-0213 flexEnz.frequence = frequence(frequence>0);
-0214 else
-0215 protFlex = proteins(frequence>0);
-0216 [~, protUsageIdx] = ismember(strcat('usage_prot_', protFlex), model.rxns);
-0217
-0218 flexEnz.uniprotIDs = protFlex;
-0219 flexEnz.oldConcs = model.ec.concs(ecProtId);
-0220 flexEnz.flexConcs = abs(model.lb(protUsageIdx));
-0221 flexEnz.frequence = frequence(frequence>0);
-0222 end
-0223 if changedProtPool
-0224 flexEnz.uniprotIDs(end+1) = {'prot_pool'};
-0225 flexEnz.oldConcs(end+1) = oldProtPool;
-0226 flexEnz.flexConcs(end+1) = newProtPool;
-0227 flexEnz.frequence(end+1) = 1;
-0228 end
-0229 % Sort by biggest ratio increase
-0230 flexEnz.ratioIncr = flexEnz.flexConcs./flexEnz.oldConcs;
-0231 [~,b] = sort(flexEnz.ratioIncr,'descend');
-0232 flexEnz.ratioIncr = flexEnz.ratioIncr(b);
-0233 flexEnz.uniprotIDs = flexEnz.uniprotIDs(b);
-0234 flexEnz.oldConcs = flexEnz.oldConcs(b);
-0235 flexEnz.flexConcs = flexEnz.flexConcs(b);
-0236 flexEnz.frequence = flexEnz.frequence(b);
-0237 end
function [enz, controlCoeffs] = getConcControlCoeffs(model, proteins, foldChange, limit)
-
-
DESCRIPTION
-
getConcControlCoeffs
- Calculate an control coefficients of protein usage.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- proteins a list of proteins to calculate the coefficients. (Optional,
- default = model.ec.enzymes)
- foldChange a value how much increase the protein concentration.
- (Optional, default = 2)
- limit a value to determine limiting protein usage reactions.
- Calculate as usage/concentration (Optional, default = 0)
-
- Output:
- enz a logical vector of enzymes analyzed
- controlCoeffs a vector array with the coefficients
-
- Usage:
- [enz, controlCoeffs] = getConcControlCoeffs(model, proteins, foldChange, limit);
0001 function [enz, controlCoeffs] = getConcControlCoeffs(model, proteins, foldChange, limit)
-0002 % getConcControlCoeffs
-0003 % Calculate an control coefficients of protein usage.
-0004 %
-0005 % Input:
-0006 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0007 % proteins a list of proteins to calculate the coefficients. (Optional,
-0008 % default = model.ec.enzymes)
-0009 % foldChange a value how much increase the protein concentration.
-0010 % (Optional, default = 2)
-0011 % limit a value to determine limiting protein usage reactions.
-0012 % Calculate as usage/concentration (Optional, default = 0)
-0013 %
-0014 % Output:
-0015 % enz a logical vector of enzymes analyzed
-0016 % controlCoeffs a vector array with the coefficients
-0017 %
-0018 % Usage:
-0019 % [enz, controlCoeffs] = getConcControlCoeffs(model, proteins, foldChange, limit);
-0020
-0021 if nargin < 4 || isempty(limit)
-0022 limit = 0;
-0023 end
-0024
-0025 if nargin < 3 || isempty(foldChange)
-0026 foldChange = 2;
-0027 end
-0028
-0029 if nargin < 2 || isempty(proteins)
-0030 proteins = model.ec.enzymes;
-0031 end
-0032
-0033 % for now
-0034 enz = false(length(proteins),1);
-0035 controlCoeffs = zeros(length(proteins),1);
-0036
-0037 [sol,hs] = solveLP(model);
-0038 initialGrowth = sol.f;
-0039
-0040 % Get enzyme index
-0041 [~, protIdx] = ismember(proteins, model.ec.enzymes);
-0042
-0043 % Get the protein usage reactions
-0044 protUsageRxns = strcat('usage_prot_', model.ec.enzymes(protIdx));
-0045 [~, protUsageRxnIdx] = ismember(protUsageRxns, model.rxns);
-0046
-0047 for i = 1:numel(proteins)
-0048 % Get the previous concentration
-0049 prevConc = model.lb(protUsageRxnIdx(i));
-0050
-0051 % Only consider those with a usage close the UB
-0052 if (sol.x(protUsageRxnIdx(i))/prevConc) > limit
-0053
-0054 % Update the logical vector
-0055 enz(i) = 1;
-0056
-0057 % Create a temporal model since coeff will be calculated one enzyme at
-0058 % the time, without other change
-0059 tempModel = model;
-0060 % Increase the concentration by flexfactor
-0061 newConc = prevConc*(foldChange);
-0062 tempModel.lb(protUsageRxnIdx(i)) = newConc;
-0063
-0064 % Get the new growth rate after the adjustment
-0065 [tempSol,hs] = solveLP(tempModel,0,[],hs);
-0066 tempGrowth = tempSol.f;
-0067
-0068 % Calculate the coeff only if new growth rate is significantly
-0069 % higher than initial value
-0070 if (tempGrowth-initialGrowth)>1e-10
-0071 controlCoeffs(i) = (tempGrowth-initialGrowth)/(prevConc-newConc);
-0072 end
-0073 end
-0074
-0075 end
-0076 end
-0077
function ecModel = updateProtPool(ecModel, Ptot, modelAdapter)
-
-
DESCRIPTION
-
updateProtPool
- Obsolete since GECKO 3.2.0, as all (measured and unmeasured) enzymes
- are drawn from the protein pool. Instead, use setProtPoolSize. See
- https://github.com/SysBioChalmers/GECKO/issues/375 for explanation.
-
- Before GECKO 3.2.0: updates the protein pool to compensate for measured
- proteomics data (in model.ec.concs), as only the unmeasured enzymes
- draw from the protein pool.
-
- Input:
- ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
- Ptot total protein content in g/gDCW, overwrites the value
- from modelAdapter. For instance, condition-specific
- fluxData.Ptot from loadFluxData can be used. If nothing
- is provided, the modelAdapter value is used.
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- model an ecModel where model.ec.concs is populated with
- protein concentrations
- Usage:
- ecModel = updateProtPool(ecModel, Ptot, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function ecModel = updateProtPool(ecModel, Ptot, modelAdapter)
-0002 % updateProtPool
-0003 % Obsolete since GECKO 3.2.0, as all (measured and unmeasured) enzymes
-0004 % are drawn from the protein pool. Instead, use setProtPoolSize. See
-0005 % https://github.com/SysBioChalmers/GECKO/issues/375 for explanation.
-0006 %
-0007 % Before GECKO 3.2.0: updates the protein pool to compensate for measured
-0008 % proteomics data (in model.ec.concs), as only the unmeasured enzymes
-0009 % draw from the protein pool.
-0010 %
-0011 % Input:
-0012 % ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
-0013 % Ptot total protein content in g/gDCW, overwrites the value
-0014 % from modelAdapter. For instance, condition-specific
-0015 % fluxData.Ptot from loadFluxData can be used. If nothing
-0016 % is provided, the modelAdapter value is used.
-0017 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0018 % default model adapter).
-0019 %
-0020 % Output:
-0021 % model an ecModel where model.ec.concs is populated with
-0022 % protein concentrations
-0023 % Usage:
-0024 % ecModel = updateProtPool(ecModel, Ptot, modelAdapter)
-0025
-0026 % Do not run from GECKO version 3.2.0 onwards. This can be recognized by
-0027 % prot_usage reactions that are constrained by proteomics and still draw
-0028 % from the protein pool
-0029 protRxns = find(startsWith(ecModel.rxns,'usage_prot_'));
-0030 poolMetIdx = find(strcmp(ecModel.mets,'prot_pool'));
-0031 % Selected proteins with proteomics constraints
-0032 constProtRxns = ~(ecModel.lb(protRxns)==-1000);
-0033 % Are any still drawing from prot_pool? This is introduced in GECKO 3.2.0.
-0034 if any(full(ecModel.S(poolMetIdx,protRxns(constProtRxns))))
-0035 error(['In the provided ecModel, all protein usage reactions, both with '...
-0036 'and without concentration constraints, draw from the protein pool. '...
-0037 'This was introduced with GECKO 3.2.0. Since then, updateProtPool '...
-0038 'has become obsolete, use setProtPoolSize instead to constrain the '...
-0039 'total protein pool with condition-specific total protein content. See '...
-0040 '<a href="https://github.com/SysBioChalmers/GECKO/issues/375">here</a> '...
-0041 'for more explanation.'])
-0042 end
-0043
-0044 if nargin < 3 || isempty(modelAdapter)
-0045 modelAdapter = ModelAdapterManager.getDefault();
-0046 if isempty(modelAdapter)
-0047 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0048 end
-0049 end
-0050 params = modelAdapter.params;
-0051
-0052 if nargin < 2 || isempty(Ptot)
-0053 Ptot = params.Ptot;
-0054 disp(['Total protein content used: ' num2str(Ptot) ' [g protein/gDw]'])
-0055 end
-0056
-0057 % Convert Ptot to mg/gDW if provided in g/gDCW (which is default)
-0058 if Ptot < 1
-0059 Ptot = Ptot * 1000;
-0060 end
-0061
-0062 Pmeas = sum(ecModel.ec.concs,'omitnan');
-0063 if Pmeas == 0
-0064 error('The model has not yet been populated with proteomics, as ecModel.ec.concs is empty.')
-0065 end
-0066
-0067 Pnew = (Ptot - Pmeas) * params.f;
-0068
-0069 if Pnew > 0
-0070 PoolRxnIdx = strcmp(ecModel.rxns,'prot_pool_exchange');
-0071 ecModel.lb(PoolRxnIdx) = -Pnew*params.sigma;
-0072 sol = solveLP(ecModel);
-0073 if isempty(sol.x)
-0074 error(['Estimating the remaining protein pool by subtracting the '...
-0075 'sum of measured enzyme concentrations (in ecModel.ec.concs) '...
-0076 'from the total protein pool (Ptot) does not yield a functional '...
-0077 'model.'])
-0078 end
-0079 else
-0080 error('The total measured protein mass exceeds the total protein content.')
-0081 end
-0082 end
0001 %Abstract Base class for adapters for different species
-0002 classdef (Abstract) ModelAdapter
-0003 methods (Abstract)
-0004 [spont,spontRxnNames] = getSpontaneousReactions(obj,model);
-0005 end
-0006 methods
-0007 %not really needed, we could access it directly. Not very nice, but practical
-0008 function parameters = getParameters(obj)
-0009 parameters = obj.params;
-0010 end
-0011
-0012 %This allows for changing parameters in an adapter without
-0013 %creating a new class. May be convenient, although less clean
-0014 %function obj = setParameters(obj, parameters)
-0015 % obj.mParams = parameters;
-0016 %end
-0017
-0018 %The genes returned here should match the gene id set in the parameter params.uniprot.geneIDfield
-0019 function genes = getUniprotCompatibleGenes(obj,inGenes)
-0020 genes = inGenes;
-0021 end
-0022
-0023 function uniprotIDs = getUniprotIDsFromTable(obj,modelGenes)
-0024 conversionTable = fullfile(obj.params.path,'data','uniprotConversion.tsv');
-0025 if exist(conversionTable,'file')
-0026 fID=fopen(conversionTable,'r');
-0027 conversionTable = textscan(fID,'%q %q','Delimiter','\t','HeaderLines',1);
-0028 fclose(fID);
-0029
-0030 modelIDs = conversionTable{1,1};
-0031 uniprots = conversionTable{1,2};
-0032
-0033 [a,b] = ismember(modelGenes,modelIDs);
-0034
-0035 uniprotIDs = strings(numel(modelGenes),1);
-0036 uniprotIDs(a) = uniprots(b(a));
-0037 disp('The model genes are matched to Uniprot via the table at data/uniprotConversion.tsv.')
-0038 else
-0039 uniprotIDs = modelGenes;
-0040 end
-0041 end
-0042
-0043 function folder = getBrendaDBFolder(obj)
-0044 folder = fullfile(findGECKOroot(),'databases');
-0045 end
-0046
-0047 function x = getPhylDistStructPath(obj)
-0048 x = fullfile(findRAVENroot(),'external','kegg','keggPhylDist.mat');
-0049 end
-0050
-0051 % Define a model-specific function in the 'code' subfolder of the
-0052 % project folder, that can constrain the model to anaerobic
-0053 % conditions, and refer to this function in the modelAdapter. This
-0054 % function is used when running bayesianSensitivityTuning.m (via
-0055 % abc_max.m) if the fluxData has anaerobic conditions.
-0056 function ecModel = makeModelAnaerobic(obj,ecModel)
-0057 ecModel = ecModel;
-0058 end
-0059 end
-0060
-0061 %To have the params public is a bit "ugly", but very practical
-0062 %if we want to change a parameter
-0063 properties (Access = public)
-0064 params;
-0065 end
-0066 end
0001 %Abstract Base class for adapters for different species
-0002 classdef ModelAdapterManager
-0003 methods(Static)
-0004 function adapter = getAdapter(adapterPath, addToMatlabPath)
-0005 if nargin < 2
-0006 addToMatlabPath = true;
-0007 end
-0008
-0009 [adapterFolder, adapterClassName, extension] = fileparts(adapterPath);
-0010 if ~strcmp(extension, '.m')
-0011 error('Please provide the full path to the adapter file, including the file extension.');
-0012 else
-0013 s = pathsep;
-0014 pathStr = [s, path, s];
-0015 onPath = contains(pathStr, [s, adapterFolder, s], 'IgnoreCase', ispc);
-0016 % Check if the folder is on the path
-0017 if ~onPath
-0018 if addToMatlabPath
-0019 addpath(adapterFolder);
-0020 else
-0021 printOrange(['WARNING: The adapter will not be on the MATLAB path, since addToMatlabPath is false\n'...
-0022 'and it is not currently on the path. Either set addToMatlabPath to true, fix this\n'...
-0023 'manually before calling this function or make sure it is in current directory (not\n'...
-0024 'recommended). If the class is not reachable throughout the entire GECKO use there will\n'...
-0025 'be errors throughout.\n']);
-0026 end
-0027 end
-0028
-0029 end
-0030 adapter = feval(adapterClassName);
-0031 end
-0032
-0033 function out = getDefault()
-0034 out = ModelAdapterManager.setGetDefault();
-0035 end
-0036
-0037 function adapter = setDefault(adapterPath, addToMatlabPath)
-0038 if nargin < 1 || isempty(adapterPath)
-0039 adapter = ModelAdapterManager.setGetDefault(adapterPath);
-0040 return
-0041 end
-0042 if nargin < 2
-0043 addToMatlabPath = true;
-0044 end
-0045 adapter = ModelAdapterManager.setGetDefault(ModelAdapterManager.getAdapter(adapterPath, addToMatlabPath));
-0046 end
-0047
-0048 end
-0049 methods(Static,Access = private)
-0050 % This is how they recommend defining static variables in Matlab
-0051 function out = setGetDefault(val)
-0052 persistent defaultAdapter; %will be empty initially
-0053 if nargin
-0054 defaultAdapter = val;
-0055 end
-0056 out = defaultAdapter;
-0057 end
-0058 end
-0059 end
0001 classdef KEY_CLASSNAME < ModelAdapter
-0002 methods
-0003 function obj = KEY_CLASSNAME()
-0004 % Set initial values of the obj.params - they can be changed by the user
-0005
-0006 % Directory where all model-specific files and scripts are kept.
-0007 % Is assumed to follow the GECKO-defined folder structure.
-0008 obj.params.path = fullfile('KEY_PATH', 'KEY_NAME');
-0009 addpath(fullfile(obj.params.path,'code'));
-0010
-0011 % Path to the conventional GEM that this ecModel will be based on.
-0012 obj.params.convGEM = fullfile(obj.params.path,'models','yourModel.xml');
-0013
-0014 % Average enzyme saturation factor
-0015 obj.params.sigma = 0.5;
-0016
-0017 % Total protein content in the cell [g protein/gDw]
-0018 obj.params.Ptot = 0.5;
-0019
-0020 % Fraction of enzymes in the model [g enzyme/g protein]
-0021 obj.params.f = 0.5;
-0022
-0023 % Growth rate the model should be able to reach when not
-0024 % constraint by nutrient uptake (e.g. max growth rate) [1/h]
-0025 obj.params.gR_exp = 0.41;
-0026
-0027 % Provide your organism scientific name
-0028 obj.params.org_name = 'genus species';
-0029
-0030 % Taxonomic identifier for Complex Portal
-0031 obj.params.complex.taxonomicID = [];
-0032
-0033 % Provide your organism KEGG ID, selected at
-0034 % https://www.genome.jp/kegg/catalog/org_list.html
-0035 obj.params.kegg.ID = 'sce';
-0036 % Field for KEGG gene identifier; should match the gene
-0037 % identifiers used in the model. With 'kegg', it takes the
-0038 % default KEGG Entry identifier (for example YER023W here:
-0039 % https://www.genome.jp/dbget-bin/www_bget?sce:YER023W).
-0040 % Alternatively, gene identifiers from the "Other DBs" section
-0041 % of the KEGG page can be selected. For example "NCBI-GeneID",
-0042 % "UniProt", or "Ensembl". Not all DB entries are available for
-0043 % all organisms and/or genes.
-0044 obj.params.kegg.geneID = 'kegg';
-0045
-0046 % Provide what identifier should be used to query UniProt.
-0047 % Select proteome IDs at https://www.uniprot.org/proteomes/
-0048 % or taxonomy IDs at https://www.uniprot.org/taxonomy.
-0049 obj.params.uniprot.type = 'taxonomy'; % 'proteome' or 'taxonomy'
-0050 obj.params.uniprot.ID = '559292'; % should match the ID type
-0051 % Field for Uniprot gene ID - should match the gene ids used in the
-0052 % model. It should be one of the "Returned Field" entries under
-0053 % "Names & Taxonomy" at this page: https://www.uniprot.org/help/return_fields
-0054 obj.params.uniprot.geneIDfield = 'gene_oln';
-0055 % Whether only reviewed data from UniProt should be considered.
-0056 % Reviewed data has highest confidence, but coverage might be (very)
-0057 % low for non-model organisms
-0058 obj.params.uniprot.reviewed = false;
-0059
-0060 % Reaction ID for glucose exchange reaction (or other preferred carbon source)
-0061 obj.params.c_source = 'r_1714';
-0062
-0063 % Reaction ID for biomass pseudoreaction
-0064 obj.params.bioRxn = 'r_4041';
-0065
-0066 % Name of the compartment where the protein pseudometabolites
-0067 % should be located (all be located in the same compartment,
-0068 % this does not interfere with them catalyzing reactions in
-0069 % different compartments). Typically, cytoplasm is chosen.
-0070 obj.params.enzyme_comp = 'cytoplasm';
-0071 end
-0072
-0073 % function ecModel = makeModelAnaerobic(ecModel)
-0074 % % Define a model-specific function in the 'code' subfolder,
-0075 % % that can constrain the model to anaerobic conditions, and
-0076 % % include the name of this function here. This is used by
-0077 % % bayesianSensitivityTuning.m (via abc_max.m) if the fluxData
-0078 % % has anaerobic conditions.
-0079 % addpath(fullfile(obj.params.path,'code'));
-0080 % ecModel = nameOfModelSpecificFunction(ecModel);
-0081 % end
-0082
-0083 function [spont,spontRxnNames] = getSpontaneousReactions(obj,model)
-0084 % Indicates how spontaneous reactions are identified. Here it
-0085 % is done by the reaction have 'spontaneous' in its name.
-0086 spont = contains(model.rxnNames,'spontaneous');
-0087 spontRxnNames = model.rxnNames(spont);
-0088 end
-0089 end
-0090 end
function [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, newEnzymes, modelAdapter)
-
-
DESCRIPTION
-
addNewRxnsToEC
- Add new reaction to an enzyme-constrained model. This function is
- useful to simulate metabolic manipulations done to an organism such as
- integration of new genes that add a new reaction/pathway.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
- newRxns structure with the new reaction information as follow:
- rxns cell array with unique strings that
- identifies each reaction
- rxnNames cell array with the names of each reaction
- equations cell array with equation strings. Decimal
- coefficients are expressed as "1.2".
- Reversibility is indicated by "<=>" or
- "=>".
- grRules cell array with the gene-reaction
- relationship for each reaction. For example
- "(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 (opt, default '')
- newEnzymes structure with the new enzymes information as follow:
- enzymes cell array with uniprot id
- genes cell array with the respective gene
- mw cell array with the MW
- modelAdapter a loaded model adapter (Optional, will otherwise use the
- default model adapter).
-
- Output:
- model ecModel whit new reactions
- rxnsAdded cell array with the reactions added
-
- Notes:
- (i) Write equations as follows:
- 'xylitol[c] + NAD[c] => D-xylulose[c] + NADH[c] + H+[c]'
- Note that the reversibility defined in the equation will be used
- to split to construct irreversible reactions (add _REV) and the
- rules defined will be used to expand the model (add _EXP_n).
-
- (2) After add the new reactions, setKcatForReactions or
- applyCustomKcats should be run to add the kcat to the reactions,
- and subsequently applyKcatConstraints.
-
- Usage:
- [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, newEnzymes, modelAdapter);
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, newEnzymes, modelAdapter)
-0002 % addNewRxnsToEC
-0003 % Add new reaction to an enzyme-constrained model. This function is
-0004 % useful to simulate metabolic manipulations done to an organism such as
-0005 % integration of new genes that add a new reaction/pathway.
-0006 %
-0007 % Input:
-0008 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0009 % newRxns structure with the new reaction information as follow:
-0010 % rxns cell array with unique strings that
-0011 % identifies each reaction
-0012 % rxnNames cell array with the names of each reaction
-0013 % equations cell array with equation strings. Decimal
-0014 % coefficients are expressed as "1.2".
-0015 % Reversibility is indicated by "<=>" or
-0016 % "=>".
-0017 % grRules cell array with the gene-reaction
-0018 % relationship for each reaction. For example
-0019 % "(A and B) or (C)" means that the reaction
-0020 % could be catalyzed by a complex between
-0021 % A & B or by C on its own. All the genes
-0022 % have to be present in model.genes. Add
-0023 % genes with addGenesRaven before calling
-0024 % this function if needed (opt, default '')
-0025 % newEnzymes structure with the new enzymes information as follow:
-0026 % enzymes cell array with uniprot id
-0027 % genes cell array with the respective gene
-0028 % mw cell array with the MW
-0029 % modelAdapter a loaded model adapter (Optional, will otherwise use the
-0030 % default model adapter).
-0031 %
-0032 % Output:
-0033 % model ecModel whit new reactions
-0034 % rxnsAdded cell array with the reactions added
-0035 %
-0036 % Notes:
-0037 % (i) Write equations as follows:
-0038 % 'xylitol[c] + NAD[c] => D-xylulose[c] + NADH[c] + H+[c]'
-0039 % Note that the reversibility defined in the equation will be used
-0040 % to split to construct irreversible reactions (add _REV) and the
-0041 % rules defined will be used to expand the model (add _EXP_n).
-0042 %
-0043 % (2) After add the new reactions, setKcatForReactions or
-0044 % applyCustomKcats should be run to add the kcat to the reactions,
-0045 % and subsequently applyKcatConstraints.
-0046 %
-0047 % Usage:
-0048 % [model, rxnsAdded, enzAdded] = addNewRxnsToEC(model, newRxns, newEnzymes, modelAdapter);
-0049
-0050 if nargin < 4 || isempty(modelAdapter)
-0051 modelAdapter = ModelAdapterManager.getDefault();
-0052 if isempty(modelAdapter)
-0053 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0054 end
-0055 end
-0056 params = modelAdapter.getParameters();
-0057
-0058 if nargin < 3 || isempty(newEnzymes)
-0059 newEnzymes = [];
-0060 end
-0061
-0062 % Validate if GECKO version 3
-0063 if ~isfield(model, 'ec')
-0064 error('Input model is not an ecModel in GECKO 3 format (with ecModel.ec structure)')
-0065 end
-0066
-0067 if model.ec.geckoLight
-0068 error('addNewRxnsToEC only works on non-light ecModels')
-0069 else
-0070 % Check if the new proteins are not already present in the model
-0071 if ~isempty(newEnzymes)
-0072 [toRemove,~] = ismember(newEnzymes.enzymes, model.ec.enzymes);
-0073
-0074 % Remove from the list those enzymes that already present
-0075 if any(toRemove)
-0076 printOrange(['WARNING: Enzymes ' strjoin(newEnzymes.enzymes(toRemove),', ') ' are already present in the model and will not be added.\n']);
-0077 newEnzymes.enzymes(toRemove) = [];
-0078 newEnzymes.genes(toRemove) = [];
-0079 newEnzymes.mw(toRemove) = [];
-0080 end
-0081
-0082 if ~isempty(newEnzymes.enzymes)
-0083
-0084 % 1. Add new genes
-0085 genesToAdd.genes = newEnzymes.genes;
-0086 genesToAdd.geneShortNames = genesToAdd.genes;
-0087 model = addGenesRaven(model, genesToAdd);
-0088
-0089 % 2. Add new Enzyme-usage pseudometabolite
-0090
-0091 % Validate compartment to add protein pseudoreactions
-0092 compartmentID = strcmp(model.compNames,params.enzyme_comp);
-0093 if ~any(compartmentID)
-0094 error(['Compartment ' params.enzyme_comp ' (specified in params.enzyme_comp) '...
-0095 'cannot be found in model.compNames'])
-0096 end
-0097 compartmentID = model.comps(compartmentID);
-0098
-0099 proteinMets.mets = strcat('prot_', newEnzymes.enzymes);
-0100 proteinMets.metNames = proteinMets.mets;
-0101 proteinMets.compartments = compartmentID;
-0102 if isfield(model,'metMiriams')
-0103 proteinMets.metMiriams = repmat({struct('name',{{'sbo'}},'value',{{'SBO:0000252'}})},numel(proteinMets.mets),1);
-0104 end
-0105 if isfield(model,'metCharges')
-0106 proteinMets.metCharges = zeros(numel(proteinMets.mets),1);
-0107 end
-0108 proteinMets.metNotes = repmat({'Enzyme-usage pseudometabolite'},numel(proteinMets.mets),1);
-0109 model = addMets(model,proteinMets);
-0110 end
-0111 enzAdded = newEnzymes.enzymes(:);
-0112 end
-0113
-0114 % 3. Add new reactions
-0115
-0116 % Before add the reactions validate that all the genes in grRules are
-0117 % present in the model.
-0118 genesInRules = strjoin(convertCharArray(newRxns.grRules));
-0119 genesInRules = regexp(genesInRules,' |)|(|and|or','split'); % Remove all grRule punctuation
-0120 genesInRules = genesInRules(~cellfun(@isempty,genesInRules)); % Remove spaces and empty genes
-0121 genesInRules = setdiff(unique(genesInRules),model.genes); % Only keep new genes
-0122
-0123 % If there is any gene which is not present, as to be pass as
-0124 % newEnzymes input
-0125 if ~isempty(genesInRules)
-0126 errorText = 'The following genes from grRules are not present in the model, add them as newEnzymes input:';
-0127 dispEM(errorText,true,genesInRules,false)
-0128 end
-0129
-0130 % Get reversible reactions to split
-0131 toIrrev = find(cellfun(@(x) contains(x,'<=>'),newRxns.equations));
-0132 if ~isempty(toIrrev)
-0133 for i=1:numel(toIrrev)
-0134 idx = toIrrev(i);
-0135 newEquation = split(newRxns.equations(idx),'<=>');
-0136 % Remove directionality
-0137 newRxns.equations(idx) = erase(newRxns.equations(idx), '<');
-0138
-0139 % Add a new reaction to the list to be added
-0140 newRxns.rxns{end+1} = [newRxns.rxns{idx} '_REV'];
-0141 newRxns.rxnNames{end+1} = [newRxns.rxnNames{idx} ' (reversible)'];
-0142 newRxns.equations{end+1} = [strtrim(newEquation{2, 1}) ' => ' strtrim(newEquation{1, 1})];
-0143 newRxns.grRules{end+1} = newRxns.grRules{idx};
-0144 end
-0145 end
-0146
-0147 % Get reactions to separate isozymes
-0148 toSplit = find(cellfun(@(x) contains(x,'or'),newRxns.grRules));
-0149 if ~isempty(toSplit)
-0150 for i=1:numel(toSplit)
-0151 idx = toSplit(i);
-0152 newRules = split(newRxns.grRules(idx),'or');
-0153 newRules = strtrim(erase(newRules, {'('')'}));
-0154
-0155 for j=1:numel(newRules)
-0156 newRxns.rxns{end+1} = [newRxns.rxns{idx} '_EXP_' num2str(j)];
-0157 newRxns.rxnNames(end+1) = newRxns.rxnNames(idx);
-0158 newRxns.equations(end+1) = newRxns.equations(idx);
-0159 newRxns.grRules(end+1) = newRules(j);
-0160 end
-0161
-0162 % Remove the original one
-0163 newRxns.rxns(idx) = [];
-0164 newRxns.rxnNames(idx) = [];
-0165 newRxns.equations(idx) = [];
-0166 newRxns.grRules(idx) = [];
-0167 end
-0168 end
-0169
-0170 newRxns.lb = zeros(length(newRxns.rxns),1);
-0171 newRxns.ub = zeros(length(newRxns.rxns),1) + 1000;
-0172
-0173 model = addRxns(model, newRxns, 3, '', true, true);
-0174
-0175 % 4. Add protein usage reactions.
-0176 usageRxns.rxns = strcat('usage_',proteinMets.mets);
-0177 usageRxns.rxnNames = usageRxns.rxns;
-0178 usageRxns.mets = cell(numel(usageRxns.rxns),1);
-0179 usageRxns.stoichCoeffs = cell(numel(usageRxns.rxns),1);
-0180 for i=1:numel(usageRxns.mets)
-0181 usageRxns.mets{i} = {proteinMets.mets{i}, 'prot_pool'};
-0182 usageRxns.stoichCoeffs{i} = [-1,1];
-0183 end
-0184 usageRxns.lb = zeros(numel(usageRxns.rxns),1) - 1000;
-0185 usageRxns.ub = zeros(numel(usageRxns.rxns),1);
-0186 usageRxns.rev = ones(numel(usageRxns.rxns),1);
-0187 usageRxns.grRules = newEnzymes.genes;
-0188 model = addRxns(model,usageRxns);
-0189
-0190 % 5. Update .ec field with enzyme data
-0191 model.ec.enzymes = [model.ec.enzymes; newEnzymes.enzymes(:)];
-0192 model.ec.genes = [model.ec.genes; newEnzymes.genes(:)];
-0193 model.ec.mw = [model.ec.mw; newEnzymes.mw(:)];
-0194 model.ec.sequence = [model.ec.sequence; repmat({''},numel(newEnzymes.enzymes),1)];
-0195 if isfield(model.ec,'concs')
-0196 model.ec.concs = [model.ec.concs; NaN(numel(newEnzymes.enzymes),1)];
-0197 end
-0198
-0199 % 6. Expand the enzyme rxns matrix
-0200 rxnWithRule = find(~cellfun('isempty',newRxns.grRules));
-0201
-0202 model.ec.rxnEnzMat = [model.ec.rxnEnzMat, zeros(length(model.ec.rxns), length(newEnzymes.enzymes))]; % add n new enzymes
-0203 model.ec.rxnEnzMat = [model.ec.rxnEnzMat; zeros(length(rxnWithRule), length(model.ec.enzymes))]; % add n new rxns
-0204
-0205 % 7. Update .ec field with rxn data
-0206
-0207 % Get reactions to add
-0208 numRxns = length(model.ec.rxns);
-0209 for i = 1:numel(rxnWithRule)
-0210 rxnIdx = rxnWithRule(i);
-0211
-0212 model.ec.rxns(end+1) = newRxns.rxns(rxnIdx);
-0213 model.ec.kcat(end+1) = 0;
-0214 model.ec.source(end+1) = {''};
-0215 model.ec.notes(end+1) = {''};
-0216 model.ec.eccodes(end+1) = {''};
-0217
-0218 % Get rules
-0219 genes = newRxns.grRules(rxnIdx);
-0220 genes = strtrim(erase(split(genes,'and'), {'('')'}));
-0221 [~,enzIdx] = ismember(genes, model.ec.genes);
-0222
-0223 % Update the enzyme rxns matrix
-0224 model.ec.rxnEnzMat(numRxns+i, enzIdx) = 1;
-0225 end
-0226
-0227 rxnsAdded = newRxns.rxns(:);
-0228 end
-0229 end
function fseof = ecFSEOF(model,prodTargetRxn,csRxn,nSteps,outputFile,filePath,modelAdapter)
-
-
DESCRIPTION
-
ecFSEOF
- Function that runs Flux-Scanning with Enforced Objective Function (FSEOF)
- for a specified production target.
-
- Input:
- model an ecModel in GECKO 3 format (with ecModel.ec structure).
- prodTargetRxn rxn ID for the production target reaction, a exchange
- reaction is recommended.
- csRxn rxn ID for the main carbon source uptake reaction.
- nSteps number of steps for suboptimal objective in FSEOF.
- (Optional, default 16)
- outputFile bolean option to save results in a file. (Optional,
- default false)
- filePath file path for results output. It will store two files:
- - at the genes level, ecFSEOF_genes.tsv
- - at the reactions level, ecFSEOF_rxns.tsv
- (Optional, default in the 'output' sub-folder taken from
- modelAdapter, e.g. output/ecFSEOF_rxns.tsv)
- modelAdapter a loaded model adapter. (Optional, will otherwise use
- the default model adapter)
-
- Output:
- fseof an structure with all results. Contains the following fields:
- - alpha: target production used for enforced
- objetive limits (from minimum to maximum
- production)
- - v_matrix: fluxes for each target reaction predicted
- and each alpha.
- - rxnTargets: a list with all reactions with fluxes that
- change consistently as target production
- increases.
- Contains: ID, name, slope, gene rule, and
- equation
- - transportTargets: a list with all transport reactions with
- fluxes that change consistently as target
- production increases.
- Contains: ID, name, slope, gene rule, and
- equation
- - geneTargets: a list with all selected targets that
- increase production.
- Contains: gene, shortName, slope, action,
- and essentiality
-
- Usage:
- fseof = ecFSEOF(model,prodTargetRxn,csRxn,nSteps,outputFile,filePath,filterG,modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function fseof = ecFSEOF(model,prodTargetRxn,csRxn,nSteps,outputFile,filePath,modelAdapter)
-0002 % ecFSEOF
-0003 % Function that runs Flux-Scanning with Enforced Objective Function (FSEOF)
-0004 % for a specified production target.
-0005 %
-0006 % Input:
-0007 % model an ecModel in GECKO 3 format (with ecModel.ec structure).
-0008 % prodTargetRxn rxn ID for the production target reaction, a exchange
-0009 % reaction is recommended.
-0010 % csRxn rxn ID for the main carbon source uptake reaction.
-0011 % nSteps number of steps for suboptimal objective in FSEOF.
-0012 % (Optional, default 16)
-0013 % outputFile bolean option to save results in a file. (Optional,
-0014 % default false)
-0015 % filePath file path for results output. It will store two files:
-0016 % - at the genes level, ecFSEOF_genes.tsv
-0017 % - at the reactions level, ecFSEOF_rxns.tsv
-0018 % (Optional, default in the 'output' sub-folder taken from
-0019 % modelAdapter, e.g. output/ecFSEOF_rxns.tsv)
-0020 % modelAdapter a loaded model adapter. (Optional, will otherwise use
-0021 % the default model adapter)
-0022 %
-0023 % Output:
-0024 % fseof an structure with all results. Contains the following fields:
-0025 % - alpha: target production used for enforced
-0026 % objetive limits (from minimum to maximum
-0027 % production)
-0028 % - v_matrix: fluxes for each target reaction predicted
-0029 % and each alpha.
-0030 % - rxnTargets: a list with all reactions with fluxes that
-0031 % change consistently as target production
-0032 % increases.
-0033 % Contains: ID, name, slope, gene rule, and
-0034 % equation
-0035 % - transportTargets: a list with all transport reactions with
-0036 % fluxes that change consistently as target
-0037 % production increases.
-0038 % Contains: ID, name, slope, gene rule, and
-0039 % equation
-0040 % - geneTargets: a list with all selected targets that
-0041 % increase production.
-0042 % Contains: gene, shortName, slope, action,
-0043 % and essentiality
-0044 %
-0045 % Usage:
-0046 % fseof = ecFSEOF(model,prodTargetRxn,csRxn,nSteps,outputFile,filePath,filterG,modelAdapter)
-0047
-0048 if nargin < 7 || isempty(modelAdapter)
-0049 modelAdapter = ModelAdapterManager.getDefault();
-0050 if isempty(modelAdapter)
-0051 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0052 end
-0053 end
-0054 params = modelAdapter.getParameters();
-0055
-0056 if nargin < 5 || isempty(outputFile)
-0057 outputFile = false;
-0058 end
-0059
-0060 if nargin < 6 || isempty(filePath)
-0061 filePath = fullfile(params.path,'output');
-0062 end
-0063
-0064 if nargin < 4 || isempty(nSteps)
-0065 nSteps = 16;
-0066 end
-0067
-0068 % Get relevant rxn indexes
-0069 prodTargetRxnIdx = getIndexes(model, prodTargetRxn,'rxns');
-0070 csRxnIdx = getIndexes(model, csRxn,'rxns');
-0071 bioRxnIdx = getIndexes(model, params.bioRxn,'rxns');
-0072
-0073 % Standardize grRules and rxnGeneMat in model
-0074 [grRules,rxnGeneMat] = standardizeGrRules(model,true);
-0075 model.grRules = grRules;
-0076 model.rxnGeneMat = rxnGeneMat;
-0077
-0078 % Check carbon source uptake rate and LB defined
-0079 model = setParam(model, 'obj', params.bioRxn, 1);
-0080 sol = solveLP(model);
-0081 if model.lb(csRxnIdx) < sol.x(csRxnIdx)
-0082 printOrange(['WARNING: Carbon source lower bound was set to ' num2str(model.lb(csRxnIdx)) ...
-0083 ', but the uptake rate after model optimization is ' num2str(sol.x(csRxnIdx)) '.\n']);
-0084 end
-0085
-0086 % run FSEOF analysis
-0087
-0088 % Find out the initial target production.
-0089 iniTarget = sol.x(prodTargetRxnIdx);
-0090
-0091 % Find out the maximum theoretical yield of target reaction.
-0092 model = setParam(model,'obj',prodTargetRxn,1);
-0093 sol = solveLP(model,1);
-0094 % Set to 90%% based on https://doi.org/10.1128/AEM.00115-10
-0095 maxTarget = sol.x(prodTargetRxnIdx) * 0.9;
-0096
-0097 % Define alpha vector for suboptimal enforced objective values between
-0098 % minimal production and 90%% of the maximum theoretical yield, initialize
-0099 % fluxes matriz
-0100 alpha = iniTarget:((maxTarget-iniTarget)/(nSteps-1)):maxTarget;
-0101 v_matrix = zeros(length(model.rxns),length(alpha));
-0102
-0103 % Enforce objective flux iteratively
-0104 progressbar('Flux Scanning with Enforced Objective Function')
-0105 for i = 1:nSteps
-0106 % Enforce the objetive flux of product formation
-0107 model = setParam(model,'eq',prodTargetRxnIdx,alpha(i));
-0108
-0109 % Restore minimum biomass lb to zero and set it as objective
-0110 model.lb(bioRxnIdx) = 0;
-0111 model = setParam(model,'obj',params.bioRxn,1);
-0112 sol = solveLP(model,1);
-0113
-0114 % Store flux distribution
-0115 v_matrix(:,i) = sol.x;
-0116
-0117 progressbar(i/nSteps)
-0118 end
-0119 progressbar(1) % Make sure it closes
-0120
-0121 % Take out rxns with no grRule and standard gene
-0122 withGR = ~cellfun(@isempty,model.grRules);
-0123 stdIdx = contains(model.grRules,'standard');
-0124 withGR(stdIdx) = 0;
-0125 target_rxns = model.rxns(withGR);
-0126 v_matrix = v_matrix(withGR,:);
-0127 rxnGeneM = model.rxnGeneMat(withGR,:);
-0128
-0129 % Filter out rxns that are always zero
-0130 zero_flux = ~all(abs(v_matrix(:,1:nSteps))==0,2);
-0131 target_rxns = target_rxns(zero_flux,:);
-0132 v_matrix = v_matrix(zero_flux,:);
-0133 rxnGeneM = rxnGeneM(zero_flux,:);
-0134
-0135 % Identify those rxns that always increase or decrease, and calculate the
-0136 % slope as the difference in the flux when enforce objetive target
-0137 % production is set to 90%% of the maximum teorethical yield
-0138 % << v_matrix(i,nSteps-1) >> and the flux when the enforce objetive target
-0139 % production is set to the minimum << v_matrix(i,1) >> for the reaction i,
-0140 % divided by maxTarget-maxTarget/nSteps-1.
-0141 slope_rxns = zeros(size(target_rxns));
-0142 targets = logical(size(target_rxns));
-0143 target_type = cell(size(target_rxns));
-0144 for i = 1:length(target_rxns)
-0145 if issorted(abs(v_matrix(i,1:nSteps)),'strictascend')
-0146 % Those reactions that always increase while enforcing target
-0147 % production are suggested for Over Expression
-0148 targets(i) = true;
-0149 slope_rxns(i) = abs(v_matrix(i,nSteps-1)-v_matrix(i,1))/abs(maxTarget-maxTarget/nSteps-1);
-0150 target_type(i) = {'OE'};
-0151 elseif issorted(abs(v_matrix(i,1:nSteps)),'strictdescend')
-0152 % Those reactions that always decrease while enforcing target
-0153 % production are suggested for KnockDown or KnockOut. KO are those
-0154 % reactions which have zero flux when enforcing target production
-0155 % to 90%% of the maximum theoretical yield.
-0156 targets(i) = true;
-0157 slope_rxns(i) = abs(v_matrix(i,nSteps-1)-v_matrix(i,1))/abs(maxTarget-maxTarget/nSteps-1);
-0158 if v_matrix(i,nSteps) == 0
-0159 target_type(i) = {'KO'};
-0160 else
-0161 target_type(i) = {'KD'};
-0162 end
-0163 end
-0164 end
-0165
-0166 % Only keep those reaction that shows an increase or decrease pattern.
-0167 target_rxns = target_rxns(targets);
-0168 v_matrix = v_matrix(targets,:);
-0169 rxnGeneM = rxnGeneM(targets,:);
-0170 slope_rxns = slope_rxns(targets);
-0171 target_type = target_type(targets);
-0172
-0173 % Order from highest to lowest slope
-0174 [~,order] = sort(slope_rxns,'descend');
-0175 target_rxns = target_rxns(order);
-0176 v_matrix = v_matrix(order,:);
-0177 rxnGeneM = rxnGeneM(order,:);
-0178 slope_rxns = slope_rxns(order);
-0179 target_type = target_type(order);
-0180
-0181 % Filter out reactions with slope < quantile
-0182 non_zero_slope = slope_rxns > quantile(slope_rxns,0.75);
-0183 target_rxns = target_rxns(non_zero_slope);
-0184 v_matrix = v_matrix(non_zero_slope,:);
-0185 rxnGeneM = rxnGeneM(non_zero_slope,:);
-0186 slope_rxns = slope_rxns(non_zero_slope);
-0187 target_type = target_type(non_zero_slope);
-0188
-0189 % Create gene list of those connected to the remaining rxns
-0190 genes = model.genes(sum(rxnGeneM,1) > 0);
-0191 slope_genes = zeros(size(genes));
-0192 rxnGeneM = rxnGeneM(:,sum(rxnGeneM,1) > 0);
-0193 target_type_genes = cell(size(genes));
-0194 essentiality = cell(size(genes));
-0195
-0196 % Validate for gene essentiality
-0197 progressbar('Checking for gene essentiality')
-0198 for i = 1:numel(genes)
-0199
-0200 % Block protein usage to KO al the reactions associated to it.
-0201 usage_rxn_idx = strcmpi(model.ec.genes, genes{i});
-0202 usage_rxn = strcat('usage_prot_', model.ec.enzymes(usage_rxn_idx));
-0203 tempModel = setParam(model, 'eq', usage_rxn, 0);
-0204 solKO = solveLP(tempModel);
-0205 % Check if no feasible solution was found
-0206 if solKO.stat == -1 || solKO.x(bioRxnIdx) < 1e-8
-0207 essentiality(i) = {'essential'};
-0208 end
-0209
-0210 % Since a gene can be involved in multiple reactions, multiple
-0211 % engineering manipulations can be suggested for the same gene
-0212 % e.g. (OE and KD). So, reconcilie them, and report only one.
-0213
-0214 % Get reaction index, in targets set, for each gene
-0215 rxns_for_gene = find(rxnGeneM(:,i) > 0);
-0216 actions = unique(target_type(rxns_for_gene));
-0217 if numel(actions) > 1
-0218 % If any OE is suggested give the highest priority over KD or KO
-0219 if any(startsWith(actions, 'OE'))
-0220 target_type_genes(i) = {'OE'};
-0221 % Otherwise change to KO if not essential
-0222 elseif ~any(startsWith(actions, 'OE')) && ~isequal(essentiality(i),{'essential'})
-0223 target_type_genes(i) = {'KO'};
-0224 else
-0225 target_type_genes(i) = {'KD'};
-0226 end
-0227 else
-0228 target_type_genes(i) = actions;
-0229 end
-0230 % Extract all the slope (from rxns across alphas) conected to
-0231 % each remaining gene
-0232 slope_set = slope_rxns(rxns_for_gene);
-0233 slope_genes(i) = mean(slope_set);
-0234
-0235 progressbar(i/numel(genes))
-0236 end
-0237 progressbar(1) % Make sure it closes
-0238
-0239 % Order genes from highest to lowest slope:
-0240 [~,order] = sort(slope_genes,'descend');
-0241 genes = genes(order,:);
-0242 slope_genes = slope_genes(order,:);
-0243 target_type_genes = target_type_genes(order,:);
-0244 essentiality = essentiality(order,:);
-0245
-0246 % Create output:
-0247
-0248 % Report values used to enforce objective target production
-0249 fseof.alpha = alpha;
-0250
-0251 % Exclude enzyme usage reactions
-0252 toKeep = ~startsWith(target_rxns,'usage_prot_');
-0253 rxnIdx = getIndexes(model,target_rxns(toKeep),'rxns');
-0254
-0255 % Report the fluxes at each alpha
-0256 fseof.v_matrix = array2table(v_matrix(toKeep,:), ...
-0257 'VariableNames', string(alpha), 'RowNames', model.rxns(rxnIdx));
-0258
-0259 % Report reaction targets
-0260 fseof.rxnTargets(:,1) = model.rxns(rxnIdx);
-0261 fseof.rxnTargets(:,2) = model.rxnNames(rxnIdx);
-0262 fseof.rxnTargets(:,3) = num2cell(slope_rxns(toKeep));
-0263 fseof.rxnTargets(:,4) = model.grRules(rxnIdx);
-0264 fseof.rxnTargets(:,5) = constructEquations(model,rxnIdx);
-0265
-0266 % Identify transport reactions; defined as reactions involving (at least)
-0267 % one metabolite name in more than one compartment.
-0268 transportRxns = false(numel(rxnIdx),1);
-0269 for i = 1:numel(rxnIdx)
-0270 % Get the involved metabolites in each reaction
-0271 mets = model.metNames(model.S(:,rxnIdx(i))~=0);
-0272 % Remove enzyme metabolite
-0273 mets(startsWith(mets,'prot_')) = [];
-0274 % Validate if it is a transport reaciton
-0275 transportRxns(i) = numel(mets) ~= numel(unique(mets));
-0276 end
-0277
-0278 % Create separate table for transport reactions
-0279 fseof.transportTargets = fseof.rxnTargets(transportRxns,:);
-0280 fseof.rxnTargets(transportRxns,:) = [];
-0281
-0282 % Report gene targets
-0283 geneIdx = cellfun(@(x) find(strcmpi(model.genes,x)),genes);
-0284 fseof.geneTargets(:,1) = genes;
-0285 fseof.geneTargets(:,2) = model.geneShortNames(geneIdx);
-0286 fseof.geneTargets(:,3) = num2cell(slope_genes);
-0287 fseof.geneTargets(:,4) = target_type_genes;
-0288 fseof.geneTargets(:,5) = essentiality;
-0289
-0290 % Save results in a file if defined
-0291 if outputFile
-0292 % Write file with gene targets
-0293 writetable(cell2table(fseof.geneTargets, ...
-0294 'VariableNames', {'gene_IDs''gene_names''slope''action''essentiality'}), ...
-0295 fullfile(filePath, 'ecFSEOF_genes.tsv'), ...
-0296 'FileType', 'text', ...
-0297 'Delimiter', '\t', ...
-0298 'QuoteStrings', false);
-0299
-0300 % Write file with rxn targets
-0301 writetable(cell2table(fseof.rxnTargets, ...
-0302 'VariableNames', {'rxn_IDs''rxnNames''slope''grRules''rxn_formula'}), ...
-0303 fullfile(filePath, 'ecFSEOF_rxns.tsv'), ...
-0304 'FileType', 'text', ...
-0305 'Delimiter', '\t', ...
-0306 'QuoteStrings', false);
-0307
-0308 % Write file with transport targets
-0309 writetable(cell2table(fseof.transportTargets, ...
-0310 'VariableNames', {'rxn_IDs''rxnNames''slope''grRules''rxn_formula'}), ...
-0311 fullfile(filePath, 'ecFSEOF_transporter.tsv'), ...
-0312 'FileType', 'text', ...
-0313 'Delimiter', '\t', ...
-0314 'QuoteStrings', false);
-0315
-0316 disp(['ecFSEOF results stored at: ' newline filePath]);
-0317 end
-0318
-0319 end
function [minFlux, maxFlux] = ecFVA(ecModel, model)
-
-
DESCRIPTION
-
ecFVA
- Flux variability analysis is performed on the ecModel, and isozymic
- reactions are combined to construct ouput minFlux and maxFlux vectors,
- which follow the same order of model.rxns. The output from this
- function does not include enzyme usage reactions, to observe these, on
- could consider running flux variability directly on the ecModel.
-
- Input:
- ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
- model non-ecModel variant of the ecModel, to which the minFlux
- and maxFlux will be mapped
- Output:
- minFlux vector of minimum flux rates, corresponding to model.rxns
- maxFlux vector of maximum flux rates, corresponding to model.rxns
-
- Usage: [minFlux, maxFlux] = ecFVA(ecModel, model)
function usageData = enzymeUsage(ecModel,fluxes,zero)
-
-
DESCRIPTION
-
enzymeUsage
- Gives enzyme usages based on a provided flux distribution, as obtained
- from a full GECKO model. It can give:
- 1) absolute usage: the specific enzyme usage in mg/gDCW, which can
- be given for enzymes with- and without concentration information;
- 2) capacity usage: the ratio of available enzyme that is used, calcuted
- by (absUsage/UB) (note that capacity usage is 0 if an enzyme
- concentration was not constrained in the model);
- 3) UB: the upper bound of each enzyme exchange reaction, which may not
- be the same as the enzyme concentration, if it has been
- flexibilized
- 4) protID: the protein identifiers for each enzyme (if the model has an
- enzymes field than this order is used, otherwise it is given
- alphabetically.
-
- Input:
- ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
- fluxes vector of fluxes, for instance sol.x
- zero logical whether also enzymes with zero absolute usage
- should be included (Optional, default true)
-
- Output:
- usageData structure with enzyme usage data
- capUsage vector of enzyme capacity usages
- absUsage vector of absolute enzyme usages
- UB vector of enzyme exchange reaction upper bounds
- protID string array of matching protein IDs
- fluxes vector of fluxes, copy of input fluxes
-
- Usage:
- usageData = enzymeUsage(ecModel,fluxes,zero)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function usageData = enzymeUsage(ecModel,fluxes,zero)
-0002 % enzymeUsage
-0003 % Gives enzyme usages based on a provided flux distribution, as obtained
-0004 % from a full GECKO model. It can give:
-0005 % 1) absolute usage: the specific enzyme usage in mg/gDCW, which can
-0006 % be given for enzymes with- and without concentration information;
-0007 % 2) capacity usage: the ratio of available enzyme that is used, calcuted
-0008 % by (absUsage/UB) (note that capacity usage is 0 if an enzyme
-0009 % concentration was not constrained in the model);
-0010 % 3) UB: the upper bound of each enzyme exchange reaction, which may not
-0011 % be the same as the enzyme concentration, if it has been
-0012 % flexibilized
-0013 % 4) protID: the protein identifiers for each enzyme (if the model has an
-0014 % enzymes field than this order is used, otherwise it is given
-0015 % alphabetically.
-0016 %
-0017 % Input:
-0018 % ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
-0019 % fluxes vector of fluxes, for instance sol.x
-0020 % zero logical whether also enzymes with zero absolute usage
-0021 % should be included (Optional, default true)
-0022 %
-0023 % Output:
-0024 % usageData structure with enzyme usage data
-0025 % capUsage vector of enzyme capacity usages
-0026 % absUsage vector of absolute enzyme usages
-0027 % UB vector of enzyme exchange reaction upper bounds
-0028 % protID string array of matching protein IDs
-0029 % fluxes vector of fluxes, copy of input fluxes
-0030 %
-0031 % Usage:
-0032 % usageData = enzymeUsage(ecModel,fluxes,zero)
-0033
-0034 if nargin<3
-0035 zero=true;
-0036 end
-0037 if ecModel.ec.geckoLight
-0038 error('This function does not work on GECKO light models.')
-0039 end
-0040 usageData.protID = ecModel.ec.enzymes;
-0041 [~,rxnIdx] = ismember(strcat('usage_prot_',ecModel.ec.enzymes),ecModel.rxns);
-0042
-0043 usageData.LB = ecModel.lb(rxnIdx);
-0044 usageData.absUsage = abs(fluxes(rxnIdx));
-0045 usageData.capUsage = abs(usageData.absUsage./usageData.LB);
-0046 usageData.fluxes = fluxes;
-0047
-0048 if ~zero
-0049 nonzero = usageData.absUsage<0;
-0050 usageData.absUsage = usageData.absUsage(nonzero);
-0051 usageData.capUsage = usageData.capUsage(nonzero);
-0052 usageData.LB = usageData.LB(nonzero);
-0053 usageData.protID = usageData.protID(nonzero);
-0054 end
-0055 end
findGECKOroot
- Finds the root of the GECKO directory, by searching for the path to
- GECKO.png. Can also record the current directory, in case a function will
- use the geckoPath to navigate to a precise folder, and it should return to
- the previous directory afterwards.
0001 function [geckoPath, prevDir] = findGECKOroot()
-0002 % findGECKOroot
-0003 % Finds the root of the GECKO directory, by searching for the path to
-0004 % GECKO.png. Can also record the current directory, in case a function will
-0005 % use the geckoPath to navigate to a precise folder, and it should return to
-0006 % the previous directory afterwards.
-0007
-0008 ST=dbstack('-completenames');
-0009 prevDir = pwd();
-0010 geckoPath = ST(strcmp({ST.name},'findGECKOroot')).file;
-0011 rootFound = 0;
-0012 while rootFound == 0
-0013 isRoot = exist(fullfile(geckoPath,'GECKOInstaller.m'),'file');
-0014 if isRoot == 2
-0015 rootFound = 1;
-0016 else
-0017 ravenPathOld = geckoPath;
-0018 geckoPath = fileparts(geckoPath);
-0019 if strcmp(ravenPathOld,geckoPath)
-0020 error('Cannot find the GECKO root directory. Make sure you have not removed the GECKOInstaller.m file from your GECKO installation.')
-0021 end
-0022 end
-0023 end
function smallEcModel = getSubsetEcModel(bigEcModel,smallGEM)
-
-
DESCRIPTION
-
getSubset_ecModel
- Generate a context-specific ecModel (strain/cell-line/tissue) by
- mapping reactions and genes from a conventional context-specific GEM to
- a general ecModel (bigEcModel), to yield a context-specific ecModel.
-
- Both models (bigEcModel and the smallGEM) should have derived from the
- same starting model. If an ecModel with human-GEM 1.15.0 is made
- (yielding the bigEcModel), then human-GEM 1.15.0 should also be the
- basis from which the context-specific GEM (smallGEM) was generated by
- for instance tINIT.
-
- Input:
- bigEcModel enzyme-constrained version of conventional model of
- metabolism for a given species
- smallGEM Reduced model (subset of the general model) for a
- specific strain (microbes) or cell-line/tissues (mammals)
-
- Output:
- smallEcModel Enzyme-constrained version of the context-specific model
-
- Usage: smallEcModel = getSubsetEcModel(smallGEM,bigEcModel)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function smallEcModel = getSubsetEcModel(bigEcModel,smallGEM)
-0002 % getSubset_ecModel
-0003 % Generate a context-specific ecModel (strain/cell-line/tissue) by
-0004 % mapping reactions and genes from a conventional context-specific GEM to
-0005 % a general ecModel (bigEcModel), to yield a context-specific ecModel.
-0006 %
-0007 % Both models (bigEcModel and the smallGEM) should have derived from the
-0008 % same starting model. If an ecModel with human-GEM 1.15.0 is made
-0009 % (yielding the bigEcModel), then human-GEM 1.15.0 should also be the
-0010 % basis from which the context-specific GEM (smallGEM) was generated by
-0011 % for instance tINIT.
-0012 %
-0013 % Input:
-0014 % bigEcModel enzyme-constrained version of conventional model of
-0015 % metabolism for a given species
-0016 % smallGEM Reduced model (subset of the general model) for a
-0017 % specific strain (microbes) or cell-line/tissues (mammals)
-0018 %
-0019 % Output:
-0020 % smallEcModel Enzyme-constrained version of the context-specific model
-0021 %
-0022 % Usage: smallEcModel = getSubsetEcModel(smallGEM,bigEcModel)
-0023
-0024 % Check if models were derived from the same starting GEM
-0025 Rxn_format = regexprep(bigEcModel.rxns,'_REV|_EXP_\d+','');
-0026 rxnsDiff = find(~ismember(smallGEM.rxns,Rxn_format));
-0027 if numel(rxnsDiff) > 0
-0028 dispEM(['While both models should have derived from the same starting ',...
-0029 'GEM, the following reactions from smallGEM could not be found ',...
-0030 'in bigEcModel:'],true,smallGEM.rxns(rxnsDiff),false);
-0031 end
-0032
-0033 % Check if original bigEcModel contains context-dependent protein constraints
-0034 if any(bigEcModel.lb(startsWith(bigEcModel.rxns,'usage_prot_')) ~= -1000)
-0035 printOrange(['WARNING: The bigEcModel is constraint by protein concentrations that are\n'...
-0036 'likely not relevant in the constructed smallEcModel.\n']);
-0037 end
-0038
-0039 % Remove genes (and associated reactions) that are absent in smallGEM
-0040 genesToRemove = setdiff(bigEcModel.genes,smallGEM.genes);
-0041 % If genesToRemove contains "standard", then remove this entry
-0042 if ismember('standard', genesToRemove)
-0043 genesToRemove(strcmp(genesToRemove, 'standard')) = [];
-0044 end
-0045 smallEcModel = removeGenes(bigEcModel,genesToRemove,true,true,false);
-0046
-0047 % Remove genes from ec-structure
-0048 enzToRemove = ismember(smallEcModel.ec.genes,genesToRemove);
-0049 smallEcModel.ec.genes(enzToRemove) = [];
-0050 smallEcModel.ec.enzymes(enzToRemove) = [];
-0051 smallEcModel.ec.concs(enzToRemove) = [];
-0052 smallEcModel.ec.mw(enzToRemove) = [];
-0053 smallEcModel.ec.sequence(enzToRemove) = [];
-0054 smallEcModel.ec.rxnEnzMat(:,enzToRemove) = [];
-0055
-0056 % Remove any reaction (except prot_ associated) that is absent from smallGEM
-0057 trimRxns = regexprep(smallEcModel.rxns,'_REV|_EXP_\d+','');
-0058 protRxns = startsWith(smallEcModel.rxns,{'usage_prot_','prot_pool_exchange'});
-0059 keepRxns = ismember(trimRxns,smallGEM.rxns) | protRxns;
-0060 smallEcModel = removeReactions(smallEcModel,~keepRxns, true, true, true);
-0061
-0062 % Remove reactions from ec-structure
-0063 ecRxnsToRemove = ~ismember(smallEcModel.ec.rxns,smallEcModel.rxns);
-0064
-0065 smallEcModel.ec.rxns(ecRxnsToRemove) = [];
-0066 smallEcModel.ec.kcat(ecRxnsToRemove) = [];
-0067 smallEcModel.ec.source(ecRxnsToRemove) = [];
-0068 smallEcModel.ec.notes(ecRxnsToRemove) = [];
-0069 smallEcModel.ec.eccodes(ecRxnsToRemove) = [];
-0070 smallEcModel.ec.rxnEnzMat(ecRxnsToRemove,:) = [];
-0071 end
-0072
function model = loadConventionalGEM(filename, modelAdapter)
-
-
DESCRIPTION
-
loadConventionalGEM
- Loads the conventional GEM (non-ecModel) from the location specified in
- the modelAdapter. By default, it looks in the models/ subdirectory of
- the param.path specified in the modelAdapter. When loading
- conventional GEMs from other locations, one can directly use importModel.
-
- Input:
- filename name of the model file, located in the the models/
- subfolder of param.path as specified in the
- modelAdapter (Optional, will otherwise use the
- value specified as param.convGEM in the
- modelAdapter)
- modelAdapter a loaded model adapter, from where the model folder is
- read (Optional, will otherwise use the default model adapter).
-
- Output:
- model model in RAVEN format
-
- Usage:
- model = loadConventionalGEM(filename, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function model = loadConventionalGEM(filename, modelAdapter)
-0002 % loadConventionalGEM
-0003 % Loads the conventional GEM (non-ecModel) from the location specified in
-0004 % the modelAdapter. By default, it looks in the models/ subdirectory of
-0005 % the param.path specified in the modelAdapter. When loading
-0006 % conventional GEMs from other locations, one can directly use importModel.
-0007 %
-0008 % Input:
-0009 % filename name of the model file, located in the the models/
-0010 % subfolder of param.path as specified in the
-0011 % modelAdapter (Optional, will otherwise use the
-0012 % value specified as param.convGEM in the
-0013 % modelAdapter)
-0014 % modelAdapter a loaded model adapter, from where the model folder is
-0015 % read (Optional, will otherwise use the default model adapter).
-0016 %
-0017 % Output:
-0018 % model model in RAVEN format
-0019 %
-0020 % Usage:
-0021 % model = loadConventionalGEM(filename, modelAdapter)
-0022
-0023
-0024 if nargin < 2 || isempty(modelAdapter)
-0025 modelAdapter = ModelAdapterManager.getDefault();
-0026 if isempty(modelAdapter)
-0027 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0028 end
-0029 end
-0030 params = modelAdapter.getParameters();
-0031 if nargin < 1 || isempty(filename)
-0032 filename = params.convGEM;
-0033 else
-0034 filename = fullfile(params.path,'models',[filename, '.xml'])
-0035 end
-0036
-0037 if endsWith(filename,'.xml')
-0038 model = importModel(filename);
-0039 elseif endsWith(filename,'.yml')
-0040 model = readYAMLmodel(filename);
-0041 end
function model = loadEcModel(filename, modelAdapter)
-
-
DESCRIPTION
-
loadEcModel
- Loads the ecModel that matches the modelAdapter. By default, it loads
- the models/ecModel.yml in the directory specified as param.path in
- the modelAdapter. Alternative files in the same folder can be loaded by
- providing the appropriate filename. If loading models from other
- locations, one can directly use readYAMLmodel.
-
- Input:
- filename name of the ecModel file (Optional, default 'ecModel.yml').
- Fill should be located in the models/ subfolder of
- param.path as specified in the modelAdapter.
- modelAdapter a loaded model adapter, from where the model folder is
- read (Optional, will otherwise use the default model adapter).
-
- Output:
- model an ecModel in GECKO 3 format (with ecModel.ec structure)
-
- Usage:
- model = loadEcModel(filename, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function model = loadEcModel(filename, modelAdapter)
-0002 % loadEcModel
-0003 % Loads the ecModel that matches the modelAdapter. By default, it loads
-0004 % the models/ecModel.yml in the directory specified as param.path in
-0005 % the modelAdapter. Alternative files in the same folder can be loaded by
-0006 % providing the appropriate filename. If loading models from other
-0007 % locations, one can directly use readYAMLmodel.
-0008 %
-0009 % Input:
-0010 % filename name of the ecModel file (Optional, default 'ecModel.yml').
-0011 % Fill should be located in the models/ subfolder of
-0012 % param.path as specified in the modelAdapter.
-0013 % modelAdapter a loaded model adapter, from where the model folder is
-0014 % read (Optional, will otherwise use the default model adapter).
-0015 %
-0016 % Output:
-0017 % model an ecModel in GECKO 3 format (with ecModel.ec structure)
-0018 %
-0019 % Usage:
-0020 % model = loadEcModel(filename, modelAdapter)
-0021
-0022 if nargin < 2 || isempty(modelAdapter)
-0023 modelAdapter = ModelAdapterManager.getDefault();
-0024 if isempty(modelAdapter)
-0025 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0026 end
-0027 end
-0028 params = modelAdapter.getParameters();
-0029 if nargin < 1 || isempty(filename)
-0030 filename = 'ecModel.yml';
-0031 else
-0032 end
-0033
-0034 if ~endsWith(filename,{'yml','yaml'})
-0035 error(['ecModels should preferably be distributed in YAML file format, ', ...
-0036 'as otherwise model content will be lost. If the model is in ', ...
-0037 'SBML format, you can use the usual importModel function to load ', ...
-0038 'this as any other genome-scale model.'])
-0039 end
-0040
-0041 filename = fullfile(params.path,'models',filename);
-0042 if endsWith(filename,{'yml','yaml'})
-0043 model = readYAMLmodel(filename);
-0044 elseif endsWith(filename,{'xml','sbml'})
-0045 model = importModel(filename);
-0046 end
-0047 end
function fluxData = loadFluxData(fluxDataFile, modelAdapter)
-
-
DESCRIPTION
-
loadFluxData
- Function that loads total protein measurements and flux data (exchange
- fluxes for carbon source, O2, CO2, etc.)
-
- Input:
- fluxDataFile path to file with flux data. (Optional, default
- reads data/fluxData.tsv as specified in modelAdapter)
- modelAdapter a loaded model adapter (Optional, will otherwise use
- the default model adapter)
-
- Output:
- fluxData structure with flux data
- conds sampling condition
- Ptot total protein (g/gDCW)
- grRate growth rate (1/h)
- exchFluxes exchange fluxes (mmol/gDCWh)
- exchMets exchanged metabolites, matching exchFluxes
- exchRxnIDs exchange reaction IDs, matching exchMets
- bayesianRMSEweight if column existed in fluxData.tsv, weights for
- RMSE calculation in Bayesian kcat tuning
- source if column existed in fluxData.tsv, description
- where the data comes from
-
- Usage:
- fluxData = loadFluxData(fluxDataFile, modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function fluxData = loadFluxData(fluxDataFile, modelAdapter)
-0002 % loadFluxData
-0003 % Function that loads total protein measurements and flux data (exchange
-0004 % fluxes for carbon source, O2, CO2, etc.)
-0005 %
-0006 % Input:
-0007 % fluxDataFile path to file with flux data. (Optional, default
-0008 % reads data/fluxData.tsv as specified in modelAdapter)
-0009 % modelAdapter a loaded model adapter (Optional, will otherwise use
-0010 % the default model adapter)
-0011 %
-0012 % Output:
-0013 % fluxData structure with flux data
-0014 % conds sampling condition
-0015 % Ptot total protein (g/gDCW)
-0016 % grRate growth rate (1/h)
-0017 % exchFluxes exchange fluxes (mmol/gDCWh)
-0018 % exchMets exchanged metabolites, matching exchFluxes
-0019 % exchRxnIDs exchange reaction IDs, matching exchMets
-0020 % bayesianRMSEweight if column existed in fluxData.tsv, weights for
-0021 % RMSE calculation in Bayesian kcat tuning
-0022 % source if column existed in fluxData.tsv, description
-0023 % where the data comes from
-0024 %
-0025 % Usage:
-0026 % fluxData = loadFluxData(fluxDataFile, modelAdapter)
-0027
-0028 if nargin < 2 || isempty(modelAdapter)
-0029 modelAdapter = ModelAdapterManager.getDefault();
-0030 if isempty(modelAdapter)
-0031 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0032 end
-0033 end
-0034 params = modelAdapter.getParameters();
-0035
-0036 if nargin < 1 || isempty(fluxDataFile)
-0037 fluxDataFile = fullfile(params.path,'data','fluxData.tsv');
-0038 end
-0039
-0040 %Load total protein content and flux data
-0041 fID = fopen(fluxDataFile);
-0042 formatStr = '%s';
-0043 data = textscan(fID,formatStr,'Delimiter','\n');
-0044 fclose(fID);
-0045 fluxDataRaw = [];
-0046 for i=1:length(data{1})
-0047 row = data{1}(i);
-0048 row = strsplit(row{1},'\t');
-0049 row = row(1:end);
-0050 fluxDataRaw = [fluxDataRaw; row];
-0051 end
-0052 if size(fluxDataRaw,2) == 0
-0053 error('The input file %s is likely not tab-delimited.', fluxDataFile)
-0054 end
-0055 fluxData = [];
-0056
-0057 %Find additional fields
-0058 extraFieldNames = {'bayesianRMSEweight','source'};
-0059 extraFieldNumeric = [true,false];
-0060 [~, extraFields] = ismember(extraFieldNames,fluxDataRaw(1,:));
-0061 for i=1:numel(extraFields)
-0062 if extraFields(1) ~= 0
-0063 if extraFieldNumeric(i)
-0064 fluxData.(extraFieldNames{i}) = str2double(fluxDataRaw(2:end,extraFields(i)));
-0065 else
-0066 fluxData.(extraFieldNames{i}) = fluxDataRaw(2:end,extraFields(i));
-0067 end
-0068 end
-0069 end
-0070 fluxDataRaw(:,extraFields) = [];
-0071
-0072 %Extract observed byProduct names from file
-0073 exchRxns = fluxDataRaw(1,4:end);
-0074 exchMets = strtrim(regexprep(exchRxns,'(.*)\(.*\)$','$1'));
-0075 exchRxns = regexprep(exchRxns,'.*\((.*)\)$','$1');
-0076
-0077 fluxData.conds = fluxDataRaw(2:end,1);
-0078 fluxData.Ptot = str2double(fluxDataRaw(2:end,2));
-0079 fluxData.grRate = str2double(fluxDataRaw(2:end,3));
-0080 fluxData.exchFluxes = str2double(fluxDataRaw(2:end,4:end));
-0081 %fluxData.exchFluxes(isnan(fluxData.exchFluxes)) = 0;
-0082 fluxData.exchMets = exchMets;
-0083 fluxData.exchRxnIDs = exchRxns;
-0084 end
loadProtData
- Function that loads absolute proteomics data (in mg/gDCW) and returns
- mean values across replicates for each condition in the data file. By
- default it also filters the data by various criteria, to remove
- uncertain data (see input parameters).
-
- Input:
- replPerCond vector with number of replicates for each condition in
- the dataset. Example: [3, 2] if first conditions has
- triplicates and second condition has duplicates.
- protDataFile path to file with proteomics data, where protein levels
- are in mg/gDCW (Optional, default reads
- data/proteomics.tsv as specified in modelAdapter)
- Alternatively, protDataFile can be a protData structure
- that was previously made by loadProtdata.
- filterData logical whether abundances should be filtered. If
- false, minVal, maxRSD, maxMissing and addStdevs are not
- considered. (Optional, default true)
- modelAdapter a loaded model adapter (Optional, will otherwise use
- the default model adapter)
- minVal threshold of mean protein measurement per condition.
- (Optional, default = 0)
- maxRSD maximum relative standard per condition. (Optional,
- default = 1)
- maxMissing ratio of replicates for which a protein level might be
- missing. (Optional, default = 1/3 (or 1/2 if number of
- replicates = 2))
- If conditions have different number of replicates (as
- indicated in replPerCond), maxMissing can also be a
- vector of the same length as replPerCond, with
- individual maxMissing parameters for each replicate.
- cutLowest percentage of lowest mean values per condition to be
- discared (not considering NaN values). (Optional, default 5)
- addStdevs how many standard deviations should be added to the mean
- value of each protein measurement across replicates,
- broadening the confidence interval. (Optional,
- default = 1)
-
- Output:
- protData structure with (filtered) proteome data
- uniprotIDs cell arrray with Uniprot IDs matching
- protData.abundances
- abundances matrix of proteomics data, where each
- column contains mean abundances per
- condition
-
- Usage:
- protData = loadProtData(replPerCond, protDataFile, filterData, modelAdapter, minVal, maxRSD, maxMissing, cutLowest, addStdevs)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function protData = loadProtData(replPerCond, protDataFile, filterData, modelAdapter, minVal, maxRSD, maxMissing, cutLowest, addStdevs)
-0002 % loadProtData
-0003 % Function that loads absolute proteomics data (in mg/gDCW) and returns
-0004 % mean values across replicates for each condition in the data file. By
-0005 % default it also filters the data by various criteria, to remove
-0006 % uncertain data (see input parameters).
-0007 %
-0008 % Input:
-0009 % replPerCond vector with number of replicates for each condition in
-0010 % the dataset. Example: [3, 2] if first conditions has
-0011 % triplicates and second condition has duplicates.
-0012 % protDataFile path to file with proteomics data, where protein levels
-0013 % are in mg/gDCW (Optional, default reads
-0014 % data/proteomics.tsv as specified in modelAdapter)
-0015 % Alternatively, protDataFile can be a protData structure
-0016 % that was previously made by loadProtdata.
-0017 % filterData logical whether abundances should be filtered. If
-0018 % false, minVal, maxRSD, maxMissing and addStdevs are not
-0019 % considered. (Optional, default true)
-0020 % modelAdapter a loaded model adapter (Optional, will otherwise use
-0021 % the default model adapter)
-0022 % minVal threshold of mean protein measurement per condition.
-0023 % (Optional, default = 0)
-0024 % maxRSD maximum relative standard per condition. (Optional,
-0025 % default = 1)
-0026 % maxMissing ratio of replicates for which a protein level might be
-0027 % missing. (Optional, default = 1/3 (or 1/2 if number of
-0028 % replicates = 2))
-0029 % If conditions have different number of replicates (as
-0030 % indicated in replPerCond), maxMissing can also be a
-0031 % vector of the same length as replPerCond, with
-0032 % individual maxMissing parameters for each replicate.
-0033 % cutLowest percentage of lowest mean values per condition to be
-0034 % discared (not considering NaN values). (Optional, default 5)
-0035 % addStdevs how many standard deviations should be added to the mean
-0036 % value of each protein measurement across replicates,
-0037 % broadening the confidence interval. (Optional,
-0038 % default = 1)
-0039 %
-0040 % Output:
-0041 % protData structure with (filtered) proteome data
-0042 % uniprotIDs cell arrray with Uniprot IDs matching
-0043 % protData.abundances
-0044 % abundances matrix of proteomics data, where each
-0045 % column contains mean abundances per
-0046 % condition
-0047 %
-0048 % Usage:
-0049 % protData = loadProtData(replPerCond, protDataFile, filterData, modelAdapter, minVal, maxRSD, maxMissing, cutLowest, addStdevs)
-0050
-0051 if nargin < 8 || isempty(addStdevs)
-0052 addStdevs = 1;
-0053 end
-0054
-0055 if nargin < 7 || isempty(maxMissing)
-0056 maxMissing = 2/3;
-0057 end
-0058
-0059 if nargin < 6 || isempty(maxRSD)
-0060 maxRSD = 1;
-0061 end
-0062
-0063 if nargin < 5 || isempty(minVal)
-0064 minVal = 0;
-0065 end
-0066
-0067 if nargin < 4 || isempty(modelAdapter)
-0068 modelAdapter = ModelAdapterManager.getDefault();
-0069 if isempty(modelAdapter)
-0070 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0071 end
-0072 end
-0073 params = modelAdapter.getParameters();
-0074
-0075 if nargin < 3 || isempty(filterData)
-0076 filterData = true;
-0077 end
-0078
-0079 if nargin < 2 || isempty(protDataFile)
-0080 protDataFile = fullfile(params.path,'data','proteomics.tsv');
-0081 end
-0082
-0083 format = '%s';
-0084 for i=1:sum(replPerCond)
-0085 format = [format ' %f'];
-0086 end
-0087 if ~isstruct(protDataFile)
-0088 checkFileExistence(protDataFile);
-0089 fID = fopen(protDataFile);
-0090 protDataRaw = textscan(fID,format,'Delimiter','\t','HeaderLines',1,'TreatAsEmpty',{'NA','na','NaN','#VALUE!'});
-0091 uniprotIDs = protDataRaw{1};
-0092 abundances = cell2mat(protDataRaw(2:end));
-0093 fclose(fID);
-0094 else
-0095 uniprotIDs = protDataFile.uniprotIDs;
-0096 abundances = protDataFile.abundances;
-0097 end
-0098
-0099 %Remove entriew without ID
-0100 remData = cellfun(@isempty,uniprotIDs);
-0101 uniprotIDs(remData,:) = [];
-0102 abundances(remData,:) = [];
-0103 m = size(abundances,1);
-0104 filtAbund = nan(m,numel(replPerCond));
-0105
-0106 if filterData
-0107 for i=1:numel(replPerCond)
-0108 condAbund = abundances(:,1:replPerCond(i));
-0109 if i<numel(replPerCond)
-0110 abundances = abundances(:,replPerCond(i)+1:end);
-0111 end
-0112 % First filter maxMissing
-0113 if size(condAbund,2) > 1
-0114 if numel(maxMissing)>1
-0115 maxMisRepl = maxMissing(i);
-0116 else
-0117 maxMisRepl = maxMissing;
-0118 end
-0119 remData = sum(condAbund>0,2)<maxMisRepl*size(condAbund,2);
-0120 condAbund(remData,:) = nan;
-0121 end
-0122 % Filter by RSD
-0123 remData = (std(condAbund,0,2,'omitnan')./mean(condAbund,2,'omitnan'))>maxRSD;
-0124 condAbund(remData) = nan;
-0125 % Add stdevs
-0126 condAbund = mean(condAbund,2,'omitnan')+(addStdevs*std(condAbund,0,2,'omitnan'));
-0127 % Filter by minVal
-0128 remData = mean(condAbund,2,'omitnan')<minVal;
-0129 condAbund(remData) = nan;
-0130 % Remove bottom 5%
-0131 numData = find(~isnan(condAbund));
-0132 [~,sortData] = sort(condAbund);
-0133 lowCutoff = floor(numel(numData)*0.05);
-0134 condAbund(sortData(1:lowCutoff)) = nan;
-0135 % Combine conditions
-0136 filtAbund(:,i) = condAbund;
-0137 end
-0138 else
-0139 for i=1:numel(replPerCond)
-0140 condAbund = abundances(:,1:replPerCond(i));
-0141 if i<numel(replPerCond)
-0142 abundances = abundances(:,replPerCond(i)+1:end);
-0143 end
-0144 filtAbund(:,i) = mean(condAbund,2,'omitnan');
-0145 end
-0146 end
-0147 notAllNan = logical(sum(~isnan(filtAbund),2));
-0148 protData.abundances = filtAbund(notAllNan,:);
-0149 protData.uniprotIDs = uniprotIDs(notAllNan);
-0150 end
-0151
function [mappedFlux, enzUsageFlux, usageEnz] = mapRxnsToConv(ecModel, model, fluxVect)
-
-
DESCRIPTION
-
mapRxnsToConv
- A vector (or matrix) of fluxes is mapped to the reactions in the
- conventional starting model that was used to construct the ecModel. It
- is essential that the provided conventional model is indeed the model
- that was used to initate ecModel reconstruction.
-
- Input:
- ecModel an ecModel in GECKO 3 format (with ecModel.ec structure),
- that was used to obtain fluxVect
- model the starting model for ecModel, to which the reactions
- should be mapped
- fluxVect vector or matrix of flux values, matching ecModel.rxns
-
- Output:
- mappedFlux vector or matrix of flux values, matching model.rxns
- enzUsageFlux vector or matrix of flux values from enzyme usage
- reactions, as these are absent from mappedFlux
- usageEnz cell array with protein IDs, matching enzUsageFlux
-
- Usage:
- [mappedFlux, enzUsageFlux, usageEnz] = mapRxnsToConv(ecModel, model, fluxVect)
0001 function [mappedFlux, enzUsageFlux, usageEnz] = mapRxnsToConv(ecModel, model, fluxVect)
-0002 % mapRxnsToConv
-0003 % A vector (or matrix) of fluxes is mapped to the reactions in the
-0004 % conventional starting model that was used to construct the ecModel. It
-0005 % is essential that the provided conventional model is indeed the model
-0006 % that was used to initate ecModel reconstruction.
-0007 %
-0008 % Input:
-0009 % ecModel an ecModel in GECKO 3 format (with ecModel.ec structure),
-0010 % that was used to obtain fluxVect
-0011 % model the starting model for ecModel, to which the reactions
-0012 % should be mapped
-0013 % fluxVect vector or matrix of flux values, matching ecModel.rxns
-0014 %
-0015 % Output:
-0016 % mappedFlux vector or matrix of flux values, matching model.rxns
-0017 % enzUsageFlux vector or matrix of flux values from enzyme usage
-0018 % reactions, as these are absent from mappedFlux
-0019 % usageEnz cell array with protein IDs, matching enzUsageFlux
-0020 %
-0021 % Usage:
-0022 % [mappedFlux, enzUsageFlux, usageEnz] = mapRxnsToConv(ecModel, model, fluxVect)
-0023
-0024 if isempty(fluxVect)
-0025 error('No or empty flux vector provided')
-0026 end
-0027
-0028 fluxes = fluxVect;
-0029 rxnIDs = ecModel.rxns;
-0030
-0031 % Invert flux of _REV reactions
-0032 revRxns = endsWith(rxnIDs,'_REV') | contains(rxnIDs,'_REV_EXP_');
-0033 fluxes(revRxns,:) = -fluxes(revRxns,:);
-0034 rxnIDs(revRxns) = replace(rxnIDs(revRxns),'_REV','');
-0035 % Remove _EXP_. suffixes
-0036 rxnIDs = regexprep(rxnIDs,'_EXP_\d+','');
-0037
-0038 % Map and sum fluxes to converted reaction IDs
-0039 [rxnIDmap, convRxnID] = findgroups(rxnIDs);
-0040 newVect = splitapply(@(x){sum(x,1)}, fluxes, rxnIDmap);
-0041 newVect = cell2mat(newVect);
-0042
-0043 % Place in same order as in original model
-0044 [mapCheck,origIdx] = ismember(model.rxns,convRxnID);
-0045 if ~all(mapCheck)
-0046 error('Not all reactions from model.rxns can be found in the ecModel. Are you sure that ecModel is derived from model?')
-0047 end
-0048 mappedFlux=newVect(origIdx,:);
-0049
-0050 % Separately report enzyme usages
-0051 usageEnz = startsWith(ecModel.rxns,{'usage_prot_','prot_pool_exchange'});
-0052 enzUsageFlux = fluxVect(usageEnz,:);
-0053 usageEnz = regexprep(ecModel.rxns(usageEnz),'usage_prot_','');
-0054 usageEnz = regexprep(usageEnz,'prot_pool_exchange','pool');
-0055 end
plotEcFVA
- Plots cumulative distribution functions of ecFVA results from one or
- model ecModel(s) and/or GEM(s).
-
- Input:
- minFlux vector of minimum flux values, coming from ecFVA. If
- multiple ecModels/GEMs are to be visualized, then each
- column represents a separate model.
- maxFlux vector of maximum flux values, matching minFlux.
function usageReport = reportEnzymeUsage(ecModel, usageData, highCapUsage, topAbsUsage)
-
-
DESCRIPTION
-
reportEnzymeUsage
- Summarizes the results from enzymeUsage.
-
- Input:
- ecModel a GECKO3 ecModel
- usageData output from enzymeUsage
- highCapUsage minimum ratio of enzyme capacity usage to be considered
- as high usage (Optional, default 0.9, refering to a
- minimum of 90% capacity usage)
- topAbsUsage number of top enzymes with highest absolute usage
- (Optional, default 10, returning the top 10 enzymes
- with highest absolute usage. With Inf or 0, all enzymes
- are returned)
-
- Output:
- usageReport table with summary information
-
- Usage:
- usageReport = reportEnzymeUsage(ecModel, usageData, highCapUsage)
function saveEcModel(ecModel,filename,modelAdapter)
-
-
DESCRIPTION
-
saveECmodel
- Saves the ecModel in either YAML format (= default and preferred, as
- all ecModel content is reserved) and/or SBML format (= more widely
- compatible with other constraint-based modelling tools, can be used for
- running simulations like FBA etc., but this model cannot be loaded back
- into MATLAB for applying further GECKO functions, as model.ec is lost).
-
- The ecModel is saved to the ecModel-specific models/ subfolder. For
- saving to other locations, writeYAMLmodel or exportModel are more
- suitable.
-
- Input:
- ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
- filename ending with either .yml or .xml, specifying if the
- ecModel should be saved in YAML or SBML file format. If
- no file extension is given, the ecModel will be saved
- in YAML format. If no filename is given, 'ecModel.yml'
- is used.
- modelAdapter a loaded model adapter, from where the model folder is
- read (Optional, will otherwise use the default model adapter).
-
- Usage:
- saveECmodel(ecModel,filename,modelAdapter)
-
-
-
CROSS-REFERENCE INFORMATION
-This function calls:
-
-
-This function is called by:
-
-
-
-
-
-
-
SOURCE CODE
-
0001 function saveEcModel(ecModel,filename,modelAdapter)
-0002 % saveECmodel
-0003 % Saves the ecModel in either YAML format (= default and preferred, as
-0004 % all ecModel content is reserved) and/or SBML format (= more widely
-0005 % compatible with other constraint-based modelling tools, can be used for
-0006 % running simulations like FBA etc., but this model cannot be loaded back
-0007 % into MATLAB for applying further GECKO functions, as model.ec is lost).
-0008 %
-0009 % The ecModel is saved to the ecModel-specific models/ subfolder. For
-0010 % saving to other locations, writeYAMLmodel or exportModel are more
-0011 % suitable.
-0012 %
-0013 % Input:
-0014 % ecModel an ecModel in GECKO 3 format (with ecModel.ec structure)
-0015 % filename ending with either .yml or .xml, specifying if the
-0016 % ecModel should be saved in YAML or SBML file format. If
-0017 % no file extension is given, the ecModel will be saved
-0018 % in YAML format. If no filename is given, 'ecModel.yml'
-0019 % is used.
-0020 % modelAdapter a loaded model adapter, from where the model folder is
-0021 % read (Optional, will otherwise use the default model adapter).
-0022 %
-0023 % Usage:
-0024 % saveECmodel(ecModel,filename,modelAdapter)
-0025
-0026
-0027 if nargin < 3 || isempty(modelAdapter)
-0028 modelAdapter = ModelAdapterManager.getDefault();
-0029 if isempty(modelAdapter)
-0030 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
-0031 end
-0032 end
-0033 params = modelAdapter.getParameters();
-0034 if nargin < 2 || isempty(filename)
-0035 filename = 'ecModel';
-0036 end
-0037 filename = fullfile(params.path,'models',filename);
-0038
-0039 ecModel.description = ['Enzyme-constrained model of ' ecModel.id];
-0040
-0041 switch filename(end-3:end)
-0042 case {'.xml','sbml'}
-0043 exportModel(ecModel, filename);
-0044 %Convert notation "e-005" to "e-05 " in stoich. coeffs. to avoid
-0045 %inconsistencies between Windows and MAC:
-0046 copyfile(filename,'backup.xml')
-0047 fin = fopen('backup.xml', 'r');
-0048 fout = fopen(filename, 'w');
-0049 still_reading = true;
-0050 while still_reading
-0051 inline = fgets(fin);
-0052 if ~ischar(inline)
-0053 still_reading = false;
-0054 else
-0055 if ~isempty(regexp(inline,'-00[0-9]','once'))
-0056 inline = strrep(inline,'-00','-0');
-0057 elseif ~isempty(regexp(inline,'-01[0-9]','once'))
-0058 inline = strrep(inline,'-01','-1');
-0059 end
-0060 fwrite(fout, inline);
-0061 end
-0062 end
-0063 fclose('all');
-0064 delete('backup.xml');
-0065 otherwise% Assume YAML
-0066 writeYAMLmodel(ecModel,filename);
-0067 end
-0068 end
startGECKOproject
- Function that create a basic project structure for GECKO. If a project
- with the same name exits, the project will not be created
-
- Input:
- name an name for the folder struture used in GECKO. Also
- creates a basic adapter class, which must be manually
- adjusted. If not defined, a dialog box will appear.
- path a path where to create the folder. If not defined, a
- dialog box will appear.
-
- Usage:
- startGECKOproject(name, path)
0001 function startGECKOproject(name, path)
-0002 % startGECKOproject
-0003 % Function that create a basic project structure for GECKO. If a project
-0004 % with the same name exits, the project will not be created
-0005 %
-0006 % Input:
-0007 % name an name for the folder struture used in GECKO. Also
-0008 % creates a basic adapter class, which must be manually
-0009 % adjusted. If not defined, a dialog box will appear.
-0010 % path a path where to create the folder. If not defined, a
-0011 % dialog box will appear.
-0012 %
-0013 % Usage:
-0014 % startGECKOproject(name, path)
-0015
-0016 if nargin < 1 || isempty(name)
-0017 prompt = {'Please provide a project name (e.g. ecYeastGEM)'};
-0018 dlgtitle = 'Project name';
-0019 dims = [1 100];
-0020 definput = {'ecModelGEM'};
-0021 opts.Interpreter = 'tex';
-0022 name = inputdlg(prompt,dlgtitle,dims,definput,opts);
-0023 name = char(name);
-0024 end
-0025
-0026 if nargin < 2 || isempty(path)
-0027 path = uigetdir('Project Folder path');
-0028 end
-0029
-0030 fullPath = fullfile(path, name);
-0031 % Validate if the project does not exits
-0032 if ~exist(fullPath, 'dir')
-0033 % Create the subfolder
-0034 dir = {'code', 'data', 'models', 'output'};
-0035 for i = 1:length(dir)
-0036 status = mkdir(fullPath, dir{i});
-0037 fid = fopen(fullfile(fullPath, dir{i}, '.keep'), 'w');
-0038 fclose(fid);
-0039 end
-0040
-0041 % Read the template adapter class
-0042 fid = fopen(fullfile(findGECKOroot, 'src', 'geckomat', ...
-0043 'model_adapter', 'adapterTemplate.m'));
-0044 f = fread(fid, '*char')';
-0045 fclose(fid);
-0046
-0047 % Replace key values
-0048 f = strrep(f, 'KEY_CLASSNAME', [name 'Adapter']);
-0049 f = strrep(f, 'KEY_PATH', path);
-0050 f = strrep(f, 'KEY_NAME', name);
-0051
-0052 % Save the class file
-0053 filename = fullfile(path, name, [name 'Adapter.m']);
-0054 fid = fopen(filename, 'w');
-0055 fwrite(fid, f);
-0056 fclose(fid);
-0057 else
-0058 printOrange('WARNING: A project with the same name exits at the same location. The project was not created.\n');
-0059 end
-0060 cd(fullPath)
-0061 end
updateGeckoDoc
- Updates HTML documentation files for all geckomat GECKO functions. Uses the
- m2html functions that are provided with RAVEN.
-
- Usage: updateGECKOdoc()
0001 function updateGECKOdoc()
-0002 % updateGeckoDoc
-0003 % Updates HTML documentation files for all geckomat GECKO functions. Uses the
-0004 % m2html functions that are provided with RAVEN.
-0005 %
-0006 % Usage: updateGECKOdoc()
-0007
-0008 %Make sure that RAVEN-provided m2html is used
-0009 ravenDir=findRAVENroot();
-0010 path(fullfile(ravenDir,'software','m2html'),path);
-0011
-0012 %Get the GECKO path
-0013 geckoDir=findGECKOroot();
-0014
-0015 %Remove old doc directory
-0016 rmdir(fullfile(geckoDir,'doc'),'s');
-0017
-0018 %Get a non-redundant list of GECKO subdirectories containing MATLAB
-0019 %functions. Absolute paths are not compatible with M2HTML, so convert them
-0020 %to the relative paths instead.
-0021 geckoDirs=dir(fullfile(geckoDir,'src','geckomat','**/*.m'));
-0022 geckoDirs=unique({geckoDirs.folder})';
-0023
-0024 %Make relative path
-0025 relStart = numel(geckoDir)+2;
-0026 for i=1:numel(geckoDirs)
-0027 geckoDirs{i,1} = geckoDirs{i,1}(relStart:end);
-0028 end
-0029
-0030 %Save the current working directory and go to RAVEN root directory
-0031 originalDir=pwd;
-0032 cd(geckoDir);
-0033 %Generate HTML documentation files for RAVEN MATLAB functions
-0034 m2html('mFiles',geckoDirs,'htmldir','doc');
-0035 %Go back to the original working directory
-0036 cd(originalDir);
-0037 end
applyECcodes Copy EC numbers from ecModel.ec.eccodes to ecModel.eccodes.
+
+
SYNOPSIS
+
function ecModel = applyECcodes(ecModel, varargin)
+
+
DESCRIPTION
+
applyECcodes Copy EC numbers from ecModel.ec.eccodes to ecModel.eccodes.
+
+ Copies the EC numbers stored in the ecModel.ec structure across to the
+ top-level ecModel.eccodes field.
+
+ Parameters
+ ----------
+ ecModel : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ overwrite : logical
+ whether existing (non-empty) ecModel.eccodes entries should be
+ overwritten by a non-empty ecModel.ec.eccodes entry. Empty
+ ecModel.eccodes entries are always overwritten if possible (default
+ false). An empty ecModel.ec.eccodes source entry is never copied, so
+ it can never erase an existing annotation.
+
+ Returns
+ -------
+ ecModel : struct
+ ecModel with populated model.eccodes.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ ecModel = applyECcodes(ecModel, true);
+ ecModel = applyECcodes(ecModel, 'overwrite', true);
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function ecModel = applyECcodes(ecModel, varargin)
+0002 % applyECcodes Copy EC numbers from ecModel.ec.eccodes to ecModel.eccodes.
+0003 %
+0004 % Copies the EC numbers stored in the ecModel.ec structure across to the
+0005 % top-level ecModel.eccodes field.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % ecModel : struct
+0010 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0011 %
+0012 % Name-Value Arguments
+0013 % --------------------
+0014 % overwrite : logical
+0015 % whether existing (non-empty) ecModel.eccodes entries should be
+0016 % overwritten by a non-empty ecModel.ec.eccodes entry. Empty
+0017 % ecModel.eccodes entries are always overwritten if possible (default
+0018 % false). An empty ecModel.ec.eccodes source entry is never copied, so
+0019 % it can never erase an existing annotation.
+0020 %
+0021 % Returns
+0022 % -------
+0023 % ecModel : struct
+0024 % ecModel with populated model.eccodes.
+0025 %
+0026 % Examples
+0027 % --------
+0028 % % optional arguments may be given positionally or as name-value pairs:
+0029 % ecModel = applyECcodes(ecModel, true);
+0030 % ecModel = applyECcodes(ecModel, 'overwrite', true);
+0031
+0032 p = parseGECKOargs(varargin, { ...
+0033 'overwrite', false});
+0034 overwrite = p.overwrite;
+0035
+0036 if ~isfield(ecModel.ec,'eccodes')
+0037 error('ecModel.ec.eccodes does not exist')
+0038 end
+0039
+0040 [a,b] = ismember(ecModel.rxns,ecModel.ec.rxns);
+0041 if ~isfield(ecModel,'eccodes')
+0042 ecModel.eccodes = cell(numel(ecModel.rxns),1);
+0043 end
+0044
+0045 if overwrite
+0046 % Never overwrite an existing eccodes entry with an empty source: an
+0047 % unset ec.eccodes value means "no info to copy", not "clear the
+0048 % annotation".
+0049 nonEmptySource = false(size(a));
+0050 nonEmptySource(a) = ~cellfun(@isempty, ecModel.ec.eccodes(b(a)));
+0051 a = a & nonEmptySource;
+0052 ecModel.eccodes(a) = ecModel.ec.eccodes(b(a));
+0053 else% Only replace emptyEcCodes
+0054 emptyEcCodes = cellfun(@isempty, ecModel.eccodes);
+0055 a = a & emptyEcCodes;
+0056 ecModel.eccodes(a) = ecModel.ec.eccodes(b(a));
+0057 end
+0058 end
calculateMW Calculate the molecular weight of a protein.
+
+
SYNOPSIS
+
function MW = calculateMW(sequence)
+
+
DESCRIPTION
+
calculateMW Calculate the molecular weight of a protein.
+
+ Calculates the molecular weight of a protein from its amino acid sequence.
+ Matching is case-insensitive. Sequences with no recognized residue
+ (including an empty sequence) return NaN, rather than being mistaken for
+ an 18 Da protein.
+
+ Parameters
+ ----------
+ sequence : char
+ the amino acid sequence (can include extra things such as spaces or
+ numbers, which are ignored).
+
+ Returns
+ -------
+ MW : double
+ the molecular weight in Da, or NaN if no residue was recognized.
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function MW = calculateMW(sequence)
+0002 % calculateMW Calculate the molecular weight of a protein.
+0003 %
+0004 % Calculates the molecular weight of a protein from its amino acid sequence.
+0005 % Matching is case-insensitive. Sequences with no recognized residue
+0006 % (including an empty sequence) return NaN, rather than being mistaken for
+0007 % an 18 Da protein.
+0008 %
+0009 % Parameters
+0010 % ----------
+0011 % sequence : char
+0012 % the amino acid sequence (can include extra things such as spaces or
+0013 % numbers, which are ignored).
+0014 %
+0015 % Returns
+0016 % -------
+0017 % MW : double
+0018 % the molecular weight in Da, or NaN if no residue was recognized.
+0019
+0020 % A Alanine
+0021 % B Aspartic acid or Asparagine
+0022 % C Cysteine
+0023 % D Aspartic acid
+0024 % E Glutamic acid
+0025 % F Phenylalanine
+0026 % G Glycine
+0027 % H Histidine
+0028 % I Isoleucine
+0029 % J Leucine or Isoleucine
+0030 % K Lysine
+0031 % L Leucine
+0032 % M Methionine
+0033 % N Asparagine
+0034 % O Pyrrolysine
+0035 % P Proline
+0036 % Q Glutamine
+0037 % R Arginine
+0038 % S Serine
+0039 % T Threonine
+0040 % U Selenocysteine
+0041 % V Valine
+0042 % W Tryptophan
+0043 % X any
+0044 % Y Tyrosine
+0045 % Z Glutamic acid or Glutamine
+0046
+0047 % The 20 standard residues, individually sourced.
+0048 aa_codes = {'A','C','D','E','F','G','H','I','K','L','M','N','P','Q','R', ...
+0049 'S','T','V','W','Y'};
+0050 aa_MWs = [71.08 103.14 115.09 129.11 147.17 57.05 137.14 113.16 128.17 ...
+0051 113.16 131.20 114.10 97.12 128.13 156.19 87.08 101.10 99.13 ...
+0052 186.21 163.17];
+0053
+0054 % Ambiguous / non-standard codes, derived from the standard table above
+0055 % rather than pre-rounded constants: B/Z average their two possible
+0056 % residues, X averages across all 20 standard residues, J is Leu-or-Ile
+0057 % (both 113.16, so either index gives the same value), O/U have no
+0058 % standard-residue equivalent and keep their literature values.
+0059 bMW = mean(aa_MWs(ismember(aa_codes,{'D','N'})));
+0060 zMW = mean(aa_MWs(ismember(aa_codes,{'E','Q'})));
+0061 xMW = mean(aa_MWs);
+0062 jMW = aa_MWs(strcmp(aa_codes,'L'));
+0063
+0064 allCodes = [aa_codes, {'B','J','O','U','X','Z'}];
+0065 allMWs = [aa_MWs, bMW, jMW, 255.31, 150.04, xMW, zMW];
+0066
+0067 % Average mass of water (g/mol): the two H atoms and one OH that remain
+0068 % after peptide bond condensation.
+0069 waterMW = 18.01528;
+0070
+0071 sequence = upper(sequence);
+0072 MW = waterMW;
+0073 nMatched = 0;
+0074 for i = 1:length(allCodes)
+0075 count = length(strfind(sequence,allCodes{i}));
+0076 MW = MW + count*allMWs(i);
+0077 nMatched = nMatched + count;
+0078 end
+0079 if nMatched == 0
+0080 MW = NaN;
+0081 end
+0082
+0083 end
+0084
+0085 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
findECInDB Look up EC numbers for a reaction's gene set in a UniProt or KEGG index.
+
+
SYNOPSIS
+
function [EC,conflicts] = findECInDB(gene_set, DBecNum, DBMW, geneIndex, geneHashMap)
+
+
DESCRIPTION
+
findECInDB Look up EC numbers for a reaction's gene set in a UniProt or KEGG index.
+
+ For each gene, finds its matching database entries and takes the EC number
+ of the lightest-weight matching protein when a single EC number applies to
+ that gene; genes with several distinct EC numbers among their matches are
+ also reported as conflicts (the first match is still used for EC). Genes
+ without an EC number are left out. When gene_set has more than one gene
+ (an enzyme complex), the numbers found for each gene are combined by
+ intersection where they share a match (a wildcard such as "1.1.1.-" matches
+ the specific codes of its prefix), otherwise by union. The EC set of every
+ gene set, one gene or a complex, is cleaned of a wildcard code such as
+ "1.1.1.-" when a more specific code of the same prefix ("1.1.1.4") is
+ present; only codes with four levels take part, and empty codes are removed.
+
+ Parameters
+ ----------
+ gene_set : cell
+ genes from a given metabolic reaction.
+ DBecNum : cell
+ array of EC numbers.
+ DBMW : double
+ array of molecular weights in Da.
+ geneIndex : cell
+ as generated by getECfromDatabase.
+ geneHashMap : containers.Map
+ as generated by getECfromDatabase.
+
+ Returns
+ -------
+ EC : char
+ EC numbers that apply to gene_set, each prefixed with "EC" and
+ separated by ";" (e.g. "EC1.1.1.1;EC1.1.1.2").
+ conflicts : cell
+ genes with multiple protein matches.
+
+ Examples
+ --------
+ [EC, conflicts] = findECInDB(gene_set, DBecNum, DBMW, geneIndex, geneHashMap);
+
+ See also
+ --------
+ getECfromDatabase, getECstring
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
getECstring getECstring Build a single string of EC numbers for a protein.
+This function is called by:
+
+
getECfromDatabase getECfromDatabase Populate model.ec.eccodes from UniProt and KEGG databases.
0001 function [EC,conflicts] = findECInDB(gene_set, DBecNum, DBMW, geneIndex, geneHashMap)
+0002 % findECInDB Look up EC numbers for a reaction's gene set in a UniProt or KEGG index.
+0003 %
+0004 % For each gene, finds its matching database entries and takes the EC number
+0005 % of the lightest-weight matching protein when a single EC number applies to
+0006 % that gene; genes with several distinct EC numbers among their matches are
+0007 % also reported as conflicts (the first match is still used for EC). Genes
+0008 % without an EC number are left out. When gene_set has more than one gene
+0009 % (an enzyme complex), the numbers found for each gene are combined by
+0010 % intersection where they share a match (a wildcard such as "1.1.1.-" matches
+0011 % the specific codes of its prefix), otherwise by union. The EC set of every
+0012 % gene set, one gene or a complex, is cleaned of a wildcard code such as
+0013 % "1.1.1.-" when a more specific code of the same prefix ("1.1.1.4") is
+0014 % present; only codes with four levels take part, and empty codes are removed.
+0015 %
+0016 % Parameters
+0017 % ----------
+0018 % gene_set : cell
+0019 % genes from a given metabolic reaction.
+0020 % DBecNum : cell
+0021 % array of EC numbers.
+0022 % DBMW : double
+0023 % array of molecular weights in Da.
+0024 % geneIndex : cell
+0025 % as generated by getECfromDatabase.
+0026 % geneHashMap : containers.Map
+0027 % as generated by getECfromDatabase.
+0028 %
+0029 % Returns
+0030 % -------
+0031 % EC : char
+0032 % EC numbers that apply to gene_set, each prefixed with "EC" and
+0033 % separated by ";" (e.g. "EC1.1.1.1;EC1.1.1.2").
+0034 % conflicts : cell
+0035 % genes with multiple protein matches.
+0036 %
+0037 % Examples
+0038 % --------
+0039 % [EC, conflicts] = findECInDB(gene_set, DBecNum, DBMW, geneIndex, geneHashMap);
+0040 %
+0041 % See also
+0042 % --------
+0043 % getECfromDatabase, getECstring
+0044
+0045 %Preallocate function variables
+0046 conflicts = cell(1,2);
+0047
+0048 %Split the gene set and match each gene:
+0049 %gene_set = strsplit(grRule,' and '); %this string splitting together with getSimpleGeneSets takes 2.6 s, that is ok.
+0050 EC_set = cell(size(gene_set));
+0051
+0052 %find index indices of all genes in one call
+0053
+0054 %this loop is slow
+0055 for j = 1:length(gene_set)
+0056 gene = gene_set{j};
+0057 matches = [];
+0058 %Get the indexes for all of the proteins related to gene
+0059 if isKey(geneHashMap,gene) %annoyingly, this seems to be needed
+0060 matchInd = cell2mat(values(geneHashMap,gene_set(j)));
+0061 matches = geneIndex{matchInd};
+0062 end
+0063 if ~isempty(matches)
+0064 %Get the indexes of the matched protein(s) with non-empty EC#s
+0065 nonEmpty = matches(~cellfun(@isempty,DBecNum(matches)));
+0066 if ~isempty(nonEmpty)
+0067 [geneECs,ia] = unique(DBecNum(nonEmpty),'stable');
+0068 %For genes with multiple proteins associated to several
+0069 %non-empty ecNumbers
+0070 if length(geneECs)>1
+0071 indexes = nonEmpty(ia);
+0072 ecNum = DBecNum(indexes);
+0073 %Save first match
+0074 EC_set{j} = getECstring(EC_set{j},ecNum{1});
+0075 %Save additional matches as potential conflicts
+0076 if ~ismember(gene,conflicts{1})
+0077 conflicts{1} = [conflicts{1}; {gene}];
+0078 conflicts{2} = [conflicts{2}; {indexes}];
+0079 end
+0080 else
+0081 %If there is a single unique ec number for the matched
+0082 %protein(s), then choose the lightest protein
+0083 [~,minW] = min(DBMW(nonEmpty));
+0084 matches = nonEmpty(minW);
+0085 EC_set{j} = getECstring(EC_set{j},DBecNum{matches});
+0086 end
+0087 end
+0088 end
+0089
+0090 if isempty(EC_set{j})
+0091 EC_set{j} = '';
+0092 end
+0093 end
+0094
+0095 %EC: Split the codes of each gene, leave out the genes without any, and
+0096 %combine the rest (union and intersection only apply for complexes, i.e. more
+0097 %than one gene with codes):
+0098 tokens = cell(size(EC_set));
+0099 for j = 1:length(EC_set)
+0100 codes = strsplit(EC_set{j},' ');
+0101 tokens{j} = codes(~cellfun(@isempty,codes));
+0102 end
+0103 tokens = tokens(~cellfun(@isempty,tokens));
+0104 if isempty(tokens)
+0105 EC_set = {};
+0106 elseif isscalar(tokens)
+0107 EC_set = dropWildcardParents(tokens{1});
+0108 else
+0109 uni_EC = tokens{1};
+0110 int_EC = tokens{1};
+0111 for j = 2:length(tokens)
+0112 uni_EC = dropWildcardParents([uni_EC tokens{j}]);
+0113 int_EC = wildcardIntersection(int_EC,tokens{j});
+0114 end
+0115 %Use the intersection found, if any. If not, use the union:
+0116 if isempty(int_EC)
+0117 EC_set = uni_EC;
+0118 else
+0119 EC_set = int_EC;
+0120 end
+0121 EC_set = dropWildcardParents(EC_set);
+0122 end
+0123
+0124 %Add new codes as new possible isozymes and remove the initial "EC" text:
+0125
+0126 for i = 1:length(EC_set)
+0127 if ~isempty(EC_set{i})
+0128 EC_set{i} = extractAfter(EC_set{i},2);%remove 2 first chars
+0129 end
+0130 end
+0131
+0132 %EC_set = cellfun(@(x) extractAfter(x,2),EC_set,'UniformOutput',false);%remove 2 first chars
+0133 if isempty(EC_set)
+0134 EC = '';
+0135 else
+0136 EC = strjoin(EC_set,';');
+0137 end
+0138
+0139
+0140 %Delete repeated Uniprots (and the corresponding ECs):
+0141 %EC(deleted) = []; %Not sure if this needs to be done somehow, don't think so
+0142
+0143 end
+0144
+0145 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0146 function int_EC = wildcardIntersection(prev_EC,new_EC)
+0147 %Finds the common elements between two cell arrays, if any. Also considers
+0148 %wildcards (e.g. if 'EC1.1.1.1' is in one array and 'EC1.1.1.-' is in the
+0149 %other one, then 'EC1.1.1.1' is added to the intersection). A pair adds its
+0150 %code only when one code of the pair is the wildcard parent of the other, or
+0151 %the two are equal four-level codes.
+0152 int_EC = {};
+0153 for i = 1:length(prev_EC)
+0154 for j = 1:length(new_EC)
+0155 survivors = dropWildcardParents({prev_EC{i} new_EC{j}});
+0156 if length(survivors) == 1
+0157 int_EC = [int_EC survivors];
+0158 end
+0159 end
+0160 end
+0161 end
+0162 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0163 function int_EC = dropWildcardParents(EC)
+0164 %Removes empty codes and every code that is a wildcard parent of another
+0165 %code in the cell array (e.g. 'EC1.2.3.-' when 'EC1.2.3.4' is present). Only
+0166 %codes with four levels are compared, so 'EC1.1.1' is neither a parent nor a
+0167 %child of 'EC1.1.1.4'. Equal codes collapse to the first.
+0168 EC = EC(~cellfun(@isempty,EC) & ~strcmp(EC,'EC'));
+0169 keep = true(1,numel(EC));
+0170 for i = 1:numel(EC)
+0171 if ~keep(i)
+0172 continue;
+0173 end
+0174 for j = i+1:numel(EC)
+0175 if ~keep(j)
+0176 continue;
+0177 end
+0178 iInJ = subsumes(EC{i},EC{j});
+0179 jInI = subsumes(EC{j},EC{i});
+0180 if iInJ && jInI
+0181 keep(j) = false;
+0182 elseif iInJ
+0183 keep(i) = false;
+0184 break;
+0185 elseif jInI
+0186 keep(j) = false;
+0187 end
+0188 end
+0189 end
+0190 int_EC = EC(keep);
+0191 end
+0192 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0193 function tf = subsumes(general,specific)
+0194 %True if the code general is equal to or a wildcard parent of the code
+0195 %specific; both carry the "EC" prefix and need four levels.
+0196 general = strsplit(extractAfter(general,2),'.');
+0197 specific = strsplit(extractAfter(specific,2),'.');
+0198 tf = false;
+0199 if numel(general) ~= 4 || numel(specific) ~= 4
+0200 return;
+0201 end
+0202 for k = 1:4
+0203 if strcmp(general{k},'-')
+0204 tf = true;
+0205 return;
+0206 end
+0207 if strcmp(specific{k},'-') || ~strcmp(general{k},specific{k})
+0208 return;
+0209 end
+0210 end
+0211 tf = true;
+0212 end
getECfromDatabase Populate model.ec.eccodes from UniProt and KEGG databases.
+
+
SYNOPSIS
+
function model = getECfromDatabase(model, varargin)
+
+
DESCRIPTION
+
getECfromDatabase Populate model.ec.eccodes from UniProt and KEGG databases.
+
+ Populates the model.ec.eccodes field with enzyme codes that are extracted
+ from UniProt and KEGG databases, as assigned to the proteins that catalyze
+ the specific reactions.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ ecRxns : logical
+ of length model.ec.rxns that specifies which model.ec.eccodes entries
+ should be queried. Existing values in model.ec.eccodes will be wiped.
+ Entries that are indicated by false will be kept and not modified by
+ this function (by default all model.ec.eccodes entries are populated
+ by this function).
+ action : char
+ response action if multiple proteins with different EC numbers are
+ found for a given gene in a metabolic reaction (default 'display'):
+
+ - 'display' displays all found multiplicities.
+ - 'ignore' ignore multiplicities and use the protein with the lowest
+ index in the database.
+ - 'add' adds all the multiple proteins as isozymes for the given
+ reaction.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ ecModel with populated model.ec.eccodes.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ model = getECfromDatabase(model, ecRxns, action, modelAdapter);
+ model = getECfromDatabase(model, 'action', 'ignore');
+
+ See also
+ --------
+ getECfromGEM, findECInDB, loadDatabases
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
findECInDB findECInDB Look up EC numbers for a reaction's gene set in a UniProt or KEGG index.
0001 function model = getECfromDatabase(model, varargin)
+0002 % getECfromDatabase Populate model.ec.eccodes from UniProt and KEGG databases.
+0003 %
+0004 % Populates the model.ec.eccodes field with enzyme codes that are extracted
+0005 % from UniProt and KEGG databases, as assigned to the proteins that catalyze
+0006 % the specific reactions.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % model : struct
+0011 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0012 %
+0013 % Name-Value Arguments
+0014 % --------------------
+0015 % ecRxns : logical
+0016 % of length model.ec.rxns that specifies which model.ec.eccodes entries
+0017 % should be queried. Existing values in model.ec.eccodes will be wiped.
+0018 % Entries that are indicated by false will be kept and not modified by
+0019 % this function (by default all model.ec.eccodes entries are populated
+0020 % by this function).
+0021 % action : char
+0022 % response action if multiple proteins with different EC numbers are
+0023 % found for a given gene in a metabolic reaction (default 'display'):
+0024 %
+0025 % - 'display' displays all found multiplicities.
+0026 % - 'ignore' ignore multiplicities and use the protein with the lowest
+0027 % index in the database.
+0028 % - 'add' adds all the multiple proteins as isozymes for the given
+0029 % reaction.
+0030 % modelAdapter : ModelAdapter
+0031 % a loaded model adapter (default: the current default model adapter).
+0032 %
+0033 % Returns
+0034 % -------
+0035 % model : struct
+0036 % ecModel with populated model.ec.eccodes.
+0037 %
+0038 % Examples
+0039 % --------
+0040 % % optional arguments may be given positionally or as name-value pairs:
+0041 % model = getECfromDatabase(model, ecRxns, action, modelAdapter);
+0042 % model = getECfromDatabase(model, 'action', 'ignore');
+0043 %
+0044 % See also
+0045 % --------
+0046 % getECfromGEM, findECInDB, loadDatabases
+0047
+0048 p = parseGECKOargs(varargin, { ...
+0049 'ecRxns', []; ...
+0050 'action', 'display'; ...
+0051 'modelAdapter', []});
+0052 ecRxns = p.ecRxns;
+0053 action = p.action;
+0054 modelAdapter = p.modelAdapter;
+0055
+0056 if isempty(modelAdapter)
+0057 modelAdapter = ModelAdapterManager.getDefault();
+0058 if isempty(modelAdapter)
+0059 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0060 end
+0061 end
+0062 params = modelAdapter.getParameters();
+0063
+0064 rxnEnzMat = model.ec.rxnEnzMat;
+0065 genes = modelAdapter.getUniprotCompatibleGenes(model.ec.genes);
+0066
+0067 data = loadDatabases('both', modelAdapter);
+0068 uniprot = data.uniprot;
+0069 kegg = data.kegg;
+0070
+0071 modelGenes = modelAdapter.getUniprotIDsFromTable(model.genes);
+0072 DBgenesUniprot = data.uniprot.genes;
+0073 if ~isequal(modelGenes,model.genes)
+0074 [Lia,Locb] = ismember(modelGenes,uniprot.ID);
+0075 DBgenesUniprot(Locb(Lia)) = model.genes(Lia);
+0076 keepEntry = unique(Locb(Lia));
+0077 DBgenesUniprot = DBgenesUniprot(keepEntry);
+0078 else
+0079 keepEntry = true(numel(DBgenesUniprot),1);
+0080 end
+0081 DBecNumUniprot = data.uniprot.eccodes(keepEntry);
+0082 DBMWUniprot = data.uniprot.MW(keepEntry);
+0083 %Build an index from gene to prot for faster processing later
+0084 [geneIndexUniprot,geneHashMapUniprot] = hashGeneToProt(DBgenesUniprot);
+0085
+0086 if ~isempty(kegg)
+0087 DBgenesKEGG = data.kegg.genes;
+0088 DBecNumKEGG = data.kegg.eccodes;
+0089 DBMWKEGG = data.kegg.MW;
+0090 [geneIndexKEGG,geneHashMapKEGG] = hashGeneToProt(DBgenesKEGG);
+0091 end
+0092 n = size(rxnEnzMat,1);
+0093
+0094 eccodes = cell(n,1);
+0095 eccodes(:)= {''};
+0096 conflicts = cell(1,4);
+0097
+0098 rxnEnzMat = logical(rxnEnzMat);
+0099 PB = progressReport(n, 'Assigning EC numbers from database');
+0100 for i = 1:n
+0101 PB.count;
+0102 gns = genes(rxnEnzMat(i,:).');
+0103 if ~isempty(gns)
+0104 %Find match in Uniprot:
+0105 [new_EC,multGenes] = findECInDB(gns,DBecNumUniprot,DBMWUniprot,geneIndexUniprot,geneHashMapUniprot);
+0106 if ~isempty(new_EC)
+0107 DBase = 'uniprot';
+0108 if ~isempty(multGenes{1})
+0109 multGenes{3} = DBase;
+0110 end
+0111 end
+0112 if ~isempty(kegg) && (isempty(new_EC) || endsWith(new_EC,'-'))
+0113 %Find match in KEGG
+0114 [new_EC_kegg,multGenes] = findECInDB(gns,DBecNumKEGG,DBMWKEGG,geneIndexKEGG,geneHashMapKEGG);
+0115 if ~isempty(new_EC_kegg)
+0116 DBase = 'kegg';
+0117 if ~isempty(multGenes{1})
+0118 multGenes{3} = DBase;
+0119 end
+0120 new_EC=new_EC_kegg;
+0121 end
+0122 end
+0123 eccodes{i} = new_EC;
+0124
+0125 if ~isempty(multGenes{1})
+0126 %Rxn index
+0127 conflicts{1} = [conflicts{1};i];
+0128 %Gene IDs
+0129 conflicts{2} = [conflicts{2};multGenes{1}];
+0130 %Indexes in DB
+0131 conflicts{3} = [conflicts{3};multGenes{2}];
+0132 %DB name
+0133 conflicts{4} = [conflicts{4};{multGenes{3}}];
+0134
+0135 %{ I don't understand the purpose of this, let's skip it for now
+0136 %if strcmpi(action,'add')
+0137 % if strcmpi(DBase,'swissprot')
+0138 % [uni,EC,MW,Genes] = addMultipleMatches(uni,EC,MW,Genes,multGenes,swissprot);
+0139 % elseif strcmpi(DBase,'KEGG')
+0140 % [uni,EC,MW,Genes] = addMultipleMatches(uni,EC,MW,Genes,multGenes,kegg);
+0141 % end
+0142 %end
+0143 %}
+0144 end
+0145 end
+0146 end
+0147
+0148 %Display error message with the multiple gene-protein matches found
+0149 if strcmpi(action,'display') && ~isempty(conflicts{1})
+0150 displayErrorMessage(conflicts,uniprot,kegg)
+0151 end
+0152
+0153 if isempty(ecRxns) || all(ecRxns)
+0154 model.ec.eccodes = eccodes;
+0155 else
+0156 if ~isfield(model.ec,'eccodes')
+0157 model.ec.eccodes(1:numel(model.ec.rxns),1) = {''};
+0158 end
+0159 %Probably faster to subset with ecRxns in the beginning of the script,
+0160 %but this was at the moment simpler to implement.
+0161 model.ec.eccodes(ecRxns) = eccodes(ecRxns);
+0162 end
+0163
+0164 function displayErrorMessage(conflicts,uniprot,kegg)
+0165 STR = ['\n ' num2str(length(conflicts{1})) ' genes with multiple associated proteins were found, please'];
+0166 STR = [STR, ' revise case by case in the uniprot and kegg files:\n\n'];
+0167 for i=1:length(conflicts{1})
+0168 if strcmpi(conflicts{4}{i},'uniprot')
+0169 DB = uniprot.ID;
+0170 else
+0171 DB = kegg.uniprot;
+0172 end
+0173 proteins = DB(conflicts{3}{i});
+0174 STR = [STR, '- gene: ' conflicts{2}{i} ' Proteins: ' strjoin(proteins) '\n'];
+0175 end
+0176 STR = [STR, '\nIf a wrongly annotated case was found then call the '];
+0177 STR = [STR, 'getECfromDatabase.m function again with the option action'];
+0178 STR = [STR, '= ignore\n\n'];
+0179 STR = [STR, 'If the conflicting proteins are desired to be kept as isozymes'];
+0180 STR = [STR, ' then call the getECfromDatabase.m function'];
+0181 STR = [STR, ' again with the option action = add\n'];
+0182 error(sprintf(STR))
+0183 end
+0184
+0185 function [geneIndex,geneHashMap]=hashGeneToProt(proteinDB)
+0186
+0187 [x,y] = size(proteinDB);
+0188 genesForIndex = reshape(proteinDB, x*y, 1);
+0189 genesForIndex = genesForIndex(~cellfun(@isempty, genesForIndex));
+0190 genesForIndex = unique(genesForIndex);
+0191 geneIndex = cell(length(genesForIndex),1);
+0192 geneHashMap = containers.Map(genesForIndex,1:length(genesForIndex));
+0193 protIndices = 1:length(proteinDB(:,1));
+0194 for i = 1:y
+0195 tmp1 = proteinDB(:,i);
+0196 sel = ~cellfun(@isempty, tmp1);
+0197 indices = cell2mat(values(geneHashMap,tmp1(sel)));
+0198 protIndicesSel = protIndices(sel);
+0199 for j = 1:length(indices)
+0200 geneIndex{indices(j)} = [geneIndex{indices(j)};protIndicesSel(j)];
+0201 end
+0202 end
+0203 end
+0204 end
getECfromGEM Populate model.ec.eccodes from the model.eccodes field.
+
+
SYNOPSIS
+
function [model, invalidEC, invalidECpos] = getECfromGEM(model, varargin)
+
+
DESCRIPTION
+
getECfromGEM Populate model.ec.eccodes from the model.eccodes field.
+
+ Uses the model.eccodes to populate the model.ec.eccodes field. EC numbers
+ must be formatted as four numbers separated by periods, possibly with
+ trailing wildcards; incorrectly formatted EC numbers are skipped.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ ecRxns : logical
+ of length model.ec.rxns that specifies for which reactions the
+ existing model.ec.eccodes entry should be kept and not modified by
+ this function (by default all model.ec.eccodes entries are populated
+ by this function).
+
+ Returns
+ -------
+ model : struct
+ ecModel with populated model.ec.eccodes.
+ invalidEC : cell
+ incorrectly formatted EC numbers.
+ invalidECpos : double
+ position of invalidEC in model.eccodes.
+
+ Notes
+ -----
+ Valid EC numbers are e.g. 1.2.3.4 or 1.2.3.-, while invalid EC numbers are
+ 1.2.3 or 1_2_3_4. Multiple EC numbers are separated by ; for instance
+ 1.2.3.4;1.2.3.5 not 1.2.3.4|1.2.3.5.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, invalidEC, invalidECpos] = getECfromGEM(model, ecRxns);
+ [model, invalidEC, invalidECpos] = getECfromGEM(model, 'ecRxns', ecRxns);
+
+ See also
+ --------
+ getECfromDatabase, applyECcodes
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [model, invalidEC, invalidECpos] = getECfromGEM(model, varargin)
+0002 % getECfromGEM Populate model.ec.eccodes from the model.eccodes field.
+0003 %
+0004 % Uses the model.eccodes to populate the model.ec.eccodes field. EC numbers
+0005 % must be formatted as four numbers separated by periods, possibly with
+0006 % trailing wildcards; incorrectly formatted EC numbers are skipped.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % model : struct
+0011 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0012 %
+0013 % Name-Value Arguments
+0014 % --------------------
+0015 % ecRxns : logical
+0016 % of length model.ec.rxns that specifies for which reactions the
+0017 % existing model.ec.eccodes entry should be kept and not modified by
+0018 % this function (by default all model.ec.eccodes entries are populated
+0019 % by this function).
+0020 %
+0021 % Returns
+0022 % -------
+0023 % model : struct
+0024 % ecModel with populated model.ec.eccodes.
+0025 % invalidEC : cell
+0026 % incorrectly formatted EC numbers.
+0027 % invalidECpos : double
+0028 % position of invalidEC in model.eccodes.
+0029 %
+0030 % Notes
+0031 % -----
+0032 % Valid EC numbers are e.g. 1.2.3.4 or 1.2.3.-, while invalid EC numbers are
+0033 % 1.2.3 or 1_2_3_4. Multiple EC numbers are separated by ; for instance
+0034 % 1.2.3.4;1.2.3.5 not 1.2.3.4|1.2.3.5.
+0035 %
+0036 % Examples
+0037 % --------
+0038 % % optional arguments may be given positionally or as name-value pairs:
+0039 % [model, invalidEC, invalidECpos] = getECfromGEM(model, ecRxns);
+0040 % [model, invalidEC, invalidECpos] = getECfromGEM(model, 'ecRxns', ecRxns);
+0041 %
+0042 % See also
+0043 % --------
+0044 % getECfromDatabase, applyECcodes
+0045
+0046 p = parseGECKOargs(varargin, { ...
+0047 'ecRxns', []});
+0048 ecRxns = p.ecRxns;
+0049
+0050 if ~isfield(model,'eccodes')
+0051 error('The model has no model.eccodes field.')
+0052 end
+0053
+0054 %Need to remove the prefix of GECKO light rxn names in the ec structure
+0055 if ~model.ec.geckoLight
+0056 rxnNames = model.ec.rxns;
+0057 else
+0058 rxnNames = extractAfter(model.ec.rxns, 4);
+0059 end
+0060
+0061 [~,rxnIdxs] = ismember(rxnNames,model.rxns);
+0062
+0063 % Check if eccodes are valid. Each of the four dot-separated components must
+0064 % be either a run of digits or the wildcard '-'; \w (the previous class) also
+0065 % matches letters and underscores, so malformed codes such as '1.1.1.n12' or
+0066 % '1_2_3_4' were incorrectly accepted.
+0067 eccodes = model.eccodes;
+0068 invalidEC = regexprep(eccodes,'(\d+\.(\d+|-)\.(\d+|-)\.(\d+|-))(;\d+\.(\d+|-)\.(\d+|-)\.(\d+|-))*(.*)','$3');
+0069 invalidEC = ~cellfun(@isempty,invalidEC);
+0070 invalidECpos = find(invalidEC);
+0071 if any(invalidECpos)
+0072 invalidEC = model.eccodes(invalidEC);
+0073 if nargout<2
+0074 fprintf('Skipped incorrectly formatted EC numbers, rerun getECfromGEM with all outputs to get a list.\n')
+0075 else
+0076 fprintf('Skipped incorrectly formatted EC numbers.\n')
+0077 end
+0078 eccodes(invalidECpos)={''};
+0079 else
+0080 invalidEC = [];
+0081 end
+0082 if isempty(ecRxns) || all(ecRxns)
+0083 model.ec.eccodes = eccodes(rxnIdxs);
+0084 else
+0085 if ~isfield(model.ec,'eccodes')
+0086 model.ec.eccodes(1:numel(model.ec.rxns),1) = {''};
+0087 end
+0088 model.ec.eccodes(ecRxns) = eccodes(rxnIdxs(ecRxns));
+0089 end
+0090 end
getECstring Build a single string of EC numbers for a protein.
+
+
SYNOPSIS
+
function EC_set = getECstring(EC_set,ecNumbers)
+
+
DESCRIPTION
+
getECstring Build a single string of EC numbers for a protein.
+
+ Provides a single string with all the EC numbers associated to a given
+ protein, according to the following format:
+ "ECX.X.X.X ECX.X.X.X ECX.X.X.X".
+
+ Parameters
+ ----------
+ EC_set : char
+ existing EC string to which the EC numbers are appended.
+ ecNumbers : char
+ EC numbers associated to the protein, separated by white space and/or
+ semicolons.
+
+ Returns
+ -------
+ EC_set : char
+ a single string with all the EC numbers, each prefixed with "EC".
+
+ Examples
+ --------
+ EC_set = getECstring(EC_set, ecNumbers);
+
+ See also
+ --------
+ findECInDB
+In case of several ec numbers for the same protein
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
findECInDB findECInDB Look up EC numbers for a reaction's gene set in a UniProt or KEGG index.
+
+
+
+
+
SOURCE CODE
+
0001 function EC_set = getECstring(EC_set,ecNumbers)
+0002 % getECstring Build a single string of EC numbers for a protein.
+0003 %
+0004 % Provides a single string with all the EC numbers associated to a given
+0005 % protein, according to the following format:
+0006 % "ECX.X.X.X ECX.X.X.X ECX.X.X.X".
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % EC_set : char
+0011 % existing EC string to which the EC numbers are appended.
+0012 % ecNumbers : char
+0013 % EC numbers associated to the protein, separated by white space and/or
+0014 % semicolons.
+0015 %
+0016 % Returns
+0017 % -------
+0018 % EC_set : char
+0019 % a single string with all the EC numbers, each prefixed with "EC".
+0020 %
+0021 % Examples
+0022 % --------
+0023 % EC_set = getECstring(EC_set, ecNumbers);
+0024 %
+0025 % See also
+0026 % --------
+0027 % findECInDB
+0028 %In case of several ec numbers for the same protein
+0029 new_EC_set = regexp(strtrim(ecNumbers),'[;\s]+','split');
+0030 new_EC_set = new_EC_set(~cellfun(@isempty,new_EC_set));
+0031 for l = 1:length(new_EC_set)
+0032 EC_set = [EC_set 'EC' new_EC_set{l}];
+0033 if l<length(new_EC_set)
+0034 EC_set = [EC_set ' '];
+0035 end
+0036 end
+0037 end
loadConventionalGEM Load the conventional GEM (non-ecModel).
+
+
SYNOPSIS
+
function model = loadConventionalGEM(varargin)
+
+
DESCRIPTION
+
loadConventionalGEM Load the conventional GEM (non-ecModel).
+
+ Loads the conventional GEM (non-ecModel) from the location specified in
+ the modelAdapter. By default, it looks in the models/ subdirectory of the
+ param.path specified in the modelAdapter. When loading conventional GEMs
+ from other locations, one can directly use importModel.
+
+ Name-Value Arguments
+ --------------------
+ filename : char
+ name of the model file, without extension, located in the models/
+ subfolder of param.path as specified in the modelAdapter; it is
+ loaded as SBML (.xml) (default: the value specified as param.convGEM
+ in the modelAdapter, which may instead point to a YAML file).
+ modelAdapter : ModelAdapter
+ a loaded model adapter, from where the model folder is read (default:
+ the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ model in RAVEN format.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ model = loadConventionalGEM(filename, modelAdapter);
+ model = loadConventionalGEM('filename', filename);
+
+ See also
+ --------
+ loadEcModel
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function model = loadConventionalGEM(varargin)
+0002 % loadConventionalGEM Load the conventional GEM (non-ecModel).
+0003 %
+0004 % Loads the conventional GEM (non-ecModel) from the location specified in
+0005 % the modelAdapter. By default, it looks in the models/ subdirectory of the
+0006 % param.path specified in the modelAdapter. When loading conventional GEMs
+0007 % from other locations, one can directly use importModel.
+0008 %
+0009 % Name-Value Arguments
+0010 % --------------------
+0011 % filename : char
+0012 % name of the model file, without extension, located in the models/
+0013 % subfolder of param.path as specified in the modelAdapter; it is
+0014 % loaded as SBML (.xml) (default: the value specified as param.convGEM
+0015 % in the modelAdapter, which may instead point to a YAML file).
+0016 % modelAdapter : ModelAdapter
+0017 % a loaded model adapter, from where the model folder is read (default:
+0018 % the current default model adapter).
+0019 %
+0020 % Returns
+0021 % -------
+0022 % model : struct
+0023 % model in RAVEN format.
+0024 %
+0025 % Examples
+0026 % --------
+0027 % % optional arguments may be given positionally or as name-value pairs:
+0028 % model = loadConventionalGEM(filename, modelAdapter);
+0029 % model = loadConventionalGEM('filename', filename);
+0030 %
+0031 % See also
+0032 % --------
+0033 % loadEcModel
+0034
+0035 p = parseGECKOargs(varargin, { ...
+0036 'filename', []; ...
+0037 'modelAdapter', []});
+0038 filename = p.filename;
+0039 modelAdapter = p.modelAdapter;
+0040
+0041 if isempty(modelAdapter)
+0042 modelAdapter = ModelAdapterManager.getDefault();
+0043 if isempty(modelAdapter)
+0044 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0045 end
+0046 end
+0047 params = modelAdapter.getParameters();
+0048 if isempty(filename)
+0049 filename = params.convGEM;
+0050 else
+0051 filename = fullfile(params.path,'models',[filename, '.xml'])
+0052 end
+0053
+0054 if endsWith(filename,'.xml')
+0055 model = importModel(filename);
+0056 elseif endsWith(filename,'.yml')
+0057 model = readYAMLmodel(filename);
+0058 end
loadEcModel Load the ecModel that matches the modelAdapter.
+
+
SYNOPSIS
+
function model = loadEcModel(varargin)
+
+
DESCRIPTION
+
loadEcModel Load the ecModel that matches the modelAdapter.
+
+ Loads the ecModel that matches the modelAdapter. By default, it loads the
+ models/ecModel.yml in the directory specified as param.path in the
+ modelAdapter. Alternative files in the same folder can be loaded by
+ providing the appropriate filename. If loading models from other
+ locations, one can directly use readYAMLmodel.
+
+ An ecModel whose protein usage and pool reactions run in the GECKO 3
+ direction is upgraded in the loaded model with upgradeEcModel, with a
+ warning. An ecModel format (gecko_format in the file's metaData, see
+ saveEcModel) above 4 is an error.
+
+ Name-Value Arguments
+ --------------------
+ filename : char
+ name of the ecModel file (default 'ecModel.yml'). File should be
+ located in the models/ subfolder of param.path as specified in the
+ modelAdapter.
+ modelAdapter : ModelAdapter
+ a loaded model adapter, from where the model folder is read (default:
+ the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ an ecModel (with ecModel.ec structure).
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ model = loadEcModel(filename, modelAdapter);
+ model = loadEcModel('filename', filename);
+
+ See also
+ --------
+ loadConventionalGEM, saveEcModel
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function model = loadEcModel(varargin)
+0002 % loadEcModel Load the ecModel that matches the modelAdapter.
+0003 %
+0004 % Loads the ecModel that matches the modelAdapter. By default, it loads the
+0005 % models/ecModel.yml in the directory specified as param.path in the
+0006 % modelAdapter. Alternative files in the same folder can be loaded by
+0007 % providing the appropriate filename. If loading models from other
+0008 % locations, one can directly use readYAMLmodel.
+0009 %
+0010 % An ecModel whose protein usage and pool reactions run in the GECKO 3
+0011 % direction is upgraded in the loaded model with upgradeEcModel, with a
+0012 % warning. An ecModel format (gecko_format in the file's metaData, see
+0013 % saveEcModel) above 4 is an error.
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % filename : char
+0018 % name of the ecModel file (default 'ecModel.yml'). File should be
+0019 % located in the models/ subfolder of param.path as specified in the
+0020 % modelAdapter.
+0021 % modelAdapter : ModelAdapter
+0022 % a loaded model adapter, from where the model folder is read (default:
+0023 % the current default model adapter).
+0024 %
+0025 % Returns
+0026 % -------
+0027 % model : struct
+0028 % an ecModel (with ecModel.ec structure).
+0029 %
+0030 % Examples
+0031 % --------
+0032 % % optional arguments may be given positionally or as name-value pairs:
+0033 % model = loadEcModel(filename, modelAdapter);
+0034 % model = loadEcModel('filename', filename);
+0035 %
+0036 % See also
+0037 % --------
+0038 % loadConventionalGEM, saveEcModel
+0039
+0040 p = parseGECKOargs(varargin, { ...
+0041 'filename', []; ...
+0042 'modelAdapter', []});
+0043 filename = p.filename;
+0044 modelAdapter = p.modelAdapter;
+0045
+0046 if isempty(modelAdapter)
+0047 modelAdapter = ModelAdapterManager.getDefault();
+0048 if isempty(modelAdapter)
+0049 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0050 end
+0051 end
+0052 params = modelAdapter.getParameters();
+0053 if isempty(filename)
+0054 filename = 'ecModel.yml';
+0055 end
+0056
+0057 if ~endsWith(filename,{'yml','yaml'})
+0058 error(['ecModels should preferably be distributed in YAML file format, ', ...
+0059 'as otherwise model content will be lost. If the model is in ', ...
+0060 'SBML format, you can use the usual importModel function to load ', ...
+0061 'this as any other genome-scale model.'])
+0062 end
+0063
+0064 filename = fullfile(params.path,'models',filename);
+0065 if endsWith(filename,{'yml','yaml'})
+0066 model = readYAMLmodel(filename);
+0067 elseif endsWith(filename,{'xml','sbml'})
+0068 model = importModel(filename);
+0069 end
+0070
+0071 if isfield(model, 'annotation') && isfield(model.annotation, 'gecko_format') ...
+0072 && str2double(model.annotation.gecko_format) > 4
+0073 writer = 'an unknown release';
+0074 if isfield(model.annotation, 'generator')
+0075 writer = model.annotation.generator;
+0076 end
+0077 error('loadEcModel:format', ['%s has ecModel format %s (written by %s); '...
+0078 'this GECKO reads format 4 and older. Update GECKO to load it.'], ...
+0079 filename, model.annotation.gecko_format, writer)
+0080 end
+0081 [model, changed] = upgradeEcModel(model);
+0082 if ~isempty(changed)
+0083 warning('loadEcModel:upgraded', ['%s has %d protein usage and pool reactions in the GECKO 3 '...
+0084 'direction; they are turned forward in the loaded model. upgradeGECKOproject upgrades the '...
+0085 'files of the project.'], filename, numel(changed))
+0086 end
+0087 end
loadFluxData Load total protein measurements and exchange flux data.
+
+
SYNOPSIS
+
function fluxData = loadFluxData(varargin)
+
+
DESCRIPTION
+
loadFluxData Load total protein measurements and exchange flux data.
+
+ Loads total protein content and exchange flux data (e.g. carbon source,
+ O2, CO2) from a tab-delimited file.
+
+ Name-Value Arguments
+ --------------------
+ fluxDataFile : char
+ path to file with flux data (default reads data/fluxData.tsv as
+ specified in modelAdapter).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ fluxData : struct
+ structure with flux data.
+
+ Notes
+ -----
+ The fluxData structure has the following fields:
+
+ - conds : sampling condition.
+ - Ptot : total protein (g/gDCW).
+ - grRate : growth rate (1/h).
+ - exchFluxes : exchange fluxes (mmol/gDCWh).
+ - exchMets : exchanged metabolites, matching exchFluxes.
+ - exchRxnIDs : exchange reaction IDs, matching exchMets.
+ - evotuneRMSEweight : if column existed in fluxData.tsv, weights for RMSE calculation in evotune kcat tuning.
+ - source : if column existed in fluxData.tsv, description where the data comes from.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ fluxData = loadFluxData(fluxDataFile, modelAdapter);
+ fluxData = loadFluxData('fluxDataFile', fluxDataFile);
+
+ See also
+ --------
+ loadProtData
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function fluxData = loadFluxData(varargin)
+0002 % loadFluxData Load total protein measurements and exchange flux data.
+0003 %
+0004 % Loads total protein content and exchange flux data (e.g. carbon source,
+0005 % O2, CO2) from a tab-delimited file.
+0006 %
+0007 % Name-Value Arguments
+0008 % --------------------
+0009 % fluxDataFile : char
+0010 % path to file with flux data (default reads data/fluxData.tsv as
+0011 % specified in modelAdapter).
+0012 % modelAdapter : ModelAdapter
+0013 % a loaded model adapter (default: the current default model adapter).
+0014 %
+0015 % Returns
+0016 % -------
+0017 % fluxData : struct
+0018 % structure with flux data.
+0019 %
+0020 % Notes
+0021 % -----
+0022 % The fluxData structure has the following fields:
+0023 %
+0024 % - conds : sampling condition.
+0025 % - Ptot : total protein (g/gDCW).
+0026 % - grRate : growth rate (1/h).
+0027 % - exchFluxes : exchange fluxes (mmol/gDCWh).
+0028 % - exchMets : exchanged metabolites, matching exchFluxes.
+0029 % - exchRxnIDs : exchange reaction IDs, matching exchMets.
+0030 % - evotuneRMSEweight : if column existed in fluxData.tsv, weights for RMSE calculation in evotune kcat tuning.
+0031 % - source : if column existed in fluxData.tsv, description where the data comes from.
+0032 %
+0033 % Examples
+0034 % --------
+0035 % % optional arguments may be given positionally or as name-value pairs:
+0036 % fluxData = loadFluxData(fluxDataFile, modelAdapter);
+0037 % fluxData = loadFluxData('fluxDataFile', fluxDataFile);
+0038 %
+0039 % See also
+0040 % --------
+0041 % loadProtData
+0042
+0043 p = parseGECKOargs(varargin, { ...
+0044 'fluxDataFile', []; ...
+0045 'modelAdapter', []});
+0046 fluxDataFile = p.fluxDataFile;
+0047 modelAdapter = p.modelAdapter;
+0048
+0049 if isempty(modelAdapter)
+0050 modelAdapter = ModelAdapterManager.getDefault();
+0051 if isempty(modelAdapter)
+0052 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0053 end
+0054 end
+0055 params = modelAdapter.getParameters();
+0056
+0057 if isempty(fluxDataFile)
+0058 fluxDataFile = fullfile(params.path,'data','fluxData.tsv');
+0059 end
+0060
+0061 %Load total protein content and flux data
+0062 try
+0063 fID = fopen(fluxDataFile);
+0064 formatStr = '%s';
+0065 data = textscan(fID,formatStr,'Delimiter','\n');
+0066 fclose(fID);
+0067 catch
+0068 warning('Failed to load %s.',fluxDataFile);
+0069 fluxData = [];
+0070 return
+0071 end
+0072 fluxDataRaw = [];
+0073 for i=1:length(data{1})
+0074 row = data{1}(i);
+0075 row = strsplit(row{1},'\t');
+0076 row = row(1:end);
+0077 fluxDataRaw = [fluxDataRaw; row];
+0078 end
+0079 if size(fluxDataRaw,2) == 0
+0080 error('The input file %s is likely not tab-delimited.', fluxDataFile)
+0081 end
+0082 fluxData = [];
+0083
+0084 %Find additional fields
+0085 extraFieldNames = {'evotuneRMSEweight','source'};
+0086 extraFieldNumeric = [true,false];
+0087 [logicalExtra, extraFields] = ismember(extraFieldNames,fluxDataRaw(1,:));
+0088 extraFields(~logicalExtra) = [];
+0089 extraFieldNames(~logicalExtra) = [];
+0090 extraFieldNumeric(~logicalExtra) = [];
+0091 if any(extraFields)
+0092 for i=1:numel(extraFields)
+0093 if extraFieldNumeric(i)
+0094 fluxData.(extraFieldNames{i}) = str2double(fluxDataRaw(2:end,extraFields(i)));
+0095 else
+0096 fluxData.(extraFieldNames{i}) = fluxDataRaw(2:end,extraFields(i));
+0097 end
+0098 end
+0099 fluxDataRaw(:,extraFields) = [];
+0100 end
+0101
+0102 %Extract observed byProduct names from file
+0103 exchRxns = fluxDataRaw(1,4:end);
+0104 exchMets = strtrim(regexprep(exchRxns,'(.*)\(.*\)$','$1'));
+0105 exchRxns = regexprep(exchRxns,'.*\((.*)\)$','$1');
+0106
+0107 fluxData.conds = fluxDataRaw(2:end,1);
+0108 fluxData.Ptot = str2double(fluxDataRaw(2:end,2));
+0109 fluxData.grRate = str2double(fluxDataRaw(2:end,3));
+0110 fluxData.exchFluxes = str2double(fluxDataRaw(2:end,4:end));
+0111 %fluxData.exchFluxes(isnan(fluxData.exchFluxes)) = 0;
+0112 fluxData.exchMets = exchMets;
+0113 fluxData.exchRxnIDs = exchRxns;
+0114 end
loadProtData Load absolute proteomics data and average over replicates.
+
+
SYNOPSIS
+
function protData = loadProtData(replPerCond, varargin)
+
+
DESCRIPTION
+
loadProtData Load absolute proteomics data and average over replicates.
+
+ Loads absolute proteomics data (in mg/gDCW) and returns mean values
+ across replicates for each condition in the data file. By default it also
+ filters the data by various criteria, to remove uncertain data (see input
+ parameters).
+
+ Parameters
+ ----------
+ replPerCond : double
+ vector with number of replicates for each condition in the dataset.
+ Example: [3, 2] if first condition has triplicates and second
+ condition has duplicates.
+
+ Name-Value Arguments
+ --------------------
+ protDataFile : char
+ path to file with proteomics data, where protein levels are in
+ mg/gDCW (default reads data/proteomics.tsv as specified in
+ modelAdapter). Alternatively, protDataFile can be a protData structure
+ that was previously made by loadProtData.
+ filterData : logical
+ whether abundances should be filtered. If false, minVal, maxRSD,
+ maxMissing, cutLowest and addStdevs are not considered (default true).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ minVal : double
+ minimum mean protein measurement per condition; proteins with a lower
+ mean abundance are discarded (set to NaN) for that condition (default
+ 0, i.e. no lower threshold).
+ maxRSD : double
+ maximum relative standard deviation (RSD, the standard deviation
+ across replicates divided by their mean) allowed per condition;
+ proteins exceeding it are discarded (set to NaN) for that condition
+ (default 1, i.e. RSD up to 100%).
+ maxMissing : double
+ minimum fraction of replicates within a condition that must have a
+ measured value; proteins with fewer present replicates than this
+ fraction requires are discarded (set to NaN) for that condition
+ (default 2/3, i.e. at least two of three replicates must have a
+ value). If conditions have different number of replicates (as
+ indicated in replPerCond), maxMissing can also be a vector of the
+ same length as replPerCond, with individual values per condition.
+ cutLowest : double
+ percentage of lowest mean values per condition to be discarded (not
+ considering NaN values) (default 5).
+ addStdevs : double
+ how many standard deviations should be added to the mean value of
+ each protein measurement across replicates, broadening the confidence
+ interval (default 1).
+
+ Returns
+ -------
+ protData : struct
+ structure with (filtered) proteome data.
+
+ Notes
+ -----
+ The protData structure has the following fields:
+
+ - uniprotIDs : cell array with Uniprot IDs matching protData.abundances.
+ - abundances : matrix of proteomics data, where each column contains mean abundances per condition.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ protData = loadProtData(replPerCond, protDataFile, filterData, modelAdapter, minVal, maxRSD, maxMissing, cutLowest, addStdevs);
+ protData = loadProtData(replPerCond, 'filterData', false);
+
+ See also
+ --------
+ loadFluxData
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function protData = loadProtData(replPerCond, varargin)
+0002 % loadProtData Load absolute proteomics data and average over replicates.
+0003 %
+0004 % Loads absolute proteomics data (in mg/gDCW) and returns mean values
+0005 % across replicates for each condition in the data file. By default it also
+0006 % filters the data by various criteria, to remove uncertain data (see input
+0007 % parameters).
+0008 %
+0009 % Parameters
+0010 % ----------
+0011 % replPerCond : double
+0012 % vector with number of replicates for each condition in the dataset.
+0013 % Example: [3, 2] if first condition has triplicates and second
+0014 % condition has duplicates.
+0015 %
+0016 % Name-Value Arguments
+0017 % --------------------
+0018 % protDataFile : char
+0019 % path to file with proteomics data, where protein levels are in
+0020 % mg/gDCW (default reads data/proteomics.tsv as specified in
+0021 % modelAdapter). Alternatively, protDataFile can be a protData structure
+0022 % that was previously made by loadProtData.
+0023 % filterData : logical
+0024 % whether abundances should be filtered. If false, minVal, maxRSD,
+0025 % maxMissing, cutLowest and addStdevs are not considered (default true).
+0026 % modelAdapter : ModelAdapter
+0027 % a loaded model adapter (default: the current default model adapter).
+0028 % minVal : double
+0029 % minimum mean protein measurement per condition; proteins with a lower
+0030 % mean abundance are discarded (set to NaN) for that condition (default
+0031 % 0, i.e. no lower threshold).
+0032 % maxRSD : double
+0033 % maximum relative standard deviation (RSD, the standard deviation
+0034 % across replicates divided by their mean) allowed per condition;
+0035 % proteins exceeding it are discarded (set to NaN) for that condition
+0036 % (default 1, i.e. RSD up to 100%).
+0037 % maxMissing : double
+0038 % minimum fraction of replicates within a condition that must have a
+0039 % measured value; proteins with fewer present replicates than this
+0040 % fraction requires are discarded (set to NaN) for that condition
+0041 % (default 2/3, i.e. at least two of three replicates must have a
+0042 % value). If conditions have different number of replicates (as
+0043 % indicated in replPerCond), maxMissing can also be a vector of the
+0044 % same length as replPerCond, with individual values per condition.
+0045 % cutLowest : double
+0046 % percentage of lowest mean values per condition to be discarded (not
+0047 % considering NaN values) (default 5).
+0048 % addStdevs : double
+0049 % how many standard deviations should be added to the mean value of
+0050 % each protein measurement across replicates, broadening the confidence
+0051 % interval (default 1).
+0052 %
+0053 % Returns
+0054 % -------
+0055 % protData : struct
+0056 % structure with (filtered) proteome data.
+0057 %
+0058 % Notes
+0059 % -----
+0060 % The protData structure has the following fields:
+0061 %
+0062 % - uniprotIDs : cell array with Uniprot IDs matching protData.abundances.
+0063 % - abundances : matrix of proteomics data, where each column contains mean abundances per condition.
+0064 %
+0065 % Examples
+0066 % --------
+0067 % % optional arguments may be given positionally or as name-value pairs:
+0068 % protData = loadProtData(replPerCond, protDataFile, filterData, modelAdapter, minVal, maxRSD, maxMissing, cutLowest, addStdevs);
+0069 % protData = loadProtData(replPerCond, 'filterData', false);
+0070 %
+0071 % See also
+0072 % --------
+0073 % loadFluxData
+0074
+0075 p = parseGECKOargs(varargin, { ...
+0076 'protDataFile', []; ...
+0077 'filterData', true; ...
+0078 'modelAdapter', []; ...
+0079 'minVal', 0; ...
+0080 'maxRSD', 1; ...
+0081 'maxMissing', 2/3; ...
+0082 'cutLowest', 5; ...
+0083 'addStdevs', 1});
+0084 protDataFile = p.protDataFile;
+0085 filterData = p.filterData;
+0086 modelAdapter = p.modelAdapter;
+0087 minVal = p.minVal;
+0088 maxRSD = p.maxRSD;
+0089 maxMissing = p.maxMissing;
+0090 cutLowest = p.cutLowest;
+0091 addStdevs = p.addStdevs;
+0092
+0093 if isempty(modelAdapter)
+0094 modelAdapter = ModelAdapterManager.getDefault();
+0095 if isempty(modelAdapter)
+0096 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0097 end
+0098 end
+0099 params = modelAdapter.getParameters();
+0100
+0101 if isempty(protDataFile)
+0102 protDataFile = fullfile(params.path,'data','proteomics.tsv');
+0103 end
+0104
+0105 format = '%s';
+0106 for i=1:sum(replPerCond)
+0107 format = [format ' %f'];
+0108 end
+0109 if ~isstruct(protDataFile)
+0110 checkFileExistence(protDataFile);
+0111 fID = fopen(protDataFile);
+0112 protDataRaw = textscan(fID,format,'Delimiter','\t','HeaderLines',1,'TreatAsEmpty',{'NA','na','NaN','#VALUE!'});
+0113 uniprotIDs = protDataRaw{1};
+0114 abundances = cell2mat(protDataRaw(2:end));
+0115 fclose(fID);
+0116 else
+0117 uniprotIDs = protDataFile.uniprotIDs;
+0118 abundances = protDataFile.abundances;
+0119 end
+0120
+0121 %Remove entriew without ID
+0122 remData = cellfun(@isempty,uniprotIDs);
+0123 uniprotIDs(remData,:) = [];
+0124 abundances(remData,:) = [];
+0125 m = size(abundances,1);
+0126 filtAbund = nan(m,numel(replPerCond));
+0127
+0128 if filterData
+0129 for i=1:numel(replPerCond)
+0130 condAbund = abundances(:,1:replPerCond(i));
+0131 if i<numel(replPerCond)
+0132 abundances = abundances(:,replPerCond(i)+1:end);
+0133 end
+0134 % First filter maxMissing
+0135 if size(condAbund,2) > 1
+0136 if numel(maxMissing)>1
+0137 maxMisRepl = maxMissing(i);
+0138 else
+0139 maxMisRepl = maxMissing;
+0140 end
+0141 remData = sum(condAbund>0,2)<maxMisRepl*size(condAbund,2);
+0142 condAbund(remData,:) = nan;
+0143 end
+0144 % Filter by RSD
+0145 remData = (std(condAbund,0,2,'omitnan')./mean(condAbund,2,'omitnan'))>maxRSD;
+0146 condAbund(remData,:) = nan;
+0147 % Add stdevs
+0148 condAbund = mean(condAbund,2,'omitnan')+(addStdevs*std(condAbund,0,2,'omitnan'));
+0149 % Filter by minVal
+0150 remData = mean(condAbund,2,'omitnan')<minVal;
+0151 condAbund(remData) = nan;
+0152 % Remove the lowest cutLowest percent
+0153 numData = find(~isnan(condAbund));
+0154 [~,sortData] = sort(condAbund);
+0155 lowCutoff = floor(numel(numData)*cutLowest/100);
+0156 condAbund(sortData(1:lowCutoff)) = nan;
+0157 % Combine conditions
+0158 filtAbund(:,i) = condAbund;
+0159 end
+0160 else
+0161 for i=1:numel(replPerCond)
+0162 condAbund = abundances(:,1:replPerCond(i));
+0163 if i<numel(replPerCond)
+0164 abundances = abundances(:,replPerCond(i)+1:end);
+0165 end
+0166 filtAbund(:,i) = mean(condAbund,2,'omitnan');
+0167 end
+0168 end
+0169 notAllNan = logical(sum(~isnan(filtAbund),2));
+0170 protData.abundances = filtAbund(notAllNan,:);
+0171 protData.uniprotIDs = uniprotIDs(notAllNan);
+0172 end
+0173
saveEcModel Save an ecModel in YAML and/or SBML format.
+
+
SYNOPSIS
+
function saveEcModel(ecModel,varargin)
+
+
DESCRIPTION
+
saveEcModel Save an ecModel in YAML and/or SBML format.
+
+ Saves the ecModel in either YAML format (= default and preferred, as all
+ ecModel content is reserved) and/or SBML format (= more widely compatible
+ with other constraint-based modelling tools, can be used for running
+ simulations like FBA etc., but this model cannot be loaded back into
+ MATLAB for applying further GECKO functions, as model.ec is lost).
+
+ A YAML file records its ecModel format (gecko_format, see loadEcModel)
+ and the GECKO release that wrote it (generator) in its metaData, in
+ place of the geckopy_version that earlier geckopy releases wrote.
+
+ The ecModel is saved to the ecModel-specific models/ subfolder. For
+ saving to other locations, writeYAMLmodel or exportModel are more
+ suitable.
+
+ Parameters
+ ----------
+ ecModel : struct
+ an ecModel (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ filename : char
+ ending with either .yml or .xml, specifying if the ecModel should be
+ saved in YAML or SBML file format. If no file extension is given, the
+ ecModel will be saved in YAML format. If no filename is given,
+ 'ecModel.yml' is used.
+ modelAdapter : ModelAdapter
+ a loaded model adapter, from where the model folder is read (default:
+ the current default model adapter).
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ saveEcModel(ecModel,filename,modelAdapter);
+ saveEcModel(ecModel,'filename',filename);
+
+ See also
+ --------
+ loadEcModel
0001 function saveEcModel(ecModel,varargin)
+0002 % saveEcModel Save an ecModel in YAML and/or SBML format.
+0003 %
+0004 % Saves the ecModel in either YAML format (= default and preferred, as all
+0005 % ecModel content is reserved) and/or SBML format (= more widely compatible
+0006 % with other constraint-based modelling tools, can be used for running
+0007 % simulations like FBA etc., but this model cannot be loaded back into
+0008 % MATLAB for applying further GECKO functions, as model.ec is lost).
+0009 %
+0010 % A YAML file records its ecModel format (gecko_format, see loadEcModel)
+0011 % and the GECKO release that wrote it (generator) in its metaData, in
+0012 % place of the geckopy_version that earlier geckopy releases wrote.
+0013 %
+0014 % The ecModel is saved to the ecModel-specific models/ subfolder. For
+0015 % saving to other locations, writeYAMLmodel or exportModel are more
+0016 % suitable.
+0017 %
+0018 % Parameters
+0019 % ----------
+0020 % ecModel : struct
+0021 % an ecModel (with ecModel.ec structure).
+0022 %
+0023 % Name-Value Arguments
+0024 % --------------------
+0025 % filename : char
+0026 % ending with either .yml or .xml, specifying if the ecModel should be
+0027 % saved in YAML or SBML file format. If no file extension is given, the
+0028 % ecModel will be saved in YAML format. If no filename is given,
+0029 % 'ecModel.yml' is used.
+0030 % modelAdapter : ModelAdapter
+0031 % a loaded model adapter, from where the model folder is read (default:
+0032 % the current default model adapter).
+0033 %
+0034 % Examples
+0035 % --------
+0036 % % optional arguments may be given positionally or as name-value pairs:
+0037 % saveEcModel(ecModel,filename,modelAdapter);
+0038 % saveEcModel(ecModel,'filename',filename);
+0039 %
+0040 % See also
+0041 % --------
+0042 % loadEcModel
+0043
+0044 p = parseGECKOargs(varargin, { ...
+0045 'filename', []; ...
+0046 'modelAdapter', []});
+0047 filename = p.filename;
+0048 modelAdapter = p.modelAdapter;
+0049
+0050 if isempty(modelAdapter)
+0051 modelAdapter = ModelAdapterManager.getDefault();
+0052 if isempty(modelAdapter)
+0053 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0054 end
+0055 end
+0056 params = modelAdapter.getParameters();
+0057 if isempty(filename)
+0058 filename = 'ecModel';
+0059 end
+0060 filename = fullfile(params.path,'models',filename);
+0061
+0062 switch filename(end-3:end)
+0063 case {'.xml','sbml'}
+0064 exportModel(ecModel, filename);
+0065 %Convert notation "e-005" to "e-05 " in stoich. coeffs. to avoid
+0066 %inconsistencies between Windows and MAC:
+0067 copyfile(filename,'backup.xml')
+0068 fin = fopen('backup.xml', 'r');
+0069 fout = fopen(filename, 'w');
+0070 still_reading = true;
+0071 while still_reading
+0072 inline = fgets(fin);
+0073 if ~ischar(inline)
+0074 still_reading = false;
+0075 else
+0076 if ~isempty(regexp(inline,'-00[0-9]','once'))
+0077 inline = strrep(inline,'-00','-0');
+0078 elseif ~isempty(regexp(inline,'-01[0-9]','once'))
+0079 inline = strrep(inline,'-01','-1');
+0080 end
+0081 fwrite(fout, inline);
+0082 end
+0083 end
+0084 fclose('all');
+0085 delete('backup.xml');
+0086 otherwise% Assume YAML
+0087 % The ecModel format the file follows (as project_format in
+0088 % model_adapter.toml) and the release that wrote it.
+0089 ecModel.annotation.gecko_format = '4';
+0090 ecModel.annotation.generator = ['GECKO 'geckoVersion()];
+0091 if isfield(ecModel.annotation, 'geckopy_version')
+0092 ecModel.annotation = rmfield(ecModel.annotation, 'geckopy_version');
+0093 end
+0094 writeYAMLmodel(ecModel,filename);
+0095 end
+0096 end
+0097
+0098 function v = geckoVersion()
+0099 file = fullfile(findGECKOroot, 'version.txt');
+0100 if isfile(file)
+0101 v = strtrim(fileread(file));
+0102 else
+0103 v = 'unknown';
+0104 end
+0105 end
bestParsimonious The fewest-changes point whose RMSE is within
+
+
SYNOPSIS
+
function point = bestParsimonious(points,varargin)
+
+
DESCRIPTION
+
bestParsimonious The fewest-changes point whose RMSE is within
+ tolerance of the best on a parsimonyFrontier.
+
+ Defaults to 2%, which is the scale of alternate-optimum noise typically
+ seen from re-solving an LP with a slightly different kcat vector --
+ below that, two vectors are not meaningfully different in fit, so the
+ sparser one wins.
+
+ Parameters
+ ----------
+ points : struct array
+ as returned by parsimonyFrontier.
+
+ Name-Value Arguments
+ --------------------
+ tolerance : double
+ default 0.02.
+
+ Returns
+ -------
+ point : struct
+ the chosen entry of points.
+
+ See also
+ --------
+ parsimonyFrontier
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function point = bestParsimonious(points,varargin)
+0002 % bestParsimonious The fewest-changes point whose RMSE is within
+0003 % tolerance of the best on a parsimonyFrontier.
+0004 %
+0005 % Defaults to 2%, which is the scale of alternate-optimum noise typically
+0006 % seen from re-solving an LP with a slightly different kcat vector --
+0007 % below that, two vectors are not meaningfully different in fit, so the
+0008 % sparser one wins.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % points : struct array
+0013 % as returned by parsimonyFrontier.
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % tolerance : double
+0018 % default 0.02.
+0019 %
+0020 % Returns
+0021 % -------
+0022 % point : struct
+0023 % the chosen entry of points.
+0024 %
+0025 % See also
+0026 % --------
+0027 % parsimonyFrontier
+0028 p = parseGECKOargs(varargin, { ...
+0029 'tolerance', 0.02});
+0030 tolerance = p.tolerance;
+0031
+0032 if isempty(points)
+0033 error('points is empty.')
+0034 end
+0035 rmses = [points.rmse];
+0036 bestRmse = min(rmses);
+0037 within = points(rmses <= bestRmse * (1+tolerance));
+0038 [~,idx] = min([within.nChanged]);
+0039 point = within(idx);
+0040 end
cmaesKcatTuning Tune kcats against experimental data with CMA-ES.
+
+
SYNOPSIS
+
function [ecModel,result] = cmaesKcatTuning(ecModel,varargin)
+
+
DESCRIPTION
+
cmaesKcatTuning Tune kcats against experimental data with CMA-ES.
+
+ Configuration comes entirely from modelAdapter.params.evotune: trust
+ tiers (kcatSources/sigma0logSource/sigma0logDefault), maxGrowthWeight,
+ priorPenaltyWeight and tieIsozymes control the search, and
+ maxGenerations/rmseThreshold are its stopping conditions.
+
+ Vendors a separable (diagonal-covariance) CMA-ES -- MATLAB has no
+ built-in implementation and Global Optimization Toolbox doesn't ship
+ one either. Separable rather than full-covariance because that is what
+ geckopy's own port actually runs (cma's CMA_diagonal=True) and what its
+ defaults were validated against; it is also lower risk to implement
+ correctly. See the "CMA-ES core" local functions below.
+
+ Parameters
+ ----------
+ ecModel : struct
+ ecModel with a populated ec.kcat.
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ evoData : struct
+ as returned by loadEvotuneData (default: loaded from modelAdapter).
+ tunableMask, screen, targetImpactShare
+ control which kcats are searched over, in order of precedence. Pass
+ tunableMask to fix the set yourself. Otherwise, a mask is built by
+ selectTunableMask at targetImpactShare (default 0.9), from screen if
+ given, or from a fresh screenKcatLeverage call otherwise.
+ popsize : double
+ CMA-ES population size. Defaults to 4 + floor(3*log(n)), the
+ standard dimension-scaled default, so it adapts to however many
+ free parameters the mask/tying produced.
+ nProc : double
+ number of parallel workers used to score each generation's
+ candidates (default 1, i.e. serial). Requires Parallel Computing
+ Toolbox for > 1.
+ seed : double
+ if given, seeds the random draws for a reproducible run.
+ verbose : logical
+ whether per-generation progress is printed (default true).
+
+ Returns
+ -------
+ ecModel : struct
+ mutated: tunable rows carry the best kcat vector CMA-ES found, with
+ kcat constraints already applied. MATLAB structs are copied on
+ assignment, so -- unlike geckopy's in-place mutation -- this output
+ must be captured by the caller.
+ result : struct
+ rxns/oldKcat/newKcat/groups cover the selected tunable set, tied
+ groups included (their members share one value in newKcat).
+ rmseTrace/objectiveTrace record the best-so-far plain RMSE and
+ optimised objective per generation. nGenerations is how many
+ generations actually ran. converged is true if
+ rmseTrace(end) <= params.rmseThreshold.
+
+ Raises
+ ------
+ error
+ if there are no tunable kcats, or fewer than two free parameters
+ remain after masking and tying -- too little for CMA-ES to search
+ over.
+
+ See also
+ --------
+ screenKcatLeverage, selectTunableMask, tunePriorPenaltyWeight
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
evotuneScore evotuneScore Score one ecModel's current kcats against evotune data.
loadEvotuneData loadEvotuneData Load experimental data for evotune (CMA-ES) kcat tuning.
resolveTunableContext resolveTunableContext The tunable kcat subset plus everything derived
screenKcatLeverage screenKcatLeverage Report which kcats the data can actually speak to.
selectTunableMask selectTunableMask Build a tunableMask from a screenKcatLeverage report.
evotuneScore Score one ecModel's current kcats against evotune data.
+
+
SYNOPSIS
+
function [objective,rmse,rmseDetail,ecModel] = evotuneScore(ecModel,evoData,varargin)
+
+
DESCRIPTION
+
evotuneScore Score one ecModel's current kcats against evotune data.
+
+ Internal helper used by cmaesKcatTuning, screenKcatLeverage and
+ tunePriorPenaltyWeight to score one ecModel (with a given set of kcats
+ already applied via applyKcatConstraints) against the experimental
+ growth and flux data. Fuses what geckopy splits into simulate.py (the
+ FBA half) and distance.py (the RMSE half) into one function.
+
+ Parameters
+ ----------
+ ecModel : struct
+ ecModel with the current candidate kcats already applied (via
+ applyKcatConstraints). Not mutated.
+ evoData : struct
+ structure with experimental data to be used, as returned by
+ loadEvotuneData.
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ maxGrowthWeight : double
+ relative weight of the max-growth dataset against the flux dataset:
+ (rmseFlux + w*rmseMaxGrate) / (w+1) (default 1.0).
+ priorPenaltyWeight : double
+ weight on a prior term added to the objective (default 0, i.e. the
+ objective equals the plain RMSE). See kcatVec/kcat0/sigma0log below.
+ kcatVec, kcat0, sigma0log : double
+ only used when priorPenaltyWeight > 0: the tunable kcat subset's
+ current values, prior values, and per-kcat prior log-space std dev,
+ all the same length and in the same order. The penalty added to the
+ objective is priorPenaltyWeight * mean((log(kcatVec./kcat0)./sigma0log).^2).
+
+ Returns
+ -------
+ objective : double
+ rmse, plus the prior penalty term when priorPenaltyWeight > 0. This
+ is what a search should minimise.
+ rmse : double
+ the plain (unpenalised) average RMSE, always comparable across runs
+ regardless of priorPenaltyWeight.
+ rmseDetail : cell
+ per-condition RMSE values, labelled by data type and condition index.
+ ecModel : struct
+ the input ecModel, with ec.excarbon cached on it if it wasn't
+ already present. MATLAB structs are copied on assignment, not
+ shared by reference like a Python object, so callers scoring many
+ candidates in a loop should carry this output back in as the base
+ copy for the next candidate, rather than recomputing excarbon (an
+ expensive metabolite-formula scan) on every single call.
+
+ See also
+ --------
+ cmaesKcatTuning, screenKcatLeverage, loadEvotuneData
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
cmaesKcatTuning cmaesKcatTuning Tune kcats against experimental data with CMA-ES.
screenKcatLeverage screenKcatLeverage Report which kcats the data can actually speak to.
0001 function [objective,rmse,rmseDetail,ecModel] = evotuneScore(ecModel,evoData,varargin)
+0002 % evotuneScore Score one ecModel's current kcats against evotune data.
+0003 %
+0004 % Internal helper used by cmaesKcatTuning, screenKcatLeverage and
+0005 % tunePriorPenaltyWeight to score one ecModel (with a given set of kcats
+0006 % already applied via applyKcatConstraints) against the experimental
+0007 % growth and flux data. Fuses what geckopy splits into simulate.py (the
+0008 % FBA half) and distance.py (the RMSE half) into one function.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % ecModel : struct
+0013 % ecModel with the current candidate kcats already applied (via
+0014 % applyKcatConstraints). Not mutated.
+0015 % evoData : struct
+0016 % structure with experimental data to be used, as returned by
+0017 % loadEvotuneData.
+0018 %
+0019 % Name-Value Arguments
+0020 % --------------------
+0021 % modelAdapter : ModelAdapter
+0022 % a loaded model adapter (default: the current default model adapter).
+0023 % maxGrowthWeight : double
+0024 % relative weight of the max-growth dataset against the flux dataset:
+0025 % (rmseFlux + w*rmseMaxGrate) / (w+1) (default 1.0).
+0026 % priorPenaltyWeight : double
+0027 % weight on a prior term added to the objective (default 0, i.e. the
+0028 % objective equals the plain RMSE). See kcatVec/kcat0/sigma0log below.
+0029 % kcatVec, kcat0, sigma0log : double
+0030 % only used when priorPenaltyWeight > 0: the tunable kcat subset's
+0031 % current values, prior values, and per-kcat prior log-space std dev,
+0032 % all the same length and in the same order. The penalty added to the
+0033 % objective is priorPenaltyWeight * mean((log(kcatVec./kcat0)./sigma0log).^2).
+0034 %
+0035 % Returns
+0036 % -------
+0037 % objective : double
+0038 % rmse, plus the prior penalty term when priorPenaltyWeight > 0. This
+0039 % is what a search should minimise.
+0040 % rmse : double
+0041 % the plain (unpenalised) average RMSE, always comparable across runs
+0042 % regardless of priorPenaltyWeight.
+0043 % rmseDetail : cell
+0044 % per-condition RMSE values, labelled by data type and condition index.
+0045 % ecModel : struct
+0046 % the input ecModel, with ec.excarbon cached on it if it wasn't
+0047 % already present. MATLAB structs are copied on assignment, not
+0048 % shared by reference like a Python object, so callers scoring many
+0049 % candidates in a loop should carry this output back in as the base
+0050 % copy for the next candidate, rather than recomputing excarbon (an
+0051 % expensive metabolite-formula scan) on every single call.
+0052 %
+0053 % See also
+0054 % --------
+0055 % cmaesKcatTuning, screenKcatLeverage, loadEvotuneData
+0056
+0057 p = parseGECKOargs(varargin, { ...
+0058 'modelAdapter', []; ...
+0059 'maxGrowthWeight', 1.0; ...
+0060 'priorPenaltyWeight', 0.0; ...
+0061 'kcatVec', []; ...
+0062 'kcat0', []; ...
+0063 'sigma0log', []});
+0064 modelAdapter = p.modelAdapter;
+0065 maxGrowthWeight = p.maxGrowthWeight;
+0066 priorPenaltyWeight = p.priorPenaltyWeight;
+0067
+0068 if isempty(modelAdapter)
+0069 modelAdapter = ModelAdapterManager.getDefault();
+0070 if isempty(modelAdapter)
+0071 error('Either send in a modelAdapter or set the default ecModel adapter in the ModelAdapterManager.')
+0072 end
+0073 end
+0074
+0075 if ~isfield(ecModel,'excarbon')
+0076 ecModel = fillCarbonNum(ecModel);
+0077 ecModel.excarbon(ecModel.excarbon == 0) = 1;
+0078 end
+0079
+0080 rmse_1 = []; rmseDetail1 = []; rmse_2 = []; rmseDetail2 = [];
+0081
+0082 if ~isempty(evoData.fluxData)
+0083 [rmse_1, rmseDetail1] = simulateCondition(ecModel,evoData.fluxData,true,evoData.zeroFlux,modelAdapter);
+0084 rmseDetail1 = [cellstr("fluxData_" + string(1:numel(rmseDetail1)))',num2cell(rmseDetail1)];
+0085 end
+0086 if ~isempty(evoData.maxGrate)
+0087 [rmse_2, rmseDetail2] = simulateCondition(ecModel,evoData.maxGrate,false,evoData.zeroFlux,modelAdapter);
+0088 rmseDetail2 = [cellstr("maxGrowth_" + string(1:numel(rmseDetail2)))',num2cell(rmseDetail2)];
+0089 end
+0090
+0091 parts = []; weights = [];
+0092 if ~isempty(rmse_1)
+0093 parts(end+1) = rmse_1; weights(end+1) = 1.0;
+0094 end
+0095 if ~isempty(rmse_2)
+0096 parts(end+1) = rmse_2; weights(end+1) = maxGrowthWeight;
+0097 end
+0098 if isempty(parts)
+0099 rmse = NaN;
+0100 else
+0101 rmse = sum(parts .* weights) / sum(weights);
+0102 end
+0103 rmseDetail = [rmseDetail1;rmseDetail2];
+0104
+0105 objective = rmse;
+0106 if priorPenaltyWeight > 0 && ~isempty(p.kcatVec) && ~isempty(p.kcat0) && ~isempty(p.sigma0log)
+0107 dev = log(p.kcatVec(:) ./ p.kcat0(:)) ./ p.sigma0log(:);
+0108 objective = rmse + priorPenaltyWeight * mean(dev.^2);
+0109 end
+0110 end
+0111
+0112 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+0113 function [rmse, rmseList] = simulateCondition(ecModel,data,constrain,rxn2block,modelAdapter)
+0114 % One condition's FBA simulation + carbon-weighted RMSE against measured
+0115 % data.
+0116
+0117 rmseList = zeros(length(data.conds),1);
+0118
+0119 [~,exchIdx] = ismember(data.conds,data.exchMets);
+0120 if any(exchIdx == 0)
+0121 missingMets = strjoin(unique(data.conds(exchIdx == 0)), '; ');
+0122 error('Carbon source(s) "%s" in the provided fluxData or maxGrowth cannot be matched by name with an exchange reaction.', missingMets);
+0123 end
+0124
+0125 for i = 1:length(data.conds)
+0126 % Set all other carbon sources to zero, then unblock this condition's
+0127 model_tmp = setParam(ecModel,'lb',data.exchRxnIDs(unique(exchIdx)),0);
+0128 if constrain % RMSE from flux data: constrain carbon uptake at the measured rate
+0129 model_tmp = setParam(model_tmp,'lb',data.exchRxnIDs(exchIdx(i)),data.exchFluxes(i,exchIdx(i)));
+0130 else% RMSE from max growth: leave carbon uptake fully open
+0131 model_tmp = setParam(model_tmp,'lb',data.exchRxnIDs(exchIdx(i)),-1000);
+0132 end
+0133
+0134 % This row's own carbon source is never itself asserted zero-flux.
+0135 rxn2blockIter = rxn2block;
+0136 rxn2blockIter(strcmp(rxn2blockIter,data.exchRxnIDs(exchIdx(i)))) = [];
+0137
+0138 o2Flux = data.exchFluxes(i,strcmp('oxygen',data.exchMets));
+0139 if o2Flux == 0
+0140 model_tmp = modelAdapter.makeModelAnaerobic(model_tmp);
+0141 end
+0142 if ~(isnan(data.Ptot(i)) || data.Ptot(i) == 0)
+0143 model_tmp = modelAdapter.changeProteinBiomass(model_tmp,data.Ptot(i));
+0144 end
+0145
+0146 sol = solveLP(model_tmp);
+0147 if checkSolution(sol)
+0148 bioRxn = getIndexes(model_tmp,data.biomass,'rxns');
+0149 % Biomass carries an assumed 41 Cmmol/gDCW, matching fillCarbonNum.
+0150 bioMeas = data.grRate(i) * 41;
+0151 bioSim = sol.x(bioRxn) * 41;
+0152
+0153 if constrain
+0154 fluxToCheck = ~isnan(data.exchFluxes(i,:));
+0155 fluxRxnIdx = getIndexes(model_tmp,data.exchRxnIDs(fluxToCheck),'rxns');
+0156 cNormMeasFlux = model_tmp.excarbon(fluxRxnIdx) .* transpose(data.exchFluxes(i,fluxToCheck));
+0157 cNormSimFlux = model_tmp.excarbon(fluxRxnIdx) .* sol.x(fluxRxnIdx);
+0158
+0159 blockRxnIdx = getIndexes(model_tmp,rxn2blockIter,'rxns');
+0160 blockSim = sol.x(blockRxnIdx) .* model_tmp.excarbon(blockRxnIdx);
+0161 % Already-correct zero predictions don't dilute the RMSE.
+0162 blockSim(blockSim == 0) = [];
+0163 blockMeas = zeros(numel(blockSim),1);
+0164
+0165 measured = [cNormMeasFlux; bioMeas; blockMeas];
+0166 simulated = [cNormSimFlux; bioSim; blockSim];
+0167 else
+0168 measured = bioMeas;
+0169 simulated = bioSim;
+0170 end
+0171 rmseList(i) = sqrt(mean((measured - simulated).^2));
+0172 else
+0173 rmseList(i) = NaN;
+0174 end
+0175 end
+0176 rmseList(isnan(rmseList)) = 99; % Fixed penalty for an infeasible condition.
+0177 if isfield(data,'evotuneRMSEweight') && ~isempty(data.evotuneRMSEweight)
+0178 rmseList = rmseList .* data.evotuneRMSEweight;
+0179 end
+0180 rmse = mean(rmseList);
+0181 end
isozymeTieMap Index of the parameter each isozyme copy follows.
+
+
SYNOPSIS
+
function tieMap = isozymeTieMap(rxnIds,kcat0,varargin)
+
+
DESCRIPTION
+
isozymeTieMap Index of the parameter each isozyme copy follows.
+
+ A reaction catalysed by several isozymes gets one ec.rxns row per
+ isozyme, each with its own kcat. Where the kcat assignment could tell
+ them apart, those are genuinely different parameters; where it could
+ not, every copy shares the same value from the same source, and an
+ untied search is then free to invent a difference between them that
+ means nothing biologically. Copies group together (and share one
+ parameter) when they share a base reaction (the part of the rxn id
+ before its "_EXP_" isozyme suffix), a prior value, and -- when sources
+ is given -- a source.
+
+ Parameters
+ ----------
+ rxnIds : cell
+ ec.rxns entries.
+ kcat0 : double
+ prior kcat per row, same length as rxnIds.
+
+ Name-Value Arguments
+ --------------------
+ sources : cell
+ ec.source entries, same length as rxnIds (default: not used, so
+ grouping ignores source).
+ relTol : double
+ relative tolerance for treating two prior values as equal (default
+ 1e-9).
+
+ Returns
+ -------
+ tieMap : double
+ one entry per position. tieMap(i) == i for a free parameter,
+ otherwise the (lower) index of the representative it follows.
+
+ See also
+ --------
+ cmaesKcatTuning, screenKcatLeverage
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
resolveTunableContext resolveTunableContext The tunable kcat subset plus everything derived
0001 function tieMap = isozymeTieMap(rxnIds,kcat0,varargin)
+0002 % isozymeTieMap Index of the parameter each isozyme copy follows.
+0003 %
+0004 % A reaction catalysed by several isozymes gets one ec.rxns row per
+0005 % isozyme, each with its own kcat. Where the kcat assignment could tell
+0006 % them apart, those are genuinely different parameters; where it could
+0007 % not, every copy shares the same value from the same source, and an
+0008 % untied search is then free to invent a difference between them that
+0009 % means nothing biologically. Copies group together (and share one
+0010 % parameter) when they share a base reaction (the part of the rxn id
+0011 % before its "_EXP_" isozyme suffix), a prior value, and -- when sources
+0012 % is given -- a source.
+0013 %
+0014 % Parameters
+0015 % ----------
+0016 % rxnIds : cell
+0017 % ec.rxns entries.
+0018 % kcat0 : double
+0019 % prior kcat per row, same length as rxnIds.
+0020 %
+0021 % Name-Value Arguments
+0022 % --------------------
+0023 % sources : cell
+0024 % ec.source entries, same length as rxnIds (default: not used, so
+0025 % grouping ignores source).
+0026 % relTol : double
+0027 % relative tolerance for treating two prior values as equal (default
+0028 % 1e-9).
+0029 %
+0030 % Returns
+0031 % -------
+0032 % tieMap : double
+0033 % one entry per position. tieMap(i) == i for a free parameter,
+0034 % otherwise the (lower) index of the representative it follows.
+0035 %
+0036 % See also
+0037 % --------
+0038 % cmaesKcatTuning, screenKcatLeverage
+0039 p = parseGECKOargs(varargin, { ...
+0040 'sources', []; ...
+0041 'relTol', 1e-9});
+0042 sources = p.sources;
+0043 relTol = p.relTol;
+0044
+0045 n = numel(kcat0);
+0046 if numel(rxnIds) ~= n
+0047 error('rxnIds has %d entries; kcat0 has %d.', numel(rxnIds), n)
+0048 end
+0049 tieMap = (1:n)';
+0050
+0051 keys = cell(n,1);
+0052 for i = 1:n
+0053 base = baseReaction(rxnIds{i});
+0054 if kcat0(i) > 0
+0055 logKey = sprintf('%d', round(log(kcat0(i)) / relTol));
+0056 else
+0057 logKey = 'NONE';
+0058 end
+0059 if isempty(sources)
+0060 srcKey = '';
+0061 else
+0062 srcKey = char(sources{i});
+0063 end
+0064 keys{i} = strjoin({base, logKey, srcKey}, char(31));
+0065 end
+0066
+0067 [~, ~, groupIdx] = unique(keys);
+0068 for g = 1:max(groupIdx)
+0069 members = find(groupIdx == g);
+0070 if numel(members) > 1
+0071 tieMap(members) = members(1);
+0072 end
+0073 end
+0074 end
+0075
+0076 function base = baseReaction(rxnId)
+0077 % The reaction an ec.rxns entry belongs to, isozyme suffix removed.
+0078 parts = strsplit(rxnId, '_EXP_');
+0079 base = parts{1};
+0080 end
loadEvotuneData Load experimental data for evotune (CMA-ES) kcat tuning.
+
+
SYNOPSIS
+
function evoData = loadEvotuneData(varargin)
+
+
DESCRIPTION
+
loadEvotuneData Load experimental data for evotune (CMA-ES) kcat tuning.
+
+ Reads the three experimental data files used by cmaesKcatTuning from the
+ data subfolder of the model directory defined by the model adapter. Each
+ file is optional: a missing file yields [] (fluxData/maxGrate) or an
+ empty cell array (zeroFlux) rather than an error.
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ evoData : struct
+ structure with the loaded experimental data.
+
+ Notes
+ -----
+ The evoData structure has the following fields:
+
+ - fluxData : flux data loaded from evotuneFluxData.tsv, with the biomass
+ reaction id stored in fluxData.biomass. [] if that file isn't present.
+ - maxGrate : maximum growth-rate data loaded from evotuneMaxGrowth.tsv,
+ with the biomass reaction id stored in maxGrate.biomass. [] if that
+ file isn't present.
+ - zeroFlux : reaction IDs assumed to carry zero flux, loaded from
+ evotuneZeroExch.tsv. Empty cell array if that file isn't present.
+
+ See also
+ --------
+ cmaesKcatTuning, evotuneScore
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
cmaesKcatTuning cmaesKcatTuning Tune kcats against experimental data with CMA-ES.
screenKcatLeverage screenKcatLeverage Report which kcats the data can actually speak to.
0001 function evoData = loadEvotuneData(varargin)
+0002 % loadEvotuneData Load experimental data for evotune (CMA-ES) kcat tuning.
+0003 %
+0004 % Reads the three experimental data files used by cmaesKcatTuning from the
+0005 % data subfolder of the model directory defined by the model adapter. Each
+0006 % file is optional: a missing file yields [] (fluxData/maxGrate) or an
+0007 % empty cell array (zeroFlux) rather than an error.
+0008 %
+0009 % Name-Value Arguments
+0010 % --------------------
+0011 % modelAdapter : ModelAdapter
+0012 % a loaded model adapter (default: the current default model adapter).
+0013 %
+0014 % Returns
+0015 % -------
+0016 % evoData : struct
+0017 % structure with the loaded experimental data.
+0018 %
+0019 % Notes
+0020 % -----
+0021 % The evoData structure has the following fields:
+0022 %
+0023 % - fluxData : flux data loaded from evotuneFluxData.tsv, with the biomass
+0024 % reaction id stored in fluxData.biomass. [] if that file isn't present.
+0025 % - maxGrate : maximum growth-rate data loaded from evotuneMaxGrowth.tsv,
+0026 % with the biomass reaction id stored in maxGrate.biomass. [] if that
+0027 % file isn't present.
+0028 % - zeroFlux : reaction IDs assumed to carry zero flux, loaded from
+0029 % evotuneZeroExch.tsv. Empty cell array if that file isn't present.
+0030 %
+0031 % See also
+0032 % --------
+0033 % cmaesKcatTuning, evotuneScore
+0034 p = parseGECKOargs(varargin, { ...
+0035 'modelAdapter', []});
+0036 modelAdapter = p.modelAdapter;
+0037
+0038 if isempty(modelAdapter)
+0039 modelAdapter = ModelAdapterManager.getDefault();
+0040 if isempty(modelAdapter)
+0041 error('Either send in a modelAdapter or set the default ecModel adapter in the ModelAdapterManager.')
+0042 end
+0043 end
+0044
+0045 basePath = modelAdapter.params.path;
+0046
+0047 evoData.fluxData = loadFluxData(fullfile(basePath,'data','evotuneFluxData.tsv'), modelAdapter);
+0048 if ~isempty(evoData.fluxData)
+0049 evoData.fluxData.biomass = modelAdapter.params.bioRxn;
+0050 end
+0051
+0052 evoData.maxGrate = loadFluxData(fullfile(basePath,'data','evotuneMaxGrowth.tsv'), modelAdapter);
+0053 if ~isempty(evoData.maxGrate)
+0054 evoData.maxGrate.biomass = modelAdapter.params.bioRxn;
+0055 end
+0056
+0057 % Unlike loadFluxData (used for fluxData/maxGrate above, which already
+0058 % degrades to [] on a missing file), readtable has no such guard, so this
+0059 % is wrapped explicitly to keep all three files uniformly optional.
+0060 zeroExchFile = fullfile(basePath,'data','evotuneZeroExch.tsv');
+0061 if isfile(zeroExchFile)
+0062 evoData.zeroFlux = table2cell(readtable(zeroExchFile, 'Delimiter', '\t', 'FileType','delimitedtext'));
+0063 else
+0064 evoData.zeroFlux = {};
+0065 end
+0066 end
nChanged How many kcats differ from their prior by more than relTol.
+
+
SYNOPSIS
+
function n = nChanged(kcat,kcat0,varargin)
+
+
DESCRIPTION
+
nChanged How many kcats differ from their prior by more than relTol.
+
+ The 2% default matches the threshold MATLAB's own per-source
+ "unchanged" reporting has historically used.
+
+ Parameters
+ ----------
+ kcat, kcat0 : double
+ tuned and prior kcat vectors, same length.
+
+ Name-Value Arguments
+ --------------------
+ relTol : double
+ default 0.02.
+
+ Returns
+ -------
+ n : double
+ count of entries where abs(log(kcat./kcat0)) > log1p(relTol).
+
+ See also
+ --------
+ tunePriorPenaltyWeight, parsimonyFrontier
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
parsimonyFrontier parsimonyFrontier Score kcat with progressively more of it reverted to
parsimonyFrontier Score kcat with progressively more of it reverted to
+
+
SYNOPSIS
+
function points = parsimonyFrontier(scoreFcn,kcat,kcat0,sigma0log,varargin)
+
+
DESCRIPTION
+
parsimonyFrontier Score kcat with progressively more of it reverted to
+ prior.
+
+ A tuned kcat vector that fits well by rewriting every parameter is not
+ a useful answer: changes should be few, and concentrated in the
+ parameters least confident about. This sweeps a revert-threshold (in
+ units of each kcat's own prior sigma) and reports how RMSE trades off
+ against how many kcats stayed at their tuned value, so a sparser
+ result within noise of the best fit can be preferred over the raw
+ tuner output. See bestParsimonious to pick a point off this frontier.
+
+ Parameters
+ ----------
+ scoreFcn : function_handle
+ scores one kcat vector; called once per threshold. Build it against
+ a specific ecModel/evoData, e.g.
+ scoreFcn = @(vec) evotuneScoreVector(ecModel,tunableIdx, ...
+ ecRxnIdsTunable,evoData,modelAdapter,params,vec);
+ where evotuneScoreVector applies vec to ecModel.ec.kcat(tunableIdx),
+ calls applyKcatConstraints, and returns evotuneScore's plain rmse.
+ kcat, kcat0, sigma0log : double
+ tuned vector, prior vector, and per-kcat prior sigma, all the same
+ length.
+
+ Name-Value Arguments
+ --------------------
+ thresholds : double
+ sigma cutoffs to sweep, ascending (default
+ [0 1 2 3 4 6 8 10 15]). 0 reverts nothing, reproducing kcat's own
+ score.
+
+ Returns
+ -------
+ points : struct array
+ one entry per threshold, in the order given: threshold, kcat (the
+ partially-reverted vector), nChanged, meanDev (mean movement in
+ sigma units), rmse.
+
+ See also
+ --------
+ bestParsimonious, nChanged
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
nChanged nChanged How many kcats differ from their prior by more than relTol.
0001 function points = parsimonyFrontier(scoreFcn,kcat,kcat0,sigma0log,varargin)
+0002 % parsimonyFrontier Score kcat with progressively more of it reverted to
+0003 % prior.
+0004 %
+0005 % A tuned kcat vector that fits well by rewriting every parameter is not
+0006 % a useful answer: changes should be few, and concentrated in the
+0007 % parameters least confident about. This sweeps a revert-threshold (in
+0008 % units of each kcat's own prior sigma) and reports how RMSE trades off
+0009 % against how many kcats stayed at their tuned value, so a sparser
+0010 % result within noise of the best fit can be preferred over the raw
+0011 % tuner output. See bestParsimonious to pick a point off this frontier.
+0012 %
+0013 % Parameters
+0014 % ----------
+0015 % scoreFcn : function_handle
+0016 % scores one kcat vector; called once per threshold. Build it against
+0017 % a specific ecModel/evoData, e.g.
+0018 % scoreFcn = @(vec) evotuneScoreVector(ecModel,tunableIdx, ...
+0019 % ecRxnIdsTunable,evoData,modelAdapter,params,vec);
+0020 % where evotuneScoreVector applies vec to ecModel.ec.kcat(tunableIdx),
+0021 % calls applyKcatConstraints, and returns evotuneScore's plain rmse.
+0022 % kcat, kcat0, sigma0log : double
+0023 % tuned vector, prior vector, and per-kcat prior sigma, all the same
+0024 % length.
+0025 %
+0026 % Name-Value Arguments
+0027 % --------------------
+0028 % thresholds : double
+0029 % sigma cutoffs to sweep, ascending (default
+0030 % [0 1 2 3 4 6 8 10 15]). 0 reverts nothing, reproducing kcat's own
+0031 % score.
+0032 %
+0033 % Returns
+0034 % -------
+0035 % points : struct array
+0036 % one entry per threshold, in the order given: threshold, kcat (the
+0037 % partially-reverted vector), nChanged, meanDev (mean movement in
+0038 % sigma units), rmse.
+0039 %
+0040 % See also
+0041 % --------
+0042 % bestParsimonious, nChanged
+0043 p = parseGECKOargs(varargin, { ...
+0044 'thresholds', [0 1 2 3 4 6 8 10 15]});
+0045 thresholds = p.thresholds;
+0046
+0047 kcat0 = kcat0(:);
+0048 sigma0log = sigma0log(:);
+0049 kcat = kcat(:);
+0050
+0051 points = repmat(struct('threshold',[],'kcat',[],'nChanged',[],'meanDev',[],'rmse',[]), ...
+0052 numel(thresholds), 1);
+0053 for t = 1:numel(thresholds)
+0054 thr = thresholds(t);
+0055 vec = revertBelow(kcat,kcat0,sigma0log,thr);
+0056 points(t).threshold = thr;
+0057 points(t).kcat = vec;
+0058 points(t).nChanged = nChanged(vec,kcat0);
+0059 points(t).meanDev = mean(movementInSigma(vec,kcat0,sigma0log));
+0060 points(t).rmse = scoreFcn(vec);
+0061 end
+0062 end
+0063
+0064 function dev = movementInSigma(kcat,kcat0,sigma0log)
+0065 % Per-kcat displacement from the prior, in prior standard deviations.
+0066 dev = abs(log(kcat ./ kcat0)) ./ sigma0log;
+0067 end
+0068
+0069 function vec = revertBelow(kcat,kcat0,sigma0log,threshold)
+0070 % Put every kcat that moved less than threshold sigma back on its prior.
+0071 keep = movementInSigma(kcat,kcat0,sigma0log) >= threshold;
+0072 vec = kcat0;
+0073 vec(keep) = kcat(keep);
+0074 end
resolveTunableContext The tunable kcat subset plus everything derived
+
+
SYNOPSIS
+
function [tunableIdx,ecRxnIdsTunable,kcat0,groups,sigma0log,tieMap] = resolveTunableContext(ecModel,params,tunableMask)
+
+
DESCRIPTION
+
resolveTunableContext The tunable kcat subset plus everything derived
+ from it: source-group trust tiers and isozyme tie groups.
+
+ Shared by screenKcatLeverage and cmaesKcatTuning so a screen and the
+ tuning run it feeds are always looking at exactly the same parameters.
+
+ Parameters
+ ----------
+ ecModel : struct
+ ecModel with a populated ec.kcat.
+ params : struct
+ modelAdapter.params.evotune (kcatSources, sigma0logSource,
+ sigma0logDefault, tieIsozymes).
+ tunableMask : logical
+ same length as ecModel.ec.rxns; [] to consider every kcat > 0.
+
+ Returns
+ -------
+ tunableIdx : double
+ indices into ecModel.ec.rxns/ec.kcat of the tunable subset.
+ ecRxnIdsTunable : cell
+ kcat0 : double
+ groups : cell
+ source-group name per tunable row ('unlabelled' if unmatched).
+ sigma0log : double
+ per-row prior std dev in log-space.
+ tieMap : double
+ isozymeTieMap(ecRxnIdsTunable,kcat0,'sources',groups) if
+ params.tieIsozymes, otherwise every row is its own free parameter.
+
+ See also
+ --------
+ screenKcatLeverage, cmaesKcatTuning, isozymeTieMap
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
isozymeTieMap isozymeTieMap Index of the parameter each isozyme copy follows.
+This function is called by:
+
+
cmaesKcatTuning cmaesKcatTuning Tune kcats against experimental data with CMA-ES.
screenKcatLeverage screenKcatLeverage Report which kcats the data can actually speak to.
+
+
+
+
+
SOURCE CODE
+
0001 function [tunableIdx,ecRxnIdsTunable,kcat0,groups,sigma0log,tieMap] = resolveTunableContext(ecModel,params,tunableMask)
+0002 % resolveTunableContext The tunable kcat subset plus everything derived
+0003 % from it: source-group trust tiers and isozyme tie groups.
+0004 %
+0005 % Shared by screenKcatLeverage and cmaesKcatTuning so a screen and the
+0006 % tuning run it feeds are always looking at exactly the same parameters.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % ecModel : struct
+0011 % ecModel with a populated ec.kcat.
+0012 % params : struct
+0013 % modelAdapter.params.evotune (kcatSources, sigma0logSource,
+0014 % sigma0logDefault, tieIsozymes).
+0015 % tunableMask : logical
+0016 % same length as ecModel.ec.rxns; [] to consider every kcat > 0.
+0017 %
+0018 % Returns
+0019 % -------
+0020 % tunableIdx : double
+0021 % indices into ecModel.ec.rxns/ec.kcat of the tunable subset.
+0022 % ecRxnIdsTunable : cell
+0023 % kcat0 : double
+0024 % groups : cell
+0025 % source-group name per tunable row ('unlabelled' if unmatched).
+0026 % sigma0log : double
+0027 % per-row prior std dev in log-space.
+0028 % tieMap : double
+0029 % isozymeTieMap(ecRxnIdsTunable,kcat0,'sources',groups) if
+0030 % params.tieIsozymes, otherwise every row is its own free parameter.
+0031 %
+0032 % See also
+0033 % --------
+0034 % screenKcatLeverage, cmaesKcatTuning, isozymeTieMap
+0035 isTunable = ecModel.ec.kcat > 0;
+0036 if ~isempty(tunableMask)
+0037 isTunable = isTunable & tunableMask(:);
+0038 end
+0039 tunableIdx = find(isTunable);
+0040 if isempty(tunableIdx)
+0041 if isempty(tunableMask)
+0042 error('No tunable kcats: ecModel.ec.kcat is all <= 0.')
+0043 else
+0044 error('No tunable kcats: ecModel.ec.kcat is all <= 0, or tunableMask excludes every one with a kcat.')
+0045 end
+0046 end
+0047
+0048 ecRxnIdsTunable = ecModel.ec.rxns(tunableIdx);
+0049 kcat0 = ecModel.ec.kcat(tunableIdx);
+0050 sources = ecModel.ec.source(tunableIdx);
+0051
+0052 groups = repmat({'unlabelled'}, numel(tunableIdx), 1);
+0053 sigma0log = params.sigma0logDefault * ones(numel(tunableIdx),1);
+0054 for i = 1:numel(params.kcatSources)
+0055 idx = strcmpi(sources, params.kcatSources{i});
+0056 groups(idx) = params.kcatSources(i);
+0057 sigma0log(idx) = params.sigma0logSource(i);
+0058 end
+0059
+0060 if isfield(params,'tieIsozymes') && params.tieIsozymes
+0061 tieMap = isozymeTieMap(ecRxnIdsTunable, kcat0, 'sources', groups);
+0062 else
+0063 tieMap = (1:numel(kcat0))';
+0064 end
+0065 end
screenKcatLeverage Report which kcats the data can actually speak to.
+
+
SYNOPSIS
+
function screen = screenKcatLeverage(ecModel,varargin)
+
+
DESCRIPTION
+
screenKcatLeverage Report which kcats the data can actually speak to.
+
+ No optimisation happens here -- this only says which parameters are
+ worth curating or tuning, and can be read on its own well before any
+ tuning run. Each tie-group (isozyme copies sharing a prior and a source
+ move together when params.tieIsozymes) is perturbed up and down by
+ fold, and its leverage is the largest resulting change in RMSE. Groups
+ are ranked by that leverage weighted by sigma0log: a trusted source
+ needs proportionally more measured effect to rank alongside an
+ untrusted one.
+
+ Costs one simulation per condition per tie-group probed (two probes
+ each, up and down) plus one baseline -- comparable in scale to a full
+ tuning run's evaluation budget, not a cheap pre-check.
+
+ Parameters
+ ----------
+ ecModel : struct
+ ecModel with a populated ec.kcat.
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ evoData : struct
+ as returned by loadEvotuneData (default: loaded from modelAdapter).
+ tunableMask : logical
+ restrict which kcats are screened (default: every ec.kcat > 0).
+ fold : double
+ fold-change used to perturb each tie-group up and down (default 2.0).
+ nProc : double
+ number of parallel workers used to score candidate vectors (default
+ 1, i.e. serial). Requires Parallel Computing Toolbox for > 1.
+
+ Returns
+ -------
+ screen : table
+ one row per tie-group, sorted by rank descending: rxnId (the
+ group's representative), nIsozymes, sourceGroup, kcat0, leverage,
+ sigma0log, rank (leverage*sigma0log), cumLeverageShare (running
+ total of rank as a fraction of its sum), and positions (a cell
+ array of indices into ecModel.ec.rxns) -- what selectTunableMask
+ needs to expand a row back into a mask.
+
+ See also
+ --------
+ selectTunableMask, cmaesKcatTuning
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
evotuneScore evotuneScore Score one ecModel's current kcats against evotune data.
loadEvotuneData loadEvotuneData Load experimental data for evotune (CMA-ES) kcat tuning.
resolveTunableContext resolveTunableContext The tunable kcat subset plus everything derived
+This function is called by:
+
+
cmaesKcatTuning cmaesKcatTuning Tune kcats against experimental data with CMA-ES.
0001 function screen = screenKcatLeverage(ecModel,varargin)
+0002 % screenKcatLeverage Report which kcats the data can actually speak to.
+0003 %
+0004 % No optimisation happens here -- this only says which parameters are
+0005 % worth curating or tuning, and can be read on its own well before any
+0006 % tuning run. Each tie-group (isozyme copies sharing a prior and a source
+0007 % move together when params.tieIsozymes) is perturbed up and down by
+0008 % fold, and its leverage is the largest resulting change in RMSE. Groups
+0009 % are ranked by that leverage weighted by sigma0log: a trusted source
+0010 % needs proportionally more measured effect to rank alongside an
+0011 % untrusted one.
+0012 %
+0013 % Costs one simulation per condition per tie-group probed (two probes
+0014 % each, up and down) plus one baseline -- comparable in scale to a full
+0015 % tuning run's evaluation budget, not a cheap pre-check.
+0016 %
+0017 % Parameters
+0018 % ----------
+0019 % ecModel : struct
+0020 % ecModel with a populated ec.kcat.
+0021 %
+0022 % Name-Value Arguments
+0023 % --------------------
+0024 % modelAdapter : ModelAdapter
+0025 % a loaded model adapter (default: the current default model adapter).
+0026 % evoData : struct
+0027 % as returned by loadEvotuneData (default: loaded from modelAdapter).
+0028 % tunableMask : logical
+0029 % restrict which kcats are screened (default: every ec.kcat > 0).
+0030 % fold : double
+0031 % fold-change used to perturb each tie-group up and down (default 2.0).
+0032 % nProc : double
+0033 % number of parallel workers used to score candidate vectors (default
+0034 % 1, i.e. serial). Requires Parallel Computing Toolbox for > 1.
+0035 %
+0036 % Returns
+0037 % -------
+0038 % screen : table
+0039 % one row per tie-group, sorted by rank descending: rxnId (the
+0040 % group's representative), nIsozymes, sourceGroup, kcat0, leverage,
+0041 % sigma0log, rank (leverage*sigma0log), cumLeverageShare (running
+0042 % total of rank as a fraction of its sum), and positions (a cell
+0043 % array of indices into ecModel.ec.rxns) -- what selectTunableMask
+0044 % needs to expand a row back into a mask.
+0045 %
+0046 % See also
+0047 % --------
+0048 % selectTunableMask, cmaesKcatTuning
+0049 p = parseGECKOargs(varargin, { ...
+0050 'modelAdapter', []; ...
+0051 'evoData', []; ...
+0052 'tunableMask', []; ...
+0053 'fold', 2.0; ...
+0054 'nProc', 1});
+0055 modelAdapter = p.modelAdapter;
+0056 evoData = p.evoData;
+0057 tunableMask = p.tunableMask;
+0058 fold = p.fold;
+0059 nProc = max(1, round(p.nProc));
+0060
+0061 if isempty(modelAdapter)
+0062 modelAdapter = ModelAdapterManager.getDefault();
+0063 if isempty(modelAdapter)
+0064 error('Either send in a modelAdapter or set the default ecModel adapter in the ModelAdapterManager.')
+0065 end
+0066 end
+0067 params = modelAdapter.params.evotune;
+0068 if isempty(evoData)
+0069 evoData = loadEvotuneData('modelAdapter', modelAdapter);
+0070 end
+0071
+0072 [tunableIdx,ecRxnIdsTunable,kcat0,groups,sigma0log,tieMap] = ...
+0073 resolveTunableContext(ecModel,params,tunableMask);
+0074
+0075 reps = find(tieMap == (1:numel(tieMap))');
+0076 nReps = numel(reps);
+0077 membersOf = cell(nReps,1);
+0078 for r = 1:nReps
+0079 membersOf{r} = find(tieMap == reps(r));
+0080 end
+0081
+0082 vectors = cell(1 + 2*nReps, 1);
+0083 vectors{1} = kcat0;
+0084 for r = 1:nReps
+0085 members = membersOf{r};
+0086 up = kcat0; up(members) = up(members) * fold;
+0087 dn = kcat0; dn(members) = dn(members) / fold;
+0088 vectors{1 + 2*r - 1} = up;
+0089 vectors{1 + 2*r} = dn;
+0090 end
+0091 nVec = numel(vectors);
+0092 rmses = zeros(nVec,1);
+0093
+0094 if nProc == 1
+0095 % Serial path: carry the excarbon-augmented model from one call into
+0096 % the next, so the metabolite-formula scan behind it (evotuneScore's
+0097 % 4th output) runs once for the whole screen rather than once per
+0098 % probe vector.
+0099 baseModel = ecModel;
+0100 for v = 1:nVec
+0101 [~,rmses(v),~,baseModel] = scoreVector(baseModel,tunableIdx,ecRxnIdsTunable,evoData,modelAdapter,params,vectors{v});
+0102 end
+0103 else
+0104 parfor v = 1:nVec
+0105 [~,rmses(v)] = scoreVector(ecModel,tunableIdx,ecRxnIdsTunable,evoData,modelAdapter,params,vectors{v});
+0106 end
+0107 end
+0108
+0109 baseRmse = rmses(1);
+0110 rxnId = cell(nReps,1);
+0111 nIsozymes = zeros(nReps,1);
+0112 sourceGroup = cell(nReps,1);
+0113 kcat0Col = zeros(nReps,1);
+0114 leverage = zeros(nReps,1);
+0115 sigma0logCol= zeros(nReps,1);
+0116 positions = cell(nReps,1);
+0117 for r = 1:nReps
+0118 upRmse = rmses(1 + 2*r - 1);
+0119 dnRmse = rmses(1 + 2*r);
+0120 members = membersOf{r};
+0121 rxnId{r} = ecRxnIdsTunable{reps(r)};
+0122 nIsozymes(r) = numel(members);
+0123 sourceGroup{r} = groups{reps(r)};
+0124 kcat0Col(r) = kcat0(reps(r));
+0125 leverage(r) = max(abs(upRmse - baseRmse), abs(dnRmse - baseRmse));
+0126 sigma0logCol(r) = sigma0log(reps(r));
+0127 positions{r} = tunableIdx(members);
+0128 end
+0129
+0130 rnk = leverage .* sigma0logCol;
+0131 [~,ord] = sort(rnk,'descend');
+0132 rxnId=rxnId(ord); nIsozymes=nIsozymes(ord); sourceGroup=sourceGroup(ord);
+0133 kcat0Col=kcat0Col(ord); leverage=leverage(ord); sigma0logCol=sigma0logCol(ord);
+0134 positions=positions(ord); rnk=rnk(ord);
+0135
+0136 total = sum(rnk);
+0137 if total > 0
+0138 cumLeverageShare = cumsum(rnk) / total;
+0139 else
+0140 cumLeverageShare = zeros(size(rnk));
+0141 end
+0142
+0143 screen = table(rxnId,nIsozymes,sourceGroup,kcat0Col,leverage,sigma0logCol,rnk,cumLeverageShare,positions, ...
+0144 'VariableNames',{'rxnId','nIsozymes','sourceGroup','kcat0','leverage','sigma0log','rank','cumLeverageShare','positions'});
+0145 end
+0146
+0147 function [obj,rmse,rmseDetail,ecModel] = scoreVector(ecModel,tunableIdx,ecRxnIdsTunable,evoData,modelAdapter,params,kcatVec)
+0148 ecModel.ec.kcat(tunableIdx) = kcatVec;
+0149 ecModel = applyKcatConstraints(ecModel,'updateRxns',ecRxnIdsTunable);
+0150 [obj,rmse,rmseDetail,ecModel] = evotuneScore(ecModel,evoData,'modelAdapter',modelAdapter,'maxGrowthWeight',params.maxGrowthWeight);
+0151 end
selectTunableMask Build a tunableMask from a screenKcatLeverage report.
+
+
SYNOPSIS
+
function mask = selectTunableMask(ecModel,screen,varargin)
+
+
DESCRIPTION
+
selectTunableMask Build a tunableMask from a screenKcatLeverage report.
+
+ Keeps the fewest highest-ranked groups whose combined leverage reaches
+ targetImpactShare of the total -- a relative cutoff, so the same
+ targetImpactShare selects a comparable quality of parameter set on any
+ model, rather than an absolute leverage value or a fixed count
+ calibrated on a different model's scale.
+
+ Pure and cheap -- no simulation, only screen is read.
+
+ Parameters
+ ----------
+ ecModel : struct
+ screen : table
+ as returned by screenKcatLeverage.
+
+ Name-Value Arguments
+ --------------------
+ targetImpactShare : double
+ default 0.9. Not a validated universal constant -- check the
+ resulting mask's size against screenKcatLeverage's own curve before
+ trusting it on a new model.
+
+ Returns
+ -------
+ mask : logical
+ length numel(ecModel.ec.rxns).
+
+ See also
+ --------
+ screenKcatLeverage, cmaesKcatTuning
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
cmaesKcatTuning cmaesKcatTuning Tune kcats against experimental data with CMA-ES.
0001 function mask = selectTunableMask(ecModel,screen,varargin)
+0002 % selectTunableMask Build a tunableMask from a screenKcatLeverage report.
+0003 %
+0004 % Keeps the fewest highest-ranked groups whose combined leverage reaches
+0005 % targetImpactShare of the total -- a relative cutoff, so the same
+0006 % targetImpactShare selects a comparable quality of parameter set on any
+0007 % model, rather than an absolute leverage value or a fixed count
+0008 % calibrated on a different model's scale.
+0009 %
+0010 % Pure and cheap -- no simulation, only screen is read.
+0011 %
+0012 % Parameters
+0013 % ----------
+0014 % ecModel : struct
+0015 % screen : table
+0016 % as returned by screenKcatLeverage.
+0017 %
+0018 % Name-Value Arguments
+0019 % --------------------
+0020 % targetImpactShare : double
+0021 % default 0.9. Not a validated universal constant -- check the
+0022 % resulting mask's size against screenKcatLeverage's own curve before
+0023 % trusting it on a new model.
+0024 %
+0025 % Returns
+0026 % -------
+0027 % mask : logical
+0028 % length numel(ecModel.ec.rxns).
+0029 %
+0030 % See also
+0031 % --------
+0032 % screenKcatLeverage, cmaesKcatTuning
+0033 p = parseGECKOargs(varargin, { ...
+0034 'targetImpactShare', 0.9});
+0035 targetImpactShare = p.targetImpactShare;
+0036
+0037 mask = false(numel(ecModel.ec.rxns),1);
+0038 if isempty(screen) || height(screen) == 0
+0039 return
+0040 end
+0041 prevCum = [0; screen.cumLeverageShare(1:end-1)];
+0042 keep = prevCum < targetImpactShare;
+0043 for i = find(keep)'
+0044 mask(screen.positions{i}) = true;
+0045 end
+0046 end
tunePriorPenaltyWeight Sweep priorPenaltyWeight and report the
+
+
SYNOPSIS
+
function sweepTable = tunePriorPenaltyWeight(ecModel,varargin)
+
+
DESCRIPTION
+
tunePriorPenaltyWeight Sweep priorPenaltyWeight and report the
+ fit/reproducibility trade-off, so the value doesn't have to be picked
+ blind.
+
+ priorPenaltyWeight charges for moving a kcat away from its prior, and
+ mainly exists to keep large corrections reproducible across seeds
+ rather than landing on an arbitrary point along a flat direction. How
+ strong a charge is enough is model- and data-specific: too little and
+ the search's largest corrections disagree across seeds; too much and
+ the search stops finding real corrections at all. This runs
+ cmaesKcatTuning at every value in candidates, each at every seed in
+ seeds, all against the same tunable set, and reports both fit and
+ cross-seed reproducibility per value.
+
+ This is exactly as expensive as it sounds: numel(candidates)*numel(seeds)
+ full cmaesKcatTuning runs, none of them reusable across candidates. Pass
+ a screen you already have to at least avoid recomputing that part.
+
+ Parameters
+ ----------
+ ecModel : struct
+ ecModel with a populated ec.kcat. Not mutated (MATLAB structs are
+ copied on assignment; every candidate/seed pair starts fresh from
+ this input regardless of what earlier runs did).
+
+ Name-Value Arguments
+ --------------------
+ modelAdapter, evoData, tunableMask, screen, targetImpactShare, popsize,
+ nProc, verbose
+ as in cmaesKcatTuning.
+ candidates : double
+ priorPenaltyWeight values to try (default [0.0, 0.01, 0.03, 0.1]).
+ seeds : double
+ seeds to run each candidate at (default [0, 1]). Reproducibility
+ columns pool every pair of seeds, so more than two sharpens them
+ but costs proportionally more.
+ foldThreshold : double
+ a kcat counts as "moved" past this fold change from its prior
+ (default 2.0). Matches nChanged's convention loosely (a different
+ quantity, same spirit).
+
+ Returns
+ -------
+ sweepTable : table
+ one row per candidate: priorPenaltyWeight, nSeeds, distanceMean,
+ distanceSpread (max minus min across seeds), nChangedMean,
+ nMovers (kcats past foldThreshold in either seed of a pair, summed
+ over every pair), nBothMoved (past it in both), pctDirectionAgree,
+ medianFoldSpread and maxFoldSpread (among movers, the largest
+ fold-change between what two seeds landed on), and fitCost
+ (distanceMean relative to the best candidate's, as a fraction).
+
+ See also
+ --------
+ cmaesKcatTuning, screenKcatLeverage, selectTunableMask, nChanged
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
cmaesKcatTuning cmaesKcatTuning Tune kcats against experimental data with CMA-ES.
loadEvotuneData loadEvotuneData Load experimental data for evotune (CMA-ES) kcat tuning.
nChanged nChanged How many kcats differ from their prior by more than relTol.
screenKcatLeverage screenKcatLeverage Report which kcats the data can actually speak to.
selectTunableMask selectTunableMask Build a tunableMask from a screenKcatLeverage report.
0001 function sweepTable = tunePriorPenaltyWeight(ecModel,varargin)
+0002 % tunePriorPenaltyWeight Sweep priorPenaltyWeight and report the
+0003 % fit/reproducibility trade-off, so the value doesn't have to be picked
+0004 % blind.
+0005 %
+0006 % priorPenaltyWeight charges for moving a kcat away from its prior, and
+0007 % mainly exists to keep large corrections reproducible across seeds
+0008 % rather than landing on an arbitrary point along a flat direction. How
+0009 % strong a charge is enough is model- and data-specific: too little and
+0010 % the search's largest corrections disagree across seeds; too much and
+0011 % the search stops finding real corrections at all. This runs
+0012 % cmaesKcatTuning at every value in candidates, each at every seed in
+0013 % seeds, all against the same tunable set, and reports both fit and
+0014 % cross-seed reproducibility per value.
+0015 %
+0016 % This is exactly as expensive as it sounds: numel(candidates)*numel(seeds)
+0017 % full cmaesKcatTuning runs, none of them reusable across candidates. Pass
+0018 % a screen you already have to at least avoid recomputing that part.
+0019 %
+0020 % Parameters
+0021 % ----------
+0022 % ecModel : struct
+0023 % ecModel with a populated ec.kcat. Not mutated (MATLAB structs are
+0024 % copied on assignment; every candidate/seed pair starts fresh from
+0025 % this input regardless of what earlier runs did).
+0026 %
+0027 % Name-Value Arguments
+0028 % --------------------
+0029 % modelAdapter, evoData, tunableMask, screen, targetImpactShare, popsize,
+0030 % nProc, verbose
+0031 % as in cmaesKcatTuning.
+0032 % candidates : double
+0033 % priorPenaltyWeight values to try (default [0.0, 0.01, 0.03, 0.1]).
+0034 % seeds : double
+0035 % seeds to run each candidate at (default [0, 1]). Reproducibility
+0036 % columns pool every pair of seeds, so more than two sharpens them
+0037 % but costs proportionally more.
+0038 % foldThreshold : double
+0039 % a kcat counts as "moved" past this fold change from its prior
+0040 % (default 2.0). Matches nChanged's convention loosely (a different
+0041 % quantity, same spirit).
+0042 %
+0043 % Returns
+0044 % -------
+0045 % sweepTable : table
+0046 % one row per candidate: priorPenaltyWeight, nSeeds, distanceMean,
+0047 % distanceSpread (max minus min across seeds), nChangedMean,
+0048 % nMovers (kcats past foldThreshold in either seed of a pair, summed
+0049 % over every pair), nBothMoved (past it in both), pctDirectionAgree,
+0050 % medianFoldSpread and maxFoldSpread (among movers, the largest
+0051 % fold-change between what two seeds landed on), and fitCost
+0052 % (distanceMean relative to the best candidate's, as a fraction).
+0053 %
+0054 % See also
+0055 % --------
+0056 % cmaesKcatTuning, screenKcatLeverage, selectTunableMask, nChanged
+0057 p = parseGECKOargs(varargin, { ...
+0058 'modelAdapter', []; ...
+0059 'evoData', []; ...
+0060 'tunableMask', []; ...
+0061 'screen', []; ...
+0062 'targetImpactShare', 0.9; ...
+0063 'popsize', []; ...
+0064 'nProc', 1; ...
+0065 'verbose', true; ...
+0066 'candidates', [0.0, 0.01, 0.03, 0.1]; ...
+0067 'seeds', [0, 1]; ...
+0068 'foldThreshold', 2.0});
+0069 modelAdapter = p.modelAdapter;
+0070 evoData = p.evoData;
+0071 tunableMask = p.tunableMask;
+0072 screen = p.screen;
+0073 targetImpactShare = p.targetImpactShare;
+0074 popsize = p.popsize;
+0075 nProc = p.nProc;
+0076 verbose = p.verbose;
+0077 candidates = p.candidates;
+0078 seeds = p.seeds;
+0079 foldThreshold = p.foldThreshold;
+0080
+0081 if isempty(modelAdapter)
+0082 modelAdapter = ModelAdapterManager.getDefault();
+0083 if isempty(modelAdapter)
+0084 error('Either send in a modelAdapter or set the default ecModel adapter in the ModelAdapterManager.')
+0085 end
+0086 end
+0087 if isempty(evoData)
+0088 evoData = loadEvotuneData('modelAdapter', modelAdapter);
+0089 end
+0090 if isempty(tunableMask)
+0091 if isempty(screen)
+0092 screen = screenKcatLeverage(ecModel, 'modelAdapter',modelAdapter, ...
+0093 'evoData',evoData, 'nProc',nProc);
+0094 end
+0095 tunableMask = selectTunableMask(ecModel, screen, 'targetImpactShare',targetImpactShare);
+0096 end
+0097
+0098 nCand = numel(candidates);
+0099 priorPenaltyWeight = zeros(nCand,1);
+0100 nSeeds = zeros(nCand,1);
+0101 distanceMean = zeros(nCand,1);
+0102 distanceSpread = zeros(nCand,1);
+0103 nChangedMean = zeros(nCand,1);
+0104 nMovers = zeros(nCand,1);
+0105 nBothMoved = zeros(nCand,1);
+0106 pctDirectionAgree = zeros(nCand,1);
+0107 medianFoldSpread = zeros(nCand,1);
+0108 maxFoldSpread = zeros(nCand,1);
+0109
+0110 for c = 1:nCand
+0111 lam = candidates(c);
+0112 lamParams = modelAdapter.params.evotune;
+0113 lamParams.priorPenaltyWeight = lam;
+0114
+0115 vectors = cell(numel(seeds),1);
+0116 distances = zeros(numel(seeds),1);
+0117 changed = zeros(numel(seeds),1);
+0118 oldKcat = [];
+0119 for s = 1:numel(seeds)
+0120 seedVal = seeds(s);
+0121 adapterCopy = modelAdapter;
+0122 adapterCopy.params.evotune = lamParams;
+0123 [~,res] = cmaesKcatTuning(ecModel, 'modelAdapter',adapterCopy, ...
+0124 'evoData',evoData, 'tunableMask',tunableMask, ...
+0125 'popsize',popsize, 'nProc',nProc, 'seed',seedVal, 'verbose',verbose);
+0126 vectors{s} = res.newKcat;
+0127 if ~isempty(res.rmseTrace)
+0128 distances(s) = res.rmseTrace(end);
+0129 else
+0130 distances(s) = NaN;
+0131 end
+0132 changed(s) = nChanged(res.newKcat, res.oldKcat);
+0133 oldKcat = res.oldKcat;
+0134 if verbose
+0135 fprintf('priorPenaltyWeight=%g seed=%d: distance %.4f, %d changed\n', ...
+0136 lam, seedVal, distances(s), changed(s));
+0137 end
+0138 end
+0139
+0140 movers = 0; both = 0; agree = 0; spreads = [];
+0141 for i = 1:numel(vectors)
+0142 for j = (i+1):numel(vectors)
+0143 fa = foldChangeLocal(vectors{i}, oldKcat);
+0144 fb = foldChangeLocal(vectors{j}, oldKcat);
+0145 moversIJ = (fa > foldThreshold) | (fb > foldThreshold);
+0146 bothIJ = (fa > foldThreshold) & (fb > foldThreshold);
+0147 agreeIJ = sign(log(vectors{i}(bothIJ) ./ oldKcat(bothIJ))) == ...
+0148 sign(log(vectors{j}(bothIJ) ./ oldKcat(bothIJ)));
+0149 movers = movers + sum(moversIJ);
+0150 both = both + sum(bothIJ);
+0151 agree = agree + sum(agreeIJ);
+0152 if any(moversIJ)
+0153 ratio = vectors{i}(moversIJ) ./ vectors{j}(moversIJ);
+0154 spreads = [spreads; max(ratio, 1./ratio)]; %#ok<AGROW>
+0155 end
+0156 end
+0157 end
+0158
+0159 priorPenaltyWeight(c) = lam;
+0160 nSeeds(c) = numel(seeds);
+0161 distanceMean(c) = mean(distances);
+0162 if numel(distances) > 1
+0163 distanceSpread(c) = max(distances) - min(distances);
+0164 else
+0165 distanceSpread(c) = 0;
+0166 end
+0167 nChangedMean(c) = mean(changed);
+0168 nMovers(c) = movers;
+0169 nBothMoved(c) = both;
+0170 if both > 0
+0171 pctDirectionAgree(c) = agree / both;
+0172 else
+0173 pctDirectionAgree(c) = NaN;
+0174 end
+0175 if ~isempty(spreads)
+0176 medianFoldSpread(c) = median(spreads);
+0177 maxFoldSpread(c) = max(spreads);
+0178 else
+0179 medianFoldSpread(c) = NaN;
+0180 maxFoldSpread(c) = NaN;
+0181 end
+0182 end
+0183
+0184 fitCost = distanceMean / min(distanceMean) - 1.0;
+0185
+0186 sweepTable = table(priorPenaltyWeight,nSeeds,distanceMean,distanceSpread,nChangedMean, ...
+0187 nMovers,nBothMoved,pctDirectionAgree,medianFoldSpread,maxFoldSpread,fitCost);
+0188 end
+0189
+0190 function f = foldChangeLocal(kcat,kcat0)
+0191 % Per-kcat fold change from the prior, always >= 1. The only caller of
+0192 % this in the whole port is this file, so it stays local rather than
+0193 % getting its own file (see nChanged.m for the parsimony function that
+0194 % genuinely is needed from two places).
+0195 f = exp(abs(log(kcat(:) ./ kcat0(:))));
+0196 end
0001 function model = fillCarbonNum(model)
+0002 % fillCarbonNum Add a carbon number field to the model.
+0003 %
+0004 % Adds a model.excarbon field with the carbon number of each exchange
+0005 % reaction, used to weight the RMSE during evotune kcat tuning.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % model : struct
+0010 % ecModel that was generated by makeEcModel, or loaded from an earlier
+0011 % run. Not compatible with ecModels generated by earlier GECKO versions
+0012 % (pre 3.0).
+0013 %
+0014 % Returns
+0015 % -------
+0016 % model : struct
+0017 % ecModel with an added model.excarbon field.
+0018
+0019 % get carbonnum for each exchange rxn to further calculation of error
+0020 model.excarbon = zeros(length(model.rxns),1);
+0021 [EXrxn, EXrxnIdx] = getExchangeRxns(model);
+0022 [CarbonNum,EXfors] = getcarbonnum(model,EXrxn);
+0023 model.excarbon(EXrxnIdx) = CarbonNum;
+0024 model.excarbon(strcmp(model.rxnNames,'growth')) = 41;
+0025
+0026 function [CarbonNum,EXfors,changedmets] = getcarbonnum(model,exrxn)
+0027 % getcarbonnum Get carbon numbers matching the model.
+0028 %
+0029 % Parameters
+0030 % ----------
+0031 % model : struct
+0032 % ecModel that was generated by makeEcModel, or loaded from an earlier
+0033 % run. Not compatible with ecModels generated by earlier GECKO versions
+0034 % (pre 3.0).
+0035 % exrxn : cell
+0036 % exchange reactions to use for counting.
+0037 %
+0038 % Returns
+0039 % -------
+0040 % CarbonNum : double
+0041 % carbon number for each exchange reaction.
+0042 % EXfors : cell
+0043 % formulae of the exchanged metabolites.
+0044 % changedmets : cell
+0045 % exchange reactions whose carbon number could not be determined.
+0046 %
+0047
+0048 % exrxn must be an exchange rxn and only contain one mets in the equation
+0049 [~,idx] = ismember(exrxn,model.rxns);
+0050 EXmets = model.S(:,idx);
+0051 % find all exchange mets
+0052 EXmetsIdx = zeros(length(exrxn),1);
+0053 for k = 1:length(EXmets(1,:))
+0054 EXmetsIdx(k) = find(EXmets(:,k)); %
+0055 end
+0056 EXfors = model.metFormulas(EXmetsIdx);
+0057 [Ematrix, elements] = getElementalComposition(EXfors,{'C'});
+0058 Ematrix = Ematrix(:,1);
+0059 changedmets = exrxn(isnan(Ematrix));
+0060 Ematrix(isnan(Ematrix)) = 1;
+0061 CarbonNum = Ematrix;
+0062
+0063 function [Ematrix, elements] = getElementalComposition(formulae, elements, chargeInFormula)
+0064 % Get the complete elemental composition matrix. It supports formulae with
+0065 % generic elements, parentheses and decimal places
+0066 %
+0067 % USAGE:
+0068 % [Ematrix, elements] = getElementalComposition(formulae, elements, chargeInFormula)
+0069 %
+0070 % INPUT:
+0071 % formulae: cell array of strings of chemical formulae. Can contain any generic elements starting
+0072 % with a capital letter followed by lowercase letters or '_', followed by a non-negative number.
+0073 % Also support '()', '[]', '{}'. E.g. {'H2O'; '[H2O]2(CuSO4)Generic_element0.5'}
+0074 % OPTIONAL INPUTS:
+0075 % elements: elements from previous call to preserve the order (default {})
+0076 % chargeInFormula: true to accept formulae containing the generic element 'Charge' representing the charges,
+0077 % followed by a real number, e.g., 'HCharge1', 'SO4Charge-2' (default false).
+0078 %
+0079 % OUTPUTS:
+0080 % Ematrix: elemental composition matrix (#formulae x #elements)
+0081 % elements: cell array of elements corresponding to the columns of Ematrix
+0082 %
+0083 % E.g., [Ematrix, elements] = getElementalComposition({'H2O'; '[H2O]2(CuSO4)Generic_element0.5'}) would return:
+0084 % elements = {'H', 'O', 'Cu', 'S', 'Generic_element'}
+0085 % Ematrix = [ 2, 1, 0, 0, 0;
+0086 % 4, 6, 1, 1, 0.5]
+0087 %
+0088 % Siu Hung Joshua Chan May 2017
+0089
+0090 if nargin < 3 || isempty(chargeInFormula)
+0091 chargeInFormula = false;
+0092 else
+0093 chargeInFormula = logical(chargeInFormula);
+0094 end
+0095 % for recalling the original formula at the top level if there are parentheses
+0096 % in the formula leading to iterative calling
+0097 persistent formTopLv
+0098 persistent formCurLv
+0099 % for storing error message during iterative calling
+0100 persistent errMsg
+0101 persistent errMsgInThisLoop
+0102 persistent topLvJ
+0103 persistent selfCall
+0104 if isempty(selfCall)
+0105 selfCall = 0;
+0106 end
+0107 if ~selfCall
+0108 [formTopLv, errMsg] = deal('');
+0109 end
+0110
+0111 if ~isstruct(formulae)
+0112 if ~iscell(formulae)
+0113 % make sure it is a cell array of strings
+0114 formulae = {formulae};
+0115 end
+0116 else
+0117 % also accept COBRA model as input
+0118 if isfield(formulae, 'metFormulas')
+0119 formulae = formulae.metFormulas;
+0120 else
+0121 error('The 1st input ''formulae'' should be a cell array of strings of formulae or a COBRA model with *.metFormulas')
+0122 end
+0123 end
+0124
+0125 if nargin < 2 || isempty(elements)
+0126 elements = {};
+0127 elseif numel(unique(elements)) < numel(elements)
+0128 error('Repeated elements in the input ''elements'' array.')
+0129 else
+0130 elements = elements(:)'; % make sure it is a row vector
+0131 end
+0132
+0133 % the Ematrix
+0134 Ematrix = zeros(numel(formulae), numel(elements));
+0135 % replace all brackets and braces by parentheses
+0136 formulae = regexprep(formulae, '[\[\{]', '\(');
+0137 formulae = regexprep(formulae, '[\]\}]', '\)');
+0138 digit = floor(log10(numel(formulae))) + 1;
+0139 for j = 1:numel(formulae)
+0140 if ~selfCall
+0141 % reset top level information for each formula
+0142 [formTopLv, topLvJ, errMsgInThisLoop] = deal(formulae{j}, j, false);
+0143 end
+0144 formulae{j} = strtrim(formulae{j});
+0145 if ~isempty(formulae{j})
+0146 % get all outer parentheses
+0147 parenthesis = [];
+0148 stP = [];
+0149 stPpos = [];
+0150 lv = 0;
+0151 k = 1;
+0152 while k <= length(formulae{j})
+0153 if strcmp(formulae{j}(k),'(')
+0154 if lv == 0
+0155 pStart = k;
+0156 end
+0157 lv = lv + 1;
+0158 elseif strcmp(formulae{j}(k),')')
+0159 if lv == 1
+0160 parenthesis = [parenthesis; [pStart k]];
+0161 % right parenthesis, get the following stoichiometry if any
+0162 stPre = regexp(formulae{j}(k+1:end), '^(\+|\-)?\d*\.?\d*', 'match');
+0163 if isempty(stPre)
+0164 stP = [stP; 1];
+0165 stPpos = [stPpos; k k];
+0166 else
+0167 stCheck = str2double(stPre{1});
+0168 s = '';
+0169 if isnan(stCheck)
+0170 s = 'Invalid';
+0171 elseif stCheck < 0
+0172 s = 'Negative';
+0173 end
+0174 if ~isempty(s) % error if not convertible to number or negative
+0175 f = [formulae{j}(parenthesis(end,1):parenthesis(end,2)), stPre{1}];
+0176 addErrorMessage(sprintf(' %s stoichiometry in ''%s''\n', s, f))
+0177 end
+0178 % stoichiometry
+0179 stP = [stP; stCheck];
+0180 % position of the stoichiometry in the text
+0181 stPpos = [stPpos; k + 1, k + length(stPre{1})];
+0182 k = k + length(stPre{1});
+0183 end
+0184 end
+0185 lv = lv - 1;
+0186 end
+0187 k = k + 1;
+0188 end
+0189 if isempty(parenthesis)
+0190 % No parenthesis, parse the formula
+0191 re = regexp(formulae{j}, '([A-Z][a-z_]*)((?:\+|\-)?\d*\.?\d*)', 'tokens');
+0192 s = strjoin(cellfun(@(x) strjoin(x,''), re, 'UniformOutput', false), '');
+0193 errorFlag = 0;
+0194 if ~strcmp(s, formulae{j}) % check if the entire formula is retrieved
+0195 errorFlag = 1;
+0196 else
+0197 % check the stoichiometry
+0198 eleCheck = cellfun(@(x) x{1}, re, 'UniformOutput', false);
+0199 stCheck = cellfun(@(x) str2double(x{2}), re);
+0200 stCheck(cellfun(@(x) isempty(x{2}), re)) = 1; % empty value => stoichiometry = 1
+0201 if any(isnan(stCheck))
+0202 f = strjoin(cellfun(@(x) strjoin(x, ''), re(isnan(stCheck)), 'UniformOutput', false), ''', ''');
+0203 [errorFlag, errMsgKey] = deal(2, 'Invalid');
+0204 else
+0205 % if chargeInFormula is true, detect negative stoich for non-charge elements, else any negative stoich
+0206 negSt = (~chargeInFormula | ~strcmp(eleCheck, 'Charge')) & stCheck < 0;
+0207 if any(negSt)
+0208 f = strjoin(cellfun(@(x) strjoin(x, ''), re(negSt), 'UniformOutput', false), ''', ''');
+0209 [errorFlag, errMsgKey] = deal(2, 'Negative');
+0210 end
+0211 end
+0212 end
+0213 if errorFlag > 0
+0214 if selfCall <= 1
+0215 s2 = sprintf('from the part ''%s''', formulae{j});
+0216 elseif selfCall == 2
+0217 s2 = sprintf('from the part ''(%s)''', formCurLv);
+0218 elseif selfCall == 3
+0219 s2 = sprintf('from the part ''(%s)''', formulae{j});
+0220 end
+0221 if errorFlag == 1
+0222 s2 = sprintf([' Only ''%s'' can be recognized %s.\n'...
+0223 ' Each element should start with a capital letter followed by lower case letters'...
+0224 ' or ''_'' with indefinite length and followed by a number.\n'], s, s2);
+0225 elseif errorFlag == 2
+0226 s2 = sprintf(' %s stoichiometry in ''%s'' %s\n', errMsgKey, f, s2);
+0227 end
+0228 addErrorMessage(s2);
+0229 end
+0230 elementJ = repmat({''}, 1, numel(re));
+0231 nEj = 0;
+0232 stoichJ = zeros(numel(re),1);
+0233 for k = 1:numel(re)
+0234 [ynK,idK] = ismember(re{k}(1), elementJ(1:nEj));
+0235 if ynK
+0236 k2 = idK;
+0237 else
+0238 nEj = nEj + 1;
+0239 elementJ{nEj} = re{k}{1};
+0240 k2 = nEj;
+0241 end
+0242 if isempty(re{k}{2})
+0243 stoichJ(k2) = stoichJ(k2) + 1;
+0244 else
+0245 stoichJ(k2) = stoichJ(k2) + str2double(re{k}{2});
+0246 end
+0247 end
+0248 elementJ = elementJ(1:nEj);
+0249 stoichJ = stoichJ(1:nEj);
+0250 [ynE, idE] = ismember(elementJ, elements); % map to existing elements
+0251 if any(~ynE)
+0252 idE(~ynE) = (numel(elements) + 1):(numel(elements) + sum(~ynE));
+0253 Ematrix(:, (numel(elements) + 1):(numel(elements) + sum(~ynE))) = 0;
+0254 elements = [elements, elementJ(~ynE)];
+0255 end
+0256 Ematrix(j, idE) = stoichJ;
+0257 else
+0258 % parentheses found. Iteratively get the formula inside parentheses
+0259 rest = true(length(formulae{j}),1);
+0260 for k = 1:size(parenthesis,1)
+0261 selfCallCur = selfCall;
+0262 selfCall = 3;
+0263 [EmatrixK, elements] = getElementalComposition(formulae{j}(...
+0264 (parenthesis(k,1)+1):(parenthesis(k,2)-1)), elements, chargeInFormula);
+0265 if numel(elements) > size(Ematrix, 2)
+0266 Ematrix(:, (size(Ematrix, 2) + 1):numel(elements)) = 0;
+0267 end
+0268 selfCall = selfCallCur;
+0269 Ematrix(j, 1:numel(elements)) = Ematrix(j, 1:numel(elements)) + EmatrixK * stP(k);
+0270 rest(parenthesis(k,1):stPpos(k,2)) = false;
+0271 end
+0272 if any(rest)
+0273 formCurLv = formulae{j};
+0274 selfCallCur = selfCall;
+0275 selfCall = 1 + (selfCall >= 1);
+0276 while any(rest)
+0277 % get the consecutive part of the formula that is not in parentheses
+0278 [pStart, pEnd] = deal(find(rest, 1), find(~rest));
+0279 pEnd = min(pEnd(pEnd > pStart));
+0280 if isempty(pEnd)
+0281 pEnd = numel(rest);
+0282 else
+0283 pEnd = pEnd - 1;
+0284 end
+0285 [EmatrixK, elements] = getElementalComposition(formulae{j}(pStart:pEnd), elements, chargeInFormula);
+0286 if numel(elements) > size(Ematrix, 2)
+0287 Ematrix(:, (size(Ematrix, 2) + 1):numel(elements)) = 0;
+0288 end
+0289 Ematrix(j, 1:numel(elements)) = Ematrix(j, 1:numel(elements)) + EmatrixK;
+0290 rest(pStart:pEnd) = false;
+0291 end
+0292 selfCall = selfCallCur;
+0293 end
+0294 end
+0295 else
+0296 Ematrix(j,:) = NaN;
+0297 end
+0298 end
+0299 Ematrix = Ematrix(:, 1:numel(elements));
+0300 if ~selfCall
+0301 selfCall = [];
+0302 if ~isempty(errMsg)
+0303 error(['%s\n', errMsg], 'Invalid formula input:')
+0304 end
+0305 end
+0306
+0307 % nested function for adding error messages
+0308 function addErrorMessage(s)
+0309 if ~errMsgInThisLoop
+0310 errMsg = [errMsg, sprintf(['#%0' num2str(digit) 'd: %s\n'], topLvJ, formTopLv)];
+0311 errMsgInThisLoop = true;
+0312 end
+0313 errMsg = [errMsg, s];
+0314 end
+0315 end
+0316
+0317 end
+0318
+0319 end
sensitivityTuning Relax the most limiting kcats to reach a growth rate.
+
+
SYNOPSIS
+
function [model, tunedKcats] = sensitivityTuning(model, varargin)
+
+
DESCRIPTION
+
sensitivityTuning Relax the most limiting kcats to reach a growth rate.
+
+ Iteratively increases the kcat of whichever enzyme currently limits growth
+ the most, until the model reaches the desired growth rate, updating the
+ tuned values in model.ec.kcat. The loop also stops, with a warning, when an
+ iteration raises growth by less than 1e-9.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ desiredGrowthRate : double
+ kcats will be relaxed until this growth rate is reached (default: the
+ experimental growth rate params.gR_exp from the model adapter).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ foldChange : double
+ kcat values will be increased by this fold-change (default 10).
+ protToIgnore : cell
+ vector of protein ids to be ignored in tuned kcats, e.g. {'P38122',
+ 'Q99271'} (default []).
+ verbose : logical
+ whether progress should be reported (default true).
+
+ Returns
+ -------
+ model : struct
+ ecModel with updated model.ec.kcat.
+ tunedKcats : struct
+ structure with information on tuned kcat values.
+
+ Notes
+ -----
+ The tunedKcats structure has the following fields:
+
+ - rxns : identifiers of reactions with tuned kcat values.
+ - rxnNames : names of the reactions in tunedKcats.rxns.
+ - enzymes : enzymes that catalyze the reactions in tunedKcats.rxns, whose
+ kcat value has been tuned.
+ - oldKcat : kcat values in the input model.
+ - newKcat : kcat values in the output model, after tuning.
+ - source : source label of each kcat, taken from the input model before
+ tuning.
+
+ The model.ec.notes field will contain the original kcat value and source,
+ unless the kcat has previously been set by sensitivityTuning, in which case
+ the notes field remains unchanged.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, tunedKcats] = sensitivityTuning(model);
+ [model, tunedKcats] = sensitivityTuning(model, 'foldChange', 10);
+ [model, tunedKcats] = sensitivityTuning(model, desiredGrowthRate, modelAdapter, foldChange, protToIgnore, verbose);
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [model, tunedKcats] = sensitivityTuning(model, varargin)
+0002 % sensitivityTuning Relax the most limiting kcats to reach a growth rate.
+0003 %
+0004 % Iteratively increases the kcat of whichever enzyme currently limits growth
+0005 % the most, until the model reaches the desired growth rate, updating the
+0006 % tuned values in model.ec.kcat. The loop also stops, with a warning, when an
+0007 % iteration raises growth by less than 1e-9.
+0008 %
+0009 % Parameters
+0010 % ----------
+0011 % model : struct
+0012 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0013 %
+0014 % Name-Value Arguments
+0015 % --------------------
+0016 % desiredGrowthRate : double
+0017 % kcats will be relaxed until this growth rate is reached (default: the
+0018 % experimental growth rate params.gR_exp from the model adapter).
+0019 % modelAdapter : ModelAdapter
+0020 % a loaded model adapter (default: the current default model adapter).
+0021 % foldChange : double
+0022 % kcat values will be increased by this fold-change (default 10).
+0023 % protToIgnore : cell
+0024 % vector of protein ids to be ignored in tuned kcats, e.g. {'P38122',
+0025 % 'Q99271'} (default []).
+0026 % verbose : logical
+0027 % whether progress should be reported (default true).
+0028 %
+0029 % Returns
+0030 % -------
+0031 % model : struct
+0032 % ecModel with updated model.ec.kcat.
+0033 % tunedKcats : struct
+0034 % structure with information on tuned kcat values.
+0035 %
+0036 % Notes
+0037 % -----
+0038 % The tunedKcats structure has the following fields:
+0039 %
+0040 % - rxns : identifiers of reactions with tuned kcat values.
+0041 % - rxnNames : names of the reactions in tunedKcats.rxns.
+0042 % - enzymes : enzymes that catalyze the reactions in tunedKcats.rxns, whose
+0043 % kcat value has been tuned.
+0044 % - oldKcat : kcat values in the input model.
+0045 % - newKcat : kcat values in the output model, after tuning.
+0046 % - source : source label of each kcat, taken from the input model before
+0047 % tuning.
+0048 %
+0049 % The model.ec.notes field will contain the original kcat value and source,
+0050 % unless the kcat has previously been set by sensitivityTuning, in which case
+0051 % the notes field remains unchanged.
+0052 %
+0053 % Examples
+0054 % --------
+0055 % % optional arguments may be given positionally or as name-value pairs:
+0056 % [model, tunedKcats] = sensitivityTuning(model);
+0057 % [model, tunedKcats] = sensitivityTuning(model, 'foldChange', 10);
+0058 % [model, tunedKcats] = sensitivityTuning(model, desiredGrowthRate, modelAdapter, foldChange, protToIgnore, verbose);
+0059
+0060 p = parseGECKOargs(varargin, { ...
+0061 'desiredGrowthRate', []; ...
+0062 'modelAdapter', []; ...
+0063 'foldChange', 10; ...
+0064 'protToIgnore', {}; ...
+0065 'verbose', true});
+0066 desiredGrowthRate = p.desiredGrowthRate;
+0067 modelAdapter = p.modelAdapter;
+0068 foldChange = p.foldChange;
+0069 protToIgnore = p.protToIgnore;
+0070 verbose = p.verbose;
+0071
+0072 if isempty(modelAdapter)
+0073 modelAdapter = ModelAdapterManager.getDefault();
+0074 if isempty(modelAdapter)
+0075 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0076 end
+0077 end
+0078 params = modelAdapter.params;
+0079 if isempty(desiredGrowthRate)
+0080 desiredGrowthRate = params.gR_exp;
+0081 end
+0082
+0083 kcatList = [];
+0084 m = model;
+0085 m.c = double(strcmp(m.rxns, params.bioRxn));% Make sure that growth is maximized
+0086
+0087 [res,hs] = solveLP(m);
+0088 if isempty(res.x)
+0089 error('FBA of input model gives no valid result. Reduce protein pool constraint with setProtPoolSize and/or check if exchange constraints are correctly defined.')
+0090 end
+0091 % The loop stops when growth rises by less than growthStallTol over the
+0092 % previous iteration (the first iteration has nothing to compare with).
+0093 lastGrowth = NaN;
+0094 growthStallTol = 1e-9;
+0095 if ~m.ec.geckoLight
+0096 %for the full model, we first find the draw reaction with the most flux
+0097 drawRxns = startsWith(m.rxns, 'usage_prot_');
+0098 idxToIgnore = cellfun(@(x) find(strcmpi(model.rxns, ['usage_prot_' x])), protToIgnore);
+0099 iteration = 1;
+0100 while true
+0101 [res,hs] = solveLP(m,0,[],hs); %skip parsimonius, only takes time
+0102 if isempty(res.f)
+0103 printOrange('WARNING: The model has no feasible solution with the increased kcats, tuning stops.\n');
+0104 break;
+0105 end
+0106 if ~isnan(lastGrowth) && res.f <= lastGrowth + growthStallTol
+0107 printOrange('WARNING: No growth increase from increased kcats - check if the constraints on the uptake reactions are too tight.\n');
+0108 break;
+0109 end
+0110 lastGrowth = res.f;
+0111 if verbose; disp(['Iteration ' num2str(iteration) ': Growth: ' num2str(lastGrowth)]); end
+0112 if (lastGrowth >= desiredGrowthRate)
+0113 break;
+0114 end
+0115 %If you get an error here, it is likely due to numerical issues in the solver
+0116 %The trick where we don't allow low kcats is to fix that, but maybe
+0117 %it is not enough.
+0118 iteration = iteration + 1;
+0119 %find the highest draw_prot rxn flux
+0120 drawFluxes = zeros(length(drawRxns),1);
+0121 drawFluxes(drawRxns) = res.x(drawRxns);
+0122 % Remove from the list user defined proteins
+0123 drawFluxes(idxToIgnore) = 0;
+0124 if ~any(drawFluxes > 0)
+0125 printOrange('WARNING: No protein usage reaction carries flux, tuning cannot continue.\n');
+0126 break;
+0127 end
+0128 [~,sel] = max(drawFluxes); % since bounds 0 to 1000
+0129 %Now get the metabolite
+0130 metSel = m.S(:,sel) > 0; % positive coeff (forward usage)
+0131 %now find the reaction with the largest consumption of this protein
+0132 protFluxes = m.S(metSel,:).' .* res.x; %negative
+0133 [~,rxnSel] = min(protFluxes);
+0134 kcatList = [kcatList, rxnSel];
+0135 rxn = m.rxns(rxnSel);
+0136 targetSubRxn = strcmp(m.ec.rxns, rxn);
+0137 if ~strcmp(m.ec.source(targetSubRxn),'sensitivityTuning')
+0138 oldNote = m.ec.notes{targetSubRxn};
+0139 newNote = ['preTuneKcat=' num2str(m.ec.kcat(targetSubRxn)) ' | source:' m.ec.source{targetSubRxn}];
+0140 if ~isempty(oldNote)
+0141 newNote = [oldNote '; ' newNote];
+0142 end
+0143 m.ec.notes{targetSubRxn} = newNote;
+0144 end
+0145 m.ec.kcat(targetSubRxn) = m.ec.kcat(targetSubRxn) .* foldChange;
+0146 m.ec.source(targetSubRxn) = {'sensitivityTuning'};
+0147 m = applyKcatConstraints(m,targetSubRxn);
+0148 end
+0149
+0150 else
+0151 origRxns = extractAfter(m.ec.rxns,4);
+0152 %find the reactions involved in proteins to be ignored
+0153 idxToIgnore = cellfun(@(x) find(m.ec.rxnEnzMat(:, strcmpi(m.ec.enzymes, x))), protToIgnore, 'UniformOutput', false);
+0154 %create an unique vector
+0155 idxToIgnore = unique(cat(1, idxToIgnore{:}));
+0156 %get the correct idx in model.rxns
+0157 idxToIgnore = cellfun(@(x) find(strcmpi(m.rxns, x)), origRxns(idxToIgnore));
+0158 iteration = 1;
+0159 while true
+0160 res = solveLP(m,0); %skip parsimonius, only takes time
+0161 if isempty(res.f)
+0162 printOrange('WARNING: The model has no feasible solution with the increased kcats, tuning stops.\n');
+0163 break;
+0164 end
+0165 if ~isnan(lastGrowth) && res.f <= lastGrowth + growthStallTol
+0166 printOrange('No growth increase from increased kcats - check if the constraints on the uptake reactions are too tight.\n');
+0167 break;
+0168 end
+0169 lastGrowth = res.f;
+0170 if (lastGrowth >= desiredGrowthRate)
+0171 break;
+0172 end
+0173 %If you get an error here, it is likely due to numerical issues in the solver
+0174 %The trick where we don't allow low kcats is to fix that, but maybe
+0175 %it is not enough.
+0176 if verbose; disp(['Iteration ' num2str(iteration) ': Growth: ' num2str(lastGrowth)]); end
+0177 iteration = iteration + 1;
+0178 %find the highest protein usage flux
+0179 protPoolStoich = m.S(strcmp(m.mets, 'prot_pool'),:).';
+0180 protPoolStoich(idxToIgnore) = 0;
+0181 if ~any(res.x .* protPoolStoich < 0)
+0182 printOrange('WARNING: No reaction draws on the protein pool, tuning cannot continue.\n');
+0183 break;
+0184 end
+0185 [~,sel] = min(res.x .* protPoolStoich); %max consumption
+0186 kcatList = [kcatList, sel];
+0187 rxn = m.rxns(sel.');
+0188 targetSubRxns = strcmp(origRxns, rxn);
+0189 m.ec.kcat(targetSubRxns) = m.ec.kcat(targetSubRxns) .* foldChange;
+0190 m = applyKcatConstraints(m,rxn);
+0191 end
+0192 end
+0193
+0194 kcatList = unique(kcatList);
+0195 tunedKcats.rxns = m.rxns(kcatList);
+0196 tunedKcats.rxnNames = m.rxnNames(kcatList);
+0197 if ~model.ec.geckoLight
+0198 [~, rxnIdx] = ismember(tunedKcats.rxns,m.ec.rxns);
+0199 else
+0200 [~, rxnIdx] = ismember(tunedKcats.rxns,origRxns);
+0201 end
+0202 tunedKcats.enzymes = cell(numel(kcatList),1);
+0203 for i=1:numel(rxnIdx)
+0204 [~, metIdx] = find(m.ec.rxnEnzMat(rxnIdx(i),:));
+0205 tunedKcats.enzymes{i} = strjoin(m.ec.enzymes(metIdx),';');
+0206 end
+0207 tunedKcats.oldKcat = model.ec.kcat(rxnIdx);
+0208 tunedKcats.newKcat = m.ec.kcat(rxnIdx);
+0209 tunedKcats.source = model.ec.source(rxnIdx);
+0210
+0211 model = m;
+0212 end
sigmaFitter Fit the average enzyme saturation factor of an ecModel.
+
+
SYNOPSIS
+
function [model, sigma] = sigmaFitter(model, varargin)
+
+
DESCRIPTION
+
sigmaFitter Fit the average enzyme saturation factor of an ecModel.
+
+ Scans candidate sigma-factors from 0.01 to 1 in steps of 0.01 and picks the
+ one whose simulated growth rate best matches the provided experimental
+ growth rate, then applies it to the model's protein pool constraint.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ growthRate : double
+ growth rate that should be reached (default: read from the model
+ adapter).
+ Ptot : double
+ total cellular protein content in g/gDCW (default: read from the model
+ adapter; if not specified there, 0.5 g/gDCW is assumed).
+ f : double
+ estimated fraction of enzymes in the model (default: read from the
+ model adapter; if not specified there, 0.5 is assumed).
+ makePlot : logical
+ whether a plot should be made (default true).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ ecModel with protein pool exchange upper bound adapted to the optimal
+ sigma-factor.
+ sigma : double
+ optimal sigma-factor.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, sigma] = sigmaFitter(model);
+ [model, sigma] = sigmaFitter(model, 'makePlot', false);
+ [model, sigma] = sigmaFitter(model, growthRate, Ptot, f, makePlot, modelAdapter);
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [model, sigma] = sigmaFitter(model, varargin)
+0002 % sigmaFitter Fit the average enzyme saturation factor of an ecModel.
+0003 %
+0004 % Scans candidate sigma-factors from 0.01 to 1 in steps of 0.01 and picks the
+0005 % one whose simulated growth rate best matches the provided experimental
+0006 % growth rate, then applies it to the model's protein pool constraint.
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % model : struct
+0011 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0012 %
+0013 % Name-Value Arguments
+0014 % --------------------
+0015 % growthRate : double
+0016 % growth rate that should be reached (default: read from the model
+0017 % adapter).
+0018 % Ptot : double
+0019 % total cellular protein content in g/gDCW (default: read from the model
+0020 % adapter; if not specified there, 0.5 g/gDCW is assumed).
+0021 % f : double
+0022 % estimated fraction of enzymes in the model (default: read from the
+0023 % model adapter; if not specified there, 0.5 is assumed).
+0024 % makePlot : logical
+0025 % whether a plot should be made (default true).
+0026 % modelAdapter : ModelAdapter
+0027 % a loaded model adapter (default: the current default model adapter).
+0028 %
+0029 % Returns
+0030 % -------
+0031 % model : struct
+0032 % ecModel with protein pool exchange upper bound adapted to the optimal
+0033 % sigma-factor.
+0034 % sigma : double
+0035 % optimal sigma-factor.
+0036 %
+0037 % Examples
+0038 % --------
+0039 % % optional arguments may be given positionally or as name-value pairs:
+0040 % [model, sigma] = sigmaFitter(model);
+0041 % [model, sigma] = sigmaFitter(model, 'makePlot', false);
+0042 % [model, sigma] = sigmaFitter(model, growthRate, Ptot, f, makePlot, modelAdapter);
+0043
+0044 p = parseGECKOargs(varargin, { ...
+0045 'growthRate', []; ...
+0046 'Ptot', []; ...
+0047 'f', []; ...
+0048 'makePlot', true; ...
+0049 'modelAdapter', []});
+0050 growthRate = p.growthRate;
+0051 Ptot = p.Ptot;
+0052 f = p.f;
+0053 makePlot = p.makePlot;
+0054 modelAdapter = p.modelAdapter;
+0055
+0056 if isempty(modelAdapter)
+0057 modelAdapter = ModelAdapterManager.getDefault();
+0058 if isempty(modelAdapter)
+0059 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0060 end
+0061 end
+0062
+0063 if isempty(makePlot)
+0064 makePlot = true;
+0065 end
+0066 if isempty(f)
+0067 f = modelAdapter.getParameters().f;
+0068 end
+0069 if isempty(Ptot)
+0070 Ptot = modelAdapter.getParameters().Ptot;
+0071 end
+0072 if isempty(growthRate)
+0073 growthRate = modelAdapter.getParameters().gR_exp;
+0074 end
+0075
+0076 objValues = [];
+0077 errors = [];
+0078 sigParam = [];
+0079 objPos = find(model.c);
+0080 %Relax bounds for the objective function
+0081 model.lb(objPos) = 0;
+0082 model.ub(objPos) = 1000;
+0083 hsSol=[];
+0084 for i=1:100
+0085 %Constrains the ecModel with the i-th sigma factor
+0086 sigma = i/100;
+0087 model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter);
+0088 [solution, hsSol] = solveLP(model,0,[],hsSol);
+0089 if isempty(solution.x)
+0090 solution.x=zeros(length(model.rxns),1);
+0091 end
+0092 objValues = [objValues; solution.x(objPos)];
+0093 error = abs(((growthRate-solution.x(objPos))/growthRate)*100);
+0094 errors = [errors; error];
+0095 error = num2str(((growthRate-solution.x(objPos))/growthRate)*100);
+0096 sigParam = [sigParam; sigma];
+0097 end
+0098 [~, minIndx] = min(errors);
+0099 sigma = sigParam(minIndx);
+0100 % The loop above leaves the protein pool sized for the *last* sigma tried
+0101 % (i=100, i.e. sigma=1), not the best-fitting one found by the search --
+0102 % re-apply it here so the returned model actually matches the returned
+0103 % sigma, per this function's own documented contract ("ecModel with
+0104 % protein pool exchange upper bound adapted to the optimal sigma-factor").
+0105 model = setProtPoolSize(model, Ptot, f, sigma, modelAdapter);
+0106 if makePlot
+0107 figure
+0108 plot(sigParam,errors,'LineWidth',5)
+0109 title('Sigma fitting')
+0110 xlabel('Average enzyme saturation [-]')
+0111 ylabel('Absolute relative error [%]')
+0112 end
+0113 end
truncateValues Round values in selected columns to six significant digits.
+
+
SYNOPSIS
+
function table = truncateValues(table,cols)
+
+
DESCRIPTION
+
truncateValues Round values in selected columns to six significant digits.
+
+ Rounds each value in the given columns of a cell array or table to six
+ significant digits, based on the value's order of magnitude.
+
+ Parameters
+ ----------
+ table : cell or table
+ a cell array or table where some columns have values that should be
+ truncated.
+ cols : double
+ index or indices of columns with values to be truncated.
+
+ Returns
+ -------
+ table : cell or table
+ the input with the selected columns rounded.
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function table = truncateValues(table,cols)
+0002 % truncateValues Round values in selected columns to six significant digits.
+0003 %
+0004 % Rounds each value in the given columns of a cell array or table to six
+0005 % significant digits, based on the value's order of magnitude.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % table : cell or table
+0010 % a cell array or table where some columns have values that should be
+0011 % truncated.
+0012 % cols : double
+0013 % index or indices of columns with values to be truncated.
+0014 %
+0015 % Returns
+0016 % -------
+0017 % table : cell or table
+0018 % the input with the selected columns rounded.
+0019
+0020 [m,n] = size(table);
+0021 for i = 1:m
+0022 for j = cols
+0023 orderMagn = max([ceil(log10(abs(table{i,j}))),0]);
+0024 table{i,j} = round(table{i,j},6-orderMagn);
+0025 end
+0026 end
+0027 end
+0028
+0029 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
calculateFfactor Compute the f factor for an ecModel.
+
+
SYNOPSIS
+
function f = calculateFfactor(model, varargin)
+
+
DESCRIPTION
+
calculateFfactor Compute the f factor for an ecModel.
+
+ Computes the f factor, as a proxy to the mass fraction of proteins
+ accounted for in an ecModel out of the total protein content in cells.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ protData : struct
+ structure with proteome data, from loadProtData (by default it instead
+ attempts to load data/paxDB.tsv).
+ enzymes : cell
+ list of enzymes (default model.ec.enzymes).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ f : double
+ f-factor.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ f = calculateFfactor(model);
+ f = calculateFfactor(model, 'enzymes', enzymes);
+
+ See also
+ --------
+ loadProtData, getProtFromProteomics
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function f = calculateFfactor(model, varargin)
+0002 % calculateFfactor Compute the f factor for an ecModel.
+0003 %
+0004 % Computes the f factor, as a proxy to the mass fraction of proteins
+0005 % accounted for in an ecModel out of the total protein content in cells.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % model : struct
+0010 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0011 %
+0012 % Name-Value Arguments
+0013 % --------------------
+0014 % protData : struct
+0015 % structure with proteome data, from loadProtData (by default it instead
+0016 % attempts to load data/paxDB.tsv).
+0017 % enzymes : cell
+0018 % list of enzymes (default model.ec.enzymes).
+0019 % modelAdapter : ModelAdapter
+0020 % a loaded model adapter (default: the current default model adapter).
+0021 %
+0022 % Returns
+0023 % -------
+0024 % f : double
+0025 % f-factor.
+0026 %
+0027 % Examples
+0028 % --------
+0029 % % optional arguments may be given positionally or as name-value pairs:
+0030 % f = calculateFfactor(model);
+0031 % f = calculateFfactor(model, 'enzymes', enzymes);
+0032 %
+0033 % See also
+0034 % --------
+0035 % loadProtData, getProtFromProteomics
+0036
+0037 p = parseGECKOargs(varargin, { ...
+0038 'protData', []; ...
+0039 'enzymes', []; ...
+0040 'modelAdapter', []});
+0041 protData = p.protData;
+0042 enzymes = p.enzymes;
+0043 modelAdapter = p.modelAdapter;
+0044
+0045 if isempty(modelAdapter)
+0046 modelAdapter = ModelAdapterManager.getDefault();
+0047 if isempty(modelAdapter)
+0048 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0049 end
+0050 end
+0051 params = modelAdapter.getParameters();
+0052
+0053 if isempty(enzymes)
+0054 enzymes = model.ec.enzymes;
+0055 end
+0056
+0057 % Gather proteome data in protData structure
+0058 if isempty(protData)
+0059 if exist(fullfile(params.path,'data','paxDB.tsv'),'file')
+0060 protData = fullfile(params.path,'data','paxDB.tsv');
+0061 else
+0062 printOrange('WARNING: No proteomics data is provided or can be found. Default f value of 0.5 is returned.\n');
+0063 f = 0.5;
+0064 return
+0065 end
+0066 end
+0067
+0068 if ischar(protData) && endsWith(protData,'paxDB.tsv')
+0069 % Gather Uniprot database for finding MW. Only needed here, to map paxDB.tsv's gene
+0070 % identifiers onto UniProt IDs and molecular weights --- a protData struct supplied by
+0071 % the caller already carries UniProt IDs and needs no lookup, so this stays inside the
+0072 % branch that actually uses it rather than running (and potentially downloading or
+0073 % reading UniProt data) on every call.
+0074 uniprotDB = loadDatabases('uniprot', modelAdapter);
+0075 uniprotDB = uniprotDB.uniprot;
+0076
+0077 fID = fopen(fullfile(protData),'r');
+0078 fileContent = textscan(fID,'%s','delimiter','\n');
+0079 headerLines = find(startsWith(fileContent{1},'#'),1,'last');
+0080 fclose(fID);
+0081
+0082 %Read data file, excluding headerlines
+0083 fID = fopen(fullfile(protData),'r');
+0084 fileContent = textscan(fID,'%s %s %f','delimiter','\t','HeaderLines',headerLines);
+0085 genes = fileContent{2};
+0086 %Remove internal geneIDs modifiers
+0087 genes = regexprep(genes,'^\d+\.','');
+0088 level = fileContent{3};
+0089 fclose(fID);
+0090 [a,b] = ismember(genes,uniprotDB.genes);
+0091 uniprot = uniprotDB.ID(b(a));
+0092 level(~a) = [];
+0093 clear protData
+0094 protData.uniprotIDs = uniprot;
+0095 protData.level = level;
+0096 % Get MW and abundance (unit does not matter, f is fraction)
+0097 [~,idx] = ismember(protData.uniprotIDs,uniprotDB.ID);
+0098 protData.MW = uniprotDB.MW(idx);
+0099 protData.abundances = protData.level .* protData.MW;
+0100 end
+0101
+0102 avgAbundances = mean(protData.abundances,2);
+0103 totalProt = sum(avgAbundances,'omitnan');
+0104
+0105 % Get enzymes in model
+0106 enzymesInModel = ismember(protData.uniprotIDs,enzymes);
+0107 totalEnz = sum(avgAbundances(enzymesInModel),'omitnan');
+0108
+0109 f = totalEnz/totalProt;
+0110 end
constrainEnzConcs Constrain enzyme usages by their concentration.
+
+
SYNOPSIS
+
function model = constrainEnzConcs(model, varargin)
+
+
DESCRIPTION
+
constrainEnzConcs Constrain enzyme usages by their concentration.
+
+ Constrain enzyme usages by their concentration as provided in
+ model.ec.concs. For enzymes with non-NaN entries in model.ec.concs, their
+ enzyme usage reaction will no longer draw from the protein pool, but is
+ rather constraint by the measured protein abundance.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure) with enzyme
+ concentrations in model.ec.concs.
+
+ Name-Value Arguments
+ --------------------
+ removeConstraints : logical
+ whether enzyme concentration constraints should be removed
+ (model.ec.concs will remain unchanged) (default false).
+
+ Returns
+ -------
+ model : struct
+ an ecModel constraint with available enzyme concentrations.
+
+ Notes
+ -----
+ To populate model.ec.concs you should run fillEnzConcs.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ model = constrainEnzConcs(model);
+ model = constrainEnzConcs(model, 'removeConstraints', true);
+
+ See also
+ --------
+ fillEnzConcs
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function model = constrainEnzConcs(model, varargin)
+0002 % constrainEnzConcs Constrain enzyme usages by their concentration.
+0003 %
+0004 % Constrain enzyme usages by their concentration as provided in
+0005 % model.ec.concs. For enzymes with non-NaN entries in model.ec.concs, their
+0006 % enzyme usage reaction will no longer draw from the protein pool, but is
+0007 % rather constraint by the measured protein abundance.
+0008 %
+0009 % Parameters
+0010 % ----------
+0011 % model : struct
+0012 % an ecModel in GECKO 3 format (with ecModel.ec structure) with enzyme
+0013 % concentrations in model.ec.concs.
+0014 %
+0015 % Name-Value Arguments
+0016 % --------------------
+0017 % removeConstraints : logical
+0018 % whether enzyme concentration constraints should be removed
+0019 % (model.ec.concs will remain unchanged) (default false).
+0020 %
+0021 % Returns
+0022 % -------
+0023 % model : struct
+0024 % an ecModel constraint with available enzyme concentrations.
+0025 %
+0026 % Notes
+0027 % -----
+0028 % To populate model.ec.concs you should run fillEnzConcs.
+0029 %
+0030 % Examples
+0031 % --------
+0032 % % optional arguments may be given positionally or as name-value pairs:
+0033 % model = constrainEnzConcs(model);
+0034 % model = constrainEnzConcs(model, 'removeConstraints', true);
+0035 %
+0036 % See also
+0037 % --------
+0038 % fillEnzConcs
+0039
+0040 %Enzyme with NaN entry in model.ec.concs => draw from prot_pool
+0041 %Enzyme with numeric entry in model.ec.concs => exchange reaction with
+0042 %enzyme level as UB
+0043
+0044 p = parseGECKOargs(varargin, { ...
+0045 'removeConstraints', false});
+0046 removeConstraints = p.removeConstraints;
+0047
+0048 %Get indices of usage reactions
+0049 usageRxns = strcat('usage_prot_',model.ec.enzymes);
+0050 [~, usageRxnsIdx] = ismember(usageRxns, model.rxns);
+0051
+0052 if any(usageRxnsIdx == 0)
+0053 error('Usage reactions are not defined for all enzymes. This is done by makeEcModel.')
+0054 end
+0055 %Get index of protein pool metabolite
+0056 protPoolIdx = find(ismember(model.mets,'prot_pool'));
+0057 if ~any(protPoolIdx)
+0058 error('Cannot find protein pool pseudometabolite.')
+0059 end
+0060
+0061 %Protein that should be constraint by UB
+0062 if removeConstraints
+0063 protCons = [];
+0064 else
+0065 protCons = ~isnan(model.ec.concs);
+0066 end
+0067
+0068 %Set all reactions to draw from prot_pool (forward: prot_pool -> prot_<id>)
+0069 model.S(protPoolIdx, usageRxnsIdx) = -1;
+0070 model.lb(usageRxnsIdx) = 0;
+0071 model.ub(usageRxnsIdx) = 1000;
+0072
+0073 %If non-NaN in model.ec.concs, then constrain by UB
+0074 if any(protCons)
+0075 % model.S(protPoolIdx, usageRxnsIdx(protCons)) = 0; % Since GECKO 3.2.0
+0076 model.ub(usageRxnsIdx(protCons)) = model.ec.concs(protCons);
+0077 end
+0078 end
constrainFluxData Constrain ecModel fluxes to provided flux data.
+
+
SYNOPSIS
+
function model = constrainFluxData(model, varargin)
+
+
DESCRIPTION
+
constrainFluxData Constrain ecModel fluxes to provided flux data.
+
+ Constrains fluxes to the data that is provided in the fluxData structure,
+ which itself is read by loadFluxData from data/fluxData.tsv.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ fluxData : struct
+ structure with flux data, with the fields:
+
+ - conds : sampling condition.
+ - Ptot : total protein (g/gDCW).
+ - grRate : growth rate (1/h).
+ - exchFluxes : exchange fluxes (mmol/gDCWh).
+ - exchMets : exchanged metabolites, matching exchFluxes.
+ - exchRxnIDs : exchange reaction IDs, matching exchMets.
+ condition : double or char
+ either index number or name of the sample condition in fluxData.conds
+ (default 1).
+ maxMinGrowth : char
+ 'max' if the provided growth rate should be set as maximum growth rate
+ (= upper bound), or 'min' if it should be set as minimum growth rate
+ (= lower bound). The latter option is suitable if minimization of
+ prot_pool_exchange is used as objective function (default 'max').
+ looseStrictFlux : char or double
+ how strictly constrained the exchange fluxes should be (default
+ 'loose'):
+
+ - 'loose' : the exchange fluxes are constraint only by the "outer
+ bounds". If exchFluxes(i) > 0, LB = 0 and UB = exchFluxes(i). If
+ exchFluxes(i) < 0, LB = exchFluxes(i) and UB = 0.
+ - 0-100 : LB and UB constraints are set with a specified percentage of
+ variance around exchFluxes. If 10 is specified, LB = exchFluxes*0.95
+ and UB = exchFluxes*1.05. This allows for 10% variance around the
+ exchFluxes values, but strictly forces a flux through the exchRxns.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ model : struct
+ an ecModel where fluxes are constraint.
+
+ Notes
+ -----
+ If a provided constraint is either -1000 or 1000, then the function will
+ update the reaction lower and upper bound to either allow uptake or
+ excretion, irrespective of what option is given as the looseStrictFlux
+ parameter.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ model = constrainFluxData(model);
+ model = constrainFluxData(model, 'condition', 2);
+
+ See also
+ --------
+ loadFluxData
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function model = constrainFluxData(model, varargin)
+0002 % constrainFluxData Constrain ecModel fluxes to provided flux data.
+0003 %
+0004 % Constrains fluxes to the data that is provided in the fluxData structure,
+0005 % which itself is read by loadFluxData from data/fluxData.tsv.
+0006 %
+0007 % Parameters
+0008 % ----------
+0009 % model : struct
+0010 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0011 %
+0012 % Name-Value Arguments
+0013 % --------------------
+0014 % fluxData : struct
+0015 % structure with flux data, with the fields:
+0016 %
+0017 % - conds : sampling condition.
+0018 % - Ptot : total protein (g/gDCW).
+0019 % - grRate : growth rate (1/h).
+0020 % - exchFluxes : exchange fluxes (mmol/gDCWh).
+0021 % - exchMets : exchanged metabolites, matching exchFluxes.
+0022 % - exchRxnIDs : exchange reaction IDs, matching exchMets.
+0023 % condition : double or char
+0024 % either index number or name of the sample condition in fluxData.conds
+0025 % (default 1).
+0026 % maxMinGrowth : char
+0027 % 'max' if the provided growth rate should be set as maximum growth rate
+0028 % (= upper bound), or 'min' if it should be set as minimum growth rate
+0029 % (= lower bound). The latter option is suitable if minimization of
+0030 % prot_pool_exchange is used as objective function (default 'max').
+0031 % looseStrictFlux : char or double
+0032 % how strictly constrained the exchange fluxes should be (default
+0033 % 'loose'):
+0034 %
+0035 % - 'loose' : the exchange fluxes are constraint only by the "outer
+0036 % bounds". If exchFluxes(i) > 0, LB = 0 and UB = exchFluxes(i). If
+0037 % exchFluxes(i) < 0, LB = exchFluxes(i) and UB = 0.
+0038 % - 0-100 : LB and UB constraints are set with a specified percentage of
+0039 % variance around exchFluxes. If 10 is specified, LB = exchFluxes*0.95
+0040 % and UB = exchFluxes*1.05. This allows for 10% variance around the
+0041 % exchFluxes values, but strictly forces a flux through the exchRxns.
+0042 % modelAdapter : ModelAdapter
+0043 % a loaded model adapter (default: the current default model adapter).
+0044 %
+0045 % Returns
+0046 % -------
+0047 % model : struct
+0048 % an ecModel where fluxes are constraint.
+0049 %
+0050 % Notes
+0051 % -----
+0052 % If a provided constraint is either -1000 or 1000, then the function will
+0053 % update the reaction lower and upper bound to either allow uptake or
+0054 % excretion, irrespective of what option is given as the looseStrictFlux
+0055 % parameter.
+0056 %
+0057 % Examples
+0058 % --------
+0059 % % optional arguments may be given positionally or as name-value pairs:
+0060 % model = constrainFluxData(model);
+0061 % model = constrainFluxData(model, 'condition', 2);
+0062 %
+0063 % See also
+0064 % --------
+0065 % loadFluxData
+0066
+0067 p = parseGECKOargs(varargin, { ...
+0068 'fluxData', []; ...
+0069 'condition', []; ...
+0070 'maxMinGrowth', []; ...
+0071 'looseStrictFlux', []; ...
+0072 'modelAdapter', []});
+0073 fluxData = p.fluxData;
+0074 condition = p.condition;
+0075 maxMinGrowth = p.maxMinGrowth;
+0076 looseStrictFlux = p.looseStrictFlux;
+0077 modelAdapter = p.modelAdapter;
+0078
+0079 if isempty(modelAdapter)
+0080 modelAdapter = ModelAdapterManager.getDefault();
+0081 if isempty(modelAdapter)
+0082 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0083 end
+0084 end
+0085 params = modelAdapter.getParameters();
+0086
+0087 if isempty(looseStrictFlux)
+0088 looseStrictFlux = 'loose';
+0089 end
+0090
+0091 if isempty(maxMinGrowth)
+0092 maxMinGrowth = 'max';
+0093 end
+0094
+0095 if isempty(fluxData)
+0096 fluxData = loadFluxData(fullfile(params.path,'data','fluxData.tsv'),modelAdapter);
+0097 end
+0098
+0099 if isempty(condition)
+0100 condition = 1;
+0101 elseif ~isnumeric(condition)
+0102 idx = find(strcmp(fluxData.conds,condition));
+0103 if isempty(condition)
+0104 error(['Condition ' condition ' cannot be found in fluxData'])
+0105 else
+0106 condition = idx;
+0107 end
+0108 end
+0109
+0110 % Select the exchange fluxes of specified condition
+0111 fluxData.exchFluxes = fluxData.exchFluxes(condition,:);
+0112
+0113 %Set original c-source to zero
+0114 model = setParam(model,'eq',params.c_source,0);
+0115 %Set growth
+0116 switch maxMinGrowth
+0117 case'max'
+0118 model = setParam(model,'lb',params.bioRxn,0);
+0119 model = setParam(model,'ub',params.bioRxn,fluxData.grRate(condition));
+0120 case'min'
+0121 model = setParam(model,'lb',params.bioRxn,fluxData.grRate(condition));
+0122 model = setParam(model,'ub',params.bioRxn,1000);
+0123 end
+0124
+0125 naFlux = isnan(fluxData.exchFluxes);
+0126 negFlux = lt(fluxData.exchFluxes,0); % less than 0
+0127 ub = fluxData.exchFluxes(~(negFlux | naFlux));
+0128 posFlux = fluxData.exchRxnIDs(~(negFlux | naFlux));
+0129 lb = fluxData.exchFluxes(negFlux & ~naFlux);
+0130 negFlux = fluxData.exchRxnIDs(negFlux & ~naFlux);
+0131
+0132 switch looseStrictFlux
+0133 case'loose'
+0134 model = setParam(model,'lb',negFlux,lb);
+0135 model = setParam(model,'ub',negFlux,0);
+0136 model = setParam(model,'lb',posFlux,0);
+0137 model = setParam(model,'ub',posFlux,ub);
+0138 otherwise
+0139 model = setParam(model,'var',negFlux,lb,looseStrictFlux);
+0140 model = setParam(model,'var',posFlux,ub,looseStrictFlux);
+0141 end
+0142 extremeFlux = find(abs(fluxData.exchFluxes)==1000);
+0143 if any(extremeFlux)
+0144 exchFlux = fluxData.exchFluxes(extremeFlux);
+0145 if any(exchFlux==-1000)
+0146 model = setParam(model,'lb',fluxData.exchRxnIDs(extremeFlux(exchFlux==-1000)),-1000);
+0147 model = setParam(model,'ub',fluxData.exchRxnIDs(extremeFlux(exchFlux==-1000)),0);
+0148 end
+0149 if any(exchFlux==1000)
+0150 model = setParam(model,'lb',fluxData.exchRxnIDs(extremeFlux(exchFlux==1000)),0);
+0151 model = setParam(model,'ub',fluxData.exchRxnIDs(extremeFlux(exchFlux==1000)),1000);
+0152 end
+0153 end
+0154 end
fillEnzConcs Populate model.ec.concs from proteome data.
+
+
SYNOPSIS
+
function model = fillEnzConcs(model, protData, varargin)
+
+
DESCRIPTION
+
fillEnzConcs Populate model.ec.concs from proteome data.
+
+ Uses the protein concentrations from protData to fill model.ec.concs.
+ Protein levels should be provided in mg/gDCW. If no data is provided for a
+ particular protein, its level is NaN. Existing entries in model.ec.concs
+ are overwritten.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+ protData : struct
+ structure with proteome data, from loadProtFluxData, with the fields:
+
+ - uniprotIDs : cell array with Uniprot IDs matching protData.abundances.
+ - abundances : matrix of proteomics data.
+
+ Name-Value Arguments
+ --------------------
+ dataCol : double
+ number indicating the column in protData.abundances that contains the
+ relevant protein concentrations. protData may contain data from
+ multiple conditions/samples/experiments, each with their own column in
+ protData.abundances (default 1).
+
+ Returns
+ -------
+ model : struct
+ an ecModel where model.ec.concs is populated with protein
+ concentrations.
+
+ Notes
+ -----
+ To also constrain the model with the content of model.ec.concs, you should
+ run constrainEnzConcs.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ model = fillEnzConcs(model, protData);
+ model = fillEnzConcs(model, protData, 'dataCol', 2);
+
+ See also
+ --------
+ constrainEnzConcs, loadProtFluxData
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function model = fillEnzConcs(model, protData, varargin)
+0002 % fillEnzConcs Populate model.ec.concs from proteome data.
+0003 %
+0004 % Uses the protein concentrations from protData to fill model.ec.concs.
+0005 % Protein levels should be provided in mg/gDCW. If no data is provided for a
+0006 % particular protein, its level is NaN. Existing entries in model.ec.concs
+0007 % are overwritten.
+0008 %
+0009 % Parameters
+0010 % ----------
+0011 % model : struct
+0012 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0013 % protData : struct
+0014 % structure with proteome data, from loadProtFluxData, with the fields:
+0015 %
+0016 % - uniprotIDs : cell array with Uniprot IDs matching protData.abundances.
+0017 % - abundances : matrix of proteomics data.
+0018 %
+0019 % Name-Value Arguments
+0020 % --------------------
+0021 % dataCol : double
+0022 % number indicating the column in protData.abundances that contains the
+0023 % relevant protein concentrations. protData may contain data from
+0024 % multiple conditions/samples/experiments, each with their own column in
+0025 % protData.abundances (default 1).
+0026 %
+0027 % Returns
+0028 % -------
+0029 % model : struct
+0030 % an ecModel where model.ec.concs is populated with protein
+0031 % concentrations.
+0032 %
+0033 % Notes
+0034 % -----
+0035 % To also constrain the model with the content of model.ec.concs, you should
+0036 % run constrainEnzConcs.
+0037 %
+0038 % Examples
+0039 % --------
+0040 % % optional arguments may be given positionally or as name-value pairs:
+0041 % model = fillEnzConcs(model, protData);
+0042 % model = fillEnzConcs(model, protData, 'dataCol', 2);
+0043 %
+0044 % See also
+0045 % --------
+0046 % constrainEnzConcs, loadProtFluxData
+0047
+0048 p = parseGECKOargs(varargin, { ...
+0049 'dataCol', 1});
+0050 dataCol = p.dataCol;
+0051 if isempty(dataCol); dataCol = 1; end
+0052
+0053 uniprotIDs = protData.uniprotIDs;
+0054 abundances = protData.abundances(:,dataCol);
+0055
+0056 %Redefine an empty model.ec.concs vector
+0057 model.ec.concs=nan(numel(model.ec.enzymes),1);
+0058
+0059 [a,b] = ismember(uniprotIDs, model.ec.enzymes);
+0060 model.ec.concs(b(a)) = abundances(a);
+0061 end
flexibilizeEnzConcs Flexibilize enzyme concentrations of an ecModel.
+
+
SYNOPSIS
+
function [model, flexEnz] = flexibilizeEnzConcs(model, varargin)
+
+
DESCRIPTION
+
flexibilizeEnzConcs Flexibilize enzyme concentrations of an ecModel.
+
+ Flexibilize enzyme concentration of an ecModel constrained with proteomics
+ data. The upper bound of the protein usage reaction is changed, while the
+ concentrations in ecModel.ec.concs remain unchanged.
+
+ If no (more) limiting enzyme concentrations can be found, it might be the
+ protein pool exchange that is limiting growth. In that case, an attempt
+ will be made to relax the protein pool exchange reaction, and if the growth
+ rate indeed increases, it is suggested to set the protein pool exchange
+ unconstrained (ub=1000) before again running flexibilizeEnzConcs. Such
+ situations, where a proteomics integrated ecModel is overconstrained, may
+ occur if the ecModel should be able to simulate maximum growth rate (from
+ e.g. batch cultivation).
+
+ If relaxing the protein pool exchange does not increase the growth rate,
+ then this is not due to enzyme constraints, but rather an issue with the
+ metabolic network itself, or the set nutrient exchange is not sufficient.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ expGrowth : double
+ estimated experimental growth rate. If not specified, the value will be
+ read from the model adapter.
+ foldChange : double
+ a value how much to increase the enzyme concentration (default 2).
+ iterPerEnzyme : double
+ the number of iterations that an enzyme can be increased. A zero number
+ can be defined; if zero is defined no limit will be set, and it will
+ increase the enzyme concentration until it reaches the defined growth
+ rate (default 5).
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+ verbose : logical
+ whether progress should be reported (default true).
+
+ Returns
+ -------
+ model : struct
+ ecModel where the constraint of measured enzyme concentrations have
+ been flexibilized (=relaxed) in the S-matrix, to allow the model to
+ reach the growth rate defined in expGrowth, while the values in
+ ecModel.ec.concs have remained untouched.
+ flexEnz : struct
+ array with information about flexibilized proteins, with the fields:
+
+ - uniprotIDs : enzymes whose usage UB was flexibilized.
+ - oldConcs : original concentrations, from model.ec.concs.
+ - flexConcs : flexibilized concentrations, new UB in model.
+ - ratioIncr : ratio by which the concentration increased; the enzymes
+ will be sorted by this field.
+ - frequence : numeric how often the enzyme has been step-wise
+ flexibilized.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [model, flexEnz] = flexibilizeEnzConcs(model);
+ [model, flexEnz] = flexibilizeEnzConcs(model, 'foldChange', 3);
+
+ See also
+ --------
+ constrainEnzConcs, getConcControlCoeffs
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
getConcControlCoeffs getConcControlCoeffs Calculate control coefficients of protein usage.
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function [model, flexEnz] = flexibilizeEnzConcs(model, varargin)
+0002 % flexibilizeEnzConcs Flexibilize enzyme concentrations of an ecModel.
+0003 %
+0004 % Flexibilize enzyme concentration of an ecModel constrained with proteomics
+0005 % data. The upper bound of the protein usage reaction is changed, while the
+0006 % concentrations in ecModel.ec.concs remain unchanged.
+0007 %
+0008 % If no (more) limiting enzyme concentrations can be found, it might be the
+0009 % protein pool exchange that is limiting growth. In that case, an attempt
+0010 % will be made to relax the protein pool exchange reaction, and if the growth
+0011 % rate indeed increases, it is suggested to set the protein pool exchange
+0012 % unconstrained (ub=1000) before again running flexibilizeEnzConcs. Such
+0013 % situations, where a proteomics integrated ecModel is overconstrained, may
+0014 % occur if the ecModel should be able to simulate maximum growth rate (from
+0015 % e.g. batch cultivation).
+0016 %
+0017 % If relaxing the protein pool exchange does not increase the growth rate,
+0018 % then this is not due to enzyme constraints, but rather an issue with the
+0019 % metabolic network itself, or the set nutrient exchange is not sufficient.
+0020 %
+0021 % Parameters
+0022 % ----------
+0023 % model : struct
+0024 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0025 %
+0026 % Name-Value Arguments
+0027 % --------------------
+0028 % expGrowth : double
+0029 % estimated experimental growth rate. If not specified, the value will be
+0030 % read from the model adapter.
+0031 % foldChange : double
+0032 % a value how much to increase the enzyme concentration (default 2).
+0033 % iterPerEnzyme : double
+0034 % the number of iterations that an enzyme can be increased. A zero number
+0035 % can be defined; if zero is defined no limit will be set, and it will
+0036 % increase the enzyme concentration until it reaches the defined growth
+0037 % rate (default 5).
+0038 % modelAdapter : ModelAdapter
+0039 % a loaded model adapter (default: the current default model adapter).
+0040 % verbose : logical
+0041 % whether progress should be reported (default true).
+0042 %
+0043 % Returns
+0044 % -------
+0045 % model : struct
+0046 % ecModel where the constraint of measured enzyme concentrations have
+0047 % been flexibilized (=relaxed) in the S-matrix, to allow the model to
+0048 % reach the growth rate defined in expGrowth, while the values in
+0049 % ecModel.ec.concs have remained untouched.
+0050 % flexEnz : struct
+0051 % array with information about flexibilized proteins, with the fields:
+0052 %
+0053 % - uniprotIDs : enzymes whose usage UB was flexibilized.
+0054 % - oldConcs : original concentrations, from model.ec.concs.
+0055 % - flexConcs : flexibilized concentrations, new UB in model.
+0056 % - ratioIncr : ratio by which the concentration increased; the enzymes
+0057 % will be sorted by this field.
+0058 % - frequence : numeric how often the enzyme has been step-wise
+0059 % flexibilized.
+0060 %
+0061 % Examples
+0062 % --------
+0063 % % optional arguments may be given positionally or as name-value pairs:
+0064 % [model, flexEnz] = flexibilizeEnzConcs(model);
+0065 % [model, flexEnz] = flexibilizeEnzConcs(model, 'foldChange', 3);
+0066 %
+0067 % See also
+0068 % --------
+0069 % constrainEnzConcs, getConcControlCoeffs
+0070
+0071 p = parseGECKOargs(varargin, { ...
+0072 'expGrowth', []; ...
+0073 'foldChange', []; ...
+0074 'iterPerEnzyme', []; ...
+0075 'modelAdapter', []; ...
+0076 'verbose', []});
+0077 expGrowth = p.expGrowth;
+0078 foldChange = p.foldChange;
+0079 iterPerEnzyme = p.iterPerEnzyme;
+0080 modelAdapter = p.modelAdapter;
+0081 verbose = p.verbose;
+0082
+0083 if isempty(verbose)
+0084 verbose = true;
+0085 end
+0086 if isempty(modelAdapter)
+0087 modelAdapter = ModelAdapterManager.getDefault();
+0088 if isempty(modelAdapter)
+0089 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0090 end
+0091 end
+0092 params = modelAdapter.getParameters();
+0093
+0094 if isempty(iterPerEnzyme)
+0095 iterPerEnzyme = 5;
+0096 end
+0097
+0098 if isempty(foldChange)
+0099 foldChange = 2;
+0100 end
+0101
+0102 if isempty(expGrowth)
+0103 expGrowth = modelAdapter.getParameters().gR_exp;
+0104 end
+0105
+0106 % If a zero value is defined, not iteration limit will be set
+0107 if iterPerEnzyme == 0
+0108 iterPerEnzyme = inf;
+0109 end
+0110
+0111 bioRxnIdx = getIndexes(model,params.bioRxn,'rxns');
+0112 if model.ub(bioRxnIdx) < expGrowth
+0113 fprintf(['The upper bound of the biomass reaction was %s, while expGrowth '...
+0114 'is %s. The upper bound has been set to expGrowth.'],...
+0115 num2str(model.ub(bioRxnIdx)), num2str(expGrowth))
+0116 model.ub(bioRxnIdx) = expGrowth;
+0117 end
+0118
+0119 % Keep track if protein pool will be flexibilized
+0120 changedProtPool = false;
+0121
+0122 % In case the model have not been protein constrained
+0123 % model = constrainEnzConcs(model);
+0124
+0125 [sol,hs] = solveLP(model);
+0126 predGrowth = sol.f;
+0127
+0128 % Get those proteins with a concentration defined
+0129 protConcs = find(~isnan(model.ec.concs));
+0130
+0131 % Get enzymes names with a concentration value
+0132 proteins = model.ec.enzymes(protConcs);
+0133
+0134 % Store how many time is predicted a enzyme with the highest coeff
+0135 frequence = zeros(numel(proteins),1);
+0136
+0137 flexBreak=false;
+0138 if any(protConcs)
+0139 while predGrowth < expGrowth
+0140
+0141 % Get the control coefficients
+0142 [~, controlCoeffs] = getConcControlCoeffs(model, proteins, foldChange, 0.75);
+0143
+0144 % Stop looking for limiting proteins all coeffs are zero
+0145 if any(controlCoeffs)
+0146
+0147 % Find the maximum coefficient index
+0148 [~,maxIdx] = max(controlCoeffs);
+0149
+0150 % Increase +1 the frequence
+0151 frequence(maxIdx) = frequence(maxIdx) + 1;
+0152
+0153 % Allow to increase the UB maximum five times
+0154 if frequence(maxIdx) <= iterPerEnzyme
+0155
+0156 % Set how much will increase the UB
+0157 increase = foldChange*frequence(maxIdx);
+0158
+0159 % Get usage rxn for the highest coeff
+0160 protUsageIdx = strcmpi(model.rxns, strcat('usage_prot_', proteins(maxIdx)));
+0161
+0162 % replace the UB
+0163 model.ub(protUsageIdx) = model.ec.concs(protConcs(maxIdx)) * (1+increase);
+0164
+0165 % Get the new growth rate
+0166 sol = solveLP(model,0,[],hs);
+0167 predGrowth = sol.f;
+0168
+0169 if verbose
+0170 disp(['Protein ' proteins{maxIdx} ' UB adjusted. Grow: ' num2str(predGrowth)])
+0171 end
+0172 else
+0173 printOrange(['WARNING: Limit has been reached. Protein ' proteins{maxIdx} ' seems to be problematic. Consider changing the kcat.\n']);
+0174 flexBreak=true;
+0175 break
+0176 end
+0177 else
+0178 disp('No (more) limiting enzymes have been found. Attempting to increase protein pool exchange...')
+0179 protPoolIdx = getIndexes(model,'prot_pool_exchange','rxns');
+0180 oldProtPool = model.ub(protPoolIdx);
+0181 tempModel = setParam(model,'ub',protPoolIdx,1000);
+0182 tempModel = setParam(tempModel,'ub',bioRxnIdx,expGrowth);
+0183 sol = solveLP(tempModel);
+0184 if (sol.f-predGrowth)>1e-3 % There is improvement in growth rate
+0185 % Find new protein pool constraint
+0186 predGrowth = sol.f;
+0187 tempModel = setParam(tempModel,'lb',bioRxnIdx,predGrowth);
+0188 % minimise pool usage (solveLP maximises the objective, so use -1)
+0189 tempModel = setParam(tempModel,'obj',protPoolIdx,-1);
+0190 sol = solveLP(tempModel);
+0191 newProtPool = sol.x(protPoolIdx);
+0192 model.ub(protPoolIdx) = newProtPool;
+0193 fprintf(['Changing the upper bound of protein pool exchange from %s ',...
+0194 'to %s enabled a\ngrowth rate of %s. It can be helpful to set ', ...
+0195 'the upper bound of protein\npool exchange to 1000 before ',...
+0196 'running flexibilizeEnzConcs.\n'],num2str(oldProtPool), ...
+0197 num2str(newProtPool),num2str(predGrowth));
+0198 changedProtPool = true;
+0199 else
+0200 fprintf(['Protein pool exchange was also not limiting. Inability to reach growth ',...
+0201 'rate is not related to\nenzyme constraints. Maximum growth rate ',...
+0202 'is %s.\n'], num2str(predGrowth))
+0203 end
+0204 % To return an usable model with these conditions, set the
+0205 % highest growth rate reached as the experimental value
+0206 expGrowth = predGrowth;
+0207 break
+0208 end
+0209 end
+0210 else
+0211 error('Protein concentrations have not been defined. Please run readProteomics and constrainEnzConcs')
+0212 end
+0213
+0214 protFlex = proteins(frequence>0);
+0215 [~,protUsageIdx] = ismember(strcat('usage_prot_', protFlex), model.rxns);
+0216 [~,ecProtId] = ismember(protFlex, model.ec.enzymes);
+0217 oldConcs = model.ec.concs(ecProtId);
+0218 %frequence = frequence(ecProtId);
+0219
+0220 if flexBreak == false && ~isempty(protFlex)
+0221 % Not all flexibilized proteins require flexibilization in the end
+0222 % Test flux distribution with minimum prot_pool usage
+0223 modelTemp = setParam(model,'var',params.bioRxn,expGrowth,0.5);
+0224 % minimise pool usage (solveLP maximises the objective, so use -1)
+0225 modelTemp = setParam(modelTemp,'obj','prot_pool_exchange',-1);
+0226 sol = solveLP(modelTemp,1);
+0227 % Check which concentrations can remain unchanged
+0228 newConcs = abs(sol.x(protUsageIdx));
+0229 keepOld = newConcs<oldConcs;
+0230 % Set UBs
+0231 model.ub(protUsageIdx(keepOld)) = oldConcs(keepOld);
+0232 model.ub(protUsageIdx(~keepOld)) = newConcs(~keepOld);
+0233 % Output structure
+0234 protFlex(keepOld) = [];
+0235 oldConcs(keepOld) = [];
+0236 newConcs(keepOld) = [];
+0237 frequence=frequence(frequence>0);
+0238 frequence(keepOld) = [];
+0239
+0240 flexEnz.uniprotIDs = protFlex;
+0241 flexEnz.oldConcs = oldConcs;
+0242 flexEnz.flexConcs = newConcs;
+0243 flexEnz.frequence = frequence(frequence>0);
+0244 else
+0245 protFlex = proteins(frequence>0);
+0246 [~, protUsageIdx] = ismember(strcat('usage_prot_', protFlex), model.rxns);
+0247
+0248 flexEnz.uniprotIDs = protFlex;
+0249 flexEnz.oldConcs = model.ec.concs(ecProtId);
+0250 flexEnz.flexConcs = model.ub(protUsageIdx);
+0251 flexEnz.frequence = frequence(frequence>0);
+0252 end
+0253 if changedProtPool
+0254 flexEnz.uniprotIDs(end+1) = {'prot_pool'};
+0255 flexEnz.oldConcs(end+1) = oldProtPool;
+0256 flexEnz.flexConcs(end+1) = newProtPool;
+0257 flexEnz.frequence(end+1) = 1;
+0258 end
+0259 % Sort by biggest ratio increase
+0260 flexEnz.ratioIncr = flexEnz.flexConcs./flexEnz.oldConcs;
+0261 [~,b] = sort(flexEnz.ratioIncr,'descend');
+0262 flexEnz.ratioIncr = flexEnz.ratioIncr(b);
+0263 flexEnz.uniprotIDs = flexEnz.uniprotIDs(b);
+0264 flexEnz.oldConcs = flexEnz.oldConcs(b);
+0265 flexEnz.flexConcs = flexEnz.flexConcs(b);
+0266 flexEnz.frequence = flexEnz.frequence(b);
+0267 end
getConcControlCoeffs Calculate control coefficients of protein usage.
+
+
SYNOPSIS
+
function [enz, controlCoeffs] = getConcControlCoeffs(model, varargin)
+
+
DESCRIPTION
+
getConcControlCoeffs Calculate control coefficients of protein usage.
+
+ For each candidate protein whose usage reaction is running above the
+ given limit fraction of its upper bound, temporarily increases that
+ protein's usage upper bound by foldChange, re-solves, and expresses the
+ resulting growth-rate increase per unit of concentration increase as a
+ control coefficient. Proteins below the limit, or whose increase does
+ not measurably raise growth, get a coefficient of zero.
+
+ Parameters
+ ----------
+ model : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ proteins : cell
+ a list of proteins to calculate the coefficients (default
+ model.ec.enzymes).
+ foldChange : double
+ a value how much to increase the protein concentration (default 2).
+ limit : double
+ threshold on the usage/concentration ratio (how saturated a
+ protein's usage upper bound already is); only proteins with a ratio
+ above this value are evaluated for a control coefficient (default
+ 0, i.e. any nonzero usage is evaluated).
+
+ Returns
+ -------
+ enz : logical
+ flags which of the input proteins had a usage/concentration ratio
+ above limit and were evaluated for a control coefficient.
+ controlCoeffs : double
+ control coefficient per input protein (0 for proteins not evaluated
+ or whose increase did not measurably raise growth).
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ [enz, controlCoeffs] = getConcControlCoeffs(model);
+ [enz, controlCoeffs] = getConcControlCoeffs(model, 'foldChange', 3);
+
+ See also
+ --------
+ flexibilizeEnzConcs
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
flexibilizeEnzConcs flexibilizeEnzConcs Flexibilize enzyme concentrations of an ecModel.
+
+
+
+
+
SOURCE CODE
+
0001 function [enz, controlCoeffs] = getConcControlCoeffs(model, varargin)
+0002 % getConcControlCoeffs Calculate control coefficients of protein usage.
+0003 %
+0004 % For each candidate protein whose usage reaction is running above the
+0005 % given limit fraction of its upper bound, temporarily increases that
+0006 % protein's usage upper bound by foldChange, re-solves, and expresses the
+0007 % resulting growth-rate increase per unit of concentration increase as a
+0008 % control coefficient. Proteins below the limit, or whose increase does
+0009 % not measurably raise growth, get a coefficient of zero.
+0010 %
+0011 % Parameters
+0012 % ----------
+0013 % model : struct
+0014 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0015 %
+0016 % Name-Value Arguments
+0017 % --------------------
+0018 % proteins : cell
+0019 % a list of proteins to calculate the coefficients (default
+0020 % model.ec.enzymes).
+0021 % foldChange : double
+0022 % a value how much to increase the protein concentration (default 2).
+0023 % limit : double
+0024 % threshold on the usage/concentration ratio (how saturated a
+0025 % protein's usage upper bound already is); only proteins with a ratio
+0026 % above this value are evaluated for a control coefficient (default
+0027 % 0, i.e. any nonzero usage is evaluated).
+0028 %
+0029 % Returns
+0030 % -------
+0031 % enz : logical
+0032 % flags which of the input proteins had a usage/concentration ratio
+0033 % above limit and were evaluated for a control coefficient.
+0034 % controlCoeffs : double
+0035 % control coefficient per input protein (0 for proteins not evaluated
+0036 % or whose increase did not measurably raise growth).
+0037 %
+0038 % Examples
+0039 % --------
+0040 % % optional arguments may be given positionally or as name-value pairs:
+0041 % [enz, controlCoeffs] = getConcControlCoeffs(model);
+0042 % [enz, controlCoeffs] = getConcControlCoeffs(model, 'foldChange', 3);
+0043 %
+0044 % See also
+0045 % --------
+0046 % flexibilizeEnzConcs
+0047
+0048 p = parseGECKOargs(varargin, { ...
+0049 'proteins', []; ...
+0050 'foldChange', []; ...
+0051 'limit', []});
+0052 proteins = p.proteins;
+0053 foldChange = p.foldChange;
+0054 limit = p.limit;
+0055
+0056 if isempty(limit)
+0057 limit = 0;
+0058 end
+0059
+0060 if isempty(foldChange)
+0061 foldChange = 2;
+0062 end
+0063
+0064 if isempty(proteins)
+0065 proteins = model.ec.enzymes;
+0066 end
+0067
+0068 % for now
+0069 enz = false(length(proteins),1);
+0070 controlCoeffs = zeros(length(proteins),1);
+0071
+0072 [sol,hs] = solveLP(model);
+0073 initialGrowth = sol.f;
+0074
+0075 % Get enzyme index
+0076 [~, protIdx] = ismember(proteins, model.ec.enzymes);
+0077
+0078 % Get the protein usage reactions
+0079 protUsageRxns = strcat('usage_prot_', model.ec.enzymes(protIdx));
+0080 [~, protUsageRxnIdx] = ismember(protUsageRxns, model.rxns);
+0081
+0082 for i = 1:numel(proteins)
+0083 % Get the previous concentration
+0084 prevConc = model.ub(protUsageRxnIdx(i));
+0085
+0086 % Only consider those with a usage close the UB
+0087 if (sol.x(protUsageRxnIdx(i))/prevConc) > limit
+0088
+0089 % Update the logical vector
+0090 enz(i) = 1;
+0091
+0092 % Create a temporal model since coeff will be calculated one enzyme at
+0093 % the time, without other change
+0094 tempModel = model;
+0095 % Increase the concentration by flexfactor
+0096 newConc = prevConc*(foldChange);
+0097 tempModel.ub(protUsageRxnIdx(i)) = newConc;
+0098
+0099 % Get the new growth rate after the adjustment
+0100 [tempSol,hs] = solveLP(tempModel,0,[],hs);
+0101 tempGrowth = tempSol.f;
+0102
+0103 % Calculate the coeff only if new growth rate is significantly
+0104 % higher than initial value
+0105 if (tempGrowth-initialGrowth)>1e-10
+0106 controlCoeffs(i) = (tempGrowth-initialGrowth)/(newConc-prevConc);
+0107 end
+0108 end
+0109
+0110 end
+0111 end
+0112
relaxProteomicsGreedy Greedy shadow-price-ordered relaxation of proteomics
+
+
SYNOPSIS
+
function result = relaxProteomicsGreedy(model, varargin)
+
+
DESCRIPTION
+
relaxProteomicsGreedy Greedy shadow-price-ordered relaxation of proteomics
+ constraints.
+
+ While growth is below minimalGrowth, finds the still-proteomics-constrained
+ enzyme (usage_prot_<id> upper bound below defaultUpperBound) with the
+ largest absolute shadow price on its prot_<id> mass-balance constraint,
+ fully relaxes it back to defaultUpperBound, re-solves, and repeats.
+
+ Parameters
+ ----------
+ model : struct
+ a full ecModel in GECKO 3 format, with some usage_prot_<id> reactions
+ constrained below defaultUpperBound (e.g. by constrainEnzConcs).
+
+ Name-Value Arguments
+ --------------------
+ minimalGrowth : double
+ target objective value. Required.
+ enzymeSet : cell
+ subset of uniprot ids eligible for relaxation (default: every
+ currently proteomics-constrained enzyme).
+ maxIterations : double
+ safety cap; errors if exceeded while eligible enzymes still remained
+ (default 100).
+ defaultUpperBound : double
+ value to restore on a relaxed usage reaction, matching the shared-pool
+ default makeEcModel itself sets (default 1000).
+
+ Returns
+ -------
+ result : struct
+ - relaxed : struct mapping each relaxed uniprot id to its original
+ model.ec.concs value, so the caller can restore it later.
+ - trace : struct array, one row per relaxation step, with fields
+ iteration (0-based), relaxedUniprot, growthBefore, growthAfter,
+ shadowPrice.
+ - finalGrowth : objective value when the loop stopped.
+ - converged : true iff finalGrowth >= minimalGrowth.
+
+ Examples
+ --------
+ result = relaxProteomicsGreedy(ecModel, 'minimalGrowth', 0.1);
+
+ Notes
+ -----
+ Different relaxation strategy from flexibilizeEnzConcs: this one is
+ shadow-price-ordered (one solve per step) and often converges in fewer
+ iterations when one or two enzymes dominate the infeasibility. The
+ trade-off is that it fully unconstrains each picked enzyme with no
+ tighten-back pass, so the result is looser (less proteomics-faithful).
+
+ There are two distinct non-convergence outcomes: running out of
+ eligible candidates before reaching minimalGrowth returns normally with
+ converged=false; exhausting maxIterations while candidates still
+ remained raises an error instead.
+
+ See also
+ --------
+ flexibilizeEnzConcs, getEnzymeBottlenecks
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function result = relaxProteomicsGreedy(model, varargin)
+0002 % relaxProteomicsGreedy Greedy shadow-price-ordered relaxation of proteomics
+0003 % constraints.
+0004 %
+0005 % While growth is below minimalGrowth, finds the still-proteomics-constrained
+0006 % enzyme (usage_prot_<id> upper bound below defaultUpperBound) with the
+0007 % largest absolute shadow price on its prot_<id> mass-balance constraint,
+0008 % fully relaxes it back to defaultUpperBound, re-solves, and repeats.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % model : struct
+0013 % a full ecModel in GECKO 3 format, with some usage_prot_<id> reactions
+0014 % constrained below defaultUpperBound (e.g. by constrainEnzConcs).
+0015 %
+0016 % Name-Value Arguments
+0017 % --------------------
+0018 % minimalGrowth : double
+0019 % target objective value. Required.
+0020 % enzymeSet : cell
+0021 % subset of uniprot ids eligible for relaxation (default: every
+0022 % currently proteomics-constrained enzyme).
+0023 % maxIterations : double
+0024 % safety cap; errors if exceeded while eligible enzymes still remained
+0025 % (default 100).
+0026 % defaultUpperBound : double
+0027 % value to restore on a relaxed usage reaction, matching the shared-pool
+0028 % default makeEcModel itself sets (default 1000).
+0029 %
+0030 % Returns
+0031 % -------
+0032 % result : struct
+0033 % - relaxed : struct mapping each relaxed uniprot id to its original
+0034 % model.ec.concs value, so the caller can restore it later.
+0035 % - trace : struct array, one row per relaxation step, with fields
+0036 % iteration (0-based), relaxedUniprot, growthBefore, growthAfter,
+0037 % shadowPrice.
+0038 % - finalGrowth : objective value when the loop stopped.
+0039 % - converged : true iff finalGrowth >= minimalGrowth.
+0040 %
+0041 % Examples
+0042 % --------
+0043 % result = relaxProteomicsGreedy(ecModel, 'minimalGrowth', 0.1);
+0044 %
+0045 % Notes
+0046 % -----
+0047 % Different relaxation strategy from flexibilizeEnzConcs: this one is
+0048 % shadow-price-ordered (one solve per step) and often converges in fewer
+0049 % iterations when one or two enzymes dominate the infeasibility. The
+0050 % trade-off is that it fully unconstrains each picked enzyme with no
+0051 % tighten-back pass, so the result is looser (less proteomics-faithful).
+0052 %
+0053 % There are two distinct non-convergence outcomes: running out of
+0054 % eligible candidates before reaching minimalGrowth returns normally with
+0055 % converged=false; exhausting maxIterations while candidates still
+0056 % remained raises an error instead.
+0057 %
+0058 % See also
+0059 % --------
+0060 % flexibilizeEnzConcs, getEnzymeBottlenecks
+0061
+0062 p = parseGECKOargs(varargin, {'minimalGrowth', []; 'enzymeSet', []; ...
+0063 'maxIterations', []; 'defaultUpperBound', []});
+0064 minimalGrowth = p.minimalGrowth;
+0065 enzymeSet = p.enzymeSet;
+0066 maxIterations = p.maxIterations;
+0067 defaultUpperBound = p.defaultUpperBound;
+0068 if isempty(minimalGrowth)
+0069 error('relaxProteomicsGreedy: minimalGrowth is required.')
+0070 end
+0071 if isempty(maxIterations)
+0072 maxIterations = 100;
+0073 end
+0074 if isempty(defaultUpperBound)
+0075 defaultUpperBound = 1000;
+0076 end
+0077
+0078 if isfield(model.ec, 'geckoLight') && model.ec.geckoLight
+0079 error(['relaxProteomicsGreedy requires a full ecModel, not gecko-light: light models have no '...
+0080 'per-enzyme prot_<id> / usage_prot_<id> machinery.'])
+0081 end
+0082
+0083 usageRxns = strcat('usage_prot_', model.ec.enzymes);
+0084 [found, usageIdx] = ismember(usageRxns, model.rxns);
+0085 if ~all(found)
+0086 error('Usage reactions are not defined for all enzymes. This is done by makeEcModel.')
+0087 end
+0088 protMets = strcat('prot_', model.ec.enzymes);
+0089 [found, protMetIdx] = ismember(protMets, model.mets);
+0090 if ~all(found)
+0091 error('Cannot find prot_<id> metabolites for all enzymes. This is done by makeEcModel.')
+0092 end
+0093
+0094 if isempty(enzymeSet)
+0095 pool = model.ec.enzymes;
+0096 else
+0097 pool = intersect(enzymeSet(:), model.ec.enzymes, 'stable');
+0098 end
+0099 [~, poolIdx] = ismember(pool, model.ec.enzymes);
+0100 poolIdx = poolIdx(poolIdx > 0);
+0101 candidates = poolIdx(model.ub(usageIdx(poolIdx)) < defaultUpperBound);
+0102
+0103 relaxed = struct();
+0104 trace = struct('iteration', {}, 'relaxedUniprot', {}, 'growthBefore', {}, ...
+0105 'growthAfter', {}, 'shadowPrice', {});
+0106
+0107 sol = solveLP(model);
+0108 if sol.stat == 1
+0109 growth = sol.f;
+0110 else
+0111 growth = -inf;
+0112 end
+0113
+0114 for it = 0:(maxIterations - 1)
+0115 if growth >= minimalGrowth
+0116 result.relaxed = relaxed;
+0117 result.trace = trace;
+0118 result.finalGrowth = growth;
+0119 result.converged = true;
+0120 return
+0121 end
+0122 if isempty(candidates)
+0123 warning('relaxProteomicsGreedy: no more candidates to relax; stopping.')
+0124 result.relaxed = relaxed;
+0125 result.trace = trace;
+0126 result.finalGrowth = growth;
+0127 result.converged = false;
+0128 return
+0129 end
+0130
+0131 shadowPrices = abs(sol.sPrice(protMetIdx(candidates)));
+0132 [~, bestPos] = max(shadowPrices);
+0133 targetIdx = candidates(bestPos);
+0134 targetUniprot = model.ec.enzymes{targetIdx};
+0135 sp = sol.sPrice(protMetIdx(targetIdx));
+0136
+0137 relaxed.(matlab.lang.makeValidName(targetUniprot)) = model.ec.concs(targetIdx);
+0138 model.ub(usageIdx(targetIdx)) = defaultUpperBound;
+0139 candidates(candidates == targetIdx) = [];
+0140
+0141 newSol = solveLP(model);
+0142 if newSol.stat == 1
+0143 newGrowth = newSol.f;
+0144 else
+0145 newGrowth = -inf;
+0146 end
+0147
+0148 trace(end+1) = struct('iteration', it, 'relaxedUniprot', targetUniprot, ...
+0149 'growthBefore', growth, 'growthAfter', newGrowth, 'shadowPrice', sp); %#ok<AGROW>
+0150
+0151 growth = newGrowth;
+0152 sol = newSol;
+0153 end
+0154
+0155 error('relaxProteomicsGreedy: did not converge in %d iterations (final growth %.4g < target %.4g).', ...
+0156 maxIterations, growth, minimalGrowth)
+0157 end
updateProtPool Update the protein pool to compensate for proteomics.
+
+
SYNOPSIS
+
function ecModel = updateProtPool(ecModel, varargin)
+
+
DESCRIPTION
+
updateProtPool Update the protein pool to compensate for proteomics.
+
+ Only applicable to ecModels where unmeasured enzymes draw from the
+ protein pool while measured enzymes (constrained via model.ec.concs) do
+ not: shrinks the protein pool exchange upper bound so it represents only
+ the remaining, unmeasured protein content (total protein content Ptot
+ minus the measured protein mass). For ecModels generated with GECKO
+ 3.2.0 or later, where all enzymes draw from the protein pool regardless
+ of whether they are measured, this function raises an error instead;
+ use setProtPoolSize (see
+ https://github.com/SysBioChalmers/GECKO/issues/375).
+
+ Parameters
+ ----------
+ ecModel : struct
+ an ecModel in GECKO 3 format (with ecModel.ec structure).
+
+ Name-Value Arguments
+ --------------------
+ Ptot : double
+ total protein content in g/gDCW, overwrites the value from
+ modelAdapter. For instance, condition-specific fluxData.Ptot from
+ loadFluxData can be used. If nothing is provided, the modelAdapter
+ value is used.
+ modelAdapter : ModelAdapter
+ a loaded model adapter (default: the current default model adapter).
+
+ Returns
+ -------
+ ecModel : struct
+ an ecModel where the protein pool exchange reaction's upper bound
+ has been reduced to reflect only the unmeasured protein content.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ ecModel = updateProtPool(ecModel);
+ ecModel = updateProtPool(ecModel, 'Ptot', 0.5);
+
+ See also
+ --------
+ setProtPoolSize, loadFluxData
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function ecModel = updateProtPool(ecModel, varargin)
+0002 % updateProtPool Update the protein pool to compensate for proteomics.
+0003 %
+0004 % Only applicable to ecModels where unmeasured enzymes draw from the
+0005 % protein pool while measured enzymes (constrained via model.ec.concs) do
+0006 % not: shrinks the protein pool exchange upper bound so it represents only
+0007 % the remaining, unmeasured protein content (total protein content Ptot
+0008 % minus the measured protein mass). For ecModels generated with GECKO
+0009 % 3.2.0 or later, where all enzymes draw from the protein pool regardless
+0010 % of whether they are measured, this function raises an error instead;
+0011 % use setProtPoolSize (see
+0012 % https://github.com/SysBioChalmers/GECKO/issues/375).
+0013 %
+0014 % Parameters
+0015 % ----------
+0016 % ecModel : struct
+0017 % an ecModel in GECKO 3 format (with ecModel.ec structure).
+0018 %
+0019 % Name-Value Arguments
+0020 % --------------------
+0021 % Ptot : double
+0022 % total protein content in g/gDCW, overwrites the value from
+0023 % modelAdapter. For instance, condition-specific fluxData.Ptot from
+0024 % loadFluxData can be used. If nothing is provided, the modelAdapter
+0025 % value is used.
+0026 % modelAdapter : ModelAdapter
+0027 % a loaded model adapter (default: the current default model adapter).
+0028 %
+0029 % Returns
+0030 % -------
+0031 % ecModel : struct
+0032 % an ecModel where the protein pool exchange reaction's upper bound
+0033 % has been reduced to reflect only the unmeasured protein content.
+0034 %
+0035 % Examples
+0036 % --------
+0037 % % optional arguments may be given positionally or as name-value pairs:
+0038 % ecModel = updateProtPool(ecModel);
+0039 % ecModel = updateProtPool(ecModel, 'Ptot', 0.5);
+0040 %
+0041 % See also
+0042 % --------
+0043 % setProtPoolSize, loadFluxData
+0044
+0045 p = parseGECKOargs(varargin, { ...
+0046 'Ptot', []; ...
+0047 'modelAdapter', []});
+0048 Ptot = p.Ptot;
+0049 modelAdapter = p.modelAdapter;
+0050
+0051 % Do not run from GECKO version 3.2.0 onwards. This can be recognized by
+0052 % prot_usage reactions that are constrained by proteomics and still draw
+0053 % from the protein pool
+0054 protRxns = find(startsWith(ecModel.rxns,'usage_prot_'));
+0055 poolMetIdx = find(strcmp(ecModel.mets,'prot_pool'));
+0056 % Selected proteins with proteomics constraints
+0057 constProtRxns = ~(ecModel.ub(protRxns)==1000);
+0058 % Are any still drawing from prot_pool? This is introduced in GECKO 3.2.0.
+0059 if any(full(ecModel.S(poolMetIdx,protRxns(constProtRxns))))
+0060 error(['In the provided ecModel, all protein usage reactions, both with '...
+0061 'and without concentration constraints, draw from the protein pool. '...
+0062 'This was introduced with GECKO 3.2.0. Since then, updateProtPool '...
+0063 'has become obsolete, use setProtPoolSize instead to constrain the '...
+0064 'total protein pool with condition-specific total protein content. See '...
+0065 '<a href="https://github.com/SysBioChalmers/GECKO/issues/375">here</a> '...
+0066 'for more explanation.'])
+0067 end
+0068
+0069 if isempty(modelAdapter)
+0070 modelAdapter = ModelAdapterManager.getDefault();
+0071 if isempty(modelAdapter)
+0072 error('Either send in a modelAdapter or set the default model adapter in the ModelAdapterManager.')
+0073 end
+0074 end
+0075 params = modelAdapter.params;
+0076
+0077 if isempty(Ptot)
+0078 Ptot = params.Ptot;
+0079 disp(['Total protein content used: ' num2str(Ptot) ' [g protein/gDw]'])
+0080 end
+0081
+0082 % Convert Ptot to mg/gDW if provided in g/gDCW (which is default)
+0083 if Ptot < 1
+0084 Ptot = Ptot * 1000;
+0085 end
+0086
+0087 Pmeas = sum(ecModel.ec.concs,'omitnan');
+0088 if Pmeas == 0
+0089 error('The model has not yet been populated with proteomics, as ecModel.ec.concs is empty.')
+0090 end
+0091
+0092 Pnew = (Ptot - Pmeas) * params.f;
+0093
+0094 if Pnew > 0
+0095 PoolRxnIdx = strcmp(ecModel.rxns,'prot_pool_exchange');
+0096 ecModel.ub(PoolRxnIdx) = Pnew*params.sigma;
+0097 sol = solveLP(ecModel);
+0098 if isempty(sol.x)
+0099 error(['Estimating the remaining protein pool by subtracting the '...
+0100 'sum of measured enzyme concentrations (in ecModel.ec.concs) '...
+0101 'from the total protein pool (Ptot) does not yield a functional '...
+0102 'model.'])
+0103 end
+0104 else
+0105 error('The total measured protein mass exceeds the total protein content.')
+0106 end
+0107 end
ModelAdapter Abstract base class for species-specific model adapters.
+
+
SYNOPSIS
+
This is a script file.
+
+
DESCRIPTION
+
ModelAdapter Abstract base class for species-specific model adapters.
+
+ Defines the interface that every species adapter must implement and
+ provides shared default behaviour for retrieving GECKO parameters,
+ mapping model genes to UniProt, and locating reference databases.
+ Concrete subclasses override these methods for a particular organism.
+
+ Notes
+ -----
+ - params : struct of model-specific GECKO parameters (public property).
+ - getSpontaneousReactions : abstract method that subclasses must define.
+
+ See also
+ --------
+ ModelAdapterManager
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
ModelAdapter ModelAdapter Abstract base class for species-specific model adapters.
+This function is called by:
+
+
ModelAdapter ModelAdapter Abstract base class for species-specific model adapters.
TomlAdapter TomlAdapter Model adapter configured by model_adapter.toml.
adapterTemplate KEY_CLASSNAME Template ModelAdapter to copy and adapt per organism.
0001 % ModelAdapter Abstract base class for species-specific model adapters.
+0002 %
+0003 % Defines the interface that every species adapter must implement and
+0004 % provides shared default behaviour for retrieving GECKO parameters,
+0005 % mapping model genes to UniProt, and locating reference databases.
+0006 % Concrete subclasses override these methods for a particular organism.
+0007 %
+0008 % Notes
+0009 % -----
+0010 % - params : struct of model-specific GECKO parameters (public property).
+0011 % - getSpontaneousReactions : abstract method that subclasses must define.
+0012 %
+0013 % See also
+0014 % --------
+0015 % ModelAdapterManager
+0016 classdef (Abstract) ModelAdapter
+0017 methods (Abstract)
+0018 [spont,spontRxnNames] = getSpontaneousReactions(obj,model);
+0019 end
+0020 methods
+0021 %not really needed, we could access it directly. Not very nice, but practical
+0022 function parameters = getParameters(obj)
+0023 parameters = obj.params;
+0024 end
+0025
+0026 %This allows for changing parameters in an adapter without
+0027 %creating a new class. May be convenient, although less clean
+0028 %function obj = setParameters(obj, parameters)
+0029 % obj.mParams = parameters;
+0030 %end
+0031
+0032 %The genes returned here should match the gene id set in the parameter params.uniprot.geneIDfield
+0033 function genes = getUniprotCompatibleGenes(obj,inGenes)
+0034 genes = inGenes;
+0035 end
+0036
+0037 %The genes returned here should match the gene id stored in column
+0038 %2 (databases.kegg.genes) of kegg.tsv. Override when the model
+0039 %uses identifiers that need transformation before KEGG lookup.
+0040 function genes = getKeggCompatibleGenes(obj,inGenes)
+0041 genes = inGenes;
+0042 end
+0043
+0044 function uniprotIDs = getUniprotIDsFromTable(obj,modelGenes)
+0045 conversionTable = fullfile(obj.params.path,'data','uniprotConversion.tsv');
+0046 if exist(conversionTable,'file')
+0047 fID=fopen(conversionTable,'r');
+0048 conversionTable = textscan(fID,'%q %q','Delimiter','\t','HeaderLines',1);
+0049 fclose(fID);
+0050
+0051 modelIDs = conversionTable{1,1};
+0052 uniprots = conversionTable{1,2};
+0053
+0054 [a,b] = ismember(modelGenes,modelIDs);
+0055
+0056 uniprotIDs = strings(numel(modelGenes),1);
+0057 uniprotIDs(a) = uniprots(b(a));
+0058 disp('The model genes are matched to Uniprot via the table at data/uniprotConversion.tsv.')
+0059 else
+0060 uniprotIDs = modelGenes;
+0061 end
+0062 end
+0063
+0064 function folder = getBrendaDBFolder(obj)
+0065 % The project's data/brenda (geckopy brenda-refresh writes
+0066 % there) when it exists, otherwise GECKO's BRENDA release,
+0067 % downloaded once (fetchBrendaAsset).
+0068 folder = fullfile(obj.params.path,'data','brenda');
+0069 if ~isfolder(folder)
+0070 folder = fetchBrendaAsset();
+0071 end
+0072 end
+0073
+0074 function x = getPhylDistStructPath(obj)
+0075 % RAVEN 3 keeps keggPhylDist.mat under src/reconstruction/kegg
+0076 % (earlier versions of RAVEN 3 under reconstruction/kegg), RAVEN
+0077 % 2 under external/kegg.
+0078 ravenRoot = findRAVENroot();
+0079 candidates = {fullfile(ravenRoot,'src','reconstruction','kegg','keggPhylDist.mat'), ...
+0080 fullfile(ravenRoot,'reconstruction','kegg','keggPhylDist.mat'), ...
+0081 fullfile(ravenRoot,'external','kegg','keggPhylDist.mat')};
+0082 x = candidates{end};
+0083 for i = 1:numel(candidates)
+0084 if isfile(candidates{i})
+0085 x = candidates{i};
+0086 break
+0087 end
+0088 end
+0089 end
+0090
+0091 % Define a model-specific function in the 'code' subfolder of the
+0092 % project folder, that can constrain the model to anaerobic
+0093 % conditions, and refer to this function in the modelAdapter. This
+0094 % function is used when running cmaesKcatTuning.m (via
+0095 % evotuneScore.m) if the fluxData has anaerobic conditions.
+0096 function ecModel = makeModelAnaerobic(obj,ecModel)
+0097 ecModel = ecModel;
+0098 % Example from full_tutorial:
+0099 % ecModel = anaerobicModel_GECKO(ecModel);
+0100 end
+0101 % Similarly, define a function that can change protein content in
+0102 % the biomass composition
+0103 function ecModel = changeProteinBiomass(obj,ecModel,Ptot)
+0104 ecModel = ecModel;
+0105 % Example from full_tutorial:
+0106 % ecModel = scaleBioMass(ecModel,'protein',Ptot,'carbohydrate',false);
+0107 end
+0108 end
+0109
+0110 %To have the params public is a bit "ugly", but very practical
+0111 %if we want to change a parameter
+0112 properties (Access = public)
+0113 params;
+0114 end
+0115 end
ModelAdapterManager Resolve and cache the active ModelAdapter.
+
+
SYNOPSIS
+
This is a script file.
+
+
DESCRIPTION
+
ModelAdapterManager Resolve and cache the active ModelAdapter.
+
+ Static helper class that loads a model adapter from a file path and
+ stores a process-wide default adapter, so that GECKO functions can
+ retrieve the active adapter without it being passed explicitly.
+
+ An adapter is loaded from model_adapter.toml (give the file or the
+ project folder; see TomlAdapter) or from an adapter class (give its .m
+ file).
+
+ Notes
+ -----
+ All methods are static. The default adapter is held in a persistent
+ variable and shared across the whole GECKO session.
+
+ See also
+ --------
+ ModelAdapter
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
ModelAdapterManager ModelAdapterManager Resolve and cache the active ModelAdapter.
TomlAdapter TomlAdapter Model adapter configured by model_adapter.toml.
+This function is called by:
+
+
ModelAdapterManager ModelAdapterManager Resolve and cache the active ModelAdapter.
0001 % ModelAdapterManager Resolve and cache the active ModelAdapter.
+0002 %
+0003 % Static helper class that loads a model adapter from a file path and
+0004 % stores a process-wide default adapter, so that GECKO functions can
+0005 % retrieve the active adapter without it being passed explicitly.
+0006 %
+0007 % An adapter is loaded from model_adapter.toml (give the file or the
+0008 % project folder; see TomlAdapter) or from an adapter class (give its .m
+0009 % file).
+0010 %
+0011 % Notes
+0012 % -----
+0013 % All methods are static. The default adapter is held in a persistent
+0014 % variable and shared across the whole GECKO session.
+0015 %
+0016 % See also
+0017 % --------
+0018 % ModelAdapter
+0019 classdef ModelAdapterManager
+0020 methods(Static)
+0021 function adapter = getAdapter(adapterPath, addToMatlabPath)
+0022 if nargin < 2
+0023 addToMatlabPath = true;
+0024 end
+0025 ModelAdapterManager.showGECKO4Notice();
+0026
+0027 adapterPath = char(adapterPath);
+0028 if isfolder(adapterPath)
+0029 tomlFile = fullfile(adapterPath, 'model_adapter.toml');
+0030 if ~isfile(tomlFile)
+0031 error('No model_adapter.toml found in %s.', adapterPath);
+0032 end
+0033 adapter = TomlAdapter(tomlFile);
+0034 return
+0035 end
+0036 [adapterFolder, adapterClassName, extension] = fileparts(adapterPath);
+0037 if strcmp(extension, '.toml')
+0038 adapter = TomlAdapter(adapterPath);
+0039 return
+0040 elseif ~strcmp(extension, '.m')
+0041 error(['Please provide the project folder, its model_adapter.toml, or the '...
+0042 'full path to the adapter class file, including the .m extension.']);
+0043 else
+0044 s = pathsep;
+0045 pathStr = [s, path, s];
+0046 onPath = contains(pathStr, [s, adapterFolder, s], 'IgnoreCase', ispc);
+0047 % Check if the folder is on the path
+0048 if ~onPath
+0049 if addToMatlabPath
+0050 addpath(adapterFolder);
+0051 else
+0052 printOrange(['WARNING: The adapter will not be on the MATLAB path, since addToMatlabPath is false\n'...
+0053 'and it is not currently on the path. Either set addToMatlabPath to true, fix this\n'...
+0054 'manually before calling this function or make sure it is in current directory (not\n'...
+0055 'recommended). If the class is not reachable throughout the entire GECKO use there will\n'...
+0056 'be errors throughout.\n']);
+0057 end
+0058 end
+0059
+0060 end
+0061 adapter = feval(adapterClassName);
+0062 end
+0063
+0064 function out = getDefault()
+0065 out = ModelAdapterManager.setGetDefault();
+0066 end
+0067
+0068 function adapter = setDefault(adapterPath, addToMatlabPath)
+0069 if nargin < 1 || isempty(adapterPath)
+0070 adapter = ModelAdapterManager.setGetDefault(adapterPath);
+0071 return
+0072 end
+0073 if nargin < 2
+0074 addToMatlabPath = true;
+0075 end
+0076 adapter = ModelAdapterManager.setGetDefault(ModelAdapterManager.getAdapter(adapterPath, addToMatlabPath));
+0077 end
+0078
+0079 end
+0080 methods(Static,Access = private)
+0081 % This is how they recommend defining static variables in Matlab
+0082 function out = setGetDefault(val)
+0083 persistent defaultAdapter; %will be empty initially
+0084 if nargin
+0085 defaultAdapter = val;
+0086 end
+0087 out = defaultAdapter;
+0088 end
+0089
+0090 function showGECKO4Notice()
+0091 % Remove this method (and its call in getAdapter) in a future
+0092 % GECKO 4.x release, once users have had time to notice the
+0093 % backward-incompatible changes from GECKO 3.
+0094 persistent shown;
+0095 if isempty(shown)
+0096 printOrange(['NOTE: This is GECKO 4, which is not fully backward-compatible\n'...
+0097 'with GECKO 3. See https://gecko-docs.readthedocs.io/en/latest/gecko3-to-gecko4.html\n'...
+0098 'for what changed and how to stay on GECKO 3 (v3.2.5) if needed.\n']);
+0099 shown = true;
+0100 end
+0101 end
+0102 end
+0103 end
TomlAdapter Model adapter configured by model_adapter.toml.
+
+
SYNOPSIS
+
This is a script file.
+
+
DESCRIPTION
+
TomlAdapter Model adapter configured by model_adapter.toml.
+
+ The model adapter for a project whose parameters are in
+ model_adapter.toml, the configuration file that GECKO and geckopy share.
+ No adapter class has to be written: what used to be code in an adapter
+ class is set in the TOML file.
+
+ - Spontaneous reactions: [spontaneous] name_contains (reactions whose
+ name contains the text) and/or file (a TSV of reaction identifiers,
+ optionally with a second 0/1 column).
+ - Anaerobic conditions and protein content during kcat tuning:
+ [matlab] make_model_anaerobic and change_protein_biomass name MATLAB
+ functions, for instance in the project's code folder, which is added
+ to the MATLAB path. Each takes the ecModel (and, for
+ change_protein_biomass, the protein content) and returns the ecModel.
+
+ A project that needs other custom code can keep an adapter class and
+ read its parameters from the TOML file in its constructor:
+
+ obj.params = loadAdapterParams(fullfile(projectPath,'model_adapter.toml'));
+
+ Examples
+ --------
+ adapter = TomlAdapter(fullfile(projectPath,'model_adapter.toml'));
+ % or, as the default adapter:
+ ModelAdapterManager.setDefault(projectPath);
+
+ See also
+ --------
+ ModelAdapter, ModelAdapterManager, loadAdapterParams, convertAdapterToToml
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
ModelAdapter ModelAdapter Abstract base class for species-specific model adapters.
TomlAdapter TomlAdapter Model adapter configured by model_adapter.toml.
loadAdapterParams loadAdapterParams Read model adapter parameters from a TOML file.
+This function is called by:
+
+
ModelAdapterManager ModelAdapterManager Resolve and cache the active ModelAdapter.
TomlAdapter TomlAdapter Model adapter configured by model_adapter.toml.
0001 % TomlAdapter Model adapter configured by model_adapter.toml.
+0002 %
+0003 % The model adapter for a project whose parameters are in
+0004 % model_adapter.toml, the configuration file that GECKO and geckopy share.
+0005 % No adapter class has to be written: what used to be code in an adapter
+0006 % class is set in the TOML file.
+0007 %
+0008 % - Spontaneous reactions: [spontaneous] name_contains (reactions whose
+0009 % name contains the text) and/or file (a TSV of reaction identifiers,
+0010 % optionally with a second 0/1 column).
+0011 % - Anaerobic conditions and protein content during kcat tuning:
+0012 % [matlab] make_model_anaerobic and change_protein_biomass name MATLAB
+0013 % functions, for instance in the project's code folder, which is added
+0014 % to the MATLAB path. Each takes the ecModel (and, for
+0015 % change_protein_biomass, the protein content) and returns the ecModel.
+0016 %
+0017 % A project that needs other custom code can keep an adapter class and
+0018 % read its parameters from the TOML file in its constructor:
+0019 %
+0020 % obj.params = loadAdapterParams(fullfile(projectPath,'model_adapter.toml'));
+0021 %
+0022 % Examples
+0023 % --------
+0024 % adapter = TomlAdapter(fullfile(projectPath,'model_adapter.toml'));
+0025 % % or, as the default adapter:
+0026 % ModelAdapterManager.setDefault(projectPath);
+0027 %
+0028 % See also
+0029 % --------
+0030 % ModelAdapter, ModelAdapterManager, loadAdapterParams, convertAdapterToToml
+0031 classdef TomlAdapter < ModelAdapter
+0032 methods
+0033 function obj = TomlAdapter(tomlFile)
+0034 % TomlAdapter Load the adapter from model_adapter.toml.
+0035 %
+0036 % Parameters
+0037 % ----------
+0038 % tomlFile : char
+0039 % path to model_adapter.toml, or to the project folder that
+0040 % holds it.
+0041 obj.params = loadAdapterParams(tomlFile);
+0042 codeFolder = fullfile(obj.params.path, 'code');
+0043 if isfolder(codeFolder)
+0044 addpath(codeFolder);
+0045 end
+0046 end
+0047
+0048 function [spont,spontRxnNames] = getSpontaneousReactions(obj,model)
+0049 % getSpontaneousReactions Reactions set as spontaneous in [spontaneous].
+0050 %
+0051 % Returns
+0052 % -------
+0053 % spont : logical
+0054 % true for each reaction of model that is spontaneous.
+0055 % spontRxnNames : cell
+0056 % identifiers of those reactions.
+0057 spont = false(numel(model.rxns),1);
+0058 if isfield(obj.params,'spontaneous')
+0059 s = obj.params.spontaneous;
+0060 if isfield(s,'name_contains') && ~isempty(s.name_contains)
+0061 spont = spont | contains(model.rxnNames, s.name_contains);
+0062 end
+0063 if isfield(s,'file') && ~isempty(s.file)
+0064 spont = spont | ismember(model.rxns, readSpontaneousFile(s.file));
+0065 end
+0066 end
+0067 spontRxnNames = model.rxns(spont);
+0068 end
+0069
+0070 function ecModel = makeModelAnaerobic(obj,ecModel)
+0071 % makeModelAnaerobic Run the function named in [matlab] make_model_anaerobic.
+0072 fun = hookName(obj, 'makeModelAnaerobic');
+0073 if ~isempty(fun)
+0074 ecModel = feval(fun, ecModel);
+0075 end
+0076 end
+0077
+0078 function ecModel = changeProteinBiomass(obj,ecModel,Ptot)
+0079 % changeProteinBiomass Run the function named in [matlab] change_protein_biomass.
+0080 fun = hookName(obj, 'changeProteinBiomass');
+0081 if ~isempty(fun)
+0082 ecModel = feval(fun, ecModel, Ptot);
+0083 end
+0084 end
+0085 end
+0086 end
+0087
+0088 function fun = hookName(obj, field)
+0089 fun = '';
+0090 if isfield(obj.params,'matlab') && isfield(obj.params.matlab, field)
+0091 fun = obj.params.matlab.(field);
+0092 end
+0093 end
+0094
+0095 function rxns = readSpontaneousFile(file)
+0096 % Reaction identifiers in the first column; with a second column, only
+0097 % rows where it is 1 or true. The first row is a header.
+0098 fid = fopen(file, 'r');
+0099 if fid < 0
+0100 error('GECKO:TomlAdapter', 'Cannot read the spontaneous reactions file %s', file);
+0101 end
+0102 header = fgetl(fid);
+0103 nCols = numel(strsplit(header, '\t'));
+0104 if nCols >= 2
+0105 cols = textscan(fid, ['%q %q' repmat(' %*q', 1, nCols-2)], 'Delimiter', '\t');
+0106 keep = ismember(lower(strtrim(cols{2})), {'1','true'});
+0107 rxns = cols{1}(keep);
+0108 else
+0109 cols = textscan(fid, '%q', 'Delimiter', '\t');
+0110 rxns = cols{1};
+0111 end
+0112 fclose(fid);
+0113 end
KEY_CLASSNAME Template ModelAdapter to copy and adapt per organism.
+
+
SYNOPSIS
+
This is a script file.
+
+
DESCRIPTION
+
KEY_CLASSNAME Template ModelAdapter to copy and adapt per organism.
+
+ Starting point for a species-specific adapter. Copy this file, rename
+ the class, and edit the parameter values set in the constructor and the
+ getSpontaneousReactions method to match the target organism and model.
+
+ See also
+ --------
+ ModelAdapter, ModelAdapterManager
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
ModelAdapter ModelAdapter Abstract base class for species-specific model adapters.
0001 % KEY_CLASSNAME Template ModelAdapter to copy and adapt per organism.
+0002 %
+0003 % Starting point for a species-specific adapter. Copy this file, rename
+0004 % the class, and edit the parameter values set in the constructor and the
+0005 % getSpontaneousReactions method to match the target organism and model.
+0006 %
+0007 % See also
+0008 % --------
+0009 % ModelAdapter, ModelAdapterManager
+0010 classdef KEY_CLASSNAME < ModelAdapter
+0011 methods
+0012 function obj = KEY_CLASSNAME()
+0013 % KEY_CLASSNAME Construct the adapter and set default parameters.
+0014 %
+0015 % Sets initial values of obj.params; they can be changed by
+0016 % the user after construction.
+0017 %
+0018 % Returns
+0019 % -------
+0020 % obj : ModelAdapter
+0021 % the constructed adapter instance.
+0022
+0023 % Directory where all model-specific files and scripts are kept.
+0024 % Is assumed to follow the GECKO-defined folder structure.
+0025 obj.params.path = fullfile('KEY_PATH', 'KEY_NAME');
+0026 addpath(fullfile(obj.params.path,'code'));
+0027
+0028 % Path to the conventional GEM that this ecModel will be based on.
+0029 obj.params.convGEM = fullfile(obj.params.path,'models','yourModel.xml');
+0030
+0031 % Average enzyme saturation factor
+0032 obj.params.sigma = 0.5;
+0033
+0034 % Total protein content in the cell [g protein/gDw]
+0035 obj.params.Ptot = 0.5;
+0036
+0037 % Fraction of enzymes in the model [g enzyme/g protein]
+0038 obj.params.f = 0.5;
+0039
+0040 % Growth rate the model should be able to reach when not
+0041 % constraint by nutrient uptake (e.g. max growth rate) [1/h]
+0042 obj.params.gR_exp = 0.41;
+0043
+0044 % Provide your organism scientific name
+0045 obj.params.org_name = 'genus species';
+0046
+0047 % Which BRENDA aggregate fuzzyKcatMatching reads for each
+0048 % (EC, substrate, organism) triple: 'max' (the highest
+0049 % turnover reported) or 'median' (the middle one). Can also
+0050 % be given per call as
+0051 % fuzzyKcatMatching(model,'kcatAggregation','median').
+0052 obj.params.kcatAggregation = 'max';
+0053
+0054 % Taxonomic identifier for Complex Portal
+0055 obj.params.complex.taxonomicID = [];
+0056
+0057 % Provide your organism KEGG ID, selected at
+0058 % https://www.genome.jp/kegg/catalog/org_list.html
+0059 obj.params.kegg.ID = 'sce';
+0060 % Field for KEGG gene identifier; should match the gene
+0061 % identifiers used in the model. With 'kegg', it takes the
+0062 % default KEGG Entry identifier (for example YER023W here:
+0063 % https://www.genome.jp/dbget-bin/www_bget?sce:YER023W).
+0064 % Alternatively, gene identifiers from the "Other DBs" section
+0065 % of the KEGG page can be selected. For example "NCBI-GeneID",
+0066 % "UniProt", or "Ensembl". Not all DB entries are available for
+0067 % all organisms and/or genes.
+0068 obj.params.kegg.geneID = 'kegg';
+0069
+0070 % Provide what identifier should be used to query UniProt.
+0071 % Select proteome IDs at https://www.uniprot.org/proteomes/
+0072 % or taxonomy IDs at https://www.uniprot.org/taxonomy.
+0073 obj.params.uniprot.type = 'taxonomy'; % 'proteome' or 'taxonomy'
+0074 obj.params.uniprot.ID = '559292'; % should match the ID type
+0075 % Field for Uniprot gene ID - should match the gene ids used in the
+0076 % model. It should be one of the "Returned Field" entries under
+0077 % "Names & Taxonomy" at this page: https://www.uniprot.org/help/return_fields
+0078 obj.params.uniprot.geneIDfield = 'gene_oln';
+0079 % Whether only reviewed data from UniProt should be considered.
+0080 % Reviewed data has highest confidence, but coverage might be (very)
+0081 % low for non-model organisms
+0082 obj.params.uniprot.reviewed = false;
+0083
+0084 % Reaction ID for glucose exchange reaction (or other preferred carbon source)
+0085 obj.params.c_source = 'r_1714';
+0086
+0087 % Reaction ID for biomass pseudoreaction
+0088 obj.params.bioRxn = 'r_4041';
+0089
+0090 % Name of the compartment where the protein pseudometabolites
+0091 % should be located (all be located in the same compartment,
+0092 % this does not interfere with them catalyzing reactions in
+0093 % different compartments). Typically, cytoplasm is chosen.
+0094 obj.params.enzyme_comp = 'cytoplasm';
+0095
+0096 %% Hyperparameters for evotune (CMA-ES) kcat fitting
+0097 % Default initial uncertainty (standard deviation in log-space) for kcat values
+0098 obj.params.evotune.sigma0logDefault = 0.5;
+0099 % Data sources for kcat values, ordered from least to most trusted
+0100 obj.params.evotune.kcatSources = {'dlkcat','brenda','custom'};
+0101 % Initial uncertainty for each source (lower = more trusted data)
+0102 obj.params.evotune.sigma0logSource = [0.4; 0.2; 0.1];
+0103
+0104 % Weight on the max-growth RMSE against the flux RMSE:
+0105 % (rmseFlux + w*rmseMaxGrowth) / (w+1). At 1 both count equally.
+0106 obj.params.evotune.maxGrowthWeight = 1.0;
+0107 % Weight on a prior term in the search objective,
+0108 % rmse + w*mean((log(k/k0)/sigma0log)^2). Keeps large corrections
+0109 % reproducible across seeds; 0 scores on RMSE alone.
+0110 obj.params.evotune.priorPenaltyWeight = 0.03;
+0111 % Give isozyme copies of one reaction (sharing a prior value and
+0112 % a source) a single shared kcat, so the search cannot invent a
+0113 % distinction the kcat assignment never made.
+0114 obj.params.evotune.tieIsozymes = true;
+0115
+0116 % Stop optimization when RMSE falls below this threshold
+0117 obj.params.evotune.rmseThreshold = 0.2;
+0118 % Maximum number of CMA-ES generations before termination
+0119 obj.params.evotune.maxGenerations = 150;
+0120 end
+0121
+0122 % function ecModel = makeModelAnaerobic(ecModel)
+0123 % % Define a model-specific function in the 'code' subfolder,
+0124 % % that can constrain the model to anaerobic conditions, and
+0125 % % include the name of this function here. This is used by
+0126 % % cmaesKcatTuning.m (via evotuneScore.m) if the fluxData
+0127 % % has anaerobic conditions.
+0128 % addpath(fullfile(obj.params.path,'code'));
+0129 % ecModel = nameOfModelSpecificFunction(ecModel);
+0130 % end
+0131
+0132 function [spont,spontRxnNames] = getSpontaneousReactions(obj,model)
+0133 % getSpontaneousReactions Identify spontaneous reactions in the model.
+0134 %
+0135 % Indicates how spontaneous reactions are identified. Here it
+0136 % is done by the reaction having 'spontaneous' in its name.
+0137 %
+0138 % Parameters
+0139 % ----------
+0140 % model : struct
+0141 % a model in RAVEN format.
+0142 %
+0143 % Returns
+0144 % -------
+0145 % spont : logical
+0146 % true for each reaction identified as spontaneous.
+0147 % spontRxnNames : cell
+0148 % names of the reactions identified as spontaneous.
+0149 spont = contains(model.rxnNames,'spontaneous');
+0150 spontRxnNames = model.rxnNames(spont);
+0151 end
+0152 end
+0153 end
convertAdapterToToml Write the parameters of an adapter class to model_adapter.toml.
+
+
SYNOPSIS
+
function report = convertAdapterToToml(adapter, tomlFile)
+
+
DESCRIPTION
+
convertAdapterToToml Write the parameters of an adapter class to model_adapter.toml.
+
+ Converts an existing ModelAdapter class (GECKO 3 or 4 style) to
+ model_adapter.toml, the configuration file shared by GECKO and geckopy.
+ Parameter names are translated with adapterParameters.tsv (for instance
+ Ptot becomes p_tot), paths inside the project folder are written
+ relative to it, and parameters without a geckopy name are kept under
+ [matlab.params], so that loadAdapterParams gives back the same params.
+
+ Methods that the adapter class overrides cannot be written to TOML.
+ They are listed in the report, with how to carry them over:
+ - getSpontaneousReactions: set [spontaneous] name_contains or file;
+ - makeModelAnaerobic, changeProteinBiomass: set [matlab]
+ make_model_anaerobic or change_protein_biomass to a function name;
+ - other methods: keep the adapter class and let its constructor call
+ loadAdapterParams.
+
+ Parameters
+ ----------
+ adapter : ModelAdapter or char
+ an adapter object, or the path to its .m file.
+ tomlFile : char, optional
+ file to write (default: model_adapter.toml in the adapter's
+ params.path). An existing file is not overwritten.
+
+ Returns
+ -------
+ report : struct
+ - file: the file written;
+ - unmapped: parameters written under [matlab.params];
+ - methods: overridden methods that need carrying over by hand.
+
+ Examples
+ --------
+ report = convertAdapterToToml(fullfile(projectPath,'MyGEMAdapter.m'));
+ adapter = TomlAdapter(projectPath);
+
+ See also
+ --------
+ TomlAdapter, loadAdapterParams, writeAdapterToml
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
writeAdapterToml writeAdapterToml Write a struct as a model adapter TOML file.
0001 function report = convertAdapterToToml(adapter, tomlFile)
+0002 % convertAdapterToToml Write the parameters of an adapter class to model_adapter.toml.
+0003 %
+0004 % Converts an existing ModelAdapter class (GECKO 3 or 4 style) to
+0005 % model_adapter.toml, the configuration file shared by GECKO and geckopy.
+0006 % Parameter names are translated with adapterParameters.tsv (for instance
+0007 % Ptot becomes p_tot), paths inside the project folder are written
+0008 % relative to it, and parameters without a geckopy name are kept under
+0009 % [matlab.params], so that loadAdapterParams gives back the same params.
+0010 %
+0011 % Methods that the adapter class overrides cannot be written to TOML.
+0012 % They are listed in the report, with how to carry them over:
+0013 % - getSpontaneousReactions: set [spontaneous] name_contains or file;
+0014 % - makeModelAnaerobic, changeProteinBiomass: set [matlab]
+0015 % make_model_anaerobic or change_protein_biomass to a function name;
+0016 % - other methods: keep the adapter class and let its constructor call
+0017 % loadAdapterParams.
+0018 %
+0019 % Parameters
+0020 % ----------
+0021 % adapter : ModelAdapter or char
+0022 % an adapter object, or the path to its .m file.
+0023 % tomlFile : char, optional
+0024 % file to write (default: model_adapter.toml in the adapter's
+0025 % params.path). An existing file is not overwritten.
+0026 %
+0027 % Returns
+0028 % -------
+0029 % report : struct
+0030 % - file: the file written;
+0031 % - unmapped: parameters written under [matlab.params];
+0032 % - methods: overridden methods that need carrying over by hand.
+0033 %
+0034 % Examples
+0035 % --------
+0036 % report = convertAdapterToToml(fullfile(projectPath,'MyGEMAdapter.m'));
+0037 % adapter = TomlAdapter(projectPath);
+0038 %
+0039 % See also
+0040 % --------
+0041 % TomlAdapter, loadAdapterParams, writeAdapterToml
+0042
+0043 if ischar(adapter) || isstring(adapter)
+0044 adapter = ModelAdapterManager.getAdapter(char(adapter));
+0045 end
+0046 params = adapter.params;
+0047 if isfield(params,'path') && isstruct(params.path)
+0048 params.path = params.path.Name; % a struct path, as some old adapters keep it
+0049 end
+0050 projectPath = '';
+0051 if isfield(params,'path')
+0052 projectPath = char(params.path);
+0053 end
+0054 if nargin < 2 || isempty(tomlFile)
+0055 tomlFile = fullfile(projectPath, 'model_adapter.toml');
+0056 end
+0057 if isfile(tomlFile)
+0058 error('GECKO:convertAdapterToToml', '%s exists already; it is not overwritten.', tomlFile);
+0059 end
+0060
+0061 map = readMap();
+0062 data = struct();
+0063 used = {};
+0064 for k = 1:numel(map.tomlKey)
+0065 field = map.matlabField{k};
+0066 if isempty(field) || any(strcmp(field, {'evotune.kcatSources','evotune.sigma0logSource'}))
+0067 continue
+0068 end
+0069 [found, value] = getDotted(params, field);
+0070 if ~found || (isempty(value) && ~ischar(value))
+0071 if found
+0072 used{end+1} = field; %#ok<AGROW>
+0073 end
+0074 continue
+0075 end
+0076 used{end+1} = field; %#ok<AGROW>
+0077 if any(strcmp(map.tomlKey{k}, {'conv_gem','spontaneous.file'}))
+0078 value = relativePath(char(value), projectPath);
+0079 end
+0080 data = setDotted(data, map.tomlKey{k}, value);
+0081 end
+0082 used{end+1} = 'path';
+0083
+0084 % Trust tiers: one group per ec.source value.
+0085 if isfield(params,'evotune') && isfield(params.evotune,'kcatSources')
+0086 used = [used, {'evotune.kcatSources','evotune.sigma0logSource'}];
+0087 sources = params.evotune.kcatSources;
+0088 widths = params.evotune.sigma0logSource;
+0089 groups = struct();
+0090 sigma0 = struct();
+0091 for k = 1:numel(sources)
+0092 name = matlab.lang.makeValidName(sources{k});
+0093 groups.(name).sources = sources(k);
+0094 sigma0.(name) = widths(k);
+0095 end
+0096 data.evotune.source_groups = groups;
+0097 data.evotune.sigma0_log_source = sigma0;
+0098 end
+0099
+0100 % Everything else is kept for GECKO under [matlab.params].
+0101 extra = unusedFields(params, '', used);
+0102 report.unmapped = {};
+0103 if ~isempty(fieldnames(extra))
+0104 data.matlab.params = extra;
+0105 report.unmapped = flattenNames(extra, '');
+0106 end
+0107
+0108 report.methods = overriddenMethods(adapter);
+0109 report.file = tomlFile;
+0110
+0111 header = {sprintf('Model adapter for %s, converted from %s.', ...
+0112 getOr(params,'org_name','this project'), class(adapter)), ...
+0113 'Shared by GECKO (MATLAB) and geckopy; see adapterParameters.tsv in GECKO for every key.'};
+0114 writeAdapterToml(data, tomlFile, header);
+0115
+0116 fprintf('Wrote %s.\n', tomlFile);
+0117 if ~isempty(report.unmapped)
+0118 fprintf('Kept for GECKO only, under [matlab.params]: %s\n', strjoin(report.unmapped, ', '));
+0119 end
+0120 if ~isempty(report.methods)
+0121 printOrange(sprintf(['%s overrides methods that a TOML file cannot hold: %s.\n'...
+0122 'Set [spontaneous] for getSpontaneousReactions, [matlab] function names for\n'...
+0123 'makeModelAnaerobic and changeProteinBiomass, or keep the class and let its\n'...
+0124 'constructor call loadAdapterParams.\n'], class(adapter), strjoin(report.methods, ', ')));
+0125 end
+0126 end
+0127
+0128 function map = readMap()
+0129 file = fullfile(fileparts(mfilename('fullpath')), 'adapterParameters.tsv');
+0130 fid = fopen(file, 'r');
+0131 cols = textscan(fid, '%s %s %s %s', 'Delimiter', '\t', 'HeaderLines', 1, 'Whitespace', '');
+0132 fclose(fid);
+0133 map.tomlKey = cols{1};
+0134 map.matlabField = cols{2};
+0135 end
+0136
+0137 function names = overriddenMethods(adapter)
+0138 % Methods defined by the adapter class itself rather than ModelAdapter.
+0139 names = {};
+0140 mc = metaclass(adapter);
+0141 for k = 1:numel(mc.MethodList)
+0142 m = mc.MethodList(k);
+0143 if strcmp(m.DefiningClass.Name, mc.Name) && ~strcmp(m.Name, mc.Name) && ~m.Static
+0144 names{end+1} = m.Name; %#ok<AGROW>
+0145 end
+0146 end
+0147 names = sort(names);
+0148 end
+0149
+0150 function extra = unusedFields(s, prefix, used)
+0151 extra = struct();
+0152 fields = fieldnames(s);
+0153 for k = 1:numel(fields)
+0154 name = fields{k};
+0155 if ~isempty(prefix)
+0156 name = [prefix '.' name];
+0157 end
+0158 if any(strcmp(name, used))
+0159 continue
+0160 end
+0161 value = s.(fields{k});
+0162 if isstruct(value) && isscalar(value) && any(startsWith(used, [name '.']))
+0163 sub = unusedFields(value, name, used);
+0164 if ~isempty(fieldnames(sub))
+0165 extra.(fields{k}) = sub;
+0166 end
+0167 elseif ~(isempty(value) && ~ischar(value) && ~iscell(value))
+0168 extra.(fields{k}) = value;
+0169 end
+0170 end
+0171 end
+0172
+0173 function names = flattenNames(s, prefix)
+0174 names = {};
+0175 fields = fieldnames(s);
+0176 for k = 1:numel(fields)
+0177 name = fields{k};
+0178 if ~isempty(prefix)
+0179 name = [prefix '.' name];
+0180 end
+0181 if isstruct(s.(fields{k}))
+0182 names = [names, flattenNames(s.(fields{k}), name)]; %#ok<AGROW>
+0183 else
+0184 names{end+1} = name; %#ok<AGROW>
+0185 end
+0186 end
+0187 end
+0188
+0189 function p = relativePath(p, projectPath)
+0190 p = strrep(p, '\', '/');
+0191 base = strrep(projectPath, '\', '/');
+0192 if ~isempty(base) && startsWith(p, [base '/'])
+0193 p = p(numel(base)+2:end);
+0194 end
+0195 end
+0196
+0197 function [found, value] = getDotted(s, key)
+0198 parts = strsplit(key, '.');
+0199 value = s;
+0200 found = true;
+0201 for k = 1:numel(parts)
+0202 if ~isstruct(value) || ~isfield(value, parts{k})
+0203 found = false;
+0204 value = [];
+0205 return
+0206 end
+0207 value = value.(parts{k});
+0208 end
+0209 end
+0210
+0211 function s = setDotted(s, key, value)
+0212 parts = strsplit(key, '.');
+0213 if numel(parts) == 1
+0214 s.(parts{1}) = value;
+0215 return
+0216 end
+0217 if ~isfield(s, parts{1}) || ~isstruct(s.(parts{1}))
+0218 s.(parts{1}) = struct();
+0219 end
+0220 s.(parts{1}) = setDotted(s.(parts{1}), strjoin(parts(2:end), '.'), value);
+0221 end
+0222
+0223 function v = getOr(s, field, default)
+0224 v = default;
+0225 if isfield(s, field) && ~isempty(s.(field))
+0226 v = s.(field);
+0227 end
+0228 end
loadAdapterParams Read model adapter parameters from a TOML file.
+
+
SYNOPSIS
+
function params = loadAdapterParams(tomlFile)
+
+
DESCRIPTION
+
loadAdapterParams Read model adapter parameters from a TOML file.
+
+ Reads model_adapter.toml, the configuration file shared by GECKO and
+ geckopy, and returns the parameters with GECKO's field names, so that
+ they can be used as ModelAdapter.params. Keys are translated with
+ adapterParameters.tsv (for instance p_tot becomes Ptot); keys that are
+ not given get the default listed there, which is geckopy's default.
+
+ Parameters
+ ----------
+ tomlFile : char
+ path to model_adapter.toml, or to the project folder that holds it.
+
+ Returns
+ -------
+ params : struct
+ the adapter parameters, as in ModelAdapter.params:
+ - path: the project folder (the folder of the TOML file);
+ - convGEM and spontaneous.file: absolute paths;
+ - evotune.kcatSources and evotune.sigma0logSource: built from the
+ evotune.source_groups and evotune.sigma0_log_source tables, one
+ entry per ec.source value;
+ - fields under [matlab.params] are copied as they are, for
+ parameters that only GECKO uses.
+
+ Examples
+ --------
+ params = loadAdapterParams(fullfile(projectPath,'model_adapter.toml'));
+
+ See also
+ --------
+ TomlAdapter, readAdapterToml, convertAdapterToToml
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
readAdapterToml readAdapterToml Read a model adapter TOML file into a struct.
+This function is called by:
+
+
TomlAdapter TomlAdapter Model adapter configured by model_adapter.toml.
0001 function params = loadAdapterParams(tomlFile)
+0002 % loadAdapterParams Read model adapter parameters from a TOML file.
+0003 %
+0004 % Reads model_adapter.toml, the configuration file shared by GECKO and
+0005 % geckopy, and returns the parameters with GECKO's field names, so that
+0006 % they can be used as ModelAdapter.params. Keys are translated with
+0007 % adapterParameters.tsv (for instance p_tot becomes Ptot); keys that are
+0008 % not given get the default listed there, which is geckopy's default.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % tomlFile : char
+0013 % path to model_adapter.toml, or to the project folder that holds it.
+0014 %
+0015 % Returns
+0016 % -------
+0017 % params : struct
+0018 % the adapter parameters, as in ModelAdapter.params:
+0019 % - path: the project folder (the folder of the TOML file);
+0020 % - convGEM and spontaneous.file: absolute paths;
+0021 % - evotune.kcatSources and evotune.sigma0logSource: built from the
+0022 % evotune.source_groups and evotune.sigma0_log_source tables, one
+0023 % entry per ec.source value;
+0024 % - fields under [matlab.params] are copied as they are, for
+0025 % parameters that only GECKO uses.
+0026 %
+0027 % Examples
+0028 % --------
+0029 % params = loadAdapterParams(fullfile(projectPath,'model_adapter.toml'));
+0030 %
+0031 % See also
+0032 % --------
+0033 % TomlAdapter, readAdapterToml, convertAdapterToToml
+0034
+0035 if isfolder(tomlFile)
+0036 tomlFile = fullfile(tomlFile, 'model_adapter.toml');
+0037 end
+0038 if ~isfile(tomlFile)
+0039 error('GECKO:loadAdapterParams', 'No model adapter file found at %s', tomlFile);
+0040 end
+0041 tomlFile = char(tomlFile);
+0042 [projectPath, ~, ~] = fileparts(getAbsolutePath(tomlFile));
+0043
+0044 data = readAdapterToml(tomlFile);
+0045 map = adapterParameterMap();
+0046
+0047 % Unknown keys are errors, as in geckopy, so that a typo is not ignored.
+0048 known = [map.tomlKey; {'evotune.source_groups'; 'evotune.sigma0_log_source'}];
+0049 checkKnownKeys(data, '', known, tomlFile);
+0050
+0051 params = struct();
+0052 params.path = projectPath;
+0053 for k = 1:numel(map.tomlKey)
+0054 key = map.tomlKey{k};
+0055 if any(strcmp(key, {'evotune.source_groups', 'evotune.sigma0_log_source'}))
+0056 continue
+0057 end
+0058 [found, value] = getDotted(data, key);
+0059 if ~found
+0060 if isempty(map.default{k})
+0061 continue
+0062 end
+0063 value = map.default{k};
+0064 end
+0065 if isempty(map.matlabField{k})
+0066 field = key; % a key that only geckopy reads keeps its name
+0067 else
+0068 field = map.matlabField{k};
+0069 end
+0070 params = setDotted(params, field, value);
+0071 end
+0072
+0073 if ~isfield(params, 'complex')
+0074 params.complex.taxonomicID = []; % geckopy's default: no Complex Portal query
+0075 end
+0076
+0077 for f = {'convGEM'}
+0078 if isfield(params, f{1})
+0079 params.(f{1}) = projectFile(projectPath, params.(f{1}));
+0080 end
+0081 end
+0082 if isfield(params, 'spontaneous') && isfield(params.spontaneous, 'file')
+0083 params.spontaneous.file = projectFile(projectPath, params.spontaneous.file);
+0084 end
+0085
+0086 params = sourceGroups(params, data, tomlFile);
+0087
+0088 if isfield(data, 'matlab') && isfield(data.matlab, 'params')
+0089 params = mergeStruct(params, data.matlab.params);
+0090 end
+0091 end
+0092
+0093 function map = adapterParameterMap()
+0094 % Reads adapterParameters.tsv into toml keys, MATLAB fields and defaults.
+0095 file = fullfile(fileparts(mfilename('fullpath')), 'adapterParameters.tsv');
+0096 fid = fopen(file, 'r');
+0097 cols = textscan(fid, '%s %s %s %s', 'Delimiter', '\t', 'HeaderLines', 1, ...
+0098 'Whitespace', '', 'ReturnOnError', false);
+0099 fclose(fid);
+0100 map.tomlKey = cols{1};
+0101 map.matlabField = cols{2};
+0102 map.default = cell(size(cols{3}));
+0103 for k = 1:numel(cols{3})
+0104 if ~isempty(cols{3}{k})
+0105 map.default{k} = parseDefault(cols{3}{k});
+0106 end
+0107 end
+0108 end
+0109
+0110 function value = parseDefault(text)
+0111 % A default is written as a TOML value.
+0112 tmp = [tempname '.toml'];
+0113 fid = fopen(tmp, 'w');
+0114 fprintf(fid, 'x = %s\n', text);
+0115 fclose(fid);
+0116 cleanup = onCleanup(@() delete(tmp));
+0117 data = readAdapterToml(tmp);
+0118 value = data.x;
+0119 if ischar(value) && isempty(value)
+0120 value = '';
+0121 end
+0122 end
+0123
+0124 function checkKnownKeys(s, prefix, known, tomlFile)
+0125 fields = fieldnames(s);
+0126 for k = 1:numel(fields)
+0127 key = fields{k};
+0128 if ~isempty(prefix)
+0129 key = [prefix '.' key];
+0130 end
+0131 if strcmp(key, 'matlab.params')
+0132 continue% free-form parameters for GECKO only
+0133 end
+0134 if any(strcmp(key, known)) || any(startsWith(known, [key '.']))
+0135 if isstruct(s.(fields{k})) && ~any(strcmp(key, known))
+0136 checkKnownKeys(s.(fields{k}), key, known, tomlFile);
+0137 end
+0138 continue
+0139 end
+0140 if any(startsWith(key, {'evotune.source_groups.', 'evotune.sigma0_log_source.'}))
+0141 continue
+0142 end
+0143 error('GECKO:loadAdapterParams', ['Unknown key "%s" in %s. Parameters that only '...
+0144 'GECKO uses go under [matlab.params].'], key, tomlFile);
+0145 end
+0146 end
+0147
+0148 function params = sourceGroups(params, data, tomlFile)
+0149 % evotune.source_groups and evotune.sigma0_log_source become the
+0150 % kcatSources and sigma0logSource lists that evotune reads: one entry per
+0151 % ec.source value, with its group's prior width.
+0152 groups = struct();
+0153 widths = struct();
+0154 if isfield(data, 'evotune') && isfield(data.evotune, 'source_groups')
+0155 groups = data.evotune.source_groups;
+0156 if isfield(data.evotune, 'sigma0_log_source')
+0157 widths = data.evotune.sigma0_log_source;
+0158 end
+0159 else
+0160 % geckopy's defaults (EvotuneParams.source_groups, sigma0_log_source).
+0161 groups.dlkcat.sources = {'dlkcat'};
+0162 groups.brenda.sources = {'brenda'};
+0163 groups.custom.sources = {'custom'};
+0164 widths.dlkcat = 0.4;
+0165 widths.brenda = 0.2;
+0166 widths.custom = 0.1;
+0167 end
+0168 names = fieldnames(groups);
+0169 if ~isempty(setxor(names, fieldnames(widths)))
+0170 error('GECKO:loadAdapterParams', ['%s: evotune.sigma0_log_source must have '...
+0171 'the same group names as evotune.source_groups'], tomlFile);
+0172 end
+0173 kcatSources = {};
+0174 sigma0logSource = [];
+0175 for k = 1:numel(names)
+0176 group = groups.(names{k});
+0177 sources = {};
+0178 if isfield(group, 'sources')
+0179 sources = group.sources;
+0180 if ischar(sources)
+0181 sources = {sources};
+0182 end
+0183 end
+0184 if isfield(group, 'match_okp') && group.match_okp && isfield(params, 'okp')
+0185 sources{end+1} = params.okp.method; %#ok<AGROW>
+0186 end
+0187 kcatSources = [kcatSources, sources]; %#ok<AGROW>
+0188 sigma0logSource = [sigma0logSource; repmat(widths.(names{k}), numel(sources), 1)]; %#ok<AGROW>
+0189 end
+0190 params.evotune.kcatSources = kcatSources;
+0191 params.evotune.sigma0logSource = sigma0logSource;
+0192 end
+0193
+0194 function [found, value] = getDotted(s, key)
+0195 parts = strsplit(key, '.');
+0196 value = s;
+0197 found = true;
+0198 for k = 1:numel(parts)
+0199 if ~isstruct(value) || ~isfield(value, parts{k})
+0200 found = false;
+0201 value = [];
+0202 return
+0203 end
+0204 value = value.(parts{k});
+0205 end
+0206 end
+0207
+0208 function s = setDotted(s, key, value)
+0209 parts = strsplit(key, '.');
+0210 if numel(parts) == 1
+0211 s.(parts{1}) = value;
+0212 return
+0213 end
+0214 if ~isfield(s, parts{1}) || ~isstruct(s.(parts{1}))
+0215 s.(parts{1}) = struct();
+0216 end
+0217 s.(parts{1}) = setDotted(s.(parts{1}), strjoin(parts(2:end), '.'), value);
+0218 end
+0219
+0220 function s = mergeStruct(s, extra)
+0221 fields = fieldnames(extra);
+0222 for k = 1:numel(fields)
+0223 value = extra.(fields{k});
+0224 if isstruct(value) && isscalar(value) && isfield(s, fields{k}) && isstruct(s.(fields{k}))
+0225 s.(fields{k}) = mergeStruct(s.(fields{k}), value);
+0226 else
+0227 s.(fields{k}) = value;
+0228 end
+0229 end
+0230 end
+0231
+0232 function p = projectFile(projectPath, p)
+0233 if isempty(p)
+0234 return
+0235 end
+0236 if ~isAbsolute(p)
+0237 p = fullfile(projectPath, p);
+0238 end
+0239 end
+0240
+0241 function tf = isAbsolute(p)
+0242 tf = startsWith(p, '/') || startsWith(p, '\') || ~isempty(regexp(p, '^[A-Za-z]:', 'once'));
+0243 end
+0244
+0245 function p = getAbsolutePath(p)
+0246 if ~isAbsolute(p)
+0247 p = fullfile(pwd, p);
+0248 end
+0249 end
readAdapterToml Read a model adapter TOML file into a struct.
+
+
SYNOPSIS
+
function data = readAdapterToml(filename)
+
+
DESCRIPTION
+
readAdapterToml Read a model adapter TOML file into a struct.
+
+ Reads the subset of TOML that model adapter files use: comments, bare
+ keys, basic and literal strings, integers, floats, booleans, arrays of
+ these values (also over several lines), tables ([kegg], [a.b.c]) and
+ arrays of tables ([[a]]). Anything else, such as dates, inline tables,
+ multi-line strings or quoted keys, stops with an error that names the
+ line.
+
+ Parameters
+ ----------
+ filename : char
+ path to the TOML file.
+
+ Returns
+ -------
+ data : struct
+ one field per key, with tables as nested structs and arrays of
+ tables as struct arrays. Strings are char, numbers double, booleans
+ logical, arrays of strings a 1xn cell and arrays of numbers or
+ booleans a 1xn vector. An empty array is an empty cell.
+
+ Examples
+ --------
+ data = readAdapterToml(fullfile(projectPath,'model_adapter.toml'));
+
+ See also
+ --------
+ writeAdapterToml, loadAdapterParams
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
loadAdapterParams loadAdapterParams Read model adapter parameters from a TOML file.
0001 function data = readAdapterToml(filename)
+0002 % readAdapterToml Read a model adapter TOML file into a struct.
+0003 %
+0004 % Reads the subset of TOML that model adapter files use: comments, bare
+0005 % keys, basic and literal strings, integers, floats, booleans, arrays of
+0006 % these values (also over several lines), tables ([kegg], [a.b.c]) and
+0007 % arrays of tables ([[a]]). Anything else, such as dates, inline tables,
+0008 % multi-line strings or quoted keys, stops with an error that names the
+0009 % line.
+0010 %
+0011 % Parameters
+0012 % ----------
+0013 % filename : char
+0014 % path to the TOML file.
+0015 %
+0016 % Returns
+0017 % -------
+0018 % data : struct
+0019 % one field per key, with tables as nested structs and arrays of
+0020 % tables as struct arrays. Strings are char, numbers double, booleans
+0021 % logical, arrays of strings a 1xn cell and arrays of numbers or
+0022 % booleans a 1xn vector. An empty array is an empty cell.
+0023 %
+0024 % Examples
+0025 % --------
+0026 % data = readAdapterToml(fullfile(projectPath,'model_adapter.toml'));
+0027 %
+0028 % See also
+0029 % --------
+0030 % writeAdapterToml, loadAdapterParams
+0031
+0032 text = fileread(filename);
+0033 text = strrep(text, sprintf('\r\n'), newline);
+0034 lines = strsplit(text, newline, 'CollapseDelimiters', false);
+0035
+0036 data = struct();
+0037 tablePath = {}; % keys of the current table, each element {name, arrayIndex}
+0038 arrayNames = {}; % dotted names declared as arrays of tables
+0039 i = 0;
+0040 while i < numel(lines)
+0041 i = i + 1;
+0042 lineNo = i;
+0043 line = stripComment(lines{i}, filename, lineNo);
+0044 if isempty(strtrim(line))
+0045 continue
+0046 end
+0047 trimmed = strtrim(line);
+0048 if startsWith(trimmed, '[[')
+0049 if ~endsWith(trimmed, ']]')
+0050 tomlError(filename, lineNo, 'array of tables header must end with ]]');
+0051 end
+0052 keys = splitKey(trimmed(3:end-2), filename, lineNo);
+0053 [data, tablePath] = appendTableArray(data, keys, arrayNames, filename, lineNo);
+0054 arrayNames = union(arrayNames, {strjoin(keys, '.')});
+0055 elseif startsWith(trimmed, '[')
+0056 if ~endsWith(trimmed, ']')
+0057 tomlError(filename, lineNo, 'table header must end with ]');
+0058 end
+0059 keys = splitKey(trimmed(2:end-1), filename, lineNo);
+0060 tablePath = resolvePath(data, keys, arrayNames);
+0061 data = ensureTable(data, tablePath, filename, lineNo);
+0062 else
+0063 eq = findUnquoted(line, '=');
+0064 if isempty(eq)
+0065 tomlError(filename, lineNo, 'expected key = value');
+0066 end
+0067 keys = splitKey(line(1:eq-1), filename, lineNo);
+0068 valueText = strtrim(line(eq+1:end));
+0069 % Arrays may span several lines: read on until the brackets close.
+0070 while startsWith(valueText, '[') && bracketDepth(valueText) > 0
+0071 if i >= numel(lines)
+0072 tomlError(filename, lineNo, 'array is not closed');
+0073 end
+0074 i = i + 1;
+0075 valueText = [valueText ' ' strtrim(stripComment(lines{i}, filename, i))]; %#ok<AGROW>
+0076 end
+0077 value = parseValue(valueText, filename, lineNo);
+0078 data = setKey(data, [tablePath, keyPath(keys)], value, filename, lineNo);
+0079 end
+0080 end
+0081 end
+0082
+0083 function line = stripComment(line, filename, lineNo)
+0084 % Removes a # comment that is not inside a string.
+0085 inBasic = false; inLiteral = false; k = 1;
+0086 while k <= numel(line)
+0087 c = line(k);
+0088 if inBasic
+0089 if c == '\'
+0090 k = k + 1;
+0091 elseif c == '"'
+0092 inBasic = false;
+0093 end
+0094 elseif inLiteral
+0095 if c == ''''
+0096 inLiteral = false;
+0097 end
+0098 elseif c == '"'
+0099 if k + 2 <= numel(line) && strcmp(line(k:k+2), '"""')
+0100 tomlError(filename, lineNo, 'multi-line strings are not supported');
+0101 end
+0102 inBasic = true;
+0103 elseif c == ''''
+0104 if k + 2 <= numel(line) && strcmp(line(k:k+2), '''''''')
+0105 tomlError(filename, lineNo, 'multi-line strings are not supported');
+0106 end
+0107 inLiteral = true;
+0108 elseif c == '#'
+0109 line = line(1:k-1);
+0110 return
+0111 end
+0112 k = k + 1;
+0113 end
+0114 end
+0115
+0116 function idx = findUnquoted(line, ch)
+0117 % Position of the first ch outside a string, or empty.
+0118 idx = [];
+0119 inBasic = false; inLiteral = false; k = 1;
+0120 while k <= numel(line)
+0121 c = line(k);
+0122 if inBasic
+0123 if c == '\'
+0124 k = k + 1;
+0125 elseif c == '"'
+0126 inBasic = false;
+0127 end
+0128 elseif inLiteral
+0129 if c == ''''
+0130 inLiteral = false;
+0131 end
+0132 elseif c == '"'
+0133 inBasic = true;
+0134 elseif c == ''''
+0135 inLiteral = true;
+0136 elseif c == ch
+0137 idx = k;
+0138 return
+0139 end
+0140 k = k + 1;
+0141 end
+0142 end
+0143
+0144 function depth = bracketDepth(text)
+0145 % Open [ minus closed ] outside strings.
+0146 depth = 0; inBasic = false; inLiteral = false; k = 1;
+0147 while k <= numel(text)
+0148 c = text(k);
+0149 if inBasic
+0150 if c == '\'
+0151 k = k + 1;
+0152 elseif c == '"'
+0153 inBasic = false;
+0154 end
+0155 elseif inLiteral
+0156 if c == ''''
+0157 inLiteral = false;
+0158 end
+0159 elseif c == '"'
+0160 inBasic = true;
+0161 elseif c == ''''
+0162 inLiteral = true;
+0163 elseif c == '['
+0164 depth = depth + 1;
+0165 elseif c == ']'
+0166 depth = depth - 1;
+0167 end
+0168 k = k + 1;
+0169 end
+0170 end
+0171
+0172 function keys = splitKey(text, filename, lineNo)
+0173 % Dotted bare key into its parts.
+0174 keys = strtrim(strsplit(strtrim(text), '.'));
+0175 for k = 1:numel(keys)
+0176 if isempty(regexp(keys{k}, '^[A-Za-z][A-Za-z0-9_]*$', 'once'))
+0177 tomlError(filename, lineNo, sprintf(['key "%s" is not supported: use letters, '...
+0178 'digits and underscores, starting with a letter'], keys{k}));
+0179 end
+0180 end
+0181 end
+0182
+0183 function p = keyPath(keys)
+0184 p = cellfun(@(k) {k, 0}, keys, 'UniformOutput', false);
+0185 end
+0186
+0187 function path = resolvePath(data, keys, arrayNames)
+0188 % Path of {name, index} pairs; a key that names an array of tables refers
+0189 % to its last element.
+0190 path = cell(1, numel(keys));
+0191 for k = 1:numel(keys)
+0192 idx = 0;
+0193 if ismember(strjoin(keys(1:k), '.'), arrayNames)
+0194 idx = numel(getKey(data, [path(1:k-1), {{keys{k}, 0}}]));
+0195 end
+0196 path{k} = {keys{k}, idx};
+0197 end
+0198 end
+0199
+0200 function [data, tablePath] = appendTableArray(data, keys, arrayNames, filename, lineNo)
+0201 % Appends a new element to the array of tables named by keys.
+0202 parent = resolvePath(data, keys(1:end-1), arrayNames);
+0203 data = ensureTable(data, parent, filename, lineNo);
+0204 name = strjoin(keys, '.');
+0205 existing = getKey(data, [parent, {{keys{end}, 0}}]);
+0206 if isempty(existing) || ~ismember(name, arrayNames)
+0207 if ~isempty(existing)
+0208 tomlError(filename, lineNo, sprintf('"%s" is already a table or value', name));
+0209 end
+0210 n = 1;
+0211 else
+0212 n = numel(existing) + 1;
+0213 end
+0214 tablePath = [parent, {{keys{end}, n}}];
+0215 data = setTableElement(data, parent, keys{end}, n);
+0216 end
+0217
+0218 function data = ensureTable(data, path, filename, lineNo)
+0219 if isempty(path)
+0220 return
+0221 end
+0222 name = path{1}{1}; idx = path{1}{2};
+0223 if ~isfield(data, name)
+0224 data.(name) = struct();
+0225 elseif ~isstruct(data.(name))
+0226 tomlError(filename, lineNo, sprintf('"%s" is already a value, not a table', name));
+0227 end
+0228 child = data.(name);
+0229 if idx == 0
+0230 child = ensureTable(child, path(2:end), filename, lineNo);
+0231 data.(name) = child;
+0232 else
+0233 element = child(idx);
+0234 element = ensureTable(element, path(2:end), filename, lineNo);
+0235 data.(name) = assignElement(child, idx, element);
+0236 end
+0237 end
+0238
+0239 function data = setTableElement(data, parent, name, n)
+0240 % Adds element n (an empty struct) to the table array name under parent.
+0241 if isempty(parent)
+0242 if ~isfield(data, name) || isempty(data.(name)) || n == 1
+0243 data.(name) = struct();
+0244 else
+0245 data.(name) = assignElement(data.(name), n, struct());
+0246 end
+0247 return
+0248 end
+0249 pname = parent{1}{1}; pidx = parent{1}{2};
+0250 child = data.(pname);
+0251 if pidx == 0
+0252 data.(pname) = setTableElement(child, parent(2:end), name, n);
+0253 else
+0254 element = setTableElement(child(pidx), parent(2:end), name, n);
+0255 data.(pname) = assignElement(child, pidx, element);
+0256 end
+0257 end
+0258
+0259 function value = getKey(data, path)
+0260 value = [];
+0261 node = data;
+0262 for k = 1:numel(path)
+0263 name = path{k}{1}; idx = path{k}{2};
+0264 if ~isstruct(node) || ~isfield(node, name)
+0265 value = [];
+0266 return
+0267 end
+0268 node = node.(name);
+0269 if idx > 0
+0270 node = node(idx);
+0271 end
+0272 end
+0273 value = node;
+0274 end
+0275
+0276 function data = setKey(data, path, value, filename, lineNo)
+0277 name = path{1}{1}; idx = path{1}{2};
+0278 if numel(path) == 1
+0279 % [] only fills a field that another element of a table array has;
+0280 % TOML itself has no empty value.
+0281 if isfield(data, name) && ~isequal(data.(name), [])
+0282 tomlError(filename, lineNo, sprintf('key "%s" is defined twice', name));
+0283 end
+0284 data.(name) = value;
+0285 return
+0286 end
+0287 if ~isfield(data, name)
+0288 data.(name) = struct();
+0289 elseif ~isstruct(data.(name))
+0290 tomlError(filename, lineNo, sprintf('"%s" is already a value, not a table', name));
+0291 end
+0292 child = data.(name);
+0293 if idx == 0
+0294 data.(name) = setKey(child, path(2:end), value, filename, lineNo);
+0295 else
+0296 element = setKey(child(idx), path(2:end), value, filename, lineNo);
+0297 data.(name) = assignElement(child, idx, element);
+0298 end
+0299 end
+0300
+0301 function s = assignElement(s, idx, element)
+0302 % Assigns element to s(idx), adding fields so the struct array stays
+0303 % consistent.
+0304 if idx > numel(s) || isempty(fieldnames(s))
+0305 if numel(s) == 1 && isempty(fieldnames(s)) && idx == 1
+0306 s = element;
+0307 return
+0308 end
+0309 end
+0310 fs = fieldnames(element);
+0311 for f = 1:numel(fs)
+0312 if ~isfield(s, fs{f})
+0313 [s.(fs{f})] = deal([]);
+0314 end
+0315 end
+0316 fs = fieldnames(s);
+0317 for f = 1:numel(fs)
+0318 if ~isfield(element, fs{f})
+0319 element.(fs{f}) = [];
+0320 end
+0321 end
+0322 element = orderfields(element, s(1));
+0323 s(idx) = element;
+0324 end
+0325
+0326 function value = parseValue(text, filename, lineNo)
+0327 text = strtrim(text);
+0328 if isempty(text)
+0329 tomlError(filename, lineNo, 'missing value');
+0330 end
+0331 switch text(1)
+0332 case'"'
+0333 [value, rest] = parseBasicString(text, filename, lineNo);
+0334 case''''
+0335 close = find(text(2:end) == '''', 1) + 1;
+0336 if isempty(close)
+0337 tomlError(filename, lineNo, 'string is not closed');
+0338 end
+0339 value = text(2:close-1);
+0340 rest = text(close+1:end);
+0341 case'['
+0342 [value, rest] = parseArray(text, filename, lineNo);
+0343 case'{'
+0344 tomlError(filename, lineNo, 'inline tables are not supported; use a [table] instead');
+0345 otherwise
+0346 [value, rest] = parseScalar(text, filename, lineNo);
+0347 end
+0348 if ~isempty(strtrim(rest))
+0349 tomlError(filename, lineNo, sprintf('unexpected text after the value: %s', strtrim(rest)));
+0350 end
+0351 end
+0352
+0353 function [value, rest] = parseBasicString(text, filename, lineNo)
+0354 value = '';
+0355 k = 2;
+0356 while k <= numel(text)
+0357 c = text(k);
+0358 if c == '"'
+0359 rest = text(k+1:end);
+0360 return
+0361 elseif c == '\'
+0362 if k == numel(text)
+0363 break
+0364 end
+0365 e = text(k+1);
+0366 switch e
+0367 case'n', value(end+1) = newline;
+0368 case't', value(end+1) = sprintf('\t');
+0369 case'"', value(end+1) = '"';
+0370 case'\', value(end+1) = '\';
+0371 case'u'
+0372 value(end+1) = char(hex2dec(text(k+2:k+5)));
+0373 k = k + 4;
+0374 otherwise
+0375 tomlError(filename, lineNo, sprintf('escape \\%s is not supported', e));
+0376 end
+0377 k = k + 2;
+0378 continue
+0379 else
+0380 value(end+1) = c; %#ok<AGROW>
+0381 end
+0382 k = k + 1;
+0383 end
+0384 tomlError(filename, lineNo, 'string is not closed');
+0385 end
+0386
+0387 function [value, rest] = parseArray(text, filename, lineNo)
+0388 % Parses [a, b, ...]; returns a cell for strings, a vector otherwise.
+0389 items = {};
+0390 k = 2;
+0391 while true
+0392 while k <= numel(text) && (isspace(text(k)) || text(k) == ',')
+0393 k = k + 1;
+0394 end
+0395 if k > numel(text)
+0396 tomlError(filename, lineNo, 'array is not closed');
+0397 end
+0398 if text(k) == ']'
+0399 rest = text(k+1:end);
+0400 break
+0401 end
+0402 if text(k) == '['
+0403 tomlError(filename, lineNo, 'nested arrays are not supported');
+0404 end
+0405 % Find the end of this element.
+0406 if text(k) == '"'
+0407 [item, after] = parseBasicString(text(k:end), filename, lineNo);
+0408 k = numel(text) - numel(after) + 1;
+0409 elseif text(k) == ''''
+0410 close = find(text(k+1:end) == '''', 1) + k;
+0411 if isempty(close)
+0412 tomlError(filename, lineNo, 'string is not closed');
+0413 end
+0414 item = text(k+1:close-1);
+0415 k = close + 1;
+0416 else
+0417 stop = k;
+0418 while stop <= numel(text) && text(stop) ~= ',' && text(stop) ~= ']'
+0419 stop = stop + 1;
+0420 end
+0421 [item, after] = parseScalar(strtrim(text(k:stop-1)), filename, lineNo);
+0422 if ~isempty(strtrim(after))
+0423 tomlError(filename, lineNo, 'unexpected text in array');
+0424 end
+0425 k = stop;
+0426 end
+0427 items{end+1} = item; %#ok<AGROW>
+0428 end
+0429 if isempty(items)
+0430 value = {};
+0431 elseif all(cellfun(@ischar, items))
+0432 value = items;
+0433 elseif all(cellfun(@(x) isnumeric(x) && isscalar(x), items))
+0434 value = cell2mat(items);
+0435 elseif all(cellfun(@islogical, items))
+0436 value = cell2mat(items);
+0437 else
+0438 tomlError(filename, lineNo, 'arrays must hold values of one type');
+0439 end
+0440 end
+0441
+0442 function [value, rest] = parseScalar(text, filename, lineNo)
+0443 token = regexp(text, '^[^\s,\]]+', 'match', 'once');
+0444 rest = text(numel(token)+1:end);
+0445 switch token
+0446 case'true'
+0447 value = true;
+0448 case'false'
+0449 value = false;
+0450 case {'inf', '+inf'}
+0451 value = Inf;
+0452 case'-inf'
+0453 value = -Inf;
+0454 case {'nan', '+nan', '-nan'}
+0455 value = NaN;
+0456 otherwise
+0457 clean = strrep(token, '_', '');
+0458 if ~isempty(regexp(clean, '^[+-]?(0|[1-9][0-9]*)$', 'once')) || ...
+0459 ~isempty(regexp(clean, '^[+-]?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?$', 'once'))
+0460 value = str2double(clean);
+0461 elseif ~isempty(regexp(token, '^\d{4}-\d{2}-\d{2}', 'once'))
+0462 tomlError(filename, lineNo, 'dates are not supported');
+0463 else
+0464 tomlError(filename, lineNo, sprintf('cannot read value "%s"; strings need quotes', token));
+0465 end
+0466 end
+0467 end
+0468
+0469 function tomlError(filename, lineNo, message)
+0470 error('GECKO:readAdapterToml', '%s, line %d: %s', filename, lineNo, message);
+0471 end
writeAdapterToml Write a struct as a model adapter TOML file.
+
+
SYNOPSIS
+
function writeAdapterToml(data, filename, header)
+
+
DESCRIPTION
+
writeAdapterToml Write a struct as a model adapter TOML file.
+
+ Writes the subset of TOML that readAdapterToml reads: values first,
+ then each nested struct as a [table] and each struct array as an array
+ of tables ([[name]]).
+
+ Parameters
+ ----------
+ data : struct
+ values may be char or string scalars, numeric or logical scalars or
+ vectors, cell arrays of char, structs and struct arrays.
+ filename : char
+ path of the TOML file to write; an existing file is overwritten.
+ header : char or cell, optional
+ comment lines written at the top of the file (without '#').
+
+ Examples
+ --------
+ writeAdapterToml(struct('sigma',0.5), 'model_adapter.toml', ...
+ {'Adapter for ecYeastGEM'});
+
+ See also
+ --------
+ readAdapterToml, convertAdapterToToml
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
convertAdapterToToml convertAdapterToToml Write the parameters of an adapter class to model_adapter.toml.
0001 function writeAdapterToml(data, filename, header)
+0002 % writeAdapterToml Write a struct as a model adapter TOML file.
+0003 %
+0004 % Writes the subset of TOML that readAdapterToml reads: values first,
+0005 % then each nested struct as a [table] and each struct array as an array
+0006 % of tables ([[name]]).
+0007 %
+0008 % Parameters
+0009 % ----------
+0010 % data : struct
+0011 % values may be char or string scalars, numeric or logical scalars or
+0012 % vectors, cell arrays of char, structs and struct arrays.
+0013 % filename : char
+0014 % path of the TOML file to write; an existing file is overwritten.
+0015 % header : char or cell, optional
+0016 % comment lines written at the top of the file (without '#').
+0017 %
+0018 % Examples
+0019 % --------
+0020 % writeAdapterToml(struct('sigma',0.5), 'model_adapter.toml', ...
+0021 % {'Adapter for ecYeastGEM'});
+0022 %
+0023 % See also
+0024 % --------
+0025 % readAdapterToml, convertAdapterToToml
+0026
+0027 if nargin < 3
+0028 header = {};
+0029 elseif ischar(header) || isstring(header)
+0030 header = cellstr(header);
+0031 end
+0032
+0033 out = {};
+0034 for k = 1:numel(header)
+0035 out{end+1} = ['# ' header{k}]; %#ok<AGROW>
+0036 end
+0037 if ~isempty(header)
+0038 out{end+1} = '';
+0039 end
+0040 out = writeTable(out, data, {});
+0041
+0042 fid = fopen(filename, 'w');
+0043 if fid < 0
+0044 error('GECKO:writeAdapterToml', 'Cannot write %s', filename);
+0045 end
+0046 fprintf(fid, '%s\n', out{:});
+0047 fclose(fid);
+0048 end
+0049
+0050 function out = writeTable(out, s, prefix)
+0051 fields = fieldnames(s);
+0052 tables = {};
+0053 for k = 1:numel(fields)
+0054 value = s.(fields{k});
+0055 if isstruct(value)
+0056 tables{end+1} = fields{k}; %#ok<AGROW>
+0057 elseif isempty(value) && ~iscell(value) && ~ischar(value)
+0058 continue% unset value; TOML has no null
+0059 else
+0060 out{end+1} = [fields{k} ' = 'formatValue(value, strjoin([prefix fields(k)], '.'))]; %#ok<AGROW>
+0061 end
+0062 end
+0063 for k = 1:numel(tables)
+0064 value = s.(tables{k});
+0065 name = strjoin([prefix tables(k)], '.');
+0066 if numel(value) == 1
+0067 out{end+1} = ''; %#ok<AGROW>
+0068 out{end+1} = ['[' name ']']; %#ok<AGROW>
+0069 out = writeTable(out, value, [prefix tables(k)]);
+0070 else
+0071 for e = 1:numel(value)
+0072 out{end+1} = ''; %#ok<AGROW>
+0073 out{end+1} = ['[[' name ']]']; %#ok<AGROW>
+0074 out = writeTable(out, value(e), [prefix tables(k)]);
+0075 end
+0076 end
+0077 end
+0078 end
+0079
+0080 function text = formatValue(value, name)
+0081 if isstring(value) && isscalar(value)
+0082 value = char(value);
+0083 end
+0084 if ischar(value)
+0085 text = quote(value);
+0086 elseif iscell(value)
+0087 if ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), value))
+0088 error('GECKO:writeAdapterToml', '%s: only cell arrays of text can be written', name);
+0089 end
+0090 parts = cellfun(@(x) quote(char(x)), value, 'UniformOutput', false);
+0091 text = ['[' strjoin(parts(:)', ', ') ']'];
+0092 elseif islogical(value) || isnumeric(value)
+0093 if isscalar(value)
+0094 text = formatScalar(value);
+0095 else
+0096 parts = arrayfun(@formatScalar, value(:)', 'UniformOutput', false);
+0097 text = ['[' strjoin(parts, ', ') ']'];
+0098 end
+0099 else
+0100 error('GECKO:writeAdapterToml', '%s: values of class %s cannot be written', name, class(value));
+0101 end
+0102 end
+0103
+0104 function text = formatScalar(x)
+0105 if islogical(x)
+0106 if x
+0107 text = 'true';
+0108 else
+0109 text = 'false';
+0110 end
+0111 elseif isinf(x)
+0112 if x > 0
+0113 text = 'inf';
+0114 else
+0115 text = '-inf';
+0116 end
+0117 elseif isnan(x)
+0118 text = 'nan';
+0119 elseif x == round(x) && abs(x) < 1e15
+0120 text = sprintf('%d', x);
+0121 else
+0122 text = sprintf('%.17g', x);
+0123 % Use the shortest form that reads back to the same double.
+0124 for p = 1:17
+0125 candidate = sprintf('%.*g', p, x);
+0126 if str2double(candidate) == x
+0127 text = candidate;
+0128 break
+0129 end
+0130 end
+0131 if isempty(regexp(text, '[.eE]', 'once'))
+0132 text = [text '.0'];
+0133 end
+0134 end
+0135 end
+0136
+0137 function text = quote(s)
+0138 s = strrep(s, '\', '\\');
+0139 s = strrep(s, '"', '\"');
+0140 s = strrep(s, sprintf('\t'), '\t');
+0141 s = strrep(s, newline, '\n');
+0142 text = ['"' s '"'];
+0143 end
buildGECKOdoc Update HTML documentation for all GECKO MATLAB functions.
+
+
SYNOPSIS
+
function buildGECKOdoc()
+
+
DESCRIPTION
+
buildGECKOdoc Update HTML documentation for all GECKO MATLAB functions.
+
+ Updates HTML documentation files for all GECKO MATLAB functions. Uses
+ the m2html functions that are provided with RAVEN.
+
+ Examples
+ --------
+ buildGECKOdoc();
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
findGECKOroot findGECKOroot Find the root of the GECKO directory.
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function buildGECKOdoc()
+0002 % buildGECKOdoc Update HTML documentation for all GECKO MATLAB functions.
+0003 %
+0004 % Updates HTML documentation files for all GECKO MATLAB functions. Uses
+0005 % the m2html functions that are provided with RAVEN.
+0006 %
+0007 % Examples
+0008 % --------
+0009 % buildGECKOdoc();
+0010
+0011 %Make sure that RAVEN-provided m2html is used
+0012 ravenDir=findRAVENroot();
+0013 path(fullfile(ravenDir,'software','m2html'),path);
+0014
+0015 %Get the GECKO path
+0016 geckoDir=findGECKOroot();
+0017
+0018 %Remove old doc directory
+0019 rmdir(fullfile(geckoDir,'doc'),'s');
+0020
+0021 %Get a non-redundant list of GECKO subdirectories containing MATLAB
+0022 %functions. Absolute paths are not compatible with M2HTML, so convert them
+0023 %to the relative paths instead.
+0024 geckoDirs=dir(fullfile(geckoDir,'src','**/*.m'));
+0025 geckoDirs=unique({geckoDirs.folder})';
+0026
+0027 %Make relative path
+0028 relStart = numel(geckoDir)+2;
+0029 for i=1:numel(geckoDirs)
+0030 geckoDirs{i,1} = geckoDirs{i,1}(relStart:end);
+0031 end
+0032
+0033 %Save the current working directory and go to RAVEN root directory
+0034 originalDir=pwd;
+0035 cd(geckoDir);
+0036 %Generate HTML documentation files for RAVEN MATLAB functions
+0037 m2html('mFiles',geckoDirs,'htmldir','doc');
+0038 %Go back to the original working directory
+0039 cd(originalDir);
+0040 end
createGECKOproject Create a basic project structure for GECKO.
+
+
SYNOPSIS
+
function createGECKOproject(varargin)
+
+
DESCRIPTION
+
createGECKOproject Create a basic project structure for GECKO.
+
+ Creates a basic project structure for GECKO. If a project with the same
+ name exists, the project will not be created.
+
+ Name-Value Arguments
+ --------------------
+ name : char
+ a name for the folder structure used in GECKO. Also creates a basic
+ model adapter, which must be manually adjusted. If not defined, a
+ dialog box will appear.
+ path : char
+ a path where to create the folder. If not defined, a dialog box will
+ appear.
+ adapterFormat : char
+ 'toml' (default) writes model_adapter.toml, which GECKO and geckopy
+ both read (see TomlAdapter); 'class' writes an adapter class
+ <name>Adapter.m instead.
+
+ Examples
+ --------
+ % optional arguments may be given positionally or as name-value pairs:
+ createGECKOproject(name, path);
+ createGECKOproject('name', name);
+ createGECKOproject('name', name, 'adapterFormat', 'class');
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
findGECKOroot findGECKOroot Find the root of the GECKO directory.
parseGECKOargs parseGECKOargs Resolve optional arguments passed positionally or by name.
+This function is called by:
+
+
+
+
+
+
+
SOURCE CODE
+
0001 function createGECKOproject(varargin)
+0002 % createGECKOproject Create a basic project structure for GECKO.
+0003 %
+0004 % Creates a basic project structure for GECKO. If a project with the same
+0005 % name exists, the project will not be created.
+0006 %
+0007 % Name-Value Arguments
+0008 % --------------------
+0009 % name : char
+0010 % a name for the folder structure used in GECKO. Also creates a basic
+0011 % model adapter, which must be manually adjusted. If not defined, a
+0012 % dialog box will appear.
+0013 % path : char
+0014 % a path where to create the folder. If not defined, a dialog box will
+0015 % appear.
+0016 % adapterFormat : char
+0017 % 'toml' (default) writes model_adapter.toml, which GECKO and geckopy
+0018 % both read (see TomlAdapter); 'class' writes an adapter class
+0019 % <name>Adapter.m instead.
+0020 %
+0021 % Examples
+0022 % --------
+0023 % % optional arguments may be given positionally or as name-value pairs:
+0024 % createGECKOproject(name, path);
+0025 % createGECKOproject('name', name);
+0026 % createGECKOproject('name', name, 'adapterFormat', 'class');
+0027
+0028 p = parseGECKOargs(varargin, { ...
+0029 'name', []; ...
+0030 'path', []; ...
+0031 'adapterFormat', 'toml'});
+0032 name = p.name;
+0033 path = p.path;
+0034 adapterFormat = validatestring(p.adapterFormat, {'toml','class'});
+0035
+0036 if isempty(name)
+0037 prompt = {'Please provide a project name (e.g. ecYeastGEM)'};
+0038 dlgtitle = 'Project name';
+0039 dims = [1 100];
+0040 definput = {'ecModelGEM'};
+0041 opts.Interpreter = 'tex';
+0042 name = inputdlg(prompt,dlgtitle,dims,definput,opts);
+0043 name = char(name);
+0044 end
+0045
+0046 if isempty(path)
+0047 path = uigetdir('Project Folder path');
+0048 end
+0049
+0050 fullPath = fullfile(path, name);
+0051 % Validate if the project does not exits
+0052 if ~exist(fullPath, 'dir')
+0053 % Create the subfolder
+0054 dir = {'code', 'data', 'models', 'output'};
+0055 for i = 1:length(dir)
+0056 status = mkdir(fullPath, dir{i});
+0057 fid = fopen(fullfile(fullPath, dir{i}, '.keep'), 'w');
+0058 fclose(fid);
+0059 end
+0060
+0061 % Read the template adapter
+0062 if strcmp(adapterFormat, 'toml')
+0063 template = 'adapterTemplate.toml';
+0064 filename = fullfile(path, name, 'model_adapter.toml');
+0065 else
+0066 template = 'adapterTemplate.m';
+0067 filename = fullfile(path, name, [name 'Adapter.m']);
+0068 end
+0069 fid = fopen(fullfile(findGECKOroot, 'src', 'model_adapter', template));
+0070 f = fread(fid, '*char')';
+0071 fclose(fid);
+0072
+0073 % Replace key values
+0074 f = strrep(f, 'KEY_CLASSNAME', [name 'Adapter']);
+0075 f = strrep(f, 'KEY_PATH', path);
+0076 f = strrep(f, 'KEY_NAME', name);
+0077
+0078 % Save the adapter
+0079 fid = fopen(filename, 'w');
+0080 fwrite(fid, f);
+0081 fclose(fid);
+0082 else
+0083 printOrange('WARNING: A project with the same name exits at the same location. The project was not created.\n');
+0084 end
+0085 cd(fullPath)
+0086 end
findGECKOroot Find the root of the GECKO directory.
+
+
SYNOPSIS
+
function [geckoPath, prevDir] = findGECKOroot()
+
+
DESCRIPTION
+
findGECKOroot Find the root of the GECKO directory.
+
+ Finds the root of the GECKO directory, by searching for the path to
+ GECKOInstaller.m. Can also record the current directory, in case a function
+ will use the geckoPath to navigate to a precise folder, and it should
+ return to the previous directory afterwards.
+
+ Returns
+ -------
+ geckoPath : char
+ path to the root of the GECKO directory.
+ prevDir : char
+ the current directory at the time of the call, recorded so that a
+ function can return to it afterwards.
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
buildGECKOdoc buildGECKOdoc Update HTML documentation for all GECKO MATLAB functions.
createGECKOproject createGECKOproject Create a basic project structure for GECKO.
upgradeGECKOproject upgradeGECKOproject Bring a GECKO 3 project to the GECKO 4 layout.
+
+
+
+
+
SOURCE CODE
+
0001 function [geckoPath, prevDir] = findGECKOroot()
+0002 % findGECKOroot Find the root of the GECKO directory.
+0003 %
+0004 % Finds the root of the GECKO directory, by searching for the path to
+0005 % GECKOInstaller.m. Can also record the current directory, in case a function
+0006 % will use the geckoPath to navigate to a precise folder, and it should
+0007 % return to the previous directory afterwards.
+0008 %
+0009 % Returns
+0010 % -------
+0011 % geckoPath : char
+0012 % path to the root of the GECKO directory.
+0013 % prevDir : char
+0014 % the current directory at the time of the call, recorded so that a
+0015 % function can return to it afterwards.
+0016
+0017 ST=dbstack('-completenames');
+0018 prevDir = pwd();
+0019 geckoPath = ST(strcmp({ST.name},'findGECKOroot')).file;
+0020 rootFound = 0;
+0021 while rootFound == 0
+0022 isRoot = exist(fullfile(geckoPath,'GECKOInstaller.m'),'file');
+0023 if isRoot == 2
+0024 rootFound = 1;
+0025 else
+0026 ravenPathOld = geckoPath;
+0027 geckoPath = fileparts(geckoPath);
+0028 if strcmp(ravenPathOld,geckoPath)
+0029 error('Cannot find the GECKO root directory. Make sure you have not removed the GECKOInstaller.m file from your GECKO installation.')
+0030 end
+0031 end
+0032 end
parseGECKOargs Resolve optional arguments passed positionally or by name.
+
+
SYNOPSIS
+
function args = parseGECKOargs(rawArgs, spec)
+
+
DESCRIPTION
+
parseGECKOargs Resolve optional arguments passed positionally or by name.
+
+ Helper that lets a GECKO function accept its optional arguments either in
+ the traditional positional order or as name-value pairs, without losing
+ backward compatibility. 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.
+
+ Parameters
+ ----------
+ rawArgs : cell
+ the function's varargin, i.e. whatever was 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:
+
+ - 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 and should error on an invalid value. Use [] for
+ no validation.
+
+ Returns
+ -------
+ args : struct
+ a struct with one field per parameter name, holding the resolved value.
+
+ Notes
+ -----
+ The calling convention is detected from rawArgs: it is treated as name-
+ value pairs only when it has an even number of elements AND every element
+ in a name position is a string matching one of the declared parameter
+ names. Otherwise it is treated as positional and assigned in the order of
+ the spec. A call therefore uses either the positional or the name-value
+ form for the optional tail, not a mix of the two. When an optional tail
+ value could itself be a string equal to a parameter name, use the explicit
+ name-value form to remove the ambiguity.
+
+ Examples
+ --------
+ % inside makeEcModel(model, varargin):
+ p = parseGECKOargs(varargin, { ...
+ 'geckoLight', false; ...
+ 'modelAdapter', []});
+ geckoLight = p.geckoLight;
+ modelAdapter = p.modelAdapter;
+
+ % the caller may then use either form:
+ makeEcModel(model, true, adapter) % positional
+ makeEcModel(model, 'geckoLight', true) % named
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
+This function is called by:
+
+
createGECKOproject createGECKOproject Create a basic project structure for GECKO.
upgradeGECKOproject upgradeGECKOproject Bring a GECKO 3 project to the GECKO 4 layout.
+
+
+
+
+
SOURCE CODE
+
0001 function args = parseGECKOargs(rawArgs, spec)
+0002 % parseGECKOargs Resolve optional arguments passed positionally or by name.
+0003 %
+0004 % Helper that lets a GECKO function accept its optional arguments either in
+0005 % the traditional positional order or as name-value pairs, without losing
+0006 % backward compatibility. The required leading arguments stay explicit in the
+0007 % function signature; the optional tail is collected into varargin and passed
+0008 % here together with a specification of the optional parameters.
+0009 %
+0010 % Parameters
+0011 % ----------
+0012 % rawArgs : cell
+0013 % the function's varargin, i.e. whatever was supplied after the required
+0014 % 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 and should error on an invalid value. Use [] for
+0023 % no validation.
+0024 %
+0025 % Returns
+0026 % -------
+0027 % args : struct
+0028 % a struct with one field per parameter name, holding the resolved value.
+0029 %
+0030 % Notes
+0031 % -----
+0032 % The calling convention is detected from rawArgs: it is treated as name-
+0033 % value pairs only when it has an even number of elements AND every element
+0034 % in a name position is a string matching one of the declared parameter
+0035 % names. Otherwise it is treated as positional and assigned in the order of
+0036 % the spec. A call therefore uses either the positional or the name-value
+0037 % form for the optional tail, not a mix of the two. When an optional tail
+0038 % value could itself be a string equal to a parameter name, use the explicit
+0039 % name-value form to remove the ambiguity.
+0040 %
+0041 % Examples
+0042 % --------
+0043 % % inside makeEcModel(model, varargin):
+0044 % p = parseGECKOargs(varargin, { ...
+0045 % 'geckoLight', false; ...
+0046 % 'modelAdapter', []});
+0047 % geckoLight = p.geckoLight;
+0048 % modelAdapter = p.modelAdapter;
+0049 %
+0050 % % the caller may then use either form:
+0051 % makeEcModel(model, true, adapter) % positional
+0052 % makeEcModel(model, 'geckoLight', true) % named
+0053
+0054 if nargin < 2 || isempty(spec)
+0055 error('parseGECKOargs:noSpec', 'A parameter specification is required.');
+0056 end
+0057
+0058 names = spec(:, 1);
+0059 defaults = spec(:, 2);
+0060 hasValidators = size(spec, 2) >= 3;
+0061
+0062 % Seed every parameter with its default.
+0063 args = struct();
+0064 for i = 1:numel(names)
+0065 args.(names{i}) = defaults{i};
+0066 end
+0067
+0068 if ~isempty(rawArgs)
+0069 isNameValue = mod(numel(rawArgs), 2) == 0 && ...
+0070 all(cellfun(@(x) (ischar(x) || isstring(x)) && isscalar(string(x)) && ...
+0071 any(strcmpi(x, names)), rawArgs(1:2:end)));
+0072
+0073 if isNameValue
+0074 for k = 1:2:numel(rawArgs)
+0075 args.(names{strcmpi(rawArgs{k}, names)}) = rawArgs{k + 1};
+0076 end
+0077 else
+0078 if numel(rawArgs) > numel(names)
+0079 error('parseGECKOargs:tooManyArgs', ...
+0080 'Too many positional arguments: expected at most %d, got %d.', ...
+0081 numel(names), numel(rawArgs));
+0082 end
+0083 for k = 1:numel(rawArgs)
+0084 args.(names{k}) = rawArgs{k};
+0085 end
+0086 end
+0087 end
+0088
+0089 % Apply optional validators.
+0090 if hasValidators
+0091 for i = 1:numel(names)
+0092 validator = spec{i, 3};
+0093 if ~isempty(validator)
+0094 validator(args.(names{i}));
+0095 end
+0096 end
+0097 end
+0098 end
upgradeGECKOproject Bring a GECKO 3 project to the GECKO 4 layout.
+
+
SYNOPSIS
+
function report = upgradeGECKOproject(projectPath, varargin)
+
+
DESCRIPTION
+
upgradeGECKOproject Bring a GECKO 3 project to the GECKO 4 layout.
+
+ Lists, and with 'dryRun', false carries out, the steps that bring a
+ project made with GECKO 3 (or an earlier GECKO 4) to the current layout:
+
+ 1. adapter: an adapter class without model_adapter.toml gets one
+ (convertAdapterToToml). Methods the class overrides are listed: they
+ need carrying over by hand. The class itself stays.
+ 2. custom kcats: data/customKcats.tsv becomes data/kcats/custom.tsv
+ (convertCustomKcats).
+ 3. stored predictions: data/DLKcat.tsv and stored OpenKineticsPredictor
+ results become kcat source files (kcatsFromDLKcat, kcatsFromOKP with
+ 'useStored'), without running anything.
+ Steps 2 and 3 need the full ecModel the custom kcats and predictions are
+ for ('ecModel'); without it they are listed and skipped.
+ 4. saved ecModels: models/*.yml ecModels made with GECKO 3 get their
+ protein usage and pool reactions turned forward (upgradeEcModel).
+ 5. project_format = 4 is written to model_adapter.toml.
+ 6. scripts: lines of the project's .m files that call renamed or replaced
+ functions are listed, not changed.
+
+ A file that is replaced or converted is first moved to
+ legacy/<yyyy-mm-dd>/ in the project, keeping its place in the folder
+ tree. Steps that are already done are left out, so the function can run
+ again. geckopy has the same as `geckopy upgrade` (upgrade_project).
+
+ Parameters
+ ----------
+ projectPath : char
+ the project folder.
+
+ Name-Value Arguments
+ --------------------
+ dryRun : logical
+ only list the steps (default true).
+ ecModel : struct
+ the ecModel the stored predictions were made for (step 3).
+
+ Returns
+ -------
+ report : table
+ one row per step: step, status ('to do', 'done', 'skipped', 'failed',
+ 'by hand') and note; script lines are listed one per row.
+
+ Examples
+ --------
+ upgradeGECKOproject(projectPath) % see what it would do
+ upgradeGECKOproject(projectPath, 'dryRun', false, 'ecModel', ecModel)
+
+ See also
+ --------
+ convertAdapterToToml, convertCustomKcats, upgradeEcModel, createGECKOproject
+
+
+
CROSS-REFERENCE INFORMATION
+This function calls:
+
+
findGECKOroot findGECKOroot Find the root of the GECKO directory.
parseGECKOargs parseGECKOargs Resolve optional arguments passed positionally or by name.
0001 function report = upgradeGECKOproject(projectPath, varargin)
+0002 % upgradeGECKOproject Bring a GECKO 3 project to the GECKO 4 layout.
+0003 %
+0004 % Lists, and with 'dryRun', false carries out, the steps that bring a
+0005 % project made with GECKO 3 (or an earlier GECKO 4) to the current layout:
+0006 %
+0007 % 1. adapter: an adapter class without model_adapter.toml gets one
+0008 % (convertAdapterToToml). Methods the class overrides are listed: they
+0009 % need carrying over by hand. The class itself stays.
+0010 % 2. custom kcats: data/customKcats.tsv becomes data/kcats/custom.tsv
+0011 % (convertCustomKcats).
+0012 % 3. stored predictions: data/DLKcat.tsv and stored OpenKineticsPredictor
+0013 % results become kcat source files (kcatsFromDLKcat, kcatsFromOKP with
+0014 % 'useStored'), without running anything.
+0015 % Steps 2 and 3 need the full ecModel the custom kcats and predictions are
+0016 % for ('ecModel'); without it they are listed and skipped.
+0017 % 4. saved ecModels: models/*.yml ecModels made with GECKO 3 get their
+0018 % protein usage and pool reactions turned forward (upgradeEcModel).
+0019 % 5. project_format = 4 is written to model_adapter.toml.
+0020 % 6. scripts: lines of the project's .m files that call renamed or replaced
+0021 % functions are listed, not changed.
+0022 %
+0023 % A file that is replaced or converted is first moved to
+0024 % legacy/<yyyy-mm-dd>/ in the project, keeping its place in the folder
+0025 % tree. Steps that are already done are left out, so the function can run
+0026 % again. geckopy has the same as `geckopy upgrade` (upgrade_project).
+0027 %
+0028 % Parameters
+0029 % ----------
+0030 % projectPath : char
+0031 % the project folder.
+0032 %
+0033 % Name-Value Arguments
+0034 % --------------------
+0035 % dryRun : logical
+0036 % only list the steps (default true).
+0037 % ecModel : struct
+0038 % the ecModel the stored predictions were made for (step 3).
+0039 %
+0040 % Returns
+0041 % -------
+0042 % report : table
+0043 % one row per step: step, status ('to do', 'done', 'skipped', 'failed',
+0044 % 'by hand') and note; script lines are listed one per row.
+0045 %
+0046 % Examples
+0047 % --------
+0048 % upgradeGECKOproject(projectPath) % see what it would do
+0049 % upgradeGECKOproject(projectPath, 'dryRun', false, 'ecModel', ecModel)
+0050 %
+0051 % See also
+0052 % --------
+0053 % convertAdapterToToml, convertCustomKcats, upgradeEcModel, createGECKOproject
+0054
+0055 checkOptions(varargin, {'dryRun','ecModel'}, 'upgradeGECKOproject');
+0056 p = parseGECKOargs(varargin, {'dryRun', true; 'ecModel', []});
+0057 projectPath = char(projectPath);
+0058 if ~isfolder(projectPath)
+0059 error('upgradeGECKOproject:path', 'No folder %s.', projectPath)
+0060 end
+0061 apply = ~p.dryRun;
+0062 legacy = fullfile(projectPath, 'legacy', char(datetime('now', 'Format', 'yyyy-MM-dd')));
+0063 toml = fullfile(projectPath, 'model_adapter.toml');
+0064 rows = cell(0, 3);
+0065 function add(step, status, note)
+0066 rows(end+1, :) = {step, status, note};
+0067 end
+0068 function keepOriginal(file)
+0069 % Move a file to legacy/<date>/, at its place in the folder tree.
+0070 rel = file(numel(projectPath)+2:end);
+0071 target = fullfile(legacy, rel);
+0072 if ~isfolder(fileparts(target))
+0073 mkdir(fileparts(target));
+0074 end
+0075 movefile(file, target);
+0076 end
+0077 status = 'to do';
+0078 if apply
+0079 status = 'done';
+0080 end
+0081
+0082 % 1. Adapter class -> model_adapter.toml
+0083 if ~isfile(toml)
+0084 classFile = adapterClass(projectPath);
+0085 if isempty(classFile)
+0086 add('adapter', 'by hand', 'no model_adapter.toml and no adapter class; see createGECKOproject');
+0087 else
+0088 note = sprintf('%s -> model_adapter.toml', relative(classFile, projectPath));
+0089 if apply
+0090 r = convertAdapterToToml(classFile, toml);
+0091 if ~isempty(r.methods)
+0092 add('adapter methods', 'by hand', ['carry over: ' strjoin(r.methods, ', ')]);
+0093 end
+0094 end
+0095 add('adapter', status, note);
+0096 end
+0097 end
+0098
+0099 % 2. customKcats.tsv -> data/kcats/custom.tsv
+0100 custom = fullfile(projectPath, 'data', 'customKcats.tsv');
+0101 customOut = fullfile(projectPath, 'data', 'kcats', 'custom.tsv');
+0102 if isfile(custom) && ~isfile(customOut)
+0103 note = 'data/customKcats.tsv -> data/kcats/custom.tsv';
+0104 if isempty(p.ecModel)
+0105 add('custom kcats', 'skipped', [note '; give the ecModel they are for (''ecModel'')']);
+0106 else
+0107 if apply
+0108 [~, r] = convertCustomKcats(p.ecModel, 'customKcats', custom, 'outFile', customOut, ...
+0109 'modelAdapter', projectAdapter(projectPath));
+0110 keepOriginal(custom);
+0111 note = sprintf('%s (%d rows)', note, r.rows);
+0112 end
+0113 add('custom kcats', status, note);
+0114 end
+0115 end
+0116
+0117 % 3. Stored predictions -> kcat source files
+0118 stored = {};
+0119 if isfile(fullfile(projectPath, 'data', 'DLKcat.tsv')) && ~isfile(fullfile(projectPath, 'data', 'kcats', 'dlkcat.tsv'))
+0120 stored{end+1} = 'dlkcat';
+0121 end
+0122 okpFiles = dir(fullfile(projectPath, 'data', 'OKP_output*.csv'));
+0123 for k = 1:numel(okpFiles)
+0124 method = okpMethod(projectPath, okpFiles(k).name);
+0125 if ~isempty(method)
+0126 name = ['okp_' lower(regexprep(regexprep(method, '[^A-Za-z0-9]+', '_'), '^_+|_+$', ''))];
+0127 if ~isfile(fullfile(projectPath, 'data', 'kcats', [name '.tsv']))
+0128 stored{end+1} = method; %#ok<AGROW>
+0129 end
+0130 end
+0131 end
+0132 stored = unique(stored, 'stable');
+0133 if ~isempty(stored)
+0134 note = ['stored predictions -> data/kcats: ' strjoin(stored, ', ')];
+0135 if isempty(p.ecModel)
+0136 add('predictions', 'skipped', [note '; give the ecModel they were made for (''ecModel'')']);
+0137 else
+0138 if apply
+0139 adapter = projectAdapter(projectPath);
+0140 for k = 1:numel(stored)
+0141 try
+0142 if strcmp(stored{k}, 'dlkcat')
+0143 kcatsFromDLKcat(p.ecModel, 'modelAdapter', adapter);
+0144 else
+0145 kcatsFromOKP(p.ecModel, 'method', stored{k}, 'useStored', true, 'modelAdapter', adapter);
+0146 end
+0147 add('predictions', status, ['stored predictions -> data/kcats: ' stored{k}]);
+0148 catch err
+0149 add('predictions', 'failed', sprintf('%s: %s', stored{k}, err.message));
+0150 end
+0151 end
+0152 else
+0153 add('predictions', status, note);
+0154 end
+0155 end
+0156 end
+0157
+0158 % 4. Saved ecModels: protein reactions turned forward
+0159 models = dir(fullfile(projectPath, 'models', '*.yml'));
+0160 for k = 1:numel(models)
+0161 file = fullfile(models(k).folder, models(k).name);
+0162 if ~contains(fileread(file), 'prot_pool')
+0163 continue
+0164 end
+0165 try
+0166 model = readYAMLmodel(file);
+0167 catch err
+0168 add('saved ecModel', 'failed', sprintf('%s: %s', relative(file, projectPath), err.message));
+0169 continue
+0170 end
+0171 if isfield(model.annotation, 'gecko_format') && str2double(model.annotation.gecko_format) > 4
+0172 add('saved ecModel', 'failed', sprintf('%s: ecModel format %s is newer than this GECKO', ...
+0173 relative(file, projectPath), model.annotation.gecko_format));
+0174 continue
+0175 end
+0176 [model, changed] = upgradeEcModel(model);
+0177 if isempty(changed)
+0178 continue
+0179 end
+0180 if apply
+0181 model.annotation.generator = ['GECKO 'geckoVersion()];
+0182 keepOriginal(file);
+0183 writeYAMLmodel(model, file);
+0184 end
+0185 add('saved ecModel', status, sprintf('%s: %d protein reactions turned forward', ...
+0186 relative(file, projectPath), numel(changed)));
+0187 end
+0188
+0189 % 5. project_format in model_adapter.toml
+0190 adapterRow = strcmp(rows(:, 1), 'adapter') & ~strcmp(rows(:, 2), 'by hand');
+0191 if isfile(toml) || any(adapterRow)
+0192 if ~isfile(toml) || isempty(regexp(fileread(toml), '(?m)^\s*project_format\s*=', 'once'))
+0193 if apply
+0194 text = fileread(toml);
+0195 fid = fopen(toml, 'w', 'n', 'UTF-8');
+0196 fprintf(fid, 'project_format = 4\n%s', text);
+0197 fclose(fid);
+0198 end
+0199 add('project format', status, 'project_format = 4 in model_adapter.toml');
+0200 end
+0201 end
+0202
+0203 % 6. Scripts that call renamed or replaced functions
+0204 renamed = { ...
+0205 'enzymeUsage', 'getEnzymeUsage'; 'pfbaEnzymes', 'getPfbaEnzymes'; ...
+0206 'selectKcatValue', 'assignKcatValues'; 'startGECKOproject', 'createGECKOproject'; ...
+0207 'updateGECKOdoc', 'buildGECKOdoc'; 'copyECtoGEM', 'applyECcodes'; ...
+0208 'addCarbonNum', 'fillCarbonNum'; 'updateprior', 'updatePrior'; ...
+0209 'mergeDLKcatAndFuzzyKcats', 'mergeKcats'; ...
+0210 'fuzzyKcatMatching', 'kcatsFromBrenda (or kcatsFromECTree), then assignKcats'; ...
+0211 'readDLKcatOutput', 'kcatsFromDLKcat, then assignKcats'; ...
+0212 'readOpenKineticsPredictorOutput', 'kcatsFromOKP, then assignKcats'; ...
+0213 'writeOpenKineticsPredictorInput', 'kcatsFromOKP'; ...
+0214 'applyCustomKcats', 'data/kcats/custom.tsv and assignKcats'};
+0215 scripts = dir(fullfile(projectPath, '**', '*.m'));
+0216 for k = 1:numel(scripts)
+0217 file = fullfile(scripts(k).folder, scripts(k).name);
+0218 if startsWith(file, fullfile(projectPath, 'legacy'))
+0219 continue
+0220 end
+0221 lines = strsplit(fileread(file), {'\r\n', '\n'});
+0222 for r = 1:size(renamed, 1)
+0223 hit = find(~cellfun(@isempty, regexp(lines, ['(?<![\w.])' renamed{r, 1} '\s*\('], 'once')));
+0224 for h = hit(:)'
+0225 add('script', 'by hand', sprintf('%s:%d: %s -> %s', relative(file, projectPath), h, ...
+0226 renamed{r, 1}, renamed{r, 2}));
+0227 end
+0228 end
+0229 end
+0230
+0231 report = cell2table(rows, 'VariableNames', {'step', 'status', 'note'});
+0232 if isempty(rows)
+0233 fprintf('%s has the current GECKO 4 layout.\n', projectPath);
+0234 else
+0235 disp(report)
+0236 if ~apply && any(strcmp(rows(:, 2), 'to do'))
+0237 fprintf('Run upgradeGECKOproject(projectPath, ''dryRun'', false) to carry out the steps; originals go to %s.\n', legacy);
+0238 end
+0239 end
+0240 end
+0241
+0242 function adapter = projectAdapter(projectPath)
+0243 % The project's adapter: from model_adapter.toml, or its adapter class.
+0244 if isfile(fullfile(projectPath, 'model_adapter.toml'))
+0245 adapter = TomlAdapter(projectPath);
+0246 else
+0247 adapter = ModelAdapterManager.getAdapter(adapterClass(projectPath));
+0248 adapter.params.path = projectPath;
+0249 end
+0250 end
+0251
+0252 function file = adapterClass(projectPath)
+0253 % The adapter class in the project folder, or '' when there is none.
+0254 file = '';
+0255 candidates = dir(fullfile(projectPath, '*.m'));
+0256 for k = 1:numel(candidates)
+0257 f = fullfile(candidates(k).folder, candidates(k).name);
+0258 if ~isempty(regexp(fileread(f), '(?m)^\s*classdef\s+\w+\s*<\s*ModelAdapter', 'once'))
+0259 file = f;
+0260 return
+0261 end
+0262 end
+0263 end
+0264
+0265 function method = okpMethod(projectPath, outName)
+0266 % The OKP method of a stored result: from its own job file
+0267 % (OKP_job_<tag>.txt), or from OKP_job.txt for the shared OKP_output.csv.
+0268 method = '';
+0269 if strcmp(outName, 'OKP_output.csv')
+0270 jobFile = fullfile(projectPath, 'data', 'OKP_job.txt');
+0271 else
+0272 jobFile = fullfile(projectPath, 'data', ['OKP_job_' regexprep(outName, '^OKP_output_|\.csv$', '') '.txt']);
+0273 end
+0274 if ~isfile(jobFile)
+0275 return
+0276 end
+0277 tok = regexp(fileread(jobFile), '(?m)^method:\s*(\S+)', 'tokens', 'once');
+0278 if ~isempty(tok)
+0279 method = tok{1};
+0280 end
+0281 end
+0282
+0283 function r = relative(file, projectPath)
+0284 r = file(numel(projectPath)+2:end);
+0285 end
+0286
+0287 function checkOptions(args, names, functionName)
+0288 % Options are name-value pairs with known names, so a misspelled option is
+0289 % an error instead of being taken as a positional argument.
+0290 id = [functionName ':arguments'];
+0291 given = args(1:2:end);
+0292 if mod(numel(args), 2) ~= 0 || ~all(cellfun(@(x) ischar(x) || (isstring(x) && isscalar(x)), given))
+0293 error(id, 'Options of %s are name-value pairs. Options: %s.', functionName, strjoin(names, ', '))
+0294 end
+0295 unknown = setdiff(cellfun(@char, given, 'UniformOutput', false), names);
+0296 if ~isempty(unknown)
+0297 error(id, 'Unknown option of %s: %s. Options: %s.', functionName, strjoin(unknown, ', '), ...
+0298 strjoin(names, ', '))
+0299 end
+0300 end
+0301
+0302 function v = geckoVersion()
+0303 file = fullfile(findGECKOroot, 'version.txt');
+0304 if isfile(file)
+0305 v = strtrim(fileread(file));
+0306 else
+0307 v = 'unknown';
+0308 end
+0309 end