3. Reading and writing models#
Get a model in and out of both toolboxes: SBML, RAVEN YAML, Excel, and the directory layout a Git-maintained model repository expects.
Functions on this page#
MATLAB |
Python |
|
|---|---|---|
read SBML |
||
write SBML |
||
read RAVEN YAML |
||
write RAVEN YAML |
||
write the RAVEN Excel format |
||
write a Standard-GEM repository layout |
Which of these you need depends on the file, not on the model: all of them produce the same in-memory model, and none of them is the canonical format. What differs is what survives a round trip and how readable the file is, which 3.2 and 3.4 show with examples.
Setup#
This page uses two models from docs/data/:
smallYeast.yml (RAVEN YAML, 45 kB) and yeast-GEM.xml (SBML, yeast-GEM v9.1.0).
3.1 Read a model#
modelSmall = readYAMLmodel('smallYeast.yml');
modelYeast = importModel('yeast-GEM.xml');
fprintf('%s %d reactions\n', modelSmall.id, numel(modelSmall.rxns));
fprintf('%s %d reactions\n', modelYeast.id, numel(modelYeast.rxns));
[Warning: The following fields have prefixes removed from all entries. If this is undesired, run importModel with removePrefix as false. Example: importModel('filename.xml',[],false);]
[Warning: The following MIRIAM strings are associated to more than one unique metabolite name: bigg.metabolite/ficytb5 bigg.metabolite/hdd2coa bigg.metabolite/pail_cho bigg.metabolite/pchol_cho bigg.metabolite/succ bigg.metabolite/tchola chebi/CHEBI:138108 chebi/CHEBI:17140 chebi/CHEBI:18097 ...and 23 more]
smallYeast 53 reactions
yeastGEM_v9.1.0 4102 reactions
Both warnings are informational and neither stops the read. The first says
importModel stripped the R_, M_, G_ and C_ prefixes SBML requires
on identifiers, which it does only when every identifier of that type
carries one; pass removePrefix as false to keep them, which is what a
comparison against the file's own ids requires. The second reports that the
model annotates several differently-named metabolites with the same database
identifier. That is a property of yeast-GEM rather than of the reader, and it
makes those annotations unusable as a key for matching metabolites across
models.
importModel accepts SBML Level 3 Version 1 with FBC version 2 and errors
on anything older; see the RAVEN 3 migration guide
if a file is rejected.
from cobra.io import read_sbml_model
from raven_toolbox.io import read_yaml_model
small = read_yaml_model("smallYeast.yml")
yeast = read_sbml_model("yeast-GEM.xml")
print(small.id, len(small.reactions), "reactions")
print(yeast.id, len(yeast.reactions), "reactions")
smallYeast 53 reactions
yeastGEM_v9__46__1__46__0 4102 reactions
read_sbml_model is cobrapy's own reader and strips the same SBML prefixes.
read_yaml_model is raven-toolbox's, and returns a plain cobra.Model, so
the two are interchangeable from here on. It reads gzipped files
transparently and accepts both the current YAML layout and the older RAVEN
one, so an archived model does not need converting first.
The two ids printed above are not the same, which is 3.2.
3.2 Watch the identifier mangling#
SBML identifiers must be valid XML names, so they cannot contain a dot. Model,
reaction and metabolite ids that do are encoded when the file is written, as
__ around the character's ASCII code: a . becomes __46__. The encoding is
in the file, so what differs between the toolboxes is whether it is undone on
the way back in.
disp(modelYeast.id); % from yeast-GEM.xml
modelFromYaml = readYAMLmodel('yeast-GEM.yml');
disp(modelFromYaml.id); % same release, unencoded
yeastGEM_v9.1.0
yeastGEM_v9.1.0
importModel decodes every __NN__ back to its character, across reactions,
metabolites, compartments, genes, gene rules and the model id, so a
round trip through SBML returns the ids you started with.
print("from SBML:", yeast.id)
from_yaml = read_yaml_model("yeast-GEM.yml")
print("from YAML:", from_yaml.id)
print("same size:", len(from_yaml.reactions) == len(yeast.reactions))
from SBML: yeastGEM_v9__46__1__46__0
from YAML: yeastGEM_v9.1.0
same size: True
cobrapy decodes the reaction, metabolite and gene ids but leaves the model id
encoded, so the same release read from the two formats reports two different
model ids while being the same model. model.name and the annotations are
unchanged.
Note
Each toolbox is fast in one format and slow in the other Reading yeast-GEM takes about 17 s from SBML and 73 s from YAML in Python, and about 78 s from SBML and 17 s from YAML in MATLAB, so the ranking is reversed. RAVEN parses YAML itself and goes through libSBML for SBML; cobrapy has the opposite balance. The format therefore follows from what is needed from it, a readable diff or the RAVEN-specific fields YAML preserves, and a script whose run time is dominated by reading the file will often run faster in the other format.
3.3 Write a model#
writeYAMLmodel(modelSmall, 'smallYeast-copy.yml');
exportModel(modelSmall, 'smallYeast.xml');
roundtrip = importModel('smallYeast.xml');
fprintf('%d %d %d\n', numel(roundtrip.rxns), numel(roundtrip.mets), ...
numel(roundtrip.genes));
[Warning: The following fields have one or more entries that do not start with a letter or _ (conflicting with SBML specifications). Prefixes are added to all entries in those fields:]
Document written
[Warning: The following fields have prefixes removed from all entries. If this is undesired, run importModel with removePrefix as false. Example: importModel('filename.xml',[],false);]
53 52 61
The two warnings are the prefixing rule in both directions: exportModel
adds a prefix to a whole field as soon as one id in it starts with something
other than a letter or underscore, and importModel takes it off again.
Pass neverPrefix as true to suppress the addition, but the resulting file
is not valid SBML.
Both writeYAMLmodel and exportModel take sortIds, which sorts a copy
before writing and leaves the caller's model untouched.
from cobra.io import write_sbml_model
from raven_toolbox.io import write_yaml_model
write_yaml_model(small, "smallYeast-copy.yml")
write_sbml_model(small, "smallYeast.xml")
roundtrip = read_sbml_model("smallYeast.xml")
print(len(roundtrip.reactions), len(roundtrip.metabolites), len(roundtrip.genes))
53 52 61
write_yaml_model and export_to_excel take sort_ids, which sorts what is
written without touching the model. cobrapy's write_sbml_model has no such
argument, so a stable SBML diff needs the sort applied first, on a copy:
write_sbml_model(sort_identifiers(small.copy()), path).
Both toolboxes write reactions in whatever order the model holds them, so inserting one reaction near the front shifts every line after it. Sorting first confines the diff of a one-reaction change to that reaction.
3.4 Spreadsheets#
The Excel format holds the model as five sheets, covering reactions, metabolites, compartments, genes and the model's own metadata, in a form that can be edited without a toolbox.
exportToExcelFormat(modelSmall, 'fileName', 'smallYeast.xlsx');
fprintf('%s\n', strjoin(sheetnames('smallYeast.xlsx'), ', '));
RXNS, METS, COMPS, GENES, MODEL
exportToExcelFormat writes .xlsx and errors on any other extension, so a
bare directory path is rejected rather than interpreted. The
RAVEN 3 migration guide covers scripts
that pass one. There is no matching importer: curateModelFromTables reads
a curated spreadsheet back by applying tabular edits to an existing model,
not by building one from scratch.
from raven_toolbox.io import export_to_excel
export_to_excel(small, "smallYeast.xlsx")
from openpyxl import load_workbook
print(load_workbook("smallYeast.xlsx").sheetnames)
['RXNS', 'METS', 'COMPS', 'GENES', 'MODEL']
export_to_excel needs the excel extra (pip install raven-toolbox[excel])
and writes the same five sheets, plus ENZYMES and ENZRXNS for an
enzyme-constrained model. Neither toolbox exports a whole model as a set of
tab-delimited files any more; the single reaction table that exportForGit
writes as its txt format is what remains, and pandas over the model's
collections covers the rest.
3.5 Export for a model repository#
A Git-maintained model repository (yeast-GEM, Human-GEM, and the models built
from standard-GEM) keeps the same model in several formats under model/, so
that a release is usable without a toolbox and a diff is readable in a pull
request. Both toolboxes write that layout directly, one subdirectory per format
plus a dependencies.txt recording the versions the files were written with.
exportForGit(modelSmall, 'prefix', 'smallYeast', 'path', 'repo', ...
'formats', {'yml', 'xml'});
written = dir(fullfile('repo', 'model', '**', '*'));
written = written(~[written.isdir]);
fprintf('%s\n', strjoin(sort({written.name}), ', '));
[Warning: The following fields have one or more entries that do not start with a letter or _ (conflicting with SBML specifications). Prefixes are added to all entries in those fields:]
Document written
dependencies.txt, smallYeast.xml, smallYeast.yml
dir reports names, so the subdirectories that contain the files are not
visible above: the layout written is repo/model/yml/smallYeast.yml and
repo/model/xml/smallYeast.xml. Set subDirs to false to put everything
in one folder instead. Identifiers are sorted before writing, with no option
to skip it, so successive releases diff cleanly. Left to itself
exportForGit writes all five formats,
mat, txt, xlsx, xml and yml; formats narrows that. COBRAtext
switches the txt table from metabolite names to metabolite ids, and
mainBranchFlag makes the export fail unless RAVEN itself is on its main
branch, which pins a release to a released toolbox version.
from pathlib import Path
from raven_toolbox.io import export_for_git
root = export_for_git(small, "repo", prefix="smallYeast", formats=("yml", "xml"))
print(sorted(p.relative_to(root).as_posix() for p in root.rglob("*") if p.is_file()))
['dependencies.txt', 'xml/smallYeast.xml', 'yml/smallYeast.yml']
export_for_git returns the model/ directory it wrote, and sorts a copy of
the model first, so the files are diff-friendly and the model you passed is
unchanged. Its default formats are yml, xml, mat and xlsx; ask for
txt explicitly if the repository expects one. sub_dirs=False flattens the
layout, and varname sets the variable name inside the .mat file for
repositories that pin one.
Warning
What can go wrong
The SBML writer refuses an identifier. SBML ids must start with a letter or underscore. Both toolboxes add a prefix to a whole field when any id in it does not, which is why
importModelandread_sbml_modelstrip prefixes on the way back.The model id differs between two copies of the same release. cobrapy decodes
__46__-style escapes in reaction, metabolite and gene ids, but not in the model id, so yeast-GEM read from SBML reportsyeastGEM_v9__46__1__46__0. Decode it withre.sub(r"__(\d+)__", lambda m: chr(int(m.group(1))), model.id), or comparemodel.name. Do not match entities on annotations: some database ids are shared by differently named metabolites.Excel export fails with a missing module. The Python side needs
openpyxl; installraven-toolbox[excel].A YAML round trip loses a field neither format defines. RAVEN YAML carries the RAVEN model fields; anything a plugin added outside them is not guaranteed to survive. Check with a diff, not by eye.
A hand-written SBML release diffs badly.
exportForGitandexport_for_gitsort identifiers for you, but a directwrite_sbml_modeldoes not, so a file written that way reorders whenever the model does.
See also#
Getting started, what to do with the model once it is loaded.
Guide overview, the other pages and what is planned.
MATLAB vs Python, the full function mapping, including everything that resolves to cobrapy.