Skip to content

Mint every SED-ML id from one document-wide allocator - #1846

Merged
jcschaff merged 2 commits into
masterfrom
fix/sedml-document-wide-id-allocator
Aug 6, 2026
Merged

Mint every SED-ML id from one document-wide allocator#1846
jcschaff merged 2 commits into
masterfrom
fix/sedml-document-wide-id-allocator

Conversation

@jcschaff

@jcschaff jcschaff commented Aug 6, 2026

Copy link
Copy Markdown
Member

Last of the nightly SEDML_SBML_IT failures. biomodel_28625786 has an application and a simulation both named compartmental, so the exported archive carried

<model id="compartmental" name="compartmental" .../>
<uniformTimeCourse id="compartmental" name="compartmental" .../>

which the OMEX validator rejects: "Each identified SED object must have a unique id. Multiple objects have the following ids: [compartmental]".

Why the previous fix didn't cover it

SED-ML SIds share one namespace across every kind of object, but SEDMLExporter built them from several independent recipes — application names, simulation names, mangled variable names, per-kind counters — with no check that two hadn't landed on the same string. #1840 added a uniqueness guard within the application namespace only; simulation ids were still minted unguarded in createSedMLSim.

Rather than repeat that guard per kind, every mint now goes through one allocator:

private String uniqueId(String base, String kindTag) {
    String candidate = base;
    int collisionCount = 0;
    while (!this.usedSedmlIds.add(candidate)) {
        candidate = base + "_" + kindTag + collisionCount++;
    }
    return candidate;
}

A free base is returned unchanged, so ids move only where they previously collided — a correct archive is unaffected. Applications are reserved up front (reserveSimContextIds) so they keep first refusal on their name, which matters because an application's id also names the SBML file written for it. #1840's _app<n> is now one case of the general _<kind><n>.

For biomodel_28625786 the entire id diff against master is one line:

duplicate ids NEW: NONE
compartmental model : ['compartmental', 'compartmental_0', 'compartmental_1']
compartmental sim   : [('compartmental_sim0', 'compartmental')]
ids removed vs master: []
ids added   vs master: ['compartmental_sim0']

Latent collisions this also closes

Neither had been hit by a test yet:

  • Override-derived models are <simContextId>_<overrideCount>, which can land on another application's mangled id. This very model has applications simple_1 and simple_1.5simple_1_5; a sixth override variant of simple_1 would have collided.
  • Data generator ids are dataGen_<task>_<mangled var>, and TokenMangler.mangleToSName is many-to-one — species a.b and a-b both mangle to a_b.

The 'time' data generator is now looked up by the id it was actually given rather than by rebuilding that id at the use site, since the allocator is free to move it.

Test expectations

biomodel_82065439 had the same duplicate-compartmental fault recorded and now round-trips cleanly, so it failed with "passed SEDML Round trip, but knownSEDMLFault was set" — entry removed. (This is the usual signal on this suite: a fix surfaces as a failure.)

biomodel_28625786 still fails, on a validator bug rather than on the archive, and is recorded as a known fault with the diagnosis inline. biosimulators-utils 0.2.3 checks every repeatedTask setValue target against the last model in the document instead of the one the change references — sedml/validation.py:509 passes a leaked model loop variable where it means change.model. Here that lands on HPC_070907; species targets happen to exist there so they pass, the kinetic parameters do not, and the errors are then reported against models (simple_1, simple_1_5) that were never searched. Verified with lxml that those parameters do resolve against their own model sources:

<parameter constant="true" id="Km_PKA_activates_Raf" units="Unit_umol_l_1" value="0.5"/>

0.2.3 is the latest release on PyPI, so there's nothing to upgrade to; the entry comes out when the upstream fix lands.

Testing

PR CI cannot exercise this — SEDML_SBML_IT runs only in regression.yml, and these models sit behind test.include.slow. Locally:

mvn -o test -pl vcell-core -Dgroups=SEDML_SBML_IT -Dtest=SEDMLExporterSBMLTest \
    -Dtest.include.slow=true -Dtest.only=biomodel_28625786,biomodel_82065439,biomodel_220138948

The duplicate-id error is gone; biomodel_82065439 now passes outright. The full 264-model group is still running — any other model whose fault this resolves will announce itself the same way, and I'll push a follow-up commit if more turn up before merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SvqmME7MkRUNEYje5HpiLt

SED-ML SIds share a single namespace across every kind of object, but the
exporter built them from several independent recipes - application names,
simulation names, mangled variable names, per-kind counters - with no check
that two of them hadn't landed on the same string.

biomodel_28625786 has an application AND a simulation both named
"compartmental", so the archive carried <model id="compartmental"> and
<uniformTimeCourse id="compartmental">, which the OMEX validator rejects:
"Each identified SED object must have a unique id".

#1840 fixed this within the application namespace only. Rather than repeat
that guard per kind, route every mint through uniqueId(): a free base is
returned unchanged, so ids move only where they previously collided. For
biomodel_28625786 the sole difference is the simulation becoming
"compartmental_sim0" - applications are reserved up front so they keep first
refusal on their name, which also keeps the SBML file names they generate.

This closes two latent collisions of the same family that no test had hit
yet: override-derived models are <simContextId>_<overrideCount>, which can
land on another application's mangled id (this very model has applications
"simple_1" and "simple_1.5" -> "simple_1_5"), and data generator ids are
"dataGen_<task>_<mangled var>", where the mangling is many-to-one.

The 'time' data generator is now looked up by the id it was given rather
than by rebuilding that id at the use site, since the allocator is free to
move it.
biomodel_82065439 was recorded as OMEX_PARSER_ERRORS for the same duplicate
"compartmental" id; it now round-trips cleanly, so the entry has to go or the
test fails with "passed SEDML Round trip, but knownSEDMLFault was set".

biomodel_28625786 still fails, but on a bug in the validator rather than in
the archive. biosimulators-utils 0.2.3 checks every repeatedTask setValue
target against the LAST model in the document instead of the one the change
references - sedml/validation.py:509 passes a leaked `model` loop variable
where it means `change.model`. Here that lands on HPC_070907; species targets
happen to exist there so they pass, the kinetic parameters do not, and the
errors are then reported against models (simple_1, simple_1_5) that were
never searched. Those parameters do resolve against their own model sources.
Recorded as a known fault until the upstream fix lands.
@jcschaff

jcschaff commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Full-group verification complete — the run flagged as pending in the description has finished:

mvn -o test -pl vcell-core -Dgroups=SEDML_SBML_IT -Dtest=SEDMLExporterSBMLTest -Dtest.include.slow=true

[INFO] Tests run: 264, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7679 s
[INFO] BUILD SUCCESS

All 264 models including the slow set, i.e. the full suite the nightly runs. No additional models had a knownSEDMLFault resolved by this change, so the two expectation updates already in this PR are the complete set and no follow-up commit is needed.

With this, the nightly SEDML_SBML_IT group goes from 6 failures to 0 (#1827, #1835, #1840, #1841, #1843, and this).

@jcschaff
jcschaff merged commit 0e24345 into master Aug 6, 2026
9 checks passed
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