diff --git a/README.md b/README.md index 48ad16e..85729fd 100644 --- a/README.md +++ b/README.md @@ -6,49 +6,55 @@ Contributions to the collection are very welcome. Please see [CONTRIBUTING.md](C ## Overview -The following table summarizes the problems in the collection. -The full set of PEtab files are available in the respective subfolders. -A sortable version of the table is available -[here](https://benchmarking-initiative.github.io/Benchmark-Models-PEtab/). +The collection spans a wide variety of problems, in terms of application +as well as the numbers of observables, parameters, measurements, and +experimental conditions, and features such as events, preequilibration, +and noise and prior distributions. +For these details, see the sortable and filterable +[overview tables](https://benchmarking-initiative.github.io/Benchmark-Models-PEtab/). + +The following table lists the problems in the collection and the +available encodings in PEtab v1 and v2. +The PEtab files are available in the respective subdirectories. -| PEtab Problem ID | Conditions | Estimated Parameters | Events | Possible Discontinuities | Preequilibration | Postequilibration | Measurements | Observables | Noise distribution(s) | Objective prior distribution(s) | Species | References | SBML4Humans | -|:----------------------------------------------------------------------------------------|-------------:|-----------------------:|---------:|:---------------------------|-------------------:|--------------------:|---------------:|--------------:|:------------------------|:----------------------------------|----------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [Alkan_SciSignal2018](problems/Alkan_SciSignal2018/v1/) | 73 | 44 | 0 | ✓ | 0 | 0 | 1733 | 12 | normal | | 36 | [\[1\]](http://identifiers.org/doi/10.1126/scisignal.aat0229) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Alkan_SciSignal2018/v1/model.xml) | -| [Armistead_CellDeathDis2024](problems/Armistead_CellDeathDis2024/v1/) | 2 | 14 | 0 | | 0 | 0 | 58 | 4 | normal | | 4 | [\[1\]](https://doi.org/10.1038/s41419-024-07134-2) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Armistead_CellDeathDis2024/v1/model.xml) | -| [Bachmann_MSB2011](problems/Bachmann_MSB2011/v1/) | 36 | 113 | 0 | | 0 | 0 | 541 | 20 | normal; log10-normal | parameterScaleNormal | 25 | [\[1\]](http://identifiers.org/doi/10.1038/msb.2011.50) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Bachmann_MSB2011/v1/model.xml) | -| [Beer_MolBioSystems2014](problems/Beer_MolBioSystems2014/v1/) | 19 | 72 | 0 | ✓ | 0 | 0 | 27132 | 2 | normal | | 4 | [\[1\]](http://identifiers.org/doi/10.1039/c3mb70594c) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Beer_MolBioSystems2014/v1/model.xml) | -| [Bertozzi_PNAS2020](problems/Bertozzi_PNAS2020/v1/) | 2 | 8 | 0 | | 0 | 0 | 22 | 2 | normal | | 3 | [\[1\]](http://identifiers.org/doi/10.1073/pnas.2006520117) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Bertozzi_PNAS2020/v1/model.xml) | -| [Blasi_CellSystems2016](problems/Blasi_CellSystems2016/v1/) | 1 | 9 | 0 | | 0 | 1 | 252 | 15 | log-normal | | 16 | [\[1\]](http://identifiers.org/doi/10.1016/j.cels.2016.01.002) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Blasi_CellSystems2016/v1/model.xml) | -| [Boehm_JProteomeRes2014](problems/Boehm_JProteomeRes2014/v1/) | 1 | 9 | 0 | | 0 | 0 | 48 | 3 | normal | | 8 | [\[1\]](http://identifiers.org/doi/10.1021/pr5006923) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Boehm_JProteomeRes2014/v1/model.xml) | -| [Borghans_BiophysChem1997](problems/Borghans_BiophysChem1997/v1/) | 1 | 23 | 0 | | 0 | 0 | 111 | 1 | log10-normal | | 3 | [\[1\]](http://identifiers.org/doi/10.1016/s0301-4622(97)00010-0) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Borghans_BiophysChem1997/v1/model.xml) | -| [Brannmark_JBC2010](problems/Brannmark_JBC2010/v1/) | 8 | 22 | 0 | ✓ | 1 | 0 | 43 | 3 | normal | | 9 | [\[1\]](http://identifiers.org/doi/10.1074/jbc.M110.106849) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Brannmark_JBC2010/v1/model.xml) | -| [Bruno_JExpBot2016](problems/Bruno_JExpBot2016/v1/) | 6 | 13 | 0 | | 0 | 0 | 77 | 5 | normal | | 7 | [\[1\]](http://identifiers.org/doi/10.1093/jxb/erw356) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Bruno_JExpBot2016/v1/model.xml) | -| [Chen_MSB2009](problems/Chen_MSB2009/v1/) | 4 | 155 | 0 | ✓ | 0 | 0 | 120 | 3 | normal | | 500 | [\[1\]](http://identifiers.org/doi/10.1038/msb.2008.74) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Chen_MSB2009/v1/model.xml) | -| [Crauste_CellSystems2017](problems/Crauste_CellSystems2017/v1/) | 1 | 12 | 0 | | 0 | 0 | 21 | 4 | normal | | 5 | [\[1\]](http://identifiers.org/doi/10.1016/j.cels.2017.01.014) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Crauste_CellSystems2017/v1/model.xml) | -| [Elowitz_Nature2000](problems/Elowitz_Nature2000/v1/) | 1 | 21 | 0 | | 0 | 0 | 58 | 1 | log10-normal | | 8 | [\[1\]](http://identifiers.org/doi/10.1038/35002125) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Elowitz_Nature2000/v1/model.xml) | -| [Fiedler_BMCSystBiol2016](problems/Fiedler_BMCSystBiol2016/v1/) | 3 | 22 | 0 | | 0 | 0 | 72 | 2 | normal | | 6 | [\[1\]](http://identifiers.org/doi/10.1186/s12918-016-0319-7) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Fiedler_BMCSystBiol2016/v1/model.xml) | -| [Froehlich_CellSystems2018](problems/Froehlich_CellSystems2018/v1/) | 9169 | 4231 | 0 | | 0 | 9169 | 9169 | 1 | normal | | 1396 | [\[1\]](http://identifiers.org/doi/10.1016/j.cels.2018.10.013) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Froehlich_CellSystems2018/v1/model.xml) | -| [Fujita_SciSignal2010](problems/Fujita_SciSignal2010/v1/) | 6 | 19 | 0 | ✓ | 0 | 0 | 144 | 3 | normal | | 9 | [\[1\]](http://identifiers.org/doi/10.1126/scisignal.2000810) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Fujita_SciSignal2010/v1/model.xml) | -| [Giordano_Nature2020](problems/Giordano_Nature2020/v1/) | 1 | 50 | 0 | ✓ | 0 | 0 | 313 | 7 | normal | | 13 | [\[1\]](http://identifiers.org/pubmed/32322102) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Giordano_Nature2020/v1/model.xml) | -| [Isensee_JCB2018](problems/Isensee_JCB2018/v1/) | 123 | 46 | 0 | ✓ | 1 | 0 | 687 | 3 | normal | parameterScaleNormal | 25 | [\[1\]](http://identifiers.org/doi/10.1083/jcb.201708053) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Isensee_JCB2018/v1/model.xml) | -| [Lang_PLOSComputBiol2024](problems/Lang_PLOSComputBiol2024/v1/) | 1 | 294 | 0 | | 0 | 0 | 9600 | 16 | normal | laplace; uniform | 124 | [\[1\]](http://identifiers.org/doi/10.1371/journal.pcbi.1011151) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Lang_PLOSComputBiol2024/v1/model.xml) | -| [Laske_PLOSComputBiol2019](problems/Laske_PLOSComputBiol2019/v1/) | 3 | 13 | 0 | | 0 | 0 | 42 | 13 | normal; log-normal | | 41 | [\[1\]](http://identifiers.org/biomodels.db/BIOMD0000000463) [\[2\]](http://identifiers.org/biomodels.db/MODEL1307270000) [\[3\]](http://identifiers.org/doi/10.1371/journal.pcbi.1006944) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Laske_PLOSComputBiol2019/v1/model.xml) | -| [Liu_IFACPapersOnLine2025](problems/Liu_IFACPapersOnLine2025/v1/) | 2 | 9 | 1 | ✓ | 0 | 0 | 100 | 1 | normal | | 0 | [\[1\]](http://identifiers.org/doi/10.1016/j.ifacol.2025.07.160) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Liu_IFACPapersOnLine2025/v1/model.xml) | -| [Lucarelli_CellSystems2018](problems/Lucarelli_CellSystems2018/v1/) | 16 | 84 | 0 | | 0 | 0 | 1755 | 65 | normal; log10-normal | | 33 | [\[1\]](http://identifiers.org/doi/10.1016/j.cels.2017.11.010) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Lucarelli_CellSystems2018/v1/model.xml) | -| [Okuonghae_ChaosSolitonsFractals2020](problems/Okuonghae_ChaosSolitonsFractals2020/v1/) | 1 | 16 | 0 | | 0 | 0 | 92 | 2 | normal | | 9 | [\[1\]](http://identifiers.org/doi/10.1016/j.chaos.2020.110032) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Okuonghae_ChaosSolitonsFractals2020/v1/model.xml) | -| [Oliveira_NatCommun2021](problems/Oliveira_NatCommun2021/v1/) | 1 | 12 | 0 | ✓ | 0 | 0 | 120 | 2 | normal | | 9 | [\[1\]](http://identifiers.org/doi/10.1038/s41467-020-19798-3) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Oliveira_NatCommun2021/v1/model.xml) | -| [Perelson_Science1996](problems/Perelson_Science1996/v1/) | 1 | 3 | 0 | | 0 | 0 | 16 | 1 | log10-normal | | 4 | [\[1\]](http://identifiers.org/doi/10.1126/science.271.5255.1582) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Perelson_Science1996/v1/model.xml) | -| [Rahman_MBS2016](problems/Rahman_MBS2016/v1/) | 1 | 9 | 0 | | 0 | 0 | 23 | 1 | normal | | 7 | [\[1\]](http://identifiers.org/doi/10.1016/j.mbs.2016.07.009) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Rahman_MBS2016/v1/model.xml) | -| [Raia_CancerResearch2011](problems/Raia_CancerResearch2011/v1/) | 4 | 39 | 0 | | 0 | 0 | 205 | 8 | normal | | 14 | [\[1\]](http://identifiers.org/doi/10.1158/0008-5472.CAN-10-2987) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Raia_CancerResearch2011/v1/model.xml) | -| [Raimundez_PCB2020](problems/Raimundez_PCB2020/v1/) | 170 | 136 | 0 | ✓ | 4 | 0 | 627 | 79 | normal | parameterScaleNormal; laplace | 22 | [\[1\]](http://identifiers.org/doi/10.1371/journal.pcbi.1007147) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Raimundez_PCB2020/v1/model.xml) | -| [SalazarCavazos_MBoC2020](problems/SalazarCavazos_MBoC2020/v1/) | 4 | 6 | 0 | | 0 | 0 | 18 | 3 | normal | | 75 | [\[1\]](http://identifiers.org/doi/10.1091/mbc.E19-09-0548) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/SalazarCavazos_MBoC2020/v1/model.xml) | -| [Schwen_PONE2014](problems/Schwen_PONE2014/v1/) | 19 | 30 | 0 | | 0 | 0 | 286 | 4 | log10-normal | parameterScaleNormal | 11 | [\[1\]](http://identifiers.org/doi/10.1371/journal.pone.0133653) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Schwen_PONE2014/v1/model.xml) | -| [Smith_BMCSystBiol2013](problems/Smith_BMCSystBiol2013/v1/) | 35 | 25 | 3 | ✓ | 0 | 0 | 62 | 9 | normal | | 133 | [\[1\]](http://identifiers.org/doi/10.1186/1752-0509-7-41) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Smith_BMCSystBiol2013/v1/model.xml) | -| [Sneyd_PNAS2002](problems/Sneyd_PNAS2002/v1/) | 9 | 15 | 0 | | 0 | 0 | 135 | 1 | normal | | 6 | [\[1\]](http://identifiers.org/doi/10.1073/pnas.032281999) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Sneyd_PNAS2002/v1/model.xml) | -| [Weber_BMC2015](problems/Weber_BMC2015/v1/) | 2 | 36 | 0 | ✓ | 1 | 0 | 135 | 8 | normal | | 7 | [\[1\]](http://identifiers.org/doi/10.1186/s12918-015-0147-1) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Weber_BMC2015/v1/model.xml) | -| [Zhao_QuantBiol2020](problems/Zhao_QuantBiol2020/v1/) | 7 | 28 | 0 | | 0 | 0 | 82 | 1 | normal | | 5 | [\[1\]](http://identifiers.org/pubmed/32219006) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Zhao_QuantBiol2020/v1/model.xml) | -| [Zheng_PNAS2012](problems/Zheng_PNAS2012/v1/) | 1 | 46 | 0 | | 1 | 0 | 60 | 15 | normal | | 15 | [\[1\]](http://identifiers.org/doi/10.1073/pnas.1205707109) | [\[1\]](https://sbml4humans.de/report?url=https://raw.githubusercontent.com/Benchmarking-Initiative/Benchmark-Models-PEtab/master/problems/Zheng_PNAS2012/v1/model.xml) | +| ID | PEtab v1 | PEtab v2 | Description | +|:-------------------------------------------------------------------------------------|:-------------------------------------------------------|:------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [Alkan_SciSignal2018](problems/Alkan_SciSignal2018/) | [v1](problems/Alkan_SciSignal2018/v1/) | | Reaction network model of the DNA damage response and cell fate in cancer cells treated with chemotherapeutics, fitted to signaling and cell count data. | +| [Armistead_CellDeathDis2024](problems/Armistead_CellDeathDis2024/) | [v1](problems/Armistead_CellDeathDis2024/v1/) | | Small reaction network model of sphingolipid metabolism with ceramide feedback, fitted to lipidomics data of wild-type and mutant zebrafish embryos. | +| [Bachmann_MSB2011](problems/Bachmann_MSB2011/) | [v1](problems/Bachmann_MSB2011/v1/) | | Reaction network model of Epo-induced JAK2/STAT5 signaling with two negative feedback regulators (SOCS3, CIS) in murine erythroid progenitor cells. | +| [Beer_MolBioSystems2014](problems/Beer_MolBioSystems2014/) | [v1](problems/Beer_MolBioSystems2014/v1/) | | Small kinetic model of bacterial growth and production of the blue pigment indigoidine by engineered variants of a non-ribosomal peptide synthetase. | +| [Bertozzi_PNAS2020](problems/Bertozzi_PNAS2020/) | [v1](problems/Bertozzi_PNAS2020/v1/) | | SIR compartmental epidemiological model of the early COVID-19 outbreak in New York and California. | +| [Blasi_CellSystems2016](problems/Blasi_CellSystems2016/) | [v1](problems/Blasi_CellSystems2016/v1/) | | Reaction network model of combinatorial histone H4 acetylation states generated by motif-specific acetylation reactions. | +| [Boehm_JProteomeRes2014](problems/Boehm_JProteomeRes2014/) | [v1](problems/Boehm_JProteomeRes2014/v1/) | [v2](problems/Boehm_JProteomeRes2014/v2/) | Reaction network model of Epo-induced STAT5A/STAT5B phosphorylation and homo- and heterodimerization, fitted to quantitative mass spectrometry data. | +| [Borghans_BiophysChem1997](problems/Borghans_BiophysChem1997/) | [v1](problems/Borghans_BiophysChem1997/v1/) | | Three-variable ODE model of complex calcium oscillations with Ca2+-activated IP3 degradation, fitted to measured calcium bursting in a single hepatocyte. | +| [Brannmark_JBC2010](problems/Brannmark_JBC2010/) | [v1](problems/Brannmark_JBC2010/v1/) | | Reaction network model of insulin receptor and IRS1 phosphorylation with receptor endocytosis and feedback in primary human adipocytes. | +| [Bruno_JExpBot2016](problems/Bruno_JExpBot2016/) | [v1](problems/Bruno_JExpBot2016/v1/) | | Enzyme kinetic model of the in vitro cleavage of carotenoids and apocarotenoids by Arabidopsis carotenoid cleavage dioxygenase 4 (CCD4). | +| [Chen_MSB2009](problems/Chen_MSB2009/) | [v1](problems/Chen_MSB2009/v1/) | | Large mass-action reaction network model of ErbB receptor signaling to ERK and AKT in response to EGF and heregulin. | +| [Crauste_CellSystems2017](problems/Crauste_CellSystems2017/) | [v1](problems/Crauste_CellSystems2017/v1/) | | Cell population model of CD8 T cell differentiation (naive, early/late effector, memory), fitted to cell counts in mice after vaccinia virus infection. | +| [Elowitz_Nature2000](problems/Elowitz_Nature2000/) | [v1](problems/Elowitz_Nature2000/v1/) | | Gene regulatory network model of the repressilator, a synthetic three-gene transcriptional oscillator in E. coli, fitted to a fluorescence time course. | +| [Fiedler_BMCSystBiol2016](problems/Fiedler_BMCSystBiol2016/) | [v1](problems/Fiedler_BMCSystBiol2016/v1/) | | Reaction network model of RAF/MEK/ERK signaling with ERK feedback in HeLa cells, fitted to phospho-MEK/ERK data without and with RAF or MEK inhibitors. | +| [Froehlich_CellSystems2018](problems/Froehlich_CellSystems2018/) | [v1](problems/Froehlich_CellSystems2018/v1/) | | Large-scale reaction network model of cancer signaling pathways, parameterized by cell-line-specific gene expression and fitted to drug response viability data. | +| [Fujita_SciSignal2010](problems/Fujita_SciSignal2010/) | [v1](problems/Fujita_SciSignal2010/v1/) | | Reaction network model of EGF-induced EGFR, Akt and S6 phosphorylation in PC12 cells under different EGF stimulation profiles. | +| [Giordano_Nature2020](problems/Giordano_Nature2020/) | [v1](problems/Giordano_Nature2020/v1/) | | Compartmental epidemiological model (SIDARTHE, distinguishing detected/undetected and symptom severity) of the COVID-19 epidemic in Italy. | +| [Isensee_JCB2018](problems/Isensee_JCB2018/) | [v1](problems/Isensee_JCB2018/v1/) | | Reaction network model of cAMP-induced PKA activation and phosphorylation of its regulatory subunit in rat sensory neurons under pharmacological perturbations. | +| [Lang_PLOSComputBiol2024](problems/Lang_PLOSComputBiol2024/) | [v1](problems/Lang_PLOSComputBiol2024/v1/) | | Reaction network model of the human cell cycle, generated from a rule-based model and fitted to compartment-resolved single-cell imaging data of RPE-1 cells. | +| [Laske_PLOSComputBiol2019](problems/Laske_PLOSComputBiol2019/) | [v1](problems/Laske_PLOSComputBiol2019/v1/) | | Reaction network model of intracellular influenza A virus replication, fitted to viral RNA, genome import and virus release data in human A549 cells. | +| [Liu_IFACPapersOnLine2025](problems/Liu_IFACPapersOnLine2025/) | [v1](problems/Liu_IFACPapersOnLine2025/v1/) | | Population model of oncolytic virus therapy with uninfected and infected tumor cells and virus, fitted to tumor size data in zebrafish. | +| [Lucarelli_CellSystems2018](problems/Lucarelli_CellSystems2018/) | [v1](problems/Lucarelli_CellSystems2018/v1/) | | Reaction network model of TGF-beta-induced SMAD complex formation and target gene expression. | +| [Okuonghae_ChaosSolitonsFractals2020](problems/Okuonghae_ChaosSolitonsFractals2020/) | [v1](problems/Okuonghae_ChaosSolitonsFractals2020/v1/) | | Compartmental epidemiological model of COVID-19 in Lagos, Nigeria, including asymptomatic and detected infections. | +| [Oliveira_NatCommun2021](problems/Oliveira_NatCommun2021/) | [v1](problems/Oliveira_NatCommun2021/v1/) | | Compartmental epidemiological model of COVID-19 in Bahia, Brazil (state excluding the capital Salvador), with non-detected cases, hospitalization and deaths. | +| [Perelson_Science1996](problems/Perelson_Science1996/) | [v1](problems/Perelson_Science1996/v1/) | | Within-host model of HIV-1 viral dynamics, fitted to plasma HIV-1 RNA levels after treatment with the protease inhibitor ritonavir. | +| [Rahman_MBS2016](problems/Rahman_MBS2016/) | [v1](problems/Rahman_MBS2016/v1/) | | Compartmental epidemiological model of HIV transmission with CD4+ T cell count-based infection stages and treatment, fitted to HIV prevalence in South Africa. | +| [Raia_CancerResearch2011](problems/Raia_CancerResearch2011/) | [v1](problems/Raia_CancerResearch2011/v1/) | | Reaction network model of IL-13-induced JAK2/STAT5 signaling in the primary mediastinal B-cell lymphoma cell line MedB-1. | +| [Raimundez_PCB2020](problems/Raimundez_PCB2020/) | [v1](problems/Raimundez_PCB2020/v1/) | | Reaction network model of EGFR and MET signaling to ERK and AKT in two gastric cancer cell lines in response to EGF and the anti-EGFR antibody cetuximab. | +| [SalazarCavazos_MBoC2020](problems/SalazarCavazos_MBoC2020/) | [v1](problems/SalazarCavazos_MBoC2020/v1/) | | Reaction network model of EGFR dimerization, multisite phosphorylation and binding of the adaptor proteins GRB2 and SHC1, generated from a rule-based model. | +| [Schwen_PONE2014](problems/Schwen_PONE2014/) | [v1](problems/Schwen_PONE2014/v1/) | [v2](problems/Schwen_PONE2014/v2/) | Reaction network model of insulin binding, internalization and degradation in two subpopulations of isolated hepatocytes with low and high insulin binding. | +| [Smith_BMCSystBiol2013](problems/Smith_BMCSystBiol2013/) | [v1](problems/Smith_BMCSystBiol2013/v1/) | | Large reaction network model of insulin signaling in rodent adipocytes and its regulation by oxidative stress. | +| [Sneyd_PNAS2002](problems/Sneyd_PNAS2002/) | [v1](problems/Sneyd_PNAS2002/v1/) | | Kinetic model of the type-2 IP3 receptor, fitted to time courses of calcium release from liver microsomes at varying IP3 and calcium concentrations. | +| [Weber_BMC2015](problems/Weber_BMC2015/) | [v1](problems/Weber_BMC2015/v1/) | | Reaction network model of interactions of protein kinase D, a lipid kinase (PI4KIIIbeta) and a ceramide transfer protein (CERT) at the trans-Golgi network. | +| [Zhao_QuantBiol2020](problems/Zhao_QuantBiol2020/) | [v1](problems/Zhao_QuantBiol2020/v1/) | | Compartmental epidemiological model of COVID-19 in Wuhan, Hubei and the rest of China, with quarantined and unquarantined infected individuals. | +| [Zheng_PNAS2012](problems/Zheng_PNAS2012/) | [v1](problems/Zheng_PNAS2012/v1/) | | Reaction network model of the combinatorial methylation of lysines 27 and 36 on histone H3. | ## License diff --git a/src/python/MANIFEST.in b/src/python/MANIFEST.in index 2d06440..945aae1 100644 --- a/src/python/MANIFEST.in +++ b/src/python/MANIFEST.in @@ -1,2 +1,3 @@ recursive-include benchmark_models_petab/problems * include benchmark_models_petab/schemas/*.json +include benchmark_models_petab/_site/templates/*.j2 diff --git a/src/python/benchmark_models_petab/_site/__init__.py b/src/python/benchmark_models_petab/_site/__init__.py new file mode 100644 index 0000000..f28af58 --- /dev/null +++ b/src/python/benchmark_models_petab/_site/__init__.py @@ -0,0 +1,3 @@ +"""Generation of the GitHub Pages site.""" + +from .build import create_overview_page diff --git a/src/python/benchmark_models_petab/_site/build.py b/src/python/benchmark_models_petab/_site/build.py new file mode 100644 index 0000000..07f8dc0 --- /dev/null +++ b/src/python/benchmark_models_petab/_site/build.py @@ -0,0 +1,204 @@ +"""Build the overview page of the GitHub Pages site.""" + +import json +from pathlib import Path + +import pandas as pd + +from .. import overview, overview_v2, v1, v2 +from ..C import MODELS +from ..C import ( + GITHUB_DEFAULT_BRANCH, + GITHUB_REPO, + PROBLEMS_DIRNAME, + V1_DIRNAME, + V2_DIRNAME, +) +from ..metadata import get_metadata + +REPO_URL = f"https://github.com/{GITHUB_REPO}" +LOGO_URL = ( + f"https://raw.githubusercontent.com/{GITHUB_REPO}" + f"/{GITHUB_DEFAULT_BRANCH}/gfx/logo.svg" +) +CONTRIBUTING_URL = f"{REPO_URL}/blob/{GITHUB_DEFAULT_BRANCH}/CONTRIBUTING.md" + +#: Rendering of the columns that are not plain text, by column name. +#: Other columns of integer dtype are rendered as numbers. +column_kinds = { + "petab_problem_id": "id", + "short_description": "description", + "possible_discontinuities": "bool", + "noise_distributions": "tags", + "objective_prior_distributions": "tags", + "prior_distributions": "tags", + "reference_uris": "links", + "sbml4humans_urls": "links", +} + +#: Column groups, in display order, with their columns. +#: Columns without a group are shown first. +column_groups = { + "Links": ["reference_uris", "sbml4humans_urls"], + "Size": [ + "conditions", + "experiments", + "estimated_parameters", + "measurements", + "observables", + "species", + ], + "Features": [ + "events", + "preequilibration", + "postequilibration", + "possible_discontinuities", + ], + "Distributions": [ + "noise_distributions", + "objective_prior_distributions", + "prior_distributions", + ], +} + +column_group = { + col: group for group, cols in column_groups.items() for col in cols +} + +#: Statistics shown as summary tiles, as (v1 column, v2 column, label). +summary_columns = [ + ("conditions", "experiments", "simulation conditions"), + ("estimated_parameters", "estimated_parameters", "estimated parameters"), + ("measurements", "measurements", "measurements"), + ("observables", "observables", "observables"), +] + + +def get_table( + df: pd.DataFrame, + labels: dict[str, str], + descriptions: dict[str, str], + version_dirname: str, +) -> dict: + """Get the data and column specification for one overview table. + + :param df: Overview table as returned by `get_overview_df`. + :param labels: Column labels, by column name. + :param descriptions: Column descriptions, by column name. + :param version_dirname: The encoding's directory (`v1`, `v2`). + """ + df = df.reset_index() + df.insert( + 1, + "short_description", + [get_metadata(id_).short_description for id_ in df.petab_problem_id], + ) + labels = {"short_description": "Description"} | labels + descriptions = { + "short_description": ( + "Model type, biological system, and, where informative, the data." + ) + } | descriptions + # ungrouped columns first, in their original order, then the groups + ungrouped = [col for col in df.columns if col not in column_group] + ordered_columns = ungrouped + [ + col for cols in column_groups.values() for col in cols if col in df + ] + columns = [ + { + "field": col, + "title": labels.get(col, col), + "tooltip": descriptions.get(col), + "group": column_group.get(col), + "kind": column_kinds.get( + col, + "int" if pd.api.types.is_integer_dtype(df[col]) else "text", + ), + } + for col in ordered_columns + ] + return { + "columns": columns, + "rows": json.loads(df[ordered_columns].to_json(orient="records")), + "tree_url": ( + f"{REPO_URL}/tree/{GITHUB_DEFAULT_BRANCH}/{PROBLEMS_DIRNAME}" + f"/{{id}}/{version_dirname}" + ), + } + + +def get_summary_tiles(v1_df: pd.DataFrame, v2_df: pd.DataFrame) -> list: + """Get the summary statistics shown above the tables. + + The ranges are computed from the v1 statistics, and from the v2 + statistics for problems without a v1 encoding. + + :param v1_df: v1 overview table. + :param v2_df: v2 overview table. + :return: List of ``(value, label, tooltip)`` tuples. + """ + tiles = [ + ( + f"{len(MODELS)}", + "problems in total", + "Number of benchmark problems, in any PEtab version.", + ), + ( + f"{len(v1.MODELS)}", + "in PEtab v1", + "Number of problems with a PEtab v1 encoding.", + ), + ( + f"{len(v2.MODELS)}", + "in PEtab v2", + "Number of problems with a PEtab v2 encoding.", + ), + ] + v2_only = ~v2_df.index.isin(v1_df.index) + for v1_col, v2_col, label in summary_columns: + values = pd.concat([v1_df[v1_col], v2_df.loc[v2_only, v2_col]]) + tooltip = f"Smallest and largest number of {label} per problem." + if v1_col != v2_col: + tooltip += ( + f" For problems without a PEtab v1 encoding, the number of " + f"{overview_v2.columns[v2_col].lower()}." + ) + tiles.append((f"{values.min():,}–{values.max():,}", label, tooltip)) + return tiles + + +def create_overview_page(dest: Path) -> None: + """Create the HTML page with the v1 and v2 overview tables. + + :param dest: Path to the output HTML file. + """ + from jinja2 import Environment, PackageLoader, select_autoescape + + env = Environment( + loader=PackageLoader(__package__), + autoescape=select_autoescape(["html", "j2"]), + ) + v1_df = overview.get_overview_df() + v2_df = overview_v2.get_overview_df() + html = env.get_template("index.html.j2").render( + repo_url=REPO_URL, + logo_url=LOGO_URL, + contributing_url=CONTRIBUTING_URL, + summary_tiles=get_summary_tiles(v1_df, v2_df), + tables={ + "v2": get_table( + v2_df, + overview_v2.columns, + overview_v2.column_descriptions, + V2_DIRNAME, + ), + "v1": get_table( + v1_df, + overview.columns, + overview.column_descriptions, + V1_DIRNAME, + ), + }, + ) + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_text(html, encoding="utf-8") diff --git a/src/python/benchmark_models_petab/_site/templates/index.html.j2 b/src/python/benchmark_models_petab/_site/templates/index.html.j2 new file mode 100644 index 0000000..278ddd8 --- /dev/null +++ b/src/python/benchmark_models_petab/_site/templates/index.html.j2 @@ -0,0 +1,584 @@ + + + + + +Benchmark Problems + + + + + + +
+
+
+
+ PEtab Benchmark Collection logo +
+

Benchmark Problems

+

+ Parameter estimation problems with published models and experimental + data in the PEtab format, + from the Benchmark-Models-PEtab + collection. +

+
+
+ +
+
+{%- for value, label, tooltip in summary_tiles %} +
+
{{ value }}
+
{{ label }}
+
+{%- endfor %} +
+
+
+
+

+ Click a column header to sort; hover over it for a definition. Filter with + the fields below the headers; numeric columns accept, e.g., 10, + >10, <=5, or 10-100. + Click a table and press Ctrl+C to copy its visible + rows, including the column headers. +

+ + +

PEtab v2

+
+

+ +
+
+ +

PEtab v1

+
+

+ +
+
+
+ + + + + + diff --git a/src/python/benchmark_models_petab/overview.py b/src/python/benchmark_models_petab/overview.py index b146975..f1fb4d6 100644 --- a/src/python/benchmark_models_petab/overview.py +++ b/src/python/benchmark_models_petab/overview.py @@ -11,23 +11,29 @@ from petab.v1.yaml import load_yaml from sbmlmath import sbml_math_to_sympy from sbmlmath.csymbol import TimeSymbol -from . import MODELS, get_problem, get_problem_yaml_path -from .C import GITHUB_DEFAULT_BRANCH, GITHUB_REPO, PROBLEMS_DIRNAME, V1_DIRNAME +from . import MODELS, get_problem, get_problem_yaml_path, v1, v2 +from .C import ( + GITHUB_DEFAULT_BRANCH, + GITHUB_REPO, + PROBLEMS_DIRNAME, + V1_DIRNAME, + V2_DIRNAME, +) +from .metadata import get_metadata import sympy as sp from sympy.core.relational import Relational -REPO_URL = f"https://github.com/{GITHUB_REPO}/" - readme_md = Path(__file__).resolve().parent.parent / "README.md" -markdown_columns = { +#: Column labels for the overview table. +columns = { "conditions": "Conditions", "estimated_parameters": "Estimated Parameters", "events": "Events", "preequilibration": "Preequilibration", "postequilibration": "Postequilibration", "measurements": "Measurements", - "petab_problem_id": "PEtab Problem ID", + "petab_problem_id": "ID", "observables": "Observables", "species": "Species", "noise_distributions": "Noise distribution(s)", @@ -37,6 +43,42 @@ "possible_discontinuities": "Possible Discontinuities", } +#: Column descriptions for the overview table. +#: Shown as HTML on the site, so they must not contain markup characters. +column_descriptions = { + "petab_problem_id": ( + "Problem ID: first author, journal, and year of the publication." + ), + "conditions": ( + "Number of simulation conditions (combinations of preequilibration " + "and simulation condition) in the measurement table." + ), + "estimated_parameters": "Number of estimated parameters.", + "events": "Number of SBML events.", + "preequilibration": "Number of distinct preequilibration conditions.", + "postequilibration": ( + "Number of simulation conditions with steady-state measurements " + "(time = inf)." + ), + "measurements": "Number of measurements (data points).", + "observables": "Number of observables with measurements.", + "species": "Number of species in the SBML model.", + "noise_distributions": ( + "Noise distributions, including the observable transformation." + ), + "objective_prior_distributions": ( + "Prior distributions of estimated parameters that enter the " + "objective function." + ), + "reference_uris": "Publications, from the SBML model annotation.", + "sbml4humans_urls": "The SBML model rendered by SBML4Humans.", + "possible_discontinuities": ( + "Heuristic: events with assignments, or discontinuous functions " + "(piecewise, min, max, ...) with time-dependent arguments in the " + "model." + ), +} + index_column = "petab_problem_id" @@ -110,16 +152,29 @@ def get_sbml4humans_urls(petab_problem_id: str) -> List[str]: """Get URL to SBML4humans model""" yaml_file = get_problem_yaml_path(petab_problem_id) yaml_dict = load_yaml(yaml_file) + model_filenames = [ + model_filename + for problem_dict in yaml_dict[petab.PROBLEMS] + for model_filename in problem_dict[petab.SBML_FILES] + ] + return make_sbml4humans_urls(petab_problem_id, V1_DIRNAME, model_filenames) + + +def make_sbml4humans_urls( + petab_problem_id: str, version_dirname: str, model_filenames: list[str] +) -> list[str]: + """Get SBML4humans URLs for the given model files of a problem. + + :param petab_problem_id: Problem ID. + :param version_dirname: The encoding's directory (`v1`, `v2`). + :param model_filenames: Model file paths relative to that directory. + """ repo_root = f"https://raw.githubusercontent.com/{GITHUB_REPO}/{GITHUB_DEFAULT_BRANCH}" - urls = [] - for problem_dict in yaml_dict[petab.PROBLEMS]: - for model_filename in problem_dict[petab.SBML_FILES]: - gh_raw_url = ( - f"{repo_root}/{PROBLEMS_DIRNAME}/{petab_problem_id}" - f"/{V1_DIRNAME}/{model_filename}" - ) - urls.append(f"https://sbml4humans.de/report?url={gh_raw_url}") - return urls + return [ + f"https://sbml4humans.de/report?url={repo_root}/{PROBLEMS_DIRNAME}" + f"/{petab_problem_id}/{version_dirname}/{model_filename}" + for model_filename in model_filenames + ] def get_noise_distributions(observable_df): @@ -191,15 +246,21 @@ def guess_discontinuities(petab_problem: petab.Problem) -> bool: discontinuities might play a role in the simulation; the presence of discontinuities might be parameter-dependent; ... . """ - model: libsbml.Model = petab_problem.sbml_model + return sbml_model_has_discontinuities(petab_problem.sbml_model) + + +def sbml_model_has_discontinuities(model: libsbml.Model) -> bool: + """Guess whether the SBML model has discontinuities. + See :func:`guess_discontinuities`. + """ for event in model.getListOfEvents(): for ea in event.getListOfEventAssignments(): if ea.getMath(): return True # convert reactions to rate rules - sbml_doc = petab_problem.sbml_model.getSBMLDocument().clone() + sbml_doc = model.getSBMLDocument().clone() model = sbml_doc.getModel() conversion_config = libsbml.ConversionProperties() conversion_config.addOption("replaceReactions") @@ -271,7 +332,7 @@ def expr_maybe_time_dependent(expr: sp.Expr) -> bool: def get_overview_df() -> pd.DataFrame: """Get overview table with stats for all benchmark PEtab problems""" data = [] - for petab_problem_id in MODELS: + for petab_problem_id in v1.MODELS: petab_problem = get_problem(petab_problem_id) summary = get_summary(petab_problem, petab_problem_id) data.append(summary) @@ -284,153 +345,58 @@ def get_overview_df() -> pd.DataFrame: end_overview_table = "\n\n" -def show_overview_table( - markdown: bool = False, - update_readme: bool = False, -): - df = get_overview_df() - pd.options.display.width = 0 - - if markdown or update_readme: - # directory as markdown link - df.rename( - index=lambda x: f"[{x}]({PROBLEMS_DIRNAME}/{x}/{V1_DIRNAME}/)", - inplace=True, - ) - # references to markdown links - for field in "reference_uris", "sbml4humans_urls": - df[field] = df[field].apply( - lambda x: " ".join( - [f"[\\[{i + 1}\\]]({uri})" for i, uri in enumerate(x)] - ) - ) - df["possible_discontinuities"] = df["possible_discontinuities"].apply( - lambda x: "✓" if x else "" - ) - df.index.rename(markdown_columns[index_column], inplace=True) - df.rename(columns=markdown_columns, inplace=True) - markdown_overview = df.to_markdown() - if update_readme: - with open(readme_md, "r") as f: - readme_content = f.read() - before_table = readme_content.split(start_overview_table)[0] - after_table = readme_content.split(end_overview_table)[1] - new_readme_content = ( - before_table - + start_overview_table - + markdown_overview - + end_overview_table - + after_table - ) - with open(readme_md, "w") as f: - f.write(new_readme_content) - else: - print(markdown_overview) - else: - print(df) - - -def create_html_table(dest: Path) -> None: - """Create HTML table with stats for all benchmark PEtab problems. +def get_readme_table() -> str: + """Get the version-independent overview table for the README. - :param dest: Path to the output HTML file. + Lists each problem's short description and links to its PEtab v1 and + v2 encodings, where available. """ - from bokeh.io import output_file, save - from bokeh.layouts import column - from bokeh.models import ( - ColumnDataSource, - DataTable, - TableColumn, - Div, - StringFormatter, - NumberFormatter, - HTMLTemplateFormatter, - InlineStyleSheet, - ) - - dest.parent.mkdir(parents=True, exist_ok=True) - - # get the overview dataframe and prettify it - df = get_overview_df() - df["possible_discontinuities"] = df["possible_discontinuities"].apply( - lambda x: "✓" if x else "" - ) - df.fillna({"objective_prior_distributions": ""}, inplace=True) - - def get_formatter(col: str): - """Get the appropriate formatter for the column.""" - if col not in df.columns: - # index column - return HTMLTemplateFormatter( - template=f""" - <%= value %> - """ - ) - if pd.api.types.is_integer_dtype(df[col].dtype): - return NumberFormatter(text_align="right") - if col in ("reference_uris", "sbml4humans_urls"): - icon = "📚" if col == "reference_uris" else "🌠" - return HTMLTemplateFormatter( - template=""" - <% - if (Array.isArray(value)) { - urls = value; - } else { - const sanitizedValue = value.replace(/'/g, '"'); - const urls = JSON.parse(sanitizedValue); - console.log('Parsed JSON:', urls); - } - for (let i = 0; i < urls.length; i++) { - %> - LINK_TEXT - <% } %> - """.replace("LINK_TEXT", icon) + rows = [] + for petab_problem_id in MODELS: + problem_dir = f"{PROBLEMS_DIRNAME}/{petab_problem_id}" + row = {"ID": f"[{petab_problem_id}]({problem_dir}/)"} + for label, version, dirname in ( + ("PEtab v1", v1, V1_DIRNAME), + ("PEtab v2", v2, V2_DIRNAME), + ): + row[label] = ( + f"[{dirname}]({problem_dir}/{dirname}/)" + if petab_problem_id in version.MODELS + else "" ) + row["Description"] = get_metadata(petab_problem_id).short_description + rows.append(row) + return pd.DataFrame(rows).to_markdown(index=False) - return StringFormatter() - - columns = [ - TableColumn( - field=col, - title=markdown_columns.get(col, col), - formatter=get_formatter(col), - width=len(col), - ) - for col in df.reset_index().columns - ] - source = ColumnDataSource(df) +def show_overview_table( + markdown: bool = False, + update_readme: bool = False, +): + """Print the overview table or write the README table. - css = InlineStyleSheet( - css=""" - .slick-header-column { - background-color: #f4f4f4; - font-weight: bold; - } + :param markdown: Print the README table instead of the v1 statistics. + :param update_readme: Write the README table to `README.md`. """ - ) - - data_table = DataTable( - source=source, - columns=columns, - sortable=True, - sizing_mode="stretch_both", - ) - data_table.stylesheets.append(css) - - heading = Div(text="

Benchmark Problems

") - preamble = Div( - text=f""" -

- This table provides an overview of the benchmark problems - available in the Benchmark-Models-PEtab - repository. -

- """ - ) - layout = column(heading, preamble, data_table, sizing_mode="stretch_both") - output_file(dest, title="Benchmark Problems") - save(layout) + if update_readme: + with open(readme_md) as f: + readme_content = f.read() + before_table = readme_content.split(start_overview_table)[0] + after_table = readme_content.split(end_overview_table)[1] + new_readme_content = ( + before_table + + start_overview_table + + get_readme_table() + + end_overview_table + + after_table + ) + with open(readme_md, "w") as f: + f.write(new_readme_content) + elif markdown: + print(get_readme_table()) + else: + pd.options.display.width = 0 + print(get_overview_df()) def main(): @@ -439,7 +405,9 @@ def main(): ) group1 = parser.add_mutually_exclusive_group() group1.add_argument( - "--markdown", action="store_true", help="Output in markdown format" + "--markdown", + action="store_true", + help="Print the README overview table (markdown)", ) group1.add_argument( "--update", @@ -449,12 +417,15 @@ def main(): group2 = parser.add_mutually_exclusive_group() group2.add_argument( - "--html-file", help="Output the overview table to an HTML file" + "--html-file", + help="Write the v1 and v2 overview tables to an HTML file", ) args = parser.parse_args() if args.html_file: - create_html_table(dest=Path(args.html_file)) + from ._site import create_overview_page + + create_overview_page(dest=Path(args.html_file)) else: show_overview_table(markdown=args.markdown, update_readme=args.update) diff --git a/src/python/benchmark_models_petab/overview_v2.py b/src/python/benchmark_models_petab/overview_v2.py new file mode 100644 index 0000000..29ffb36 --- /dev/null +++ b/src/python/benchmark_models_petab/overview_v2.py @@ -0,0 +1,164 @@ +"""Stats for the PEtab v2 encodings of the benchmark problems.""" + +from pathlib import Path + +import libsbml +import numpy as np +import pandas as pd +import petab.v2 as petab + +from . import v2 +from .C import V2_DIRNAME +from .overview import column_descriptions as v1_column_descriptions +from .overview import columns as v1_columns +from .overview import ( + get_reference_uris, + index_column, + make_sbml4humans_urls, + sbml_model_has_discontinuities, +) + +_labels = v1_columns | { + "experiments": "Experiments", + "preequilibration": "Experiments with preequilibration", + "prior_distributions": "Prior distribution(s)", +} + +#: Column labels for the overview table, in column order. +columns = { + col: _labels[col] + for col in ( + "petab_problem_id", + "experiments", + "preequilibration", + "postequilibration", + "estimated_parameters", + "measurements", + "observables", + "species", + "events", + "possible_discontinuities", + "noise_distributions", + "prior_distributions", + "reference_uris", + "sbml4humans_urls", + ) +} + +#: Column descriptions for the overview table. +column_descriptions = v1_column_descriptions | { + "experiments": ( + "Number of experiments in the measurement table. Measurements " + "without an experiment count as one." + ), + "preequilibration": ( + "Number of experiments with a preequilibration period." + ), + "postequilibration": ( + "Number of experiments with steady-state measurements (time = inf)." + ), + "noise_distributions": "Noise distributions of the observables.", + "prior_distributions": "Prior distributions of estimated parameters.", + "possible_discontinuities": ( + "Heuristic: events with assignments, discontinuous functions " + "(piecewise, min, max, ...) with time-dependent arguments in the " + "model, or condition changes after the start of an experiment." + ), +} + + +def get_summary(problem: petab.Problem, petab_problem_id: str) -> dict: + """Get dictionary with stats for the given PEtab v2 problem. + + Measurements without an experiment ID count as one experiment. + Preequilibration and postequilibration are counted as the number of + experiments with a preequilibration period (starting at ``-inf``) or + with steady-state measurements (at ``inf``), respectively. + For problems with multiple models, species and events are the maximum + over the models, and references are collected from all models. + """ + sbml_models = get_sbml_models(problem) + experiments = {e.id: e for e in problem.experiments} + measured_experiment_ids = {m.experiment_id for m in problem.measurements} + + return { + "petab_problem_id": petab_problem_id, + "experiments": len(measured_experiment_ids), + "preequilibration": sum( + experiments[exp_id].has_preequilibration + for exp_id in measured_experiment_ids + if exp_id is not None + ), + "postequilibration": len( + {m.experiment_id for m in problem.measurements if m.time == np.inf} + ), + "estimated_parameters": problem.n_estimated, + "measurements": len(problem.measurements), + "observables": len({m.observable_id for m in problem.measurements}), + "species": max(m.getNumSpecies() for m in sbml_models), + "events": max(m.getNumEvents() for m in sbml_models), + "possible_discontinuities": ( + any(sbml_model_has_discontinuities(m) for m in sbml_models) + or any( + has_period_changes(experiments[exp_id]) + for exp_id in measured_experiment_ids + if exp_id is not None + ) + ), + "noise_distributions": "; ".join( + sorted({str(o.noise_distribution) for o in problem.observables}) + ), + "prior_distributions": "; ".join( + sorted( + { + str(p.prior_distribution) + for p in problem.parameters + if p.estimate and p.prior_distribution is not None + } + ) + ), + "reference_uris": list( + dict.fromkeys( + uri for m in sbml_models for uri in get_reference_uris(m) + ) + ), + "sbml4humans_urls": make_sbml4humans_urls( + petab_problem_id, + V2_DIRNAME, + [Path(m.rel_path).as_posix() for m in problem.models], + ), + } + + +def get_sbml_models(problem: petab.Problem) -> list[libsbml.Model]: + """Get the SBML models of the problem. + + :raises ValueError: If the problem has no model or a non-SBML model. + """ + if not problem.models: + raise ValueError("The problem has no model.") + if non_sbml := [m.model_id for m in problem.models if m.type_id != "sbml"]: + raise ValueError(f"Only SBML models are supported, not: {non_sbml}") + return [m.sbml_model for m in problem.models] + + +def has_period_changes(experiment: petab.Experiment) -> bool: + """Check whether the experiment changes conditions after its start. + + Preequilibration periods are not considered. + """ + return ( + sum(not period.is_preequilibration for period in experiment.periods) + > 1 + ) + + +def get_overview_df() -> pd.DataFrame: + """Get overview table with stats for all PEtab v2 benchmark problems.""" + data = [ + get_summary(v2.get_problem(petab_problem_id), petab_problem_id) + for petab_problem_id in v2.MODELS + ] + df = pd.DataFrame(data, columns=list(columns)) + df.set_index([index_column], inplace=True) + return df diff --git a/src/python/benchmark_models_petab/v1.py b/src/python/benchmark_models_petab/v1.py index b3f6679..a1929ab 100644 --- a/src/python/benchmark_models_petab/v1.py +++ b/src/python/benchmark_models_petab/v1.py @@ -4,10 +4,23 @@ import petab.v1 as petab -from .C import MODELS_DIR, PROBLEM_FILENAME, SIMULATIONS_FILENAME, V1_DIRNAME - import pandas as pd +from .C import ( + MODELS as ALL_MODELS, + MODELS_DIR, + PROBLEM_FILENAME, + SIMULATIONS_FILENAME, + V1_DIRNAME, +) + +#: IDs of the problems that have a PEtab v1 encoding. +MODELS: list[str] = [ + id_ + for id_ in ALL_MODELS + if Path(MODELS_DIR, id_, V1_DIRNAME, PROBLEM_FILENAME).is_file() +] + def get_problem_yaml_path(id_: str) -> Path: """Get the path to the PEtab problem YAML file. diff --git a/src/python/benchmark_models_petab/v2.py b/src/python/benchmark_models_petab/v2.py index 3a459ef..d8fff25 100644 --- a/src/python/benchmark_models_petab/v2.py +++ b/src/python/benchmark_models_petab/v2.py @@ -5,7 +5,20 @@ import pandas as pd import petab.v2 as petab -from .C import MODELS_DIR, PROBLEM_FILENAME, SIMULATIONS_FILENAME, V2_DIRNAME +from .C import ( + MODELS as ALL_MODELS, + MODELS_DIR, + PROBLEM_FILENAME, + SIMULATIONS_FILENAME, + V2_DIRNAME, +) + +#: IDs of the problems that have a PEtab v2 encoding. +MODELS: list[str] = [ + id_ + for id_ in ALL_MODELS + if Path(MODELS_DIR, id_, V2_DIRNAME, PROBLEM_FILENAME).is_file() +] def get_problem_yaml_path(id_: str) -> Path: diff --git a/src/python/pyproject.toml b/src/python/pyproject.toml index 5b1a3a0..a90a937 100644 --- a/src/python/pyproject.toml +++ b/src/python/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ [project.optional-dependencies] dev = ["pre-commit", "pytest", "ruff"] -site = ["bokeh>=3.7.3"] +site = ["jinja2"] [project.scripts] bmp-petablint = "benchmark_models_petab.check_petablint:main" diff --git a/src/python/test/test_overview.py b/src/python/test/test_overview.py new file mode 100644 index 0000000..1269427 --- /dev/null +++ b/src/python/test/test_overview.py @@ -0,0 +1,18 @@ +"""Tests for the README overview table.""" + +import benchmark_models_petab as models +from benchmark_models_petab.overview import get_readme_table + +PROBLEM_ID = "Boehm_JProteomeRes2014" + + +def test_get_readme_table(): + table = get_readme_table() + (row,) = [line for line in table.splitlines() if f"[{PROBLEM_ID}]" in line] + assert models.get_metadata(PROBLEM_ID).short_description in row + assert f"[v1](problems/{PROBLEM_ID}/v1/)" in row + assert f"[v2](problems/{PROBLEM_ID}/v2/)" in row + # a problem without a v2 encoding + no_v2 = next(id_ for id_ in models.MODELS if id_ not in models.v2.MODELS) + (row,) = [line for line in table.splitlines() if f"[{no_v2}]" in line] + assert "/v2/" not in row diff --git a/src/python/test/test_overview_v2.py b/src/python/test/test_overview_v2.py new file mode 100644 index 0000000..d1d12b6 --- /dev/null +++ b/src/python/test/test_overview_v2.py @@ -0,0 +1,115 @@ +"""Tests for the v2 overview table and the site's overview page data.""" + +import numpy as np +import pandas as pd +import petab.v2 as petab + +import benchmark_models_petab as models +from benchmark_models_petab import overview, overview_v2 +from benchmark_models_petab._site.build import ( + column_group, + get_summary_tiles, + get_table, +) + +PROBLEM_ID = "Boehm_JProteomeRes2014" + + +def test_get_summary(): + summary = overview_v2.get_summary( + models.v2.get_problem(PROBLEM_ID), PROBLEM_ID + ) + assert set(summary) == set(overview_v2.columns) + assert summary["experiments"] == 1 + assert summary["estimated_parameters"] == 9 + assert summary["measurements"] == 48 + assert summary["observables"] == 3 + assert summary["noise_distributions"] == "normal" + assert summary["prior_distributions"] == "" + assert summary["possible_discontinuities"] is False + assert summary["sbml4humans_urls"][0].endswith( + f"/{PROBLEM_ID}/v2/model.xml" + ) + + +def test_get_table(): + table = get_table( + overview_v2.get_overview_df(), + overview_v2.columns, + overview_v2.column_descriptions, + "v2", + ) + kinds = {col["field"]: col["kind"] for col in table["columns"]} + assert kinds["petab_problem_id"] == "id" + assert kinds["short_description"] == "description" + assert kinds["measurements"] == "int" + assert kinds["possible_discontinuities"] == "bool" + assert kinds["reference_uris"] == "links" + assert kinds["noise_distributions"] == "tags" + + # columns of a group are contiguous, ungrouped columns come first + groups = [col["group"] for col in table["columns"]] + assert groups[:2] == [None, None] + assert None not in groups[2:] + for group in set(groups) - {None}: + idx = [i for i, g in enumerate(groups) if g == group] + assert idx == list(range(idx[0], idx[-1] + 1)) + assert all(col["tooltip"] for col in table["columns"]) + + (row,) = [r for r in table["rows"] if r["petab_problem_id"] == PROBLEM_ID] + assert ( + row["short_description"] + == models.get_metadata(PROBLEM_ID).short_description + ) + assert row["measurements"] == 48 + assert table["tree_url"].endswith("/problems/{id}/v2") + + +def test_has_period_changes(): + def experiment(*times): + return petab.Experiment( + id="e", + periods=[petab.ExperimentPeriod(time=t) for t in times], + ) + + assert not overview_v2.has_period_changes(experiment(0)) + assert not overview_v2.has_period_changes(experiment(-np.inf, 0)) + assert overview_v2.has_period_changes(experiment(0, 10)) + assert overview_v2.has_period_changes(experiment(-np.inf, 0, 10)) + + +def test_get_summary_tiles(): + v1_df = pd.DataFrame( + { + "estimated_parameters": [3, 1200], + "measurements": [10, 27132], + "conditions": [1, 9169], + "observables": [1, 4], + }, + index=["a", "b"], + ) + # "b" is ignored, as it has a v1 encoding; "c" extends the ranges + v2_df = pd.DataFrame( + { + "estimated_parameters": [1, 2], + "measurements": [1, 30000], + "experiments": [9170, 1], + "observables": [5, 5], + }, + index=["b", "c"], + ) + tiles = { + label: value for value, label, _ in get_summary_tiles(v1_df, v2_df) + } + assert tiles["problems in total"] == str(len(models.MODELS)) + assert tiles["in PEtab v1"] == str(len(models.v1.MODELS)) + assert tiles["in PEtab v2"] == str(len(models.v2.MODELS)) + assert tiles["measurements"] == "10\u201330,000" + assert tiles["simulation conditions"] == "1\u20139,169" + assert tiles["estimated parameters"] == "2\u20131,200" + assert tiles["observables"] == "1\u20135" + + +def test_all_columns_grouped(): + for labels in (overview.columns, overview_v2.columns): + assert set(labels) - set(column_group) == {"petab_problem_id"} diff --git a/src/python/test/test_v1.py b/src/python/test/test_v1.py index 42aedfe..dbf00a8 100644 --- a/src/python/test/test_v1.py +++ b/src/python/test/test_v1.py @@ -26,3 +26,8 @@ def test_v1_submodule_matches_top_level(): assert models.get_problem is models.v1.get_problem assert models.get_problem_yaml_path is models.v1.get_problem_yaml_path assert models.get_simulation_df is models.v1.get_simulation_df + + +def test_models(): + assert "Boehm_JProteomeRes2014" in models.v1.MODELS + assert set(models.v1.MODELS) <= set(models.MODELS) diff --git a/src/python/test/test_v2.py b/src/python/test/test_v2.py index 1a2d6e8..c01d519 100644 --- a/src/python/test/test_v2.py +++ b/src/python/test/test_v2.py @@ -31,3 +31,8 @@ def test_get_problem(): assert "log10_k_phos" in problem.parameter_df.index assert "k_phos" not in problem.parameter_df.index assert len(problem.measurement_df) > 0 + + +def test_models(): + assert "Boehm_JProteomeRes2014" in models.v2.MODELS + assert set(models.v2.MODELS) <= set(models.MODELS)