Пример #1
0
    def test_validate(self):
        try:
            self.simulation_config.validate()
        except SimulatorError:
            self.fail(
                "self.simulation_config.validate() shouldn't raise SimulatorError"
            )

        # validate with defaults
        simulation_config = SimulationConfig(self.time_max)
        try:
            simulation_config.validate()
        except SimulatorError:
            self.fail(
                "simulation_config.validate() shouldn't raise SimulatorError")

        self.simulation_config.time_max = self.time_init - 1
        with self.assertRaisesRegex(
                SimulatorError,
                'time_max .* must be greater than time_init .*'):
            self.simulation_config.validate()

        self.simulation_config.time_max = 10
        self.simulation_config.profile = True
        self.simulation_config.object_memory_change_interval = 100
        with self.assertRaisesRegex(
                SimulatorError,
                'profile and object_memory_change_interval cannot both be active'
        ):
            self.simulation_config.validate()
Пример #2
0
    def _get_sim_config(time_max=None, sim_config=None, config_dict=None):
        """ External simulate interface

        Legal combinations of the three parameters:

        1. Just `time_max`
        2. Just `sim_config`, which will contain an entry for `time_max`
        3. Just `config_dict`, which must contain an entry for `time_max`

        Other combinations are illegal.

        Args:
            time_max (:obj:`float`, optional): the time of the end of the simulation
            sim_config (:obj:`SimulationConfig`, optional): the simulation run's configuration
            config_dict (:obj:`dict`, optional): a dictionary with keys chosen from the field names
                in :obj:`SimulationConfig`; note that `config_dict` is not a `kwargs` argument

        Returns:
            :obj:`SimulationConfig`: a validated simulation configuration

        Raises:
            :obj:`SimulatorError`: if no arguments are provided, or multiple arguments are provided,
                or `time_max` is missing from `config_dict`
        """
        num_args = 0
        if time_max is not None:
            num_args += 1
        if sim_config is not None:
            num_args += 1
        if config_dict:
            num_args += 1
        if num_args == 0:
            raise SimulatorError('time_max, sim_config, or config_dict must be provided')
        if 1 < num_args:
            raise SimulatorError('at most 1 of time_max, sim_config, or config_dict may be provided')

        # catch common error generated when sim_config= is not used by SimulationEngine.simulate(sim_config)
        if isinstance(time_max, SimulationConfig):
            raise SimulatorError(f"sim_config is not provided, sim_config= is probably needed")

        # initialize sim_config if it is not provided
        if sim_config is None:
            if time_max is not None:
                sim_config = SimulationConfig(time_max)
            else:   # config_dict must be initialized
                if 'time_max' not in config_dict:
                    raise SimulatorError('time_max must be provided in config_dict')
                sim_config = SimulationConfig(**config_dict)

        sim_config.validate()
        return sim_config
Пример #3
0
    def test_all_fields(self):
        profile = True
        kwargs = dict(time_max=self.time_max,
                      time_init=self.time_init,
                      stop_condition=self.stop_condition,
                      output_dir=self.output_dir,
                      progress=self.progress,
                      profile=profile)
        simulation_config = SimulationConfig(self.time_max, self.time_init,
                                             self.stop_condition,
                                             self.output_dir, self.progress,
                                             profile)
        simulation_config.validate()
        for attr, value in kwargs.items():
            self.assertEquals(getattr(simulation_config, attr), value)

        simulation_config = SimulationConfig(**kwargs)
        simulation_config.validate()
        for attr, value in kwargs.items():
            self.assertEquals(getattr(simulation_config, attr), value)
