Source code for amici.petab_import_pysb

PySB-PEtab Import
Import a model in the PySB-adapted :mod:`petab`
( format into AMICI.

import logging
import os
from typing import List, Dict, Union, Optional, Tuple, Iterable

import libsbml
import petab
import pysb
import sympy as sp

from . import petab_import
from .logging import get_logger, log_execution_time, set_log_level

logger = get_logger(__name__, logging.WARNING)

[docs]class PysbPetabProblem(petab.Problem): """Representation of a PySB-model-based PEtab problem This class extends :class:`petab.Problem` with a PySB model. The model is augmented with the observation model based on the PEtab observable table. For now, a dummy SBML model is created which allows used the existing SBML-PEtab API. :ivar pysb_model: PySB model instance from of this PEtab problem. """
[docs] def __init__(self, pysb_model: 'pysb.Model' = None, *args, **kwargs): """ Constructor :param pysb_model: PySB model instance for this PEtab problem :param args: See :meth:`petab.Problem.__init__` :param kwargs: See :meth:`petab.Problem.__init__` """ super().__init__(*args, **kwargs) self.pysb_model: 'pysb.Model' = pysb_model self._add_observation_model() if self.pysb_model is not None: self.sbml_document, self.sbml_model = \ create_dummy_sbml( self.pysb_model, observable_ids=self.observable_df.index.values if self.observable_df is not None else None )
def _add_observation_model(self): """Extend PySB model by observation model as defined in the PEtab observables table""" # add any required output parameters local_syms = {sp.Symbol.__str__(comp): comp for comp in self.pysb_model.components if isinstance(comp, sp.Symbol)} for formula in [*self.observable_df[OBSERVABLE_FORMULA], *self.observable_df[NOISE_FORMULA]]: sym = sp.sympify(formula, locals=local_syms) for s in sym.free_symbols: if not isinstance(s, pysb.Component): p = pysb.Parameter(str(s), 1.0, _export=False) self.pysb_model.add_component(p) local_syms[sp.Symbol.__str__(p)] = p # add observables and sigmas to pysb model for (observable_id, observable_formula, noise_formula) \ in zip(self.observable_df.index, self.observable_df[OBSERVABLE_FORMULA], self.observable_df[NOISE_FORMULA]): # No observableTransformation so far if OBSERVABLE_TRANSFORMATION in self.observable_df: trafo = self.observable_df.loc[observable_id, OBSERVABLE_TRANSFORMATION] if trafo and trafo != LIN: raise NotImplementedError( "Observable transformation currently unsupported " "for PySB models") obs_symbol = sp.sympify(observable_formula, locals=local_syms) if observable_id in self.pysb_model.expressions.keys(): obs_expr = self.pysb_model.expressions[observable_id] else: obs_expr = pysb.Expression(observable_id, obs_symbol, _export=False) self.pysb_model.add_component(obs_expr) local_syms[observable_id] = obs_expr sigma_id = f"{observable_id}_sigma" sigma_symbol = sp.sympify( noise_formula, locals=local_syms ) sigma_expr = pysb.Expression(sigma_id, sigma_symbol, _export=False) self.pysb_model.add_component(sigma_expr) local_syms[sigma_id] = sigma_expr
[docs] @staticmethod def from_files(condition_file: str = None, measurement_file: Union[str, Iterable[str]] = None, parameter_file: Union[str, List[str]] = None, visualization_files: Union[str, Iterable[str]] = None, observable_files: Union[str, Iterable[str]] = None, pysb_model_file: str = None, ) -> 'PysbPetabProblem': """ Factory method to load model and tables from files. Arguments: condition_file: PEtab condition table measurement_file: PEtab measurement table parameter_file: PEtab parameter table visualization_files: PEtab visualization tables observable_files: PEtab observables tables pysb_model_file: PySB model file """ condition_df = measurement_df = parameter_df = visualization_df = None observable_df = None if condition_file: condition_df = petab.conditions.get_condition_df(condition_file) if measurement_file: # If there are multiple tables, we will merge them measurement_df = petab.core.concat_tables( measurement_file, petab.measurements.get_measurement_df) if parameter_file: parameter_df = petab.parameters.get_parameter_df(parameter_file) if visualization_files: # If there are multiple tables, we will merge them visualization_df = petab.core.concat_tables( visualization_files, petab.core.get_visualization_df) if observable_files: # If there are multiple tables, we will merge them observable_df = petab.core.concat_tables( observable_files, petab.observables.get_observable_df) from amici.pysb_import import pysb_model_from_path return PysbPetabProblem( pysb_model=pysb_model_from_path( pysb_model_file=pysb_model_file), condition_df=condition_df, measurement_df=measurement_df, parameter_df=parameter_df, observable_df=observable_df, visualization_df=visualization_df)
[docs] @staticmethod def from_yaml(yaml_config: Union[Dict, str]) -> 'PysbPetabProblem': """ Factory method to load model and tables as specified by YAML file. NOTE: The PySB model is currently expected in the YAML file under ``sbml_files``. Arguments: yaml_config: PEtab configuration as dictionary or YAML file name """ from petab.yaml import (load_yaml, is_composite_problem, assert_single_condition_and_sbml_file) if isinstance(yaml_config, str): path_prefix = os.path.dirname(yaml_config) yaml_config = load_yaml(yaml_config) else: path_prefix = "" if is_composite_problem(yaml_config): raise ValueError('petab.Problem.from_yaml() can only be used for ' 'yaml files comprising a single model. ' 'Consider using ' 'petab.CompositeProblem.from_yaml() instead.') if yaml_config[FORMAT_VERSION] != petab.__format_version__: raise ValueError("Provided PEtab files are of unsupported version" f"{yaml_config[FORMAT_VERSION]}. Expected " f"{petab.__format_version__}.") problem0 = yaml_config['problems'][0] assert_single_condition_and_sbml_file(problem0) if isinstance(yaml_config[PARAMETER_FILE], list): parameter_file = [ os.path.join(path_prefix, f) for f in yaml_config[PARAMETER_FILE] ] else: parameter_file = os.path.join( path_prefix, yaml_config[PARAMETER_FILE]) return PysbPetabProblem.from_files( pysb_model_file=os.path.join( path_prefix, problem0[SBML_FILES][0]), measurement_file=[os.path.join(path_prefix, f) for f in problem0[MEASUREMENT_FILES]], condition_file=os.path.join( path_prefix, problem0[CONDITION_FILES][0]), parameter_file=parameter_file, visualization_files=[ os.path.join(path_prefix, f) for f in problem0.get(VISUALIZATION_FILES, [])], observable_files=[ os.path.join(path_prefix, f) for f in problem0.get(OBSERVABLE_FILES, [])] )
[docs]def create_dummy_sbml( pysb_model: 'pysb.Model', observable_ids: Optional[Iterable[str]] = None ) -> Tuple['libsbml.Model', 'libsbml.SBMLDocument']: """Create SBML dummy model for to use PySB models with PEtab. Model must at least contain PEtab problem parameter and noise parameters for observables. :param pysb_model: PySB model :param observable_ids: Observable IDs :return: A dummy SBML model and document. """ import libsbml document = libsbml.SBMLDocument(3, 1) dummy_sbml_model = document.createModel() dummy_sbml_model.setTimeUnits("second") dummy_sbml_model.setExtentUnits("mole") dummy_sbml_model.setSubstanceUnits('mole') for species in pysb_model.parameters: p = dummy_sbml_model.createParameter() p.setId( p.setConstant(True) p.setValue(0.0) for observable_id in observable_ids: p = dummy_sbml_model.createParameter() p.setId(f"noiseParameter1_{observable_id}") p.setConstant(True) p.setValue(0.0) return document, dummy_sbml_model
[docs]@log_execution_time('Importing PEtab model', logger) def import_model_pysb( petab_problem: PysbPetabProblem, model_output_dir: Optional[str] = None, verbose: Optional[Union[bool, int]] = True, **kwargs ) -> None: """ Create AMICI model from PySB-PEtab problem :param petab_problem: PySB PEtab problem :param model_output_dir: Directory to write the model code to. Will be created if doesn't exist. Defaults to current directory. :param verbose: Print/log extra information. :param kwargs: Additional keyword arguments to be passed to :meth:`amici.pysb_import.pysb2amici`. """ set_log_level(logger, verbose)"Importing model ...") observable_table = petab_problem.observable_df pysb_model = petab_problem.pysb_model # For pysb, we only allow parameters in the condition table # those must be pysb model parameters (either natively, or output # parameters from measurement or condition table that have been added in # PysbPetabProblem) model_parameters = [ for p in pysb_model.parameters] for x in petab_problem.condition_df.columns: if x == CONDITION_NAME: continue if x not in model_parameters: raise NotImplementedError( "For PySB PEtab import, only model parameters, but no states " "or compartments are allowed in the condition table." f"Offending column: {x}" ) constant_parameters = petab_import.get_fixed_parameters( petab_problem.sbml_model, petab_problem.condition_df) if observable_table is None: observables = None sigmas = None else: observables = [ for expr in pysb_model.expressions if in observable_table.index] sigmas = {obs_id: f"{obs_id}_sigma" for obs_id in observables} from amici.pysb_import import pysb2amici pysb2amici(pysb_model, model_output_dir, verbose=True, observables=observables, sigmas=sigmas, constant_parameters=constant_parameters, **kwargs)