diff --git a/README.md b/README.md index 7579d8d..cdc4b38 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,14 @@ If you don't need the substeps in post-processing, you can both improve simulati experiment.engine = Engine(drop_substeps=True) ``` +#### Including parameters in results + +If you'd like to include each run's parameters directly in the experiment results (rather than have to manually construct the corresponding parameters for each run afterwards), you can specify to return parameters as part of the experiment results: + +```python +experiment.engine = Engine(return_parameters=True) +``` + #### Exception handling radCAD allows you to choose whether to raise exceptions, ending the simulation, or to continue with the remaining runs and return the results along with the exceptions. Failed runs are returned as partial results - the part of the simulation result up until the timestep where the simulation failed. diff --git a/radcad/core.py b/radcad/core.py index c788bcf..93a4cba 100644 --- a/radcad/core.py +++ b/radcad/core.py @@ -60,6 +60,7 @@ def _single_run( deepcopy: bool, deepcopy_method: Callable, drop_substeps: bool, + return_parameters: bool, ): logger.info(f"Starting simulation {simulation} / run {run} / subset {subset}") @@ -127,6 +128,7 @@ def single_run( deepcopy: bool=True, deepcopy_method: Callable=default_deepcopy_method, drop_substeps: bool=False, + return_parameters: bool=False, ) -> Tuple[list, Exception, str]: result = [] @@ -144,6 +146,7 @@ def single_run( deepcopy, deepcopy_method, drop_substeps, + return_parameters, ), None, # Error None, # Traceback @@ -164,6 +167,8 @@ def _single_run_wrapper(args): if raise_exceptions and exception: raise exception else: + if run_args.return_parameters: + [substep.update(run_args.parameters) for timestep in results for substep in timestep] return results, { 'exception': exception, 'traceback': traceback, diff --git a/radcad/engine.py b/radcad/engine.py index 38ebf95..24b49d0 100644 --- a/radcad/engine.py +++ b/radcad/engine.py @@ -22,6 +22,7 @@ def __init__(self, **kwargs): **deepcopy (bool): Whether to enable deepcopy of State Variables to avoid unintended state mutation. Defaults to `True`. **deepcopy_method (Callable): Method to use for deepcopy of State Variables. By default uses Pickle for improved performance, use `copy.deepcopy` for an alternative to Pickle. **drop_substeps (bool): Whether to drop simulation result substeps during runtime to save memory and improve performance. Defaults to `False`. + **return_parameters (bool): Whether to include the parameters used for each run in the simulation results at expense of increased memory usage and reduced performance. Defaults to `False`. **_run_generator (tuple_iterator): Generator to generate simulation runs, used to implement custom execution backends. Defaults to `iter(())`. """ self.executable = None @@ -31,6 +32,7 @@ def __init__(self, **kwargs): self.deepcopy = kwargs.pop("deepcopy", True) self.deepcopy_method = kwargs.pop("deepcopy_method", core.default_deepcopy_method) self.drop_substeps = kwargs.pop("drop_substeps", False) + self.return_parameters = kwargs.pop("return_parameters", False) self._run_generator = iter(()) if kwargs: @@ -136,6 +138,7 @@ def _run_stream(self, configs): deepcopy=self.deepcopy, deepcopy_method=self.deepcopy_method, drop_substeps=self.drop_substeps, + return_parameters=self.return_parameters, )) self.executable._after_subset(context=context) self.executable._after_run(context=context) diff --git a/radcad/wrappers.py b/radcad/wrappers.py index 53718ba..34c4058 100644 --- a/radcad/wrappers.py +++ b/radcad/wrappers.py @@ -20,6 +20,7 @@ class RunArgs(NamedTuple): deepcopy: bool = None deepcopy_method: bool = None drop_substeps: bool = None + return_parameters: bool = None @dataclass @@ -55,6 +56,7 @@ def __init__(self, initial_state={}, state_update_blocks=[], params={}): self._deepcopy = True self._deepcopy_method = default_deepcopy_method self._drop_substeps = False + self._return_parameters = False def __iter__(self): while True: @@ -71,6 +73,7 @@ def __iter__(self): deepcopy = self._deepcopy, deepcopy_method = self._deepcopy_method, drop_substeps = self._drop_substeps, + return_parameters = self._return_parameters, ) result, exception = _single_run_wrapper((run_args, self._raise_exceptions)) if exception: self.exceptions.append(exception) @@ -83,6 +86,7 @@ def __call__(self, **kwargs): self._deepcopy = kwargs.pop("deepcopy", True) self._deepcopy_method = kwargs.pop("deepcopy_method", default_deepcopy_method) self._drop_substeps = kwargs.pop("drop_substeps", False) + self._return_parameters = kwargs.pop("return_parameters", False) return self