Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/change_model/upgradeEcModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
% 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
Expand All @@ -26,7 +28,7 @@
%
% Examples
% --------
% ecModel = upgradeEcModel(loadEcModel('ecYeastGEM_GECKO3.yml'));
% ecModel = upgradeEcModel(readYAMLmodel('ecYeastGEM_GECKO3.yml'));
%
% See also
% --------
Expand All @@ -37,6 +39,10 @@
if isempty(pool)
return
end
if ~isfield(model, 'annotation') || ~isfield(model.annotation, 'gecko_format') ...
|| str2double(model.annotation.gecko_format) < 4
model.annotation.gecko_format = '4';
end
usage = find(startsWith(model.rxns, 'usage_prot_'));
reverse = usage(full(model.S(pool, usage)) > 0);
exchange = find(strcmp(model.rxns, 'prot_pool_exchange'));
Expand Down
25 changes: 23 additions & 2 deletions src/io/loadEcModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
% 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
Expand All @@ -20,7 +25,7 @@
% Returns
% -------
% model : struct
% an ecModel in GECKO 3 format (with ecModel.ec structure).
% an ecModel (with ecModel.ec structure).
%
% Examples
% --------
Expand All @@ -47,7 +52,6 @@
params = modelAdapter.getParameters();
if isempty(filename)
filename = 'ecModel.yml';
else
end

if ~endsWith(filename,{'yml','yaml'})
Expand All @@ -63,4 +67,21 @@
elseif endsWith(filename,{'xml','sbml'})
model = importModel(filename);
end

if isfield(model, 'annotation') && isfield(model.annotation, 'gecko_format') ...
&& str2double(model.annotation.gecko_format) > 4
writer = 'an unknown release';
if isfield(model.annotation, 'generator')
writer = model.annotation.generator;
end
error('loadEcModel:format', ['%s has ecModel format %s (written by %s); ' ...
'this GECKO reads format 4 and older. Update GECKO to load it.'], ...
filename, model.annotation.gecko_format, writer)
end
[model, changed] = upgradeEcModel(model);
if ~isempty(changed)
warning('loadEcModel:upgraded', ['%s has %d protein usage and pool reactions in the GECKO 3 ' ...
'direction; they are turned forward in the loaded model. upgradeGECKOproject upgrades the ' ...
'files of the project.'], filename, numel(changed))
end
end
22 changes: 21 additions & 1 deletion src/io/saveEcModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ function saveEcModel(ecModel,varargin)
% 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 in GECKO 3 format (with ecModel.ec structure).
% an ecModel (with ecModel.ec structure).
%
% Name-Value Arguments
% --------------------
Expand Down Expand Up @@ -82,6 +86,22 @@ function saveEcModel(ecModel,varargin)
fclose('all');
delete('backup.xml');
otherwise % Assume YAML
% The ecModel format the file follows (as project_format in
% model_adapter.toml) and the release that wrote it.
ecModel.annotation.gecko_format = '4';
ecModel.annotation.generator = ['GECKO ' geckoVersion()];
if isfield(ecModel.annotation, 'geckopy_version')
ecModel.annotation = rmfield(ecModel.annotation, 'geckopy_version');
end
writeYAMLmodel(ecModel,filename);
end
end

function v = geckoVersion()
file = fullfile(findGECKOroot, 'version.txt');
if isfile(file)
v = strtrim(fileread(file));
else
v = 'unknown';
end
end
22 changes: 21 additions & 1 deletion src/utilities/upgradeGECKOproject.m
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,23 @@ function keepOriginal(file)
if ~contains(fileread(file), 'prot_pool')
continue
end
model = readYAMLmodel(file);
try
model = readYAMLmodel(file);
catch err
add('saved ecModel', 'failed', sprintf('%s: %s', relative(file, projectPath), err.message));
continue
end
if isfield(model.annotation, 'gecko_format') && str2double(model.annotation.gecko_format) > 4
add('saved ecModel', 'failed', sprintf('%s: ecModel format %s is newer than this GECKO', ...
relative(file, projectPath), model.annotation.gecko_format));
continue
end
[model, changed] = upgradeEcModel(model);
if isempty(changed)
continue
end
if apply
model.annotation.generator = ['GECKO ' geckoVersion()];
keepOriginal(file);
writeYAMLmodel(model, file);
end
Expand Down Expand Up @@ -287,3 +298,12 @@ function checkOptions(args, names, functionName)
strjoin(names, ', '))
end
end

