0001 function checkModelStruct(model,throwErrors,trimWarnings)
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017 if nargin<2
0018 throwErrors=true;
0019 end
0020 if nargin<3
0021 trimWarnings=true;
0022 end
0023
0024
0025 fields={'id';'name';'rxns';'mets';'S';'lb';'ub';'rev';'c';'b';'comps';'metComps'};
0026 for i=1:numel(fields)
0027 if ~isfield(model,fields{i})
0028 EM=['The model is missing the "' fields{i} '" field'];
0029 dispEM(EM,throwErrors);
0030 end
0031 end
0032
0033
0034 if ~ischar(model.id)
0035 EM='The "id" field must be a string';
0036 dispEM(EM,throwErrors);
0037 end
0038 if ~ischar(model.name)
0039 EM='The "name" field must be a string';
0040 dispEM(EM,throwErrors);
0041 end
0042 if ~iscellstr(model.rxns)
0043 EM='The "rxns" field must be a cell array of strings';
0044 dispEM(EM,throwErrors);
0045 end
0046 if ~iscellstr(model.mets)
0047 EM='The "mets" field must be a cell array of strings';
0048 dispEM(EM,throwErrors);
0049 end
0050 if ~isnumeric(model.S)
0051 EM='The "S" field must be of type "double"';
0052 dispEM(EM,throwErrors);
0053 end
0054 if ~isnumeric(model.lb)
0055 EM='The "lb" field must be of type "double"';
0056 dispEM(EM,throwErrors);
0057 end
0058 if ~isnumeric(model.ub)
0059 EM='The "ub" field must be of type "double"';
0060 dispEM(EM,throwErrors);
0061 end
0062 if ~isnumeric(model.rev)
0063 EM='The "rev" field must be of type "double"';
0064 dispEM(EM,throwErrors);
0065 end
0066 if ~isnumeric(model.c)
0067 EM='The "c" field must be of type "double"';
0068 dispEM(EM,throwErrors);
0069 end
0070 if ~isnumeric(model.b)
0071 EM='The "b" field must be of type "double"';
0072 dispEM(EM,throwErrors);
0073 end
0074 if ~iscellstr(model.comps)
0075 EM='The "comps" field must be a cell array of strings';
0076 dispEM(EM,throwErrors);
0077 end
0078 if ~isnumeric(model.metComps)
0079 EM='The "metComps" field must be of type "double"';
0080 dispEM(EM,throwErrors);
0081 end
0082 if isfield(model,'compNames')
0083 if ~iscellstr(model.compNames)
0084 EM='The "compNames" field must be a cell array of strings';
0085 dispEM(EM,throwErrors);
0086 end
0087 end
0088 if isfield(model,'compOutside')
0089 if ~iscellstr(model.compOutside)
0090 EM='The "compOutside" field must be a cell array of strings';
0091 dispEM(EM,throwErrors);
0092 end
0093 end
0094 if isfield(model,'rxnNames')
0095 if ~iscellstr(model.rxnNames)
0096 EM='The "rxnNames" field must be a cell array of strings';
0097 dispEM(EM,throwErrors);
0098 end
0099 end
0100 if isfield(model,'metNames')
0101 if ~iscellstr(model.metNames)
0102 EM='The "metNames" field must be a cell array of strings';
0103 dispEM(EM,throwErrors);
0104 end
0105 end
0106 if isfield(model,'genes')
0107 if ~iscellstr(model.genes)
0108 EM='The "genes" field must be a cell array of strings';
0109 dispEM(EM,throwErrors);
0110 end
0111 end
0112 if isfield(model,'rxnGeneMat')
0113 if ~isnumeric(model.rxnGeneMat)
0114 EM='The "rxnGeneMat" field must be of type "double"';
0115 dispEM(EM,throwErrors);
0116 end
0117 end
0118 if isfield(model,'grRules')
0119 if ~iscellstr(model.grRules)
0120 EM='The "grRules" field must be a cell array of strings';
0121 dispEM(EM,throwErrors);
0122 end
0123 if ~isfield(model,'genes')
0124 EM='If "grRules" field exists, the model should also contain a "genes" field';
0125 dispEM(EM,throwErrors);
0126 else
0127 geneList = getGenesFromGrRules(model.grRules);
0128 geneList = setdiff(unique(geneList),model.genes);
0129 if ~isempty(geneList)
0130 problemGrRules = model.rxns(contains(model.grRules,geneList));
0131 problemGrRules = strjoin(problemGrRules(:),'; ');
0132 EM=['The reaction(s) "' problemGrRules '" contain the following genes in its "grRules" field, but these are not in the "genes" field:'];
0133 dispEM(EM,throwErrors,geneList);
0134 end
0135 end
0136 end
0137 if isfield(model,'rxnComps')
0138 if ~isnumeric(model.rxnComps)
0139 EM='The "rxnComps" field must be of type "double"';
0140 dispEM(EM,throwErrors);
0141 end
0142 end
0143 if isfield(model,'inchis')
0144 if ~iscellstr(model.inchis)
0145 EM='The "inchis" field must be a cell array of strings';
0146 dispEM(EM,throwErrors);
0147 end
0148 end
0149 if isfield(model,'metSmiles')
0150 if ~iscellstr(model.metSmiles)
0151 EM='The "metSmiles" field must be a cell array of strings';
0152 dispEM(EM,throwErrors);
0153 end
0154 end
0155 if isfield(model,'metFormulas')
0156 if ~iscellstr(model.metFormulas)
0157 EM='The "metFormulas" field must be a cell array of strings';
0158 dispEM(EM,throwErrors);
0159 end
0160 end
0161 if isfield(model,'metCharges')
0162 if ~isnumeric(model.metCharges)
0163 EM='The "metCharges" field must be a double';
0164 dispEM(EM,throwErrors);
0165 end
0166 end
0167 if isfield(model,'metDeltaG')
0168 if ~isnumeric(model.metDeltaG)
0169 EM='The "metDeltaG" field must be a double';
0170 dispEM(EM,throwErrors);
0171 end
0172 end
0173 if isfield(model,'subSystems')
0174 for i=1:numel(model.subSystems)
0175 if ~iscell(model.subSystems{i,1})
0176 EM='The "subSystems" field must be a cell array';
0177 dispEM(EM,throwErrors);
0178 end
0179 end
0180 end
0181 if isfield(model,'eccodes')
0182 if ~iscellstr(model.eccodes)
0183 EM='The "eccodes" field must be a cell array of strings';
0184 dispEM(EM,throwErrors);
0185 end
0186 end
0187 if isfield(model,'unconstrained')
0188 if ~isnumeric(model.unconstrained)
0189 EM='The "unconstrained" field must be of type "double"';
0190 dispEM(EM,throwErrors);
0191 end
0192 end
0193 if isfield(model,'rxnNotes')
0194 if ~iscellstr(model.rxnNotes)
0195 EM='The "rxnNotes" field must be a cell array of strings';
0196 dispEM(EM,throwErrors);
0197 end
0198 end
0199 if isfield(model,'rxnReferences')
0200 if ~iscellstr(model.rxnReferences)
0201 EM='The "rxnReferences" field must be a cell array of strings';
0202 dispEM(EM,throwErrors);
0203 end
0204 end
0205 if isfield(model,'rxnConfidenceScores')
0206 if ~isnumeric(model.rxnConfidenceScores)
0207 EM='The "rxnConfidenceScores" field must be a double';
0208 dispEM(EM,throwErrors);
0209 end
0210 end
0211 if isfield(model,'rxnDeltaG')
0212 if ~isnumeric(model.rxnDeltaG)
0213 EM='The "rxnDeltaG" field must be a double';
0214 dispEM(EM,throwErrors);
0215 end
0216 end
0217
0218
0219 if isempty(model.id)
0220 EM='The "id" field cannot be empty';
0221 dispEM(EM,throwErrors);
0222 end
0223 if any(cellfun(@isempty,model.rxns))
0224 EM='The model contains empty reaction IDs';
0225 dispEM(EM,throwErrors);
0226 end
0227 if any(cellfun(@isempty,model.mets))
0228 EM='The model contains empty metabolite IDs';
0229 dispEM(EM,throwErrors);
0230 end
0231 if any(cellfun(@isempty,model.comps))
0232 EM='The model contains empty compartment IDs';
0233 dispEM(EM,throwErrors);
0234 end
0235 EM='The following metabolites have empty names:';
0236 dispEM(EM,throwErrors,model.mets(cellfun(@isempty,model.metNames)),trimWarnings);
0237
0238 if isfield(model,'genes')
0239 if any(cellfun(@isempty,model.genes))
0240 EM='The model contains empty gene IDs';
0241 dispEM(EM,throwErrors);
0242 end
0243 end
0244
0245
0246 fields = {'rxns';'mets';'comps';'genes'};
0247 fieldNames = {'reaction';'metabolite';'compartment';'gene'};
0248 fieldPrefix = {'R_';'M_';'C_';'G_'};
0249 for i=1:numel(fields)
0250 try
0251 numIDs = ~startsWith(model.(fields{i}),regexpPattern('^[a-zA-Z_]'));
0252 catch
0253 numIDs = [];
0254 end
0255 if any(numIDs)
0256 EM = ['The following ' fieldNames{i} ' identifiers do not start '...
0257 'with a letter or _ (conflicting with SBML specifications). '...
0258 'This does not impact RAVEN functionality, but be aware that '...
0259 'exportModel will automatically add ' fieldPrefix{i} ...
0260 ' prefixes to all ' fieldNames{i} ' identifiers:'];
0261 dispEM(EM,false,{model.(fields{i}){numIDs}},trimWarnings);
0262 end
0263 end
0264
0265
0266 EM='The following reaction IDs are duplicates:';
0267 dispEM(EM,throwErrors,model.rxns(duplicates(model.rxns)),trimWarnings);
0268 EM='The following metabolite IDs are duplicates:';
0269 dispEM(EM,throwErrors,model.mets(duplicates(model.mets)),trimWarnings);
0270 EM='The following compartment IDs are duplicates:';
0271 dispEM(EM,throwErrors,model.comps(duplicates(model.comps)),trimWarnings);
0272 if isfield(model,'genes')
0273 EM='The following genes are duplicates:';
0274 dispEM(EM,throwErrors,model.genes(duplicates(model.genes)),trimWarnings);
0275 end
0276 metInComp=strcat(model.metNames,'[',model.comps(model.metComps),']');
0277 EM='The following metabolites already exist in the same compartment:';
0278 dispEM(EM,throwErrors,metInComp(duplicates(metInComp)),trimWarnings);
0279
0280
0281 EM='The following reactions are empty (no involved metabolites):';
0282 dispEM(EM,false,model.rxns(~any(model.S,1)),trimWarnings);
0283 EM='The following metabolites are never used in a reaction:';
0284 dispEM(EM,false,model.mets(~any(model.S,2)),trimWarnings);
0285 if isfield(model,'genes')
0286 EM='The following genes are not associated to a reaction:';
0287 dispEM(EM,false,model.genes(~any(model.rxnGeneMat,1)),trimWarnings);
0288 end
0289 I=true(numel(model.comps),1);
0290 I(model.metComps)=false;
0291 EM='The following compartments contain no metabolites:';
0292 dispEM(EM,false,model.comps(I),trimWarnings);
0293
0294
0295 EM='The following reactions have contradicting bounds (lower bound is higher than upper bound):';
0296 dispEM(EM,throwErrors,model.rxns(model.lb>model.ub),trimWarnings);
0297 EM='The following reactions have lower and upper bounds that indicate reversibility, but are indicated as irreversible in model.rev:';
0298 dispEM(EM,false,model.rxns(model.lb < 0 & model.ub > 0 & model.rev==0),trimWarnings);
0299
0300
0301 if numel(find(model.c))>1
0302 EM='Multiple objective functions found. This might be intended, but results in FBCv2 non-compliant SBML file when exported';
0303 dispEM(EM,false,model.rxns(find(model.c)),trimWarnings);
0304 elseif ~any(model.c)
0305 EM='No objective function found. This might be intended, but results in FBCv2 non-compliant SBML file when exported';
0306 dispEM(EM,false);
0307 end
0308
0309
0310 if isfield(model,'compOutside')
0311 EM='The following compartments are in "compOutside" but not in "comps":';
0312 dispEM(EM,throwErrors,setdiff(model.compOutside,[{''};model.comps]),trimWarnings);
0313 end
0314
0315
0316 I=false(numel(model.metNames),1);
0317 for i=1:numel(model.metNames)
0318 index=strfind(model.metNames{i},' ');
0319 if any(index)
0320 if any(str2double(model.metNames{i}(1:index(1)-1)))
0321 I(i)=true;
0322 end
0323 end
0324 end
0325 EM='The following metabolite names begin with a number directly followed by space, which could potentially cause problems:';
0326 dispEM(EM,false,model.metNames(I),trimWarnings);
0327
0328
0329 if isfield(model,'metFormulas')
0330 [~, ~, exitFlag]=parseFormulas(model.metFormulas,true,false);
0331 EM='The composition for the following metabolites could not be parsed:';
0332 dispEM(EM,false,model.mets(exitFlag==-1),trimWarnings);
0333 end
0334
0335
0336
0337 if isfield(model,'metMiriams')
0338 miriams=containers.Map();
0339 for i=1:numel(model.mets)
0340 if ~isempty(model.metMiriams{i})
0341
0342 for j=1:numel(model.metMiriams{i}.name)
0343
0344 current=strcat(model.metMiriams{i}.name{j},'/',model.metMiriams{i}.value{j});
0345 if isKey(miriams,current)
0346 existing=miriams(current);
0347 else
0348 existing=[];
0349 end
0350 miriams(current)=[existing;i];
0351 end
0352 end
0353 end
0354
0355
0356 allMiriams=keys(miriams);
0357
0358 hasMultiple=false(numel(allMiriams),1);
0359 for i=1:numel(allMiriams)
0360 if numel(miriams(allMiriams{i}))>1
0361
0362 if numel(unique(model.metNames(miriams(allMiriams{i}))))>1
0363 if ~regexp(allMiriams{i},'^sbo\/SBO:')
0364 hasMultiple(i)=true;
0365 end
0366 end
0367 end
0368 end
0369
0370
0371 EM='The following MIRIAM strings are associated to more than one unique metabolite name:';
0372 dispEM(EM,false,allMiriams(hasMultiple),trimWarnings);
0373 end
0374
0375
0376
0377 if isfield(model,'inchis')
0378 inchis=containers.Map();
0379 for i=1:numel(model.mets)
0380 if ~isempty(model.inchis{i})
0381
0382 if isKey(inchis,model.inchis{i})
0383 existing=inchis(model.inchis{i});
0384 else
0385 existing=[];
0386 end
0387 inchis(model.inchis{i})=[existing;i];
0388 end
0389 end
0390
0391
0392 allInchis=keys(inchis);
0393
0394 hasMultiple=false(numel(allInchis),1);
0395 for i=1:numel(allInchis)
0396 if numel(inchis(allInchis{i}))>1
0397
0398 if numel(unique(model.metNames(inchis(allInchis{i}))))>1
0399 hasMultiple(i)=true;
0400 end
0401 end
0402 end
0403
0404
0405 EM='The following InChI strings are associated to more than one unique metabolite name:';
0406 dispEM(EM,false,allInchis(hasMultiple),trimWarnings);
0407 end
0408
0409
0410
0411
0412
0413
0414
0415
0416
0417
0418
0419
0420
0421
0422
0423
0424
0425
0426
0427
0428
0429
0430
0431
0432
0433
0434
0435
0436
0437
0438
0439
0440
0441 end
0442
0443 function I=duplicates(strings)
0444 I=false(numel(strings),1);
0445 [J, K]=unique(strings);
0446 if numel(J)~=numel(strings)
0447 L=1:numel(strings);
0448 L(K)=[];
0449 I(L)=true;
0450 end
0451 end