From c40970fa66a58d6b28bd122a0433f1f715235e4f Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Fri, 25 Feb 2022 11:13:54 +0300 Subject: [PATCH 1/7] currentTripMode vs currentTourMode --- .../beam/agentsim/agents/PersonAgent.scala | 103 ++++-- .../agents/modalbehaviors/ChoosesMode.scala | 302 +++++++----------- .../agents/modalbehaviors/DrivesVehicle.scala | 22 +- src/main/scala/beam/router/Modes.scala | 1 + .../plan/sampling/AvailableModeUtils.scala | 4 + 5 files changed, 200 insertions(+), 232 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala index 2170f710f79..69e2072f265 100755 --- a/src/main/scala/beam/agentsim/agents/PersonAgent.scala +++ b/src/main/scala/beam/agentsim/agents/PersonAgent.scala @@ -189,6 +189,7 @@ object PersonAgent { restOfCurrentTrip: List[EmbodiedBeamLeg] = List(), currentVehicle: VehicleStack = Vector(), currentTourMode: Option[BeamMode] = None, + currentTripMode: Option[BeamMode] = None, currentTourPersonalVehicle: Option[Id[BeamVehicle]] = None, passengerSchedule: PassengerSchedule = PassengerSchedule(), currentLegPassengerScheduleIndex: Int = 0, @@ -475,6 +476,46 @@ class PersonAgent( } } + def isFirstTripWithinTour(personData: BasePersonData, nextAct: Activity): Boolean = { + val (tripIndexOfElement: Int, _) = currentTripIndexWithinTour(personData, nextAct) + tripIndexOfElement == 0 + } + + def isLastTripWithinTour(personData: BasePersonData, nextAct: Activity): Boolean = { + val (tripIndexOfElement: Int, lastTripIndex: Int) = currentTripIndexWithinTour(personData, nextAct) + tripIndexOfElement == lastTripIndex + } + + def isFirstOrLastTripWithinTour(personData: BasePersonData, nextAct: Activity): Boolean = { + val (tripIndexOfElement: Int, lastTripIndex: Int) = currentTripIndexWithinTour(personData, nextAct) + tripIndexOfElement == 0 || tripIndexOfElement == lastTripIndex + } + + def currentTripIndexWithinTour(personData: BasePersonData, nextAct: Activity): (Int, Int) = { + val tour = currentTour(personData) + val lastTripIndex = tour.trips.size - 1 + val tripIndexOfElement = tour + .tripIndexOfElement(nextAct) + .getOrElse(throw new IllegalArgumentException(s"Element [$nextAct] not found")) + if (tripIndexOfElement == 0 && currentActivity(personData).getType != "Home") + logger.warn( + "~Activity {}, idx {}, id {}, next {}", + currentActivity(personData).getType, + tripIndexOfElement, + id, + nextAct.getType + ) + if (tripIndexOfElement == lastTripIndex && nextAct.getType != "Home") + logger.warn( + "Cur ~Activity {}, idx {}, id {}, next {}", + currentActivity(personData).getType, + tripIndexOfElement, + id, + nextAct.getType + ) + (tripIndexOfElement, lastTripIndex) + } + def currentActivity(data: BasePersonData): Activity = _experiencedBeamPlan.activities(data.currentActivityIndex) @@ -587,15 +628,10 @@ class PersonAgent( val nextCoord = nextActivity(data).get.getCoord goto(ChoosingMode) using ChoosesModeData( personData = data.copy( - // If the mode of the next leg is defined and is CAV, use it, otherwise, - // If we don't have a current tour mode (i.e. are not on a tour aka at home), - // use the mode of the next leg as the new tour mode. - currentTourMode = modeOfNextLeg match { - case Some(CAV) => - Some(CAV) - case _ => - data.currentTourMode.orElse(modeOfNextLeg) - }, + // We do not stick to current tour mode + // If we have the currentTourPersonalVehicle then we should use it + // use the mode of the next leg as the new trip mode. + currentTripMode = modeOfNextLeg, numberOfReplanningAttempts = 0, failedTrips = IndexedSeq.empty, enrouteData = EnrouteData() @@ -612,7 +648,7 @@ class PersonAgent( when(Teleporting) { case Event( TriggerWithId(PersonDepartureTrigger(tick), triggerId), - data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, false, _, _, _, _, _) + data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, _, false, _, _, _, _, _) ) => endActivityAndDepart(tick, currentTrip, data) @@ -626,7 +662,7 @@ class PersonAgent( case Event( TriggerWithId(TeleportationEndsTrigger(tick), triggerId), - data @ BasePersonData(_, Some(currentTrip), _, _, maybeCurrentTourMode, _, _, _, true, _, _, _, _, _) + data @ BasePersonData(_, Some(currentTrip), _, _, _, maybeCurrentTripMode, _, _, _, true, _, _, _, _, _) ) => holdTickAndTriggerId(tick, triggerId) @@ -639,7 +675,7 @@ class PersonAgent( startY = currentTrip.legs.head.beamLeg.travelPath.startPoint.loc.getY, endX = currentTrip.legs.last.beamLeg.travelPath.endPoint.loc.getX, endY = currentTrip.legs.last.beamLeg.travelPath.endPoint.loc.getY, - currentTourMode = maybeCurrentTourMode.map(_.value) + currentTourMode = maybeCurrentTripMode.map(_.value) ) eventsManager.processEvent(teleportationEvent) @@ -654,7 +690,7 @@ class PersonAgent( */ case Event( TriggerWithId(PersonDepartureTrigger(tick), triggerId), - data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, false, _, _, _, _, _) + data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, _, false, _, _, _, _, _) ) => endActivityAndDepart(tick, currentTrip, data) @@ -663,7 +699,7 @@ class PersonAgent( case Event( TriggerWithId(PersonDepartureTrigger(tick), triggerId), - BasePersonData(_, _, restOfCurrentTrip, _, _, _, _, _, true, _, _, _, _, _) + BasePersonData(_, _, restOfCurrentTrip, _, _, _, _, _, _, true, _, _, _, _, _) ) => // We're coming back from replanning, i.e. we are already on the trip, so we don't throw a departure event logDebug(s"replanned to leg ${restOfCurrentTrip.head}") @@ -719,7 +755,7 @@ class PersonAgent( val currentCoord = beamServices.geo.wgs2Utm(data.restOfCurrentTrip.head.beamLeg.travelPath.startPoint).loc val nextCoord = nextActivity(data).get.getCoord goto(ChoosingMode) using ChoosesModeData( - data.copy(currentTourMode = None, numberOfReplanningAttempts = data.numberOfReplanningAttempts + 1), + data.copy(currentTripMode = None, numberOfReplanningAttempts = data.numberOfReplanningAttempts + 1), currentLocation = SpaceTime( currentCoord, tick @@ -738,7 +774,7 @@ class PersonAgent( // TRANSIT FAILURE case Event( ReservationResponse(Left(firstErrorResponse), _), - data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _) + data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _, _) ) => logDebug(s"replanning because ${firstErrorResponse.errorCode}") @@ -812,7 +848,7 @@ class PersonAgent( // RIDE HAIL FAILURE case Event( response @ RideHailResponse(_, _, Some(error), _, _), - data @ BasePersonData(_, _, _, _, _, _, _, _, _, _, _, _, _, _) + data: BasePersonData ) => handleFailedRideHailReservation(error, response, data) } @@ -823,7 +859,7 @@ class PersonAgent( */ case Event( TriggerWithId(BoardVehicleTrigger(tick, vehicleToEnter), triggerId), - data @ BasePersonData(_, _, currentLeg :: _, currentVehicle, _, _, _, _, _, _, _, _, _, _) + data @ BasePersonData(_, _, currentLeg :: _, currentVehicle, _, _, _, _, _, _, _, _, _, _, _) ) => logDebug(s"PersonEntersVehicle: $vehicleToEnter @ $tick") eventsManager.processEvent(new PersonEntersVehicleEvent(tick, id, vehicleToEnter)) @@ -856,7 +892,7 @@ class PersonAgent( */ case Event( TriggerWithId(AlightVehicleTrigger(tick, vehicleToExit, energyConsumedOption), triggerId), - data @ BasePersonData(_, _, _ :: restOfCurrentTrip, currentVehicle, _, _, _, _, _, _, _, _, _, _) + data @ BasePersonData(_, _, _ :: restOfCurrentTrip, currentVehicle, _, _, _, _, _, _, _, _, _, _, _) ) if vehicleToExit.equals(currentVehicle.head) => updateFuelConsumed(energyConsumedOption) logDebug(s"PersonLeavesVehicle: $vehicleToExit @ $tick") @@ -933,6 +969,7 @@ class PersonAgent( _, _, _, + _, currentCost, _, _, @@ -977,6 +1014,7 @@ class PersonAgent( goto(ChoosingMode) using ChoosesModeData( basePersonData.copy( currentTourMode = None, // Have to give up my mode as well, perhaps there's no option left for driving. + currentTripMode = None, currentTourPersonalVehicle = None, numberOfReplanningAttempts = basePersonData.numberOfReplanningAttempts + 1 ), @@ -1073,6 +1111,7 @@ class PersonAgent( _, _, _, + _, _ ) ) if nextLeg.asDriver => @@ -1167,7 +1206,7 @@ class PersonAgent( nextState // TRANSIT but too late - case Event(StateTimeout, data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _)) + case Event(StateTimeout, data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _, _)) if nextLeg.beamLeg.mode.isTransit && nextLeg.beamLeg.startTime < _currentTick.get => // We've missed the bus. This occurs when something takes longer than planned (based on the // initial inquiry). So we replan but change tour mode to WALK_TRANSIT since we've already done our non-transit @@ -1182,7 +1221,7 @@ class PersonAgent( val nextCoord = nextActivity(data).get.getCoord goto(ChoosingMode) using ChoosesModeData( personData = data - .copy(currentTourMode = Some(WALK_TRANSIT), numberOfReplanningAttempts = data.numberOfReplanningAttempts + 1), + .copy(currentTripMode = Some(WALK_TRANSIT), numberOfReplanningAttempts = data.numberOfReplanningAttempts + 1), currentLocation = SpaceTime(currentCoord, _currentTick.get), isWithinTripReplanning = true, excludeModes = @@ -1190,7 +1229,7 @@ class PersonAgent( else Vector(BeamMode.RIDE_HAIL, BeamMode.CAR, BeamMode.CAV) ) // TRANSIT - case Event(StateTimeout, BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _)) + case Event(StateTimeout, BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _, _)) if nextLeg.beamLeg.mode.isTransit => val resRequest = TransitReservationRequest( nextLeg.beamLeg.travelPath.transitStops.get.fromIdx, @@ -1201,7 +1240,7 @@ class PersonAgent( TransitDriverAgent.selectByVehicleId(nextLeg.beamVehicleId) ! resRequest goto(WaitingForReservationConfirmation) // RIDE_HAIL - case Event(StateTimeout, BasePersonData(_, _, nextLeg :: tailOfCurrentTrip, _, _, _, _, _, _, _, _, _, _, _)) + case Event(StateTimeout, BasePersonData(_, _, nextLeg :: tailOfCurrentTrip, _, _, _, _, _, _, _, _, _, _, _, _)) if nextLeg.isRideHail => val legSegment = nextLeg :: tailOfCurrentTrip.takeWhile(leg => leg.beamVehicleId == nextLeg.beamVehicleId) @@ -1229,7 +1268,7 @@ class PersonAgent( goto(WaitingForReservationConfirmation) // CAV but too late // TODO: Refactor so it uses literally the same code block as transit - case Event(StateTimeout, data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _)) + case Event(StateTimeout, data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _, _)) if nextLeg.beamLeg.startTime < _currentTick.get => // We've missed the CAV. This occurs when something takes longer than planned (based on the // initial inquiry). So we replan but change tour mode to WALK_TRANSIT since we've already done our non-transit @@ -1244,7 +1283,7 @@ class PersonAgent( val nextCoord = nextActivity(data).get.getCoord goto(ChoosingMode) using ChoosesModeData( personData = data - .copy(currentTourMode = Some(WALK_TRANSIT), numberOfReplanningAttempts = data.numberOfReplanningAttempts + 1), + .copy(currentTripMode = Some(WALK_TRANSIT), numberOfReplanningAttempts = data.numberOfReplanningAttempts + 1), currentLocation = SpaceTime(currentCoord, _currentTick.get), isWithinTripReplanning = true, excludeModes = @@ -1253,7 +1292,7 @@ class PersonAgent( ) // CAV // TODO: Refactor so it uses literally the same code block as transit - case Event(StateTimeout, BasePersonData(_, _, nextLeg :: tailOfCurrentTrip, _, _, _, _, _, _, _, _, _, _, _)) => + case Event(StateTimeout, BasePersonData(_, _, nextLeg :: tailOfCurrentTrip, _, _, _, _, _, _, _, _, _, _, _, _)) => val legSegment = nextLeg :: tailOfCurrentTrip.takeWhile(leg => leg.beamVehicleId == nextLeg.beamVehicleId) val resRequest = ReservationRequest( legSegment.head.beamLeg, @@ -1273,7 +1312,8 @@ class PersonAgent( _, _, _, - currentTourMode @ Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION), + _, + Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION), _, _, _, @@ -1313,7 +1353,8 @@ class PersonAgent( currentTrip = None, restOfCurrentTrip = List(), currentTourPersonalVehicle = None, - currentTourMode = if (activity.getType.equals("Home")) None else currentTourMode, + currentTourMode = if (activity.getType.equals("Home")) None else data.currentTourMode, + currentTripMode = None, hasDeparted = false ) case None => @@ -1331,6 +1372,7 @@ class PersonAgent( _, _, currentTourMode, + _, currentTourPersonalVehicle, _, _, @@ -1413,6 +1455,7 @@ class PersonAgent( None }, currentTourMode = if (activity.getType.equals("Home")) None else currentTourMode, + currentTripMode = None, hasDeparted = false ) case None => @@ -1486,7 +1529,7 @@ class PersonAgent( } def getReplanningReasonFrom(data: BasePersonData, prefix: String): String = { - data.currentTourMode + data.currentTripMode .collect { case mode => s"$prefix $mode" } @@ -1560,7 +1603,7 @@ class PersonAgent( handleBoardOrAlightOutOfPlace case Event( TriggerWithId(BoardVehicleTrigger(_, vehicleId), triggerId), - BasePersonData(_, _, _, currentVehicle, _, _, _, _, _, _, _, _, _, _) + BasePersonData(_, _, _, currentVehicle, _, _, _, _, _, _, _, _, _, _, _) ) if currentVehicle.nonEmpty && currentVehicle.head.equals(vehicleId) => log.debug("Person {} in state {} received Board for vehicle that he is already on, ignoring...", id, stateName) stay() replying CompletionNotice(triggerId, Vector()) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index 46d369da40a..fef8fbbb4c6 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -128,102 +128,22 @@ trait ChoosesMode { def bodyVehiclePersonId: PersonIdWithActorRef = PersonIdWithActorRef(id, self) - def currentTourBeamVehicle: Option[BeamVehicle] = { - stateData match { - case data: ChoosesModeData => - data.personData.currentTourPersonalVehicle match { - case Some(personalVehicle) => - Option( - beamVehicles(personalVehicle) - .asInstanceOf[ActualVehicle] - .vehicle - ) - case _ => None - } - case data: BasePersonData => - data.currentTourPersonalVehicle match { - case Some(personalVehicle) => - Option( - beamVehicles(personalVehicle) - .asInstanceOf[ActualVehicle] - .vehicle - ) - case _ => None - } - case _ => - None - } - } - onTransition { case _ -> ChoosingMode => nextStateData match { // If I am already on a tour in a vehicle, only that vehicle is available to me - case ChoosesModeData( - BasePersonData(_, _, _, _, _, Some(vehicle), _, _, _, _, _, _, _, _), - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _ + case data: ChoosesModeData + if data.personData.currentTourPersonalVehicle.isDefined && ( + data.personData.currentTourMode.exists(mode => mode == CAR || mode == BIKE) + || data.personData.currentTourMode.exists(mode => mode == DRIVE_TRANSIT || mode == BIKE_TRANSIT) + && isLastTripWithinTour(data.personData, nextActivity(data.personData).get) ) => - self ! MobilityStatusResponse(Vector(beamVehicles(vehicle)), getCurrentTriggerIdOrGenerate) - // Only need to get available street vehicles if our mode requires such a vehicle - case ChoosesModeData( - BasePersonData( - _, - _, - _, - _, - Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION), - _, - _, - _, - _, - _, - _, - _, - _, - _ - ), - currentLocation, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _ - ) => - val teleportationVehicle = createSharedTeleportationVehicle(currentLocation) + self ! MobilityStatusResponse( + Vector(beamVehicles(data.personData.currentTourPersonalVehicle.get)), + getCurrentTriggerIdOrGenerate + ) + // Create teleportation vehicle if we are told to use teleportation + case data: ChoosesModeData if data.personData.currentTripMode.exists(_.isHovTeleportation) => + val teleportationVehicle = createSharedTeleportationVehicle(data.currentLocation) val vehicles = Vector(ActualVehicle(teleportationVehicle)) self ! MobilityStatusResponse(vehicles, getCurrentTriggerIdOrGenerate) // Only need to get available street vehicles if our mode requires such a vehicle @@ -242,6 +162,7 @@ trait ChoosesMode { _, _, _, + _, _ ), currentLocation, @@ -314,37 +235,22 @@ trait ChoosesMode { case Event(MobilityStatusResponse(newlyAvailableBeamVehicles, triggerId), choosesModeData: ChoosesModeData) => beamVehicles ++= newlyAvailableBeamVehicles.map(v => v.id -> v) val currentPersonLocation = choosesModeData.currentLocation - val availableModes: Seq[BeamMode] = availableModesForPerson( - matsimPlan.getPerson - ).filterNot(mode => choosesModeData.excludeModes.contains(mode)) + val availableModes: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) + val personData = choosesModeData.personData + val nextAct = nextActivity(personData).get // Make sure the current mode is allowable - val replanningIsAvailable = - choosesModeData.personData.numberOfReplanningAttempts < beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.maximumNumberOfReplanningAttempts - val correctedCurrentTourMode = choosesModeData.personData.currentTourMode match { - case Some(mode @ (HOV2_TELEPORTATION | HOV3_TELEPORTATION)) - if availableModes.contains(CAR) && replanningIsAvailable => - Some(mode) - case Some(mode) if availableModes.contains(mode) && replanningIsAvailable => Some(mode) - case Some(mode) if availableModes.contains(mode) => Some(WALK) - case None if !replanningIsAvailable => Some(WALK) - case _ => None - } + val correctedCurrentTripMode = correctCurrentTripModeAccordingToRules(personData, nextAct, availableModes) val bodyStreetVehicle = createBodyStreetVehicle(currentPersonLocation) - val nextAct = nextActivity(choosesModeData.personData).get val departTime = _currentTick.get var availablePersonalStreetVehicles = - correctedCurrentTourMode match { + correctedCurrentTripMode match { case None | Some(CAR | BIKE | HOV2_TELEPORTATION | HOV3_TELEPORTATION) => // In these cases, a personal vehicle will be involved newlyAvailableBeamVehicles case Some(DRIVE_TRANSIT | BIKE_TRANSIT) => - val tour = _experiencedBeamPlan.getTourContaining(nextAct) - val tripIndexOfElement = tour - .tripIndexOfElement(nextAct) - .getOrElse(throw new IllegalArgumentException(s"Element [$nextAct] not found")) - if (tripIndexOfElement == 0 || tripIndexOfElement == tour.trips.size - 1) { + if (isFirstOrLastTripWithinTour(personData, nextAct)) { newlyAvailableBeamVehicles } else { Vector() @@ -411,7 +317,7 @@ trait ChoosesMode { streetVehicles: Vector[StreetVehicle], byMode: BeamMode ): Vector[StreetVehicle] = { - choosesModeData.personData.currentTourPersonalVehicle match { + personData.currentTourPersonalVehicle match { case Some(personalVeh) => // We already have a vehicle we're using on this tour, so filter down to that streetVehicles.filter(_.id == personalVeh) @@ -427,7 +333,7 @@ trait ChoosesMode { var requestId: Option[Int] = None // Form and send requests - correctedCurrentTourMode match { + correctedCurrentTripMode match { case None => if (hasRideHail) { responsePlaceholders = makeResponsePlaceholders( @@ -456,7 +362,7 @@ trait ChoosesMode { makeRequestWith(withTransit = true, Vector(bodyStreetVehicle)) case Some(CAV) => // Request from household the trip legs to put into trip - householdRef ! CavTripLegsRequest(bodyVehiclePersonId, currentActivity(choosesModeData.personData)) + householdRef ! CavTripLegsRequest(bodyVehiclePersonId, currentActivity(personData)) responsePlaceholders = makeResponsePlaceholders(withPrivateCAV = true) case Some(HOV2_TELEPORTATION) => val vehicles = filterStreetVehiclesForQuery(newlyAvailableBeamVehicles.map(_.streetVehicle), CAR) @@ -508,13 +414,10 @@ trait ChoosesMode { } case Some(mode @ (DRIVE_TRANSIT | BIKE_TRANSIT)) => val vehicleMode = Modes.getAccessVehicleMode(mode) - val LastTripIndex = currentTour(choosesModeData.personData).trips.size - 1 - val tripIndexOfElement = currentTour(choosesModeData.personData) - .tripIndexOfElement(nextAct) - .getOrElse(throw new IllegalArgumentException(s"Element [$nextAct] not found")) + val (tripIndexOfElement: Int, lastTripIndex: Int) = currentTripIndexWithinTour(personData, nextAct) ( tripIndexOfElement, - choosesModeData.personData.currentTourPersonalVehicle + personData.currentTourPersonalVehicle ) match { case (0, _) if !choosesModeData.isWithinTripReplanning => // We use our car if we are not replanning, otherwise we end up doing a walk transit (catch-all below) @@ -526,7 +429,7 @@ trait ChoosesMode { :+ bodyStreetVehicle ) responsePlaceholders = makeResponsePlaceholders(withRouting = true) - case (LastTripIndex, Some(currentTourPersonalVehicle)) => + case (`lastTripIndex`, Some(currentTourPersonalVehicle)) => // At the end of the tour, only drive home a vehicle that we have also taken away from there. makeRequestWith( withTransit = true, @@ -562,7 +465,7 @@ trait ChoosesMode { logDebug(m.toString) } val newPersonData = choosesModeData.copy( - personData = choosesModeData.personData.copy(currentTourMode = correctedCurrentTourMode), + personData = personData.copy(currentTripMode = correctedCurrentTripMode), availablePersonalStreetVehicles = availablePersonalStreetVehicles, allAvailableStreetVehicles = newlyAvailableBeamVehicles, routingResponse = responsePlaceholders.routingResponse, @@ -799,6 +702,28 @@ trait ChoosesMode { ) } using completeChoiceIfReady) + private def correctCurrentTripModeAccordingToRules( + personData: BasePersonData, + nextAct: Activity, + availableModes: Seq[BeamMode] + ): Option[BeamMode] = { + val replanningIsAvailable = + personData.numberOfReplanningAttempts < beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.maximumNumberOfReplanningAttempts + (personData.currentTripMode, personData.currentTourMode) match { + case (_, Some(CAR | BIKE)) if personData.currentTourPersonalVehicle.isDefined => personData.currentTourMode + case (_, Some(DRIVE_TRANSIT | BIKE_TRANSIT)) + if personData.currentTourPersonalVehicle.isDefined && isLastTripWithinTour(personData, nextAct) => + personData.currentTourMode + case (Some(mode @ (HOV2_TELEPORTATION | HOV3_TELEPORTATION)), _) + if availableModes.contains(CAR) && replanningIsAvailable => + Some(mode) + case (Some(mode), _) if availableModes.contains(mode) && replanningIsAvailable => Some(mode) + case (Some(mode), _) if availableModes.contains(mode) => Some(WALK) + case (None, _) if !replanningIsAvailable => Some(WALK) + case _ => None + } + } + private def makeParkingInquiries( choosesModeData: ChoosesModeData, itineraries: Seq[EmbodiedBeamTrip] @@ -1182,7 +1107,7 @@ trait ChoosesMode { ) <= beamScenario.beamConfig.beam.agentsim.agents.rideHail.allocationManager.maxWaitingTimeInSec => val origLegs = travelProposal.toEmbodiedBeamLegsForCustomer(bodyVehiclePersonId) (travelProposal.poolingInfo match { - case Some(poolingInfo) if !choosesModeData.personData.currentTourMode.contains(RIDE_HAIL) => + case Some(poolingInfo) if !choosesModeData.personData.currentTripMode.contains(RIDE_HAIL) => val pooledLegs = origLegs.map { origLeg => origLeg.copy( cost = origLeg.cost * poolingInfo.costFactor, @@ -1224,7 +1149,7 @@ trait ChoosesMode { def isAvailable(mode: BeamMode): Boolean = combinedItinerariesForChoice.exists(_.tripClassifier == mode) - choosesModeData.personData.currentTourMode match { + choosesModeData.personData.currentTripMode match { case Some(expectedMode) if expectedMode.isTransit && !isAvailable(expectedMode) => eventsManager.processEvent( createFailedTransitODSkimmerEvent(currentPersonLocation.loc, nextAct.getCoord, expectedMode) @@ -1232,20 +1157,13 @@ trait ChoosesMode { case _ => } - val availableModesForTrips: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson) - .filterNot(mode => choosesModeData.excludeModes.contains(mode)) + val availableModesForTrips: Seq[BeamMode] = + availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) - val filteredItinerariesForChoice = (choosesModeData.personData.currentTourMode match { + val filteredItinerariesForChoice = (choosesModeData.personData.currentTripMode match { case Some(mode) if mode == DRIVE_TRANSIT || mode == BIKE_TRANSIT => - val LastTripIndex = currentTour(choosesModeData.personData).trips.size - 1 - val tripIndexOfElement = currentTour(choosesModeData.personData) - .tripIndexOfElement(nextAct) - .getOrElse(throw new IllegalArgumentException(s"Element [$nextAct] not found")) - ( - tripIndexOfElement, - personData.hasDeparted - ) match { - case (0 | LastTripIndex, false) => + (isFirstOrLastTripWithinTour(personData, nextAct), personData.hasDeparted) match { + case (true, false) => combinedItinerariesForChoice.filter(_.tripClassifier == mode) case _ => combinedItinerariesForChoice.filter(trip => @@ -1272,6 +1190,13 @@ trait ChoosesMode { .asInstanceOf[AttributesOfIndividual] val availableAlts = Some(filteredItinerariesForChoice.map(_.tripClassifier).mkString(":")) + def gotoFinishingModeChoice(chosenTrip: EmbodiedBeamTrip) = { + goto(FinishingModeChoice) using choosesModeData.copy( + pendingChosenTrip = Some(chosenTrip), + availableAlternatives = availableAlts + ) + } + modeChoiceCalculator( filteredItinerariesForChoice, attributesOfIndividual, @@ -1279,13 +1204,9 @@ trait ChoosesMode { Some(matsimPlan.getPerson) ) match { case Some(chosenTrip) => - val dataForNextStep = choosesModeData.copy( - pendingChosenTrip = Some(chosenTrip), - availableAlternatives = availableAlts - ) - goto(FinishingModeChoice) using dataForNextStep + gotoFinishingModeChoice(chosenTrip) case None => - choosesModeData.personData.currentTourMode match { + choosesModeData.personData.currentTripMode match { case Some(CAV) => // Special case, if you are using household CAV, no choice was necessary you just use this mode // Construct the embodied trip to allow for processing by FinishingModeChoice and scoring @@ -1307,10 +1228,7 @@ trait ChoosesMode { body.beamVehicleType.id ) val cavTrip = EmbodiedBeamTrip(walk1 +: cavTripLegs.legs.toVector :+ walk2) - goto(FinishingModeChoice) using choosesModeData.copy( - pendingChosenTrip = Some(cavTrip), - availableAlternatives = availableAlts - ) + gotoFinishingModeChoice(cavTrip) } else { val bushwhackingTrip = RoutingWorker.createBushwackingTrip( choosesModeData.currentLocation.loc, @@ -1319,49 +1237,61 @@ trait ChoosesMode { body.toStreetVehicle, geo ) - goto(FinishingModeChoice) using choosesModeData.copy( - pendingChosenTrip = Some(bushwhackingTrip), - availableAlternatives = availableAlts - ) + gotoFinishingModeChoice(bushwhackingTrip) } case Some(_) => - //give another chance to make a choice without predefined mode - self ! MobilityStatusResponse(choosesModeData.allAvailableStreetVehicles, getCurrentTriggerId.get) - stay() using ChoosesModeData( - personData = personData.copy(currentTourMode = None), - currentLocation = choosesModeData.currentLocation, - excludeModes = choosesModeData.excludeModes - ) + val correctedTripMode = + correctCurrentTripModeAccordingToRules(personData, nextAct, availableModesForTrips) + if (correctedTripMode != personData.currentTripMode) { + //give another chance to make a choice without predefined mode + gotoChoosingModeWithoutPredefinedMode(choosesModeData) + } else { + val expensiveWalkTrip = createExpensiveWalkTrip(currentPersonLocation, nextAct, routingResponse) + gotoFinishingModeChoice(expensiveWalkTrip) + } case _ => // Bad things happen but we want them to continue their day, so we signal to downstream that trip should be made to be expensive - val originalWalkTripLeg = - routingResponse.itineraries.find(_.tripClassifier == WALK) match { - case Some(originalWalkTrip) => - originalWalkTrip.legs.head - case None => - RoutingWorker - .createBushwackingTrip( - currentPersonLocation.loc, - nextAct.getCoord, - _currentTick.get, - body.toStreetVehicle, - beamServices.geo - ) - .legs - .head - } - val expensiveWalkTrip = EmbodiedBeamTrip( - Vector(originalWalkTripLeg.copy(replanningPenalty = 10.0)) - ) - - goto(FinishingModeChoice) using choosesModeData.copy( - pendingChosenTrip = Some(expensiveWalkTrip), - availableAlternatives = availableAlts - ) + val expensiveWalkTrip = createExpensiveWalkTrip(currentPersonLocation, nextAct, routingResponse) + gotoFinishingModeChoice(expensiveWalkTrip) } } } + private def createExpensiveWalkTrip( + currentPersonLocation: SpaceTime, + nextAct: Activity, + routingResponse: RoutingResponse + ) = { + val originalWalkTripLeg = + routingResponse.itineraries.find(_.tripClassifier == WALK) match { + case Some(originalWalkTrip) => + originalWalkTrip.legs.head + case None => + RoutingWorker + .createBushwackingTrip( + currentPersonLocation.loc, + nextAct.getCoord, + _currentTick.get, + body.toStreetVehicle, + beamServices.geo + ) + .legs + .head + } + val expensiveWalkTrip = EmbodiedBeamTrip( + Vector(originalWalkTripLeg.copy(replanningPenalty = 10.0)) + ) + expensiveWalkTrip + } + + private def gotoChoosingModeWithoutPredefinedMode(choosesModeData: ChoosesModeData) = { + goto(ChoosingMode) using choosesModeData.copy( + personData = choosesModeData.personData.copy(currentTripMode = None), + currentLocation = choosesModeData.currentLocation, + excludeModes = choosesModeData.excludeModes + ) + } + private def createFailedTransitODSkimmerEvent( originLocation: Location, destinationLocation: Location, @@ -1470,7 +1400,7 @@ trait ChoosesMode { tick, id, chosenTrip.tripClassifier.value, - data.personData.currentTourMode.map(_.value).getOrElse(""), + data.personData.currentTripMode.map(_.value).getOrElse(""), data.expectedMaxUtilityOfLatestChoice.getOrElse[Double](Double.NaN), _experiencedBeamPlan .activities(data.personData.currentActivityIndex) @@ -1486,7 +1416,7 @@ trait ChoosesMode { ) eventsManager.processEvent(modeChoiceEvent) - data.personData.currentTourMode match { + data.personData.currentTripMode match { case Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION) => scheduler ! CompletionNotice( triggerId, @@ -1533,8 +1463,8 @@ trait ChoosesMode { goto(WaitingForDeparture) using data.personData.copy( currentTrip = Some(chosenTrip), restOfCurrentTrip = chosenTrip.legs.toList, - currentTourMode = data.personData.currentTourMode - .orElse(Some(chosenTrip.tripClassifier)), + currentTourMode = data.personData.currentTourMode.orElse(Some(chosenTrip.tripClassifier)), + currentTripMode = data.personData.currentTripMode.orElse(Some(chosenTrip.tripClassifier)), currentTourPersonalVehicle = if (isCurrentPersonalVehicleVoided) vehiclesUsed.headOption.filter(mustBeDrivenHome).map(_.id) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala index 6ed9d63cb33..da9c4b84921 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala @@ -222,7 +222,7 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon case Event( TriggerWithId(EndLegTrigger(tick), triggerId), LiterallyDrivingData(data: BasePersonData, _, _) - ) if data.currentTourMode.contains(HOV2_TELEPORTATION) || data.currentTourMode.contains(HOV3_TELEPORTATION) => + ) if data.currentTripMode.contains(HOV2_TELEPORTATION) || data.currentTourMode.contains(HOV3_TELEPORTATION) => updateLatestObservedTick(tick) val dataForNextLegOrActivity: BasePersonData = data.copy( @@ -324,7 +324,6 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon tollsAccumulated += tollOnCurrentLeg val riders = data.passengerSchedule.schedule(currentLeg).riders.toIndexedSeq.map(_.personId) val numberOfPassengers: Int = calculateNumberOfPassengersBasedOnCurrentTourMode(data, currentLeg, riders) - val currentTourMode: Option[String] = getCurrentTourMode(data) val pte = PathTraversalEvent( tick, currentVehicleUnderControl, @@ -332,7 +331,7 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon currentBeamVehicle.beamVehicleType, numberOfPassengers, currentLeg, - currentTourMode, + getCurrentTripMode(data), fuelConsumed.primaryFuel, fuelConsumed.secondaryFuel, currentBeamVehicle.primaryFuelLevelInJoules, @@ -482,16 +481,8 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon stay() } - private def getCurrentTourMode(data: DrivingData): Option[String] = { - data match { - case bpd: BasePersonData => - bpd.currentTourMode match { - case Some(mode: BeamMode) => Some(mode.value) - case _ => None - } - case _ => None - } - } + private def getCurrentTripMode(data: DrivingData): Option[String] = + findPersonData(data).flatMap(_.currentTripMode).map(_.value) private def calculateNumberOfPassengersBasedOnCurrentTourMode( data: DrivingData, @@ -500,7 +491,7 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon ): Int = { val numberOfPassengers = data match { case bpd: BasePersonData => - (bpd.currentTourMode, currentLeg.mode) match { + (bpd.currentTripMode, currentLeg.mode) match { // can't directly check HOV2/3 because the equals in BeamMode is overridden case (Some(mode @ BeamMode.CAR), BeamMode.CAR) if mode.value == BeamMode.CAR_HOV2.value => riders.size + 1 case (Some(mode @ BeamMode.CAR), BeamMode.CAR) if mode.value == BeamMode.CAR_HOV3.value => riders.size + 2 @@ -540,7 +531,6 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon tollsAccumulated += tollOnCurrentLeg val numberOfPassengers: Int = calculateNumberOfPassengersBasedOnCurrentTourMode(data, partiallyCompletedBeamLeg, riders) - val currentTourMode: Option[String] = getCurrentTourMode(data) val pte = PathTraversalEvent( updatedStopTick, currentVehicleUnderControl, @@ -548,7 +538,7 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon currentBeamVehicle.beamVehicleType, numberOfPassengers, partiallyCompletedBeamLeg, - currentTourMode, + getCurrentTripMode(data), fuelConsumed.primaryFuel, fuelConsumed.secondaryFuel, currentBeamVehicle.primaryFuelLevelInJoules, diff --git a/src/main/scala/beam/router/Modes.scala b/src/main/scala/beam/router/Modes.scala index aa73937123f..c602f6e4f08 100755 --- a/src/main/scala/beam/router/Modes.scala +++ b/src/main/scala/beam/router/Modes.scala @@ -34,6 +34,7 @@ object Modes { def isTransit: Boolean = isR5TransitMode(this) def isMassTransit: Boolean = this == SUBWAY || this == RAIL || this == FERRY || this == TRAM def isRideHail: Boolean = this == RIDE_HAIL + def isHovTeleportation: Boolean = this == HOV2_TELEPORTATION || this == HOV3_TELEPORTATION } object BeamMode extends StringEnum[BeamMode] with StringCirceEnum[BeamMode] { diff --git a/src/main/scala/beam/utils/plan/sampling/AvailableModeUtils.scala b/src/main/scala/beam/utils/plan/sampling/AvailableModeUtils.scala index 74e96c718aa..6218ba63057 100644 --- a/src/main/scala/beam/utils/plan/sampling/AvailableModeUtils.scala +++ b/src/main/scala/beam/utils/plan/sampling/AvailableModeUtils.scala @@ -60,6 +60,10 @@ object AvailableModeUtils extends LazyLogging { getPersonCustomAttributes(person).map(_.availableModes).getOrElse(Seq.empty) } + def availableModesForPerson(person: Person, excludedModes: Seq[BeamMode]): Seq[BeamMode] = { + getPersonCustomAttributes(person).map(_.availableModes).getOrElse(Seq.empty).filterNot(excludedModes.contains) + } + /** * Sets the available modes for the given person in the population * From 4d2e37552a6bc43664f08fb211176f53ada86e30 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Fri, 25 Feb 2022 12:16:05 +0300 Subject: [PATCH 2/7] Provide personal vehicle only when agent is at home --- .../beam/agentsim/agents/PersonAgent.scala | 26 +++++-------------- .../agents/household/HouseholdActor.scala | 4 +-- .../household/HouseholdFleetManager.scala | 11 +++++--- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala index 69e2072f265..5df05df283d 100755 --- a/src/main/scala/beam/agentsim/agents/PersonAgent.scala +++ b/src/main/scala/beam/agentsim/agents/PersonAgent.scala @@ -286,6 +286,8 @@ object PersonAgent { case basePersonData: BasePersonData => Some(basePersonData) case _ => None } + + def atHome(activity: Activity): Boolean = activity.getType.equalsIgnoreCase("home") } class PersonAgent( @@ -407,7 +409,7 @@ class PersonAgent( // which is used in place of our real remaining tour distance of 0.0 // which should help encourage residential end-of-day charging val tomorrowFirstLegDistance = - if (nextAct.getType.toLowerCase == "home") { + if (atHome(nextAct)) { findFirstCarLegOfTrip(personData) match { case Some(carLeg) => carLeg.beamLeg.travelPath.distanceInM @@ -497,22 +499,6 @@ class PersonAgent( val tripIndexOfElement = tour .tripIndexOfElement(nextAct) .getOrElse(throw new IllegalArgumentException(s"Element [$nextAct] not found")) - if (tripIndexOfElement == 0 && currentActivity(personData).getType != "Home") - logger.warn( - "~Activity {}, idx {}, id {}, next {}", - currentActivity(personData).getType, - tripIndexOfElement, - id, - nextAct.getType - ) - if (tripIndexOfElement == lastTripIndex && nextAct.getType != "Home") - logger.warn( - "Cur ~Activity {}, idx {}, id {}, next {}", - currentActivity(personData).getType, - tripIndexOfElement, - id, - nextAct.getType - ) (tripIndexOfElement, lastTripIndex) } @@ -1353,7 +1339,7 @@ class PersonAgent( currentTrip = None, restOfCurrentTrip = List(), currentTourPersonalVehicle = None, - currentTourMode = if (activity.getType.equals("Home")) None else data.currentTourMode, + currentTourMode = if (atHome(activity)) None else data.currentTourMode, currentTripMode = None, hasDeparted = false ) @@ -1443,7 +1429,7 @@ class PersonAgent( currentTourPersonalVehicle = currentTourPersonalVehicle match { case Some(personalVehId) => val personalVeh = beamVehicles(personalVehId).asInstanceOf[ActualVehicle].vehicle - if (activity.getType.equals("Home")) { + if (atHome(activity)) { potentiallyChargingBeamVehicles.put(personalVeh.id, beamVehicles(personalVeh.id)) beamVehicles -= personalVeh.id personalVeh.getManager.get ! ReleaseVehicle(personalVeh, triggerId) @@ -1454,7 +1440,7 @@ class PersonAgent( case None => None }, - currentTourMode = if (activity.getType.equals("Home")) None else currentTourMode, + currentTourMode = if (atHome(activity)) None else currentTourMode, currentTripMode = None, hasDeparted = false ) diff --git a/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala b/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala index 1c25958ce19..1af1da550b8 100755 --- a/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala +++ b/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala @@ -197,8 +197,8 @@ object HouseholdActor { val homeCoordFromPlans = household.members .flatMap(person => person.getSelectedPlan.getPlanElements.asScala.flatMap { - case act: Activity if act.getType == "Home" => Some(act.getCoord) - case _ => None + case act: Activity if PersonAgent.atHome(act) => Some(act.getCoord) + case _ => None } ) .headOption diff --git a/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala b/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala index ebd7062290b..a0caffb9bad 100644 --- a/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala +++ b/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala @@ -7,6 +7,7 @@ import akka.util.Timeout import beam.agentsim.Resource.NotifyVehicleIdle import beam.agentsim.agents.BeamAgent.Finish import beam.agentsim.agents.InitializeTrigger +import beam.agentsim.agents.PersonAgent.atHome import beam.agentsim.agents.household.HouseholdActor._ import beam.agentsim.agents.household.HouseholdFleetManager.ResolvedParkingResponses import beam.agentsim.agents.modalbehaviors.DrivesVehicle.ActualVehicle @@ -104,7 +105,7 @@ class HouseholdFleetManager( case GetVehicleTypes(triggerId) => sender() ! VehicleTypesResponse(vehicles.values.map(_.beamVehicleType).toSet, triggerId) - case MobilityStatusInquiry(personId, whenWhere, _, requireVehicleCategoryAvailable, triggerId) => + case MobilityStatusInquiry(personId, whenWhere, originActivity, requireVehicleCategoryAvailable, triggerId) => { for { neededVehicleCategory <- requireVehicleCategoryAvailable @@ -157,13 +158,15 @@ class HouseholdFleetManager( } }.getOrElse { availableVehicles = availableVehicles match { - case firstVehicle :: rest => + //in case of replanning because of TRANSIT failure WALK_TRANSIT is used + //but we may want to introduce maxWalkingDistance and check that the agent is close enough to the vehicle + case firstVehicle :: rest if atHome(originActivity) => logger.debug("Vehicle {} is now taken", firstVehicle.id) firstVehicle.becomeDriver(sender) sender() ! MobilityStatusResponse(Vector(ActualVehicle(firstVehicle)), triggerId) rest - case Nil => - logger.debug(s"Not returning vehicle because no default is defined") + case _ => + logger.debug(s"Not returning vehicle because no default is defined or agent is not at home") sender() ! MobilityStatusResponse(Vector(), triggerId) Nil } From a58eef515fc6fb879767e45efd2305d7c72be42b Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Wed, 23 Mar 2022 10:47:49 +0300 Subject: [PATCH 3/7] Introduced personalVehicleModes --- .../agentsim/agents/modalbehaviors/ChoosesMode.scala | 9 ++++----- src/main/scala/beam/router/Modes.scala | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index 335ad00111f..deae736128a 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -133,8 +133,7 @@ trait ChoosesMode { // If I am already on a tour in a vehicle, only that vehicle is available to me case data: ChoosesModeData if data.personData.currentTourPersonalVehicle.isDefined && ( - data.personData.currentTourMode.exists(mode => mode == CAR || mode == BIKE) - || data.personData.currentTourMode.exists(mode => mode == DRIVE_TRANSIT || mode == BIKE_TRANSIT) + data.personData.currentTourMode.exists(mode => Modes.isPersonalVehicleMode(mode)) && isLastTripWithinTour(data.personData, nextActivity(data.personData).get) ) => self ! MobilityStatusResponse( @@ -153,7 +152,7 @@ trait ChoosesMode { _, _, _, - plansModeOption @ (None | Some(CAR | BIKE | DRIVE_TRANSIT | BIKE_TRANSIT)), + plansModeOption, _, _, _, @@ -186,7 +185,7 @@ trait ChoosesMode { _, _, _ - ) => + ) if plansModeOption.forall(Modes.isPersonalVehicleMode) => implicit val executionContext: ExecutionContext = context.system.dispatcher plansModeOption match { case Some(CAR | DRIVE_TRANSIT) => @@ -1035,7 +1034,7 @@ trait ChoosesMode { ): Seq[EmbodiedBeamTrip] = { itineraries.map { itin => itin.tripClassifier match { - case CAR | DRIVE_TRANSIT | BIKE_TRANSIT | BIKE => + case mode if Modes.isPersonalVehicleMode(mode) => // find parking legs (the subsequent leg of the same vehicle) val parkingLegs = itin.legs.zip(itin.legs.tail).collect { case (leg1, leg2) if leg1.beamVehicleId == leg2.beamVehicleId && legVehicleHasParkingBehavior(leg2) => leg2 diff --git a/src/main/scala/beam/router/Modes.scala b/src/main/scala/beam/router/Modes.scala index c602f6e4f08..c610f65a285 100755 --- a/src/main/scala/beam/router/Modes.scala +++ b/src/main/scala/beam/router/Modes.scala @@ -125,6 +125,8 @@ object Modes { val chainBasedModes = Seq(CAR, BIKE) + val personalVehicleModes = Seq(CAR, BIKE, DRIVE_TRANSIT, BIKE_TRANSIT) + val transitModes = Seq(BUS, FUNICULAR, GONDOLA, CABLE_CAR, FERRY, TRAM, TRANSIT, RAIL, SUBWAY) @@ -162,6 +164,8 @@ object Modes { def isChainBasedMode(beamMode: BeamMode): Boolean = BeamMode.chainBasedModes.contains(beamMode) + def isPersonalVehicleMode(beamMode: BeamMode): Boolean = BeamMode.personalVehicleModes.contains(beamMode) + implicit def beamMode2R5Mode(beamMode: BeamMode): Either[LegMode, TransitModes] = beamMode.r5Mode.get From 93566b68e3a94f942ddd1e9b278f9dd8a3e0867d Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Wed, 23 Mar 2022 15:45:23 +0300 Subject: [PATCH 4/7] Better handling of situation when we do the second mode choice --- .../beam/agentsim/agents/modalbehaviors/ChoosesMode.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index deae736128a..e9fa0d32ec1 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -256,7 +256,8 @@ trait ChoosesMode { val personData = choosesModeData.personData val nextAct = nextActivity(personData).get // Make sure the current mode is allowable - val correctedCurrentTripMode = correctCurrentTripModeAccordingToRules(personData, nextAct, availableModes) + val correctedCurrentTripMode = + correctCurrentTripModeAccordingToRules(personData.currentTripMode, personData, nextAct, availableModes) val bodyStreetVehicle = createBodyStreetVehicle(currentPersonLocation) val departTime = _currentTick.get @@ -723,13 +724,14 @@ trait ChoosesMode { } using completeChoiceIfReady) private def correctCurrentTripModeAccordingToRules( + currentTripMode: Option[BeamMode], personData: BasePersonData, nextAct: Activity, availableModes: Seq[BeamMode] ): Option[BeamMode] = { val replanningIsAvailable = personData.numberOfReplanningAttempts < beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.maximumNumberOfReplanningAttempts - (personData.currentTripMode, personData.currentTourMode) match { + (currentTripMode, personData.currentTourMode) match { case (_, Some(CAR | BIKE)) if personData.currentTourPersonalVehicle.isDefined => personData.currentTourMode case (_, Some(DRIVE_TRANSIT | BIKE_TRANSIT)) if personData.currentTourPersonalVehicle.isDefined && isLastTripWithinTour(personData, nextAct) => @@ -1261,7 +1263,7 @@ trait ChoosesMode { } case Some(_) => val correctedTripMode = - correctCurrentTripModeAccordingToRules(personData, nextAct, availableModesForTrips) + correctCurrentTripModeAccordingToRules(None, personData, nextAct, availableModesForTrips) if (correctedTripMode != personData.currentTripMode) { //give another chance to make a choice without predefined mode gotoChoosingModeWithoutPredefinedMode(choosesModeData) From 691456f648c493cd5092f41159b38611bc29d3a6 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Wed, 23 Mar 2022 16:47:08 +0300 Subject: [PATCH 5/7] Using currentTripMode to request available vehicles --- .../agents/modalbehaviors/ChoosesMode.scala | 55 +++---------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index e9fa0d32ec1..90ba006b69e 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -146,67 +146,28 @@ trait ChoosesMode { val vehicles = Vector(ActualVehicle(teleportationVehicle)) self ! MobilityStatusResponse(vehicles, getCurrentTriggerIdOrGenerate) // Only need to get available street vehicles if our mode requires such a vehicle - case ChoosesModeData( - BasePersonData( - currentActivityIndex, - _, - _, - _, - plansModeOption, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _ - ), - currentLocation, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _ - ) if plansModeOption.forall(Modes.isPersonalVehicleMode) => + case data: ChoosesModeData if data.personData.currentTripMode.forall(Modes.isPersonalVehicleMode) => implicit val executionContext: ExecutionContext = context.system.dispatcher - plansModeOption match { + data.personData.currentTripMode match { case Some(CAR | DRIVE_TRANSIT) => requestAvailableVehicles( vehicleFleets, - currentLocation, - _experiencedBeamPlan.activities(currentActivityIndex), + data.currentLocation, + _experiencedBeamPlan.activities(data.personData.currentActivityIndex), Some(VehicleCategory.Car) ) pipeTo self case Some(BIKE | BIKE_TRANSIT) => requestAvailableVehicles( vehicleFleets, - currentLocation, - _experiencedBeamPlan.activities(currentActivityIndex), + data.currentLocation, + _experiencedBeamPlan.activities(data.personData.currentActivityIndex), Some(VehicleCategory.Bike) ) pipeTo self case _ => requestAvailableVehicles( vehicleFleets, - currentLocation, - _experiencedBeamPlan.activities(currentActivityIndex) + data.currentLocation, + _experiencedBeamPlan.activities(data.personData.currentActivityIndex) ) pipeTo self } From 6d063a9fe572f5cbce7fd668f5f55c98eb28a8ed Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Thu, 24 Mar 2022 12:40:36 +0300 Subject: [PATCH 6/7] Using correctedCurrentTripMode when getting available vehicles. Correct trip mode for teleport modes. --- .../agents/modalbehaviors/ChoosesMode.scala | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index 90ba006b69e..cb492f44d8c 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -129,6 +129,18 @@ trait ChoosesMode { def bodyVehiclePersonId: PersonIdWithActorRef = PersonIdWithActorRef(id, self) onTransition { case _ -> ChoosingMode => + val correctedCurrentTripMode = nextStateData match { + case choosesModeData: ChoosesModeData => + val availableModes: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) + val nextAct = nextActivity(choosesModeData.personData).get + correctCurrentTripModeAccordingToRules( + choosesModeData.personData.currentTripMode, + choosesModeData.personData, + nextAct, + availableModes + ) + case _ => None + } nextStateData match { // If I am already on a tour in a vehicle, only that vehicle is available to me case data: ChoosesModeData @@ -141,14 +153,14 @@ trait ChoosesMode { getCurrentTriggerIdOrGenerate ) // Create teleportation vehicle if we are told to use teleportation - case data: ChoosesModeData if data.personData.currentTripMode.exists(_.isHovTeleportation) => + case data: ChoosesModeData if correctedCurrentTripMode.exists(_.isHovTeleportation) => val teleportationVehicle = createSharedTeleportationVehicle(data.currentLocation) val vehicles = Vector(ActualVehicle(teleportationVehicle)) self ! MobilityStatusResponse(vehicles, getCurrentTriggerIdOrGenerate) // Only need to get available street vehicles if our mode requires such a vehicle - case data: ChoosesModeData if data.personData.currentTripMode.forall(Modes.isPersonalVehicleMode) => + case data: ChoosesModeData if correctedCurrentTripMode.forall(Modes.isPersonalVehicleMode) => implicit val executionContext: ExecutionContext = context.system.dispatcher - data.personData.currentTripMode match { + correctedCurrentTripMode match { case Some(CAR | DRIVE_TRANSIT) => requestAvailableVehicles( vehicleFleets, @@ -700,6 +712,9 @@ trait ChoosesMode { case (Some(mode @ (HOV2_TELEPORTATION | HOV3_TELEPORTATION)), _) if availableModes.contains(CAR) && replanningIsAvailable => Some(mode) + case (Some(CAR_HOV2 | CAR_HOV3), Some(tourMode @ (HOV2_TELEPORTATION | HOV3_TELEPORTATION))) + if availableModes.contains(CAR) && replanningIsAvailable => + Some(tourMode) case (Some(mode), _) if availableModes.contains(mode) && replanningIsAvailable => Some(mode) case (Some(mode), _) if availableModes.contains(mode) => Some(WALK) case (None, _) if !replanningIsAvailable => Some(WALK) @@ -1415,6 +1430,7 @@ trait ChoosesMode { goto(Teleporting) using data.personData.copy( currentTrip = Some(chosenTrip), + currentTourMode = data.personData.currentTripMode, restOfCurrentTrip = List() ) From de1684a4dd732f7ca0e8aad7598eabad785c092e Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Fri, 25 Mar 2022 11:45:57 +0300 Subject: [PATCH 7/7] Fixed not using current tour personal vehicle. Fixed trip/tour field. --- .../agents/modalbehaviors/ChoosesMode.scala | 37 +++++++++---------- .../agents/modalbehaviors/DrivesVehicle.scala | 2 +- .../beam/integration/SingleModeSpec.scala | 8 ++-- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index cb492f44d8c..9307b6537ab 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -129,25 +129,24 @@ trait ChoosesMode { def bodyVehiclePersonId: PersonIdWithActorRef = PersonIdWithActorRef(id, self) onTransition { case _ -> ChoosingMode => - val correctedCurrentTripMode = nextStateData match { - case choosesModeData: ChoosesModeData => - val availableModes: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) - val nextAct = nextActivity(choosesModeData.personData).get - correctCurrentTripModeAccordingToRules( - choosesModeData.personData.currentTripMode, - choosesModeData.personData, - nextAct, - availableModes - ) - case _ => None - } + val choosesModeData: ChoosesModeData = nextStateData.asInstanceOf[ChoosesModeData] + val availableModes: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) + val nextAct = nextActivity(choosesModeData.personData).get + val correctedCurrentTripMode = correctCurrentTripModeAccordingToRules( + choosesModeData.personData.currentTripMode, + choosesModeData.personData, + nextAct, + availableModes + ) nextStateData match { // If I am already on a tour in a vehicle, only that vehicle is available to me case data: ChoosesModeData - if data.personData.currentTourPersonalVehicle.isDefined && ( - data.personData.currentTourMode.exists(mode => Modes.isPersonalVehicleMode(mode)) - && isLastTripWithinTour(data.personData, nextActivity(data.personData).get) - ) => + if data.personData.currentTourPersonalVehicle.isDefined && + ( + data.personData.currentTourMode.exists(mode => mode == CAR || mode == BIKE) || + data.personData.currentTourMode.exists(mode => mode == DRIVE_TRANSIT || mode == BIKE_TRANSIT) + && isLastTripWithinTour(data.personData, nextAct) + ) => self ! MobilityStatusResponse( Vector(beamVehicles(data.personData.currentTourPersonalVehicle.get)), getCurrentTriggerIdOrGenerate @@ -165,21 +164,21 @@ trait ChoosesMode { requestAvailableVehicles( vehicleFleets, data.currentLocation, - _experiencedBeamPlan.activities(data.personData.currentActivityIndex), + currentActivity(data.personData), Some(VehicleCategory.Car) ) pipeTo self case Some(BIKE | BIKE_TRANSIT) => requestAvailableVehicles( vehicleFleets, data.currentLocation, - _experiencedBeamPlan.activities(data.personData.currentActivityIndex), + currentActivity(data.personData), Some(VehicleCategory.Bike) ) pipeTo self case _ => requestAvailableVehicles( vehicleFleets, data.currentLocation, - _experiencedBeamPlan.activities(data.personData.currentActivityIndex) + currentActivity(data.personData) ) pipeTo self } diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala index dbcb1c91bce..dc19f806e0b 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala @@ -222,7 +222,7 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon case Event( TriggerWithId(EndLegTrigger(tick), triggerId), LiterallyDrivingData(data: BasePersonData, _, _) - ) if data.currentTripMode.contains(HOV2_TELEPORTATION) || data.currentTourMode.contains(HOV3_TELEPORTATION) => + ) if data.currentTripMode.contains(HOV2_TELEPORTATION) || data.currentTripMode.contains(HOV3_TELEPORTATION) => updateLatestObservedTick(tick) val dataForNextLegOrActivity: BasePersonData = data.copy( diff --git a/src/test/scala/beam/integration/SingleModeSpec.scala b/src/test/scala/beam/integration/SingleModeSpec.scala index f29744f7c47..0764c304fbe 100755 --- a/src/test/scala/beam/integration/SingleModeSpec.scala +++ b/src/test/scala/beam/integration/SingleModeSpec.scala @@ -11,7 +11,7 @@ import beam.router.RouteHistory import beam.sflight.RouterForTest import beam.sim.common.GeoUtilsImpl import beam.sim.{BeamHelper, BeamMobsim, RideHailFleetInitializerProvider} -import beam.utils.SimRunnerForTest +import beam.utils.{MathUtils, SimRunnerForTest} import beam.utils.TestConfigUtils.testConfig import com.typesafe.config.ConfigFactory import org.matsim.api.core.v01.events.{ActivityEndEvent, Event, PersonDepartureEvent, PersonEntersVehicleEvent} @@ -269,8 +269,10 @@ class SingleModeSpec val personDepartureEvents = events.collect { case event: PersonDepartureEvent => event } personDepartureEvents should not be empty val regularPersonEvents = filterOutProfessionalDriversAndCavs(personDepartureEvents) - val (drive, others) = regularPersonEvents.map(_.getLegMode).partition(_ == "car") - others.size should be < (0.02 * drive.size).toInt + val othersCount = regularPersonEvents.count(_.getLegMode != "car") + withClue("Majority of agents should use cars. Other modes take place when no car available.") { + othersCount should be < MathUtils.doubleToInt(0.02 * regularPersonEvents.size) + } } }