Source code for pybnf.algorithms.model_check

"""ModelCheck -- the ``check`` fit type, a first-class checking method.

A checking run (statistical model checking): evaluates the objective value and
constraint satisfaction for the given parameters without searching parameter
space. It registers in the ``checker`` family -- a peer of ``optimizer`` and
``sampler``, not a utility afterthought -- but, being neither an optimizer nor a
sampler, it lives at the algorithms package top level rather than under
optimizers/ or samplers/. Extracted byte-identical (M1 Step 4). It does not
subclass Algorithm, but it does use the execution machinery: the patched names
are resolved as core.Job / core.run_job through the core module object (ADR-0001
seam), and ConstraintCounter is read from this module's namespace (tests patch
it here).
"""


from . import core
from .core import FailedSimulation
from ..pset import PSet
from ..printing import print0, print1
from ..objective import ConstraintCounter
from ..registry import register_fit_type

import logging
import os
import copy
import traceback
from pathlib import Path


# Preserve the original module logger name so log records keep the
# 'pybnf.algorithms' channel.
logger = logging.getLogger('pybnf.algorithms')


[docs] @register_fit_type('check', family='checker', display_name='Model Check') class ModelCheck: """ An algorithm that just checks the fit quality for a job with no free parameters. Does not subclass Algorithm. To run, instead call run_check() with no Cluster. """ def __init__(self, config): """ Instantiates ModelCheck with a Configuration object. :param config: The fitting configuration :type config: Configuration """ self.config = config self.exp_data = self.config.exp_data self.objective = self.config.obj self.bootstrap_number = None logger.debug('Creating output directory') if not os.path.isdir(self.config.config['output_dir']): os.mkdir(self.config.config['output_dir']) if self.config.config['simulation_dir']: self.sim_dir = str(Path(self.config.config['simulation_dir']) / 'Simulations') else: self.sim_dir = str(Path(self.config.config['output_dir']) / 'Simulations') # Store a list of all Model objects. self.model_list = copy.deepcopy(list(self.config.models.values()))
[docs] def run_check(self, debug=False): """Main loop for executing the algorithm""" print1('Running model checking on the given model(s)') empty = PSet([]) empty.name = 'check' job = core.Job(self.model_list, empty, 'check', self.sim_dir, self.config.config['wall_time_sim'], None, None, dict(), delete_folder=False, stochastic_seed_policy=self.config.config['stochastic_seed']) result = core.run_job(job, debug, self.sim_dir) if isinstance(result, FailedSimulation): print0('Simulation failed.') return result.normalize(self.config.config['normalization']) try: result.postprocess_data(self.config.postprocessing) except Exception: logger.exception('User-defined post-processing script failed') traceback.print_exc() print0('User-defined post-processing script failed. Exiting') return result.score = self.objective.evaluate_multiple(result.simdata, self.exp_data, result.pset, self.config.constraints) if result.score is None: print0('Simulation contained NaN or Inf values. Cannot calculate objective value.') return print0(f'Objective value is {result.score}') if len(self.config.constraints) > 0: counter = ConstraintCounter() fail_count = counter.evaluate_multiple(result.simdata, self.exp_data, self.config.constraints) total = sum([len(cset.constraints) for cset in self.config.constraints]) print('Satisfied %i out of %i constraints' % (total-fail_count, total)) for cset in self.config.constraints: cset.output_itemized_eval(result.simdata, self.sim_dir)