diff --git a/optis/backend/__pycache__/agent.cpython-311.pyc b/optis/backend/__pycache__/agent.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60b04fbb5f7da1a9b02b7cb1c95d6165a64c8e19 Binary files /dev/null and b/optis/backend/__pycache__/agent.cpython-311.pyc differ diff --git a/optis/backend/__pycache__/environment.cpython-311.pyc b/optis/backend/__pycache__/environment.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e5dc19df2ec923f892d4833c9d5a8517fa0ebbc7 Binary files /dev/null and b/optis/backend/__pycache__/environment.cpython-311.pyc differ diff --git a/optis/backend/__pycache__/eventlog.cpython-311.pyc b/optis/backend/__pycache__/eventlog.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d96682b9202d2f07c88f3b7ea777fc80cf74aabc Binary files /dev/null and b/optis/backend/__pycache__/eventlog.cpython-311.pyc differ diff --git a/optis/backend/__pycache__/simplesimmodel.cpython-311.pyc b/optis/backend/__pycache__/simplesimmodel.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ae869e5646e548e0a24351de27367837deba250 Binary files /dev/null and b/optis/backend/__pycache__/simplesimmodel.cpython-311.pyc differ diff --git a/optis/backend/agent.py b/optis/backend/agent.py new file mode 100644 index 0000000000000000000000000000000000000000..36e347a00de4112decd9c189da2a9d6ea5028e2c --- /dev/null +++ b/optis/backend/agent.py @@ -0,0 +1,72 @@ +import gymnasium as gym +import numpy as np +import environment +from gymnasium.wrappers import FlattenObservation + +""" +RL agent +""" + +def q_learning(space, activities): + # Define the business process environment + env = environment.BusinessProcessEnv(space, activities) + + # Define the Q-table + num_states = pow(2,14) + + num_actions = env.action_space.n + + Q = np.zeros((num_states, num_actions), dtype = int) + + # Set the hyperparameters + alpha = 0.1 # learning rate + gamma = 0.1 # discount factor + epsilon = 0.1 # exploration rate + + mean_time = 0 + mean_reward = 0 + + # Train the agent using Q-learning + num_episodes = 1000 + for episode in range(num_episodes): + state, _ = env.reset() + state = env.flatten_observation_to_int(state) + done = False + start = env.process.env.now + while not done: + # Choose an action based on the epsilon-greedy policy + if np.random.uniform(0, 1) < epsilon: + action = env.action_space.sample() + else: + action = np.argmax(Q[state]) + + + # Execute the action and observe the next state and reward + next_state, reward, done, _ = env.step(action) + + # Update the Q-value for the current state-action pair + Q[state][action] = (1-alpha)*Q[state][action] + alpha * (reward + gamma * np.max(Q[next_state]) - Q[state][action]) + #Q[state][action] = (1-alpha)*Q[state][action] + alpha*reward + + # Transition to the next state + state = next_state + + time = env.process.env.now - start + mean_time += time + mean_reward += reward + + + if (episode % 20 == 19): + mean_reward /= 20 + mean_time /= 20 + print(f"Episode {episode-19} to episode {episode}: mean time = {mean_time}, mean reward: {mean_reward}") + + if episode == 19: + start_reward = mean_reward + + if episode == 999: + end_reward = mean_reward + improvement = end_reward - start_reward + print(f"Reward improved by {improvement}") + + return Q diff --git a/optis/backend/environment.py b/optis/backend/environment.py new file mode 100644 index 0000000000000000000000000000000000000000..9a7506b41753960fabec0f980a29ffc278c5fb98 --- /dev/null +++ b/optis/backend/environment.py @@ -0,0 +1,152 @@ +import gymnasium as gym +import numpy as np +import simpy +import simplesimmodel as model + +""" +Environment for the RL agent +""" + +class BusinessProcessEnv(gym.Env): + + def __init__(self, space, activities): + self.ressources = space[0] + self.case = space[1] + self.activities = activities + + self.observation_space = gym.spaces.Dict( + { + 'process': gym.spaces.MultiDiscrete(self.ressources), + 'case': gym.spaces.MultiDiscrete(self.case), + 'event': gym.spaces.Discrete(self.activities) + } + ) + + self.action_space = gym.spaces.Discrete(self.activities) + + self.current_state = { + 'process': np.array(self.ressources), + 'case': np.zeros(len(self.case), dtype=int), + 'event': 0 + } + + self.model_env = simpy.Environment() + self.process = model.BusinessProcess(self.model_env, self.ressources) + self.model_env.process(model.run_process(self.model_env, self.process)) + + self.reward = 0 + + + def get_current_state(self, caseid): + process, case, event = model.get_current_state(self.process, caseid) + state = { + 'process': process, + 'case': case, + 'event': event + } + return state + + def step(self, action): + + self.process.next = action + + if self.process.case_id in self.process.done_cases: + self.process.case_id = np.random.choice(self.process.active_cases) + + case_obj = self.process.case_objects[self.process.case_id] + self.current_state = self.get_current_state(case_obj) + + # print(f"{self.current_state['event']}") + + start = self.process.env.now + + self.process.flag = True + + if self.process.is_valid(self.current_state['event'], action, case_obj): + + while(self.process.flag): + self.model_env.step() + + stop = self.process.env.now + + case_obj = self.process.case_objects[self.process.case_id] + + # print(f"Agent did case {self.process.case_id} activity {action}.") + + next_state = self.get_current_state(case_obj) + self.current_state = next_state + next_state = self.flatten_observation_to_int(next_state) + + time = stop - start + reward = 10000 - time + self.reward += reward + done = True if (len(self.process.done_cases) == 5 or len(self.process.active_cases) == 0) else False + return next_state, self.reward, done, None + + else: + self.reward += 0 + next_state = self.flatten_observation_to_int(self.current_state) + done = False + return next_state, self.reward, done, None + + + def reset(self, seed=None, options=None): + # Reset the environment to the initial state + # Implement a function which extracts the current state from an event log / simulation model + super().reset(seed=seed) + + self.current_state = { + 'process': np.array(self.ressources), + 'case': np.zeros(len(self.case), dtype=int), + 'event': 0 + } + self.current_step = 0 + + self.model_env = simpy.Environment() + self.process = model.BusinessProcess(self.model_env, self.ressources) + self.model_env.process(model.run_process(self.model_env, self.process)) + self.process.done_cases = set([]) + + self.reward = 0 + + return self.current_state, None + + + def render(self, mode='human'): + # Render the current state of the environment + pass + + + def flatten_observation(self, observation): + flattened = [] + for i in observation['process']: flattened.append(i) + for j in observation['case']: flattened.append(j) + flattened.append(observation['event']) + + return flattened + + def flatten_observation_to_int(self, observation): + state = 0 + state += observation['event']*pow(2,10) + state += observation['case'][1]*pow(2,2) + state += observation['case'][2]*pow(2,2) + event = observation['event'] + if event == 0: + state += observation['process'][0]*pow(2,6) + elif event == 1: + state += observation['process'][1]*pow(2,6) + elif 1 < event <=3: + state += observation['process'][2]*pow(2,6)+observation['process'][3]*pow(2,7)+observation['process'][4]*pow(2,8) + elif 3 < event <=6: + state += observation['process'][5]*pow(2,6)+observation['process'][6]*pow(2,7) + elif 6 < event <= 8: + state += observation['process'][7]*pow(2,6)+observation['process'][8]*pow(2,7)+observation['process'][9]*pow(2,8) + elif 8 < event <= 11: + state += observation['process'][10]*pow(2,6)+observation['process'][11]*pow(2,7)+observation['process'][12]*pow(2,8) + elif 11 < event <= 14: + state += observation['process'][0]*pow(2,6) + else: + pass + + return state + diff --git a/optis/backend/eventlog.py b/optis/backend/eventlog.py new file mode 100644 index 0000000000000000000000000000000000000000..7a3625ef7aca88f1debea601923e4acfb86ad3a5 --- /dev/null +++ b/optis/backend/eventlog.py @@ -0,0 +1,172 @@ +import pandas as pd +import simplesimmodel as model +import numpy as np + +""" +Event log generator for our simulation model: +- generate an event log +- update an event log (adding new events) +- export event log +- get current state of an event log +""" + +def add_start_event(process, event_id, case_id, activity, start_timestamp): + process.event_log.append(event_id) + process.event_log[event_id] = { + 'CaseID': case_id, + 'Activity': activity, + 'StartTimestamp': float(start_timestamp), + 'EndTimestamp': None + } + process.event_counter += 1 + +def add_end_event(process, event_id, end_timestamp): + event = process.event_log[event_id] + event['EndTimestamp'] = end_timestamp + + +def export_to_csv(process, file_path): + event_log_df = pd.DataFrame.from_dict(process.event_log) + event_log_df.to_csv(file_path, index=False) + +def export_to_xes(process, file_path): + # Use appropriate code to export to XES format + pass + +def get_active_cases(): + event_log = pd.read_csv(r'D:\test\optis.csv') + active_cases = event_log.groupby('CaseID').filter(lambda x: 'order completed' not in x['Activity'].values)['CaseID'].unique().tolist() + return active_cases + + +def get_state(case_id): + + process = [] + num_s = 1 + process.append(num_s) + num_ot = 5 + process.append(num_ot) + num_sh_a = 3 + process.append(num_sh_a) + num_sh_b = 3 + process.append(num_sh_b) + num_sh_c = 3 + process.append(num_sh_c) + num_m_a = 3 + process.append(num_m_a) + num_m_b = 2 + process.append(num_m_b) + num_p_a = 4 + process.append(num_p_a) + num_p_b = 5 + process.append(num_p_b) + num_p_c = 4 + process.append(num_p_c) + num_ds_a = 8 + process.append(num_ds_a) + num_ds_b = 8 + process.append(num_ds_b) + num_ds_c = 8 + process.append(num_ds_c) + + case = [] + for i in range(15): + case.append(0) + + activity_mapping = { + 'place order': 1, + 'arrange standard order': 2, + 'arrange custom order': 3, + 'pick from stock A': 4, + 'pick from stock B': 5, + 'pick from stock C': 6, + 'manufacture A': 7, + 'manufacture B': 8, + 'pack A': 9, + 'pack B': 10, + 'pack C': 11, + 'attempt delivery A': 12, + 'attempt delivery B': 13, + 'attempt delivery C': 14, + 'order completed': 15, + } + + event_log = pd.read_csv(r'D:\test\optis.csv') + # Sort the event log by case ID and start timestamp + event_log.sort_values(by=['CaseID', 'StartTimestamp'], inplace=True) + + # Group the event log by case ID and get the last activity for each case + last_activities = event_log.groupby('CaseID').tail(1).reset_index() + + # Remap the activity names to numbers using the mapping dictionary + last_activities['Activity'] = last_activities['Activity'].map(activity_mapping) + + # Filter the cases where the end timestamp of the last activity is None or empty + unfinished_cases = last_activities[last_activities['EndTimestamp'].isnull()]['CaseID'].tolist() + + # Update the state of the ressources given all unfinished cases + for i in unfinished_cases: + activity = last_activities[last_activities['CaseID'] == i]['Activity'].values[0] + if activity == 1 or activity == 15: + process[0] -= 1 + elif activity == 2 or activity == 3: + process[1] -= 1 + else: + process[activity-2] -= 1 + + # Get the state of the case for the given Case ID + filtered_log = event_log[event_log['CaseID'] == case_id] + activities = filtered_log['Activity'].map(activity_mapping).tolist() + for i in activities: + case[i-1] += 1 + + # Get the last event for the given Case ID + event = last_activities[last_activities['CaseID'] == case_id]['Activity'].values[0] + + state = { + 'process': process, + 'case': case, + 'event': event + } + + print(state) + + """ + flattened = [] + for i in state['process']: flattened.append(i) + for j in state['case']: flattened.append(j) + flattened.append(state['event']) + + + flattened = 0 + flattened += state['event'] + for i in state['case']: flattened += i + for j in state['process']: flattened += j*process[j] + + print(flattened) + """ + flat_state = 0 + flat_state += state['event']*pow(2,10) + print(flat_state) + flat_state += state['case'][1]*pow(2,1) + flat_state += state['case'][2]*pow(2,2) + event = state['event'] + if event == 0: + flat_state += state['process'][0]*pow(2,6) + elif event == 1: + flat_state += state['process'][1]*pow(2,6) + elif 1 < event <=3: + flat_state += state['process'][2]*pow(2,6)+state['process'][3]*pow(2,7)+state['process'][4]*pow(2,8) + elif 3 < event <=6: + flat_state += state['process'][5]*pow(2,6)+state['process'][6]*pow(2,7) + elif 6 < event <= 8: + flat_state += state['process'][7]*pow(2,6)+state['process'][8]*pow(2,7)+state['process'][9]*pow(2,8) + elif 8 < event <= 11: + flat_state += state['process'][10]*pow(2,6)+state['process'][11]*pow(2,7)+state['process'][12]*pow(2,8) + elif 11 < event <= 14: + flat_state += state['process'][0]*pow(2,6) + else: + pass + + print(flat_state) + return flat_state diff --git a/optis/backend/main.py b/optis/backend/main.py new file mode 100644 index 0000000000000000000000000000000000000000..fdee31d6eb3e8d2bf65037450ec61528c9b21529 --- /dev/null +++ b/optis/backend/main.py @@ -0,0 +1,91 @@ +import simpy +import random +import numpy as np +import simplesimmodel as model +import environment +import agent +import eventlog as log +import pandas as pd + +def main(): + # Setup + # we can use a random seed if we want to generate the same results every time (maybe useful later for the training) + # random.seed(42) + # initialize the number of resources + + process = [] + num_s = 1 + process.append(num_s) + num_ot = 5 + process.append(num_ot) + num_sh_a = 3 + process.append(num_sh_a) + num_sh_b = 3 + process.append(num_sh_b) + num_sh_c = 3 + process.append(num_sh_c) + num_m_a = 3 + process.append(num_m_a) + num_m_b = 2 + process.append(num_m_b) + num_p_a = 4 + process.append(num_p_a) + num_p_b = 5 + process.append(num_p_b) + num_p_c = 4 + process.append(num_p_c) + num_ds_a = 7 + process.append(num_ds_a) + num_ds_b = 7 + process.append(num_ds_b) + num_ds_c = 7 + process.append(num_ds_c) + + case = [] + for i in range(15): + case.append(1) + + space = [process, case] + activities = 16 + + # q learning + Q = agent.q_learning(space, activities) + # print(Q) + + # generate event log + env = simpy.Environment() + business_process = model.BusinessProcess(env, process) + business_process.event_log_flag = True + env.process(model.run_process(env, business_process)) + env.run(until = 10000) + log.export_to_csv(business_process, r'D:\test\optis.csv') + + # extract active cases from event log + active_cases = log.get_active_cases() + print(active_cases) + + # test agent + for i in range(20): + caseid = random.choice(active_cases) + state = log.get_state(caseid) + + action = np.argmax(Q[state]) + print(action) + #print(Q) + print(Q[state]) + + state = Q[0] + action = np.argmax(state) + print(action) + print(state) + + state = Q[64] + action = np.argmax(state) + print(action) + print(state) + + + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/optis/backend/simplesimmodel.py b/optis/backend/simplesimmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..18cb95273cd687ac419a8d7c8cf3fce44011e738 --- /dev/null +++ b/optis/backend/simplesimmodel.py @@ -0,0 +1,553 @@ +import simpy +import random +import numpy as np +import environment +import agent +import eventlog as log + +""" +Simulation model for a simpler business process, including: +- a class representing the process +- a function representing the process flow +- a function to run the process +""" + + +""" +This class represents the process with its: +- ressources: system, managers, stock handlers (a, b and c), manufacturers (a and b), delivery services (a, b and c) +- activities +""" +class BusinessProcess(object): + """ Initialize the process with the number of the different types of ressources """ + def __init__(self, env, ressources): + + # initialize simulation environment + self.env = env + + # initialize ressources + self.system = simpy.Resource(env, ressources[0]) + self.order_taker = simpy.Resource(env, ressources[1]) + self.stock_handler_a = simpy.Resource(env, ressources[2]) + self.stock_handler_b = simpy.Resource(env, ressources[3]) + self.stock_handler_c = simpy.Resource(env, ressources[4]) + self.manufacturer_a = simpy.Resource(env, ressources[5]) + self.manufacturer_b = simpy.Resource(env, ressources[6]) + self.packer_a = simpy.Resource(env, ressources[7]) + self.packer_b = simpy.Resource(env, ressources[8]) + self.packer_c = simpy.Resource(env, ressources[9]) + # capacity of each delivery service instead of numbers of workers + self.delivery_service_a = simpy.Resource(env, ressources[10]) + self.delivery_service_b = simpy.Resource(env, ressources[11]) + self.delivery_service_c = simpy.Resource(env, ressources[12]) + + # initialize lists with active cases and all case objects + self.active_cases = [0, 1, 2] + case_0 = Case(0) + case_1 = Case(1) + case_2 = Case(2) + self.case_objects = [case_0, case_1, case_2] + + # flag indicates whether the process is currently controlled by the agent and we set it on false every time the agent does an activity + self.flag = True + + self.next = 0 + + self.case_id = 0 + + self.done_cases = set([]) + + self.event_log_flag = False + self.event_log = [] + self.event_counter = 0 + + # random.seed(1) + + def place_order(self, case): + yield self.env.timeout(0) + + def arrange_standard_order(self, case): + yield self.env.timeout(random.randint(10, 15)) + + def arrange_custom_order(self, case): + yield self.env.timeout(random.randint(20, 30)) + + def pick_from_stock_a(self, case): + yield self.env.timeout(random.randint(10, 40)) + + def pick_from_stock_b(self, case): + yield self.env.timeout(random.randint(20, 60)) + + def pick_from_stock_c(self, case): + yield self.env.timeout(random.randint(30, 80)) + + def manufacture_a(self, case): + yield self.env.timeout(random.randint(240, 360)) + + def manufacture_b(self, case): + yield self.env.timeout(random.randint(360, 480)) + + def pack_a(self, case): + yield self.env.timeout(random.randint(10, 20)) + + def pack_b(self, case): + yield self.env.timeout(random.randint(15, 30)) + + def pack_c(self, case): + yield self.env.timeout(random.randint(25, 50)) + + def attempt_delivery_a(self, case): + yield self.env.timeout(random.randint(720, 1440)) + + def attempt_delivery_b(self, case): + yield self.env.timeout(random.randint(1440, 2160)) + + def attempt_delivery_c(self, case): + yield self.env.timeout(random.randint(1440, 2880)) + + def order_completed(self, case): + yield self.env.timeout(1) + + def is_valid(self, event, action, case_obj): + if event == 0 and action == 1: + return True + elif event == 1 and (action == 2 or action == 3): + return True + elif event == 2 and case_obj.standard_order and (action == 4 or action == 5 or action == 6): + return True + elif event == 3 and (not case_obj.standard_order) and (action == 7 or action == 8): + return True + elif (4 <= event <= 8) and (action == 9 or action == 10 or action == 11): + return True + elif (9 <= event <= 11) and (12 <= action <=14): + return True + elif (12 <= event <=14) and action == 15: + return True + else: + return False + +""" +A class representing a single case from the process. Every case has an (unique) case id, +state showing which activities have been already done, current activity being executed, +agent flag showing if the agent is currently controlling the case and +a flag whether the order is standard +""" +class Case(object): + def __init__(self, case): + self.case_id = case + self.state = np.zeros(15, dtype = int) + self.current = 0 + self.agent = False + self.standard_order = True + +""" +A function, which represents the process model with it's control flow and ressource allocation. +A customer places an order at the system and then is redirected to the order takers, where it's arranged +what type of order the customer wants - standard or custom. After that depending on whether the order +is standard or custom, respectively a stock or manufacturer is chosen. +Afterwards one of three packing options is chosen and executed. +Following the packing one of the three delivery services deliver the order. +With that the order is completed. +""" +def execute_case(env, case, process): + # create a case object to keep track of case attributes if not already existing + if len(process.case_objects) <= case: + case_obj = Case(case) + + # add the case to the process's active cases list + process.case_objects.append(case_obj) + process.active_cases.append(case) + + # if the case is one of the first three choose it from the list + if len(process.case_objects) <= 3: + case_obj = process.case_objects[case] + + # if case chosen by agent set agent flag to true + if process.case_id == case: + case_obj.agent = True + + # place order + case_obj.state[0] += 1 + case_obj.current = 1 + with process.system.request() as request: + yield request + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "place order", env.now) + yield env.process(process.place_order(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + # if case_obj.agent: print(f"Case {case}: 'placed order' at {env.now:.2f}") + + + # if the last action was made from the agent set the process flag to be able to return to the environment's step function + if case_obj.agent: + process.flag = False + + with process.system.request() as request: + yield request + + # before a new action is executed check if the agent is controlling the case and set the flag to true if yes + if process.case_id == case: case_obj.agent = True + + # standard order XOR custom order + choice = random.randint(2,3) if not case_obj.agent else process.next + if choice == 2: + case_obj.standard_order = True + case_obj.state[1] += 1 + case_obj.current = 2 + with process.order_taker.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'arrange standard order' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "arrange standard order", env.now) + yield env.process(process.arrange_standard_order(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + # if case_obj.agent: print(f"Case {case}: finished 'arrange standard order' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + else: + case_obj.state[2] += 1 + case_obj.current = 3 + case_obj.standard_order = False + with process.order_taker.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'arrange custom order' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "arrange custom order", env.now) + yield env.process(process.arrange_custom_order(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + # if case_obj.agent: print(f"Case {case}: finished 'arrange custom order' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + + + if process.case_id == case: case_obj.agent = True + + if case_obj.standard_order and (not case_obj.agent): + choice = random.randint(4,6) + elif (not case_obj.standard_order) and (not case_obj.agent): + choice = random.randint(7,8) + else: + choice = process.next + + # choose stock or manufacturer + if choice == 4: + case_obj.state[3] += 1 + case_obj.current = 4 + with process.stock_handler_a.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'pick from stock A' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "pick from stock A", env.now) + yield env.process(process.pick_from_stock_a(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + # if case_obj.agent: print(f"Case {case}: finished 'pick from stock A' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + elif choice == 5: + case_obj.state[4] += 1 + case_obj.current = 5 + with process.stock_handler_b.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'pick from stock B' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "pick from stock B", env.now) + yield env.process(process.pick_from_stock_b(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + + # if case_obj.agent: print(f"Case {case}: finished 'pick from stock B' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + elif choice == 6: + case_obj.state[5] += 1 + case_obj.current = 6 + with process.stock_handler_c.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'pick from stock C' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "pick from stock C", env.now) + yield env.process(process.pick_from_stock_c(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + # if case_obj.agent: print(f"Case {case}: finished 'pick from stock C' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + elif choice == 7: + case_obj.state[6] += 1 + case_obj.current = 7 + with process.manufacturer_a.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'manufacture A' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "manufacture A", env.now) + yield env.process(process.manufacture_a(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + + # if case_obj.agent: print(f"Case {case}: finished 'manufacture A' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + else: + case_obj.state[7] += 1 + case_obj.current = 8 + with process.manufacturer_b.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'manufacture B' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "manufacture B", env.now) + yield env.process(process.manufacture_b(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + # if case_obj.agent: print(f"Case {case}: finished 'manufacture B' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + + + if process.case_id == case: case_obj.agent = True + + choice = random.randint(9,11) if not case_obj.agent else process.next + + if choice == 9: + case_obj.state[8] += 1 + case_obj.current = 9 + with process.packer_a.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'pack A' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "pack A", env.now) + yield env.process(process.pack_a(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + + # if case_obj.agent: print(f"Case {case}: finished 'pack A' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + elif choice == 10: + case_obj.state[9] += 1 + case_obj.current = 10 + with process.packer_b.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'pack B' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "pack B", env.now) + yield env.process(process.pack_b(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + + # if case_obj.agent: print(f"Case {case}: finished 'pack B' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + + with process.system.request() as request: + yield request + else: + case_obj.state[10] += 1 + case_obj.current = 11 + with process.packer_c.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'pack C' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "pack C", env.now) + yield env.process(process.pack_c(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + + # if case_obj.agent: print(f"Case {case}: finished 'pack C' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + + if process.case_id == case: case_obj.agent = True + + # choose delivery + choice = random.randint(12,14) if not case_obj.agent else process.next + if choice == 12: + case_obj.state[11] += 1 + case_obj.current = 12 + with process.delivery_service_a.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'attempt delivery A' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "attempt delivery A", env.now) + yield env.process(process.attempt_delivery_a(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + + # if case_obj.agent: print(f"Case {case}: finished 'attempt delivery A' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + elif choice == 13: + case_obj.state[12] += 1 + case_obj.current = 13 + with process.delivery_service_b.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'attempt delivery B' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "attempt delivery B", env.now) + yield env.process(process.attempt_delivery_b(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + + # if case_obj.agent: print(f"Case {case}: finished 'attempt delivery B' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + else: + case_obj.state[13] += 1 + case_obj.current = 14 + with process.delivery_service_c.request() as request: + yield request + # if case_obj.agent: print(f"Case {case}: started 'attempt delivery C' at {env.now:.2f}") + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "attempt delivery C", env.now) + yield env.process(process.attempt_delivery_c(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + + # if case_obj.agent: print(f"Case {case}: finished 'attempt delivery C' at {env.now:.2f}") + if case_obj.agent: + process.flag = False + with process.system.request() as request: + yield request + + + if process.case_id == case: case_obj.agent = True + + # case completed + case_obj.state[14] += 1 + case_obj.current = 15 + with process.system.request() as request: + yield request + if process.event_log_flag: + event_counter = process.event_counter + log.add_start_event(process, event_counter, case, "order completed", env.now) + yield env.process(process.order_completed(case)) + if process.event_log_flag: + log.add_end_event(process, event_counter, env.now) + # if case_obj.agent: print(f"Case {case}: 'completed' at {env.now:.2f}") + + + if case in process.active_cases: + process.active_cases.remove(case) + + if case_obj.agent: + for i in process.case_objects: + if (i.current == 15) and (i.case_id in process.active_cases) and (i.case_id != case): + process.active_cases.remove(i.case_id) + process.done_cases.add(process.case_id) + process.flag = False + + +""" +Get the cureent state of the process and a specific case +- available ressouces +- events which already happened in the case +- current event of the case +""" +def get_current_state(process, case): + process_state = [] + + num_system = process.system.capacity - process.system.count + process_state.append(num_system) + + num_order_taker = process.order_taker.capacity - process.order_taker.count + process_state.append(num_order_taker) + + num_stock_handler_a = process.stock_handler_a.capacity - process.stock_handler_a.count + process_state.append(num_stock_handler_a) + + num_stock_handler_b = process.stock_handler_b.capacity - process.stock_handler_b.count + process_state.append(num_stock_handler_b) + + num_stock_handler_c = process.stock_handler_c.capacity - process.stock_handler_c.count + process_state.append(num_stock_handler_c) + + num_manufacturer_a = process.manufacturer_a.capacity - process.manufacturer_a.count + process_state.append(num_manufacturer_a) + + num_manufacturer_b = process.manufacturer_b.capacity - process.manufacturer_b.count + process_state.append(num_manufacturer_b) + + num_packer_a = process.packer_a.capacity - process.packer_a.count + process_state.append(num_packer_a) + + num_packer_b = process.packer_b.capacity - process.packer_b.count + process_state.append(num_packer_b) + + num_packer_c = process.packer_c.capacity - process.packer_c.count + process_state.append(num_packer_c) + + num_delivery_service_a = process.delivery_service_a.capacity - process.delivery_service_a.count + process_state.append(num_delivery_service_a) + + num_delivery_service_b = process.delivery_service_b.capacity - process.delivery_service_b.count + process_state.append(num_delivery_service_b) + + num_delivery_service_c = process.delivery_service_c.capacity - process.delivery_service_c.count + process_state.append(num_delivery_service_c) + + cur_case = case.state + + event = case.current + + return process_state, cur_case, event + +""" +Defines how often new orders (cases) come in and starts executing them +""" +def run_process(env, process): + # process = Process(env, num_ot, num_m, num_sh_a, num_sh_b, num_sh_c, num_m_a, num_m_b, num_ds_a, num_ds_b, num_ds_c) + + # the waiting orders + for case in range(3): + env.process(execute_case(env, case, process)) + + # the new incoming orders + while case < 1000: + waittime = random.randint(10,15) + if case % 20 == 0: + waittime = 100 + yield env.timeout(waittime) # Wait a bit before generating a new case + + case += 1 + # process.active_cases.append(case) + env.process(execute_case(env, case, process)) +