Пример #4
0
class TestSimulationConfig(unittest.TestCase):
    def setUp(self):
        self.tmp_dir = tempfile.mkdtemp()
        self.time_max = 10.0
        self.time_init = 3.5

        class ExampleClass(object):
            def f():
                pass

        ec = ExampleClass()
        self.stop_condition = ec.f

        self.progress = True
        self.output_dir = self.tmp_dir
        self.simulation_config = SimulationConfig(self.time_max,
                                                  self.time_init,
                                                  self.stop_condition,
                                                  self.output_dir,
                                                  self.progress)

    def tearDown(self):
        shutil.rmtree(self.tmp_dir)

    def test(self):
        self.assertEquals(self.simulation_config.time_max, self.time_max)
        self.assertEquals(self.simulation_config.time_init, self.time_init)
        self.assertEquals(self.simulation_config.stop_condition,
                          self.stop_condition)
        self.assertEquals(self.simulation_config.progress, self.progress)
        self.assertEquals(self.simulation_config.output_dir, self.output_dir)

        # check defaults
        simulation_config = SimulationConfig(self.time_max)
        self.assertEquals(simulation_config.time_init, 0.0)
        self.assertEquals(simulation_config.stop_condition, None)
        self.assertEquals(simulation_config.progress, False)
        self.assertEquals(simulation_config.output_dir, None)

        # check keywords
        simulation_config = SimulationConfig(time_max=self.time_max)
        self.assertEquals(simulation_config.time_max, self.time_max)

    def test_setattr(self):

        # accept ints in float fields
        simulation_config = SimulationConfig(int(self.time_max))
        self.assertEquals(simulation_config.time_max, self.time_max)

        time_max = 'no'
        with self.assertRaisesRegex(SimulatorError,
                                    'time_max .* must be a float'):
            SimulationConfig(time_max)

        with self.assertRaisesRegex(SimulatorError,
                                    'time_init .* must be a float'):
            SimulationConfig(self.time_max, time_init=set())

    def test_validate_individual_fields(self):

        with self.assertRaisesRegex(SimulatorError,
                                    "stop_condition .* must be a function"):
            cfg = SimulationConfig(self.time_max, stop_condition='hi')
            cfg.validate_individual_fields()

        # test output_dir
        # test output_dir specified relative to home directory
        usr_tmp = os.path.join('~', 'tmp')
        output_dir = os.path.join(usr_tmp, 'output_dir')
        cfg = SimulationConfig(self.time_max, output_dir=output_dir)
        self.assertEquals(cfg.validate_individual_fields(), None)
        self.assertTrue(os.path.isdir(cfg.output_dir))

        _, tmp_file = tempfile.mkstemp(dir=self.tmp_dir)
        with self.assertRaisesRegex(SimulatorError,
                                    "output_dir .* must be a directory"):
            cfg = SimulationConfig(self.time_max, output_dir=tmp_file)
            cfg.validate_individual_fields()

        with self.assertRaisesRegex(SimulatorError,
                                    "output_dir .* is not empty"):
            cfg = SimulationConfig(self.time_max, output_dir=self.tmp_dir)
            cfg.validate_individual_fields()

        # output_dir gets created because it does not exist
        output_dir = os.path.join(self.tmp_dir, 'no_such_dir',
                                  'no_such_sub_dir')
        cfg = SimulationConfig(self.time_max, output_dir=output_dir)
        cfg.validate_individual_fields()
        self.assertTrue(os.path.isdir(cfg.output_dir))

        # output_dir already exists
        tmp_dir = tempfile.mkdtemp(dir=self.tmp_dir)
        cfg = SimulationConfig(self.time_max, output_dir=tmp_dir)
        self.assertEquals(cfg.validate_individual_fields(), None)

        with self.assertRaisesRegex(
                SimulatorError,
                "object_memory_change_interval .* must be non-negative"):
            cfg = SimulationConfig(self.time_max,
                                   object_memory_change_interval=-3)
            cfg.validate_individual_fields()

    def test_all_fields(self):
        profile = True
        kwargs = dict(time_max=self.time_max,
                      time_init=self.time_init,
                      stop_condition=self.stop_condition,
                      output_dir=self.output_dir,
                      progress=self.progress,
                      profile=profile)
        simulation_config = SimulationConfig(self.time_max, self.time_init,
                                             self.stop_condition,
                                             self.output_dir, self.progress,
                                             profile)
        simulation_config.validate()
        for attr, value in kwargs.items():
            self.assertEquals(getattr(simulation_config, attr), value)

        simulation_config = SimulationConfig(**kwargs)
        simulation_config.validate()
        for attr, value in kwargs.items():
            self.assertEquals(getattr(simulation_config, attr), value)

    def test_validate(self):
        try:
            self.simulation_config.validate()
        except SimulatorError:
            self.fail(
                "self.simulation_config.validate() shouldn't raise SimulatorError"
            )

        # validate with defaults
        simulation_config = SimulationConfig(self.time_max)
        try:
            simulation_config.validate()
        except SimulatorError:
            self.fail(
                "simulation_config.validate() shouldn't raise SimulatorError")

        self.simulation_config.time_max = self.time_init - 1
        with self.assertRaisesRegex(
                SimulatorError,
                'time_max .* must be greater than time_init .*'):
            self.simulation_config.validate()

        self.simulation_config.time_max = 10
        self.simulation_config.profile = True
        self.simulation_config.object_memory_change_interval = 100
        with self.assertRaisesRegex(
                SimulatorError,
                'profile and object_memory_change_interval cannot both be active'
        ):
            self.simulation_config.validate()

    simulation_config_no_stop_cond = SimulationConfig(
        10.0, 3.5, output_dir=tempfile.mkdtemp(), progress=True)

    def test_deepcopy(self):
        simulation_config_copy = copy.deepcopy(
            self.simulation_config_no_stop_cond)
        self.assertEquals(self.simulation_config_no_stop_cond,
                          simulation_config_copy)

    def test_semantically_equal(self):
        simulation_config = SimulationConfig(self.time_max, self.time_init,
                                             self.stop_condition,
                                             self.output_dir, self.progress)
        self.assertTrue(
            self.simulation_config.semantically_equal(simulation_config))
        simulation_config.progress = not simulation_config.progress
        # progress is not semantically meaningful
        self.assertTrue(
            self.simulation_config.semantically_equal(simulation_config))
        simulation_config.time_max += 1E-12
        self.assertFalse(
            self.simulation_config.semantically_equal(simulation_config))
