diff --git a/examples/simulation_run.py b/examples/simulation_run.py index 02e003ab..59231aaf 100644 --- a/examples/simulation_run.py +++ b/examples/simulation_run.py @@ -8,6 +8,7 @@ import os import matplotlib.pyplot as plt +import tempfile # import simulation and run results from wc_sim.simulation import Simulation @@ -17,7 +18,7 @@ # use a toy model model_filename = os.path.join(os.path.dirname(__file__), '../tests/fixtures', '2_species_1_reaction.xlsx') -results_dir = os.path.expanduser('~/tmp/checkpoints_dir') +results_dir = tempfile.mkdtemp(dir=os.path.join(os.path.expanduser('~/tmp/checkpoints_dir'))) # create and run simulation simulation = Simulation(model_filename) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 89c2cb1b..859a6a7a 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -76,7 +76,8 @@ def test_simulate(self): end_time = 30 with CaptureOutput(relay=False): num_events, results_dir = Simulation(TOY_MODEL_FILENAME).run(end_time=end_time, - results_dir=self.results_dir, checkpoint_period=10) + results_dir=self.results_dir, + checkpoint_period=10) # check time, and simulation config in checkpoints for time in Checkpoint.list_checkpoints(results_dir): @@ -93,7 +94,8 @@ def test_reseed(self): tmp_results_dir = tempfile.mkdtemp() with CaptureOutput(relay=False): num_events, results_dir = Simulation(TOY_MODEL_FILENAME).run(end_time=20, - results_dir=tmp_results_dir, checkpoint_period=5, seed=seed) + results_dir=tmp_results_dir, + checkpoint_period=5, seed=seed) results[seed] = {} results[seed]['num_events'] = num_events run_results[seed] = RunResults(results_dir) @@ -109,7 +111,8 @@ def test_reseed(self): tmp_results_dir = tempfile.mkdtemp() with CaptureOutput(relay=False): num_events, results_dir = Simulation(TOY_MODEL_FILENAME).run(end_time=20, - results_dir=tmp_results_dir, checkpoint_period=5, seed=seed) + results_dir=tmp_results_dir, + checkpoint_period=5, seed=seed) results[rep] = {} results[rep]['num_events'] = num_events run_results[rep] = RunResults(results_dir) @@ -160,43 +163,27 @@ def test_create_metadata_2(self): self.assertEqual(simulation_metadata.simulation.time_step, 1) def test_ckpt_dir_processing_1(self): - # checkpoints_dir does not exist + # checkpoints_dir gets created because it does not exist self.args['results_dir'] = os.path.join(self.results_dir, 'no_such_dir', 'no_such_sub_dir') self.simulation.process_and_validate_args(self.args) self.assertTrue(os.path.isdir(self.args['results_dir'])) def test_ckpt_dir_processing_2(self): - # checkpoints_dir exists, and is empty - root_dir = self.args['results_dir'] - self.simulation.process_and_validate_args(self.args) - # process_and_validate_args creates 1 timestamped sub-dir - self.assertEqual(len(os.listdir(root_dir)), 1) - - def test_ckpt_dir_processing_3(self): # checkpoints_dir is a file - self.args['results_dir'] = os.path.join(self.args['results_dir'], 'new_file') - try: - open(self.args['results_dir'], 'x') - with self.assertRaises(MultialgorithmError): - self.simulation.process_and_validate_args(self.args) - except FileExistsError: + path = os.path.join(self.args['results_dir'], 'new_file') + self.args['results_dir'] = path + with open(path, 'w'): pass + with self.assertRaises(MultialgorithmError): + self.simulation.process_and_validate_args(self.args) - def test_ckpt_dir_processing_4(self): - # timestamped sub-directory of checkpoints-dir already exists - root_dir = self.args['results_dir'] - self.simulation.process_and_validate_args(self.args) - # given the chance, albeit small, that the second has advanced and - # a different timestamped sub-directory is made, try repeatedly to create the error - # the for loop takes about 0.01 sec - raised = False - for i in range(10): - self.args['results_dir'] = root_dir - try: - self.simulation.process_and_validate_args(self.args) - except: - raised = True - self.assertTrue(raised) + def test_ckpt_dir_processing_3(self): + # checkpoints_dir is not empty + path = os.path.join(self.args['results_dir'], 'new_file') + with open(path, 'w'): + pass + with self.assertRaises(MultialgorithmError): + self.simulation.process_and_validate_args(self.args) def test_process_and_validate_args1(self): original_args = copy(self.args) diff --git a/wc_sim/multialgorithm_simulation.py b/wc_sim/multialgorithm_simulation.py index 891ab8a5..fc9ed46a 100644 --- a/wc_sim/multialgorithm_simulation.py +++ b/wc_sim/multialgorithm_simulation.py @@ -98,8 +98,8 @@ class MultialgorithmSimulation(object): Attributes: model (:obj:`Model`): a model description - args (:obj:`dict`): parameters for the simulation; if `results_dir` is provided, then also - must include checkpoint_period + args (:obj:`dict`): parameters for the simulation; if `results_dir` is an entry in `args`, + then `checkpoint_period` must also be included simulation (:obj:`SimulationEngine`): the initialized simulation checkpointing_sim_obj (:obj:`MultialgorithmicCheckpointingSimObj`): the checkpointing object; `None` if absent diff --git a/wc_sim/simulation.py b/wc_sim/simulation.py index 1e2cfd39..245f4de6 100644 --- a/wc_sim/simulation.py +++ b/wc_sim/simulation.py @@ -138,25 +138,19 @@ def process_and_validate_args(self, args): if 'results_dir' in args: results_sup_dir = os.path.abspath(os.path.expanduser(args['results_dir'])) - # if results_sup_dir is a file, raise error - if os.path.isfile(results_sup_dir): - raise MultialgorithmError("results_dir ({}) is a file, not a dir".format(results_sup_dir)) + if os.path.exists(results_sup_dir): + # raise error if results_sup_dir exists and is not a dir + if not os.path.isdir(results_sup_dir): + raise MultialgorithmError(f"results_dir ({results_sup_dir}) is not a dir") + + # raise error if results_sup_dir is not empty + if os.listdir(results_sup_dir): + raise MultialgorithmError(f"results_dir ({results_sup_dir}) is not empty") # if results_sup_dir does not exist, make it if not os.path.exists(results_sup_dir): os.makedirs(results_sup_dir) - # make a time-stamped sub-dir for this run - time_stamped_sub_dir = os.path.join(results_sup_dir, datetime.datetime.now().strftime( - '%Y-%m-%d-%H-%M-%S')) - if os.path.exists(time_stamped_sub_dir): - raise MultialgorithmError("timestamped sub-directory of results_dir ({}) already exists".format( - time_stamped_sub_dir)) - else: - os.makedirs(time_stamped_sub_dir) - # update results_dir - args['results_dir'] = time_stamped_sub_dir - # validate args if 'end_time' not in args: raise MultialgorithmError("Simulation end time, end_time, must be provided") @@ -166,8 +160,9 @@ def process_and_validate_args(self, args): if 'checkpoint_period' in args: if args['checkpoint_period'] <= 0 or args['end_time'] < args['checkpoint_period']: - raise MultialgorithmError("Checkpointing period ({}) must be positive and less than or equal to end time".format( - args['checkpoint_period'])) + raise MultialgorithmError("Checkpointing period ({}) must be positive and " + "less than or equal to end time".format( + args['checkpoint_period'])) if args['end_time'] / args['checkpoint_period'] % 1 != 0: raise MultialgorithmError('end_time ({}) must be a multiple of checkpoint_period ({})'.format( @@ -211,16 +206,13 @@ def run(self, end_time, results_dir=None, checkpoint_period=None, time_step=1, s simulation_args['checkpoint_period'] = checkpoint_period self.process_and_validate_args(simulation_args) - timestamped_results_dir = None - if results_dir: - timestamped_results_dir = simulation_args['results_dir'] multialgorithm_simulation = MultialgorithmSimulation(self.model, simulation_args) self.simulation_engine, self.dynamic_model = multialgorithm_simulation.build_simulation() self.simulation_engine.initialize() - if timestamped_results_dir: - SimulationMetadata.write_metadata(self.simulation_metadata, timestamped_results_dir) + if results_dir: + SimulationMetadata.write_metadata(self.simulation_metadata, results_dir) # run simulation try: @@ -234,15 +226,15 @@ def run(self, end_time, results_dir=None, checkpoint_period=None, time_step=1, s self.simulation_metadata.run.record_end() # update metadata in file - if timestamped_results_dir: - SimulationMetadata.write_metadata(self.simulation_metadata, timestamped_results_dir) + if results_dir: + SimulationMetadata.write_metadata(self.simulation_metadata, results_dir) print('Simulated {} events'.format(num_events)) - if timestamped_results_dir: - # summarize results in an HDF5 file in timestamped_results_dir - RunResults(timestamped_results_dir) - print("Saved checkpoints and run results in '{}'".format(timestamped_results_dir)) - return (num_events, timestamped_results_dir) + if results_dir: + # summarize results in an HDF5 file in results_dir + RunResults(results_dir) + print("Saved checkpoints and run results in '{}'".format(results_dir)) + return (num_events, results_dir) else: return (num_events, None) diff --git a/wc_sim/testing/utils.py b/wc_sim/testing/utils.py index 13f9cab2..de885505 100644 --- a/wc_sim/testing/utils.py +++ b/wc_sim/testing/utils.py @@ -8,6 +8,7 @@ from collections import defaultdict import numpy as np +import os from wc_lang import Model from wc_lang.io import Reader @@ -234,7 +235,12 @@ def define_trajectory_classes(model): def verify_closed_form_model(test_case, model_filename, results_dir): - # alternatively, just load the SpeciesTrajectory & AggregateTrajectory worksheets into pandas + # empty results_dir + for file in os.listdir(results_dir): + file_path = os.path.join(results_dir, file) + if os.path.isfile(file_path): + os.unlink(file_path) + # read model while ignoring missing models, with std dev = 0 model = read_model_and_set_all_std_devs_to_0(model_filename) # simulate model