-
Notifications
You must be signed in to change notification settings - Fork 2
/
parksim.py
276 lines (226 loc) · 10.3 KB
/
parksim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Aug 12 10:30:12 2018
@author: clisle
"""
import pandas as pd
import simpy
import random
from pprint import *
import pickle
RANDOM_SEED = 42
NEW_VISITOR_BATCHES = 200 # Total number of customers
VISITORS_PER_BATCH = 50
INTERVAL_BATCHES = 5.0 # Generate new customers roughly every x clicks
MIN_PATIENCE = 30 # Min. customer patience
MAX_PATIENCE = 60 # Max. customer patience
REPEAT_SAME_RIDE_TIME = 5
MAX_NUMBER_OF_RIDES = 15
LENGTH_OF_STAY = 10*60 # guests leave after 10 hours
attractions_weighted = []
customers = {}
customer_states = ['riding','transit','eating','shopping','leaving']
# extend the SimPy Resource to keep track of all events
class MonitoredResource(simpy.Resource):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.data = []
def request(self, *args, **kwargs):
self.data.append(('request',self._env.now, len(self.queue)))
return super().request(*args, **kwargs)
def release(self, *args, **kwargs):
self.data.append(('release',self._env.now, len(self.queue)))
return super().release(*args, **kwargs)
def initializeAttractionPicker(attractions):
for a in attractions:
for i in range(attracts[a]['desirability']):
attractions_weighted.append(attracts[a]['name'])
#print(len(attractions_weighted),attractions_weighted)
def pickNextAttractionName(name,attracts):
global attractions_weighted
global customers
# try to find something we haven't been to, but after 10
# tries, give up and go for a repeat attraction
for i in range(10):
picked = random.choice(attractions_weighted)
if picked not in customers[name]['rides']:
break
#print('picking:',picked)
return picked
def pickNextAttractionName_set(name,attracts):
weighted_set = set(attractions_weighted)
visited = set(customers[name]['rides'])
choices = weighted_set - visited
pprint(weighted_set)
print('visited:')
pprint(visited)
pprint(choices)
picked = random.choice(list(choices))
#print('picking:',picked)
return picked
# given an attraction record as an argument, request this ride and either wait
# or give up depending on the wait time in the atraction's queue
def takeNextRide(attraction):
pass
# the scaling of the Magic Kingdom map we are using has 8000 squared units / minute for walking
def calculateTravelToAttraction(person,attract):
global REPEAT_SAME_RIDE_TIME
distance = (person['x']-attract['x'])**2 + (person['y']-attract['y'])**2
# it always some time even to repeat the same ride
return distance / 16000.0 + REPEAT_SAME_RIDE_TIME
# declare the visitor to the park. We have added an __init__ method so that the customer can be assigned to a venue
# as an initial experiment so we if we can create an arbitrary number of rides
def customer(env, name, attractions):
done = False
while not done:
if name not in customers.keys():
# first time
customers[name] = {}
customers[name]['name'] = name
customers[name]['starttime'] = env.now
customers[name]['rides'] = []
customers[name]['traveltime'] = 0
customers[name]['waittime'] = 0
customers[name]['reneges'] = 0
customers[name]['left'] = ''
customers[name]['x'] = 585
customers[name]['y'] = 926
# pick the next attraction
attract_name = pickNextAttractionName(name,attractions)
attraction = attractions[attract_name]
# travel to that attraction and wait until we arrive
time_to_travel = calculateTravelToAttraction(customers[name],attraction)
#print(name,'traveling to',attract_name, 'with time',time_to_travel)
customers[name]['traveltime'] += time_to_travel
yield env.timeout(time_to_travel)
# now we have arrived at the selected attraction
customers[name]['x'] = attraction['x']
customers[name]['y'] = attraction['y']
arrive = env.now
attraction['visitor_count'] += 1
time_in_ride = attractions[attract_name]['timelength']
# processing to get our patience this time and get in the line at the ride
with attraction['resource'].request() as req:
patience = random.uniform(MIN_PATIENCE, MAX_PATIENCE)
# Wait for the ride or abort at the end of our patience
results = yield req | env.timeout(patience)
# calculate our wait time
wait = env.now - arrive
attraction['wait_times'].append(wait)
customers[name]['waittime'] += wait
if req in results:
# we actually got on the ride, hooray! calculate the ride
# time and make it a bit randomized, then wait through the ride
time_in_ride_random = random.expovariate(1.0 / time_in_ride)
yield env.timeout(time_in_ride_random)
# record that we have been to this ride in our records
customers[name]['rides'].append(attract_name)
#print('%7.4f %s: Finished' % (env.now, name))
else:
# We reneged, the wait was too long
attraction['reneged_count'] += 1
customers[name]['reneges'] += 1
# a customer decides they are done after max rides or enough hours
done_rides = len(customers[name]['rides']) > MAX_NUMBER_OF_RIDES
done_time = (env.now - customers[name]['starttime']) > LENGTH_OF_STAY
done = done_rides or done_time
customers[name]['left'] = 'rides' if done_rides else 'time'
def source(env, batches, number_per_batch, interval, attracts):
"""Source generates customers randomly. They come in batches (like off a tram)"""
for i in range(batches):
for j in range(number_per_batch):
index = i*j+j
c = customer(env, 'Customer%06d' % index, attracts)
#print('customer starting with:',rideToTry)
env.process(c)
#t = random.expovariate(1.0 / interval)
# wait until the next batch arrives
t = random.gauss(1.0 / interval, (1.0 / interval)/5.0 )
yield env.timeout(t)
def printVisitorInformation():
#for key in customers:
# pprint(customers[key])
print('-----------------------')
rideCount = 0
waitMin = 9e99
waitMax = 0
waitTotal = 0
timeLeft = 0
for cust in customers:
waitTotal += customers[cust]['waittime']
waitMax = max(waitMax,customers[cust]['waittime'])
waitMin = min(waitMax,customers[cust]['waittime'])
rideCount += len(customers[cust]['rides'])
timeLeft += (1 if customers[cust]['left'] == 'time' else 0)
waitAvg = waitTotal / len(customers.keys())
rideAvg = rideCount / len(customers.keys())
print('Visitors rode',rideAvg,'Waiting - min,avg,max:',waitMin,waitAvg,waitMax)
print(timeLeft,'of total',len(customers.keys()) ,'visitors left after max time')
def printAttractionInformation():
print('-----------------------')
for attract in attracts.keys():
if len(attracts[attract]['wait_times']) > 0:
average_wait = sum(attracts[attract]['wait_times'])/float(len(attracts[attract]['wait_times']))
else:
average_wait = 0.0
print(attracts[attract]['name'],'had',attracts[attract]['visitor_count'],
'visitors with average wait time of',average_wait,
'and ',attracts[attract]['reneged_count'],' who gave up')
# the attraction dictionary contains SimPy objects, so it isn't good to serialize out. Copy the contents
# out of the archive and return a simple dictionary
def generateSerializableVenueRecords(attracts):
outdict = {}
for venue in attracts:
outdict[attracts[venue]['name']] = {}
outdict[attracts[venue]['name']]['name'] = attracts[venue]['name']
outdict[attracts[venue]['name']]['type'] = attracts[venue]['type']
outdict[attracts[venue]['name']]['desirability'] = attracts[venue]['desirability']
outdict[attracts[venue]['name']]['wait_times'] = attracts[venue]['wait_times']
outdict[attracts[venue]['name']]['visitor_count'] = attracts[venue]['visitor_count']
outdict[attracts[venue]['name']]['reneged_count'] = attracts[venue]['reneged_count']
outdict[attracts[venue]['name']]['events'] = attracts[venue]['resource'].data
return outdict
#---------------- main loop -------------------
attractList = pd.read_csv(open("config_park.csv","r"))
# Setup and start the simulation
print('Mini park with renege')
random.seed(RANDOM_SEED)
env = simpy.Environment()
# global disctionary of all the venues (rides, restaurants, etc)
attracts = {}
for index, row in attractList.iterrows():
rowlist = row.tolist()
thisname = rowlist[0]
attracts[thisname] = {}
attracts[thisname]['name'] = thisname
attracts[thisname]['type'] = rowlist[2]
attracts[thisname]['desirability'] = int(rowlist[3])
attracts[thisname]['capacity'] = int(rowlist[4])
attracts[thisname]['timelength'] = float(rowlist[5])
attracts[thisname]['x'] = rowlist[6]
attracts[thisname]['y'] = rowlist[7]
#try:
# attracts[thisname]['resource'] = simpy.Resource(env, capacity=attracts[thisname]['capacity'])
#except:
# print('something happened assigning this attraction:',attracts[thisname]['name'],attracts[thisname]['capacity'])
attracts[thisname]['resource'] = MonitoredResource(env, capacity=attracts[thisname]['capacity'])
attracts[thisname]['wait_times'] = []
attracts[thisname]['visitor_count'] = 0
attracts[thisname]['reneged_count'] = 0
#pprint(attracts)
initializeAttractionPicker(attracts)
#pprint(attractions_weighted)
# Start processes and run
env.process(source(env, NEW_VISITOR_BATCHES, VISITORS_PER_BATCH, INTERVAL_BATCHES, attracts))
env.run()
#pprint(attracts)
#for key in customers:
# pprint(customers[key])
printVisitorInformation()
printAttractionInformation()
# make output records object
venue_records = generateSerializableVenueRecords(attracts)
records = {'attractions': venue_records, 'guests':customers}
print('storing records of the day in the park')
pickle.dump( records, open( "parksim_records.p", "wb" ) )