diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3dddaf..ceb86576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### I/O + +* `writeYAMLmodel` writes an ec reaction without enzymes (a row of zeros in `model.ec.rxnEnzMat`) with no `enzymes` entry (was: `- enzymes: !!omap` with nothing under it, which is not valid YAML). `readYAMLmodel` reads a missing `enzymes` entry as a reaction without enzymes, and still reads `- enzymes: !!omap` with no entries and `- enzymes: !!omap []` from files written earlier. + ### Manipulation * `mergeCompartments`: a merged metabolite is the copy already in compartment `s`, if any, diff --git a/src/io/writeYAMLmodel.m b/src/io/writeYAMLmodel.m index 6dd86094..22df44db 100755 --- a/src/io/writeYAMLmodel.m +++ b/src/io/writeYAMLmodel.m @@ -224,9 +224,10 @@ function writeField(model,fid,fieldName,type,pos,name,keyIndent) end elseif strcmp(fieldName,'rxnEnzMat') - %S: create header & write each enzyme in a new line - fprintf(fid,'%s: !!omap\n',name); + %enzymes: create header & write each enzyme in a new line. A + %reaction without enzymes has no entry at all. if sum(field(pos,:) ~= 0) > 0 + fprintf(fid,'%s: !!omap\n',name); model.enzymes = model.enzymes(field(pos,:) ~= 0); model.coeffs = field(pos,field(pos,:) ~= 0); %Sort metabolites: diff --git a/tests/function_tests/tIO.m b/tests/function_tests/tIO.m index 093445f1..2ce5dd9c 100644 --- a/tests/function_tests/tIO.m +++ b/tests/function_tests/tIO.m @@ -287,6 +287,60 @@ function writeReadYAMLRoundTrip(testCase) testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns)); end + function writeYAMLmodelOmitsEnzymesOfEcReactionWithoutEnzymes(testCase) + % An ec reaction whose rxnEnzMat row is empty has no enzymes + % entry in the file; the reaction before and after keep theirs. + m = testCase.ecYamlModel(); + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + txt = fileread(f); + % two ec reactions with enzymes and two ec-enzymes entries + testCase.verifyEqual(numel(strfind(txt, '- enzymes:')), 4); + lines = splitlines(txt); + idx = find(strcmp(strtrim(lines), '- id: ec_R2')); + testCase.verifyEqual(numel(idx), 1); + testCase.verifyFalse(contains(lines{idx+1}, 'enzymes')); + testCase.verifyFalse(contains(lines{idx+2}, 'enzymes')); + end + + function readYAMLmodelReadsEcReactionWithoutEnzymes(testCase) + % The reaction without enzymes has a zero row in rxnEnzMat and + % keeps its id and kcat; the other rows are unchanged. + m = testCase.ecYamlModel(); + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + evalc('m2 = readYAMLmodel(f);'); + testCase.verifyEqual(m2.ec.rxns, m.ec.rxns); + testCase.verifyEqual(m2.ec.kcat, m.ec.kcat, 'AbsTol', 1e-9); + testCase.verifyEqual(full(m2.ec.rxnEnzMat), m.ec.rxnEnzMat); + end + + function readYAMLmodelReadsOlderEmptyEnzymesForms(testCase) + % Files written earlier carry a content-less "enzymes: !!omap" + % or "enzymes: !!omap []" for a reaction without enzymes. + m = testCase.ecYamlModel(); + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + txt = fileread(f); + forms = {' - enzymes: !!omap', ' - enzymes: !!omap []'}; + for i = 1:numel(forms) + lines = splitlines(txt); + idx = find(strcmp(strtrim(lines), '- id: ec_R2')); + lines = [lines(1:idx+1); forms(i); lines(idx+2:end)]; + g = [tempname '.yml']; + testCase.addTeardown(@() delete(g)); + fid = fopen(g, 'w'); + fprintf(fid, '%s', strjoin(lines, newline)); + fclose(fid); + evalc('m2 = readYAMLmodel(g);'); + testCase.verifyEqual(m2.ec.rxns, m.ec.rxns); + testCase.verifyEqual(full(m2.ec.rxnEnzMat), m.ec.rxnEnzMat); + end + end + function readYAMLmodelKeepsSubsystemsAligned(testCase) % Reactions without a subsystem sit between, before and after % those with one: each subsystem stays with its own reaction, @@ -583,4 +637,23 @@ function writeExcelRoundTrips(testCase) end end + + methods (Access = private) + function m = ecYamlModel(testCase) + % Model with three ec reactions; the second has no enzymes. + m = testCase.model; + m.ec.geckoLight = false; + m.ec.rxns = {'ec_R1'; 'ec_R2'; 'ec_R3'}; + m.ec.kcat = [13.7; 5; 2]; + m.ec.source = {'brenda'; 'standard'; ''}; + m.ec.notes = {''; ''; ''}; + m.ec.eccodes = {'1.1.1.1'; ''; ''}; + m.ec.genes = m.genes(1:2); + m.ec.enzymes = {'P0A1'; 'P0A2'}; + m.ec.mw = [51000; 40000]; + m.ec.sequence = {'MABC'; 'MDEF'}; + m.ec.concs = [NaN; NaN]; + m.ec.rxnEnzMat = [1 2; 0 0; 0 1]; + end + end end