Release notes for sbmlutils 0.11.0
We are pleased to release the next version of sbmlutils including the following changes. This release makes SBML core round trip (#469). That changes part of the python API, listed under breaking changes: code which uses one of these has to be changed. It also changes how some existing model definitions are written, listed under behaviour changes: the model definition stays as it is, but the SBML written from it differs.
Breaking changes
- comp is no longer declared on every model. A model declares the comp package only when it uses comp content: a
portor areplacedByon any of its elements, wherever it is nested, a submodel, a port, a replaced element, a deletion or a model definition;Package.COMP_V1inpackagesstill declares it explicitly. A model without comp content is written withoutxmlns:compandcomp:required. On the documentcreate_modelwrote for such a model,model.getPlugin("comp")returnsNoneandsbmlutils.comp.create_ports(model, ...)raisesAttributeError; code which adds comp content to the written document has to addPackage.COMP_V1to thepackagesof the model Model.unitsis alist[UnitDefinition], it was theUnitsclass of the model.Model(units=U)with aUnitssubclass is still accepted and normalized to the list, which is the documented authoring style. Code which reads a unit from the class, e.g.model.units.mM, or which assigns the class after construction,model.units = U, fails: the latter raisesTypeError: 'type' object is not iterablewhen the model is writtenReaction.formulais aKineticLaw, it was theFormulanamedtuple(value, unit).reaction.formula.valuebecomesreaction.formula.math,reaction.formula.unitstays, and unpacking it as a tuple no longer works.Reaction(formula="k1*S1"), the(formula, unit)tuple and aFormulaare still accepted; theFormulanamedtuple is deprecated. The staticReaction.set_kinetic_lawis removed, it had no callersEvent.assignmentsis alist[EventAssignment], it was adictof variable to expression, so that an event assignment carries its own metadata.Event(assignments={"S1": 5})is still accepted and converted; code which readsevent.assignmentsas a dict has to iterate the list,assignment.variableandassignment.valueEvent.trigger,Event.priorityandEvent.delayare aTrigger, aPriorityand aDelay, they were formula strings, so that each carries its own metadata. Code which reads them as a string readsevent.trigger.math,event.priority.mathandevent.delay.math. Writing them is unchanged:Event(trigger="time >= 10", priority="1", delay="2")and an assignment after construction such asevent.trigger = "time >= 20"are still accepted and converted, and a trigger string assigned later keeps thepersistentandinitialValueof the trigger it replaces.event.trigger_persistentandevent.trigger_initialValuestill read and set the flags of the triggerReactionEquation.modifiersis a list ofEquationPart, so that a modifier carries its own id, name, metaid, sboTerm, notes and annotations; it was a list of species ids. A list of species ids is still accepted and converted, code which reads the modifiers readsmodifier.speciesSbase.notesholds XHTML: notes are normalized once when the element is created, markdown is still accepted and rendered, aNotesobject selects the format explicitly.notesused to hold the markdown, which was rendered on write, so notes read from a file came back nested and plain text was mutated,2*3*4became2<em>3</em>4.Sbase.get_notes_xmlis removed,Sbase.notesis the XHTML it returned- rules and events without an id are no longer given a generated one.
AssignmentRule.sidandRateRule.siddefault toNone, they defaulted toAssignmentRule_<variable>andRateRule_<variable>, which were never written, until this release made rule ids writable. Code which referenced a rule by the generated id has to give the rule itssid; aport=Trueon a rule without an id logs an error and creates no port.Event.sidacceptsNone, andsbml_to_modelreads an event without an id without one Modelis no longer a pydanticBaseModel. The base was inert:Model.__init__never reachedBaseModel.__init__, so nothing was validated, anddeepcopy,==andmodel_dumpraised anAttributeError.copy.deepcopy(model)works now and==no longer raises, it compares identity. The unusedModel.units_dictis removed
Behaviour changes
- writing a model no longer emits an SBO CVTerm (
BQB.ISonsbo/<term>) alongside thesboTermattribute, and no longer forces ametaidonto an element which had none. ThesboTermattribute already carries the term; the duplicate added an annotation which was not in the source on every round trip - an unset compartment size or parameter value is no longer written as
NaN, it stays unset. An explicitNaNis still written asNaN, since SBML distinguishes an unset value from aNaNone Compartment.spatialDimensionsdefaults toNonerather than3, and an unset value is not written. This is valid SBML, but it changes unit inheritance: a compartment inherits the model levelvolumeUnitsonly whenspatialDimensions == 3, so a compartment which relied on the old default for its unit has to setspatialDimensions=3Reaction(..., reversible=...)takes effect: it was stored and never read, thereversibleof theReactionEquationwas written instead. An explicitreversibleon the reaction now overrides the equation, an unset one defers to itEvent(..., useValuesFromTriggerTime=...)takes effect:Truewas written whatever the argument was, which changed the simulation of an event which set it toFalse- the math of a trigger, a priority or a constraint which does not parse is logged as an error, the element was written without math, silently. An empty string is such math; an element without math is
Trigger(None),Priority(None)or aConstraintwithoutmath - the id of an assignment rule, a rate rule, an initial assignment or an event assignment is written. libsbml aliases
setIdto thevariableorsymbolof these, so the id was silently lost; it is set throughsetIdAttributenow, which writes it from SBML L3V2 on - the id of a
KineticLawis written only from SBML L3V2 on, which gave the kinetic law its id attribute; below L3V2, including the default L3V1 ofcreate_model, it is not written - a name containing a space on a species reference or a modifier is rejected by libsbml (
SimpleSpeciesReference.setNameapplies the SId syntax to the name, which SBML defines as a string). The name used to vanish silently, now a warning is logged - notes rooted at
<html>, a complete XHTML document as CellDesigner writes it on every element, are kept as they are, andDocument(notes=...)appends the sbmlutils attribution to their body. A<body>is kept as it is too, and a sequence of elements such as<p>is wrapped into a<body> sbml_to_modelno longer declares the fbc package on every model it reads, it declares the packages of the document
Features
- SBML core round trips (#469):
SBML -> sbml_to_model -> create_model -> SBMLpreserves the model. Measured on the l3v2 semantic cases of the SBML test suite, by simulating each case with roadrunner before and after the round trip, the pass rate of the first 150 cases went from 57.4% to 100%. Over all 1690 cases, 1372 of the 1482 which can be compared round trip; each of the other 110 uses the comp package (103) or an id which shadows a MathML constant (7). Of the cases which use no package, 1362 of 1369 round trip, 99.5%, see Reading and writing - what now round trips: unit definitions and unit references, function definitions, events with their event assignments, triggers, priorities and delays, constraints, kinetic laws with their local parameters, notes, the metadata of modifiers, species references, triggers, priorities and delays, and the model level
conversionFactor; before, all of these were dropped. The fbc, distrib and comp packages are out of scope of this release, their content is not read yet Unit(kind, exponent, scale, multiplier)andUnitDefinition(sid, units=[...])represent any SBML unit definition exactly, including ascale, which the pint expression folds into the multiplier. The pint expression stays the authoring style.Model(units=...)accepts a list ofUnitDefinitionas well as aUnitsclass, and an element references a unit by its id, e.g.Parameter("c", value=1.0, unit="mM"), see Units- an element without math, which SBML allows from L3V2 on, round trips without math: a function definition, an initial assignment, a rule, a kinetic law, an event assignment, and the trigger, priority and delay of an event, as does an event without a trigger;
sbml_to_modeldropped them. The value of these elements acceptsNone. On anEvent,trigger=None,priority=Noneanddelay=Nonewrite no element, andTrigger(None),Priority(None)andDelay(None)write the element without math - the
mathof aConstraintis optional,Constraint("c1")is a constraint without math, which SBML allows KineticLawandLocalParametermodel a kinetic law with its own id, name, metaid, sboTerm, notes, annotations and the local parameters which are scoped to it; identically named local parameters of two reactions no longer collideEventAssignmentgives an event assignment its own metadata; theassignments={"S1": 5}dict of anEventis still accepted. The delay of an event can be written,Event(..., delay=...)raised aTypeErrorTrigger,PriorityandDelaymodel the trigger, priority and delay of an event with their own id, name, metaid, sboTerm, notes and annotations; a formula string or a number is still accepted for each,Event(delay=5)raised an error. Their id and name are written from SBML L3V2 on, which gave them these attributes. ATriggercarries its ownpersistentandinitialValue: passed together with aTrigger, atrigger_persistentortrigger_initialValueof theEventwhich differs from them is not applied and is logged as a warning, as is one set on an event without a trigger. A priority, which SBML L2 does not have, is logged as an error and not written at L2, it raised anAttributeErrorModel(..., conversionFactor=...)sets the model wide conversion factor, distinct from the one of a species- writing a model read by
sbml_to_modelno longer logs the authoring hints,'name' should be setand'sboTerm' should be set: a model read from a file has what the file had. Writing the first 194 cases of the test suite back out logged 1512 of them
Fixes
KeyValuePair.uriwas written withsetValueinstead ofsetUri, so the uri overwrote the valueUncertParameter.varwas written withsetValueinstead ofsetVarUncertSpan.varUpperwas written withsetValueLowerinstead ofsetVarUpper, so it overwrote the lower valuesbml_to_modelcalledisSetValuewithout parentheses, so every parameter received a value, also one which had none
Documentation
- Reading and writing describes the round trip, its measured coverage and what it does not preserve
- Units describes the explicit
Unitrepresentation and when to use it rather than a pint expression
Development
tests/test_roundtrip.pysimulates a model with roadrunner, round trips it and compares the trajectories. A curated set of cases runs in the default test run, the full sweep over the test suite runs withpytest -m sbml_testsuite, and the cases which do not round trip yet are recorded as strict xfails with their reason, so the list cannot go stalescripts/roundtrip_report.pyruns the sweep in parallel and reports the pass rate. Every case runs in a python process of its own, so a crash of roadrunner in native code ends a single case rather than the sweep, and it is reported apart from the round-trip failures
Your sbmlutils team