Пример #5
0
class Simulation(object):
    """ Simulate a multialgorithm model

    Attributes:
        model_path (:obj:`str`): path to a file describing a `wc_lang` model
        model (:obj:`Model`): a `wc_lang` model description
        dynamic_model (:obj:`DynamicModel`): the simulation's :obj:`DynamicModel`
        de_sim_config (:obj:`SimulationConfig`): a DE-Sim simulation configuration
        author_metadata (:obj:`AuthorMetadata`): metadata about the author of a whole-cell simulation run
        wc_sim_config (:obj:`WCSimulationConfig`): a WC-Sim simulation configuration
        simulation_engine (:obj:`SimulationEngine`): the simulation engine
    """
    def __init__(self, model):
        """
        Args:
            model (:obj:`str` or `Model`): either a path to file(s) describing a `wc_lang` model, or
                a `Model` instance

        Raises:
            :obj:`MultialgorithmError`: if `model` is invalid
        """
        if isinstance(model, Model):
            self.model_path = None
            self.model = model
        elif isinstance(model, str):
            # read model
            self.model_path = os.path.abspath(os.path.expanduser(model))
            self.model = Reader().run(self.model_path)[Model][0]
        else:
            raise MultialgorithmError(
                "model must be a `wc_lang Model` or a pathname for a model, "
                "but its type is {}".format(type(model)))

    def _prepare(self):
        """ Prepare and validate the model, and create simulation metadata
        """
        # prepare & check the model
        PrepForWcSimTransform().run(self.model)
        errors = Validator().run(self.model)
        if errors:
            raise MultialgorithmError(
                indent_forest(['The model is invalid:', [errors]]))

    def run(self,
            time_max,
            results_dir=None,
            progress_bar=True,
            checkpoint_period=None,
            seed=None,
            ode_time_step=None,
            dfba_time_step=None,
            profile=False,
            submodels_to_skip=None,
            verbose=True,
            options=None):
        """ Run one simulation

        Args:
            time_max (:obj:`float`): the maximum time of a simulation; a stop condition may end it
                earlier (sec)
            results_dir (:obj:`str`, optional): path to a directory in which results are stored
            progress_bar (:obj:`bool`, optional): whether to show the progress of a simulation in
                in a real-time bar on a terminal
            checkpoint_period (:obj:`float`, optional): the period between simulation state checkpoints (sec)
            ode_time_step (:obj:`float`, optional): time step length of ODE submodel (sec)
            dfba_time_step (:obj:`float`, optional): time step length of dFBA submodel (sec)
            profile (:obj:`bool`, optional): whether to output a profile of the simulation's performance
                created by a Python profiler
            seed (:obj:`object`, optional): a seed for the simulation's `numpy.random.RandomState`;
                if provided, `seed` will reseed the simulator's PRNG
            submodels_to_skip (:obj:`list` of :obj:`str`, optional): submodels that should not be run,
                identified by their ids
            verbose (:obj:`bool`, optional): whether to print success output
            options (:obj:`dict`, optional): options for submodels, passed to `MultialgorithmSimulation`

        Returns:
            :obj:`tuple` of (`int`, `str`): number of simulation events, pathname of directory
                containing the results, or :obj:`tuple` of (`int`, `None`): number of simulation events,
                `None` if `results_dir is None`, or :obj:`tuple` of (`pstats.Stats`, `None`): profile
                stats, `None` if `profile is True`

        Raises:
            :obj:`MultialgorithmError`: if the simulation raises an exception
        """
        self._prepare()

        # create simulation configurations
        # create and validate DE sim configuration
        self.de_sim_config = SimulationConfig(time_max,
                                              output_dir=results_dir,
                                              progress=progress_bar,
                                              profile=profile)
        self.de_sim_config.validate()

        # create and validate WC configuration
        self.wc_sim_config = WCSimulationConfig(
            de_simulation_config=self.de_sim_config,
            random_seed=seed,
            ode_time_step=ode_time_step,
            dfba_time_step=dfba_time_step,
            checkpoint_period=checkpoint_period,
            submodels_to_skip=submodels_to_skip,
            verbose=verbose)
        self.wc_sim_config.validate()

        # create author metadata for DE sim
        try:
            username = getpass.getuser()
        except KeyError:  # pragma: no cover
            username = '******'
        self.author_metadata = AuthorMetadata(
            name='Unknown name',
            email='Unknown email',
            username=username,
            organization='Unknown organization')

        # create WC sim metadata
        wc_simulation_metadata = WCSimulationMetadata(self.wc_sim_config)
        if self.model_path is not None:
            wc_simulation_metadata.set_wc_model_repo(self.model_path)

        if seed is not None:
            RandomStateManager.initialize(seed=seed)

        # create a multi-algorithmic simulator
        multialgorithm_simulation = MultialgorithmSimulation(
            self.model, self.wc_sim_config, options)
        self.simulation_engine, self.dynamic_model = multialgorithm_simulation.build_simulation(
        )
        self.simulation_engine.initialize()

        # set stop_condition after the dynamic model is created
        self.de_sim_config.stop_condition = self.dynamic_model.get_stop_condition(
        )

        # run simulation
        try:
            # provide DE config and author metadata to DE sim
            simulate_rv = self.simulation_engine.simulate(
                sim_config=self.de_sim_config,
                author_metadata=self.author_metadata)

            # add WC sim metadata to the output after the simulation, which requires an empty output dir
            # TODO: have simulation_engine.simulate() allow certain files in self.de_sim_config.output_dir, and move
            # this code above
            if self.de_sim_config.output_dir is not None:
                WCSimulationMetadata.write_dataclass(
                    wc_simulation_metadata, self.de_sim_config.output_dir)

            if profile:
                stats = simulate_rv
                return stats, None
            else:
                num_events = simulate_rv

        except SimulatorError as e:  # pragma: no cover
            raise MultialgorithmError(
                f'Simulation terminated with simulator error:\n{e}')
        except BaseException as e:  # pragma: no cover
            raise MultialgorithmError(
                f'Simulation terminated with error:\n{e}')

        if verbose:
            print(f'Simulated {num_events} events')
        if results_dir:
            # summarize results in an HDF5 file in results_dir
            RunResults(results_dir)
            if verbose:
                print(f"Saved checkpoints and run results in '{results_dir}'")
            return (num_events, results_dir)
        else:
            return (num_events, None)

    def get_simulation_engine(self):
        """ Provide the simulation's simulation engine

        Returns:
            :obj:`SimulationEngine`: the simulation's simulation engine
        """
        if hasattr(self, 'simulation_engine'):
            return self.simulation_engine
        return None

    def provide_event_counts(self):
        """ Provide the last simulation's categorized event counts

        Returns:
            :obj:`str`: the last simulation's categorized event counts, in a tab-separated table
        """
        if self.get_simulation_engine():
            return self.get_simulation_engine().provide_event_counts()
        return 'execute run() to obtain event counts'

    def run_batch(self, results_dir,
                  checkpoint_period):  # pragma: no cover  # not implemented
        """ Run all simulations specified by the simulation configuration

        Args:
            results_dir (:obj:`str`): path to a directory in which results should be stored
            checkpoint_period (:obj:`float`): the period between simulation state checkpoints (sec)

        Returns:
            :obj:`tuple` of (`int`, `str`): number of simulation events, pathname of directory
                containing the results
        """
        # todo: implement; iterate over sim configs
        for simulation in self.sim_config.iterator():
            # setup simulation changes
            pass