Skip to content

fix: write subsystems as strings in the MATLAB export - #165

Merged
edkerk merged 1 commit into
developfrom
fix-mat-export-subsystems
Sep 29, 2026
Merged

edkerk merged 1 commit into
developfrom
fix-mat-export-subsystems

Conversation

@edkerk

@edkerk edkerk commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

export_for_git now writes subsystems as strings in the .mat file.

read_yaml_model keeps a reaction's subsystem as a list, as the YAML format writes it (even for one subsystem). cobra's save_matlab_model only handles a string, and fails with TypeError: unhashable type: 'numpy.ndarray' when given a list. So any model read from a RAVEN 3-format YAML could not be exported to .mat. This was found when building Human-GEM 2.1.0 from its RAVEN 3-format YAML (SysBioChalmers/Human-GEM#1124).

  • _string_subsystems gives the MATLAB export a copy of the model with each subsystem joined by subsystem_to_str (a single subsystem stays its name; several are joined with ;), as export_to_excel already does. The model passed in is not changed, and a model without list subsystems is exported as before.
  • Test: a model with a one-item and a two-item subsystem list exports to .mat, reads back as Glycolysis and Transport;Exchange, and keeps its lists. The test fails without the fix.

The full suite (1055 tests) and ruff check src tests pass on Vera.

read_yaml_model keeps a reaction's subsystem as a list, as the YAML format
writes it, but cobra's save_matlab_model only handles a string and fails on a
list. export_for_git now joins them with subsystem_to_str for the .mat file, as
export_to_excel already does, on a copy of the model.
@edkerk
edkerk merged commit ca11646 into develop Sep 29, 2026
8 checks passed
@edkerk
edkerk deleted the fix-mat-export-subsystems branch September 29, 2026 23:55
edkerk added a commit that referenced this pull request Oct 7, 2026
The .mat export delegated to cobra's save_matlab_model, which writes a
COBRA Toolbox structure: COBRA's field names (description/modelName,
rxnECNumbers, geneNames), no slot for version, date, annotation,
metFrom/rxnFrom, rxnNotes/rxnReferences/rxnConfidenceScores, inchis or
the *Miriams structs, and rev as a platform-width integer. An integer rev
makes RAVEN's mergeLinear fail on `S(...).' > 0 | rev`, which MATLAB
rejects as a sparse integer operation.

io.mat now writes and reads the RAVEN structure directly. The layout
follows what RAVEN's own exporter produces, taken from the released
yeast-GEM and Human-GEM files: writing yeast-GEM through it reproduces
RAVEN's own yeast-GEM.mat field for field, with the same classes, shapes,
sparsity and nested subSystems/MIRIAM structures. metComps is the one
integer column, so a reader that turns the 1-based index into a position
gets one; it is signed, so a metabolite with no compartment goes negative
on that subtraction rather than wrapping round.

The SBML export lost five fields to RAVEN's importModel. Only subSystems
was actually absent, because cobra writes the groups package from
model.groups and a model read from YAML carries its subsystems on the
reaction; rxnNotes, rxnReferences and rxnConfidenceScores were present
under cobra's key names rather than the labels importModel parses, and
the model note was not written as its own notes entry. _for_sbml shapes a
copy for the SBML write, leaving the caller's model alone.

This supersedes #165, which joined subsystems into a string for cobra's
writer; the RAVEN writer keeps them as a cell per reaction.
edkerk added a commit that referenced this pull request Oct 7, 2026
* fix: write RAVEN-format .mat and RAVEN-readable SBML

The .mat export delegated to cobra's save_matlab_model, which writes a
COBRA Toolbox structure: COBRA's field names (description/modelName,
rxnECNumbers, geneNames), no slot for version, date, annotation,
metFrom/rxnFrom, rxnNotes/rxnReferences/rxnConfidenceScores, inchis or
the *Miriams structs, and rev as a platform-width integer. An integer rev
makes RAVEN's mergeLinear fail on `S(...).' > 0 | rev`, which MATLAB
rejects as a sparse integer operation.

io.mat now writes and reads the RAVEN structure directly. The layout
follows what RAVEN's own exporter produces, taken from the released
yeast-GEM and Human-GEM files: writing yeast-GEM through it reproduces
RAVEN's own yeast-GEM.mat field for field, with the same classes, shapes,
sparsity and nested subSystems/MIRIAM structures. metComps is the one
integer column, so a reader that turns the 1-based index into a position
gets one; it is signed, so a metabolite with no compartment goes negative
on that subtraction rather than wrapping round.

The SBML export lost five fields to RAVEN's importModel. Only subSystems
was actually absent, because cobra writes the groups package from
model.groups and a model read from YAML carries its subsystems on the
reaction; rxnNotes, rxnReferences and rxnConfidenceScores were present
under cobra's key names rather than the labels importModel parses, and
the model note was not written as its own notes entry. _for_sbml shapes a
copy for the SBML write, leaving the caller's model alone.

This supersedes #165, which joined subsystems into a string for cobra's
writer; the RAVEN writer keeps them as a cell per reaction.

* feat: read a COBRA Toolbox .mat as well as a RAVEN one

read_matlab_model assumed a RAVEN structure, so a COBRA one came back
half-read and silently: the skeleton (stoichiometry, bounds, GPRs,
subsystems, compartments, charges, formulas) was right, but everything
COBRA keeps under its own names was dropped -- the identifier
(description/modelName), EC codes (rxnECNumbers), gene names (geneNames)
and every cross-reference field.

A COBRA structure is now recognised and handed to cobra's own reader,
which already knows all of those mappings, rather than repeating the
table here; the result has its annotations normalised to lists so a model
read from either layout looks the same as one from read_yaml_model. A
struct that is neither raises instead of returning something plausible
but wrong.

Human-GEM 2.0.1 and 2.1.0 shipped a COBRA-format .mat, so this is the way
back from those releases: reading one and writing it out again produces
the RAVEN file they should have had.

* fix: type annotations in io.mat

Three loops over a float column reused the variable the string loops
above bound, the subsystem accumulator had no annotation, and the
metadata dict holds the default bounds as floats beside its string
fields.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant