From ff44b0bacf7e02fb03c6cb68662eefce58487592 Mon Sep 17 00:00:00 2001 From: "Joshua E. Jodesty" Date: Tue, 1 Oct 2019 07:52:56 -0400 Subject: [PATCH] parameterized execution mone --- cadCAD/__init__.py | 19 +- cadCAD/engine/__init__.py | 37 ++- cadCAD/engine/simulation.py | 41 +-- dist/cadCAD-0.0.2-py3-none-any.whl | Bin 0 -> 26763 bytes dist/cadCAD-0.0.2.tar.gz | Bin 0 -> 18279 bytes dist/cadCAD-0.3.0-py3-none-any.whl | Bin 23713 -> 0 bytes dist/cadCAD-0.3.0.tar.gz | Bin 17443 -> 0 bytes .../examples/event_bench/main.py | 2 +- requirements.txt | 1 - setup.py | 13 +- simulations/distributed/config1.py | 259 +++++++++-------- simulations/distributed/messaging.py | 132 +++++++++ simulations/distributed/messaging_app.py | 274 ++++++++++++++++++ simulations/distributed/messaging_test.py | 28 ++ 14 files changed, 649 insertions(+), 157 deletions(-) create mode 100644 dist/cadCAD-0.0.2-py3-none-any.whl create mode 100644 dist/cadCAD-0.0.2.tar.gz delete mode 100644 dist/cadCAD-0.3.0-py3-none-any.whl delete mode 100644 dist/cadCAD-0.3.0.tar.gz create mode 100644 simulations/distributed/messaging.py create mode 100644 simulations/distributed/messaging_app.py create mode 100644 simulations/distributed/messaging_test.py diff --git a/cadCAD/__init__.py b/cadCAD/__init__.py index 7346dd1..e2a753b 100644 --- a/cadCAD/__init__.py +++ b/cadCAD/__init__.py @@ -1,5 +1,20 @@ name = "cadCAD" configs = [] -from ascii_art import production -print(production) \ No newline at end of file +print(r''' + __________ ____ + ________ __ _____/ ____/ | / __ \ + / ___/ __` / __ / / / /| | / / / / +/ /__/ /_/ / /_/ / /___/ ___ |/ /_/ / +\___/\__,_/\__,_/\____/_/ |_/_____/ +by BlockScience +====================================== + Complex Adaptive Dynamics + o i e + m d s + p e i + u d g + t n + e + r +''') \ No newline at end of file diff --git a/cadCAD/engine/__init__.py b/cadCAD/engine/__init__.py index 2f9f781..e06b716 100644 --- a/cadCAD/engine/__init__.py +++ b/cadCAD/engine/__init__.py @@ -6,7 +6,9 @@ from pandas.core.frame import DataFrame from pyspark.context import SparkContext from pyspark import cloudpickle import pickle +from fn.func import curried +from cadCAD.distroduce.configuration.kakfa import configure_producer from cadCAD.utils import flatten from cadCAD.configuration import Configuration, Processor from cadCAD.configuration.utils import TensorFieldReport @@ -65,7 +67,6 @@ def parallelize_simulations( results = p.map(lambda t: t[0](t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10]), params) return results - def distributed_simulations( simulation_execs: List[Callable], var_dict_list: List[VarDictType], @@ -78,7 +79,8 @@ def distributed_simulations( sessionIDs, simulationIDs, runIDs: List[int], - sc: SparkContext = None + sc, + kafkaConfig ): func_params_zipped = list( @@ -86,11 +88,13 @@ def distributed_simulations( ) func_params_kv = [((t[0], t[1], t[2], t[3]), (t[4], t[5], t[6])) for t in func_params_zipped] def simulate(k, v): + from kafka import KafkaProducer + prod_config = kafkaConfig['producer_config'] + kafkaConfig['producer'] = KafkaProducer(**prod_config) (sim_exec, config, env_procs) = [f[1] for f in func_params_kv if f[0] == k][0] - print(env_procs) results = sim_exec( v['var_dict'], v['states_lists'], config, env_procs, v['Ts'], v['Ns'], - k[0], k[1], k[2], k[3] + k[0], k[1], k[2], k[3], kafkaConfig ) return results @@ -102,22 +106,36 @@ def distributed_simulations( {'var_dict': t[4], 'states_lists': t[5], 'Ts': t[6], 'Ns': t[7]} ) for t in val_params ] - results_rdd = sc.parallelize(val_params_kv).map(lambda x: simulate(*x)) + results_rdd = sc.parallelize(val_params_kv).coalesce(35) - return list(results_rdd.collect()) + return list(results_rdd.map(lambda x: simulate(*x)).collect()) class ExecutionContext: - def __init__(self, context: str = ExecutionMode.multi_proc) -> None: + def __init__(self, + context=ExecutionMode.multi_proc, + # spark_context=None, + # kafka_config=None, + # spark_data_transformation=None, + method=None) -> None: self.name = context - self.method = None + # self.method = method + + # def dist_proc_closure(simulation_execs, var_dict_list, states_lists, configs_structs, env_processes_list, Ts, Ns, + # userIDs, sessionIDs, simulationIDs, runIDs, + # sc=spark_context, kafkaConfig=kafka_config): + # return distributed_simulations( + # simulation_execs, var_dict_list, states_lists, configs_structs, env_processes_list, Ts, Ns, + # userIDs, sessionIDs, simulationIDs, runIDs, + # spark_context, spark_data_transformation, kafka_config + # ) if context == 'single_proc': self.method = single_proc_exec elif context == 'multi_proc': self.method = parallelize_simulations elif context == 'dist_proc': - self.method = distributed_simulations + self.method = method class Executor: @@ -176,7 +194,6 @@ class Executor: simulation_execs, var_dict_list, states_lists, configs_structs, env_processes_list, Ts, Ns, userIDs, sessionIDs, simulationIDs, runIDs ) - elif self.exec_context == ExecutionMode.dist_proc: simulations = self.exec_method( simulation_execs, var_dict_list, states_lists, configs_structs, env_processes_list, Ts, Ns, diff --git a/cadCAD/engine/simulation.py b/cadCAD/engine/simulation.py index 24949c0..55573ae 100644 --- a/cadCAD/engine/simulation.py +++ b/cadCAD/engine/simulation.py @@ -1,11 +1,8 @@ -from pprint import pprint from typing import Any, Callable, Dict, List, Tuple -from pathos.pools import ThreadPool as TPool +# from pathos.pools import ThreadPool as TPool from copy import deepcopy from functools import reduce -from pyspark import SparkContext - from cadCAD.engine.utils import engine_exception from cadCAD.utils import flatten @@ -30,13 +27,14 @@ class Executor: sub_step: int, sL: List[Dict[str, Any]], s: Dict[str, Any], - funcs: List[Callable] + funcs: List[Callable], + kafkaConfig ) -> Dict[str, Any]: ops = self.policy_ops def get_col_results(sweep_dict, sub_step, sL, s, funcs): - return list(map(lambda f: f(sweep_dict, sub_step, sL, s), funcs)) + return list(map(lambda f: f(sweep_dict, sub_step, sL, s, kafkaConfig), funcs)) def compose(init_reduction_funct, funct_list, val_list): result, i = None, 0 @@ -108,18 +106,18 @@ class Executor: policy_funcs: List[Callable], env_processes: Dict[str, Callable], time_step: int, - run: int + run: int, + kafkaConfig ) -> List[Dict[str, Any]]: 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) + self.get_policy_input(sweep_dict, sub_step, sH, last_in_obj, policy_funcs, kafkaConfig) ) - 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)) + yield self.state_update_exception(f(sweep_dict, sub_step, sH, last_in_obj, _input, kafkaConfig)) def transfer_missing_fields(source, destination): for k in source: @@ -131,7 +129,6 @@ class Executor: last_in_copy: Dict[str, Any] = transfer_missing_fields(last_in_obj, dict(generate_record(state_funcs))) last_in_copy: Dict[str, Any] = self.apply_env_proc(sweep_dict, env_processes, last_in_copy) last_in_copy['substep'], last_in_copy['timestep'], last_in_copy['run'] = sub_step, time_step, run - sL.append(last_in_copy) del last_in_copy @@ -145,7 +142,8 @@ class Executor: configs: List[Tuple[List[Callable], List[Callable]]], env_processes: Dict[str, Callable], time_step: int, - run: int + run: int, + kafkaConfig ) -> List[Dict[str, Any]]: sub_step = 0 @@ -162,11 +160,9 @@ class Executor: states_list: List[Dict[str, Any]] = [genesis_states] sub_step += 1 - for [s_conf, p_conf] in configs: # tensor field - states_list: List[Dict[str, Any]] = self.partial_state_update( - sweep_dict, sub_step, states_list, simulation_list, s_conf, p_conf, env_processes, time_step, run + sweep_dict, sub_step, states_list, simulation_list, s_conf, p_conf, env_processes, time_step, run, kafkaConfig ) sub_step += 1 @@ -182,15 +178,15 @@ class Executor: configs: List[Tuple[List[Callable], List[Callable]]], env_processes: Dict[str, Callable], time_seq: range, - run: int + run: int, + kafkaConfig ) -> List[List[Dict[str, Any]]]: - time_seq: List[int] = [x + 1 for x in time_seq] simulation_list: List[List[Dict[str, Any]]] = [states_list] for time_step in time_seq: pipe_run: List[Dict[str, Any]] = self.state_update_pipeline( - sweep_dict, simulation_list, configs, env_processes, time_step, run + sweep_dict, simulation_list, configs, env_processes, time_step, run, kafkaConfig ) _, *pipe_run = pipe_run @@ -210,12 +206,11 @@ class Executor: session_id, simulation_id, run_id, - sc: SparkContext = None + kafkaConfig ) -> List[List[Dict[str, Any]]]: def execute_run(sweep_dict, states_list, configs, env_processes, time_seq, run) -> List[Dict[str, Any]]: run += 1 - def generate_init_sys_metrics(genesis_states_list): for d in genesis_states_list: d['run'], d['substep'], d['timestep'] = run, 0, 0 @@ -225,15 +220,11 @@ class Executor: states_list_copy: List[Dict[str, Any]] = list(generate_init_sys_metrics(deepcopy(states_list))) first_timestep_per_run: List[Dict[str, Any]] = self.run_pipeline( - sweep_dict, states_list_copy, configs, env_processes, time_seq, run + sweep_dict, states_list_copy, configs, env_processes, time_seq, run, kafkaConfig ) del states_list_copy return first_timestep_per_run - # - # pprint() - # - # exit() pipe_run = flatten( [execute_run(sweep_dict, states_list, configs, env_processes, time_seq, run) for run in range(runs)] diff --git a/dist/cadCAD-0.0.2-py3-none-any.whl b/dist/cadCAD-0.0.2-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..bb931c5aac3de05028fa064bb83cd8bba7559611 GIT binary patch literal 26763 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 literal 0 HcmV?d00001 diff --git a/dist/cadCAD-0.0.2.tar.gz b/dist/cadCAD-0.0.2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ee8ebc9576fdd22c3db5d9fdcb4b5c538c3affbb GIT binary patch literal 18279 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 a-*#%MVjvzmP*7+QKb#1WtGV|1Z~q6RruvZp diff --git a/dist/cadCAD-0.3.0.tar.gz b/dist/cadCAD-0.3.0.tar.gz deleted file mode 100644 index 9c8e0fba17feae1c4e8f54cce1eb5e35f9cb9cb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17443 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 E01-vU0{{R3 diff --git a/distributed_produce/examples/event_bench/main.py b/distributed_produce/examples/event_bench/main.py index c4d1edf..e4a96a2 100644 --- a/distributed_produce/examples/event_bench/main.py +++ b/distributed_produce/examples/event_bench/main.py @@ -11,7 +11,7 @@ from distributed_produce.examples.event_bench.spark.session import spark_context def main(sc, spark_runs, rdd_parts, sim_time, sim_runs, parameterized_message): publish_times, spark_job_times = [], [] - prod_config = {'bootstrap_servers': 'localhost:9092', 'acks': 0} + prod_config = {'bootstrap_servers': 'localhost:9092', 'acks': 'all'} exec_spark_job = lambda run: distributed_produce( sc, run, sim_time, sim_runs, rdd_parts, parameterized_message, prod_config ) diff --git a/requirements.txt b/requirements.txt index 5944b38..a46529d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,3 @@ tabulate funcy pyspark kafka-python -cloudpickle \ No newline at end of file diff --git a/setup.py b/setup.py index 56e3976..71eb94c 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ provided. """ setup(name='cadCAD', - version='0.3.0', + version='0.0.2', description="cadCAD: a differential games based simulation software package for research, validation, and \ Computer Aided Design of economic systems", long_description=long_description, @@ -27,5 +27,14 @@ setup(name='cadCAD', author='Joshua E. Jodesty', author_email='joshua@block.science, joshua.jodesty@gmail.com', license='LICENSE.txt', - packages=find_packages() + packages=find_packages(), + install_requires=[ + "pandas", + "wheel", + "pathos", + "fn", + "tabulate", + "funcy", + "kafka-python" + ] ) \ No newline at end of file diff --git a/simulations/distributed/config1.py b/simulations/distributed/config1.py index 1e32d9f..8bf81da 100644 --- a/simulations/distributed/config1.py +++ b/simulations/distributed/config1.py @@ -1,124 +1,151 @@ -import numpy as np -from datetime import timedelta +import random +from copy import deepcopy +from datetime import timedelta, datetime from cadCAD.configuration import append_configs from cadCAD.configuration.utils import bound_norm_random, config_sim, time_step, env_trigger +from cadCAD.utils.sys_config import update_timestamp -seeds = { - 'z': np.random.RandomState(1), - 'a': np.random.RandomState(2), - 'b': np.random.RandomState(3), - 'c': np.random.RandomState(4) -} +def choose_rnd(x: list, choices): + def choose(x, choices): + for n in list(range(choices)): + elem = random.choice(x) + x.remove(elem) + yield elem + + copied_list = deepcopy(x) + results = list(choose(copied_list, choices)) + del copied_list + return results + + +def message(sender, receiver, _input): + return {'sender': sender, 'receiver': receiver, 'input': _input, 'sent_time': datetime.now()} + +def enter_room_msgs(room, users): + return [message(user, room, f"{user} enters chat-room") for user in users] + +def exit_room_msgs(room, users): + return [message(user, room, f"{user} exited chat-room") for user in users] + +rooms = ['room_1', 'room_2'] +user_group_1 = ['A', 'B', 'C', 'D'] +user_group_2 = ['E', 'F', 'G', 'H'] +room_1_messages = enter_room_msgs('room_1', random.shuffle(user_group_1)) +room_2_messages = enter_room_msgs('room_2', random.shuffle(user_group_2)) +def intitialize_conditions(): + users = user_group_1 + user_group_2 + messages = sorted(room_1_messages + room_2_messages, key=lambda i: i['time']) + room_1_session = {'room': 'room_1', 'users': user_group_1, 'messages': room_1_messages} + return { + 'client_a': room_1_session, + 'client_b': room_1_session, + 'server': {'rooms': rooms, 'users': users, 'messages': messages}, + 'record_creation': datetime.now() + } + + +def send_message(room, sender, receiver, _input): + return lambda _g, step, sL, s: { + 'types': ['send'], + 'events': [ + { + 'type': 'send', + 'room': room, + 'user': sender, + 'sent': [message(sender, receiver, _input)] + } + ] + } + + +def exit_room(room, sender): + return lambda _g, step, sL, s: { + 'types': ['exit'], + 'events': [ + { + 'type': 'exit', + 'room': room, + 'user': sender, + 'sent': exit_room_msgs(sender, room) + } + ] + } # Policies per Mechanism -def p1m1(_g, step, sL, s): - return {'param1': 1} -def p2m1(_g, step, sL, s): - return {'param1': 1, 'param2': 4} +# ToDo Randomize client choices in runtime +[alpha, omega] = choose_rnd(user_group_1, 2) +a_msg1 = send_message('room_1', alpha, omega, f'Hello {omega}') +b_msg1 = send_message('room_1', omega, alpha, f'Hello {alpha}') -def p1m2(_g, step, sL, s): - return {'param1': 'a', 'param2': 2} -def p2m2(_g, step, sL, s): - return {'param1': 'b', 'param2': 4} +a_msg2 = send_message('room_1', alpha, omega, f'Bye {omega}') +b_msg2 = send_message('room_1', omega, alpha, f'Bye {alpha}') -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])} +a_msg3 = exit_room('room_1', alpha) +b_msg3 = exit_room('room_1', omega) + +def remove_exited_users(users, actions): + users = deepcopy(users) + if 'exit' in actions['types']: + for event in actions['events']: + if event['type'] == 'exit': + for user in event['user']: + users.remove(user) + return users + +# State Updates + +# {'room': 'room_1', 'users': user_group_1, 'messages': room_1_messages} +def process_messages(_g, step, sL, s, actions): + + return 'client', {'room': s['room'], 'users': users, 'messages': actions['sent']} + +def process_exits(_g, step, sL, s, actions): + users = remove_exited_users(s['users'], actions) + return 'server', {'rooms': s['room'], 'users': users, 'messages': actions['sent']} -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) +update_record_creation = update_timestamp( + 'record_creation', + timedelta(days=0, minutes=0, seconds=30), + '%Y-%m-%d %H:%M:%S' +) -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) - - -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, - "timestamp": update_timestamp - } - }, - { - "policies": { - "b1": p1m2, - "b2": p2m2 - }, - "variables": { - "s1": s1m2, - "s2": s2m2 - } - }, - { - "policies": { - "b1": p1m3, - "b2": p2m3 - }, - "variables": { - "s1": s1m3, - "s2": s2m3 - } - } -] +# partial_state_update_block = [ +# { +# "policies": { +# "b1": a_msg1, +# "b2": b_msg1 +# }, +# "variables": { +# "client_a": client_a_m1, +# "client_b": client_b_m1, +# "received": update_timestamp +# } +# }, +# { +# "policies": { +# "b1": a_msg2, +# "b2": b_msg2 +# }, +# "variables": { +# "s1": s1m2, +# "s2": s2m2 +# } +# }, +# { +# "policies": { +# "b1": a_msg3, +# "b2": b_msg3 +# }, +# "variables": { +# "s1": s1m3, +# "s2": s2m3 +# } +# } +# ] sim_config = config_sim( @@ -128,11 +155,11 @@ sim_config = config_sim( } ) -append_configs( - user_id='user_a', - 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 +# append_configs( +# user_id='user_a', +# 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/simulations/distributed/messaging.py b/simulations/distributed/messaging.py new file mode 100644 index 0000000..a6082f4 --- /dev/null +++ b/simulations/distributed/messaging.py @@ -0,0 +1,132 @@ +from copy import deepcopy +from datetime import timedelta, datetime +import time + +from cadCAD.configuration import append_configs +from cadCAD.configuration.utils import config_sim + +# session = enters('room_1', ['A', 'B']) +intitial_conditions = { + 'record_creation': datetime.now(), + 'client_a': {'users': [], 'messages': [], 'avg_send_time': 1}, + 'client_b': {'users': [], 'messages': [], 'avg_send_time': 1} +} + + +# Actions +def message(client_id, room, action, _input, sender, receiver=None): + # start_time = datetime.now() + result = { + 'types': [action], + 'messages': [ + { + 'client': client_id, 'room': room, 'action': action, + 'sender': sender, 'receiver': receiver, + 'input': _input, + 'creatred': datetime.now() + } + ] + } + # datetime.now() - start_time + return result + +def enter_action(state, room, user): + return lambda _g, step, sL, s: message(state, room, 'enter', f"{user} enters {room}", user) + +def message_action(state, room, _input, sender, receiver): + return lambda _g, step, sL, s: message(state, room, 'send', _input, sender, receiver) + +def exit_action(state, room, user): + return lambda _g, step, sL, s: message(state, room, 'exit', f"{user} exited {room}", user) + +# State Updates +def update_users(users, actions, action_types=['send','enter','exit']): + users = deepcopy(users) + for action_type in action_types: + if action_type in actions['types']: + for msg in actions['messages']: + if msg['action'] == 'send' and action_type == 'send': + continue + elif msg['action'] == 'enter' and action_type == 'enter': + for user in msg['sender']: + users.append(user) # register_entered + elif msg['action'] == 'exit' and action_type == 'exit': + for user in msg['sender']: + users.remove(user) # remove_exited + return users + + +def send_message(state): + return lambda _g, step, sL, s, actions: ( + state, + { + 'users': update_users(s[state]['users'], actions), + 'messages': actions['messages'], 'avg_send_time': 1 + } + ) + +def current_time(state): + return lambda _g, step, sL, s, actions: (state, datetime.now()) + +sim_composition = [ + { + "policies": { + "b1": enter_action('server', 'room_1', 'A'), + "b2": enter_action('server', 'room_1', 'B') + }, + "variables": { + 'client_a': send_message('client_a'), + 'client_b': send_message('client_b'), + 'record_creation': current_time('record_creation') + } + }, + { + "policies": { + "b1": message_action('client_A', 'room_1', "Hi B", 'A', 'B'), + "b2": message_action('client_B', 'room_1', "Hi A", 'B', 'A') + }, + "variables": { + 'client_a': send_message('client_a'), + 'client_b': send_message('client_b'), + 'record_creation': current_time('record_creation') + } + }, + { + "policies": { + "b1": message_action('client_A', 'room_1', "Bye B", 'A', 'B'), + "b2": message_action('client_B', 'room_1', "Bye A", 'B', 'A') + }, + "variables": { + 'client_a': send_message('client_a'), + 'client_b': send_message('client_b'), + 'record_creation': current_time('record_creation') + } + }, + { + "policies": { + "b1": exit_action('server', 'room_1', 'A'), + "b2": exit_action('server', 'room_1', 'B') + }, + "variables": { + 'client_a': send_message('client_a'), + 'client_b': send_message('client_b'), + 'record_creation': current_time('record_creation') + } + } +] + +# 5 = 10000 / (500 x 4) +sim_config = config_sim( + { + "N": 5, + "T": range(500), + } +) + +append_configs( + user_id='user_a', + sim_configs=sim_config, + initial_state=intitial_conditions, + partial_state_update_blocks=sim_composition, + policy_ops=[lambda a, b: a + b] +) \ No newline at end of file diff --git a/simulations/distributed/messaging_app.py b/simulations/distributed/messaging_app.py new file mode 100644 index 0000000..fe3618c --- /dev/null +++ b/simulations/distributed/messaging_app.py @@ -0,0 +1,274 @@ +from functools import reduce + +import pandas as pd +from copy import deepcopy +from datetime import datetime + +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.utils import arrange_cols +from cadCAD import configs + +from pyspark.sql import SparkSession +from pyspark.context import SparkContext + +from kafka import KafkaProducer + +def count(start, step): + while True: + yield start + start += step + +spark = SparkSession\ + .builder\ + .appName("distroduce")\ + .getOrCreate() + +sc: SparkContext = spark.sparkContext +print(f"Spark UI: {sc.uiWebUrl}") +print() + +# session = enters('room_1', ['A', 'B']) +intitial_conditions = { + 'record_creation': datetime.now(), + 'client_a': {'users': [], 'messages': [], 'msg_count': 0, 'send_time': 0.0}, + 'client_b': {'users': [], 'messages': [], 'msg_count': 0, 'send_time': 0.0}, + 'total_msg_count': 0, + 'total_send_time': 0.000000 +} + + +# Actions +def messages(client_id, room, action, _input, sender, receiver=None): + return { + 'types': [action], + 'messages': [ + { + 'client': client_id, 'room': room, 'action': action, + 'sender': sender, 'receiver': receiver, + 'input': _input, + 'creatred': datetime.now() + } + ] + } + + +def enter_action(state, room, user): + def f(_g, step, sL, s, kafkaConfig): + msgs = messages(state, room, 'enter', f"{user} enters {room}", user) + msgs['send_times'] = [0.000000] + msgs['msg_counts'] = [len(msgs['messages'])] + return msgs + return f + +def message_actions(state, room, _input, sender, receiver): + msgs = messages(state, room, 'send', _input, sender, receiver) + msgs_list = msgs['messages'] + def send_action(_g, step, sL, s, kafkaConfig): + start_time = datetime.now() + for msg in msgs_list: + producer: KafkaProducer = kafkaConfig['producer'] + topic: str = kafkaConfig['send_topic'] + encoded_msg = str(msg).encode('utf-8') + producer.send(topic, encoded_msg) + msgs['send_times'] = [(datetime.now() - start_time).total_seconds()] + msgs['msg_counts'] = [len(msgs_list)] + return msgs + + return send_action + +def exit_action(state, room, user): + def f(_g, step, sL, s, kafkaConfig): + msgs = messages(state, room, 'exit', f"{user} exited {room}", user) + msgs_list = msgs['messages'] + msgs['send_times'] = [0.000000] + msgs['msg_counts'] = [len(msgs_list)] + return msgs + return f + +# State Updates +def update_users(users, actions, action_types=['send','enter','exit']): + users = deepcopy(users) + for action_type in action_types: + if action_type in actions['types']: + for msg in actions['messages']: + if msg['action'] == 'send' and action_type == 'send': + continue + elif msg['action'] == 'enter' and action_type == 'enter': + for user in msg['sender']: + users.append(user) # register_entered + elif msg['action'] == 'exit' and action_type == 'exit': + for user in msg['sender']: + users.remove(user) # remove_exited + return users + +add = lambda a, b: a + b +def count_messages(_g, step, sL, s, actions, kafkaConfig): + return 'total_msg_count', s['total_msg_count'] + reduce(add, actions['msg_counts']) + +def add_send_time(_g, step, sL, s, actions, kafkaConfig): + return 'total_send_time', s['total_send_time'] + reduce(add, actions['send_times']) + +def send_message(state): + return lambda _g, step, sL, s, actions, kafkaConfig: ( + state, + { + 'users': update_users(s[state]['users'], actions), + 'messages': actions['messages'], + 'msg_counts': reduce(add, actions['msg_counts']), + 'send_times': reduce(add, actions['send_times']) + } + ) + +def current_time(state): + return lambda _g, step, sL, s, actions, kafkaConfig: (state, datetime.now()) + +sim_composition = [ + { + "policies": { + "b1": enter_action('server', 'room_1', 'A'), + "b2": enter_action('server', 'room_1', 'B') + }, + "variables": { + 'client_a': send_message('client_a'), + 'client_b': send_message('client_b'), + 'total_msg_count': count_messages, + 'total_send_time': add_send_time, + 'record_creation': current_time('record_creation') + } + }, + { + "policies": { + "b1": message_actions('client_A', 'room_1', "Hi B", 'A', 'B'), + "b2": message_actions('client_B', 'room_1', "Hi A", 'B', 'A') + }, + "variables": { + 'client_a': send_message('client_a'), + 'client_b': send_message('client_b'), + 'total_msg_count': count_messages, + 'total_send_time': add_send_time, + 'record_creation': current_time('record_creation') + } + }, + { + "policies": { + "b1": message_actions('client_A', 'room_1', "Bye B", 'A', 'B'), + "b2": message_actions('client_B', 'room_1', "Bye A", 'B', 'A') + }, + "variables": { + 'client_a': send_message('client_a'), + 'client_b': send_message('client_b'), + 'total_msg_count': count_messages, + 'total_send_time': add_send_time, + 'record_creation': current_time('record_creation') + } + }, + { + "policies": { + "b1": exit_action('server', 'room_1', 'A'), + "b2": exit_action('server', 'room_1', 'B') + }, + "variables": { + 'client_a': send_message('client_a'), + 'client_b': send_message('client_b'), + 'total_msg_count': count_messages, + 'total_send_time': add_send_time, + 'record_creation': current_time('record_creation') + } + } +] + +# N = 5 = 10000 / (500 x 4) +# T = 500 +sim_config = config_sim( + { + "N": 1, + "T": range(10), + # "T": range(5000), + } +) + +append_configs( + user_id='user_a', + sim_configs=sim_config, + initial_state=intitial_conditions, + partial_state_update_blocks=sim_composition, + policy_ops=[lambda a, b: a + b] +) + +exec_mode = ExecutionMode() + +print("Simulation Execution: Distributed Execution") +kafka_config = {'send_topic': 'test', 'producer_config': {'bootstrap_servers': 'localhost:9092', 'acks': 'all'}} + +def distributed_simulations( + simulation_execs, + var_dict_list, + states_lists, + configs_structs, + env_processes_list, + Ts, + Ns, + userIDs, + sessionIDs, + simulationIDs, + runIDs, + sc=sc, + kafkaConfig=kafka_config + ): + + func_params_zipped = list( + zip(userIDs, sessionIDs, simulationIDs, runIDs, simulation_execs, configs_structs, env_processes_list) + ) + func_params_kv = [((t[0], t[1], t[2], t[3]), (t[4], t[5], t[6])) for t in func_params_zipped] + def simulate(k, v): + from kafka import KafkaProducer + prod_config = kafkaConfig['producer_config'] + kafkaConfig['producer'] = KafkaProducer(**prod_config) + (sim_exec, config, env_procs) = [f[1] for f in func_params_kv if f[0] == k][0] + results = sim_exec( + v['var_dict'], v['states_lists'], config, env_procs, v['Ts'], v['Ns'], + k[0], k[1], k[2], k[3], kafkaConfig + ) + + return results + + val_params = list(zip(userIDs, sessionIDs, simulationIDs, runIDs, var_dict_list, states_lists, Ts, Ns)) + val_params_kv = [ + ( + (t[0], t[1], t[2], t[3]), + {'var_dict': t[4], 'states_lists': t[5], 'Ts': t[6], 'Ns': t[7]} + ) for t in val_params + ] + results_rdd = sc.parallelize(val_params_kv).coalesce(35) + + return list(results_rdd.map(lambda x: simulate(*x)).collect()) + +dist_proc_ctx = ExecutionContext( + context=exec_mode.dist_proc, method=distributed_simulations +) +run = Executor(exec_context=dist_proc_ctx, configs=configs, spark_context=sc) + +i = 0 +for raw_result, tensor_field in run.execute(): + result = arrange_cols(pd.DataFrame(raw_result), False)[ + [ + 'user_id', 'session_id', 'simulation_id', 'run_id', 'timestep', 'substep', + 'record_creation', 'total_msg_count', 'total_send_time' + ] + ] + print() + if i == 0: + print(tabulate(tensor_field, headers='keys', tablefmt='psql')) + last = result.tail(1) + last['msg_per_sec'] = last['total_msg_count']/last['total_send_time'] + print("Output:") + print(tabulate(result.head(5), headers='keys', tablefmt='psql')) + print(tabulate(result.tail(5), headers='keys', tablefmt='psql')) + print(tabulate(last, headers='keys', tablefmt='psql')) + print() + i += 1 \ No newline at end of file diff --git a/simulations/distributed/messaging_test.py b/simulations/distributed/messaging_test.py new file mode 100644 index 0000000..e4150f5 --- /dev/null +++ b/simulations/distributed/messaging_test.py @@ -0,0 +1,28 @@ +import pandas as pd +from tabulate import tabulate + +from simulations.distributed.spark.session import spark_context as sc +from simulations.distributed import messaging + +from cadCAD.engine import ExecutionMode, ExecutionContext, Executor +from cadCAD.utils import arrange_cols +from cadCAD import configs + +exec_mode = ExecutionMode() + +print("Simulation Execution: Distributed Execution") +dist_proc_ctx = ExecutionContext(context=exec_mode.dist_proc) +run = Executor(exec_context=dist_proc_ctx, configs=configs, spark_context=sc) +# pprint(dist_proc_ctx) + +# print(configs) +i = 0 +for raw_result, tensor_field in run.execute(): + result = arrange_cols(pd.DataFrame(raw_result), False) + print() + print(tabulate(tensor_field, headers='keys', tablefmt='psql')) + print("Output:") + print(tabulate(result.head(1), headers='keys', tablefmt='psql')) + print(tabulate(result.tail(1), headers='keys', tablefmt='psql')) + print() + i += 1