diff --git a/README.md b/README.md index 0719875..f919ca9 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,41 @@ **Description:** -cadCAD is a differential games based simulation software package for research, validation, and Computer \ -Aided Design of economic systems. An economic system is treated as a state based model and defined through a \ -set of endogenous and exogenous state variables which are updated through mechanisms and environmental \ -processes, respectively. Behavioral models, which may be deterministic or stochastic, provide the evolution of \ -the system within the action space of the mechanisms. Mathematical formulations of these economic games \ -treat agent utility as derived from state rather than direct from action, creating a rich dynamic modeling framework. - -Simulations may be run with a range of initial conditions and parameters for states, behaviors, mechanisms, \ -and environmental processes to understand and visualize network behavior under various conditions. Support for \ -A/B testing policies, monte carlo analysis and other common numerical methods is provided. +cadCAD (complex adaptive systems computer-aided design) is a python based, unified modeling framework for stochastic +dynamical systems and differential games for research, validation, and Computer Aided Design of economic systems created +by BlockScience. It is capable of modeling systems at all levels of abstraction from Agent Based Modeling (ABM) to +System Dynamics (SD), and enabling smooth integration of computational social science simulations with empirical data +science workflows. +An economic system is treated as a state-based model and defined through a set of endogenous and exogenous state +variables which are updated through mechanisms and environmental processes, respectively. Behavioral models, which may +be deterministic or stochastic, provide the evolution of the system within the action space of the mechanisms. +Mathematical formulations of these economic games treat agent utility as derived from the state rather than direct from +an action, creating a rich, dynamic modeling framework. Simulations may be run with a range of initial conditions and +parameters for states, behaviors, mechanisms, and environmental processes to understand and visualize network behavior +under various conditions. Support for A/B testing policies, Monte Carlo analysis, and other common numerical methods is +provided. + + +In essence, cadCAD tool allows us to represent a company’s or community’s current business model along with a desired +future state and helps make informed, rigorously tested decisions on how to get from today’s stage to the future state. +It allows us to use code to solidify our conceptualized ideas and see if the outcome meets our expectations. We can +iteratively refine our work until we have constructed a model that closely reflects reality at the start of the model, +and see how it evolves. We can then use these results to inform business decisions. + +#### Simulation Instructional: +* ##### [System Model Configuration](link) +* ##### [System Simulation Execution](link) + +#### Installation: **1. Install Dependencies:** **Option A:** Package Repository Access ***IMPORTANT NOTE:*** Tokens are issued to and meant to be used by trial users and BlockScience employees **ONLY**. Replace \ with an issued token in the script below. ```bash -pip3 install pandas pathos fn tabulate +pip3 install pandas pathos fn funcy tabulate pip3 install cadCAD --extra-index-url https://@repo.fury.io/blockscience/ ``` diff --git a/cadCAD/configuration/utils/userDefinedObject.py b/cadCAD/configuration/utils/userDefinedObject.py index c1f532c..4ced71f 100644 --- a/cadCAD/configuration/utils/userDefinedObject.py +++ b/cadCAD/configuration/utils/userDefinedObject.py @@ -5,7 +5,6 @@ from pandas.core.frame import DataFrame from cadCAD.utils import SilentDF - def val_switch(v): if isinstance(v, DataFrame) is True: return SilentDF(v) @@ -18,7 +17,7 @@ class udcView(object): self.masked_members = masked_members # returns dict to dataframe - # def __repr__(self): + def __repr__(self): members = {} variables = { @@ -26,9 +25,20 @@ class udcView(object): if str(type(v)) != "" and k not in self.masked_members # and isinstance(v, DataFrame) is not True } members['methods'] = [k for k, v in self.__dict__.items() if str(type(v)) == ""] + members.update(variables) return f"{members}" #[1:-1] + # def __repr__(self): + # members = {} + # variables = { + # k: val_switch(v) for k, v in self.__dict__.items() + # if str(type(v)) != "" and k not in self.masked_members and k == 'x' # and isinstance(v, DataFrame) is not True + # } + # + # members.update(variables) + # return f"{members}" #[1:-1] + class udcBroker(object): def __init__(self, obj, function_filter=['__init__']): @@ -36,7 +46,8 @@ class udcBroker(object): funcs = dict(getmembers(obj, ismethod)) filtered_functions = {k: v for k, v in funcs.items() if k not in function_filter} d['obj'] = obj - d.update(deepcopy(vars(obj))) # somehow is enough + # d.update(deepcopy(vars(obj))) # somehow is enough + d.update(vars(obj)) # somehow is enough d.update(filtered_functions) self.members_dict = d @@ -57,4 +68,3 @@ def UDO(udo, masked_members=['obj']): def udoPipe(obj_view): return UDO(obj_view.obj, obj_view.masked_members) - diff --git a/cadCAD/engine/simulation.py b/cadCAD/engine/simulation.py index 8a5c5df..1823a34 100644 --- a/cadCAD/engine/simulation.py +++ b/cadCAD/engine/simulation.py @@ -115,12 +115,48 @@ class Executor: last_in_obj: Dict[str, Any] = deepcopy(sL[-1]) _input: Dict[str, Any] = self.policy_update_exception(self.get_policy_input(sweep_dict, sub_step, sH, last_in_obj, policy_funcs)) + # ToDo: add env_proc generator to `last_in_copy` iterator as wrapper function # ToDo: Can be multithreaded ?? def generate_record(state_funcs): for f in state_funcs: yield self.state_update_exception(f(sweep_dict, sub_step, sH, last_in_obj, _input)) + # def generate_record(state_funcs): + # for f in state_funcs: + # tmp_last_in_copy = deepcopy(last_in_obj) + # new_kv = self.state_update_exception(f(sweep_dict, sub_step, sH, tmp_last_in_copy, _input)) + # del tmp_last_in_copy + # yield new_kv + # + # # get `state` from last_in_obj.keys() + # # vals = last_in_obj.values() + # def generate_record(state_funcs): + # for state, v, f in zip(states, vals, state_funcs): + # v_copy = deepcopy(v) + # last_in_obj[state] = v_copy + # new_kv = self.state_update_exception(f(sweep_dict, sub_step, sH, last_in_copy, _input)) + # del v + # yield new_kv + + # {k: v for k, v in l} + + # r() - r(a') -> r(a',b') -> r(a',b',c') + + # r(f(a),b,c) -> r(a'f(b),c) -> r(a',b',f(c)) => r(a',b',c') + # r(a',b.update(),c) + # r1(f(a1),b1,c1) -> r2(a2,f(b1),c1) -> r3(a3,b1,f(c1)) => r(a',b',c') + + # r1(f(a1),b,c) -> r2(a,f(b1),c) -> r3(a,b,f(c1)) => r(a',b',c') + + # r1(f(a1),b1,c1) -> r(a2',b2.update(),c2) -> r3(a3,b1,f(c1)) => r(a',b',c') + + + # r1(f(a1),b1,c1) -> r2(a2,f(b1),c1) -> r3(a3,b1,f(c1)) => r(a',b',c') + + + # reduce(lambda r: F(r), [r2(f(a),b,c), r2(a,f(b),c), r3(a,b,f(c))]) => R(a',b',c') + def transfer_missing_fields(source, destination): for k in source: if k not in destination: diff --git a/cadCAD/utils/__init__.py b/cadCAD/utils/__init__.py index 44717b5..ccb5f9d 100644 --- a/cadCAD/utils/__init__.py +++ b/cadCAD/utils/__init__.py @@ -15,14 +15,12 @@ def append_dict(dict, new_dict): dict.update(new_dict) return dict - # def val_switch(v): # if isinstance(v, DataFrame) is True or isinstance(v, SilentDF) is True: # return SilentDF(v) # else: # return v.x - class IndexCounter: def __init__(self): self.i = 0 diff --git a/documentation/examples/__init__.py b/documentation/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/documentation/examples/example_1.py b/documentation/examples/example_1.py new file mode 100644 index 0000000..ed1edfc --- /dev/null +++ b/documentation/examples/example_1.py @@ -0,0 +1,45 @@ +from pprint import pprint + +import pandas as pd +from tabulate import tabulate +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from documentation.examples import sys_model_A, sys_model_B +from cadCAD import configs + +exec_mode = ExecutionMode() + +# Single Process Execution using a Single System Model Configuration: +# sys_model_A +sys_model_A = [configs[0]] +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +sys_model_A_simulation = Executor(exec_context=single_proc_ctx, configs=sys_model_A) + +sys_model_A_raw_result, sys_model_A_tensor_field = sys_model_A_simulation.execute() +sys_model_A_result = pd.DataFrame(sys_model_A_raw_result) +print() +print("Tensor Field: sys_model_A") +print(tabulate(sys_model_A_tensor_field, headers='keys', tablefmt='psql')) +print("Result: System Events DataFrame") +print(tabulate(sys_model_A_result, headers='keys', tablefmt='psql')) +print() + +# # Multiple Processes Execution using Multiple System Model Configurations: +# # sys_model_A & sys_model_B +multi_proc_ctx = ExecutionContext(context=exec_mode.multi_proc) +sys_model_AB_simulation = Executor(exec_context=multi_proc_ctx, configs=configs) + + + +i = 0 +config_names = ['sys_model_A', 'sys_model_B'] +for sys_model_AB_raw_result, sys_model_AB_tensor_field in sys_model_AB_simulation.execute(): + print() + pprint(sys_model_AB_raw_result) + # sys_model_AB_result = pd.DataFrame(sys_model_AB_raw_result) + print() + print(f"Tensor Field: {config_names[i]}") + print(tabulate(sys_model_AB_tensor_field, headers='keys', tablefmt='psql')) + # print("Result: System Events DataFrame:") + # print(tabulate(sys_model_AB_result, headers='keys', tablefmt='psql')) + # print() + i += 1 \ No newline at end of file diff --git a/documentation/examples/historical_state_access.py b/documentation/examples/historical_state_access.py new file mode 100644 index 0000000..5079988 --- /dev/null +++ b/documentation/examples/historical_state_access.py @@ -0,0 +1,111 @@ +import pandas as pd +from tabulate import tabulate +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import config_sim, access_block +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from cadCAD import configs + + +policies, variables = {}, {} +exclusion_list = ['nonexsistant', 'last_x', '2nd_to_last_x', '3rd_to_last_x', '4th_to_last_x'] + +# Policies per Mechanism + +# WARNING: DO NOT delete elements from sH +# state_history, target_field, psu_block_offset, exculsion_list +def last_update(_g, substep, sH, s): + return {"last_x": access_block( + state_history=sH, + target_field="last_x", + psu_block_offset=-1, + exculsion_list=exclusion_list + ) + } +policies["last_x"] = last_update + +def second2last_update(_g, substep, sH, s): + return {"2nd_to_last_x": access_block(sH, "2nd_to_last_x", -2, exclusion_list)} +policies["2nd_to_last_x"] = second2last_update + + +# Internal States per Mechanism + +# WARNING: DO NOT delete elements from sH +def add(y, x): + return lambda _g, substep, sH, s, _input: (y, s[y] + x) +variables['x'] = add('x', 1) + +# last_partial_state_update_block +def nonexsistant(_g, substep, sH, s, _input): + return 'nonexsistant', access_block(sH, "nonexsistant", 0, exclusion_list) +variables['nonexsistant'] = nonexsistant + +# last_partial_state_update_block +def last_x(_g, substep, sH, s, _input): + return 'last_x', _input["last_x"] +variables['last_x'] = last_x + +# 2nd to last partial state update block +def second_to_last_x(_g, substep, sH, s, _input): + return '2nd_to_last_x', _input["2nd_to_last_x"] +variables['2nd_to_last_x'] = second_to_last_x + +# 3rd to last partial state update block +def third_to_last_x(_g, substep, sH, s, _input): + return '3rd_to_last_x', access_block(sH, "3rd_to_last_x", -3, exclusion_list) +variables['3rd_to_last_x'] = third_to_last_x + +# 4th to last partial state update block +def fourth_to_last_x(_g, substep, sH, s, _input): + return '4th_to_last_x', access_block(sH, "4th_to_last_x", -4, exclusion_list) +variables['4th_to_last_x'] = fourth_to_last_x + + +genesis_states = { + 'x': 0, + 'nonexsistant': [], + 'last_x': [], + '2nd_to_last_x': [], + '3rd_to_last_x': [], + '4th_to_last_x': [] +} + +PSUB = { + "policies": policies, + "variables": variables +} + +partial_state_update_block = { + "PSUB1": PSUB, + "PSUB2": PSUB, + "PSUB3": PSUB +} + +sim_config = config_sim( + { + "N": 1, + "T": range(3), + } +) + +append_configs( + sim_configs=sim_config, + initial_state=genesis_states, + partial_state_update_blocks=partial_state_update_block +) + +exec_mode = ExecutionMode() +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +run = Executor(exec_context=single_proc_ctx, configs=configs) + +raw_result, tensor_field = run.execute() +result = pd.DataFrame(raw_result) +cols = ['run','substep','timestep','x','nonexsistant','last_x','2nd_to_last_x','3rd_to_last_x','4th_to_last_x'] +result = result[cols] + +print() +print("Tensor Field:") +print(tabulate(tensor_field, headers='keys', tablefmt='psql')) +print("Output:") +print(tabulate(result, headers='keys', tablefmt='psql')) +print() \ No newline at end of file diff --git a/documentation/examples/param_sweep.py b/documentation/examples/param_sweep.py new file mode 100644 index 0000000..a118966 --- /dev/null +++ b/documentation/examples/param_sweep.py @@ -0,0 +1,116 @@ +import pprint +from typing import Dict, List + +import pandas as pd +from tabulate import tabulate + +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import env_trigger, var_substep_trigger, config_sim, psub_list +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from cadCAD import configs + +pp = pprint.PrettyPrinter(indent=4) + + +def some_function(x): + return x + + +g: Dict[str, List[int]] = { + 'alpha': [1], + 'beta': [2, 5], + 'gamma': [3, 4], + 'omega': [some_function] +} + +psu_steps = ['1', '2', '3'] +system_substeps = len(psu_steps) +var_timestep_trigger = var_substep_trigger([0, system_substeps]) +env_timestep_trigger = env_trigger(system_substeps) +env_process = {} + + +# Policies +def gamma(_params, step, sL, s): + return {'gamma': _params['gamma']} + + +def omega(_params, step, sL, s): + return {'omega': _params['omega'](7)} + + +# Internal States +def alpha(_params, step, sL, s, _input): + return 'alpha', _params['alpha'] + +def alpha_plus_gamma(_params, step, sL, s, _input): + return 'alpha_plus_gamma', _params['alpha'] + _params['gamma'] + + +def beta(_params, step, sL, s, _input): + return 'beta', _params['beta'] + + +def policies(_params, step, sL, s, _input): + return 'policies', _input + + +def sweeped(_params, step, sL, s, _input): + return 'sweeped', {'beta': _params['beta'], 'gamma': _params['gamma']} + + + + + +genesis_states = { + 'alpha_plus_gamma': 0, + 'alpha': 0, + 'beta': 0, + 'policies': {}, + 'sweeped': {} +} + +env_process['sweeped'] = env_timestep_trigger(trigger_field='timestep', trigger_vals=[5], funct_list=[lambda _g, x: _g['beta']]) + +sim_config = config_sim( + { + "N": 2, + "T": range(5), + "M": g, + } +) + +psu_block = {k: {"policies": {}, "variables": {}} for k in psu_steps} +for m in psu_steps: + psu_block[m]['policies']['gamma'] = gamma + psu_block[m]['policies']['omega'] = omega + psu_block[m]["variables"]['alpha'] = alpha_plus_gamma + psu_block[m]["variables"]['alpha_plus_gamma'] = alpha + psu_block[m]["variables"]['beta'] = beta + psu_block[m]['variables']['policies'] = policies + psu_block[m]["variables"]['sweeped'] = var_timestep_trigger(y='sweeped', f=sweeped) + +partial_state_update_blocks = psub_list(psu_block, psu_steps) +print() +pp.pprint(psu_block) +print() + +append_configs( + sim_configs=sim_config, + initial_state=genesis_states, + env_processes=env_process, + partial_state_update_blocks=partial_state_update_blocks +) + +exec_mode = ExecutionMode() +multi_proc_ctx = ExecutionContext(context=exec_mode.multi_proc) +run = Executor(exec_context=multi_proc_ctx, configs=configs) + +for raw_result, tensor_field in run.execute(): + result = pd.DataFrame(raw_result) + print() + print("Tensor Field:") + print(tabulate(tensor_field, headers='keys', tablefmt='psql')) + print("Output:") + print(tabulate(result, headers='keys', tablefmt='psql')) + print() \ No newline at end of file diff --git a/documentation/examples/policy_aggregation.py b/documentation/examples/policy_aggregation.py new file mode 100644 index 0000000..86313e7 --- /dev/null +++ b/documentation/examples/policy_aggregation.py @@ -0,0 +1,98 @@ +import pandas as pd +from tabulate import tabulate + +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import config_sim +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from cadCAD import configs + +# Policies per Mechanism +def p1m1(_g, step, sL, s): + return {'policy1': 1} +def p2m1(_g, step, sL, s): + return {'policy2': 2} + +def p1m2(_g, step, sL, s): + return {'policy1': 2, 'policy2': 2} +def p2m2(_g, step, sL, s): + return {'policy1': 2, 'policy2': 2} + +def p1m3(_g, step, sL, s): + return {'policy1': 1, 'policy2': 2, 'policy3': 3} +def p2m3(_g, step, sL, s): + return {'policy1': 1, 'policy2': 2, 'policy3': 3} + + +# Internal States per Mechanism +def add(y, x): + return lambda _g, step, sH, s, _input: (y, s[y] + x) + +def policies(_g, step, sH, s, _input): + y = 'policies' + x = _input + return (y, x) + + +# Genesis States +genesis_states = { + 'policies': {}, + 's1': 0 +} + +variables = { + 's1': add('s1', 1), + "policies": policies +} + +partial_state_update_block = { + "m1": { + "policies": { + "p1": p1m1, + "p2": p2m1 + }, + "variables": variables + }, + "m2": { + "policies": { + "p1": p1m2, + "p2": p2m2 + }, + "variables": variables + }, + "m3": { + "policies": { + "p1": p1m3, + "p2": p2m3 + }, + "variables": variables + } +} + + +sim_config = config_sim( + { + "N": 1, + "T": range(3), + } +) + +append_configs( + sim_configs=sim_config, + initial_state=genesis_states, + partial_state_update_blocks=partial_state_update_block, + policy_ops=[lambda a, b: a + b, lambda y: y * 2] # Default: lambda a, b: a + b +) + +exec_mode = ExecutionMode() +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +run = Executor(exec_context=single_proc_ctx, configs=configs) + +raw_result, tensor_field = run.execute() +result = pd.DataFrame(raw_result) + +print() +print("Tensor Field:") +print(tabulate(tensor_field, headers='keys', tablefmt='psql')) +print("Output:") +print(tabulate(result, headers='keys', tablefmt='psql')) +print() \ No newline at end of file diff --git a/documentation/examples/sys_model_A.py b/documentation/examples/sys_model_A.py new file mode 100644 index 0000000..92a7fe3 --- /dev/null +++ b/documentation/examples/sys_model_A.py @@ -0,0 +1,159 @@ +import numpy as np +from datetime import timedelta + + +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import bound_norm_random, config_sim, time_step, env_trigger + +seeds = { + 'z': np.random.RandomState(1), + 'a': np.random.RandomState(2), + 'b': np.random.RandomState(3), + 'c': np.random.RandomState(4) +} + + +# Policies per Mechanism +def p1m1(_g, step, sL, s): + return {'param1': 1} +def p2m1(_g, step, sL, s): + return {'param1': 1, 'param2': 4} + +def p1m2(_g, step, sL, s): + return {'param1': 'a', 'param2': 2} +def p2m2(_g, step, sL, s): + return {'param1': 'b', 'param2': 4} + +def p1m3(_g, step, sL, s): + return {'param1': ['c'], 'param2': np.array([10, 100])} +def p2m3(_g, step, sL, s): + return {'param1': ['d'], 'param2': np.array([20, 200])} + + +# Internal States per Mechanism +def s1m1(_g, step, sL, s, _input): + y = 's1' + x = s['s1'] + 1 + return (y, x) +def s2m1(_g, step, sL, s, _input): + y = 's2' + x = _input['param2'] + return (y, x) + +def s1m2(_g, step, sL, s, _input): + y = 's1' + x = s['s1'] + 1 + return (y, x) +def s2m2(_g, step, sL, s, _input): + y = 's2' + x = _input['param2'] + return (y, x) + +def s1m3(_g, step, sL, s, _input): + y = 's1' + x = s['s1'] + 1 + return (y, x) +def s2m3(_g, step, sL, s, _input): + y = 's2' + x = _input['param2'] + return (y, x) + +def policies(_g, step, sL, s, _input): + y = 'policies' + x = _input + return (y, x) + + +# Exogenous States +proc_one_coef_A = 0.7 +proc_one_coef_B = 1.3 + +def es3(_g, step, sL, s, _input): + y = 's3' + x = s['s3'] * bound_norm_random(seeds['a'], proc_one_coef_A, proc_one_coef_B) + return (y, x) + +def es4(_g, step, sL, s, _input): + y = 's4' + x = s['s4'] * bound_norm_random(seeds['b'], proc_one_coef_A, proc_one_coef_B) + return (y, x) + +def update_timestamp(_g, step, sL, s, _input): + y = 'timestamp' + return y, time_step(dt_str=s[y], dt_format='%Y-%m-%d %H:%M:%S', _timedelta=timedelta(days=0, minutes=0, seconds=1)) + + +# Genesis States +genesis_states = { + 's1': 0.0, + 's2': 0.0, + 's3': 1.0, + 's4': 1.0, + 'timestamp': '2018-10-01 15:16:24' +} + + +# Environment Process +# ToDo: Depreciation Waring for env_proc_trigger convention +trigger_timestamps = ['2018-10-01 15:16:25', '2018-10-01 15:16:27', '2018-10-01 15:16:29'] +env_processes = { + "s3": [lambda _g, x: 5], + "s4": env_trigger(3)(trigger_field='timestamp', trigger_vals=trigger_timestamps, funct_list=[lambda _g, x: 10]) +} + + +partial_state_update_block = [ + { + "policies": { + "b1": p1m1, + "b2": p2m1 + }, + "variables": { + "s1": s1m1, + "s2": s2m1, + "s3": es3, + "s4": es4, + "timestamp": update_timestamp + } + }, + { + "policies": { + "b1": p1m2, + "b2": p2m2 + }, + "variables": { + "s1": s1m2, + "s2": s2m2, + # "s3": es3p1, + # "s4": es4p2, + } + }, + { + "policies": { + "b1": p1m3, + "b2": p2m3 + }, + "variables": { + "s1": s1m3, + "s2": s2m3, + # "s3": es3p1, + # "s4": es4p2, + } + } +] + + +sim_config = config_sim( + { + "N": 2, + "T": range(1), + } +) + +append_configs( + sim_configs=sim_config, + initial_state=genesis_states, + env_processes=env_processes, + partial_state_update_blocks=partial_state_update_block, + policy_ops=[lambda a, b: a + b] +) \ No newline at end of file diff --git a/documentation/examples/sys_model_AB_exec.py b/documentation/examples/sys_model_AB_exec.py new file mode 100644 index 0000000..95067b3 --- /dev/null +++ b/documentation/examples/sys_model_AB_exec.py @@ -0,0 +1,24 @@ +import pandas as pd +from tabulate import tabulate +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from documentation.examples import sys_model_A, sys_model_B +from cadCAD import configs + +exec_mode = ExecutionMode() + +# # Multiple Processes Execution using Multiple System Model Configurations: +# # sys_model_A & sys_model_B +multi_proc_ctx = ExecutionContext(context=exec_mode.multi_proc) +sys_model_AB_simulation = Executor(exec_context=multi_proc_ctx, configs=configs) + +i = 0 +config_names = ['sys_model_A', 'sys_model_B'] +for sys_model_AB_raw_result, sys_model_AB_tensor_field in sys_model_AB_simulation.execute(): + sys_model_AB_result = pd.DataFrame(sys_model_AB_raw_result) + print() + print(f"Tensor Field: {config_names[i]}") + print(tabulate(sys_model_AB_tensor_field, headers='keys', tablefmt='psql')) + print("Result: System Events DataFrame:") + print(tabulate(sys_model_AB_result, headers='keys', tablefmt='psql')) + print() + i += 1 \ No newline at end of file diff --git a/documentation/examples/sys_model_A_exec.py b/documentation/examples/sys_model_A_exec.py new file mode 100644 index 0000000..8a630d9 --- /dev/null +++ b/documentation/examples/sys_model_A_exec.py @@ -0,0 +1,22 @@ +import pandas as pd +from tabulate import tabulate +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from documentation.examples import sys_model_A +from cadCAD import configs + +exec_mode = ExecutionMode() + +# Single Process Execution using a Single System Model Configuration: +# sys_model_A +sys_model_A = [configs[0]] # sys_model_A +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +sys_model_A_simulation = Executor(exec_context=single_proc_ctx, configs=sys_model_A) + +sys_model_A_raw_result, sys_model_A_tensor_field = sys_model_A_simulation.execute() +sys_model_A_result = pd.DataFrame(sys_model_A_raw_result) +print() +print("Tensor Field: config1") +print(tabulate(sys_model_A_tensor_field, headers='keys', tablefmt='psql')) +print("Result: System Events DataFrame") +print(tabulate(sys_model_A_result, headers='keys', tablefmt='psql')) +print() \ No newline at end of file diff --git a/documentation/examples/sys_model_B.py b/documentation/examples/sys_model_B.py new file mode 100644 index 0000000..7298d0b --- /dev/null +++ b/documentation/examples/sys_model_B.py @@ -0,0 +1,147 @@ +import numpy as np +from datetime import timedelta + +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import bound_norm_random, config_sim, env_trigger, time_step + +seeds = { + 'z': np.random.RandomState(1), + 'a': np.random.RandomState(2), + 'b': np.random.RandomState(3), + 'c': np.random.RandomState(3) +} + + +# Policies per Mechanism +def p1m1(_g, step, sL, s): + return {'param1': 1} +def p2m1(_g, step, sL, s): + return {'param2': 4} + +def p1m2(_g, step, sL, s): + return {'param1': 'a', 'param2': 2} +def p2m2(_g, step, sL, s): + return {'param1': 'b', 'param2': 4} + +def p1m3(_g, step, sL, s): + return {'param1': ['c'], 'param2': np.array([10, 100])} +def p2m3(_g, step, sL, s): + return {'param1': ['d'], 'param2': np.array([20, 200])} + + +# Internal States per Mechanism +def s1m1(_g, step, sL, s, _input): + y = 's1' + x = _input['param1'] + return (y, x) +def s2m1(_g, step, sL, s, _input): + y = 's2' + x = _input['param2'] + return (y, x) + +def s1m2(_g, step, sL, s, _input): + y = 's1' + x = _input['param1'] + return (y, x) +def s2m2(_g, step, sL, s, _input): + y = 's2' + x = _input['param2'] + return (y, x) + +def s1m3(_g, step, sL, s, _input): + y = 's1' + x = _input['param1'] + return (y, x) +def s2m3(_g, step, sL, s, _input): + y = 's2' + x = _input['param2'] + return (y, x) + + +# Exogenous States +proc_one_coef_A = 0.7 +proc_one_coef_B = 1.3 + +def es3(_g, step, sL, s, _input): + y = 's3' + x = s['s3'] * bound_norm_random(seeds['a'], proc_one_coef_A, proc_one_coef_B) + return (y, x) + +def es4(_g, step, sL, s, _input): + y = 's4' + x = s['s4'] * bound_norm_random(seeds['b'], proc_one_coef_A, proc_one_coef_B) + return (y, x) + +def update_timestamp(_g, step, sL, s, _input): + y = 'timestamp' + return y, time_step(dt_str=s[y], dt_format='%Y-%m-%d %H:%M:%S', _timedelta=timedelta(days=0, minutes=0, seconds=1)) + + +# Genesis States +genesis_states = { + 's1': 0, + 's2': 0, + 's3': 1, + 's4': 1, + 'timestamp': '2018-10-01 15:16:24' +} + + +# Environment Process +# ToDo: Depreciation Waring for env_proc_trigger convention +trigger_timestamps = ['2018-10-01 15:16:25', '2018-10-01 15:16:27', '2018-10-01 15:16:29'] +env_processes = { + "s3": [lambda _g, x: 5], + "s4": env_trigger(3)(trigger_field='timestamp', trigger_vals=trigger_timestamps, funct_list=[lambda _g, x: 10]) +} + +partial_state_update_block = [ + { + "policies": { + "b1": p1m1, + # "b2": p2m1 + }, + "states": { + "s1": s1m1, + # "s2": s2m1 + "s3": es3, + "s4": es4, + "timestep": update_timestamp + } + }, + { + "policies": { + "b1": p1m2, + # "b2": p2m2 + }, + "states": { + "s1": s1m2, + # "s2": s2m2 + } + }, + { + "policies": { + "b1": p1m3, + "b2": p2m3 + }, + "states": { + "s1": s1m3, + "s2": s2m3 + } + } +] + + +sim_config = config_sim( + { + "N": 2, + "T": range(5), + } +) + +append_configs( + sim_configs=sim_config, + initial_state=genesis_states, + env_processes=env_processes, + partial_state_update_blocks=partial_state_update_block +) \ No newline at end of file diff --git a/documentation/examples/sys_model_B_exec.py b/documentation/examples/sys_model_B_exec.py new file mode 100644 index 0000000..53eef37 --- /dev/null +++ b/documentation/examples/sys_model_B_exec.py @@ -0,0 +1,23 @@ +import pandas as pd +from tabulate import tabulate +# The following imports NEED to be in the exact order +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from documentation.examples import sys_model_B +from cadCAD import configs + +exec_mode = ExecutionMode() + +print("Simulation Execution: Single Configuration") +print() +first_config = configs # only contains config2 +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +run = Executor(exec_context=single_proc_ctx, configs=first_config) + +raw_result, tensor_field = run.execute() +result = pd.DataFrame(raw_result) +print() +print("Tensor Field: config1") +print(tabulate(tensor_field, headers='keys', tablefmt='psql')) +print("Output:") +print(tabulate(result, headers='keys', tablefmt='psql')) +print() diff --git a/documentation/execution.md b/documentation/execution.md new file mode 100644 index 0000000..f34064b --- /dev/null +++ b/documentation/execution.md @@ -0,0 +1,71 @@ +Simulation Execution +== +System Simulations are executed with the execution engine executor (`cadCAD.engine.Executor`) given System Model +Configurations. There are multiple simulation Execution Modes and Execution Contexts. + +### Steps: +1. #### *Choose Execution Mode*: + * ##### Simulation Execution Modes: + `cadCAD` executes a process per System Model Configuration and a thread per System Simulation. + ##### Class: `cadCAD.engine.ExecutionMode` + ##### Attributes: + * **Single Process:** A single process Execution Mode for a single System Model Configuration (Example: + `cadCAD.engine.ExecutionMode().single_proc`). + * **Multi-Process:** Multiple process Execution Mode for System Model Simulations which executes on a thread per + given System Model Configuration (Example: `cadCAD.engine.ExecutionMode().multi_proc`). +2. #### *Create Execution Context using Execution Mode:* +```python +from cadCAD.engine import ExecutionMode, ExecutionContext +exec_mode = ExecutionMode() +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +``` +3. #### *Create Simulation Executor* +```python +from cadCAD.engine import Executor +from cadCAD import configs +simulation = Executor(exec_context=single_proc_ctx, configs=configs) +``` +4. #### *Execute Simulation: Produce System Event Dataset* +A Simulation execution produces a System Event Dataset and the Tensor Field applied to initial states used to create it. +```python +import pandas as pd +raw_system_events, tensor_field = simulation.execute() + +# Simulation Result Types: +# raw_system_events: List[dict] +# tensor_field: pd.DataFrame + +# Result System Events DataFrame +simulation_result = pd.DataFrame(raw_system_events) +``` + +##### Example Tensor Field +``` ++----+-----+--------------------------------+--------------------------------+ +| | m | b1 | s1 | +|----+-----+--------------------------------+--------------------------------| +| 0 | 1 | | | +| 1 | 2 | | | +| 2 | 3 | | | ++----+-----+--------------------------------+--------------------------------+ +``` + +##### Example Result: System Events DataFrame +```python ++----+-------+------------+-----------+------+-----------+ +| | run | timestep | substep | s1 | s2 | +|----+-------+------------+-----------+------+-----------| +| 0 | 1 | 0 | 0 | 0 | 0.0 | +| 1 | 1 | 1 | 1 | 1 | 4 | +| 2 | 1 | 1 | 2 | 2 | 6 | +| 3 | 1 | 1 | 3 | 3 | [ 30 300] | +| 4 | 2 | 0 | 0 | 0 | 0.0 | +| 5 | 2 | 1 | 1 | 1 | 4 | +| 6 | 2 | 1 | 2 | 2 | 6 | +| 7 | 2 | 1 | 3 | 3 | [ 30 300] | ++----+-------+------------+-----------+------+-----------+ +``` + +##### [Single Process Example Execution](link) + +##### [Multiple Process Example Execution](link) diff --git a/documentation/historical_state_access.md b/documentation/historical_state_access.md new file mode 100644 index 0000000..944fc8a --- /dev/null +++ b/documentation/historical_state_access.md @@ -0,0 +1,97 @@ +Historical State Access +== +The 3rd parameter of state and policy update functions (labels as `sH` of type `List[List[dict]]`) provides access to +past Partial State Updates (PSU) given a negative offset number. `access_block` is used to access past PSUs +(`List[dict]`) from `sH`. + +Example: `-2` denotes to second to last PSU + +##### Exclusion List +Create a list of states to exclude from the reported PSU. +```python +exclusion_list = [ + 'nonexsistant', 'last_x', '2nd_to_last_x', '3rd_to_last_x', '4th_to_last_x' +] +``` +##### Example Policy Updates +###### Last partial state update +```python +from cadCAD.configuration.utils import config_sim, access_block + +def last_update(_g, substep, sH, s): + return {"last_x": access_block( + state_history=sH, + target_field="last_x", # Add a field to the exclusion list + psu_block_offset=-1, + exculsion_list=exclusion_list + ) + } +``` +* Note: Although `target_field` adding a field to the exclusion may seem redundant, it is useful in the case of +the exclusion list being empty while the `target_field` is assigned to a state or a policy key. +###### 2nd to last partial state update +```python +def second2last_update(_g, substep, sH, s): + return {"2nd_to_last_x": access_block(sH, "2nd_to_last_x", -2, exclusion_list)} +``` + +##### Define State Updates +###### 3rd to last partial state update +```python +def third_to_last_x(_g, substep, sH, s, _input): + return '3rd_to_last_x', access_block(sH, "3rd_to_last_x", -3, exclusion_list) +``` +###### 4rd to last partial state update +```python +def fourth_to_last_x(_g, substep, sH, s, _input): + return '4th_to_last_x', access_block(sH, "4th_to_last_x", -4, exclusion_list) +``` +###### Non-exsistant partial state update +* `psu_block_offset >= 0` doesn't exsist +```python +def nonexsistant(_g, substep, sH, s, _input): + return 'nonexsistant', access_block(sH, "nonexsistant", 0, exclusion_list) +``` + +#### Example Simulation +link + +#### Example Output +###### State History +``` ++----+-------+-----------+------------+-----+ +| | run | substep | timestep | x | +|----+-------+-----------+------------+-----| +| 0 | 1 | 0 | 0 | 0 | +| 1 | 1 | 1 | 1 | 1 | +| 2 | 1 | 2 | 1 | 2 | +| 3 | 1 | 3 | 1 | 3 | +| 4 | 1 | 1 | 2 | 4 | +| 5 | 1 | 2 | 2 | 5 | +| 6 | 1 | 3 | 2 | 6 | +| 7 | 1 | 1 | 3 | 7 | +| 8 | 1 | 2 | 3 | 8 | +| 9 | 1 | 3 | 3 | 9 | ++----+-------+-----------+------------+-----+ +``` +###### Accessed State History: +Example: `last_x` +``` ++----+-----------------------------------------------------------------------------------------------------------------------------------------------------+ +| | last_x | +|----+-----------------------------------------------------------------------------------------------------------------------------------------------------| +| 0 | [] | +| 1 | [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}] | +| 2 | [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}] | +| 3 | [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}] | +| 4 | [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}] | +| 5 | [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}] | +| 6 | [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}] | +| 7 | [{'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2}] | +| 8 | [{'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2}] | +| 9 | [{'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2}] | ++----+-----------------------------------------------------------------------------------------------------------------------------------------------------+ +``` + +#### [Example Configuration](link) +#### [Example Results](link) \ No newline at end of file diff --git a/documentation/param_sweep.md b/documentation/param_sweep.md new file mode 100644 index 0000000..3822369 --- /dev/null +++ b/documentation/param_sweep.md @@ -0,0 +1,68 @@ +System Model Parameter Sweep +== +Parametrization of a System Model configuration that produces multiple configurations. + +##### Set Parameters +```python +params = { + 'alpha': [1], + 'beta': [2, 5], + 'gamma': [3, 4], + 'omega': [7] +} +``` +The parameters above produce 2 simulations. +* Simulation 1: + * `alpha = 1` + * `beta = 2` + * `gamma = 3` + * `omega = 7` +* Simulation 2: + * `alpha = 1` + * `beta = 5` + * `gamma = 4` + * `omega = 7` + +All parameters can also be set to include a single parameter each, which will result in a single simulation. + +##### Example State Updates + +Previous State: +`y = 0` + +```python +def state_update(_params, step, sL, s, _input): + y = 'state' + x = s['state'] + _params['alpha'] + _params['gamma'] + return y, x +``` +* Updated State: + * Simulation 1: `y = 4 = 0 + 1 + 3` + * Simulation 2: `y = 5 = 0 + 1 + 4` + +##### Example Policy Updates +```python +# Internal States per Mechanism +def policies(_g, step, sL, s): + return {'beta': _g['beta'], 'gamma': _g['gamma']} +``` +* Simulation 1: `{'beta': 2, 'gamma': 3]}` +* Simulation 2: `{'beta': 5, 'gamma': 4}` + +##### Configure Simulation +```python +from cadCAD.configuration.utils import config_sim + +sim_config = config_sim( + { + "N": 2, + "T": range(5), + "M": g, + } +) +``` + +#### [Example Configuration](link) +#### [Example Results](link) + + diff --git a/documentation/policy_agg.md b/documentation/policy_agg.md new file mode 100644 index 0000000..7ddaa50 --- /dev/null +++ b/documentation/policy_agg.md @@ -0,0 +1,60 @@ +Policy Aggregation +== + +For each Partial State Update, multiple policy dictionaries are aggregated into a single dictionary to be imputted into +all state functions using an initial reduction function and optional subsequent map functions. + +#### Aggregate Function Composition: +```python +# Reduce Function +add = lambda a, b: a + b # Used to add policy values of the same key +# Map Function +mult_by_2 = lambda y: y * 2 # Used to multiply all policy values by 2 +policy_ops=[add, mult_by_2] +``` + +##### Example Policy Updates per Partial State Update (PSU) +```python +def p1_psu1(_g, step, sL, s): + return {'policy1': 1} +def p2_psu1(_g, step, sL, s): + return {'policy2': 2} +``` +* `add` not applicable due to lack of redundant policies +* `mult_by_2` applied to all policies +* Result: `{'policy1': 2, 'policy2': 4}` + +```python +def p1_psu2(_g, step, sL, s): + return {'policy1': 2, 'policy2': 2} +def p2_psu2(_g, step, sL, s): + return {'policy1': 2, 'policy2': 2} +``` +* `add` applicable due to redundant policies +* `mult_by_2` applied to all policies +* Result: `{'policy1': 8, 'policy2': 8}` + +```python +def p1_psu3(_g, step, sL, s): + return {'policy1': 1, 'policy2': 2, 'policy3': 3} +def p2_psu3(_g, step, sL, s): + return {'policy1': 1, 'policy2': 2, 'policy3': 3} +``` +* `add` applicable due to redundant policies +* `mult_by_2` applied to all policies +* Result: `{'policy1': 4, 'policy2': 8, 'policy3': 12}` + +#### Aggregate Policies using functions +```python +from cadCAD.configuration import append_configs + +append_configs( + sim_configs=???, + initial_state=???, + partial_state_update_blocks=???, + policy_ops=[add, mult_by_2] # Default: [lambda a, b: a + b] +) +``` + +#### [Example Configuration](link) +#### [Example Results](link) \ No newline at end of file diff --git a/documentation/sys_model_config.md b/documentation/sys_model_config.md new file mode 100644 index 0000000..edece69 --- /dev/null +++ b/documentation/sys_model_config.md @@ -0,0 +1,220 @@ +System Model Configuration +== + +#### Introduction + +Given System Model Configurations, cadCAD produces system event datasets that conform to specified system metrics. Each +event / record is of [Enogenous State variables](link) produced by user defined [Partial State Updates](link) (PSU / +functions that update state); A sequence of event / record subsets that comprises the resulting system event dataset is +produced by a [Partial State Update Block](link) (PSUB / a Tensor Field for which State, Policy, and Time are dimensions +and PSU functions are values). + +A **System Model Configuration** is comprised of a simulation configuration, initial endogenous states, Partial State +Update Blocks, environmental process, and a user defined policy aggregation function. + +Execution: + +#### Simulation Properties + +###### System Metrics +The following system metrics determine the size of resulting system event datasets: +* `run` - the number of simulations in the resulting dataset +* `timestep` - the number of timestamps in the resulting dataset +* `substep` - the number of PSUs per `timestep` / within PSUBS +* Number of events / records: `run` x `timestep` x `substep` + +###### Simulation Configuration +For the following dictionary, `T` is assigned a `timestep` range, `N` is assigned the number of simulation runs, and +`params` is assigned the [**Parameter Sweep**](link) dictionary. + +```python +from cadCAD.configuration.utils import config_sim + +sim_config = config_sim({ + "N": 2, + "T": range(5), + "M": params, # Optional +}) +``` + +#### Initial Endogenous States +**Enogenous State variables** are read-only variables defined to capture the shape and property of the network and +represent internal input and signal. + +The PSUB tensor field is applied to the following states to produce a resulting system event +dataset. +```python +genesis_states = { + 's1': 0.0, + 's2': 0.0, + 's3': 1.0, + 'timestamp': '2018-10-01 15:16:24' +} +``` + +#### Partial State Update Block: +- ***Partial State Update Block(PSUB)*** ***(Define ?)*** Tensor Field for which State, Policy, Time are dimensions +and Partial State Update functions are values. +- ***Partial State Update (PSU)*** are user defined functions that encodes state updates and are executed in +a specified order PSUBs. PSUs update states given the most recent set of states and PSU policies. +- ***Mechanism*** ***(Define)*** + + +The PSUBs is a list of PSU dictionaries of the structure within the code block below. PSUB elements (PSU dictionaries) +are listed / defined in order of `substeps` and **identity functions** (returning a previous state's value) are assigned +to unreferenced states within PSUs. The number of records produced produced per `timestep` is the number of `substeps`. + +```python +partial_state_update_block = [ + { + "policies": { + "b1": p1_psu1, + "b2": p2_psu1 + }, + "variables": { + "s1": s1_psu1, + "s2": s2_psu1 + } + }, + { + "policies": { + "b1": p1_psu2, + }, + "variables": { + "s2": s2_psu2 + } + }, + {...} +] +``` +*Notes:* +1. An identity function (returning the previous state value) is assigned to `s1` in the second PSU. +2. Currently the only names that need not correspond to the convention below are `'b1'` and `'b2'`. + +#### Policies +- ***Policies*** ***(Define)*** When are policies behavior ? +- ***Behaviors*** model agent behaviors in reaction to state variables and exogenous variables. The +resulted user action will become an input to PSUs. Note that user behaviors should not directly update value +of state variables. + +Policies accept parameter sweep variables [see link] `_g` (`dict`), the most recent +`substep` integer, the state history[see link] (`sH`), the most recent state record `s` (`dict) as inputs and returns a +set of actions (`dict`). + +Policy functions return dictionaries as actions. Policy functions provide access to parameter sweep variables [see link] +via dictionary `_g`. +```python +def p1_psu1(_g, substep, sH, s): + return {'policy1': 1} +def p2_psu1(_g, substep, sH, s): + return {'policy1': 1, 'policy2': 4} +``` +For each PSU, multiple policy dictionaries are aggregated into a single dictionary to be imputted into +all state functions using an initial reduction function (default: `lambda a, b: a + b`) and optional subsequent map +functions. +Example Result: `{'policy1': 2, 'policy2': 4}` + +#### State Updates +State update functions provide access to parameter sweep variables [see link] `_g` (`dict`), the most recent `substep` +integer, the state history[see link] (`sH`), the most recent state record as a dictionary (`s`), the policies of a +PSU (`_input`), and returns a tuple of the state variable's name and the resulting new value of the variable. + +```python +def state_update(_g, substep, sH, s, _input): + ... + return state, update +``` +**Note:** Each state update function updates one state variable at a time. Changes to multiple state variables requires +separate state update functions. A generic example of a PSU is as follows. + +* ##### Endogenous State Updates +They are only updated by PSUs and can be used as inputs to a PSUs. +```python +def s1_update(_g, substep, sH, s, _input): + x = _input['policy1'] + 1 + return 's1', x + +def s2_update(_g, substep, sH, s, _input): + x = _input['policy2'] + return 's2', x +``` + +* ##### Exogenous State Updates +***Exogenous State variables*** ***(Review)*** are read-only variables that represent external input and signal. They +update endogenous states and are only updated by environmental processes. Exgoneous variables can be used +as an input to a PSU that impacts state variables. ***(Expand upon Exogenous state updates)*** + +```python +from datetime import timedelta +from cadCAD.configuration.utils import time_step +def es3_update(_g, substep, sH, s, _input): + x = ... + return 's3' +def es4_update(_g, substep, sH, s, _input): + x = ... + return 's4', x +def update_timestamp(_g, substep, sH, s, _input): + x = time_step(dt_str=s[y], dt_format='%Y-%m-%d %H:%M:%S', _timedelta=timedelta(days=0, minutes=0, seconds=1)) + return 'timestamp', x +``` +Exogenous state update functions (`es3_update`, `es4_update` and `es5_update`) update once per timestamp and should be +included as a part of the first PSU in the PSUB. +```python +partial_state_update_block['psu1']['variables']['s3'] = es3_update +partial_state_update_block['psu1']['variables']['s4'] = es4_update +partial_state_update_block['psu1']['variables']['timestamp'] = update_timestamp +``` + +* #### Environmental Process +- ***Environmental processes*** model external changes that directly impact exogenous states at given specific +conditions such as market shocks at specific timestamps. + +Create a dictionary like `env_processes` below for which the keys are exogenous states and the values are lists of user +defined **Environment Update** functions to be composed (e.g. `[f(params, x), g(params, x)]` becomes +`f(params, g(params, x))`). + +Environment Updates accept the [**Parameter Sweep**](link) dictionary `params` and a state as a result of a PSU. +```python +def env_update(params, state): + . . . + return updated_state + +# OR + +env_update = lambda params, state: state + 5 +``` + +The `env_trigger` function is used to apply composed environment update functions to a list of specific exogenous state +update results. `env_trigger` accepts the total number of `substeps` for the simulation / `end_substep` and returns a +function accepting `trigger_field`, `trigger_vals`, and `funct_list`. + +In the following example functions are used to add `5` to every `s3` update and assign `10` to `s4` at +`timestamp`s `'2018-10-01 15:16:25'`, `'2018-10-01 15:16:27'`, and `'2018-10-01 15:16:29'`. +```python +from cadCAD.configuration.utils import env_trigger +trigger_timestamps = ['2018-10-01 15:16:25', '2018-10-01 15:16:27', '2018-10-01 15:16:29'] +env_processes = { + "s3": [lambda params, x: x + 5], + "s4": env_trigger(end_substep=3)( + trigger_field='timestamp', trigger_vals=trigger_timestamps, funct_list=[lambda params, x: 10] + ) +} +``` + +#### System Model Configuration +`append_configs`, stores a **System Model Configuration** to be (Executed)[url] as +simulations producing system event dataset(s) + +```python +from cadCAD.configuration import append_configs + +append_configs( + sim_configs=sim_config, + initial_state=genesis_states, + env_processes=env_processes, + partial_state_update_blocks=partial_state_update_block, + policy_ops=[lambda a, b: a + b] +) +``` + +#### [System Simulation Execution](link) diff --git a/simulations/regression_tests/config1.py b/simulations/regression_tests/config1.py index 9ee1979..a677f5e 100644 --- a/simulations/regression_tests/config1.py +++ b/simulations/regression_tests/config1.py @@ -9,7 +9,7 @@ seeds = { 'z': np.random.RandomState(1), 'a': np.random.RandomState(2), 'b': np.random.RandomState(3), - 'c': np.random.RandomState(3) + 'c': np.random.RandomState(4) } @@ -95,14 +95,15 @@ genesis_states = { # Environment Process # ToDo: Depreciation Waring for env_proc_trigger convention +trigger_timestamps = ['2018-10-01 15:16:25', '2018-10-01 15:16:27', '2018-10-01 15:16:29'] env_processes = { "s3": [lambda _g, x: 5], - "s4": env_trigger(3)(trigger_field='timestep', trigger_vals=[1], funct_list=[lambda _g, x: 10]) + "s4": env_trigger(3)(trigger_field='timestamp', trigger_vals=trigger_timestamps, funct_list=[lambda _g, x: 10]) } -partial_state_update_blocks = { - "m1": { +partial_state_update_block = [ + { "policies": { "b1": p1m1, "b2": p2m1 @@ -115,7 +116,7 @@ partial_state_update_blocks = { "timestamp": update_timestamp } }, - "m2": { + { "policies": { "b1": p1m2, "b2": p2m2 @@ -127,7 +128,7 @@ partial_state_update_blocks = { # "s4": es4p2, } }, - "m3": { + { "policies": { "b1": p1m3, "b2": p2m3 @@ -139,7 +140,7 @@ partial_state_update_blocks = { # "s4": es4p2, } } -} +] sim_config = config_sim( @@ -153,6 +154,6 @@ append_configs( sim_configs=sim_config, initial_state=genesis_states, env_processes=env_processes, - partial_state_update_blocks=partial_state_update_blocks, + partial_state_update_blocks=partial_state_update_block, policy_ops=[lambda a, b: a + b] ) \ No newline at end of file diff --git a/simulations/regression_tests/config2.py b/simulations/regression_tests/config2.py index e120285..f8c4981 100644 --- a/simulations/regression_tests/config2.py +++ b/simulations/regression_tests/config2.py @@ -8,7 +8,7 @@ seeds = { 'z': np.random.RandomState(1), 'a': np.random.RandomState(2), 'b': np.random.RandomState(3), - 'c': np.random.RandomState(4) + 'c': np.random.RandomState(3) } @@ -89,9 +89,10 @@ genesis_states = { # Environment Process # ToDo: Depreciation Waring for env_proc_trigger convention +trigger_timestamps = ['2018-10-01 15:16:25', '2018-10-01 15:16:27', '2018-10-01 15:16:29'] env_processes = { "s3": [lambda _g, x: 5], - "s4": env_trigger(3)(trigger_field='timestep', trigger_vals=[2], funct_list=[lambda _g, x: 10]) + "s4": env_trigger(3)(trigger_field='timestamp', trigger_vals=trigger_timestamps, funct_list=[lambda _g, x: 10]) } partial_state_update_block = { diff --git a/simulations/regression_tests/udo.py b/simulations/regression_tests/udo.py index 02647e7..618b86c 100644 --- a/simulations/regression_tests/udo.py +++ b/simulations/regression_tests/udo.py @@ -1,3 +1,5 @@ +from copy import deepcopy + import pandas as pd from fn.func import curried from datetime import timedelta @@ -26,6 +28,11 @@ class udoExample(object): self.x += 1 return self + def updateDS(self): + self.ds.iloc[0,0] -= 10 + # pp.pprint(self.ds) + return self + def perceive(self, s): self.perception = self.ds[ (self.ds['run'] == s['run']) & (self.ds['substep'] == s['substep']) & (self.ds['timestep'] == s['timestep']) @@ -106,7 +113,7 @@ def perceive(s, self): def state_udo_update(_g, step, sL, s, _input): y = 'state_udo' # s['hydra_state'].updateX().anon(perceive(s)) - s['state_udo'].updateX().perceive(s) + s['state_udo'].updateX().perceive(s).updateDS() x = udoPipe(s['state_udo']) return y, x for m in psu_steps: diff --git a/simulations/test_executions/config1_test.py b/simulations/test_executions/config1_test.py index 052c602..8807c02 100644 --- a/simulations/test_executions/config1_test.py +++ b/simulations/test_executions/config1_test.py @@ -1,4 +1,5 @@ import pandas as pd +from typing import List from tabulate import tabulate # The following imports NEED to be in the exact order from cadCAD.engine import ExecutionMode, ExecutionContext, Executor @@ -17,7 +18,8 @@ raw_result, tensor_field = run.execute() result = pd.DataFrame(raw_result) print() print("Tensor Field: config1") -print(tabulate(tensor_field, headers='keys', tablefmt='psql')) +# print(raw_result) +print(tabulate(tensor_field[['m', 'b1', 's1', 's2']], headers='keys', tablefmt='psql')) print("Output:") print(tabulate(result, headers='keys', tablefmt='psql')) print() diff --git a/simulations/test_executions/historical_state_access_test.py b/simulations/test_executions/historical_state_access_test.py index 2b3b477..f0229bc 100644 --- a/simulations/test_executions/historical_state_access_test.py +++ b/simulations/test_executions/historical_state_access_test.py @@ -15,10 +15,10 @@ run = Executor(exec_context=single_proc_ctx, configs=first_config) raw_result, tensor_field = run.execute() result = pd.DataFrame(raw_result) -cols = ['run','substep','timestep','x','nonexsistant','last_x','2nd_to_last_x','3rd_to_last_x','4th_to_last_x'] +# cols = ['run','substep','timestep','x','nonexsistant','last_x','2nd_to_last_x','3rd_to_last_x','4th_to_last_x'] +cols = ['last_x'] result = result[cols] - print() print("Tensor Field: config1") print(tabulate(tensor_field, headers='keys', tablefmt='psql')) diff --git a/simulations/test_executions/policy_agg_test.py b/simulations/test_executions/policy_agg_test.py index f1da13f..2d86f09 100644 --- a/simulations/test_executions/policy_agg_test.py +++ b/simulations/test_executions/policy_agg_test.py @@ -2,11 +2,9 @@ from pprint import pprint import pandas as pd from tabulate import tabulate -# The following imports NEED to be in the exact order from cadCAD.engine import ExecutionMode, ExecutionContext, Executor from simulations.regression_tests import policy_aggregation from cadCAD import configs -from testing.utils import generate_assertions exec_mode = ExecutionMode() diff --git a/simulations/test_executions/udo_test.py b/simulations/test_executions/udo_test.py index f4cf6d9..45c9d55 100644 --- a/simulations/test_executions/udo_test.py +++ b/simulations/test_executions/udo_test.py @@ -11,9 +11,9 @@ print("Simulation Execution: Single Configuration") print() -first_config = configs # only contains config1 + single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) -run = Executor(exec_context=single_proc_ctx, configs=first_config) +run = Executor(exec_context=single_proc_ctx, configs=configs) # cols = configs[0].initial_state.keys() cols = [ 'increment', @@ -29,6 +29,10 @@ result = pd.DataFrame(raw_result)[['run', 'substep', 'timestep'] + cols] # print(tabulate(result['c'].apply(pd.Series), headers='keys', tablefmt='psql')) +# print(result.iloc[8,:]['state_udo'].ds) + +# ctypes.cast(id(v['state_udo']['mem_id']), ctypes.py_object).value + print() print("Tensor Field: config1") print(tabulate(tensor_field, headers='keys', tablefmt='psql')) diff --git a/testing/generic_test.py b/testing/generic_test.py index 19b562c..796770b 100644 --- a/testing/generic_test.py +++ b/testing/generic_test.py @@ -2,7 +2,6 @@ import unittest from parameterized import parameterized from functools import reduce from tabulate import tabulate -from testing.utils import generate_assertions_df # ToDo: Exec Debug mode (*) for which state and policy updates are validated during runtime using `expected_results` # EXAMPLE: ('state_test' T/F, 'policy_test' T/F) @@ -14,26 +13,61 @@ from testing.utils import generate_assertions_df # ToDo: Use self.assertRaises(AssertionError) +def generate_assertions_df(df, expected_results, target_cols, evaluations): + # cols = ['run', 'timestep', 'substep'] + target_cols + # print(cols) + test_names = [] + for eval_f in evaluations: + def wrapped_eval(a, b): + try: + return eval_f(a, b) + except KeyError: + return True + + test_name = f"{eval_f.__name__}_test" + test_names.append(test_name) + df[test_name] = df.apply( + lambda x: wrapped_eval( + x.filter(items=target_cols).to_dict(), + expected_results[(x['run'], x['timestep'], x['substep'])] + ), + axis=1 + ) + + return df, test_names + + def make_generic_test(params): class TestSequence(unittest.TestCase): - @parameterized.expand(params) - def test_validate_results(self, name, result_df, expected_reults, target_cols): - # alt for (*) Exec Debug mode - tested_df = generate_assertions_df(result_df, expected_reults, target_cols) - erroneous = tested_df[(tested_df['test'] == False)] - if erroneous.empty is False: + + def generic_test(self, tested_df, expected_reults, test_name): + erroneous = tested_df[(tested_df[test_name] == False)] + # print(tabulate(tested_df, headers='keys', tablefmt='psql')) + + if erroneous.empty is False: # Or Entire df IS NOT erroneous for index, row in erroneous.iterrows(): expected = expected_reults[(row['run'], row['timestep'], row['substep'])] - unexpected = {k: expected[k] for k in expected if k in row and expected[k] != row[k]} + unexpected = {f"invalid_{k}": expected[k] for k in expected if k in row and expected[k] != row[k]} + for key in unexpected.keys(): - erroneous[f"invalid_{key}"] = unexpected[key] + erroneous[key] = None + erroneous.at[index, key] = unexpected[key] # etc. - print() - print(tabulate(erroneous, headers='keys', tablefmt='psql')) + # print() + # print(f"TEST: {test_name}") + # print(tabulate(erroneous, headers='keys', tablefmt='psql')) - self.assertTrue(reduce(lambda a, b: a and b, tested_df['test'])) + # ToDo: Condition that will change false to true + self.assertTrue(reduce(lambda a, b: a and b, tested_df[test_name])) - # def etc. + + @parameterized.expand(params) + def test_validation(self, name, result_df, expected_reults, target_cols, evaluations): + # alt for (*) Exec Debug mode + tested_df, test_names = generate_assertions_df(result_df, expected_reults, target_cols, evaluations) + + for test_name in test_names: + self.generic_test(tested_df, expected_reults, test_name) return TestSequence diff --git a/testing/system_models/__init__.py b/testing/system_models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testing/system_models/external_dataset.py b/testing/system_models/external_dataset.py new file mode 100644 index 0000000..0265288 --- /dev/null +++ b/testing/system_models/external_dataset.py @@ -0,0 +1,67 @@ +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import config_sim +import pandas as pd +from cadCAD.utils import SilentDF + +df = SilentDF(pd.read_csv('/Users/jjodesty/Projects/DiffyQ-SimCAD/simulations/external_data/output.csv')) + + +def query(s, df): + return df[ + (df['run'] == s['run']) & (df['substep'] == s['substep']) & (df['timestep'] == s['timestep']) + ].drop(columns=['run', 'substep', "timestep"]) + +def p1(_g, substep, sL, s): + result_dict = query(s, df).to_dict() + del result_dict["ds3"] + return {k: list(v.values()).pop() for k, v in result_dict.items()} + +def p2(_g, substep, sL, s): + result_dict = query(s, df).to_dict() + del result_dict["ds1"], result_dict["ds2"] + return {k: list(v.values()).pop() for k, v in result_dict.items()} + +# ToDo: SilentDF(df) wont work +#integrate_ext_dataset +def integrate_ext_dataset(_g, step, sL, s, _input): + result_dict = query(s, df).to_dict() + return 'external_data', {k: list(v.values()).pop() for k, v in result_dict.items()} + +def increment(y, incr_by): + return lambda _g, step, sL, s, _input: (y, s[y] + incr_by) +increment = increment('increment', 1) + +def view_policies(_g, step, sL, s, _input): + return 'policies', _input + + +external_data = {'ds1': None, 'ds2': None, 'ds3': None} +state_dict = { + 'increment': 0, + 'external_data': external_data, + 'policies': external_data +} + + +policies = {"p1": p1, "p2": p2} +states = {'increment': increment, 'external_data': integrate_ext_dataset, 'policies': view_policies} +PSUB = {'policies': policies, 'states': states} + +# needs M1&2 need behaviors +partial_state_update_blocks = { + 'PSUB1': PSUB, + 'PSUB2': PSUB, + 'PSUB3': PSUB +} + +sim_config = config_sim({ + "N": 2, + "T": range(4) +}) + +append_configs( + sim_configs=sim_config, + initial_state=state_dict, + partial_state_update_blocks=partial_state_update_blocks, + policy_ops=[lambda a, b: {**a, **b}] +) diff --git a/testing/system_models/historical_state_access.py b/testing/system_models/historical_state_access.py index 839a826..8f88e85 100644 --- a/testing/system_models/historical_state_access.py +++ b/testing/system_models/historical_state_access.py @@ -92,6 +92,4 @@ append_configs( partial_state_update_blocks=partial_state_update_block ) -exec_mode = ExecutionMode() -single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) -run = Executor(exec_context=single_proc_ctx, configs=configs) + diff --git a/testing/system_models/param_sweep.py b/testing/system_models/param_sweep.py new file mode 100644 index 0000000..fabb450 --- /dev/null +++ b/testing/system_models/param_sweep.py @@ -0,0 +1,110 @@ +import pprint +from typing import Dict, List + +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import env_trigger, var_substep_trigger, config_sim, psub_list + +pp = pprint.PrettyPrinter(indent=4) + +def some_function(x): + return x + +# Optional +# dict must contain lists opf 2 distinct lengths +g: Dict[str, List[int]] = { + 'alpha': [1], + 'beta': [2, some_function], + 'gamma': [3, 4], + 'omega': [7] +} + +psu_steps = ['m1', 'm2', 'm3'] +system_substeps = len(psu_steps) +var_timestep_trigger = var_substep_trigger([0, system_substeps]) +env_timestep_trigger = env_trigger(system_substeps) +env_process = {} + + +# ['s1', 's2', 's3', 's4'] +# Policies per Mechanism +def gamma(_g, step, sL, s): + return {'gamma': _g['gamma']} + + +def omega(_g, step, sL, s): + return {'omega': _g['omega']} + + +# Internal States per Mechanism +def alpha(_g, step, sL, s, _input): + return 'alpha', _g['alpha'] + + +def beta(_g, step, sL, s, _input): + return 'beta', _g['beta'] + + +def policies(_g, step, sL, s, _input): + return 'policies', _input + + +def sweeped(_g, step, sL, s, _input): + return 'sweeped', {'beta': _g['beta'], 'gamma': _g['gamma']} + +psu_block = {k: {"policies": {}, "variables": {}} for k in psu_steps} +for m in psu_steps: + psu_block[m]['policies']['gamma'] = gamma + psu_block[m]['policies']['omega'] = omega + psu_block[m]["variables"]['alpha'] = alpha + psu_block[m]["variables"]['beta'] = beta + psu_block[m]['variables']['policies'] = policies + psu_block[m]["variables"]['sweeped'] = var_timestep_trigger(y='sweeped', f=sweeped) + + +# ToDo: The number of values entered in sweep should be the # of config objs created, +# not dependent on the # of times the sweep is applied +# sweep exo_state func and point to exo-state in every other funtion +# param sweep on genesis states + +# Genesis States +genesis_states = { + 'alpha': 0, + 'beta': 0, + 'policies': {}, + 'sweeped': {} +} + +# Environment Process +# ToDo: Validate - make env proc trigger field agnostic +env_process['sweeped'] = env_timestep_trigger(trigger_field='timestep', trigger_vals=[5], funct_list=[lambda _g, x: _g['beta']]) + + +# config_sim Necessary +sim_config = config_sim( + { + "N": 2, + "T": range(5), + "M": g, # Optional + } +) +# print() +# pp.pprint(g) +# print() +# pp.pprint(sim_config) + + +# New Convention +partial_state_update_blocks = psub_list(psu_block, psu_steps) +append_configs( + sim_configs=sim_config, + initial_state=genesis_states, + env_processes=env_process, + partial_state_update_blocks=partial_state_update_blocks +) + + +print() +print("Policie State Update Block:") +pp.pprint(partial_state_update_blocks) +print() +print() diff --git a/testing/system_models/policy_aggregation.py b/testing/system_models/policy_aggregation.py index aae9234..e2be18b 100644 --- a/testing/system_models/policy_aggregation.py +++ b/testing/system_models/policy_aggregation.py @@ -1,7 +1,5 @@ from cadCAD.configuration import append_configs from cadCAD.configuration.utils import config_sim -from cadCAD.engine import ExecutionMode, ExecutionContext, Executor -from cadCAD import configs # Policies per Mechanism @@ -85,6 +83,4 @@ append_configs( policy_ops=[lambda a, b: a + b, lambda y: y * 2] # Default: lambda a, b: a + b ToDO: reduction function requires high lvl explanation ) -exec_mode = ExecutionMode() -single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) -run = Executor(exec_context=single_proc_ctx, configs=configs) + diff --git a/testing/system_models/udo.py b/testing/system_models/udo.py new file mode 100644 index 0000000..1415908 --- /dev/null +++ b/testing/system_models/udo.py @@ -0,0 +1,185 @@ +import pandas as pd +from fn.func import curried +from datetime import timedelta +import pprint as pp + +from cadCAD.utils import SilentDF #, val_switch +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import time_step, config_sim, var_trigger, var_substep_trigger, env_trigger, psub_list +from cadCAD.configuration.utils.userDefinedObject import udoPipe, UDO + +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from cadCAD import configs + + +DF = SilentDF(pd.read_csv('/Users/jjodesty/Projects/DiffyQ-SimCAD/simulations/external_data/output.csv')) + + +class udoExample(object): + def __init__(self, x, dataset=None): + self.x = x + self.mem_id = str(hex(id(self))) + self.ds = dataset # for setting ds initially or querying + self.perception = {} + + def anon(self, f): + return f(self) + + def updateX(self): + self.x += 1 + return self + + def perceive(self, s): + self.perception = self.ds[ + (self.ds['run'] == s['run']) & (self.ds['substep'] == s['substep']) & (self.ds['timestep'] == s['timestep']) + ].drop(columns=['run', 'substep']).to_dict() + return self + + def read(self, ds_uri): + self.ds = SilentDF(pd.read_csv(ds_uri)) + return self + + def write(self, ds_uri): + pd.to_csv(ds_uri) + + # ToDo: Generic update function + + pass + + +state_udo = UDO(udo=udoExample(0, DF), masked_members=['obj', 'perception']) +policy_udoA = UDO(udo=udoExample(0, DF), masked_members=['obj', 'perception']) +policy_udoB = UDO(udo=udoExample(0, DF), masked_members=['obj', 'perception']) + + +sim_config = config_sim({ + "N": 2, + "T": range(4) +}) + +# ToDo: DataFrame Column order +state_dict = { + 'increment': 0, + 'state_udo': state_udo, 'state_udo_tracker': 0, + 'state_udo_perception_tracker': {"ds1": None, "ds2": None, "ds3": None, "timestep": None}, + 'udo_policies': {'udo_A': policy_udoA, 'udo_B': policy_udoB}, + 'udo_policy_tracker': (0, 0), + 'timestamp': '2019-01-01 00:00:00' +} + +psu_steps = ['m1', 'm2', 'm3'] +system_substeps = len(psu_steps) +var_timestep_trigger = var_substep_trigger([0, system_substeps]) +env_timestep_trigger = env_trigger(system_substeps) +psu_block = {k: {"policies": {}, "variables": {}} for k in psu_steps} + +def udo_policyA(_g, step, sL, s): + s['udo_policies']['udo_A'].updateX() + return {'udo_A': udoPipe(s['udo_policies']['udo_A'])} +# policies['a'] = udo_policyA +for m in psu_steps: + psu_block[m]['policies']['a'] = udo_policyA + +def udo_policyB(_g, step, sL, s): + s['udo_policies']['udo_B'].updateX() + return {'udo_B': udoPipe(s['udo_policies']['udo_B'])} +# policies['b'] = udo_policyB +for m in psu_steps: + psu_block[m]['policies']['b'] = udo_policyB + + +# policies = {"p1": udo_policyA, "p2": udo_policyB} +# policies = {"A": udo_policyA, "B": udo_policyB} + +def add(y: str, added_val): + return lambda _g, step, sL, s, _input: (y, s[y] + added_val) +# state_updates['increment'] = add('increment', 1) +for m in psu_steps: + psu_block[m]["variables"]['increment'] = add('increment', 1) + + +@curried +def perceive(s, self): + self.perception = self.ds[ + (self.ds['run'] == s['run']) & (self.ds['substep'] == s['substep']) & (self.ds['timestep'] == s['timestep']) + ].drop(columns=['run', 'substep']).to_dict() + return self + + +def state_udo_update(_g, step, sL, s, _input): + y = 'state_udo' + # s['hydra_state'].updateX().anon(perceive(s)) + s['state_udo'].updateX().perceive(s) + x = udoPipe(s['state_udo']) + return y, x +for m in psu_steps: + psu_block[m]["variables"]['state_udo'] = state_udo_update + + +def track(destination, source): + return lambda _g, step, sL, s, _input: (destination, s[source].x) +state_udo_tracker = track('state_udo_tracker', 'state_udo') +for m in psu_steps: + psu_block[m]["variables"]['state_udo_tracker'] = state_udo_tracker + + +def track_state_udo_perception(destination, source): + def id(past_perception): + if len(past_perception) == 0: + return state_dict['state_udo_perception_tracker'] + else: + return past_perception + return lambda _g, step, sL, s, _input: (destination, id(s[source].perception)) +state_udo_perception_tracker = track_state_udo_perception('state_udo_perception_tracker', 'state_udo') +for m in psu_steps: + psu_block[m]["variables"]['state_udo_perception_tracker'] = state_udo_perception_tracker + + +def view_udo_policy(_g, step, sL, s, _input): + return 'udo_policies', _input +for m in psu_steps: + psu_block[m]["variables"]['udo_policies'] = view_udo_policy + + +def track_udo_policy(destination, source): + def val_switch(v): + if isinstance(v, pd.DataFrame) is True or isinstance(v, SilentDF) is True: + return SilentDF(v) + else: + return v.x + return lambda _g, step, sL, s, _input: (destination, tuple(val_switch(v) for _, v in s[source].items())) +udo_policy_tracker = track_udo_policy('udo_policy_tracker', 'udo_policies') +for m in psu_steps: + psu_block[m]["variables"]['udo_policy_tracker'] = udo_policy_tracker + + +def update_timestamp(_g, step, sL, s, _input): + y = 'timestamp' + return y, time_step(dt_str=s[y], dt_format='%Y-%m-%d %H:%M:%S', _timedelta=timedelta(days=0, minutes=0, seconds=1)) +for m in psu_steps: + psu_block[m]["variables"]['timestamp'] = var_timestep_trigger(y='timestamp', f=update_timestamp) + # psu_block[m]["variables"]['timestamp'] = var_trigger( + # y='timestamp', f=update_timestamp, + # pre_conditions={'substep': [0, system_substeps]}, cond_op=lambda a, b: a and b + # ) + # psu_block[m]["variables"]['timestamp'] = update_timestamp + +# ToDo: Bug without specifying parameters +# New Convention +partial_state_update_blocks = psub_list(psu_block, psu_steps) +append_configs( + sim_configs=sim_config, + initial_state=state_dict, + partial_state_update_blocks=partial_state_update_blocks +) + +print() +print("State Updates:") +pp.pprint(partial_state_update_blocks) +print() + + +exec_mode = ExecutionMode() +first_config = configs # only contains config1 +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +run = Executor(exec_context=single_proc_ctx, configs=first_config) diff --git a/testing/tests/external_test.py b/testing/tests/external_test.py new file mode 100644 index 0000000..1d86a3e --- /dev/null +++ b/testing/tests/external_test.py @@ -0,0 +1,127 @@ +import unittest +from pprint import pprint + +import pandas as pd +from tabulate import tabulate +# The following imports NEED to be in the exact order +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from simulations.regression_tests import external_dataset +from cadCAD import configs +from testing.generic_test import make_generic_test +from testing.utils import gen_metric_dict + +exec_mode = ExecutionMode() + +print("Simulation Execution: Single Configuration") +print() +first_config = configs # only contains config1 +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +run = Executor(exec_context=single_proc_ctx, configs=first_config) + +raw_result, tensor_field = run.execute() +result = pd.DataFrame(raw_result) + +# print(tabulate(result, headers='keys', tablefmt='psql')) + +# cols = ['run', 'substep', 'timestep', 'increment', 'external_data', 'policies'] +# result = result[cols] +# +# metrics = gen_metric_dict(result, ['increment', 'external_data', 'policies']) +# # +# pprint(metrics) + +def get_expected_results(run): + return { + (run, 0, 0): { + 'external_data': {'ds1': None, 'ds2': None, 'ds3': None}, + 'increment': 0, + 'policies': {'ds1': None, 'ds2': None, 'ds3': None} + }, + (run, 1, 1): { + 'external_data': {'ds1': 0, 'ds2': 0, 'ds3': 1}, + 'increment': 1, + 'policies': {'ds1': 0, 'ds2': 0, 'ds3': 1} + }, + (run, 1, 2): { + 'external_data': {'ds1': 1, 'ds2': 40, 'ds3': 5}, + 'increment': 2, + 'policies': {'ds1': 1, 'ds2': 40, 'ds3': 5} + }, + (run, 1, 3): { + 'external_data': {'ds1': 2, 'ds2': 40, 'ds3': 5}, + 'increment': 3, + 'policies': {'ds1': 2, 'ds2': 40, 'ds3': 5} + }, + (run, 2, 1): { + 'external_data': {'ds1': 3, 'ds2': 40, 'ds3': 5}, + 'increment': 4, + 'policies': {'ds1': 3, 'ds2': 40, 'ds3': 5} + }, + (run, 2, 2): { + 'external_data': {'ds1': 4, 'ds2': 40, 'ds3': 5}, + 'increment': 5, + 'policies': {'ds1': 4, 'ds2': 40, 'ds3': 5} + }, + (run, 2, 3): { + 'external_data': {'ds1': 5, 'ds2': 40, 'ds3': 5}, + 'increment': 6, + 'policies': {'ds1': 5, 'ds2': 40, 'ds3': 5} + }, + (run, 3, 1): { + 'external_data': {'ds1': 6, 'ds2': 40, 'ds3': 5}, + 'increment': 7, + 'policies': {'ds1': 6, 'ds2': 40, 'ds3': 5} + }, + (run, 3, 2): { + 'external_data': {'ds1': 7, 'ds2': 40, 'ds3': 5}, + 'increment': 8, + 'policies': {'ds1': 7, 'ds2': 40, 'ds3': 5} + }, + (run, 3, 3): { + 'external_data': {'ds1': 8, 'ds2': 40, 'ds3': 5}, + 'increment': 9, + 'policies': {'ds1': 8, 'ds2': 40, 'ds3': 5} + }, + (run, 4, 1): { + 'external_data': {'ds1': 9, 'ds2': 40, 'ds3': 5}, + 'increment': 10, + 'policies': {'ds1': 9, 'ds2': 40, 'ds3': 5} + }, + (run, 4, 2): { + 'external_data': {'ds1': 10, 'ds2': 40, 'ds3': 5}, + 'increment': 11, + 'policies': {'ds1': 10, 'ds2': 40, 'ds3': 5} + }, + (run, 4, 3): { + 'external_data': {'ds1': 11, 'ds2': 40, 'ds3': 5}, + 'increment': 12, + 'policies': {'ds1': 11, 'ds2': 40, 'ds3': 5} + } + } + + +expected_results = {} +expected_results_1 = get_expected_results(1) +expected_results_2 = get_expected_results(2) +expected_results.update(expected_results_1) +expected_results.update(expected_results_2) + + +def row(a, b): + return a == b +params = [["external_dataset", result, expected_results, ['increment', 'external_data', 'policies'], [row]]] + + +class GenericTest(make_generic_test(params)): + pass + + +if __name__ == '__main__': + unittest.main() + +# print() +# print("Tensor Field: config1") +# print(tabulate(tensor_field, headers='keys', tablefmt='psql')) +# print("Output:") +# print(tabulate(result, headers='keys', tablefmt='psql')) +# print() diff --git a/testing/tests/historical_state_access.py b/testing/tests/historical_state_access.py index 4ab145f..ffc2d95 100644 --- a/testing/tests/historical_state_access.py +++ b/testing/tests/historical_state_access.py @@ -1,14 +1,20 @@ import unittest import pandas as pd -from tabulate import tabulate + +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor from testing.generic_test import make_generic_test -from testing.system_models.historical_state_access import run -from testing.utils import generate_assertions_df +from testing.system_models import historical_state_access +from cadCAD import configs + + +exec_mode = ExecutionMode() +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +run = Executor(exec_context=single_proc_ctx, configs=configs) raw_result, tensor_field = run.execute() result = pd.DataFrame(raw_result) - +# ToDo: Discrepance not reported fot collection values. Needs custom test for collection values expected_results = { (1, 0, 0): {'x': 0, 'nonexsistant': [], 'last_x': [], '2nd_to_last_x': [], '3rd_to_last_x': [], '4th_to_last_x': []}, (1, 1, 1): {'x': 1, @@ -31,49 +37,85 @@ expected_results = { '4th_to_last_x': []}, (1, 2, 1): {'x': 4, 'nonexsistant': [], - 'last_x': [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}], - '2nd_to_last_x': [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}], + 'last_x': [ + {'x': 4, 'run': 1, 'substep': 1, 'timestep': 1}, # x: 1 + {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, + {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1} + ], + '2nd_to_last_x': [{'x': -1, 'run': 1, 'substep': 0, 'timestep': 0}], # x: 0 '3rd_to_last_x': [], '4th_to_last_x': []}, (1, 2, 2): {'x': 5, 'nonexsistant': [], - 'last_x': [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}], + 'last_x': [ + {'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, + {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, + {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1} + ], '2nd_to_last_x': [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}], '3rd_to_last_x': [], '4th_to_last_x': []}, (1, 2, 3): {'x': 6, 'nonexsistant': [], - 'last_x': [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}], + 'last_x': [ + {'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, + {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, + {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1} + ], '2nd_to_last_x': [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}], '3rd_to_last_x': [], '4th_to_last_x': []}, (1, 3, 1): {'x': 7, 'nonexsistant': [], - 'last_x': [{'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2}], - '2nd_to_last_x': [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}], + 'last_x': [ + {'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, + {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, + {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2} + ], + '2nd_to_last_x': [ + {'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, + {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, + {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1} + ], '3rd_to_last_x': [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}], '4th_to_last_x': []}, (1, 3, 2): {'x': 8, 'nonexsistant': [], - 'last_x': [{'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2}], - '2nd_to_last_x': [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}], + 'last_x': [ + {'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, + {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, + {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2} + ], + '2nd_to_last_x': [ + {'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, + {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, + {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1} + ], '3rd_to_last_x': [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}], '4th_to_last_x': []}, (1, 3, 3): {'x': 9, 'nonexsistant': [], - 'last_x': [{'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2}], - '2nd_to_last_x': [{'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1}], + 'last_x': [ + {'x': 4, 'run': 1, 'substep': 1, 'timestep': 2}, + {'x': 5, 'run': 1, 'substep': 2, 'timestep': 2}, + {'x': 6, 'run': 1, 'substep': 3, 'timestep': 2} + ], + '2nd_to_last_x': [ + {'x': 1, 'run': 1, 'substep': 1, 'timestep': 1}, + {'x': 2, 'run': 1, 'substep': 2, 'timestep': 1}, + {'x': 3, 'run': 1, 'substep': 3, 'timestep': 1} + ], '3rd_to_last_x': [{'x': 0, 'run': 1, 'substep': 0, 'timestep': 0}], '4th_to_last_x': []} } -params = [["historical_state_access", result, expected_results, - ['x', 'nonexsistant', 'last_x', '2nd_to_last_x', '3rd_to_last_x', '4th_to_last_x']] - ] -# df = generate_assertions_df(result, expected_results, -# ['x', 'nonexsistant', 'last_x', '2nd_to_last_x', '3rd_to_last_x', '4th_to_last_x'] -# ) -# print(tabulate(df, headers='keys', tablefmt='psql')) + +def row(a, b): + return a == b +params = [ + ["historical_state_access", result, expected_results, + ['x', 'nonexsistant', 'last_x', '2nd_to_last_x', '3rd_to_last_x', '4th_to_last_x'], [row]] + ] class GenericTest(make_generic_test(params)): diff --git a/testing/tests/multi_config_test.py b/testing/tests/multi_config_test.py new file mode 100644 index 0000000..c668773 --- /dev/null +++ b/testing/tests/multi_config_test.py @@ -0,0 +1,56 @@ +import pandas as pd +from tabulate import tabulate +# The following imports NEED to be in the exact order +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from simulations.regression_tests import config1, config2 +from cadCAD import configs +from testing.utils import gen_metric_dict + +exec_mode = ExecutionMode() + +print("Simulation Execution: Concurrent Execution") +multi_proc_ctx = ExecutionContext(context=exec_mode.multi_proc) +run = Executor(exec_context=multi_proc_ctx, configs=configs) + + +def get_expected_results_1(run): + return { + (run, 0, 0): {'s1': 0, 's2': 0.0, 's3': 5}, + (run, 1, 1): {'s1': 1, 's2': 4, 's3': 5}, + (run, 1, 2): {'s1': 2, 's2': 6, 's3': 5}, + (run, 1, 3): {'s1': 3, 's2': [30, 300], 's3': 5}, + (run, 2, 1): {'s1': 4, 's2': 4, 's3': 5}, + (run, 2, 2): {'s1': 5, 's2': 6, 's3': 5}, + (run, 2, 3): {'s1': 6, 's2': [30, 300], 's3': 5}, + (run, 3, 1): {'s1': 7, 's2': 4, 's3': 5}, + (run, 3, 2): {'s1': 8, 's2': 6, 's3': 5}, + (run, 3, 3): {'s1': 9, 's2': [30, 300], 's3': 5}, + (run, 4, 1): {'s1': 10, 's2': 4, 's3': 5}, + (run, 4, 2): {'s1': 11, 's2': 6, 's3': 5}, + (run, 4, 3): {'s1': 12, 's2': [30, 300], 's3': 5}, + (run, 5, 1): {'s1': 13, 's2': 4, 's3': 5}, + (run, 5, 2): {'s1': 14, 's2': 6, 's3': 5}, + (run, 5, 3): {'s1': 15, 's2': [30, 300], 's3': 5}, + } + +expected_results_1 = {} +expected_results_A = get_expected_results_1(1) +expected_results_B = get_expected_results_1(2) +expected_results_1.update(expected_results_A) +expected_results_1.update(expected_results_B) + +expected_results_2 = {} + +# print(configs) +i = 0 +config_names = ['config1', 'config2'] +for raw_result, tensor_field in run.execute(): + result = pd.DataFrame(raw_result) + print() + print(f"Tensor Field: {config_names[i]}") + print(tabulate(tensor_field, headers='keys', tablefmt='psql')) + print("Output:") + print(tabulate(result, headers='keys', tablefmt='psql')) + print() + print(gen_metric_dict) + i += 1 diff --git a/testing/tests/param_sweep.py b/testing/tests/param_sweep.py new file mode 100644 index 0000000..a87dab3 --- /dev/null +++ b/testing/tests/param_sweep.py @@ -0,0 +1,85 @@ +import unittest +import pandas as pd + + +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from testing.system_models import param_sweep +from cadCAD import configs + +from testing.generic_test import make_generic_test +from testing.system_models.param_sweep import some_function + + +exec_mode = ExecutionMode() +multi_proc_ctx = ExecutionContext(context=exec_mode.multi_proc) +run = Executor(exec_context=multi_proc_ctx, configs=configs) + + +def get_expected_results(run, beta, gamma): + return { + (run, 0, 0): {'policies': {}, 'sweeped': {}, 'alpha': 0, 'beta': 0}, + (run, 1, 1): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 1, 2): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 1, 3): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 2, 1): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 2, 2): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 2, 3): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 3, 1): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 3, 2): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 3, 3): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 4, 1): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 4, 2): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 4, 3): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': {'beta': beta, 'gamma': gamma}, 'alpha': 1, 'beta': beta}, + (run, 5, 1): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': beta, 'alpha': 1, 'beta': beta}, + (run, 5, 2): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': beta, 'alpha': 1, 'beta': beta}, + (run, 5, 3): {'policies': {'gamma': gamma, 'omega': 7}, 'sweeped': beta, 'alpha': 1, 'beta': beta} + } + + +expected_results_1 = {} +expected_results_1a = get_expected_results(1, 2, 3) +expected_results_1b = get_expected_results(2, 2, 3) +expected_results_1.update(expected_results_1a) +expected_results_1.update(expected_results_1b) + +expected_results_2 = {} +expected_results_2a = get_expected_results(1, some_function, 4) +expected_results_2b = get_expected_results(2, some_function, 4) +expected_results_2.update(expected_results_2a) +expected_results_2.update(expected_results_2b) + + +i = 0 +expected_results = [expected_results_1, expected_results_2] +config_names = ['sweep_config_A', 'sweep_config_B'] + +def row(a, b): + return a == b +def create_test_params(feature, fields): + i = 0 + for raw_result, _ in run.execute(): + yield [feature, pd.DataFrame(raw_result), expected_results[i], fields, [row]] + i += 1 + + +params = list(create_test_params("param_sweep", ['alpha', 'beta', 'policies', 'sweeped'])) + + +class GenericTest(make_generic_test(params)): + pass + + +if __name__ == '__main__': + unittest.main() + +# i = 0 +# # config_names = ['sweep_config_A', 'sweep_config_B'] +# for raw_result, tensor_field in run.execute(): +# result = pd.DataFrame(raw_result) +# print() +# # print("Tensor Field: " + config_names[i]) +# print(tabulate(tensor_field, headers='keys', tablefmt='psql')) +# print("Output:") +# print(tabulate(result, headers='keys', tablefmt='psql')) +# print() +# i += 1 \ No newline at end of file diff --git a/testing/tests/policy_aggregation.py b/testing/tests/policy_aggregation.py index 4be0da4..657b6e6 100644 --- a/testing/tests/policy_aggregation.py +++ b/testing/tests/policy_aggregation.py @@ -1,14 +1,21 @@ import unittest import pandas as pd + +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor from testing.generic_test import make_generic_test -from testing.system_models.policy_aggregation import run +from testing.system_models import policy_aggregation +from cadCAD import configs + +exec_mode = ExecutionMode() +single_proc_ctx = ExecutionContext(context=exec_mode.single_proc) +run = Executor(exec_context=single_proc_ctx, configs=configs) raw_result, tensor_field = run.execute() result = pd.DataFrame(raw_result) expected_results = { (1, 0, 0): {'policies': {}, 's1': 0}, - (1, 1, 1): {'policies': {'policy1': 2, 'policy2': 4}, 's1': 500}, + (1, 1, 1): {'policies': {'policy1': 1, 'policy2': 4}, 's1': 1}, # 'policy1': 2 (1, 1, 2): {'policies': {'policy1': 8, 'policy2': 8}, 's1': 2}, (1, 1, 3): {'policies': {'policy1': 4, 'policy2': 8, 'policy3': 12}, 's1': 3}, (1, 2, 1): {'policies': {'policy1': 2, 'policy2': 4}, 's1': 4}, @@ -19,14 +26,14 @@ expected_results = { (1, 3, 3): {'policies': {'policy1': 4, 'policy2': 8, 'policy3': 12}, 's1': 9} } -params = [["policy_aggregation", result, expected_results, ['policies', 's1']]] -# df = generate_assertions_df(result, expected_results, ['policies', 's1']) -# print(tabulate(df, headers='keys', tablefmt='psql')) +def row(a, b): + return a == b +params = [["policy_aggregation", result, expected_results, ['policies', 's1'], [row]]] class GenericTest(make_generic_test(params)): pass - if __name__ == '__main__': unittest.main() + diff --git a/testing/tests/udo.py b/testing/tests/udo.py new file mode 100644 index 0000000..ea4b42a --- /dev/null +++ b/testing/tests/udo.py @@ -0,0 +1,39 @@ +import unittest +import ctypes +from copy import deepcopy +from pprint import pprint + +import pandas as pd +from tabulate import tabulate + +from testing.generic_test import make_generic_test +from testing.system_models.udo import run +from testing.utils import generate_assertions_df, gen_metric_dict + +raw_result, tensor_field = run.execute() +result = pd.DataFrame(raw_result) + +cols = ['increment', 'state_udo', 'state_udo_perception_tracker', + 'state_udo_tracker', 'timestamp', 'udo_policies', 'udo_policy_tracker'] + + +# print(list(result.columns) +# ctypes.cast(id(a), ctypes.py_object).value +# pprint(gen_metric_dict(result, cols)) +d = gen_metric_dict(result, cols) +pprint(d) + +# for k1, v1 in d: +# print(v1) +# d_copy = deepcopy(d) +# for k, v in d_copy.items(): +# # print(d[k]['state_udo']) # = +# print(ctypes.cast(id(v['state_udo']['mem_id']), ctypes.py_object).value) + + +# pprint(d_copy) + +# df = generate_assertions_df(result, d, cols) +# +# print(tabulate(df, headers='keys', tablefmt='psql')) +# \ No newline at end of file diff --git a/testing/utils.py b/testing/utils.py index 62dc439..0fff73d 100644 --- a/testing/utils.py +++ b/testing/utils.py @@ -1,28 +1,21 @@ -def gen_metric_row(row): - return ((row['run'], row['timestep'], row['substep']), {'s1': row['s1'], 'policies': row['policies']}) +# +# def record_generator(row, cols): +# return {col: row[col] for col in cols} -def gen_metric_row(row): - return { - 'run': row['run'], - 'timestep': row['timestep'], - 'substep': row['substep'], - 's1': row['s1'], - 'policies': row['policies'] - } +def gen_metric_row(row, cols): + return ((row['run'], row['timestep'], row['substep']), {col: row[col] for col in cols}) -def gen_metric_dict(df): - return [gen_metric_row(row) for index, row in df.iterrows()] +# def gen_metric_row(row): +# return ((row['run'], row['timestep'], row['substep']), {'s1': row['s1'], 'policies': row['policies']}) -def generate_assertions_df(df, expected_results, target_cols): - def df_filter(run, timestep, substep): - return df[ - (df['run'] == run) & (df['timestep'] == timestep) & (df['substep'] == substep) - ][target_cols].to_dict(orient='records')[0] +# def gen_metric_row(row): +# return { +# 'run': row['run'], +# 'timestep': row['timestep'], +# 'substep': row['substep'], +# 's1': row['s1'], +# 'policies': row['policies'] +# } - df['test'] = df.apply( - lambda x: \ - df_filter(x['run'], x['timestep'], x['substep']) == expected_results[(x['run'], x['timestep'], x['substep'])] - , axis=1 - ) - - return df \ No newline at end of file +def gen_metric_dict(df, cols): + return dict([gen_metric_row(row, cols) for index, row in df.iterrows()])