diff --git a/.gitignore b/.gitignore index 5194f30..adec4a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.idea +jupyter notebook.idea .ipynb_checkpoints .DS_Store .idea diff --git a/cadCAD/configuration/__init__.py b/cadCAD/configuration/__init__.py index 0bae909..8720047 100644 --- a/cadCAD/configuration/__init__.py +++ b/cadCAD/configuration/__init__.py @@ -28,7 +28,7 @@ class Configuration(object): sanitize_config(self) - +# ToDo: Remove Seeds def append_configs(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) -> None: if _exo_update_per_ts is True: diff --git a/cadCAD/configuration/utils/__init__.py b/cadCAD/configuration/utils/__init__.py index 1e69c66..9345fa3 100644 --- a/cadCAD/configuration/utils/__init__.py +++ b/cadCAD/configuration/utils/__init__.py @@ -134,20 +134,43 @@ def trigger_condition(s, conditions, cond_opp): condition_bools = [s[field] in precondition_values for field, precondition_values in conditions.items()] return reduce(cond_opp, condition_bools) -def apply_state_condition(conditions, cond_opp, y, f, _g, step, sL, s, _input): - if trigger_condition(s, conditions, cond_opp): +def apply_state_condition(pre_conditions, cond_opp, y, f, _g, step, sL, s, _input): + if trigger_condition(s, pre_conditions, cond_opp): return f(_g, step, sL, s, _input) else: return y, s[y] -def proc_trigger(y, f, conditions, cond_op): - return lambda _g, step, sL, s, _input: apply_state_condition(conditions, cond_op, y, f, _g, step, sL, s, _input) +def var_trigger(y, f, pre_conditions, cond_op): + return lambda _g, step, sL, s, _input: apply_state_condition(pre_conditions, cond_op, y, f, _g, step, sL, s, _input) -def timestep_trigger(end_substep, y, f): - conditions = {'substep': [0, end_substep]} - cond_opp = lambda a, b: a and b - return proc_trigger(y, f, conditions, cond_opp) +def var_substep_trigger(substeps): + def trigger(end_substep, y, f): + pre_conditions = {'substep': substeps} + cond_opp = lambda a, b: a and b + return var_trigger(y, f, pre_conditions, cond_opp) + return lambda y, f: curry(trigger)(substeps)(y)(f) + + +def env_trigger(end_substep): + def trigger(end_substep, trigger_field, trigger_vals, funct_list): + def env_update(state_dict, target_value): + state_dict_copy = deepcopy(state_dict) + # Use supstep to simulate current sysMetrics + if state_dict_copy['substep'] == end_substep: + state_dict_copy['timestep'] = state_dict_copy['timestep'] + 1 + + if state_dict_copy[trigger_field] in trigger_vals: + for g in funct_list: + target_value = g(target_value) + + del state_dict_copy + return target_value + + return env_update + + return lambda trigger_field, trigger_vals, funct_list: \ + curry(trigger)(end_substep)(trigger_field)(trigger_vals)(funct_list) # trigger = curry(_trigger) # print(timestep_trigger) @@ -157,19 +180,15 @@ def timestep_trigger(end_substep, y, f): def config_sim(d): def process_variables(d): return flatten_tabulated_dict(tabulate_dict(d)) + if "M" in d: - return [ - { - "N": d["N"], - "T": d["T"], - "M": M - } - for M in process_variables(d["M"]) - ] + return [{"N": d["N"], "T": d["T"], "M": M} for M in process_variables(d["M"])] else: d["M"] = [{}] return d +def psub_list(psu_block, psu_steps): + return [psu_block[psu] for psu in psu_steps] def psub(policies, state_updates): return { diff --git a/cadCAD/configuration/utils/userDefinedObject.py b/cadCAD/configuration/utils/userDefinedObject.py index c013952..c1f532c 100644 --- a/cadCAD/configuration/utils/userDefinedObject.py +++ b/cadCAD/configuration/utils/userDefinedObject.py @@ -27,7 +27,7 @@ class udcView(object): } members['methods'] = [k for k, v in self.__dict__.items() if str(type(v)) == ""] members.update(variables) - return f"{members}" + return f"{members}" #[1:-1] class udcBroker(object): diff --git a/cadCAD/engine/simulation.py b/cadCAD/engine/simulation.py index 36d4067..a6e4750 100644 --- a/cadCAD/engine/simulation.py +++ b/cadCAD/engine/simulation.py @@ -3,6 +3,7 @@ from typing import Any, Callable, Dict, List, Tuple from pathos.pools import ThreadPool as TPool from copy import deepcopy from functools import reduce +from funcy import compose from cadCAD.engine.utils import engine_exception from cadCAD.utils import flatten @@ -72,19 +73,48 @@ class Executor: # return {k: reduce(f1, val_list) for k, val_list in new_dict.items()} # return foldr(call, col_results)(ops) + # def apply_env_proc( + # self, + # env_processes: Dict[str, Callable], + # state_dict: Dict[str, Any], + # time_step: int + # ) -> Dict[str, Any]: + # for state in state_dict.keys(): + # if state in list(env_processes.keys()): + # env_state: Callable = env_processes[state] + # if (env_state.__name__ == '_curried') or (env_state.__name__ == 'proc_trigger'): + # state_dict[state] = env_state(sub_step)(state_dict[state]) + # else: + # state_dict[state] = env_state(state_dict[state]) + # + # return state_dict + def apply_env_proc( self, env_processes: Dict[str, Callable], state_dict: Dict[str, Any], - sub_step: int ) -> Dict[str, Any]: - for state in state_dict.keys(): - if state in list(env_processes.keys()): - env_state: Callable = env_processes[state] - if (env_state.__name__ == '_curried') or (env_state.__name__ == 'proc_trigger'): - state_dict[state] = env_state(sub_step)(state_dict[state]) - else: - state_dict[state] = env_state(state_dict[state]) + + def env_composition(target_field, state_dict, target_value): + function_type = type(lambda x: x) + env_update = env_processes[target_field] + if isinstance(env_update, list): + target_value = compose(*env_update[::-1])(target_value) + elif isinstance(env_update, function_type): + target_value = env_update(state_dict, target_value) + else: + target_value = env_update + + return target_value + + filtered_state_dict = {k: v for k, v in state_dict.items() if k in env_processes.keys()} + env_proc_dict = { + target_field: env_composition(target_field, state_dict, target_value) + for target_field, target_value in filtered_state_dict.items() + } + + for k, v in env_proc_dict.items(): + state_dict[k] = v return state_dict @@ -137,7 +167,10 @@ class Executor: last_in_copy: Dict[str, Any] = transfer_missing_fields(last_in_obj, dict(generate_record(state_funcs))) # ToDo: Remove - last_in_copy: Dict[str, Any] = self.apply_env_proc(env_processes, last_in_copy, last_in_copy['timestep']) + # last_in_copy: Dict[str, Any] = self.apply_env_proc(env_processes, last_in_copy, last_in_copy['timestep']) + last_in_copy: Dict[str, Any] = self.apply_env_proc(env_processes, last_in_copy) + + # ToDo: make 'substep' & 'timestep' reserve fields last_in_copy['substep'], last_in_copy['timestep'], last_in_copy['run'] = sub_step, time_step, run diff --git a/cadCAD/utils/sys_config.py b/cadCAD/utils/sys_config.py index b6e3a92..738161c 100644 --- a/cadCAD/utils/sys_config.py +++ b/cadCAD/utils/sys_config.py @@ -1,5 +1,10 @@ +from copy import deepcopy + +from fn.func import curried + from cadCAD.configuration.utils import ep_time_step, time_step from funcy import curry +import pprint as pp # from fn import _ from functools import reduce @@ -25,7 +30,7 @@ def update_timestamp(y, timedelta, format): def apply(f, y: str, incr_by: int): return lambda _g, step, sL, s, _input: (y, curry(f)(s[y])(incr_by)) -def add(y: str, incr_by: int): +def add(y: str, incr_by): return apply(lambda a, b: a + b, y, incr_by) def increment_state_by_int(y: str, incr_by: int): @@ -85,15 +90,22 @@ def time_model(y, substeps, time_delta, ts_format='%Y-%m-%d %H:%M:%S'): # multi_cond_opp = lambda a, b: a and b # return proc_trigger2(y, f, multi_conditions, multi_cond_opp) +# +# @curried -def env_trigger(trigger_field, trigger_val, input, funct_list): - y, x = input - if trigger_field == trigger_val: - i = 0 - for g in funct_list: - x = g(x) - return y, x + +# print(env_trigger(3).__module__) +# pp.pprint(dir(env_trigger)) + + + +# @curried +# def env_proc_trigger(trigger_time, update_f, time): +# if time == trigger_time: +# return update_f +# else: +# return lambda x: x diff --git a/dist/cadCAD-0.2-py3-none-any.whl b/dist/cadCAD-0.2.2-py3-none-any.whl similarity index 64% rename from dist/cadCAD-0.2-py3-none-any.whl rename to dist/cadCAD-0.2.2-py3-none-any.whl index ccb0606..c460fad 100644 Binary files a/dist/cadCAD-0.2-py3-none-any.whl and b/dist/cadCAD-0.2.2-py3-none-any.whl differ diff --git a/requirements.txt b/requirements.txt index 70c7dfe..2030bc8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ pandas wheel pathos fn -tabulate \ No newline at end of file +tabulate +funcy \ No newline at end of file diff --git a/simulations/historical_state_access_run.py b/simulations/historical_state_access_run.py index d2a5970..67ad0b5 100644 --- a/simulations/historical_state_access_run.py +++ b/simulations/historical_state_access_run.py @@ -16,7 +16,6 @@ run = Executor(exec_context=single_proc_ctx, configs=first_config) raw_result, tensor_field = run.main() result = pd.DataFrame(raw_result) def delSH(d): - print(d) if 'sh' in d.keys(): del d['sh'] return d diff --git a/simulations/param_sweep_test.py b/simulations/param_sweep_test.py index c43a903..2b057c3 100644 --- a/simulations/param_sweep_test.py +++ b/simulations/param_sweep_test.py @@ -2,7 +2,8 @@ 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.validation import new_sweep_config +# from simulations.validation import new_sweep_config +from simulations.regression_tests import sweep_config from cadCAD import configs exec_mode = ExecutionMode() diff --git a/simulations/regression_tests/sweep_config.py b/simulations/regression_tests/sweep_config.py index a2b9fe0..21d2c49 100644 --- a/simulations/regression_tests/sweep_config.py +++ b/simulations/regression_tests/sweep_config.py @@ -1,10 +1,12 @@ from decimal import Decimal import numpy as np from datetime import timedelta +from funcy import compose import pprint from cadCAD.configuration import append_configs -from cadCAD.configuration.utils import proc_trigger, ep_time_step, config_sim +from cadCAD.configuration.utils import proc_trigger, env_trigger, var_substep_trigger, config_sim, env_proc_trigger, \ + time_step, psub_list from typing import Dict, List @@ -25,74 +27,95 @@ g: Dict[str, List[int]] = { 'omega': [7] } +psu_steps = ['m1', 'm2', 'm3'] +system_substeps = len(psu_steps) +var_timestep_trigger = var_substep_trigger(system_substeps) +env_timestep_trigger = env_trigger(system_substeps) +env_process = {} +psu_block = {k: {"policies": {}, "variables": {}} for k in psu_steps} + +# ['s1', 's2', 's3', 's4'] # Policies per Mechanism def p1m1(_g, step, sL, s): return {'param1': 1} +psu_block['m1']['policies']['p1'] = p1m1 def p2m1(_g, step, sL, s): return {'param2': 4} +psu_block['m1']['policies']['p2'] = p2m1 def p1m2(_g, step, sL, s): return {'param1': 'a', 'param2': _g['beta']} +psu_block['m2']['policies']['p1'] = p1m2 def p2m2(_g, step, sL, s): return {'param1': 'b', 'param2': 0} +psu_block['m2']['policies']['p2'] = p2m2 def p1m3(_g, step, sL, s): return {'param1': np.array([10, 100])} +psu_block['m3']['policies']['p1'] = p1m3 def p2m3(_g, step, sL, s): return {'param1': np.array([20, 200])} +psu_block['m3']['policies']['p2'] = p2m3 # Internal States per Mechanism def s1m1(_g, step, sL, s, _input): return 's1', 0 +psu_block['m1']["variables"]['s1'] = s1m1 def s2m1(_g, step, sL, s, _input): return 's2', _g['beta'] +psu_block['m1']["variables"]['s2'] = s2m1 def s1m2(_g, step, sL, s, _input): return 's1', _input['param2'] +psu_block['m2']["variables"]['s1'] = s1m2 def s2m2(_g, step, sL, s, _input): return 's2', _input['param2'] +psu_block['m2']["variables"]['s2'] = s2m2 def s1m3(_g, step, sL, s, _input): return 's1', 0 +psu_block['m3']["variables"]['s1'] = s1m3 def s2m3(_g, step, sL, s, _input): return 's2', 0 +psu_block['m3']["variables"]['s2'] = s2m3 # Exogenous States +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 ['m1','m2','m3']: + # psu_block[m]["variables"]['timestamp'] = update_timestamp + psu_block[m]["variables"]['timestamp'] = var_timestep_trigger(y='timestamp', f=update_timestamp) + # psu_block[m]["variables"]['timestamp'] = proc_trigger( + # y='timestamp', f=update_timestamp, pre_conditions={'substep': [0, system_substeps]}, cond_op=lambda a, b: a and b + # ) + proc_one_coef_A = 0.7 -proc_one_coef_B = 1.3 - - def es3p1(_g, step, sL, s, _input): - return 's3', _g['gamma'] -# @curried + return 's3', s['s3'] +# use `timestep_trigger` to update every ts +for m in ['m1','m2','m3']: + psu_block[m]["variables"]['s3'] = var_timestep_trigger(y='s3', f=es3p1) + +proc_one_coef_B = 1.3 def es4p2(_g, step, sL, s, _input): - return 's4', _g['gamma'] - -ts_format = '%Y-%m-%d %H:%M:%S' -t_delta = timedelta(days=0, minutes=0, seconds=1) -def es5p2(_g, step, sL, s, _input): - y = 'timestep' - x = ep_time_step(s, dt_str=s['timestep'], fromat_str=ts_format, _timedelta=t_delta) - return (y, x) + return 's4', s['s4'] #+ 4 #g['gamma'] + proc_one_coef_B +for m in ['m1','m2','m3']: + psu_block[m]["variables"]['s4'] = var_timestep_trigger(y='s4', f=es4p2) -# Environment States -# @curried -# def env_a(param, x): -# return x + param -def env_a(x): - return x -def env_b(x): - return 10 - +# 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 = { @@ -100,83 +123,37 @@ genesis_states = { 's2': Decimal(0.0), 's3': Decimal(1.0), 's4': Decimal(1.0), -# 'timestep': '2018-10-01 15:16:24' + 'timestamp': '2018-10-01 15:16:24' } +# Environment Process +# ToDo: Validate - make env proc trigger field agnostic +env_process["s3"] = [lambda x: x + 1, lambda x: x + 1] +env_process["s4"] = env_timestep_trigger(trigger_field='timestep', trigger_vals=[5], funct_list=[lambda x: 1, lambda x: x + 2]) -# remove `exo_update_per_ts` to update every ts -raw_exogenous_states = { - "s3": es3p1, - "s4": es4p2, -# "timestep": es5p2 -} - - -# ToDo: make env proc trigger field agnostic -# ToDo: input json into function renaming __name__ -triggered_env_b = proc_trigger(1, env_b) -env_processes = { - "s3": env_a, #sweep(beta, env_a), - "s4": triggered_env_b #rename('parameterized', triggered_env_b) #sweep(beta, triggered_env_b) -} -# parameterized_env_processes = parameterize_states(env_processes) -# -# pp.pprint(parameterized_env_processes) -# exit() - -# 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 - -partial_state_update_block = { - "m1": { - "policies": { - "b1": p1m1, - "b2": p2m1 - }, - "variables": { - "s1": s1m1, - "s2": s2m1 - } - }, - "m2": { - "policies": { - "b1": p1m2, - "b2": p2m2, - }, - "variables": { - "s1": s1m2, - "s2": s2m2 - } - }, - "m3": { - "policies": { - "b1": p1m3, - "b2": p2m3 - }, - "variables": { - "s1": s1m3, - "s2": s2m3 - } - } -} - # config_sim Necessary sim_config = config_sim( { "N": 2, "T": range(5), - "M": g # Optional + "M": g, # Optional } ) # New Convention +partial_state_update_blocks = psub_list(psu_block, psu_steps) append_configs( sim_configs=sim_config, initial_state=genesis_states, seeds=seeds, - raw_exogenous_states=raw_exogenous_states, - env_processes=env_processes, - partial_state_update_blocks=partial_state_update_block -) \ No newline at end of file + 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/simulations/regression_tests/udo.py b/simulations/regression_tests/udo.py index f974b3e..e71ad13 100644 --- a/simulations/regression_tests/udo.py +++ b/simulations/regression_tests/udo.py @@ -4,7 +4,8 @@ from functools import reduce from cadCAD.utils import SilentDF #, val_switch from cadCAD.configuration import append_configs -from cadCAD.configuration.utils import time_step, config_sim, proc_trigger, timestep_trigger, genereate_psubs +from cadCAD.configuration.utils import time_step, config_sim, var_trigger, var_substep_trigger, genereate_psubs, \ + env_trigger, psub_list from cadCAD.configuration.utils.userDefinedObject import udoPipe, UDO import pandas as pd @@ -69,32 +70,36 @@ state_dict = { 'timestamp': '2019-01-01 00:00:00' } -policies, state_updates = {}, {} -# -# def assign_udo_policy(udo): -# def policy(_g, step, sL, s): -# s['udo_policies'][udo].updateX() -# return {udo: udoPipe(s['udo_policies'][udo])} -# return policy -# policies_updates = {p: assign_udo_policy(udo) for p, udo in zip(['p1', 'p2'], ['udo_A', 'udo_B'])} +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 +# 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 +# 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 increment_state_by_int(y: str, incr_by: int): -# return lambda _g, step, sL, s, _input: (y, s[y] + incr_by) -state_updates['increment'] = add('increment', 1) +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): @@ -110,12 +115,15 @@ def state_udo_update(_g, step, sL, s, _input): s['state_udo'].updateX().perceive(s) x = udoPipe(s['state_udo']) return y, x -state_updates['state_udo'] = state_udo_update +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_updates['state_udo_tracker'] = track('state_udo_tracker', 'state_udo') +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): @@ -125,12 +133,15 @@ def track_state_udo_perception(destination, source): else: return past_perception return lambda _g, step, sL, s, _input: (destination, id(s[source].perception)) -state_updates['state_udo_perception_tracker'] = track_state_udo_perception('state_udo_perception_tracker', 'state_udo') +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 -state_updates['udo_policies'] = view_udo_policy +for m in psu_steps: + psu_block[m]["variables"]['udo_policies'] = view_udo_policy def track_udo_policy(destination, source): @@ -140,56 +151,32 @@ def track_udo_policy(destination, source): else: return v.x return lambda _g, step, sL, s, _input: (destination, tuple(val_switch(v) for _, v in s[source].items())) -state_updates['udo_policy_tracker'] = track_udo_policy('udo_policy_tracker', 'udo_policies') +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 - -system_substeps = 3 -# state_updates['timestamp'] = update_timestamp -state_updates['timestamp'] = timestep_trigger(end_substep=system_substeps, y='timestamp', f=update_timestamp) -# state_updates['timestamp'] = proc_trigger(y='timestamp', f=update_timestamp, conditions={'substep': [0, substeps]}, cond_op=lambda a, b: a and b) - -print() -print("State Updates:") -pp.pprint(state_updates) -print() -print("Policies:") -pp.pprint(policies) -print() - -filter_out = lambda remove_list, state_list: list(filter(lambda state: state not in remove_list, state_list)) - -states = list(state_updates.keys()) -# states_noTS = filter_out(['timestamp'], states) -# states_grid = [states,states_noTS,states_noTS] - -# states_grid = [states] * system_substeps # -states_grid = [states,states,states] -policy_grid = [['a', 'b'], ['a', 'b'], ['a', 'b']] - - -PSUBS = genereate_psubs(policy_grid, states_grid, policies, state_updates) -pp.pprint(PSUBS) # 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, - seeds={}, - raw_exogenous_states={}, - env_processes={}, - partial_state_update_blocks=PSUBS, - # policy_ops=[lambda a, b: {**a, **b}] + partial_state_update_blocks=partial_state_update_blocks ) -# pp.pprint(partial_state_update_blocks) - -# PSUB = { -# 'policies': policies, -# 'states': state_updates -# } -# partial_state_update_blocks = [PSUB] * substeps - - +print() +print("State Updates:") +pp.pprint(partial_state_update_blocks) +print() \ No newline at end of file diff --git a/simulations/validation/udo.py b/simulations/regression_tests/udo_inter_substep_update.py similarity index 98% rename from simulations/validation/udo.py rename to simulations/regression_tests/udo_inter_substep_update.py index e3c2e66..0e4ffc9 100644 --- a/simulations/validation/udo.py +++ b/simulations/regression_tests/udo_inter_substep_update.py @@ -5,7 +5,7 @@ from cadCAD.configuration import append_configs from cadCAD.configuration.utils import time_step, ep_time_step, config_sim from cadCAD.configuration.utils.userDefinedObject import udoPipe, UDO import pandas as pd - +import pprint as pp from fn.func import curried DF = SilentDF(pd.read_csv('/Users/jjodesty/Projects/DiffyQ-SimCAD/simulations/external_data/output.csv')) @@ -161,3 +161,8 @@ append_configs( partial_state_update_blocks=partial_state_update_blocks, # policy_ops=[lambda a, b: {**a, **b}] ) + +print() +print("State Updates:") +pp.pprint(partial_state_update_blocks) +print() \ No newline at end of file diff --git a/simulations/validation/marbles.py b/simulations/tutorials/marbles.py similarity index 100% rename from simulations/validation/marbles.py rename to simulations/tutorials/marbles.py diff --git a/simulations/validation/marbles2.py b/simulations/tutorials/marbles2.py similarity index 54% rename from simulations/validation/marbles2.py rename to simulations/tutorials/marbles2.py index 91c3f93..f934327 100644 --- a/simulations/validation/marbles2.py +++ b/simulations/tutorials/marbles2.py @@ -1,4 +1,5 @@ from cadCAD.configuration import append_configs +from cadCAD.configuration.utils.userDefinedObject import udoPipe, UDO import networkx as nx import matplotlib.pyplot as plt import numpy as np @@ -21,11 +22,98 @@ for node in G.nodes: scale=100 nx.draw_kamada_kawai(G, node_size=balls*scale,labels=nx.get_node_attributes(G,'initial_balls')) -initial_conditions = {'balls': balls, 'network': G} +def greedy_robot(src_balls, dst_balls): + # robot wishes to accumlate balls at its source + # takes half of its neighbors balls + if src_balls < dst_balls: + return -np.floor(dst_balls / 2) + else: + return 0 + + +def fair_robot(src_balls, dst_balls): + # robot follows the simple balancing rule + return np.sign(src_balls - dst_balls) + + +def giving_robot(src_balls, dst_balls): + # robot wishes to gice away balls one at a time + if src_balls > 0: + return 1 + else: + return 0 + +robot_strategies = [greedy_robot,fair_robot, giving_robot] + +for node in G.nodes: + nstrats = len(robot_strategies) + rv = np.random.randint(0,nstrats) + G.nodes[node]['strat'] = robot_strategies[rv] + +for e in G.edges: + owner_node = e[0] + G.edges[e]['strat'] = G.nodes[owner_node]['strat'] + +default_policy = {'nodes': [], 'edges': {}, 'quantity': {}, 'node_strats': {}, 'edge_strats': {}, 'delta': {}} +class robot(object): + def __init__(self, graph, balls, internal_policy=default_policy): + self.mem_id = str(hex(id(self))) + self.internal_policy = internal_policy + self.graph = graph + self.balls = balls + + + def robotic_network(self, graph, balls): # move balls + self.graph, self.balls = graph, balls + delta_balls = {} + for e in self.graph.edges: + src = e[0] + src_balls = self.balls[src] + dst = e[1] + dst_balls = self.balls[dst] + + # transfer balls according to specific robot strat + strat = self.graph.edges[e]['strat'] + delta_balls[e] = strat(src_balls, dst_balls) + + self.internal_policy = {'nodes': [], 'edges': {}, 'quantity': {}, 'node_strats': {}, 'edge_strats': {}, 'delta': delta_balls} + return self + + def agent_arrival(self, graph, balls): # add node + self.graph, self.balls = graph, balls + node = len(self.graph.nodes) + edge_list = self.graph.edges + + # choose a m random edges without replacement + # new = np.random.choose(edgelist,m) + new = [0, 1] # tester + + nodes = [node] + edges = [(node, new_node) for new_node in new] + + initial_balls = {node: np.random.randint(1, 25)} + + rv = np.random.randint(0, nstrats) + node_strat = {node: robot_strategies[rv]} + + edge_strats = {e: robot_strategies[rv] for e in edges} + + self.internal_policy = {'nodes': nodes, + 'edges': edges, + 'quantity': initial_balls, + 'node_strats': node_strat, + 'edge_strats': edge_strats, + 'delta': np.zeros(node + 1) + } + + return self + +robot_udo = UDO(udo=robot(G, balls), masked_members=['obj']) +initial_conditions = {'balls': balls, 'network': G, 'robot': robot_udo} def update_balls(params, step, sL, s, _input): - delta_balls = _input['delta'] + delta_balls = _input['robot'].internal_policy['delta'] new_balls = s['balls'] for e in G.edges: move_ball = delta_balls[e] @@ -42,20 +130,20 @@ def update_balls(params, step, sL, s, _input): def update_network(params, step, sL, s, _input): - new_nodes = _input['nodes'] - new_edges = _input['edges'] - new_balls = _input['quantity'] + new_nodes = _input['robot'].internal_policy['nodes'] + new_edges = _input['robot'].internal_policy['edges'] + new_balls = _input['robot'].internal_policy['quantity'] graph = s['network'] for node in new_nodes: graph.add_node(node) graph.nodes[node]['initial_balls'] = new_balls[node] - graph.nodes[node]['strat'] = _input['node_strats'][node] + graph.nodes[node]['strat'] = _input['robot'].internal_policy['node_strats'][node] for edge in new_edges: graph.add_edge(edge[0], edge[1]) - graph.edges[edge]['strat'] = _input['edge_strats'][edge] + graph.edges[edge]['strat'] = _input['robot'].internal_policy['edge_strats'][edge] key = 'network' value = graph @@ -63,8 +151,8 @@ def update_network(params, step, sL, s, _input): def update_network_balls(params, step, sL, s, _input): - new_nodes = _input['nodes'] - new_balls = _input['quantity'] + new_nodes = _input['robot'].internal_policy['nodes'] + new_balls = _input['robot'].internal_policy['quantity'] balls = np.zeros(len(s['balls']) + len(new_nodes)) for node in s['network'].nodes: @@ -79,91 +167,17 @@ def update_network_balls(params, step, sL, s, _input): return (key, value) -def greedy_robot(src_balls, dst_balls): - # robot wishes to accumlate balls at its source - # takes half of its neighbors balls - if src_balls < dst_balls: - delta = -np.floor(dst_balls / 2) - else: - delta = 0 - - return delta - - -def fair_robot(src_balls, dst_balls): - # robot follows the simple balancing rule - delta = np.sign(src_balls - dst_balls) - - return delta - - -def giving_robot(src_balls, dst_balls): - # robot wishes to gice away balls one at a time - if src_balls > 0: - delta = 1 - else: - delta = 0 - - return delta - -robot_strategies = [greedy_robot,fair_robot, giving_robot] - -for node in G.nodes: - nstrats = len(robot_strategies) - rv = np.random.randint(0,nstrats) - G.nodes[node]['strat'] = robot_strategies[rv] - -for e in G.edges: - owner_node = e[0] - G.edges[e]['strat'] = G.nodes[owner_node]['strat'] - - def robotic_network(params, step, sL, s): - graph = s['network'] - - delta_balls = {} - for e in graph.edges: - src = e[0] - src_balls = s['balls'][src] - dst = e[1] - dst_balls = s['balls'][dst] - - # transfer balls according to specific robot strat - strat = graph.edges[e]['strat'] - delta_balls[e] = strat(src_balls, dst_balls) - - return_dict = {'nodes': [], 'edges': {}, 'quantity': {}, 'node_strats': {}, 'edge_strats': {}, 'delta': delta_balls} - - return (return_dict) + s['robot'].robotic_network(s['network'], s['balls']) + return {'robot': udoPipe(s['robot'])} def agent_arrival(params, step, sL, s): - node = len(s['network'].nodes) - edge_list = s['network'].edges - - # choose a m random edges without replacement - # new = np.random.choose(edgelist,m) - new = [0, 1] # tester - - nodes = [node] - edges = [(node, new_node) for new_node in new] - - initial_balls = {node: np.random.randint(1, 25)} - - rv = np.random.randint(0, nstrats) - node_strat = {node: robot_strategies[rv]} - - edge_strats = {e: robot_strategies[rv] for e in edges} - - return_dict = {'nodes': nodes, - 'edges': edges, - 'quantity': initial_balls, - 'node_strats': node_strat, - 'edge_strats': edge_strats, - 'delta': np.zeros(node + 1) - } - return (return_dict) + s['robot'].agent_arrival(s['network'], s['balls']) + return {'robot': udoPipe(s['robot'])} +def get_robot(params, step, sL, s, _input): + return 'robot', _input['robot'] partial_state_update_blocks = [ { @@ -172,7 +186,8 @@ partial_state_update_blocks = [ 'p1': robotic_network }, 'variables': { # The following state variables will be updated simultaneously - 'balls': update_balls + 'balls': update_balls, + 'robot': get_robot } }, @@ -183,7 +198,8 @@ partial_state_update_blocks = [ }, 'variables': { # The following state variables will be updated simultaneously 'network': update_network, - 'balls': update_network_balls + 'balls': update_network_balls, + 'robot': get_robot } } ] diff --git a/simulations/validation/marbles2_run.py b/simulations/tutorials/marbles2_run.py similarity index 89% rename from simulations/validation/marbles2_run.py rename to simulations/tutorials/marbles2_run.py index b275b76..b004696 100644 --- a/simulations/validation/marbles2_run.py +++ b/simulations/tutorials/marbles2_run.py @@ -2,7 +2,6 @@ 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.validation import marbles2 from cadCAD import configs exec_mode = ExecutionMode() @@ -20,6 +19,7 @@ print("Tensor Field: config1") print(tabulate(tensor_field, headers='keys', tablefmt='psql')) print("Output:") print(tabulate(result, headers='keys', tablefmt='psql')) +print(result[['network']]) print() print(result[['network', 'substep']]) \ No newline at end of file diff --git a/simulations/validation/robot-marbles-agents-advanced-udo.ipynb b/simulations/tutorials/robot-marbles-agents-advanced.ipynb similarity index 100% rename from simulations/validation/robot-marbles-agents-advanced-udo.ipynb rename to simulations/tutorials/robot-marbles-agents-advanced.ipynb diff --git a/simulations/udo_run.py b/simulations/udo_run.py index bbb07ba..319969d 100644 --- a/simulations/udo_run.py +++ b/simulations/udo_run.py @@ -2,10 +2,9 @@ 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.validation import udo +from simulations.regression_tests import udo_inter_substep_update from cadCAD import configs - exec_mode = ExecutionMode() print("Simulation Execution: Single Configuration") diff --git a/simulations/udo_test.py b/simulations/udo_test.py index b31713b..6bb9333 100644 --- a/simulations/udo_test.py +++ b/simulations/udo_test.py @@ -5,7 +5,6 @@ from cadCAD.engine import ExecutionMode, ExecutionContext, Executor from simulations.regression_tests import udo from cadCAD import configs - exec_mode = ExecutionMode() print("Simulation Execution: Single Configuration") diff --git a/simulations/validation/historical_state_access.py b/simulations/validation/historical_state_access.py index 05e293d..d4a86ed 100644 --- a/simulations/validation/historical_state_access.py +++ b/simulations/validation/historical_state_access.py @@ -1,25 +1,25 @@ from cadCAD.configuration import append_configs from cadCAD.configuration.utils import config_sim +# Policies per Mechanism +# def p(_g, substep, sH, s): +# return {'last_update_block': sH[-1]} + +# def policies(_g, substep, sH, s, _input): +# y = 'policies' +# x = _input +# return (y, x) + +# policies = {"p1": p, "p2": p} + + # last_partial_state_update_block def last_update_block(_g, substep, sH, s, _input): return 'sh', sH[-1] - -# Policies per Mechanism -def p(_g, substep, sH, s): - return {'last_update_block': sH[-1]} - def add(y, x): return lambda _g, substep, sH, s, _input: (y, s[y] + x) -def policies(_g, substep, sH, s, _input): - y = 'policies' - x = _input - return (y, x) - -policies = {"p1": p, "p2": p} - genesis_states = { 's': 0, 'sh': [{}], # {[], {}} @@ -34,7 +34,7 @@ variables = { PSUB = { - "policies": policies, + "policies": {}, #policies, "variables": variables } @@ -58,6 +58,5 @@ append_configs( seeds={}, raw_exogenous_states={}, env_processes={}, - partial_state_update_blocks=partial_state_update_block, - policy_ops=[lambda a, b: a + b] + partial_state_update_blocks=partial_state_update_block )