Skip to content

Commit b9294e6

Browse files
dweindldilpath
andauthored
Generation of condition-specific SBML models (#108)
New function `petab.get_model_for_condition` to generate condition-specific SBML models based on a PEtab problem. Co-authored-by: Dilan Pathirana <[email protected]>
1 parent 65fd2da commit b9294e6

File tree

2 files changed

+202
-1
lines changed

2 files changed

+202
-1
lines changed

petab/sbml.py

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
"""Functions for interacting with SBML models"""
22
import logging
33
import re
4+
from numbers import Number
45
from pathlib import Path
5-
from typing import Any, Dict, List, Tuple, Union
6+
from typing import Any, Dict, List, Optional, Tuple, Union
67
from warnings import warn
78

89
import libsbml
910
from pandas.io.common import get_handle, is_file_like, is_url
1011

12+
import petab
13+
1114
logger = logging.getLogger(__name__)
1215
__all__ = ['add_global_parameter',
1316
'add_model_output',
1417
'add_model_output_sigma',
1518
'add_model_output_with_sigma',
1619
'assignment_rules_to_dict',
1720
'create_assigment_rule',
21+
'get_model_for_condition',
1822
'get_model_parameters',
1923
'get_observables',
2024
'get_sbml_model',
@@ -481,3 +485,118 @@ def load_sbml_from_file(
481485
sbml_model = sbml_document.getModel()
482486

483487
return sbml_reader, sbml_document, sbml_model
488+
489+
490+
def get_model_for_condition(
491+
petab_problem: "petab.Problem",
492+
sim_condition_id: str = None,
493+
preeq_condition_id: Optional[str] = None,
494+
) -> Tuple[libsbml.SBMLDocument, libsbml.Model]:
495+
"""Create an SBML model for the given condition.
496+
497+
Creates a copy of the model and updates parameters according to the PEtab
498+
files. Estimated parameters are set to their ``nominalValue``.
499+
Observables defined in the observables table are not added to the model.
500+
501+
:param petab_problem: PEtab problem
502+
:param sim_condition_id: Simulation ``conditionId`` for which to generate a
503+
model
504+
:param preeq_condition_id: Preequilibration ``conditionId`` of the settings
505+
for which to generate a model. This is only used to determine the
506+
relevant output parameter overrides. Preequilibration is not encoded
507+
in the resulting model.
508+
:return: The generated SBML document, and SBML model
509+
"""
510+
condition_dict = {petab.SIMULATION_CONDITION_ID: sim_condition_id}
511+
if preeq_condition_id:
512+
condition_dict[petab.PREEQUILIBRATION_CONDITION_ID] = \
513+
preeq_condition_id
514+
cur_measurement_df = petab.measurements.get_rows_for_condition(
515+
measurement_df=petab_problem.measurement_df,
516+
condition=condition_dict,
517+
)
518+
parameter_map, scale_map = \
519+
petab.parameter_mapping.get_parameter_mapping_for_condition(
520+
condition_id=sim_condition_id,
521+
is_preeq=False,
522+
cur_measurement_df=cur_measurement_df,
523+
sbml_model=petab_problem.sbml_model,
524+
condition_df=petab_problem.condition_df,
525+
parameter_df=petab_problem.parameter_df,
526+
warn_unmapped=True,
527+
scaled_parameters=False,
528+
fill_fixed_parameters=True,
529+
# will only become problematic once the observable and noise terms
530+
# are added to the model
531+
allow_timepoint_specific_numeric_noise_parameters=True,
532+
)
533+
# create a copy of the model
534+
sbml_doc = petab_problem.sbml_model.getSBMLDocument().clone()
535+
sbml_model = sbml_doc.getModel()
536+
537+
# fill in parameters
538+
def get_param_value(parameter_id: str):
539+
"""Parameter value from mapping or nominal value"""
540+
mapped_value = parameter_map.get(parameter_id)
541+
if not isinstance(mapped_value, str):
542+
return mapped_value
543+
544+
# estimated parameter, look up in nominal parameters
545+
return petab_problem.parameter_df.loc[mapped_value,
546+
petab.NOMINAL_VALUE]
547+
548+
def remove_rules(target_id: str):
549+
if sbml_model.removeRuleByVariable(target_id):
550+
warn("An SBML rule was removed to set the component "
551+
f"{target_id} to a constant value.")
552+
sbml_model.removeInitialAssignment(target_id)
553+
554+
for parameter in sbml_model.getListOfParameters():
555+
new_value = get_param_value(parameter.getId())
556+
if new_value:
557+
parameter.setValue(new_value)
558+
# remove rules that would override that value
559+
remove_rules(parameter.getId())
560+
561+
# set concentrations for any overridden species
562+
for component_id in petab_problem.condition_df:
563+
sbml_species = sbml_model.getSpecies(component_id)
564+
if not sbml_species:
565+
continue
566+
567+
# remove any rules overriding that species' initials
568+
remove_rules(component_id)
569+
570+
# set initial concentration/amount
571+
new_value = petab.to_float_if_float(
572+
petab_problem.condition_df.loc[sim_condition_id, component_id])
573+
if not isinstance(new_value, Number):
574+
# parameter reference in condition table
575+
new_value = get_param_value(new_value)
576+
577+
if sbml_species.isSetInitialAmount() \
578+
or (sbml_species.getHasOnlySubstanceUnits()
579+
and not sbml_species.isSetInitialConcentration()):
580+
sbml_species.setInitialAmount(new_value)
581+
else:
582+
sbml_species.setInitialConcentration(new_value)
583+
584+
# set compartment size for any compartments in the condition table
585+
for component_id in petab_problem.condition_df:
586+
sbml_compartment = sbml_model.getCompartment(component_id)
587+
if not sbml_compartment:
588+
continue
589+
590+
# remove any rules overriding that compartment's size
591+
remove_rules(component_id)
592+
593+
# set initial concentration/amount
594+
new_value = petab.to_float_if_float(
595+
petab_problem.condition_df.loc[sim_condition_id, component_id])
596+
if not isinstance(new_value, Number):
597+
# parameter reference in condition table
598+
new_value = get_param_value(new_value)
599+
600+
sbml_compartment.setSize(new_value)
601+
602+
return sbml_doc, sbml_model

tests/test_sbml.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import sys
33
import os
44

5+
import pandas as pd
6+
57
sys.path.append(os.getcwd())
68
import petab # noqa: E402
79

@@ -31,3 +33,83 @@ def test_assignment_rules_to_dict():
3133
assert actual == expected
3234
assert model.getAssignmentRuleByVariable('observable_1') is None
3335
assert len(model.getListOfParameters()) == 0
36+
37+
38+
def test_get_condition_specific_models():
39+
"""Test for petab.sbml.get_condition_specific_models"""
40+
# Create test model and data files
41+
sbml_document = libsbml.SBMLDocument(3, 1)
42+
sbml_model = sbml_document.createModel()
43+
44+
c = sbml_model.createCompartment()
45+
c.setId("compartment_1")
46+
c.setSize(1)
47+
48+
for i in range(1, 4):
49+
p = sbml_model.createParameter()
50+
p.setId(f"parameter_{i}")
51+
p.setValue(i)
52+
53+
for i in range(1, 4):
54+
s = sbml_model.createSpecies()
55+
s.setId(f"species_{i}")
56+
s.setInitialConcentration(10 * i)
57+
58+
ia = sbml_model.createInitialAssignment()
59+
ia.setSymbol("species_2")
60+
ia.setMath(libsbml.parseL3Formula("25"))
61+
62+
condition_df = pd.DataFrame({
63+
petab.CONDITION_ID: ["condition_1"],
64+
"parameter_3": ['parameter_2'],
65+
"species_1": [15],
66+
"species_2": [25],
67+
"species_3": ['parameter_1'],
68+
"compartment_1": [2],
69+
})
70+
condition_df.set_index([petab.CONDITION_ID], inplace=True)
71+
72+
observable_df = pd.DataFrame({
73+
petab.OBSERVABLE_ID: ["observable_1"],
74+
petab.OBSERVABLE_FORMULA: ["2 * species_1"],
75+
})
76+
observable_df.set_index([petab.OBSERVABLE_ID], inplace=True)
77+
78+
measurement_df = pd.DataFrame({
79+
petab.OBSERVABLE_ID: ["observable_1"],
80+
petab.SIMULATION_CONDITION_ID: ["condition_1"],
81+
petab.TIME: [0.0],
82+
})
83+
84+
parameter_df = pd.DataFrame({
85+
petab.PARAMETER_ID: ["parameter_1", "parameter_2"],
86+
petab.PARAMETER_SCALE: [petab.LOG10] * 2,
87+
petab.NOMINAL_VALUE: [1.25, 2.25],
88+
petab.ESTIMATE: [0, 1],
89+
})
90+
parameter_df.set_index([petab.PARAMETER_ID], inplace=True)
91+
92+
petab_problem = petab.Problem(
93+
sbml_model=sbml_model,
94+
condition_df=condition_df,
95+
observable_df=observable_df,
96+
measurement_df=measurement_df,
97+
parameter_df=parameter_df
98+
)
99+
100+
# Actual test
101+
condition_doc, condition_model = petab.get_model_for_condition(
102+
petab_problem, "condition_1")
103+
104+
assert condition_model.getSpecies(
105+
"species_1").getInitialConcentration() == 15
106+
assert condition_model.getSpecies(
107+
"species_2").getInitialConcentration() == 25
108+
assert condition_model.getSpecies(
109+
"species_3").getInitialConcentration() == 1.25
110+
assert len(condition_model.getListOfInitialAssignments()) == 0, \
111+
"InitialAssignment not removed"
112+
assert condition_model.getCompartment("compartment_1").getSize() == 2.0
113+
assert condition_model.getParameter("parameter_1").getValue() == 1.25
114+
assert condition_model.getParameter("parameter_2").getValue() == 2.25
115+
assert condition_model.getParameter("parameter_3").getValue() == 2.25

0 commit comments

Comments
 (0)