def test_runSimulation_maxEvents(self): """Tests execution of simulation events with max_event parameter.""" # The FEL contains multiple events. testEventTimestamp = 10 testEventData = {'processed': False} testEvent = engine.DiscreteEvent( MockEvent, testEventTimestamp, data=testEventData) self.simEngine.schedule(testEvent) earlierTestEventData = {'processed': False} earlierTestEvent = engine.DiscreteEvent( MockEvent, testEventTimestamp - 1, data=earlierTestEventData) self.simEngine.schedule(earlierTestEvent) # The events are unprocessed. self.assertEqual(2, len(self.simEngine.FEL)) self.assertEqual(False, testEventData['processed']) self.assertEqual(False, earlierTestEventData['processed']) # The simulation runs with max_events parameter. self.simEngine.runSimulation(maxEvents=1) # Only max_event events were processed. self.assertEqual([testEvent], self.simEngine.FEL) self.assertEqual(False, testEventData['processed']) self.assertEqual(True, earlierTestEventData['processed']) # The simulation time has been updated. self.assertEqual( earlierTestEvent.timestamp, self.simEngine.currentTime())
def test_runSimulation(self): """Tests execution of simulation events.""" # The simulation time is at zero. self.assertEqual(0, self.simEngine.currentTime()) # The FEL contains events. testEventTimestamp = 10 testEventData = {'processed': False} testEvent = engine.DiscreteEvent( MockEvent, testEventTimestamp, data=testEventData) self.simEngine.schedule(testEvent) earlierTestEventData = {'processed': False} earlierTestEvent = engine.DiscreteEvent( MockEvent, testEventTimestamp - 1, data=earlierTestEventData) self.simEngine.schedule(earlierTestEvent) # The events are unprocessed. self.assertEqual(2, len(self.simEngine.FEL)) self.assertEqual(False, testEventData['processed']) self.assertEqual(False, earlierTestEventData['processed']) # The simulation runs. self.simEngine.runSimulation() # All events in the FEL were processed. self.assertEqual(0, len(self.simEngine.FEL)) self.assertEqual(True, testEventData['processed']) self.assertEqual(True, earlierTestEventData['processed']) # The simulation time has been updated. self.assertEqual(testEventTimestamp, self.simEngine.currentTime())
def test_schedule(self): """Tests scheduling of events.""" # FEL is empty before adding events. self.assertEqual(0, len(self.simEngine.FEL)) # An event is scheduled in the FEL. testEventTimestamp = 10 testEvent = engine.DiscreteEvent( MockEvent, testEventTimestamp, data={'processed': False}) self.simEngine.schedule(testEvent) self.assertEqual( [testEvent], self.simEngine.FEL) # An earlier event is scheduled at the front of the FEL. earlierTestEvent = engine.DiscreteEvent( MockEvent, testEventTimestamp - 1, data={'processed': False}) self.simEngine.schedule(earlierTestEvent) self.assertEqual( [earlierTestEvent, testEvent], self.simEngine.FEL) # An later event is scheduled at the end of the FEL. laterTestEvent = engine.DiscreteEvent( MockEvent, testEventTimestamp + 1, data={'processed': False}) self.simEngine.schedule(laterTestEvent) self.assertEqual( [earlierTestEvent, testEvent, laterTestEvent], self.simEngine.FEL)
def test_initializeEvent(self): """Tests the Initialize event.""" self.globalData = self._initGlobalData(self.TEST_NUM_STATIONS, initEntities=False) # The Initialize event is scheduled (but not yet processed). initEvent = engine.DiscreteEvent( nycbike.Initialize, -1, globalData=self.globalData, initialDistribution=self.TEST_INITIAL_BIKE_DISTRIBUTION, racksPerStation=30) self.simEngine.schedule(initEvent) # Stations are not yet initialized. self.assertEqual([], self.globalData['stations']) self.assertEqual([], self.globalData['pickupQueues']) self.assertEqual([], self.globalData['dropoffQueues']) # Arrival events are not yet scheduled. self.assertEqual([initEvent], self.simEngine.FEL) # The Initialize event is processed. self.simEngine.runSimulation(maxEvents=1) # Checkout lines/counters are initialized. self.assertEqual(self.TEST_NUM_STATIONS, len(self.globalData['stations'])) self.assertEqual(self.TEST_NUM_STATIONS, len(self.globalData['pickupQueues'])) self.assertEqual(self.TEST_NUM_STATIONS, len(self.globalData['dropoffQueues'])) # Arrival events are scheduled. self.assertTrue(initEvent not in self.simEngine.FEL) self.assertTrue(len(self.simEngine.FEL) > 0)
def test_rideEndEvent_racksAvailable(self): """Tests the RideEnd event when racks are available.""" currentTime = 100 self.simEngine.simTime = currentTime self.globalData = self._initGlobalData(self.TEST_NUM_STATIONS, initEntities=True) # The station has racks available. testStationID = 0 testStation = self.globalData['stations'][testStationID] numBikesBefore = testStation.numBikes numRacksBefore = testStation.numRacks self.assertTrue(numRacksBefore > 0) # Two customers are waiting for bike pickup. testPickupQueue = self.globalData['pickupQueues'][testStationID] # One customer has waited too long and will leave the station. longWaitCustomer = nycbike.Customer() longWaitCustomer.startID = testStationID longWaitCustomer.startPickupWait = currentTime - 100 # > REFUND_TIME testPickupQueue.put(longWaitCustomer) # The other customer has just arrived. shortWaitCustomer = nycbike.Customer() shortWaitCustomer.startID = testStationID shortWaitCustomer.startPickupWait = currentTime - 1 # < REFUND_TIME testPickupQueue.put(shortWaitCustomer) # The RideEnd event is scheduled and processed. customer = nycbike.Customer() customer.endID = testStationID rideEndEvent = engine.DiscreteEvent(nycbike.RideEnd, 10, globalData=self.globalData, stationID=testStationID, customer=customer) self.simEngine.schedule(rideEndEvent) self.simEngine.runSimulation(maxEvents=1) # The station attributes were updated. self.assertEqual(numBikesBefore + 1, testStation.numBikes) self.assertEqual(numRacksBefore - 1, testStation.numRacks) # Revenue is unchanged. self.assertEqual(0, self.globalData['statistics']['Revenue']) # No customers are waiting for pickup. self.assertEqual(0, len(testPickupQueue)) # An Arrival event was scheduled for the short-wait customer. self.assertEqual(1, len(self.simEngine.FEL)) self.assertEqual(nycbike.Arrival, self.simEngine.FEL[0].handler) self.assertEqual(shortWaitCustomer, self.simEngine.FEL[0].handlerKwargs['customer'])
def test_arrivalEvent_noBikesAvailable(self): """Tests the Arrival event when no bikes are available.""" self.globalData = self._initGlobalData(self.TEST_NUM_STATIONS, initEntities=True) # The station has zero bikes available. testStationID = 0 testStation = self.globalData['stations'][testStationID] testStation.numBikes = 0 numRacksBefore = testStation.numRacks # There are zero people waiting for a bike. testPickupQueue = self.globalData['pickupQueues'][testStationID] self.assertEqual(0, len(testPickupQueue)) # The Arrival event is scheduled and processed. arrivalEvent = engine.DiscreteEvent(nycbike.Arrival, 10, globalData=self.globalData, stationID=testStationID) self.simEngine.schedule(arrivalEvent) self.simEngine.runSimulation(maxEvents=1) # The customer was put in the pickup waiting queue. self.assertEqual(1, len(testPickupQueue)) # The start of waiting time was recorded. customer = testPickupQueue.remove() self.assertEqual(arrivalEvent.timestamp, customer.startPickupWait) # The station attributes were not updated. self.assertEqual(0, testStation.numBikes) self.assertEqual(numRacksBefore, testStation.numRacks) # Revenue is unchanged. self.assertEqual(0, self.globalData['statistics']['Revenue']) # The next Arrival event was scheduled for the station. scheduledArrivals = [ e for e in self.simEngine.FEL if e.handler == nycbike.Arrival ] self.assertEqual(1, len(scheduledArrivals)) self.assertEqual(testStationID, scheduledArrivals[0].handlerKwargs['stationID']) # No other events were scheduled. self.assertEqual(1, len(self.simEngine.FEL))
def test_rideEndEvent_noRacksAvailable(self): """Tests the RideEnd event when no racks are available.""" currentTime = 100 self.simEngine.simTime = currentTime self.globalData = self._initGlobalData(self.TEST_NUM_STATIONS, initEntities=True) # The station has zero racks available. testStationID = 0 testStation = self.globalData['stations'][testStationID] numBikesBefore = testStation.numBikes testStation.numRacks = 0 # One customer is waiting for bike pickup. testPickupQueue = self.globalData['pickupQueues'][testStationID] waitingCustomer = nycbike.Customer() waitingCustomer.startID = testStationID waitingCustomer.startPickupWait = currentTime - 1 # < REFUND_TIME testPickupQueue.put(waitingCustomer) # The RideEnd event is scheduled and processed. customer = nycbike.Customer() customer.endID = testStationID rideEndEvent = engine.DiscreteEvent(nycbike.RideEnd, 10, globalData=self.globalData, stationID=testStationID, customer=customer) self.simEngine.schedule(rideEndEvent) self.simEngine.runSimulation(maxEvents=1) # The station attributes were not changed. self.assertEqual(numBikesBefore, testStation.numBikes) self.assertEqual(0, testStation.numRacks) # Customers are still waiting for pickup. self.assertTrue(waitingCustomer in testPickupQueue) # The current customer entered the dropoff queue. self.assertTrue( customer in self.globalData['dropoffQueues'][testStationID]) # The start of dropoff waiting time was recorded. self.assertEqual(rideEndEvent.timestamp, customer.startDropoffWait)
def Initialize(simEngine, **kwargs): """Initializes bike stations and schedules arrivals.""" initialDistribution = kwargs['initialDistribution'] globalData = kwargs['globalData'] arrivalTimes = globalData['arrivalTimes'] numStations = len(arrivalTimes) # Initialize bike stations and queues. for stationID in range(numStations): globalData['stations'].append( Station(stationID, kwargs['racksPerStation'], initialDistribution[stationID])) globalData['pickupQueues'].append(Queue()) globalData['dropoffQueues'].append(Queue()) # Schedule first arrival event for each station. for stationID in range(numStations): if len(arrivalTimes[stationID]) > 0: t = arrivalTimes[stationID].pop(0) simEngine.schedule( engine.DiscreteEvent(Arrival, t, stationID=stationID, globalData=globalData))
def run(self, initialDistribution=None, totalNumBikes=NUM_BIKES, racksPerStation=RACKS, scaleArrivalRate=1, rngSeed=None, tripDataDir=None): """Runs the store checkout simulation until it completes. Args: initialDistribution: Initial distribution of bikes to stations. totalNumBikes: Total number of bikes used in the simulation. This parameter is ignored if initialDistribution is specified. racksPerStation: Number of bike racks per station. scaleArrivalRate: Scale factor for number of arrivals that occur during the simulation. Returns: Dictionary of simulation results. """ simStartTime = time.time() logging.info('Citi Bike Sharing Simulation') logging.info('\ttotalNumBikes: %d' % totalNumBikes) logging.info('\tracksPerStation: %d' % racksPerStation) logging.info('\tscaleArrivalRate: %.3f' % scaleArrivalRate) # Seed RNG if specified. if rngSeed is not None: logging.info('RNG seed: %d' % rngSeed) np.random.seed(rngSeed) # Load statistics derived from the Citi Bike trip dataset. tripDataDir = {'tripDataDir': tripDataDir} if tripDataDir else {} tripCountData, tripDurations, destinationP = ( load_trip_stats.loadTripStatistics(**tripDataDir)) numStations = tripCountData.shape[0] # Compute arrival times based on trip count data for each station and # the arrival rate scale factor. tripCountData = np.rint(tripCountData * scaleArrivalRate).astype(int) arrivalTimes = self.computeArrivalTimes(tripCountData) # Initial distribution of bikes to stations (set at time 00:00). if initialDistribution is None: initialDistribution = self.almostUniformWithTotalSum( numStations, totalNumBikes) assert len(initialDistribution) == len(tripCountData) # Initialize simulation statistics. statistics = { 'Revenue': 0, 'TimeWaitForDropoff': np.zeros(numStations), 'TimeWaitForCycle': np.zeros(numStations), 'CustomersLost': np.zeros(numStations), 'BikesLost': 0, 'IdleTime': np.zeros(numStations), } # Global simulation variables. globalData = { # Entities. 'stations': [], 'pickupQueues': [], 'dropoffQueues': [], # Citi bike dataset statistics. 'arrivalTimes': arrivalTimes, 'tripDurations': tripDurations, 'destinationP': destinationP, # Simulation statistics. 'statistics': statistics, # Constants. 'bikeLossProb': BIKE_LOSS_PROBABILITY, } # Initialize the simulation engine. simEngine = engine.DiscreteEventSimulationEngine() # Schedule initial event. initEvent = engine.DiscreteEvent( Initialize, -1, globalData=globalData, initialDistribution=initialDistribution, racksPerStation=racksPerStation) simEngine.schedule(initEvent) endEvent = engine.DiscreteEvent(endSim, 1440) # Run the simulation. simEngine.runSimulation() simDuration = time.time() - simStartTime logging.info('Simulation complete. Took %.3f seconds.\n' % simDuration) # Report simulation statistics. logging.info('Revenue: %.2f dollars' % statistics['Revenue']) logging.info('TimeWaitForCycle: %.3f minutes' % statistics['TimeWaitForCycle'].sum()) logging.info('TimeWaitForDropoff: %.3f minutes' % statistics['TimeWaitForDropoff'].sum()) logging.info('CustomersLost: %d' % statistics['CustomersLost'].sum()) logging.info('BikesLost: %d' % statistics['BikesLost']) logging.info('TotalIdleTime: %d' % statistics['IdleTime'].sum()) return statistics
def RideEnd(simEngine, **kwargs): """Customer finishes the bike ride.""" globalData = kwargs['globalData'] customer = kwargs['customer'] stationID = customer.endID currentTime = simEngine.currentTime() # Check if there are empty racks to keep the bike. if globalData['stations'][stationID].numRacks <= 0: # No empty racks. The customer begins waiting in queue. customer.startDropoffWait = currentTime globalData['dropoffQueues'][stationID].put(customer) logging.debug( '\t(customer %d) damn there are no empty racks at station %d at time %.3f' % (customer.customerID, stationID, currentTime)) return # Update total Idle Time till current time if currentTime < 1440: globalData['statistics']['IdleTime'][ stationID] += globalData['stations'][stationID].numBikes * ( currentTime - globalData['stations'][stationID].lastEvent) globalData['stations'][stationID].lastEvent = currentTime # Customer returns the bike to the rack. globalData['stations'][stationID].numRacks -= 1 globalData['stations'][stationID].numBikes += 1 logging.debug( '\t(customer %d) perfecto! i reached my destination %d at time %.3f, my journey is complete' % (customer.customerID, stationID, currentTime)) # If there is at least one customer waiting for a bike and waittime < 5 # mins, schedule arrival event. Note: not every customer waiting for a # bike eventually takes a bike..customers leave after 5 mins. while (True): # Check if customers are waiting. if len(globalData['pickupQueues'][stationID]) == 0: break waitingCustomer = globalData['pickupQueues'][stationID].remove() waitTime = currentTime - waitingCustomer.startPickupWait globalData['statistics']['TimeWaitForCycle'][stationID] += waitTime if waitTime < REFUND_TIME: # Next waiting customer gets a bike simEngine.schedule( engine.DiscreteEvent(Arrival, currentTime, customer=waitingCustomer, stationID=stationID, globalData=globalData)) logging.debug( '\t(customer %d) finally i get my ride at stn %d after waiting for %.3f having arrived at %.3f' % (waitingCustomer.customerID, stationID, waitTime, currentTime)) break else: # We lose a customer globalData['statistics']['CustomersLost'][stationID] += 1 logging.debug( '\t(customer %d) @#$%%! u wasted my time! i waited for %.3f minutes for a bike at stn %d, i dont want it anymore' % (waitingCustomer.customerID, waitTime, stationID))
def Arrival(simEngine, **kwargs): """Customer arrives at the station to pick up a bike.""" globalData = kwargs['globalData'] stationID = kwargs['stationID'] numStations = globalData['destinationP'].shape[0] numTimeframes = globalData['destinationP'].shape[1] # Customer who will pick up a bike. if 'customer' in kwargs: customer = kwargs['customer'] else: customer = Customer() customer.startID = stationID currentTime = simEngine.currentTime() # Checks the ArrivalData for the next arrival and schedules it. # Note: schedule immediately in case customer has to wait in line / leaves if len(globalData['arrivalTimes'][stationID]) > 0: t = globalData['arrivalTimes'][stationID].pop(0) simEngine.schedule( engine.DiscreteEvent(Arrival, t, stationID=stationID, globalData=globalData)) # Check if there are bikes available. if globalData['stations'][stationID].numBikes <= 0: # Customer begins waiting for bike to become available. customer.startPickupWait = currentTime globalData['pickupQueues'][stationID].put(customer) logging.debug( '\t(customer %d) Damn! where are all the bikes at station %d, the time is %.3f' % (customer.customerID, stationID, currentTime)) return # Customer pays to rent bike. globalData['statistics']['Revenue'] += TRIP_COST # Select destination based on the probabilities. currentTimeframe = int( np.floor((currentTime / float(DAY_DURATION)) * numTimeframes)) currentTimeframe = min(currentTimeframe, numTimeframes - 1) customer.endID = np.random.choice( numStations, p=globalData['destinationP'][stationID][currentTimeframe]) # Schedule end of ride using the average trip duration. t = (currentTime + globalData['tripDurations'][stationID][customer.endID]) # Determine if bike will become lost or damaged. rideOutcome = RideEnd if np.random.random() <= globalData['bikeLossProb']: rideOutcome = RideCrash simEngine.schedule( engine.DiscreteEvent(rideOutcome, t, customer=customer, globalData=globalData)) # Update total Idle Time till current time if currentTime <= 1440: globalData['statistics']['IdleTime'][ stationID] += globalData['stations'][stationID].numBikes * ( currentTime - globalData['stations'][stationID].lastEvent) globalData['stations'][stationID].lastEvent = currentTime # Update number of bikes and racks for the station. globalData['stations'][stationID].numBikes -= 1 globalData['stations'][stationID].numRacks += 1 logging.debug( '\t(customer %d) yay! i got a bike from %d at time %.3f n im going to %d n will reach at %.3f' % (customer.customerID, stationID, currentTime, customer.endID, t)) # Checks if there are people waiting to put the bikes back. if len(globalData['dropoffQueues'][stationID]) > 0: # Calculate time the customer waited to drop off the bike. waitingCustomer = globalData['dropoffQueues'][stationID].remove() waitTime = currentTime - waitingCustomer.startDropoffWait # Update total wait time. globalData['statistics']['TimeWaitForDropoff'][stationID] += waitTime logging.debug( '\t(customer %d) finally i can return my bike at stn %d after waiting for %.3f having arrived at %.3f' % (waitingCustomer.customerID, stationID, waitTime, currentTime)) # If customer has waited too long to return the bike, refund is given. if waitTime > REFUND_TIME: globalData['statistics']['Revenue'] -= TRIP_COST logging.debug( '\t(customer %d) at least i got my refund for waiting too long to return the bike' % (waitingCustomer.customerID)) # Schedule RideEnd for the waiting customer. simEngine.schedule( engine.DiscreteEvent(RideEnd, currentTime, customer=waitingCustomer, globalData=globalData))
def test_arrivalEvent_bikesAvailable(self): """Tests the Arrival event when bikes are available.""" currentTime = 100 self.simEngine.simTime = currentTime self.globalData = self._initGlobalData(self.TEST_NUM_STATIONS, initEntities=True) # The simulation time is 00:00, so we expect station 2 to be chosen # as the destination from station 0 (other stations have zero # probability of being the destination). self.simEngine.simTime = 0 expectedDestID = 2 # There is initially non-zero revenue. revenueBefore = 500 self.globalData['statistics']['Revenue'] = revenueBefore # The station has bikes available. testStationID = 0 testStation = self.globalData['stations'][testStationID] numBikesBefore = testStation.numBikes numRacksBefore = testStation.numRacks self.assertTrue(numBikesBefore > 0) # A customer has waited too long and should receive a refund. testDropoffQueue = self.globalData['dropoffQueues'][testStationID] longWaitCustomer = nycbike.Customer() longWaitCustomer.startID = testStationID longWaitCustomer.startDropoffWait = currentTime - 100 # > REFUND_TIME testDropoffQueue.put(longWaitCustomer) # The Arrival event is scheduled and processed. arrivalEvent = engine.DiscreteEvent(nycbike.Arrival, 10, globalData=self.globalData, stationID=testStationID) self.simEngine.schedule(arrivalEvent) self.simEngine.runSimulation(maxEvents=1) # The station attributes were updated. self.assertEqual(numBikesBefore - 1, testStation.numBikes) self.assertEqual(numRacksBefore + 1, testStation.numRacks) # The current customer paid for the bike. expectedRevenue = revenueBefore + nycbike.TRIP_COST # A RideEnd event was scheduled at the destination. rideEndEvents = [ e for e in self.simEngine.FEL if (e.handler == nycbike.RideEnd) ] rideEndEvent = rideEndEvents[0] # The trip destination is correct. ridingCustomer = rideEndEvent.handlerKwargs['customer'] self.assertEqual(expectedDestID, ridingCustomer.endID) # The trip duration is correct. expectedRideEndTime = ( arrivalEvent.timestamp + self.TEST_TRIP_DURATIONS[testStationID][expectedDestID]) self.assertEqual(expectedRideEndTime, rideEndEvent.timestamp) # The long-wait customer received a refund and left. self.assertFalse(longWaitCustomer in testDropoffQueue) expectedRevenue -= nycbike.TRIP_COST # The next Arrival event was scheduled for the station. scheduledArrivals = [ e for e in self.simEngine.FEL if e.handler == nycbike.Arrival ] self.assertEqual(1, len(scheduledArrivals)) self.assertEqual(testStationID, scheduledArrivals[0].handlerKwargs['stationID'])