Source code for pysb.testing.modeltests

import pysb
import abc
from pysb.core import SelfExporter
from pysb.pattern import SpeciesPatternMatcher, ReactionPatternMatcher, \
    RulePatternMatcher


[docs]class ModelAssertionFailure(Exception): def __init__(self, assertion, model, message=None): self.assertion = assertion self.model = model self.message = message def __repr__(self): base_msg = '%s on model %s failed' % (self.assertion, self.model.name) if self.message: return base_msg + ': ' + str(self.message) else: return base_msg def __str__(self): return repr(self)
[docs]class TestSuite(object): """ A suite of tests for checking properties of a model There are two modes of operation: building a test suite using add() and executing all the tests at once with check_all(), or executing tests immediately with check(). Examples -------- Create a test suite for the EARM 1.0 model: >>> from pysb.testing.modeltests import TestSuite, SpeciesExists, \ SpeciesDoesNotExist >>> from pysb.bng import generate_equations >>> from pysb.examples.earm_1_0 import model >>> ts = TestSuite(model) Create variables for model components (not needed for models defined interactively): >>> AMito, mCytoC, mSmac, cSmac, L, CPARP = [model.monomers[m] for m in \ ('AMito', 'mCytoC', 'mSmac', 'cSmac', \ 'L', 'CPARP')] Add some assertions: Check that AMito(b=1) % mSmac(b=1) exists in the species graph (note this doesn't guarantee the species will actually be producted/consumed/change in concentration; that depends on the rate constants): >>> ts.add(SpeciesExists(AMito(b=1) % mSmac(b=1))) This is the opposite check, that the complex above doesn't exist, which should of course fail: >>> ts.add(SpeciesDoesNotExist(AMito(b=1) % mSmac(b=1))) We can also specify that species matching a pattern should never exist in a model. For example, we shouldn't ever be producing unbound ligand in the EARM 1.0 model: >>> ts.add(SpeciesNeverProduct(L(b=None))) We could also have used SpeciesOnlyReactant. The difference is the latter checks for an appearance as a reactant, whereas SpeciesNeverProduct would pass whether the species appeared as a reactant or not. >>> ts.add(SpeciesOnlyReactant(L(b=None))) CPARP is an output in this model, so it should appear as a product but never as a reactant: >>> ts.add(SpeciesOnlyProduct(CPARP())) When we're ready, we can generate the reactions and check the assertions: >>> generate_equations(model) >>> ts.check_all() # doctest:+ELLIPSIS SpeciesExists(AMito() % mSmac())...OK... SpeciesDoesNotExist(AMito() % mSmac())...FAIL... [AMito(b=1) % mSmac(b=1)]... SpeciesExists(AMito(b=1) % mCytoC(b=1))...OK... SpeciesNeverProduct(L(b=None))...OK... SpeciesOnlyProduct(CPARP())...OK... We can also execute any test immediately without adding it to the test suite (note that some tests require a reaction network to be generated): >>> ts.check(SpeciesExists(L(b=None))) True """ _KNOWN_CACHES = {'species_pattern_matcher': SpeciesPatternMatcher, 'rule_pattern_matcher': RulePatternMatcher, 'reaction_pattern_matcher': ReactionPatternMatcher} _COL = {'OK': '\033[92m', 'FAIL': '\033[91m', 'END': '\033[0m'} def __init__(self, model=None): self._caches = {} self.assertions = [] self._model = model if model: self._model = model elif SelfExporter.default_model: self._model = SelfExporter.default_model else: raise Exception('A model must be specified explicitly if the ' 'PySB self-exporter is not in use') @property def model(self): return self._model @model.setter def model(self, model): """ Changing the model invalidates any caches """ self._caches = {} self._model = model def _ensure_required_caches(self, assertion): for cache in assertion.required_caches: if cache not in self._KNOWN_CACHES.keys(): raise Exception('Unknown assertion cache: %s' % cache) self._caches[cache] = self._KNOWN_CACHES[cache](self.model) def add(self, assertion): self.assertions.append(assertion)
[docs] def check(self, assertion): """ Checks an assertion immediately without adding it to the test suite Parameters ---------- assertion: ModelAssertion An instance of the ModelAssertion subclass Returns ------- True if assertion succeeded or raises a ModelAssertionFailure exception if not """ self._ensure_required_caches(assertion) return assertion.check(self.model, **{name: self._caches[name] for name in assertion.required_caches})
[docs] def check_all(self, stop_on_exception=False): """Runs all assertions in the test suite""" for a in self.assertions: print('%s... ' % repr(a), end="") try: self.check(a) print('%sOK%s' % (self._COL['OK'], self._COL['END'])) except ModelAssertionFailure as e: print('%sFAIL%s' % (self._COL['FAIL'], self._COL['END'])) print(' ' + str(e.message)) if stop_on_exception: return except Exception as e: print('%sERROR%s' % (self._COL['FAIL'], self._COL['END'])) print(' ' + str(e)) if stop_on_exception: return
[docs]class ModelAssertion(object): """ Base class for model assertions """ @abc.abstractmethod def __init__(self, *args, **kwargs): self.required_caches = set() self._last_result = None def __repr__(self): return '%s()' % self.__class__.__name__ @abc.abstractmethod def check(self, model, **kwargs): if not isinstance(model, pysb.Model): raise ValueError('model should be an instance of pysb.Model')
def _negated_subclass(assertion_class, class_name): """ Creates a negated version of an assertion class through subclassing Parameters ---------- assertion_class: class ModelAssertion or its subclasses (not an instance) Returns ------- A negated version of the ModelAssertion class """ class NegatedSubclass(assertion_class): """ Negated version of :class:`.{}` """.format(assertion_class.__class__.__name__) def check(self, model, **kwargs): try: super(self.__class__, self).check(model, **kwargs) except ModelAssertionFailure: return True raise ModelAssertionFailure(assertion=self, model=model, message=self._last_result) NegatedSubclass.__name__ = class_name return NegatedSubclass
[docs]class ReactionNetworkAssertion(ModelAssertion): """ Base class for reaction network assertions Checks the reaction network has been generated """ def __init__(self, *args, **kwargs): super(ReactionNetworkAssertion, self).__init__(*args, **kwargs) @abc.abstractmethod def check(self, model, **kwargs): super(ReactionNetworkAssertion, self).check(model) if not model.species: raise ModelAssertionFailure(assertion=self, model=model, message='Reaction network has not ' 'been generated yet')
[docs]class SpeciesAssertion(ReactionNetworkAssertion): """ Class for checking species within a reaction network """ def __init__(self, *args, **kwargs): super(SpeciesAssertion, self).__init__(*args, **kwargs) self.required_caches.add('species_pattern_matcher') self.pattern = args[0] @abc.abstractmethod def check(self, model, **kwargs): super(SpeciesAssertion, self).check(model) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self.pattern)
[docs]class SpeciesExists(SpeciesAssertion): """ Checks a species pattern exists in the list of species """ def check(self, model, **kwargs): self._last_result = kwargs['species_pattern_matcher'].match( self.pattern) if self._last_result: return True else: raise ModelAssertionFailure(assertion=self, model=model)
SpeciesDoesNotExist = _negated_subclass(SpeciesExists, 'SpeciesDoesNotExist')
[docs]class ReactionAssertion(ReactionNetworkAssertion): def __init__(self, *args, **kwargs): super(ReactionAssertion, self).__init__(*args, **kwargs) self.required_caches.add('reaction_pattern_matcher') self.pattern = args[0] @abc.abstractmethod def check(self, model, **kwargs): super(ReactionAssertion, self).check(model) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self.pattern)
[docs]class SpeciesIsProduct(ReactionAssertion): """ Checks a species pattern appears on the product side of a reaction """ def check(self, model, **kwargs): self._last_result = kwargs[ 'reaction_pattern_matcher'].match_products(self.pattern) if not self._last_result: raise ModelAssertionFailure(assertion=self, model=model) else: return True
SpeciesNeverProduct = _negated_subclass(SpeciesIsProduct, 'SpeciesNeverProduct')
[docs]class SpeciesIsReactant(ReactionAssertion): """ Checks a species pattern appears on the reactant side of a reaction """ def check(self, model, **kwargs): self._last_result = kwargs[ 'reaction_pattern_matcher'].match_reactants(self.pattern) if self._last_result: raise ModelAssertionFailure(assertion=self, model=model, message=self._last_result) else: return True
SpeciesNeverReactant = _negated_subclass(SpeciesIsReactant, 'SpeciesNeverReactant')
[docs]class SpeciesOnlyProduct(ReactionAssertion): """ Checks a species appears as a product but never as a reactant """ def check(self, model, **kwargs): rpm = kwargs['reaction_pattern_matcher'] if not rpm.match_products(self.pattern): raise ModelAssertionFailure(assertion=self, model=model, message='Does not appear as product') as_reactant = rpm.match_reactants(self.pattern) if as_reactant: raise ModelAssertionFailure(assertion=self, model=model, message='Appears as reactant:' + str(as_reactant)) return True
[docs]class SpeciesOnlyReactant(ReactionAssertion): """ Checks a species appears as a reactant but never as a product """ def check(self, model, **kwargs): rpm = kwargs['reaction_pattern_matcher'] if not rpm.match_reactants(self.pattern): raise ModelAssertionFailure(assertion=self, model=model, message='Does not appear as reactant') as_product = rpm.match_products(self.pattern) if as_product: raise ModelAssertionFailure(assertion=self, model=model, message='Appears as product: ' + str(as_product)) return True
[docs]class RuleAssertion(ModelAssertion): def __init__(self, *args, **kwargs): super(RuleAssertion, self).__init__(*args, **kwargs) self.required_caches.add('rule_pattern_matcher') self.pattern = args[0] @abc.abstractmethod def check(self, model, **kwargs): super(RuleAssertion, self).check(model) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self.pattern)
[docs]class AllObservablesInRules(RuleAssertion): def check(self, model, **kwargs): rpm = kwargs['rule_pattern_matcher'] unmatched_observables = [] for obs in model.observables: matches = rpm.match_rules(obs.reaction_pattern) if not matches: unmatched_observables.append(obs) if unmatched_observables: raise ModelAssertionFailure(assertion=self, model=model, message='Unmatched Observables: ' + str(unmatched_observables)) return True