From 99f04ac848bfbee4181b2f81a05b28cfe6854f7a Mon Sep 17 00:00:00 2001 From: Aleksandra <aleks.dimitrova.21@gmail.com> Date: Tue, 6 Jun 2023 17:10:18 +0200 Subject: [PATCH] backend --- backend/__pycache__/agent.cpython-311.pyc | Bin 2465 -> 2874 bytes .../__pycache__/environment.cpython-311.pyc | Bin 6200 -> 10311 bytes backend/__pycache__/eventlog.cpython-311.pyc | Bin 0 -> 7786 bytes .../simplesimmodel.cpython-311.pyc | Bin 31186 -> 28897 bytes backend/agent.py | 63 +++- backend/environment.py | 83 ++++- backend/eventlog.py | 155 +++++++- backend/input.py | 2 + backend/main.py | 91 +++++ backend/simplesimmodel.py | 352 ++++++++++-------- simpy_tutorial/eventlog.py | 7 +- 11 files changed, 562 insertions(+), 191 deletions(-) create mode 100644 backend/__pycache__/eventlog.cpython-311.pyc create mode 100644 backend/input.py create mode 100644 backend/main.py diff --git a/backend/__pycache__/agent.cpython-311.pyc b/backend/__pycache__/agent.cpython-311.pyc index 87f701a04a31d900d91fd18b6bc837516be90163..51f4f916a98f67a7704f896ffc5c1d4ef0b81626 100644 GIT binary patch delta 1690 zcmZ1|yi1I4IWI340|NsCyGDJAHt$5fN^Wij1_ovZ28PdJEE6ZqtLJ1W0f~UrFfcHr zu%@srV`5-f%>)%-sAcA4sA0-tgYmOiV60m95`Gw)fgy_n#;#$mVNYW!5rXj<7}l_( znaPpDQOlXaQOlKL&QZ&q!dc6c!dc6k!d1&xB8t#f!?%o)fnhZxOvHs@0%L4*Eq@Ku z0`AEZnS>|vGD$J=fLYc|B97d(LN!cTyfD>Sd~g=rnYF?-Ld7Rb;4%yhSpqOs3j{$X zX)~m7rZA#9w}v|#tf)u|IUp7Yfz(gtW#XUwfr(WdZU+NHmM~a30|P@1$7CL6c}5Yi zN_%Emt`x=^&MGdjL>#k(N);;uLk(w*SPd6SP{PBYhNDJwG9R<V<aNxPj2x5Wm?b7( zVCJpos1cBaS;0^vVgs^CpjNy_v{rzVp+;!^NkZXJBMfpxtwaq^jYJhM14AuO4R4JY zn8jPepTbkc#=wxmn+;9`MH)4{DLhpY3=FlrH9R0zJxD`|F5CqSDSS2jDZC&fXEUVm z*YM9~ND-*vnaz+QSi?J;VJ-)1G%_-PR6`ACs^P7btl>`)g7PJ6cx$C<q-rEVrqxQL z*dkmbJ)2=J(=thvRKdtFfw8Bxh9j6kQ)F@ii%k7Xkh5Qc;#iaM7HdjLWkG8CteGIN z)4m8Km-&L3fx+#i2m=GdOHfo*akv&_7U!p=Dpc_(l;kU<!gzdE3c0C?c?u<&xv2`a z3RV0%5Pnf=d16tDl|troW(I~T;UF+qAv3q2D8DR~Jw+j@QbALE@=g{zALfGm@>@KK z$t9WjdGW;siOH$AnDPp4u~wxP<rm*#&de*h#afh@m!5iywJ5bXwd9smT25j~Nornv zeo}F2QCT8bV@ZB|W?so;OIBwwnOm$7W4J(umSvV?rWO}TPoBhTG5IX35f3-Wz3~wD zP5#H~ZONIRlM-KCl30>@iv{G3TRg=jiA5z)CAYXz^HN}JZm0)xQ}arSWhMu(Nl#8^ zv*B`JU|;|hA;t41uVwSA|G>h+%JG$pK}d8$;Dn-yl2arbygsmSv2uKnU=S9&BBXRh zNTtF1D+dE7cMrz|tr^)D1huw0U*NE~$YF7X!=l0Q0~-UcK!fWI9{vW`8+?Kd?hgb6 z8$53a2{(9uU}F%GXz;l!EHOjoqOjT(VYLqoMy%!^K|}}p4PmkAmKKvNCt6Rj?&RoT z?I^q<Abedw?~;JtiqeY$rdI?^J6Lb<i*&Hu5D=ciJ%#^@fN}@x4I!})3=*6IlaH~d z)+;J4P+iErLU4uY8i@^RS2Qgz%3JPmJrH&x^=eqem9Ur#v2hpTGq1#ET@1^<5|(|@ zFZ-fv_JzFs3k9WD^2#oh*Ivo1yXabX(Wd^Qe0_&wM{1A9gs_V|G9Ne?B;`6hZ-~fs zxPD+^<rVm#&LAufw)cY>gMe^Pbx-{jex=D%IRxu(a0pK*yT~Ei!3_!tkqJqiJ~Nyy z@yK1^k-NboKEvoDk8Fqc4Ia@MLY@8{o_Bc!F9;RS2wxC1GwOm+@dcsc4dD>_geQc) zkX8s`T;wUa!c%gAr{n>*(1fHe+YZ|s(rO((A2=9zgeN3-*>%|6kW}f=@VX(TvY_Uo zlyQeQ*l^Po+!usQF9?}l<T1O#V|IbZ?B@e+o(7M}H#y|lKqXNT-{gNBvZ|nTu#%z3 wk%57s$ew|L;TMNZZhlH>PO4pzI|Bm)sFW^_o9w{ps+h#c$oqi-I|<eg0ArB5SO5S3 delta 1223 zcmdlbwosUFIWI340|NuYY0=`8RE~*!mE4>R3=GT+3=E%7F;AQ{ufB%Gg&~%`mc53# zhP_INfuWYUhogoSix_ha8$`Z_DT@_kBnW4*fJhLo<tX6=(GZ-)4xt$sYM5&{(wItk zV0;FKEDjiZ4eK&hCI*JpOfZ33&J?y<t`zoK?i99Ko)nH+-W1MSz7pZd2N>nqjcfU9 zm=<tN-o>QE6T`&7P%BU?Si`h{d$I_VIzLyfPz_TS+?L4>Od>J0!Zkw02T-i$huO41 z0LDV5Q#eu>m!Y_*h9?`WphybY+Y1CI?_yGyME0-{SQ!HYLk(w*z~nw=3AS318m0xp zlO>o9m})pDZ(x>?tKq5<tKmioClQ3U$vw=<j39-o>YOzKk}wH|8W9_iv7)sCHA3qT zp!?FJM!1TLfuUBshPOt%ikE?*mY0*EL>sP~A%(k!e>Ou3PmTC&hPf=u7#SE=Gs0vU z8746Hn5HlUGidToe#0VTtI2qaHKnAoAT@o~Oc2;<pT5rhVU@_b_x44gSbCYlz`)>E z#o=0zS)8Aes!+vgrBITYo2p=|peZ)FkJWDSMOH~o*37&TL$h0~MTvRoskc~*Qj1ed zZV9L5B$kw<=EdhH6{i-JC6;97=S`Mia~6}h#adjDn4EfxD>1nwvn;bDGqt!#d~zY1 z1ruZD<P~hjlV7lTaj}4`DVAVhV3=IUE-|@_-Ga-4fq?;(xQjI>pJDf_f50nsfkpcQ zi*^U&46hZ6GyP|H&G5RxD}I4R;{uCD2jdK>1%)%^XGqPEy1~LXLHi<$Y=hSWA<-*B zN>_wb8oZx!aP@FZ(3+8bK~QU}(iV*i9M%^(tgmobH#mM^W8f8NaJ|6;BKQOw+#d)C zHh6wuV-OK<@VP52F+=8}u$s>mVYLqo@~p-mK|}}p4PmkAmXj<eT2Ha=<mh1SD7+yc zd|g2Ol7RXO){6pqR|NDrSa0x)bg<kI5T3$4h5w3xatG@TA+Zk({G9TWFLI<NC@3xv zol~)b?TWnq2Gt#A2VBoOopHMm9C9Hv{z`DdMW4hgK8Y7y5-(aOUPwv3kd}QVCFeqJ z@s*U4i`FF<jY==dmv%UIr1p4B2)oE5^MQ>)RKCOYgFJ(<IM|90N|RS}N*jM*Vd3QY z%EQ3PcY)7vhWG_O!wY<dD@-8tMGm7Y97Y#7j4pB*eXy5jHU0qd+vHp>c@IGb28JST z1_p*(Lg|&cd5OiDrMY_LMTrFksYS&_paP}{6hA8&iY!4Y%^4UNesS33=BJeAq}mnP aGcYiKQekoA<SSgRGS-a79~dwRuvP$>kv8lA diff --git a/backend/__pycache__/environment.cpython-311.pyc b/backend/__pycache__/environment.cpython-311.pyc index 51daa198751b4836531ffe9cc564ed462aed29b8..4b3cb87be7554aa6bcb531d2a1fd6d299d80c0a5 100644 GIT binary patch delta 4771 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 delta 975 zcmX>eu)~0FIWI340|NsCqiAu;MgEC=5{!Nm)%}zhgBdhgUV<e2l0hU4Gea4l9VQ-B zQs(SnNMlT4ZQ+b!OW{gkZ()pLPvK}`h~nsAs9=oZ3}(>e-ps@JhlP=MGbfud6QkB- zTaI9Eb_NCpkU5`qST@h*Si;CMfw72ZvLn|XhZ1F|@eB+MDa<ttS#WL^6O1{VA%&%e zaW+E=>s;1lj0_B`8DTPv43qzISu+Yv4pdapt7WZWs9?+zhDp}2)G$|ZF)(m5RPiz} zBs0asWlA&|7$$Gz6rH?=TY{S{8*EXrY>DRN^W6I0>=1qtQwn1qV+uztTMcUs1Cpt1 zDGX^W!3?N&rEsF#QNyr6Xfh+0!sJXI!4RCLXma_56oF!+ir+CkHLpY=B{M}KIk7lZ zp^8f(F}WnOEVHCi;TNOcOI8L3h9Zzl-7@F=|Nmc8bn-f$Ms?PLqRhOKTf%8Mi6tee zdGYy4#i>PQi6xo&c|}Sf9g++T44Ukd!+4FvQ}Xjti$O7=00Bj+AO%*F*YL`)88a|2 z6u+H(j&~{ZM+Wi9WqhGpPk96{2>C8>S>kg=#o~gH?*$>>18xxdf{^b;9=|I*eiwNB z9!SbgzQ8A|V0cN=aD&!GN!u%uwjEv%cmz7Ud%S0aEO4C}GsFHOkJe;K{s<Y9i#+lm z@f(t|*CoL^RIV7gT$FUZBI&xhm;WV`kRixTAeU>h6oHaPk;~)`AvH#~$?Jt=8Dlq} z74l_dEZ!_FV#ml{WW&I~P^C6`Bd_q}W>I0ripkSO>v=Vq{QNW-i@ZVp=9=s;ukT_7 zGSUr1_=5<Lu3NnE@yR)f#l^{~IXUt1nv6wWpa1}+D+frhuz-|;2yT!>-{j-+di?6F z<{uc~M95?o1xI-XHde_Gs6>%EOs^*AWJ7gFM&HS`>JnUWpezS6tbFo(b!!){Vn#;E W4-DAJ8NzeKKZ51HfJsbsU`qfmZ{H07 diff --git a/backend/__pycache__/eventlog.cpython-311.pyc b/backend/__pycache__/eventlog.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c000c77be103887b9d6c9671a5555bb455cc198 GIT binary patch literal 7786 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 literal 0 HcmV?d00001 diff --git a/backend/__pycache__/simplesimmodel.cpython-311.pyc b/backend/__pycache__/simplesimmodel.cpython-311.pyc index 7f8f062a2173a3f3440055bc14475b66274869c3..def6d5e7c495421b42862ead8fe246f98d50bf31 100644 GIT binary patch literal 28897 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 literal 31186 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 STLTz=U}j*Ey1*<6ju8M8O;XeV diff --git a/backend/agent.py b/backend/agent.py index 111df06..e75453c 100644 --- a/backend/agent.py +++ b/backend/agent.py @@ -18,26 +18,44 @@ def q_learning(space, activities): case_space = env.observation_space['case'].nvec event_space = env.observation_space['event'].n - for i in process_space: num_states *= i - for i in case_space: num_states *= i - num_states *= event_space + """ + for i in process_space: num_states *= (i+1) + for i in case_space: num_states *= (i+1) + num_states *= event_space + 1 + """ + num_states = pow(2,14) + + """ + process_space = env.observation_space['process'] + case_space = env.observation_space['case'] + event_space = env.observation_space['event'] + + state_shape = [] + for i in process_space: state_shape.append(i.n + 1) + for j in case_space: state_shape.append(j.n + 1) + state_shape.append(event_space.n) + state_shape = tuple(state_shape) + """ num_actions = env.action_space.n - Q = np.zeros((num_states, num_actions), dtype=np.int16) + + # Q = np.zeros(state_shape + (num_actions,), dtype=np.int8) + Q = np.zeros((num_states, num_actions), dtype = int) # Set the hyperparameters alpha = 0.1 # learning rate - gamma = 0.99 # discount factor + gamma = 0.1 # discount factor epsilon = 0.1 # exploration rate mean_time = 0 + mean_reward = 0 # Train the agent using Q-learning - num_episodes = 100 + num_episodes = 1000 for episode in range(num_episodes): state, _ = env.reset() - state = env.flatten_observation(state) + state = env.flatten_observation_to_int(state) done = False start = env.process.env.now while not done: @@ -52,18 +70,39 @@ def q_learning(space, activities): next_state, reward, done, _ = env.step(action) # Update the Q-value for the current state-action pair - Q[state][action] = Q[state][action] + alpha * (reward + gamma * np.max(Q[next_state]) - Q[state][action]) + 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 + old_state = state state = next_state + + """ + if old_state != state: + print(state) + print(action) + print(Q[state][action]) + """ + 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}") - """ + print(f"Episode {episode-19} to episode {episode}: mean time = {mean_time}, mean reward: {mean_reward}") + + if episode == 19: + start_reward = mean_reward + + # print(f"Episode {episode}: time = {time}, reward = {reward}") + + if episode == 999: + end_reward = mean_reward + improvement = end_reward - start_reward + print(f"Reward improved by {improvement}") - print(f"Episode {episode}: time = {time}") \ No newline at end of file + return Q diff --git a/backend/environment.py b/backend/environment.py index b96b642..1ee254b 100644 --- a/backend/environment.py +++ b/backend/environment.py @@ -7,7 +7,6 @@ import simplesimmodel as model Environment for the RL agent """ - class BusinessProcessEnv(gym.Env): def __init__(self, space, activities): @@ -73,19 +72,21 @@ class BusinessProcessEnv(gym.Env): case_obj = self.process.case_objects[self.process.case_id] - print(f"Agent did case {self.process.case_id} activity {action}.") + # 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(next_state) + next_state = self.flatten_observation_to_int(next_state) - self.reward += -(stop - start) + 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 += -100 - next_state = self.flatten_observation(self.current_state) + self.reward += 0 + next_state = self.flatten_observation_to_int(self.current_state) done = False return next_state, self.reward, done, None @@ -125,4 +126,74 @@ class BusinessProcessEnv(gym.Env): 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 + +def main(): + 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(1) + + space = [process, case] + activities = 16 + + env = BusinessProcessEnv(space, activities) + state = env.current_state + flattened = env.flatten_observation_to_int(state) + print(flattened) + for value in range(env.action_space.n): + print(value) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/backend/eventlog.py b/backend/eventlog.py index 36848b5..7a3625e 100644 --- a/backend/eventlog.py +++ b/backend/eventlog.py @@ -1,33 +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): - # if event_id in process.event_log: event = process.event_log[event_id] event['EndTimestamp'] = end_timestamp - # process.event_log.append(event) - # del process.event_log[event_id] -# add functions for adding events with their attributes to the log -def export_to_csv(env, file_path): - event_log_df = pd.DataFrame.from_dict(env.bigeventlog) - event_log_df.to_csv(file_path) +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 \ No newline at end of file + 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/backend/input.py b/backend/input.py new file mode 100644 index 0000000..e8c6f1e --- /dev/null +++ b/backend/input.py @@ -0,0 +1,2 @@ +import pandas as pd + diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..fdee31d --- /dev/null +++ b/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/backend/simplesimmodel.py b/backend/simplesimmodel.py index fe23eb4..18cb952 100644 --- a/backend/simplesimmodel.py +++ b/backend/simplesimmodel.py @@ -3,6 +3,7 @@ import random import numpy as np import environment import agent +import eventlog as log """ Simulation model for a simpler business process, including: @@ -56,7 +57,11 @@ class BusinessProcess(object): self.done_cases = set([]) - random.seed(1) + 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) @@ -162,20 +167,25 @@ def execute_case(env, case, process): case_obj.agent = True # place order + case_obj.state[0] += 1 + case_obj.current = 1 with process.system.request() as request: yield request - case_obj.state[0] += 1 - case_obj.current = 1 + 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 case_obj.agent: print(f"Case {case}: 'placed order' at {env.now:.2f}") + 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 + 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 @@ -184,32 +194,40 @@ def execute_case(env, case, process): 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 - case_obj.state[1] += 1 - case_obj.current = 2 - if case_obj.agent: print(f"Case {case}: started 'arrange standard order' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'arrange standard order' at {env.now:.2f}") + 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 + 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 - case_obj.state[2] += 1 - case_obj.current = 3 - if case_obj.agent: print(f"Case {case}: started 'arrange custom order' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'arrange custom order' at {env.now:.2f}") + 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 + with process.system.request() as request: + yield request if process.case_id == case: case_obj.agent = True @@ -223,70 +241,92 @@ def execute_case(env, case, process): # 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 - case_obj.state[3] += 1 - case_obj.current = 4 - if case_obj.agent: print(f"Case {case}: started 'pick from stock A' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'pick from stock A' at {env.now:.2f}") + 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 + 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 - case_obj.state[4] += 1 - case_obj.current = 5 - if case_obj.agent: print(f"Case {case}: started 'pick from stock B' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'pick from stock B' at {env.now:.2f}") + 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 + 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 - case_obj.state[5] += 1 - case_obj.current = 6 - if case_obj.agent: print(f"Case {case}: started 'pick from stock C' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'pick from stock C' at {env.now:.2f}") + 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 + 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 - case_obj.state[6] += 1 - case_obj.current = 7 - if case_obj.agent: print(f"Case {case}: started 'manufacture A' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'manufacture A' at {env.now:.2f}") + 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 + 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 - case_obj.state[7] += 1 - case_obj.current = 8 - if case_obj.agent: print(f"Case {case}: started 'manufacture B' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'manufacture B' at {env.now:.2f}") + 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 + with process.system.request() as request: + yield request if process.case_id == case: case_obj.agent = True @@ -294,99 +334,135 @@ def execute_case(env, case, process): 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 - case_obj.state[8] += 1 - case_obj.current = 9 - if case_obj.agent: print(f"Case {case}: started 'pack A' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'pack A' at {env.now:.2f}") + 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 + 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 - case_obj.state[9] += 1 - case_obj.current = 10 - if case_obj.agent: print(f"Case {case}: started 'pack B' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'pack B' at {env.now:.2f}") + 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 + process.flag = False - with process.system.request() as request: - yield request + 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 - case_obj.state[10] += 1 - case_obj.current = 11 - if case_obj.agent: print(f"Case {case}: started 'pack C' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'pack C' at {env.now:.2f}") + 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 + 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 - case_obj.state[11] += 1 - case_obj.current = 12 - if case_obj.agent: print(f"Case {case}: started 'attempt delivery A' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'attempt delivery A' at {env.now:.2f}") + 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 + 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 - case_obj.state[12] += 1 - case_obj.current = 13 - if case_obj.agent: print(f"Case {case}: started 'attempt delivery B' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'attempt delivery B' at {env.now:.2f}") + 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 + 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 - case_obj.state[13] += 1 - case_obj.current = 14 - if case_obj.agent: print(f"Case {case}: started 'attempt delivery C' at {env.now:.2f}") + # 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 case_obj.agent: print(f"Case {case}: finished 'attempt delivery C' at {env.now:.2f}") + 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 + 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 - case_obj.state[14] += 1 - case_obj.current = 15 + 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 case_obj.agent: print(f"Case {case}: 'completed' at {env.now:.2f}") + 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: @@ -399,6 +475,13 @@ def execute_case(env, case, process): 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 = [] @@ -447,6 +530,9 @@ def get_current_state(process, case): 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) @@ -457,63 +543,11 @@ def run_process(env, 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)) -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 = 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(1) - - space = [process, case] - activities = 16 - - business_env = environment.BusinessProcessEnv(space, activities) - print(business_env.observation_space.shape) - print(business_env.observation_space.sample()) - - state, _ = business_env.reset() - print(state) - print(business_env.current_state) - print(state['event']) - print(business_env.flatten_observation(state)) - agent.q_learning(space, activities) - -if __name__ == "__main__": - main() diff --git a/simpy_tutorial/eventlog.py b/simpy_tutorial/eventlog.py index 8a31eaf..e7f2df2 100644 --- a/simpy_tutorial/eventlog.py +++ b/simpy_tutorial/eventlog.py @@ -24,15 +24,10 @@ def add_end_event(process, event_id, end_timestamp): # add functions for adding events with their attributes to the log -<<<<<<<< HEAD:simpy_tutorial/eventlog.py -def export_to_csv(env, file_path): - event_log_df = pd.DataFrame.from_dict(env.bigeventlog) - event_log_df.to_csv(file_path) -======== + 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) ->>>>>>>> 575b59827b5f928da4165070a4a1d51a85eed774:Simpy_Tutorial/eventlog.py def export_to_xes(process, file_path): # Use appropriate code to export to XES format -- GitLab