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 = strjoin(model.grRules);
0128 geneList = regexp(geneList,' |)|(|and|or','split');
0129 geneList = geneList(~cellfun(@isempty,geneList));
0130 geneList = setdiff(unique(geneList),model.genes);
0131 if ~isempty(geneList)
0132 problemGrRules = model.rxns(contains(model.grRules,geneList));
0133 problemGrRules = strjoin(problemGrRules(:),'; ');
0134 EM=['The reaction(s) "' problemGrRules '" contain the following genes in its "grRules" field, but these are not in the "genes" field:'];
0135 dispEM(EM,throwErrors,geneList);
0136 end
0137 end
0138 end
0139 if isfield(model,'rxnComps')
0140 if ~isnumeric(model.rxnComps)
0141 EM='The "rxnComps" field must be of type "double"';
0142 dispEM(EM,throwErrors);
0143 end
0144 end
0145 if isfield(model,'inchis')
0146 if ~iscellstr(model.inchis)
0147 EM='The "inchis" field must be a cell array of strings';
0148 dispEM(EM,throwErrors);
0149 end
0150 end
0151 if isfield(model,'metSmiles')
0152 if ~iscellstr(model.metSmiles)
0153 EM='The "metSmiles" field must be a cell array of strings';
0154 dispEM(EM,throwErrors);
0155 end
0156 end
0157 if isfield(model,'metFormulas')
0158 if ~iscellstr(model.metFormulas)
0159 EM='The "metFormulas" field must be a cell array of strings';
0160 dispEM(EM,throwErrors);
0161 end
0162 end
0163 if isfield(model,'metCharges')
0164 if ~isnumeric(model.metCharges)
0165 EM='The "metCharges" field must be a double';
0166 dispEM(EM,throwErrors);
0167 end
0168 end
0169 if isfield(model,'metDeltaG')
0170 if ~isnumeric(model.metDeltaG)
0171 EM='The "metDeltaG" field must be a double';
0172 dispEM(EM,throwErrors);
0173 end
0174 end
0175 if isfield(model,'subSystems')
0176 for i=1:numel(model.subSystems)
0177 if ~iscell(model.subSystems{i,1})
0178 EM='The "subSystems" field must be a cell array';
0179 dispEM(EM,throwErrors);
0180 end
0181 end
0182 end
0183 if isfield(model,'eccodes')
0184 if ~iscellstr(model.eccodes)
0185 EM='The "eccodes" field must be a cell array of strings';
0186 dispEM(EM,throwErrors);
0187 end
0188 end
0189 if isfield(model,'unconstrained')
0190 if ~isnumeric(model.unconstrained)
0191 EM='The "unconstrained" field must be of type "double"';
0192 dispEM(EM,throwErrors);
0193 end
0194 end
0195 if isfield(model,'rxnNotes')
0196 if ~iscellstr(model.rxnNotes)
0197 EM='The "rxnNotes" field must be a cell array of strings';
0198 dispEM(EM,throwErrors);
0199 end
0200 end
0201 if isfield(model,'rxnReferences')
0202 if ~iscellstr(model.rxnReferences)
0203 EM='The "rxnReferences" field must be a cell array of strings';
0204 dispEM(EM,throwErrors);
0205 end
0206 end
0207 if isfield(model,'rxnConfidenceScores')
0208 if ~isnumeric(model.rxnConfidenceScores)
0209 EM='The "rxnConfidenceScores" field must be a double';
0210 dispEM(EM,throwErrors);
0211 end
0212 end
0213 if isfield(model,'rxnDeltaG')
0214 if ~isnumeric(model.rxnDeltaG)
0215 EM='The "rxnDeltaG" field must be a double';
0216 dispEM(EM,throwErrors);
0217 end
0218 end
0219
0220
0221 if isempty(model.id)
0222 EM='The "id" field cannot be empty';
0223 dispEM(EM,throwErrors);
0224 end
0225 if any(cellfun(@isempty,model.rxns))
0226 EM='The model contains empty reaction IDs';
0227 dispEM(EM,throwErrors);
0228 end
0229 if any(cellfun(@isempty,model.mets))
0230 EM='The model contains empty metabolite IDs';
0231 dispEM(EM,throwErrors);
0232 end
0233 if any(cellfun(@isempty,model.comps))
0234 EM='The model contains empty compartment IDs';
0235 dispEM(EM,throwErrors);
0236 end
0237 EM='The following metabolites have empty names:';
0238 dispEM(EM,throwErrors,model.mets(cellfun(@isempty,model.metNames)),trimWarnings);
0239
0240 if isfield(model,'genes')
0241 if any(cellfun(@isempty,model.genes))
0242 EM='The model contains empty gene IDs';
0243 dispEM(EM,throwErrors);
0244 end
0245 end
0246
0247
0248 fields = {'rxns';'mets';'comps';'genes'};
0249 fieldNames = {'reaction';'metabolite';'compartment';'gene'};
0250 fieldPrefix = {'R_';'M_';'C_';'G_'};
0251 for i=1:numel(fields)
0252 try
0253 numIDs = ~startsWith(model.(fields{i}),regexpPattern('^[a-zA-Z_]'));
0254 catch
0255 numIDs = [];
0256 end
0257 if any(numIDs)
0258 EM = ['The following ' fieldNames{i} ' identifiers do not start '...
0259 'with a letter or _ (conflicting with SBML specifications). '...
0260 'This does not impact RAVEN functionality, but be aware that '...
0261 'exportModel will automatically add ' fieldPrefix{i} ...
0262 ' prefixes to all ' fieldNames{i} ' identifiers:'];
0263 dispEM(EM,false,{model.(fields{i}){numIDs}},trimWarnings);
0264 end
0265 end
0266
0267
0268 EM='The following reaction IDs are duplicates:';
0269 dispEM(EM,throwErrors,model.rxns(duplicates(model.rxns)),trimWarnings);
0270 EM='The following metabolite IDs are duplicates:';
0271 dispEM(EM,throwErrors,model.mets(duplicates(model.mets)),trimWarnings);
0272 EM='The following compartment IDs are duplicates:';
0273 dispEM(EM,throwErrors,model.comps(duplicates(model.comps)),trimWarnings);
0274 if isfield(model,'genes')
0275 EM='The following genes are duplicates:';
0276 dispEM(EM,throwErrors,model.genes(duplicates(model.genes)),trimWarnings);
0277 end
0278 metInComp=strcat(model.metNames,'[',model.comps(model.metComps),']');
0279 EM='The following metabolites already exist in the same compartment:';
0280 dispEM(EM,throwErrors,metInComp(duplicates(metInComp)),trimWarnings);
0281
0282
0283 EM='The following reactions are empty (no involved metabolites):';
0284 dispEM(EM,false,model.rxns(~any(model.S,1)),trimWarnings);
0285 EM='The following metabolites are never used in a reaction:';
0286 dispEM(EM,false,model.mets(~any(model.S,2)),trimWarnings);
0287 if isfield(model,'genes')
0288 EM='The following genes are not associated to a reaction:';
0289 dispEM(EM,false,model.genes(~any(model.rxnGeneMat,1)),trimWarnings);
0290 end
0291 I=true(numel(model.comps),1);
0292 I(model.metComps)=false;
0293 EM='The following compartments contain no metabolites:';
0294 dispEM(EM,false,model.comps(I),trimWarnings);
0295
0296
0297 EM='The following reactions have contradicting bounds (lower bound is higher than upper bound):';
0298 dispEM(EM,throwErrors,model.rxns(model.lb>model.ub),trimWarnings);
0299 EM='The following reactions have lower and upper bounds that indicate reversibility, but are indicated as irreversible in model.rev:';
0300 dispEM(EM,false,model.rxns(model.lb < 0 & model.ub > 0 & model.rev==0),trimWarnings);
0301
0302
0303 if numel(find(model.c))>1
0304 EM='Multiple objective functions found. This might be intended, but results in FBCv2 non-compliant SBML file when exported';
0305 dispEM(EM,false,model.rxns(find(model.c)),trimWarnings);
0306 elseif ~any(model.c)
0307 EM='No objective function found. This might be intended, but results in FBCv2 non-compliant SBML file when exported';
0308 dispEM(EM,false);
0309 end
0310
0311
0312 if isfield(model,'compOutside')
0313 EM='The following compartments are in "compOutside" but not in "comps":';
0314 dispEM(EM,throwErrors,setdiff(model.compOutside,[{''};model.comps]),trimWarnings);
0315 end
0316
0317
0318 I=false(numel(model.metNames),1);
0319 for i=1:numel(model.metNames)
0320 index=strfind(model.metNames{i},' ');
0321 if any(index)
0322 if any(str2double(model.metNames{i}(1:index(1)-1)))
0323 I(i)=true;
0324 end
0325 end
0326 end
0327 EM='The following metabolite names begin with a number directly followed by space, which could potentially cause problems:';
0328 dispEM(EM,false,model.metNames(I),trimWarnings);
0329
0330
0331 if isfield(model,'metFormulas')
0332 [~, ~, exitFlag]=parseFormulas(model.metFormulas,true,false);
0333 EM='The composition for the following metabolites could not be parsed:';
0334 dispEM(EM,false,model.mets(exitFlag==-1),trimWarnings);
0335 end
0336
0337
0338
0339 if isfield(model,'metMiriams')
0340 miriams=containers.Map();
0341 for i=1:numel(model.mets)
0342 if ~isempty(model.metMiriams{i})
0343
0344 for j=1:numel(model.metMiriams{i}.name)
0345
0346 current=strcat(model.metMiriams{i}.name{j},'/',model.metMiriams{i}.value{j});
0347 if isKey(miriams,current)
0348 existing=miriams(current);
0349 else
0350 existing=[];
0351 end
0352 miriams(current)=[existing;i];
0353 end
0354 end
0355 end
0356
0357
0358 allMiriams=keys(miriams);
0359
0360 hasMultiple=false(numel(allMiriams),1);
0361 for i=1:numel(allMiriams)
0362 if numel(miriams(allMiriams{i}))>1
0363
0364 if numel(unique(model.metNames(miriams(allMiriams{i}))))>1
0365 if ~regexp(allMiriams{i},'^sbo\/SBO:')
0366 hasMultiple(i)=true;
0367 end
0368 end
0369 end
0370 end
0371
0372
0373 EM='The following MIRIAM strings are associated to more than one unique metabolite name:';
0374 dispEM(EM,false,allMiriams(hasMultiple),trimWarnings);
0375 end
0376
0377
0378
0379 if isfield(model,'inchis')
0380 inchis=containers.Map();
0381 for i=1:numel(model.mets)
0382 if ~isempty(model.inchis{i})
0383
0384 if isKey(inchis,model.inchis{i})
0385 existing=inchis(model.inchis{i});
0386 else
0387 existing=[];
0388 end
0389 inchis(model.inchis{i})=[existing;i];
0390 end
0391 end
0392
0393
0394 allInchis=keys(inchis);
0395
0396 hasMultiple=false(numel(allInchis),1);
0397 for i=1:numel(allInchis)
0398 if numel(inchis(allInchis{i}))>1
0399
0400 if numel(unique(model.metNames(inchis(allInchis{i}))))>1
0401 hasMultiple(i)=true;
0402 end
0403 end
0404 end
0405
0406
0407 EM='The following InChI strings are associated to more than one unique metabolite name:';
0408 dispEM(EM,false,allInchis(hasMultiple),trimWarnings);
0409 end
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
0442
0443 end
0444
0445 function I=duplicates(strings)
0446 I=false(numel(strings),1);
0447 [J, K]=unique(strings);
0448 if numel(J)~=numel(strings)
0449 L=1:numel(strings);
0450 L(K)=[];
0451 I(L)=true;
0452 end
0453 end