pypi sbmlutils 0.11.0

3 hours ago

Release notes for sbmlutils 0.11.0

sbmlutils

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 port or a replacedBy on any of its elements, wherever it is nested, a submodel, a port, a replaced element, a deletion or a model definition; Package.COMP_V1 in packages still declares it explicitly. A model without comp content is written without xmlns:comp and comp:required. On the document create_model wrote for such a model, model.getPlugin("comp") returns None and sbmlutils.comp.create_ports(model, ...) raises AttributeError; code which adds comp content to the written document has to add Package.COMP_V1 to the packages of the model
  • Model.units is a list[UnitDefinition], it was the Units class of the model. Model(units=U) with a Units subclass 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 raises TypeError: 'type' object is not iterable when the model is written
  • Reaction.formula is a KineticLaw, it was the Formula namedtuple (value, unit). reaction.formula.value becomes reaction.formula.math, reaction.formula.unit stays, and unpacking it as a tuple no longer works. Reaction(formula="k1*S1"), the (formula, unit) tuple and a Formula are still accepted; the Formula namedtuple is deprecated. The static Reaction.set_kinetic_law is removed, it had no callers
  • Event.assignments is a list[EventAssignment], it was a dict of variable to expression, so that an event assignment carries its own metadata. Event(assignments={"S1": 5}) is still accepted and converted; code which reads event.assignments as a dict has to iterate the list, assignment.variable and assignment.value
  • Event.trigger, Event.priority and Event.delay are a Trigger, a Priority and a Delay, they were formula strings, so that each carries its own metadata. Code which reads them as a string reads event.trigger.math, event.priority.math and event.delay.math. Writing them is unchanged: Event(trigger="time >= 10", priority="1", delay="2") and an assignment after construction such as event.trigger = "time >= 20" are still accepted and converted, and a trigger string assigned later keeps the persistent and initialValue of the trigger it replaces. event.trigger_persistent and event.trigger_initialValue still read and set the flags of the trigger
  • ReactionEquation.modifiers is a list of EquationPart, 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 reads modifier.species
  • Sbase.notes holds XHTML: notes are normalized once when the element is created, markdown is still accepted and rendered, a Notes object selects the format explicitly. notes used 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*4 became 2<em>3</em>4. Sbase.get_notes_xml is removed, Sbase.notes is the XHTML it returned
  • rules and events without an id are no longer given a generated one. AssignmentRule.sid and RateRule.sid default to None, they defaulted to AssignmentRule_<variable> and RateRule_<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 its sid; a port=True on a rule without an id logs an error and creates no port. Event.sid accepts None, and sbml_to_model reads an event without an id without one
  • Model is no longer a pydantic BaseModel. The base was inert: Model.__init__ never reached BaseModel.__init__, so nothing was validated, and deepcopy, == and model_dump raised an AttributeError. copy.deepcopy(model) works now and == no longer raises, it compares identity. The unused Model.units_dict is removed

Behaviour changes

  • writing a model no longer emits an SBO CVTerm (BQB.IS on sbo/<term>) alongside the sboTerm attribute, and no longer forces a metaid onto an element which had none. The sboTerm attribute 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 explicit NaN is still written as NaN, since SBML distinguishes an unset value from a NaN one
  • Compartment.spatialDimensions defaults to None rather than 3, and an unset value is not written. This is valid SBML, but it changes unit inheritance: a compartment inherits the model level volumeUnits only when spatialDimensions == 3, so a compartment which relied on the old default for its unit has to set spatialDimensions=3
  • Reaction(..., reversible=...) takes effect: it was stored and never read, the reversible of the ReactionEquation was written instead. An explicit reversible on the reaction now overrides the equation, an unset one defers to it
  • Event(..., useValuesFromTriggerTime=...) takes effect: True was written whatever the argument was, which changed the simulation of an event which set it to False
  • 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 a Constraint without math
  • the id of an assignment rule, a rate rule, an initial assignment or an event assignment is written. libsbml aliases setId to the variable or symbol of these, so the id was silently lost; it is set through setIdAttribute now, which writes it from SBML L3V2 on
  • the id of a KineticLaw is written only from SBML L3V2 on, which gave the kinetic law its id attribute; below L3V2, including the default L3V1 of create_model, it is not written
  • a name containing a space on a species reference or a modifier is rejected by libsbml (SimpleSpeciesReference.setName applies 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, and Document(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_model no 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 -> SBML preserves 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) and UnitDefinition(sid, units=[...]) represent any SBML unit definition exactly, including a scale, which the pint expression folds into the multiplier. The pint expression stays the authoring style. Model(units=...) accepts a list of UnitDefinition as well as a Units class, 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_model dropped them. The value of these elements accepts None. On an Event, trigger=None, priority=None and delay=None write no element, and Trigger(None), Priority(None) and Delay(None) write the element without math
  • the math of a Constraint is optional, Constraint("c1") is a constraint without math, which SBML allows
  • KineticLaw and LocalParameter model 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 collide
  • EventAssignment gives an event assignment its own metadata; the assignments={"S1": 5} dict of an Event is still accepted. The delay of an event can be written, Event(..., delay=...) raised a TypeError
  • Trigger, Priority and Delay model 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. A Trigger carries its own persistent and initialValue: passed together with a Trigger, a trigger_persistent or trigger_initialValue of the Event which 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 an AttributeError
  • Model(..., conversionFactor=...) sets the model wide conversion factor, distinct from the one of a species
  • writing a model read by sbml_to_model no longer logs the authoring hints, 'name' should be set and '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.uri was written with setValue instead of setUri, so the uri overwrote the value
  • UncertParameter.var was written with setValue instead of setVar
  • UncertSpan.varUpper was written with setValueLower instead of setVarUpper, so it overwrote the lower value
  • sbml_to_model called isSetValue without 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 Unit representation and when to use it rather than a pint expression

Development

  • tests/test_roundtrip.py simulates 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 with pytest -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 stale
  • scripts/roundtrip_report.py runs 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

Don't miss a new sbmlutils release

NewReleases is sending notifications on new releases.