Release notes for sbmlsim 0.9.5
A patch release of the fixes which the port of the models of pkdb_models onto sbmlsim 0.9.4 found, and a legend outside the plots which widens only the gap right of its plot. A simulation no longer depends on whether another instance of its model is alive (every model is compiled when it is loaded), on the point before it in a scan or on the references of a design, which also no longer set the start values of a fit; the processes of a pool end with the process which started them; the figures of sbmlsim do not depend on the backend of pyplot; a tall figure has its plots right below its title and a heatmap makes room for long labels. Visible changes of figures: a figure with a legend outside, the title of a figure which colour bars made wider (centered over its panels), a titled figure taller than 9.2 inches, every heatmap by a pixel, and a figure whose legend is taller than its plot (taller). The classifications of the IPCS return None for an undefined value.
Features
- A legend outside widens only the gap right of its plot (ruling 67 of the port of pkdb_models).
legend_position="outside"added the width of a panel to every gap between the plots of a figure (wspace + 1.0), whether a plot had a legend or not, so the panels shrank, a legend could cover the labels of the plot right of it and the legend of the last column was cut at the edge of the figure; the band figures of the acetaminophen model of pkdb_models were made 72 inches wide to make room. A legend outside is now placed right of its plot together with the colour bars: its column gets the room of its widest legend, measured on the legend, the figure is that much wider and the panels keep their size, so a column without a legend gets no room. One legend per row is the legend of the last plot of every row (legend=Falseon the others), which widens the figure by the legend alone. The legend starts0.12inches right of the plot (or of its right y axis or its last colour bar) with its top at the top of the plot; a legend which is taller than its plot gives its row the room by which it overflows the plot and the figure is that much taller, so it no longer runs into the legend or the plot below it. The title of a figure which became wider is centered over its panels, also of a figure which only its colour bars made wider (before, the title was centered over the figure with the empty room right of its last colour bar, so the titles of these figures move). The margin of matplotlib right of the panels stays right of the last legend, whichbbox_inches="tight", as the experiments save their figures, crops. The interactive figures of plotly keep their one legend of the figure right of all panels.
Fixes
- An empty column without a unit is no error (#310).
DataSet.from_dfloggedColumn 'time_unit' units are missingas an error for a unit column without a unit also when its column had no number either, e.g. the time of a table of PK-DB which does not report it (timeandtime_unitempty in every row): one error per dataset and load, 289 in one run of the apixaban model of pkdb_models. Such a column has no unit as before; the error is logged only when the column or itssd,se,minormaxhas a number, a debug message otherwise, and the same holds for the statistics of aunitcolumn. - The classifications of the IPCS classify the magnitude and give no class for an undefined value (#311).
uncertainty_classificationraised aValueErrorfor a negative uncertainty, although its docstring classifies|U|:U = (P97.5 - P2.5) / P50is negative for an output with a negative median, e.g. the change of the body weight (U = -1.66in the dulaglutide model of pkdb_models), so a sampling analysis of such an output failed in its classification (0.8.5 to 0.9.4). It now classifies|U|;uncertainty_classificationandsensitivity_classificationreturnNoneforNaNinstead of raising, and their symbols are empty, so one undefined index does not stop the classification of the others. Their return types are nowUncertaintyClassification | NoneandSensitivityClassification | None: a caller which reads.valueof the class handlesNonefor an undefined value (a type checker reports it); pkpdsim 0.5.5 adopts them and drops its workarounds. An infinite value, e.g.Uof an output whose median is 0, is classifiedhigh, as before. - A tall figure has its plots right below its title (#312). The top of the plots was capped at the fraction of matplotlib (
0.88), so for a figure taller than about 9.2 inches the gap between the title of the figure and the titles of its plots grew with the height: 1.2 inches above the plots for two rows, 3.6 inches for six rows (the RAAS figure of the losartan model of pkdb_models). The plots now begin below the band of the titles whatever the height; the gap between the title and the titles of the plots is 0.27 inches with the default sizes, and a figure of at most 9.2 inches does not change. - A simulation does not depend on whether another instance of its model is alive (#313). roadrunner 2.10 takes the compiled model of a new instance from its cache in the process when another instance of the same SBML is alive, and gives such an instance no piecewise triggers (
createModelData(..., 0)inLLVMModelGenerator::createModel), so CVODE's root finding does not stop at the switches of the piecewise functions of the model: the instance integrates a few1e-9apart from a compiled one. In a run of experiments the first experiment compiled its model and every later one took it from the cache, while the workers of a pool compiled theirs, so the first experiment, the later ones, a serial run and a pool run differed (0.9.4; 0.8.5 kept an instance alive from the start, so every experiment of it took the model from the cache): the first experiment of the empagliflozin, dapagliflozin and canagliflozin models of pkdb_models differed by4.4e-10to4.7e-9from a run with an instance of the model alive from the start. sbmlsim now compiles a model whenever it loads it (load_roadrunner,LoadSBMLOptions.RECOMPILE), which costs the compilation of the model once per loaded model, i.e. once per model of anExperimentRunnerand of a worker of a pool, and an instance no longer shares its code with another one (1.2 s and 41 MB per instance for the acetaminophen model of pkdb_models, against 0.044 s and 16 MB from the cache; 0.2 s for dapagliflozin), and a run with an instance alive from the start equals one without, bitwise. The compiled model is the more accurate one (on a rate which switches at a time,3.0e-5against3.6e-5from the exact solution); the results of a model with piecewise functions which ran from the cache change by the root finding at their switches, in these three models by up to1.5e-8(4.5e-7in the parameter scan of empagliflozin), the ones of a compiled model and of a model without piecewise functions do not change. - A heatmap of long labels is laid out (#314).
plot_heatmapand the other heatmaps ofsbmlsim.sensitivity.plotswarnedconstrained_layout not applied because axes sizes collapsed to zero(14 times for the heatmap of the dulaglutide model of pkdb_models) when the labels of the parameters or of the observables were wider than the first figure, which was as wide as the cells and a margin, and the layout was not applied. The figure is now made as large as its labels before the first layout, measured on them, and an observable which is wider than its cell is rotated before the first layout. Every heatmap changes slightly, also one with short labels: the first figure starts wider and the sizing converges to a size which differs in the last digits (a cell of 0.9009 instead of 0.8998 inches), which shifts the drawing by a pixel or its antialiasing. - The start values of a fit are the values of the model as loaded (#315).
OptimizationProblemread the start values of the fit parameters (xmodel) from the current state of the first model, and the references of a design (sampling.references, throughparameters_ofor a design with a simulation) left that model initialized with the pre-initialization of their simulation, soxmodelwas a value of a simulation of an experiment and not of the model; which model was first depended on the hash seed. In the midazolam model of pkdb_modelsxmodelofGU__MIDOH_f,0.41166in the model, was15.0or0.05depending onPYTHONHASHSEED, and the cost of the fitALLatxmodel32143.7or512492.3instead of41640.5(0.8.5 to 0.9.4); it is now0.41166and41640.5for every hash seed.xmodelis now read from the model as loaded (RoadrunnerSBMLModel.loaded_value), so are the values of the model which the derived changes of a hybridization read (model_values), and the references reset the model after they read it, also when the initialization fails (RoadrunnerSBMLModel.reset), so they leave no value of the pre-initialization behind. A reset without a simulation, i.e. the next initialization after the references, after the load or after a failed initialization, set back the values the initialization replaced, so an amount it read under a changed volume was set back under the volume as loaded and was off in the last bits (the mechanism of #318): after references which set a volume fraction and the concentration of a species in that volume a simulation differed from the simulation on a fresh model in about one of ten cases. It now sets every value which differs from the model as loaded back, as the reset after a simulation does, and the model is the model as loaded, bitwise. - The processes of a pool end with the process which started it (#316). When the main process of a pool died (a signal, a crash, a killed command), its workers waited for their next task forever, reparented to init, with the memory of their models (about 300 MB per worker for a PBPK model, 3.6 GB for the 20 workers of a sensitivity analysis of the midazolam model of pkdb_models), and kept the forkserver and the resource tracker of multiprocessing alive, until they were killed by hand. A worker now waits in a thread for the sentinel of its parent, which every start method provides, and ends when the parent ended, an idle worker at once and a busy one when the call of roadrunner it is in returns (roadrunner holds the GIL while it integrates); the forkserver and the resource tracker end with the last process which uses them. A second after
SIGKILLorSIGABRTof the main process no process of an idle pool is left (fork,forkserver,spawn). Underforka child which the main process forked outside sbmlsim inherits the pipe the workers watch and keeps them alive as long as it lives. - The figures of sbmlsim do not depend on the backend of pyplot (#317). With an interactive backend of matplotlib (TkAgg, the default when
DISPLAYis set), a figure ofpyplotholds objects of Tk, which the garbage collector releases in whatever thread it runs, e.g. a thread of a pool: Tk then aborts the process (RuntimeError: main thread is not in main loop,Tcl_AsyncDelete: async handler deleted by the wrong thread, exit 132). The figures of the report of a fit, of the profile likelihood (plot_profiles,plot_profile) and ofDataSetsComparison.plot_diffwere drawn withpyplot; they are now amatplotlib.figure.Figure, as the figures of the experiments and of the sensitivity analysis already were, and the report sets its matplotlib parameters withrc_context, which does not resolve the backend. A returned figure is not held bypyplot, a caller who shows it attaches it to a manager, asSimulationExperiment.show_mpl_figuresdoes. The abort which #317 reports, a sensitivity analysis of the midazolam model of pkdb_models, came from the figures of the sampling analysis of pkpdsim, which pkpdsim 0.5.4 draws on aFigureas well (matthiaskoenig/pkpdsim#37); sbmlsim 0.9.5 makes its own figures independent of the backend, and a run which aborts no longer leaves its pool behind (#316). - A point of a scan does not depend on the point before it (#318). roadrunner's reset computes the initial amount of a concentration species from the volume of its compartment with the parameters of the simulation before, and roadrunner reads and writes the amount of such a species through the current volume of its compartment,
(amount / volume) * volume: an amount which the reset computed with the volume of the point before could read as the amount as loaded under the volume as loaded, so the restore of #303 did not see it, and differ from it in the last bits under the volume of the next point. In the acetaminophen model of pkdb_models, whose volume of the blood of the rest of the body is an assignment rule of the volume fractions of the organs, a point withFVpa = 0.005after one withFVpa = 0.015differed from the point alone by1.278e-7(0.8.5 to 0.9.4), and a point of a pool depended on what its worker ran before. The parameters and the compartments as loaded are now set back before the reset as well, so that it computes every initial value as at the load; the results of a point after another one change in the last bits, the first point of a model does not change.