function v = geckoVersion()
file = fullfile(findGECKOroot, 'version.txt');
if isfile(file)
v = strtrim(fileread(file));
else
v = 'unknown';
end
end
8 changes: 4 additions & 4 deletions test/VerificationMatrix.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ gather_kcats/kcatsFromECTree.m tc0086, tc0087
gather_kcats/kcatsFromHomologs.m tc0095, tc0096
gather_kcats/extractEnzymeSubstratePairs.m tc0096, tc0015
gather_kcats/convertCustomKcats.m tc0091, tc0092, tc0094
change_model/upgradeEcModel.m tc0093, tc0094
utilities/upgradeGECKOproject.m tc0094
change_model/upgradeEcModel.m tc0093, tc0094, tc0099
utilities/upgradeGECKOproject.m tc0094, tc0099
gather_kcats/readOpenKineticsPredictorOutput.m tc0030, tc0069
gather_kcats/reviewKcatAssignment.m Has only been tested by running it manually, not tested by test cases
gather_kcats/ecStatsFromBrenda.m Not verified against real BRENDA data in this environment -- see its own TODO comment
Expand Down Expand Up @@ -70,8 +70,8 @@ model_adapter/adapterParameters.tsv tc0064, tc0065
model_adapter/adapterTemplate.toml tc0068
utilities/* Not explicitly tested - The code has existed for a long time. There are a lot of functions in here, unclear which of them are still useful and functional.
io/loadConventionalGEM.m tc0009
io/loadEcModel.m tc0009
io/saveEcModel.m tc0009
io/loadEcModel.m tc0009, tc0099
io/saveEcModel.m tc0009, tc0099
tutorials/tutorial_Human-GEM/HumanGEMAdapter.m tcm0002 - does not test all aspects of the adapter, but some.
tutorials/full_ecModel/model_adapter.toml tcm0001 - does not test all aspects of the adapter, but some.
tutorials/tutorial_yeast-GEM/code/changeMedia_batch.m Not explicitly tested - The code has existed for a long time
Expand Down
3 changes: 3 additions & 0 deletions test/unit_tests/geckoCoreFunctionTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ function testsaveECModel_tc0009(testCase)
saveEcModel(ecModel, [], adapter);
loadedEcModel = loadEcModel([],adapter);
delete(fullfile(adapter.params.path,'models','ecModel.yml'));
verifyEqual(testCase, loadedEcModel.annotation.gecko_format, '4')
verifyTrue(testCase, startsWith(loadedEcModel.annotation.generator, 'GECKO '))
loadedEcModel.annotation = rmfield(loadedEcModel.annotation, {'gecko_format', 'generator'});
verifyEqual(testCase, ecModel, loadedEcModel)

% Test loading of conventional GEM
Expand Down
30 changes: 30 additions & 0 deletions test/unit_tests/upgradeTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,43 @@ function testUpgradeGECKOproject_tc0094(testCase)
saved = readYAMLmodel(fullfile(project, 'models', 'ecModel.yml'));
[~, changed] = upgradeEcModel(saved);
verifyEmpty(testCase, changed)
verifyEqual(testCase, saved.annotation.gecko_format, '4')
verifyTrue(testCase, startsWith(saved.annotation.generator, 'GECKO '))
params = loadAdapterParams(fullfile(project, 'model_adapter.toml'));
verifyEqual(testCase, params.projectFormat, 4)

again = upgradeGECKOproject(project);
verifyTrue(testCase, all(ismember(again.status, {'by hand'})))
end

function testEcModelFormat_tc0099(testCase)
% saveEcModel records the format and the release; loadEcModel upgrades
% a GECKO 3 file without them and refuses a newer format.
[model, adapter, project] = buildProject();
testCase.addTeardown(@() rmdir(project, 's'));
mkdir(fullfile(project, 'models'));
saveEcModel(model, 'ecModel.yml', adapter);
text = fileread(fullfile(project, 'models', 'ecModel.yml'));
verifyNotEmpty(testCase, regexp(text, '- gecko_format: "4"', 'once'))
verifyNotEmpty(testCase, regexp(text, '- generator: GECKO \S+', 'once'))
loaded = verifyWarningFree(testCase, @() loadEcModel('ecModel.yml', adapter));
verifyEqual(testCase, loaded.S, model.S)

writeYAMLmodel(gecko3(model), fullfile(project, 'models', 'gecko3.yml'));
up = verifyWarning(testCase, @() loadEcModel('gecko3.yml', adapter), 'loadEcModel:upgraded');
verifyEqual(testCase, up.S, model.S)
verifyEqual(testCase, [up.lb up.ub], [model.lb model.ub])
verifyEqual(testCase, up.annotation.gecko_format, '4')

writeLines(fullfile(project, 'models', 'newer.yml'), ...
strsplit(strrep(text, '- gecko_format: "4"', '- gecko_format: "5"'), newline));
verifyError(testCase, @() loadEcModel('newer.yml', adapter), 'loadEcModel:format')
newer = readYAMLmodel(fullfile(project, 'models', 'newer.yml'));
verifyEqual(testCase, getfield(upgradeEcModel(newer), 'annotation', 'gecko_format'), '5')
report = upgradeGECKOproject(project);
verifyEqual(testCase, report.status(contains(report.note, 'newer.yml')), {'failed'})
end

function old = gecko3(model)
% The model as GECKO 3 defined it: usage and pool exchange reversed.
r = startsWith(model.rxns, 'usage_prot_') | strcmp(model.rxns, 'prot_pool_exchange');
Expand Down
Loading