def get_new_hd(lcsidefol, veh, lcsidelane): """Calculates new headways for a vehicle and its left or right follower. Args: lcsidefol: either the lfol or rfol of veh. veh: Vehicle whose lane changing model is being evaluated lcsidelane: the lcside lane of veh. Returns: newlcsidefolhd: new float headway for lcsidefol newlcsidehd: new float headway for veh """ # helper functino for set_lc_helper lcsidelead = lcsidefol.lead if lcsidelead is None: newlcsidehd = None else: newlcsidehd = get_headway(veh, lcsidelead) # if newlcsidehd < 0: # newlcsidehd = 1e-6 if lcsidefol.cf_parameters is None: newlcsidefolhd = None else: newlcsidefolhd = get_headway(lcsidefol, veh) # if newlcsidefolhd < 0: # newlcsidefolhd = 1e-6 return newlcsidefolhd, newlcsidehd
def update_calibration(vehicles, add_events, lc_events, addtime, lctime, timeind, dt, lc_event): """Main logic for a single step of the Calibration simulation. At the beginning of the timestep, vehicles/states/events are assumed to be fully updated. Then, in order, -call each vehicle's cf model (done in Calibration.step). -check for lead change events in the next timestep, and apply them if applicable. -update all vehicle's states, except headway, for the next timestep. -check for add events, and add the vehicles if applicable. -update all vehicle's headways. Then, when the timestep is incremented, all vehicles/states/events are fully updated, and the iteration can continue. Args: vehicles: set of vehicles currently in simulation add_events: sorted list of current add events lc_events: sorted list of current lead change events addtime: next time an add event occurs lctime: next time a lead change event occurs timeind: time index dt: timestep lc_event: function to apply a single entry in lc_events """ lctime = update_lc_event(lc_events, lctime, timeind, dt, lc_event) for veh in vehicles: veh.update(timeind, dt) addtime = update_add_event(vehicles, add_events, addtime, timeind, dt, lc_event) for veh in vehicles: veh.hd = get_headway(veh, veh.lead) return addtime, lctime
def new_relaxation(veh, timeind, dt, relax_speed=False): """Generates relaxation for a vehicle after it experiences a lane change. This is called directly after a vehicle changes it lane, while it still has the old value for its headway, and its position has not yet been updated. See (https://arxiv.org/abs/1904.08395) for an explanation of the relaxation model. This implements single parameter relaxation. The Vehicle attributes associated with relaxation are relax_parameters: float which gives the relaxation constant (units of time) in_relax: bool of whether or not vehicle is experiencing relaxation currently relax: list of floats which stores the relaxation values relax_start: time index (timeind) of the 0 index of relax relaxmem: memory of past relaxation, list of tuples of (start, end, relax) Args: veh: Vehicle to add relaxation to timeind: int giving the timestep of the simulation (0 indexed) dt: float of time unit that passes in each timestep relax_speed: If True, we relax leader's speed as well. Returns: None. Modifies relaxation attributes for vehicle in place. """ rp = veh.relax_parameters if rp is None: # no relax -> do nothing return if veh.lead is None: if veh.in_relax: # new lead is None -> reset relaxation veh.in_relax = False veh.relax = veh.relax[:timeind - veh.relax_start] veh.relaxmem.append((veh.relax, veh.relax_start)) return prevlead = veh.leadmem[-2][0] if prevlead is None: olds = veh.get_eql(veh.speed) # olds = veh.get_eql(veh.lead.speed) else: olds = veh.hd news = get_headway(veh, veh.lead) if relax_speed: # relax speed + headway so we have a list of tuples if prevlead is None: oldv = veh.speed else: oldv = prevlead.speed newv = veh.lead.speed relaxamount_s = olds - news relaxamount_v = oldv - newv relax_helper_vhd(rp, relaxamount_s, relaxamount_v, veh, timeind, dt) else: # relax headway only = list of float of relax values relaxamount = olds - news # edge case error here, see update_lane_routes.update_veh_after_lc relax_helper(rp, relaxamount, veh, timeind, dt)
def simulate(self, parameters): """Does a full simulation and returns the loss. Args: parameters: list of parameters for the vehicles. Returns: loss (float). """ # reset all vehicles, and assign their parameters for veh in self.all_vehicles: veh.initialize(parameters) # initialize events, add the first vehicle(s), initialize vehicles headways/LeadVehicles self.vehicles = set() self.add_events = self.all_add_events.copy() self.lc_events = self.all_lc_events.copy() self.addtime = self.add_events[-1][0] self.lctime = self.lc_events[-1][0] if len(self.lc_events)>0 else math.inf self.timeind = self.starttime self.addtime = update_add_event(self.vehicles, self.add_events, self.addtime, self.timeind-1, self.dt, self.lc_event) for veh in self.vehicles: if veh.in_leadveh: veh.leadveh.update(self.timeind) veh.hd = get_headway(veh, veh.lead) # do simulation by calling step repeatedly for i in range(self.endtime - self.starttime): self.step() # calculate and return loss loss = 0 for veh in self.all_vehicles: loss += veh.loss() return loss
def update_net(vehicles, lc_actions, inflow_lanes, merge_lanes, vehid, timeind, dt): """Updates all quantities for a road network. The simulation logics are as follows. At the beginning of the timestep, all vehicles/states/events are assumed to be fully updated for the current timestep. Then, in order: -evaluate the longitudinal action (cf model) for all vehicles (done in Simulation.step) -evaluation the latitudinal action (lc model) for all vehicles (done in Simulation.step) -complete requested lane changes -move vehicle to the new lane and set its new route/lane events -updating all leader/follower relationships (including l/rfol) for all vehicles involved. This means updating vehicles in up to 4 lanes (opside, self lane, lcside, and new lcside). -apply relaxation to any vehicles as necessary -update all states and memory for all vehicles -update headway for all vehicles -update any merge anchors -updating lane/route events for all vehicles, including removing vehicles if they leave the network. -update all lfol/rfol relationships -update the inflow conditions for all lanes with inflow, including possible adding new vehicles, and generating the parameters for the next new vehicle After the updating is complete, all vehicles/states/events are updated to their values for the next timestep, so when the time index is incremented, the iteration can continue. Args: vehicles: set containing all vehicle objects being actively simulated lc_actions: dictionary with keys as vehicles which request lane changes in the current timestep, values are a string either 'l' or 'r' which indicates the side of the change inflow_lanes: iterable of lanes which have upstream (inflow) boundary conditions. These lanes must have get_inflow, increment_inflow, and new_vehicle methods merge_lanes: iterable of lanes which have merge anchors. These lanes must have a merge_anchors attribute vehid: int giving the unique vehicle ID for the next vehicle to be generated timeind: int giving the timestep of the simulation (0 indexed) dt: float of time unit that passes in each timestep Returns: vehid: updated int value of next vehicle ID to be added remove_vehicles: list of vehicles which were removed from simulation at current timestep Modifies vehicles in place (potentially all their non-parameter/bounds attributes, e.g. pos/spd, lead/fol relationships, their lane/roads, memory, etc.). """ # update followers/leaders for all lane changes relaxvehs = [] # keeps track of vehicles which have relaxation applied for veh in lc_actions.keys(): relaxvehs.append(veh.fol) # update leader follower relationships, lane/road update_lane_routes.update_veh_after_lc( lc_actions, veh, timeind) # this is the only thing # which can't be done in parralel relaxvehs.append(veh) relaxvehs.append(veh.fol) # update a vehicle's lane events and route events for the new lane update_lane_routes.set_lane_events(veh) update_lane_routes.set_route_events(veh, timeind) for veh in set(relaxvehs): # apply relaxation veh.set_relax(timeind, dt) # update all states, memory and headway for veh in vehicles: veh.update(timeind, dt) for veh in vehicles: if veh.lead is not None: veh.hd = get_headway(veh, veh.lead) # update left and right followers # vehicle_orders.update_all_lrfol(vehicles) vehicle_orders.update_all_lrfol_multiple( vehicles) # handles edge cases explicitly, slower # update merge_anchors for curlane in merge_lanes: update_lane_routes.update_merge_anchors(curlane, lc_actions) # update roads (lane events) and routes remove_vehicles = [] for veh in vehicles: # check vehicle's lane events and route events, acting if necessary update_lane_routes.update_lane_events(veh, timeind, remove_vehicles) update_lane_routes.update_route_events(veh, timeind) # remove vehicles which leave for veh in remove_vehicles: vehicles.remove(veh) # update inflow, adding vehicles if necessary for curlane in inflow_lanes: vehid = curlane.increment_inflow(vehicles, vehid, timeind, dt) # for veh in vehicles: # debugging # if not veh._chk_leadfol(verbose = False): # # print('-------- Report for Vehicle '+str(veh.vehid)+' at time '+str(timeind)+'--------') # # veh._leadfol() # veh._chk_leadfol() # # raise ValueError('incorrect vehicle order') return vehid, remove_vehicles
def _chk_leadfol(self, verbose=True): """Returns True if the leader/follower relationships of the Vehicle are correct.""" # If verbose = True, we print whether each test is passing or not. lfolpass = True lfolmsg = [] if self.lfol is not None: if self.lfol is self: lfolpass = False lfolmsg.append('lfol is self') if self not in self.lfol.rlead: lfolpass = False lfolmsg.append('rlead of lfol is missing self') if self.lfol.lane.anchor is not self.llane.anchor: lfolpass = False lfolmsg.append('lfol is not in left lane') if get_dist(self, self.lfol) > 0: lfolpass = False lfolmsg.append('lfol is in front of self') if self.lfol.lead is not None: if get_dist(self, self.lfol.lead) < 0: lfolpass = False lfolmsg.append('lfol leader is behind self') lead, fol = self.lane.leadfol_find(self, self.lfol) if fol is not self.lfol: if self.lfol.pos == self.pos: pass else: lfolpass = False lfolmsg.append('lfol is not correct vehicle - should be ' + str(fol)) elif self.llane is not None: unused, fol = self.llane.leadfol_find(self, self.llane.anchor) lfolpass = False lfolmsg.append('lfol is None - should be ' + str(fol)) rfolpass = True rfolmsg = [] if self.rfol is not None: if self.rfol is self: rfolpass = False rfolmsg.append('rfol is self') if self not in self.rfol.llead: rfolpass = False rfolmsg.append('llead of rfol is missing self') if self.rfol.lane.anchor is not self.rlane.anchor: rfolpass = False rfolmsg.append('rfol is not in right lane') if get_dist(self, self.rfol) > 0: rfolpass = False rfolmsg.append('rfol is in front of self') if self.rfol.lead is not None: if get_dist(self, self.rfol.lead) < 0: rfolpass = False rfolmsg.append('rfol leader is behind self') lead, fol = self.lane.leadfol_find(self, self.rfol) if fol is not self.rfol: if self.rfol.pos == self.pos: pass else: rfolpass = False rfolmsg.append('rfol is not correct vehicle - should be ' + str(fol)) elif self.rlane is not None: unused, fol = self.rlane.leadfol_find(self, self.rlane.anchor) rfolpass = False rfolmsg.append('lfol is None - should be ' + str(fol)) rleadpass = True rleadmsg = [] for i in self.rlead: if i.lfol is not self: rleadpass = False rleadmsg.append('rlead does not have self as lfol') if get_dist(self, i) < 0: rleadpass = False rleadmsg.append('rlead is behind self') if len(self.rlead) != len(set(self.rlead)): rleadpass = False rleadmsg.append('repeated rlead') lleadpass = True lleadmsg = [] for i in self.llead: if i.rfol is not self: lleadpass = False lleadmsg.append('llead does not have self as rfol') if get_dist(self, i) < 0: lleadpass = False lleadmsg.append('llead is behind self') if len(self.llead) != len(set(self.llead)): lleadpass = False lleadmsg.append('repeated llead') leadpass = True leadmsg = [] if self.lead is not None: if self.lead.fol is not self: leadpass = False leadmsg.append('leader does not have self as follower') if get_headway(self, self.lead) < 0: leadpass = False leadmsg.append('leader is behind self') folpass = True folmsg = [] if self.fol.lead is not self: folpass = False folmsg.append('follower does not have self as leader') if get_headway(self, self.fol) > 0: folpass = False folmsg.append('follower is ahead of self') res = lfolpass and rfolpass and rleadpass and lleadpass and leadpass and folpass if verbose: if res: print('passing results for ' + str(self)) else: print('errors for ' + str(self)) if not lfolpass: for i in lfolmsg: print(i) if not rfolpass: for i in rfolmsg: print(i) if not rleadpass: for i in rleadmsg: print(i) if not lleadpass: for i in lleadmsg: print(i) if not leadpass: for i in leadmsg: print(i) if not folpass: for i in folmsg: print(i) return res
def set_lc_helper(veh, lside, rside, timeind, chk_lc=True, chk_lc_prob=1, get_fol=True): """Calculates the new headways to be passed to the lane changing (LC) model. Evaluates the lane changing situation to decide if we need to evaluate lane changing model on the left side, right side, both sides, or neither. For any sides we need to evaluate, finds the new headways (new vehicle headway, new lcside follower headway). If new headways are negative, returns positive instead. Args: veh: Vehicle to have their lane changing model called. timeind: time index chk_lc: bool, if True we are in mandatory or active discretionary state. If False we are in normal discretionary state and don't necessarily evaluate the LC model every timestep. chk_lc_prob: float between 0 and 1 which gives the probability of checking the lane changing model when the vehicle is in the normal discretionary state. get_fol: if True, we also find the new follower headway. Returns: bool: True if we want to call the lane changing model. tuple of floats: (newlfolhd, newlhd, newrfolhd, newrhd, newfolhd). float headways, giving the new headway for that vehicle. If get_fol = False, newfolhd is not present. If a vehicle would have no leader in the new configuration, None is returned as the headway. If a AnchorVehicle acts as a (l/r)fol, the headway is computed as normal. """ if not lside and not rside: return False, None if not chk_lc: # decide if we want to evaluate lc model or not - this only applies to discretionary state # when vehicle is not actively trying to change if timeind < veh.disc_cooldown: return False, None if chk_lc_prob >= 1: pass elif np.random.rand() > chk_lc_prob: return False, None # next we compute quantities to send to LC model for the required sides if lside: newlfolhd, newlhd = get_new_hd(veh.lfol, veh, veh.llane) else: newlfolhd = newlhd = None if rside: newrfolhd, newrhd = get_new_hd(veh.rfol, veh, veh.rlane) else: newrfolhd = newrhd = None if get_fol: if veh.lead is None: newfolhd = None else: newfolhd = get_headway(veh.fol, veh.lead) else: return True, (newlfolhd, newlhd, newrfolhd, newrhd) return True, (newlfolhd, newlhd, newrfolhd, newrhd, newfolhd)
def set_lc_helper(veh, chk_lc=1, get_fol=True): """Calculates the new headways to be passed to the lane changing (LC) model. Evaluates the lane changing situation to decide if we need to evaluate lane changing model on the left side, right side, both sides, or neither. For any sides we need to evaluate, finds the new headways (new vehicle headway, new lcside follower headway). If new headways are negative, returns positive instead. Args: veh: Vehicle to have their lane changing model called. chk_lc: float between 0 and 1 which gives the probability of checking the lane changing model when the vehicle is in a discretionary state. get_fol: if True, we also find the new follower headway. Returns: bool: True if we want to call the lane changing model. tuple of floats: (lside, rside, newlfolhd, newlhd, newrfolhd, newrhd, newfolhd). lside/rside are bools which are True if we need to check that side in the LC model. rest are float headways, giving the new headway for that vehicle. If get_fol = False, newfolhd is not present. """ # first determine what situation we are in and which sides we need to check l_lc, r_lc = veh.l_lc, veh.r_lc if l_lc is None: # TODO keep the lside, rside, chk_cond in memory instead of always updating them if r_lc is None: return False, None elif r_lc == 'discretionary': lside, rside = False, True chk_cond = not veh.lc_side else: lside, rside = False, True chk_cond = False elif l_lc == 'discretionary': if r_lc is None: lside, rside = True, False chk_cond = not veh.lc_side elif r_lc == 'discretionary': if veh.lc_side is not None: chk_cond = False if veh.lc_side == 'l': lside, rside = True, False else: lside, rside = False, True else: chk_cond = True lside, rside = True, True else: lside, rside = False, True chk_cond = False else: lside, rside = True, False chk_cond = False if chk_cond: # decide if we want to evaluate lc model or not - this only applies to discretionary state # when there is no cooperation or tactical positioning if chk_lc >= 1: pass elif np.random.rand() > chk_lc: return False, None # next we compute quantities to send to LC model for the required sides if lside: newlfolhd, newlhd = get_new_hd( veh.lfol, veh, veh.llane) # better to just store left/right lanes else: newlfolhd = newlhd = None if rside: newrfolhd, newrhd = get_new_hd(veh.rfol, veh, veh.rlane) else: newrfolhd = newrhd = None # if get_fol option is given to wrapper, it means model requires the follower's quantities as well if get_fol: fol, lead = veh.fol, veh.lead if fol.cf_parameters is None: newfolhd = None elif lead is None: newfolhd = None else: newfolhd = get_headway(fol, lead) else: return True, (lside, rside, newlfolhd, newlhd, newrfolhd, newrhd) return True, (lside, rside, newlfolhd, newlhd, newrfolhd, newrhd, newfolhd)