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()
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
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)
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))
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