Source code for momba.analysis.checkers

# -*- coding:utf-8 -*-
#
# Copyright (C) 2020, Maximilian Köhl <koehl@cs.uni-saarland.de>

from __future__ import annotations

import dataclasses as d
import typing as t

import abc
import fractions

from .. import model


Properties = t.Mapping[str, model.Expression]
Result = t.Mapping[str, fractions.Fraction]


[docs]class Checker(abc.ABC): """An abstract class for model checkers.""" @property def description(self) -> str: return self.__class__.__name__
[docs] @abc.abstractmethod def check( self, network: model.Network, *, properties: t.Optional[Properties] = None, property_names: t.Optional[t.Iterable[str]] = None, ) -> Result: """Model checks the given properties on the network.""" raise NotImplementedError()
class CrossCheckError(Exception): pass class PropertyMismatchError(Exception): pass class DeltaExceededError(Exception): pass @d.dataclass(frozen=True, eq=False) class CrossChecker(Checker): checkers: t.Sequence[Checker] allowed_delta: fractions.Fraction = fractions.Fraction("1e-4") @property def description(self) -> str: return f"CrossChecker (Δ = {float(self.allowed_delta)})" def check( self, network: model.Network, *, properties: t.Optional[Properties] = None, property_names: t.Optional[t.Iterable[str]] = None, ) -> Result: assert self.checkers, "checkers for cross-checking must be non-empty" results = [ checker.check(network, properties=properties, property_names=property_names) for checker in self.checkers ] names = frozenset(results[0].keys()) for result in results: if names != result.keys(): raise PropertyMismatchError( "checkers did not provide results for the same properties" ) for first in range(len(self.checkers)): for second in range(first + 1, len(self.checkers)): for name in names: first_value = results[first][name] second_value = results[second][name] if abs(first_value - second_value) > self.allowed_delta: raise DeltaExceededError( f"allowed delta of {self.allowed_delta} has been exceeded for {name}" ) return results[0]