diff --git a/.circleci/config.yml b/.circleci/config.yml index 263f8b48..8d16cb4f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,6 +35,12 @@ jobs: - run: command: python testing/tests/run1psub0.py name: Run Starts at 1 for PSUB 0 + - run: + command: python testing/tests/append_mod_test.py + name: Auto Append Model ID + - run: + command: python testing/tests/cadCAD_exp.py + name: Package Root Experiment and configs object # - run: # command: python -m unittest discover -s testing/tests -p "*_test.py" # name: Test Suite diff --git a/CHANGELOG.md b/CHANGELOG.md index 5500ea72..2ad665dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,50 @@ # Changelog: +### September 28, 2021 +#### New Features: +* **ver. ≥ `0.4.28`:** + * ##### [Experiments](documentation#experiments) + * ##### System Model Configuration + * Configurations (`cadCAD.utils.Configuration`'s) are now accessed via the `configs` member of `cadCAD.configuration.Experiment`. + [Example:](documentation#experiments) `cadCAD.configuration.Experiment().configs` + * `cadCAD.configs` has been re-included for backwards compatibility and has been assigned `cadCAD.experiment.configs` + * ##### Experiments + * `cadCAD.configuration.Experiment()` is unique representation of an experiment of one or more configured System + Models. + * The `cadCAD` module now contains a default Experiment object `cadCAD.experiment` as an instantiation of + `cadCAD.configuration.Experiment()` + * An `Experiment`'s `append_model` method stores multiple system model `Configuration`'s for simulation + execution within `cadCAD.configuration.Experiment().configs`. + `cadCAD.configuration.Experiment().model_ids` contains system model labels and/or indexes for + `cadCAD.configuration.Experiment().configs` + * The `Experiment`'s `append_model` method is defined with `model_id` parameter that accepts a system model + label. + * If duplicate `model_id`'s are specified, an index is appended to the label after the `@` symbol. + (Example: `cadCAD.configuration.Experiment().model_ids = ['sys_model', 'sys_model@1', 'sys_model@2', ...]`) + * If `model_id`'s are not specified or duplicate, the label is auto-generated as a string indicating the + system model index within `cadCAD.configuration.Experiment().configs`. + (Example of unspecified system models at indexes 1, 3, and 4: + `cadCAD.configuration.Experiment().model_ids = ['sys_model', '1', 'sys_model@2', '3', '4', ...]`) + * ##### [Upgrade Guide:](https://github.com/cadCAD-org/cadCAD/blob/master/documentation/cadCAD-v0.4.28-Model-Upgrade-Guide.md) specific to feature changes / additions + ### August 25, 2021 #### New Features: * **ver. ≥ `0.4.27`:** * ##### [Experiments](documentation#experiments) - * ##### [System Model Configurations] + * ##### System Model Configurations * Configurations (`cadCAD.utils.Configuration`'s) as are no longer a part of the `cadCAD` module (as `cadCAD.configs`) and are now accessed via the `configs` member of `cadCAD.configuration.Experiment`. [Example:](documentation#experiments) `cadCAD.configuration.Experiment().configs` - * `cadCAD.configuration.Experiment` is unique representation of an experiment of one or more configured System - Models. An `Experiment`'s `append_model` method stores multiple system model `Configuration`'s for simulation - execution. - * The `Experiment`'s `append_model` method requires a `model_id` parameter that is auto-created as `'sys_model_#'`. - * **Requirements:** - * Users must use different `model_id`'s when appending multiple System Model Configurations. - * Users can no longer use the `config_list` method of `cadCAD.configuration.Experiment` - * **Backwards Compatibility:** The `append_model` method of `cadCAD.configuration.Experiment` can also be used as - the `append_configs` method. + * ##### Experiments + * `cadCAD.configuration.Experiment` is unique representation of an experiment of one or more configured System + Models. An `Experiment`'s `append_model` method stores multiple system model `Configuration`'s for simulation + execution. + * The `Experiment`'s `append_model` method requires a `model_id` parameter that is auto-created as `'sys_model_#'`. + * **Requirements:** + * Users must use different `model_id`'s when appending multiple System Model Configurations. + * Users can no longer use the `config_list` method of `cadCAD.configuration.Experiment` + * **Backwards Compatibility:** The `append_model` method of `cadCAD.configuration.Experiment` can also be used as + the `append_configs` method. * Removed [Nix](https://nixos.org/) * ##### [Upgrade Guide:](https://github.com/cadCAD-org/cadCAD/blob/master/documentation/cadCAD-v0.4.27-Model-Upgrade-Guide.md) specific to feature changes / additions * **Fixes:** diff --git a/README.md b/README.md index bb77351c..122565cb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ / ___/ __` / __ / / / /| | / / / / / /__/ /_/ / /_/ / /___/ ___ |/ /_/ / \___/\__,_/\__,_/\____/_/ |_/_____/ -by cadCAD ver. 0.4.27 +by cadCAD ver. 0.4.28 ====================================== Complex Adaptive Dynamics o i e @@ -20,7 +20,7 @@ through simulation, with support for Monte Carlo methods, A/B testing and parame # Getting Started -#### Change Log: [ver. 0.4.27](CHANGELOG.md) +#### Change Log: [ver. 0.4.28](CHANGELOG.md) [Previous Stable Release (No Longer Supported)](https://github.com/cadCAD-org/cadCAD/tree/b9cc6b2e4af15d6361d60d6ec059246ab8fbf6da) @@ -47,7 +47,7 @@ $ ## 1. Installation: Requires [>= Python 3.6.13](https://www.python.org/downloads/) -**Option A: Install Using **[pip](https://pypi.org/project/cadCAD/0.4.27/)** +**Option A: Install Using **[pip](https://pypi.org/project/cadCAD/0.4.28/)** ```bash pip3 install cadCAD ``` diff --git a/cadCAD/__init__.py b/cadCAD/__init__.py index 9cb5946c..fbfbaf52 100644 --- a/cadCAD/__init__.py +++ b/cadCAD/__init__.py @@ -1,7 +1,10 @@ import os, dill +from cadCAD.configuration import Experiment name = "cadCAD" -version = "0.4.27" +version = "0.4.28" +experiment = Experiment() +configs = experiment.configs if os.name == 'nt': dill.settings['recurse'] = True diff --git a/cadCAD/configuration/__init__.py b/cadCAD/configuration/__init__.py index 08c0ac7c..b7e191f8 100644 --- a/cadCAD/configuration/__init__.py +++ b/cadCAD/configuration/__init__.py @@ -59,11 +59,10 @@ def __init__(self): self.exp_window = deque([self.exp_id, None], 2) self.subset_window = deque([self.subset_id, None], 2) - def append_model( self, user_id='cadCAD_user', - model_id='sys_model_#', + model_id=None, sim_configs={}, initial_state={}, seeds={}, raw_exogenous_states={}, env_processes={}, partial_state_update_blocks={}, policy_ops=[lambda a, b: a + b], _exo_update_per_ts: bool = True, **kwargs # config_list=deepcopy(global_configs) @@ -106,6 +105,18 @@ def append_model( sim_cnt_local += 1 + if model_id == None: + new_model_id = str(len(self.model_ids)) + if new_model_id in self.model_ids: + model_id = f"model@{len(self.model_ids)}" + else: + model_id = str(new_model_id) + elif model_id != None: + if model_id in self.model_ids: + model_id = f"{model_id}@{len(self.model_ids)}" + else: + model_id = str(model_id) + run_id = 0 new_model_ids, new_configs = [], [] for sim_config in new_sim_configs: @@ -161,8 +172,8 @@ def append_model( self.model_ids.append(model_id) else: except_str = f""" - Error: Duplicate model_id in Experiment - \'{model_id}\' in {self.model_ids} - -- Specify unique model_id for each use of `.append_config` per `Experiment()` + Error: Duplicate model_id in Experiment - \'{model_id}\' in {self.model_ids} + -- Specify unique model_id for each use of `.append_model` per `Experiment()` """ raise Exception(except_str) @@ -264,4 +275,4 @@ def only_ep_handler(state_dict): sdf_values, bdf_values = only_ep_handler(initial_state) zipped_list = list(zip(sdf_values, bdf_values)) - return list(map(lambda x: (x[0] + exo_proc, x[1]), zipped_list)) + return list(map(lambda x: (x[0] + exo_proc, x[1]), zipped_list)) \ No newline at end of file diff --git a/dist/cadCAD-0.4.27.tar.gz b/dist/cadCAD-0.4.27.tar.gz deleted file mode 100644 index e8d329fc..00000000 Binary files a/dist/cadCAD-0.4.27.tar.gz and /dev/null differ diff --git a/dist/cadCAD-0.4.27-py3-none-any.whl b/dist/cadCAD-0.4.28-py3-none-any.whl similarity index 67% rename from dist/cadCAD-0.4.27-py3-none-any.whl rename to dist/cadCAD-0.4.28-py3-none-any.whl index 5fd34b59..db314a31 100644 Binary files a/dist/cadCAD-0.4.27-py3-none-any.whl and b/dist/cadCAD-0.4.28-py3-none-any.whl differ diff --git a/dist/cadCAD-0.4.28.tar.gz b/dist/cadCAD-0.4.28.tar.gz new file mode 100644 index 00000000..84524405 Binary files /dev/null and b/dist/cadCAD-0.4.28.tar.gz differ diff --git a/documentation/README.md b/documentation/README.md index 616fb437..1c8ca8c4 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -12,7 +12,7 @@ cadCAD according to the definitions set by the user in [Partial State Update Blo A Simulation Configuration is comprised of a [System Model](#System-Model) and a set of [Simulation Properties](#Simulation-Properties). ### Experiments -`cadCAD.configuration.Experiment` is a unique representation of an experiment of one or more configured System Models. +`cadCAD.configuration.Experiment` is a unique representation of an experiment of one or more configured System Models. The `append_model` method of `Experiment` appends a System Model configurations, each representing a single `run`. ```python @@ -20,7 +20,7 @@ from cadCAD.configuration import Experiment exp = Experiment() exp.append_model( - model_id = ..., # System Model + model_id = ..., # OPTIONAL: System Model label initial_state = ..., # System Model partial_state_update_blocks = ..., # System Model policy_ops = ..., # System Model @@ -29,7 +29,7 @@ exp.append_model( ) ``` Parameters: `append_model` -* **model_id** : str - System Model Identification +* **model_id** : str - OPTIONAL: System Model label * **initial_state** : _dict_ - [State Variables](#State-Variables) and their initial values * **partial_state_update_blocks** : List[dict[dict]] - List of [Partial State Update Blocks](#Partial-State-Update-Blocks) * **policy_ops** : List[functions] - See [Policy Aggregation](Policy_Aggregation.md) diff --git a/documentation/cadCAD-v0.4.28-Model-Upgrade-Guide.md b/documentation/cadCAD-v0.4.28-Model-Upgrade-Guide.md new file mode 100644 index 00000000..ded5adf5 --- /dev/null +++ b/documentation/cadCAD-v0.4.28-Model-Upgrade-Guide.md @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + +
+ Feature + + ver. 0.4.28 + + ver. 0.4.23 +
+
+ Experiments & System Model Configurations +
+
+
+from cadCAD.configuration import Experiment
+
+exp = Experiment()
+exp.append_model(
+    model_id = 'sys_model_1', # System Model - OPTIONAL
+    initial_state = ..., # System Model
+    partial_state_update_blocks = ..., # System Model
+    policy_ops = ..., # System Model
+    sim_configs = ..., # Simulation Properties
+)
+exp.append_model(...)
+
+configs = exp.configs
+
+
+
+from cadCAD import configs
+from cadCAD.configuration import Experiment
+exp = Experiment()
+exp.append_configs(...)       
+
+
+
+ cadCAD Post-Processing Modifications +
+
+
+import pandas as pd
+from tabulate import tabulate
+from cadCAD.engine import ExecutionMode, ExecutionContext, Executor
+from simulations.regression_tests.experiments import multi_exp
+from simulations.regression_tests.models import config_multi_1, config_multi_2
+
+exec_mode = ExecutionMode()
+
+local_proc_ctx = ExecutionContext(context=exec_mode.local_mode)
+run = Executor(exec_context=local_proc_ctx, configs=multi_exp.configs)
+
+raw_result, tensor_fields, _ = run.execute()
+result = pd.DataFrame(raw_result)
+print(tabulate(tensor_fields[0], headers='keys', tablefmt='psql'))
+print(tabulate(result, headers='keys', tablefmt='psql'))
+
+
+
+import pandas as pd
+from tabulate import tabulate
+from cadCAD.engine import ExecutionMode, ExecutionContext, Executor
+import system_model_A, system_model_B
+
+from cadCAD import configs
+exec_mode = ExecutionMode()
+
+local_ctx = ExecutionContext(context=exec_mode.local_mode)
+simulation = Executor(exec_context=local_ctx, configs=configs)
+raw_result, sys_model, _ = simulation.execute()
+result = pd.DataFrame(raw_result)
+print(tabulate(result, headers='keys', tablefmt='psql'))           
+   
+
diff --git a/setup.py b/setup.py index 81a7c5c5..795f5b96 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ """ name = "cadCAD" -version = "0.4.27" +version = "0.4.28" setup(name=name, version=version, diff --git a/testing/models/__init__.py b/testing/models/__init__.py index e69de29b..e47f7858 100644 --- a/testing/models/__init__.py +++ b/testing/models/__init__.py @@ -0,0 +1,3 @@ +from cadCAD.configuration import Experiment + +exp = Experiment() \ No newline at end of file diff --git a/testing/models/param_sweep.py b/testing/models/param_sweep.py index 55658c5c..b2558bef 100644 --- a/testing/models/param_sweep.py +++ b/testing/models/param_sweep.py @@ -1,6 +1,7 @@ import pprint from typing import Dict, List +from cadCAD import experiment from cadCAD.configuration import Experiment from cadCAD.configuration.utils import env_trigger, var_substep_trigger, config_sim, psub_list @@ -93,3 +94,9 @@ def sweeped(_g, step, sL, s, _input, **kwargs): env_processes=env_process, partial_state_update_blocks=partial_state_update_blocks ) +experiment.append_model( + sim_configs=sim_config, + initial_state=genesis_states, + env_processes=env_process, + partial_state_update_blocks=partial_state_update_blocks +) diff --git a/testing/tests/append_mod_test.py b/testing/tests/append_mod_test.py new file mode 100644 index 00000000..51a43627 --- /dev/null +++ b/testing/tests/append_mod_test.py @@ -0,0 +1,78 @@ +import unittest +from copy import deepcopy + +from testing.models import exp +from testing.models.param_sweep import sim_config as sim_config_a +from testing.models.param_sweep import genesis_states as genesis_states_a +from testing.models.param_sweep import env_process as env_process_a +from testing.models.param_sweep import partial_state_update_blocks as psubs_a + + +def append_model_id(model_ids, sim_config, genesis_states, env_process, psubs): + exp_copy = deepcopy(exp) + for mod_id in model_ids: + exp_copy.append_model( + model_id=mod_id, + sim_configs=sim_config, + initial_state=genesis_states, + env_processes=env_process, + partial_state_update_blocks=psubs, + policy_ops=[lambda a, b: a + b] + ) + return exp_copy + + +class AppendModelTest(unittest.TestCase): + def test_index_model_ids(self): + no_id_exp = append_model_id( + model_ids=[None] * 10, + sim_config=sim_config_a, + genesis_states=genesis_states_a, + env_process=env_process_a, + psubs=psubs_a + ) + expected = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + self.assertEqual(no_id_exp.model_ids == expected, True, "Incorrect Indexing of System Model IDs") + + def test_same_model_ids(self): + same_id_exp = append_model_id( + model_ids=['sys_model'] * 10, + sim_config=sim_config_a, + genesis_states=genesis_states_a, + env_process=env_process_a, + psubs=psubs_a + ) + expected = [ + 'sys_model', 'sys_model@1', 'sys_model@2', 'sys_model@3', 'sys_model@4', 'sys_model@5', + 'sys_model@6', 'sys_model@7', 'sys_model@8', 'sys_model@9' + ] + self.assertEqual(same_id_exp.model_ids == expected, True, "Incorrect Duplicate Indexing of System Model IDs") + + def test_different_model_ids(self): + diff_id_exp = append_model_id( + model_ids=[f'sys_model_{i}' for i in list(range(10))], + sim_config=sim_config_a, + genesis_states=genesis_states_a, + env_process=env_process_a, + psubs=psubs_a + ) + expected = [ + 'sys_model_0', 'sys_model_1', 'sys_model_2', 'sys_model_3', 'sys_model_4', 'sys_model_5', + 'sys_model_6', 'sys_model_7', 'sys_model_8', 'sys_model_9' + ] + self.assertEqual(diff_id_exp.model_ids == expected, True, "Incorrect Unique System Model IDs") + + def test_mix_model_ids(self): + mix_exp = append_model_id( + model_ids=[None, 'sys_model_A', None, 'sys_model_B', 'model@3', 'model@3'], + sim_config=sim_config_a, + genesis_states=genesis_states_a, + env_process=env_process_a, + psubs=psubs_a + ) + expected = ['0', 'sys_model_A', '2', 'sys_model_B', 'model@3', 'model@3@5'] + self.assertEqual(mix_exp.model_ids == expected, True, "Incorrect System Model ID Mix") + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/testing/tests/cadCAD_exp.py b/testing/tests/cadCAD_exp.py new file mode 100644 index 00000000..bc167283 --- /dev/null +++ b/testing/tests/cadCAD_exp.py @@ -0,0 +1,24 @@ +import unittest, pandas as pd +from tabulate import tabulate +from testing.models import param_sweep +from testing.results_comparison import dataframe_difference, compare_results +from cadCAD import configs +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor + +exec_mode = ExecutionMode() +exec_ctx = ExecutionContext(context=exec_mode.local_mode) +run = Executor(exec_context=exec_ctx, configs=configs) +raw_result, _, _ = run.execute() + +result_df = pd.DataFrame(raw_result) +expected_df = pd.read_pickle("expected_results/param_sweep_4.pkl") +result_diff = dataframe_difference(result_df, expected_df) +print(tabulate(result_diff, headers='keys', tablefmt='psql')) + + +class ParamSweepTest(compare_results(result_diff)): + pass + + +if __name__ == '__main__': + unittest.main()