From c40970fa66a58d6b28bd122a0433f1f715235e4f Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Fri, 25 Feb 2022 11:13:54 +0300 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 07/10] 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) + } } } From 8a0dacda2827a65b441a9159cb3f324141fa971a Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Mon, 28 Mar 2022 18:49:17 +0300 Subject: [PATCH 08/10] Keep track of current tour/trip mode in _experiencedBeamPlan. Moved method atHome to BeamPlan. --- .../beam/agentsim/agents/PersonAgent.scala | 48 +++++++---------- .../agents/household/HouseholdActor.scala | 5 +- .../household/HouseholdFleetManager.scala | 2 +- .../agents/modalbehaviors/ChoosesMode.scala | 53 ++++++++++++++----- .../agentsim/agents/planning/BeamPlan.scala | 39 ++++++++++---- .../agentsim/agents/planning/Strategy.scala | 4 +- .../beam/agentsim/planning/BeamPlanSpec.scala | 26 ++++----- .../beam/integration/SingleModeSpec.scala | 10 ++-- 8 files changed, 117 insertions(+), 70 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala index 1df2f842243..3c612bc427b 100755 --- a/src/main/scala/beam/agentsim/agents/PersonAgent.scala +++ b/src/main/scala/beam/agentsim/agents/PersonAgent.scala @@ -17,6 +17,8 @@ import beam.agentsim.agents.parking.ChoosesParking.{ ChoosingParkingSpot, ReleasingParkingSpot } +import beam.agentsim.agents.planning.BeamPlan.atHome +import beam.agentsim.agents.planning.Strategy.ModeChoiceStrategy import beam.agentsim.agents.planning.{BeamPlan, Tour} import beam.agentsim.agents.ridehail.RideHailManager.TravelProposal import beam.agentsim.agents.ridehail._ @@ -188,7 +190,6 @@ object PersonAgent { currentTrip: Option[EmbodiedBeamTrip] = None, restOfCurrentTrip: List[EmbodiedBeamLeg] = List(), currentVehicle: VehicleStack = Vector(), - currentTourMode: Option[BeamMode] = None, currentTripMode: Option[BeamMode] = None, currentTourPersonalVehicle: Option[Id[BeamVehicle]] = None, passengerSchedule: PassengerSchedule = PassengerSchedule(), @@ -286,8 +287,6 @@ object PersonAgent { case basePersonData: BasePersonData => Some(basePersonData) case _ => None } - - def atHome(activity: Activity): Boolean = activity.getType.equalsIgnoreCase("home") } class PersonAgent( @@ -604,16 +603,12 @@ class PersonAgent( case Some(nextAct) => logDebug(s"wants to go to ${nextAct.getType} @ $tick") holdTickAndTriggerId(tick, triggerId) - val indexOfNextActivity = _experiencedBeamPlan.getPlanElements.indexOf(nextAct) - val modeOfNextLeg = _experiencedBeamPlan.getPlanElements.get(indexOfNextActivity - 1) match { - case leg: Leg => BeamMode.fromString(leg.getMode) - case _ => None - } + val modeOfNextLeg = _experiencedBeamPlan.getTripStrategy[ModeChoiceStrategy](nextAct).flatMap(_.mode) val currentCoord = currentActivity(data).getCoord val nextCoord = nextActivity(data).get.getCoord goto(ChoosingMode) using ChoosesModeData( personData = data.copy( - // We do not stick to current tour mode + // We current tour mode is defined in _experiencedBeamPlan.getTourStrategy // If we have the currentTourPersonalVehicle then we should use it // use the mode of the next leg as the new trip mode. currentTripMode = modeOfNextLeg, @@ -633,7 +628,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) @@ -647,7 +642,7 @@ class PersonAgent( case Event( TriggerWithId(TeleportationEndsTrigger(tick), triggerId), - data @ BasePersonData(_, Some(currentTrip), _, _, _, maybeCurrentTripMode, _, _, _, true, _, _, _, _, _) + data @ BasePersonData(_, Some(currentTrip), _, _, maybeCurrentTripMode, _, _, _, true, _, _, _, _, _) ) => holdTickAndTriggerId(tick, triggerId) @@ -679,7 +674,7 @@ class PersonAgent( */ case Event( TriggerWithId(PersonDepartureTrigger(tick), triggerId), - data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, _, false, _, _, _, _, _) + data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, false, _, _, _, _, _) ) => endActivityAndDepart(tick, currentTrip, data) @@ -688,7 +683,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}") @@ -763,7 +758,7 @@ class PersonAgent( // TRANSIT FAILURE case Event( ReservationResponse(Left(firstErrorResponse), _), - data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _, _) + data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _) ) => logDebug(s"replanning because ${firstErrorResponse.errorCode}") @@ -848,7 +843,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)) @@ -881,7 +876,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") @@ -958,7 +953,6 @@ class PersonAgent( _, _, _, - _, currentCost, _, _, @@ -1000,9 +994,10 @@ class PersonAgent( val currentCoord = beamServices.geo.wgs2Utm(basePersonData.restOfCurrentTrip.head.beamLeg.travelPath.startPoint).loc val nextCoord = nextActivity(basePersonData).get.getCoord + // Have to give up my mode as well, perhaps there's no option left for driving. + _experiencedBeamPlan.putStrategy(currentTour(basePersonData), ModeChoiceStrategy(mode = None)) 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 @@ -1100,7 +1095,6 @@ class PersonAgent( _, _, _, - _, _ ) ) if nextLeg.asDriver => @@ -1195,7 +1189,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 @@ -1218,7 +1212,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, @@ -1229,7 +1223,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) @@ -1257,7 +1251,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 @@ -1281,7 +1275,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, @@ -1301,7 +1295,6 @@ class PersonAgent( _, _, _, - _, Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION), _, _, @@ -1342,7 +1335,6 @@ class PersonAgent( currentTrip = None, restOfCurrentTrip = List(), currentTourPersonalVehicle = None, - currentTourMode = if (atHome(activity)) None else data.currentTourMode, currentTripMode = None, hasDeparted = false ) @@ -1360,7 +1352,6 @@ class PersonAgent( Some(currentTrip), _, _, - currentTourMode, _, currentTourPersonalVehicle, _, @@ -1443,7 +1434,6 @@ class PersonAgent( case None => None }, - currentTourMode = if (atHome(activity)) None else currentTourMode, currentTripMode = None, hasDeparted = false ) @@ -1592,7 +1582,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/household/HouseholdActor.scala b/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala index 1af1da550b8..b8fdf8dc9b0 100755 --- a/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala +++ b/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala @@ -11,6 +11,7 @@ import beam.agentsim.agents.modalbehaviors.ChoosesMode.{CavTripLegsRequest, CavT import beam.agentsim.agents.modalbehaviors.DrivesVehicle.VehicleOrToken import beam.agentsim.agents.modalbehaviors.ModeChoiceCalculator import beam.agentsim.agents.planning.BeamPlan +import beam.agentsim.agents.planning.BeamPlan.atHome import beam.agentsim.agents.ridehail.RideHailAgent.{ ModifyPassengerSchedule, ModifyPassengerScheduleAck, @@ -197,8 +198,8 @@ object HouseholdActor { val homeCoordFromPlans = household.members .flatMap(person => person.getSelectedPlan.getPlanElements.asScala.flatMap { - case act: Activity if PersonAgent.atHome(act) => Some(act.getCoord) - case _ => None + case act: Activity if 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 a0caffb9bad..1f39666be49 100644 --- a/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala +++ b/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala @@ -7,10 +7,10 @@ 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 +import beam.agentsim.agents.planning.BeamPlan.atHome import beam.agentsim.agents.vehicles.BeamVehicle import beam.agentsim.events.SpaceTime import beam.agentsim.infrastructure.{ParkingInquiry, ParkingInquiryResponse} diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index 9307b6537ab..5e10e467afc 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -8,6 +8,7 @@ import beam.agentsim.agents._ import beam.agentsim.agents.household.HouseholdActor.{MobilityStatusInquiry, MobilityStatusResponse, ReleaseVehicle} import beam.agentsim.agents.modalbehaviors.ChoosesMode._ import beam.agentsim.agents.modalbehaviors.DrivesVehicle.{ActualVehicle, Token, VehicleOrToken} +import beam.agentsim.agents.planning.Strategy.ModeChoiceStrategy import beam.agentsim.agents.ridehail.{RideHailInquiry, RideHailRequest, RideHailResponse} import beam.agentsim.agents.vehicles.AccessErrorCodes.RideHailNotRequestedError import beam.agentsim.agents.vehicles.VehicleCategory.VehicleCategory @@ -132,8 +133,10 @@ trait ChoosesMode { val choosesModeData: ChoosesModeData = nextStateData.asInstanceOf[ChoosesModeData] val availableModes: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) val nextAct = nextActivity(choosesModeData.personData).get + val currentTourMode = _experiencedBeamPlan.getTourStrategy[ModeChoiceStrategy](nextAct).flatMap(_.mode) val correctedCurrentTripMode = correctCurrentTripModeAccordingToRules( choosesModeData.personData.currentTripMode, + currentTourMode, choosesModeData.personData, nextAct, availableModes @@ -143,8 +146,8 @@ trait ChoosesMode { 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) + currentTourMode.exists(mode => mode == CAR || mode == BIKE) || + currentTourMode.exists(mode => mode == DRIVE_TRANSIT || mode == BIKE_TRANSIT) && isLastTripWithinTour(data.personData, nextAct) ) => self ! MobilityStatusResponse( @@ -227,9 +230,15 @@ trait ChoosesMode { val availableModes: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) val personData = choosesModeData.personData val nextAct = nextActivity(personData).get + val currentTourMode = _experiencedBeamPlan.getTourStrategy[ModeChoiceStrategy](nextAct).flatMap(_.mode) // Make sure the current mode is allowable - val correctedCurrentTripMode = - correctCurrentTripModeAccordingToRules(personData.currentTripMode, personData, nextAct, availableModes) + val correctedCurrentTripMode = correctCurrentTripModeAccordingToRules( + personData.currentTripMode, + currentTourMode, + personData, + nextAct, + availableModes + ) val bodyStreetVehicle = createBodyStreetVehicle(currentPersonLocation) val departTime = _currentTick.get @@ -697,17 +706,18 @@ trait ChoosesMode { private def correctCurrentTripModeAccordingToRules( currentTripMode: Option[BeamMode], + currentTourMode: Option[BeamMode], personData: BasePersonData, nextAct: Activity, availableModes: Seq[BeamMode] ): Option[BeamMode] = { val replanningIsAvailable = personData.numberOfReplanningAttempts < beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.maximumNumberOfReplanningAttempts - (currentTripMode, personData.currentTourMode) match { - case (_, Some(CAR | BIKE)) if personData.currentTourPersonalVehicle.isDefined => personData.currentTourMode + (currentTripMode, currentTourMode) match { + case (_, Some(CAR | BIKE)) if personData.currentTourPersonalVehicle.isDefined => currentTourMode case (_, Some(DRIVE_TRANSIT | BIKE_TRANSIT)) if personData.currentTourPersonalVehicle.isDefined && isLastTripWithinTour(personData, nextAct) => - personData.currentTourMode + currentTourMode case (Some(mode @ (HOV2_TELEPORTATION | HOV3_TELEPORTATION)), _) if availableModes.contains(CAR) && replanningIsAvailable => Some(mode) @@ -1237,8 +1247,15 @@ trait ChoosesMode { gotoFinishingModeChoice(bushwhackingTrip) } case Some(_) => + val currentTourMode = _experiencedBeamPlan.getTourStrategy[ModeChoiceStrategy](nextAct).flatMap(_.mode) val correctedTripMode = - correctCurrentTripModeAccordingToRules(None, personData, nextAct, availableModesForTrips) + correctCurrentTripModeAccordingToRules( + None, + currentTourMode, + personData, + nextAct, + availableModesForTrips + ) if (correctedTripMode != personData.currentTripMode) { //give another chance to make a choice without predefined mode gotoChoosingModeWithoutPredefinedMode(choosesModeData) @@ -1397,6 +1414,10 @@ trait ChoosesMode { _experiencedBeamPlan.activities(data.personData.currentActivityIndex).getAttributes.getAttribute("trip_id") ).getOrElse("").toString + val nextAct = nextActivity(data.personData).get + val currentTour = _experiencedBeamPlan.getTourContaining(nextAct) + val currentTrip = _experiencedBeamPlan.getTripContaining(nextAct) + val modeChoiceEvent = new ModeChoiceEvent( tick, id, @@ -1407,10 +1428,10 @@ trait ChoosesMode { data.availableAlternatives.get, data.availablePersonalStreetVehicles.nonEmpty, chosenTrip.legs.view.map(_.beamLeg.travelPath.distanceInM).sum, - _experiencedBeamPlan.tourIndexOfElement(nextActivity(data.personData).get), + _experiencedBeamPlan.tourIndexOfElement(nextAct), chosenTrip, _experiencedBeamPlan.activities(data.personData.currentActivityIndex).getType, - nextActivity(data.personData).get.getType, + nextAct.getType, tripId ) eventsManager.processEvent(modeChoiceEvent) @@ -1427,9 +1448,11 @@ trait ChoosesMode { ) ) + val strategy = ModeChoiceStrategy(data.personData.currentTripMode) + _experiencedBeamPlan.putStrategy(currentTrip, strategy) + _experiencedBeamPlan.putStrategy(currentTour, strategy) goto(Teleporting) using data.personData.copy( currentTrip = Some(chosenTrip), - currentTourMode = data.personData.currentTripMode, restOfCurrentTrip = List() ) @@ -1460,11 +1483,15 @@ trait ChoosesMode { ) ) ) + val chosenTripMode = Some(chosenTrip.tripClassifier) + val currentTourMode = _experiencedBeamPlan.getTourStrategy[ModeChoiceStrategy](nextAct).flatMap(_.mode) + val currentTourModeToStore = currentTourMode.orElse(chosenTripMode) + _experiencedBeamPlan.putStrategy(currentTrip, ModeChoiceStrategy(chosenTripMode)) + _experiencedBeamPlan.putStrategy(currentTour, ModeChoiceStrategy(currentTourModeToStore)) goto(WaitingForDeparture) using data.personData.copy( currentTrip = Some(chosenTrip), restOfCurrentTrip = chosenTrip.legs.toList, - currentTourMode = data.personData.currentTourMode.orElse(Some(chosenTrip.tripClassifier)), - currentTripMode = data.personData.currentTripMode.orElse(Some(chosenTrip.tripClassifier)), + currentTripMode = data.personData.currentTripMode.orElse(chosenTripMode), currentTourPersonalVehicle = if (isCurrentPersonalVehicleVoided) vehiclesUsed.headOption.filter(mustBeDrivenHome).map(_.id) diff --git a/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala b/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala index 26fb0da164b..ccb3719cd2e 100755 --- a/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala +++ b/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala @@ -1,7 +1,8 @@ package beam.agentsim.agents.planning -import java.{lang, util} +import beam.agentsim.agents.planning.BeamPlan.atHome +import java.{lang, util} import beam.agentsim.agents.planning.Strategy.{ModeChoiceStrategy, Strategy} import beam.router.Modes.BeamMode import org.matsim.api.core.v01.population._ @@ -10,6 +11,7 @@ import org.matsim.utils.objectattributes.attributable.Attributes import scala.collection.JavaConverters._ import scala.collection.mutable +import scala.reflect.ClassTag /** * BeamPlan @@ -85,6 +87,8 @@ object BeamPlan { newPlan.setScore(plan.getScore) newPlan } + + def atHome(activity: Activity): Boolean = activity.getType.equalsIgnoreCase("home") } class BeamPlan extends Plan { @@ -115,7 +119,7 @@ class BeamPlan extends Plan { case activity: Activity => val nextTrip = Trip(activity, nextLeg, nextTour) nextTour.addTrip(nextTrip) - if (activity.getType.equalsIgnoreCase("home")) { + if (atHome(activity)) { tours = tours :+ nextTour nextTour = new Tour } @@ -145,14 +149,14 @@ class BeamPlan extends Plan { } def putStrategy(planElement: PlanElement, strategy: Strategy): Unit = { - if (!strategies.contains(planElement)) { - strategies.put(planElement, mutable.Map[Class[_ <: Strategy], Strategy]()) - } - strategies(planElement).put(strategy.getClass, strategy) + val planElementMap = strategies.getOrElseUpdate(planElement, mutable.Map.empty[Class[_ <: Strategy], Strategy]) + planElementMap.put(strategy.getClass, strategy) planElement match { case tour: Tour => - tour.trips.foreach(trip => putStrategy(trip, strategy)) + strategy.tripStrategies(tour).foreach { case (trip, strategy) => + putStrategy(trip, strategy) + } case trip: Trip => putStrategy(trip.activity, strategy) trip.leg.foreach(theLeg => putStrategy(theLeg, strategy)) @@ -161,8 +165,17 @@ class BeamPlan extends Plan { } } - def getStrategy(planElement: PlanElement, forClass: Class[_ <: Strategy]): Option[Strategy] = { - strategies.getOrElse(planElement, mutable.Map()).get(forClass) + def getStrategy[T <: Strategy: ClassTag](planElement: PlanElement): Option[T] = { + val forClass: Class[T] = implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]] + strategies.getOrElse(planElement, Map.empty[Class[_ <: Strategy], Strategy]).get(forClass).asInstanceOf[Option[T]] + } + + def getTripStrategy[T <: Strategy: ClassTag](activity: Activity): Option[T] = { + getStrategy(actsLegToTrip(activity)) + } + + def getTourStrategy[T <: Strategy: ClassTag](activity: Activity): Option[T] = { + getStrategy(getTourContaining(activity)) } def isLastElementInTour(planElement: PlanElement): Boolean = { @@ -204,6 +217,14 @@ class BeamPlan extends Plan { } } + def getTourContaining(index: Int): Tour = { + getTourContaining(activities(index)) + } + + def getTripContaining(index: Int): Trip = { + getTripContaining(activities(index)) + } + ////////////////////////////////////////////////////////////////////// // Supporting legacy interface ////////////////////////////////////////////////////////////////////// diff --git a/src/main/scala/beam/agentsim/agents/planning/Strategy.scala b/src/main/scala/beam/agentsim/agents/planning/Strategy.scala index a96d7a93e48..561e72166ec 100755 --- a/src/main/scala/beam/agentsim/agents/planning/Strategy.scala +++ b/src/main/scala/beam/agentsim/agents/planning/Strategy.scala @@ -7,7 +7,9 @@ import beam.router.Modes.BeamMode */ object Strategy { - sealed trait Strategy + trait Strategy { + def tripStrategies(tour: Tour): Seq[(Trip, Strategy)] = Seq.empty + } case class ModeChoiceStrategy(mode: Option[BeamMode]) extends Strategy diff --git a/src/test/scala/beam/agentsim/planning/BeamPlanSpec.scala b/src/test/scala/beam/agentsim/planning/BeamPlanSpec.scala index f72479fafe4..b3866df31e0 100755 --- a/src/test/scala/beam/agentsim/planning/BeamPlanSpec.scala +++ b/src/test/scala/beam/agentsim/planning/BeamPlanSpec.scala @@ -52,47 +52,49 @@ class BeamPlanSpec extends AnyWordSpecLike with Matchers with BeamHelper { val beamPlan = BeamPlan(matsimPlan) val act = beamPlan.activities.head beamPlan.putStrategy(act, strat) - beamPlan.getStrategy(act, classOf[ModeChoiceStrategy]) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](act) should be(Some(strat)) } "should attach a strategy to a leg" in { val beamPlan = BeamPlan(matsimPlan) val leg = beamPlan.legs.head beamPlan.putStrategy(leg, strat) - beamPlan.getStrategy(leg, classOf[ModeChoiceStrategy]) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](leg) should be(Some(strat)) } "should attach a strategy to a trip" in { val beamPlan = BeamPlan(matsimPlan) val trip = beamPlan.trips.head beamPlan.putStrategy(trip, strat) - beamPlan.getStrategy(trip, classOf[ModeChoiceStrategy]) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](trip) should be(Some(strat)) } "should attach a strategy to a tour" in { val beamPlan = BeamPlan(matsimPlan) val tour = beamPlan.tours.head beamPlan.putStrategy(tour, strat) - beamPlan.getStrategy(tour, classOf[ModeChoiceStrategy]) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](tour) should be(Some(strat)) } "should attach a strategy to a trip and the trip's activity and leg" in { val beamPlan = BeamPlan(matsimPlan) val trip = beamPlan.trips.head beamPlan.putStrategy(trip, strat) - beamPlan.getStrategy(trip.activity, classOf[ModeChoiceStrategy]) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](trip.activity) should be(Some(strat)) trip.leg match { case Some(leg) => - beamPlan.getStrategy(leg, classOf[ModeChoiceStrategy]) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](leg) should be(Some(strat)) case None => } } - "should attach a strategy to a tour and the tour's trips, activities, and trips" in { + "should not attach a strategy to tour's trips, activities, and legs" in { val beamPlan = BeamPlan(matsimPlan) - val tour = beamPlan.tours.head - beamPlan.putStrategy(tour, strat) + val tour = beamPlan.tours(1) + val strategy = ModeChoiceStrategy(None) + beamPlan.putStrategy(tour, strategy) + beamPlan.getStrategy[ModeChoiceStrategy](tour) should be(Some(strategy)) tour.trips.foreach { trip => - beamPlan.getStrategy(trip, classOf[ModeChoiceStrategy]) should be(Some(strat)) - beamPlan.getStrategy(trip.activity, classOf[ModeChoiceStrategy]) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](trip) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](trip.activity) should be(Some(strat)) trip.leg match { case Some(leg) => - beamPlan.getStrategy(leg, classOf[ModeChoiceStrategy]) should be(Some(strat)) + beamPlan.getStrategy[ModeChoiceStrategy](leg) should be(Some(strat)) case None => } } diff --git a/src/test/scala/beam/integration/SingleModeSpec.scala b/src/test/scala/beam/integration/SingleModeSpec.scala index 0764c304fbe..43e4faafad9 100755 --- a/src/test/scala/beam/integration/SingleModeSpec.scala +++ b/src/test/scala/beam/integration/SingleModeSpec.scala @@ -154,8 +154,10 @@ class SingleModeSpec val newPlanElements = person.getSelectedPlan.getPlanElements.asScala.collect { case activity: Activity if activity.getType == "Home" => Seq(activity, scenario.getPopulation.getFactory.createLeg("drive_transit")) - case activity: Activity => Seq(activity) - case _: Leg => Nil + case activity: Activity => + Seq(activity) + Seq(activity, scenario.getPopulation.getFactory.createLeg("")) + case _: Leg => Nil }.flatten if (newPlanElements.last.isInstanceOf[Leg]) { newPlanElements.remove(newPlanElements.size - 1) @@ -205,7 +207,9 @@ class SingleModeSpec val regularPersonEvents = filterOutProfessionalDriversAndCavs(personDepartureEvents) val (driveTransit, others) = regularPersonEvents.map(_.getLegMode).partition(_ == "drive_transit") //router gives too little 'drive transit' trips, most of the persons chooses 'car' in this case - others.count(_ == "walk_transit") should be < (0.2 * driveTransit.size).toInt + withClue("When transit is available majority of agents should use drive_transit") { + others.count(_ == "walk_transit") should be < MathUtils.doubleToInt(0.2 * driveTransit.size) + } val eventsByPerson = events.groupBy(_.getAttributes.get("person")) From eb4fafbd11144b6d48edb4d82f600e028439247c Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Mon, 28 Mar 2022 18:50:58 +0300 Subject: [PATCH 09/10] Removed unused parameter from an example in inputs.rst --- docs/inputs.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/inputs.rst b/docs/inputs.rst index bff0f8f1ce5..da66699bfa7 100755 --- a/docs/inputs.rst +++ b/docs/inputs.rst @@ -236,7 +236,6 @@ beam.routing { # Departure window in min departureWindow = "double | 15.0" numberOfSamples = "int | 1" - osmFile = ${beam.routing.r5.directory}"/beamville.osm.pbf" osmMapdbFile = ${beam.routing.r5.directory}"/osm.mapdb" mNetBuilder.fromCRS = "EPSG:4326" # WGS84 mNetBuilder.toCRS = "EPSG:26910" # UTM10N From 103a0c3e3e2ab6cceb36114c44f08ffd09610d9c Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Tue, 29 Mar 2022 17:22:49 +0300 Subject: [PATCH 10/10] Moved trip mode calculation to ModeChoiceStrategy --- .../agents/modalbehaviors/ChoosesMode.scala | 36 ++++------------- .../agentsim/agents/planning/BeamPlan.scala | 2 +- .../agentsim/agents/planning/Strategy.scala | 40 ++++++++++++++++++- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index 5e10e467afc..4449bf96abc 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -136,9 +136,7 @@ trait ChoosesMode { val currentTourMode = _experiencedBeamPlan.getTourStrategy[ModeChoiceStrategy](nextAct).flatMap(_.mode) val correctedCurrentTripMode = correctCurrentTripModeAccordingToRules( choosesModeData.personData.currentTripMode, - currentTourMode, choosesModeData.personData, - nextAct, availableModes ) nextStateData match { @@ -230,13 +228,10 @@ trait ChoosesMode { val availableModes: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) val personData = choosesModeData.personData val nextAct = nextActivity(personData).get - val currentTourMode = _experiencedBeamPlan.getTourStrategy[ModeChoiceStrategy](nextAct).flatMap(_.mode) // Make sure the current mode is allowable val correctedCurrentTripMode = correctCurrentTripModeAccordingToRules( personData.currentTripMode, - currentTourMode, personData, - nextAct, availableModes ) @@ -706,28 +701,19 @@ trait ChoosesMode { private def correctCurrentTripModeAccordingToRules( currentTripMode: Option[BeamMode], - currentTourMode: Option[BeamMode], personData: BasePersonData, - nextAct: Activity, availableModes: Seq[BeamMode] ): Option[BeamMode] = { val replanningIsAvailable = personData.numberOfReplanningAttempts < beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.maximumNumberOfReplanningAttempts - (currentTripMode, currentTourMode) match { - case (_, Some(CAR | BIKE)) if personData.currentTourPersonalVehicle.isDefined => currentTourMode - case (_, Some(DRIVE_TRANSIT | BIKE_TRANSIT)) - if personData.currentTourPersonalVehicle.isDefined && isLastTripWithinTour(personData, nextAct) => - currentTourMode - case (Some(mode @ (HOV2_TELEPORTATION | HOV3_TELEPORTATION)), _) + currentTripMode match { + 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) - case _ => None + 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 } } @@ -1247,15 +1233,7 @@ trait ChoosesMode { gotoFinishingModeChoice(bushwhackingTrip) } case Some(_) => - val currentTourMode = _experiencedBeamPlan.getTourStrategy[ModeChoiceStrategy](nextAct).flatMap(_.mode) - val correctedTripMode = - correctCurrentTripModeAccordingToRules( - None, - currentTourMode, - personData, - nextAct, - availableModesForTrips - ) + val correctedTripMode = correctCurrentTripModeAccordingToRules(None, personData, availableModesForTrips) if (correctedTripMode != personData.currentTripMode) { //give another chance to make a choice without predefined mode gotoChoosingModeWithoutPredefinedMode(choosesModeData) diff --git a/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala b/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala index ccb3719cd2e..6be4d59b132 100755 --- a/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala +++ b/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala @@ -154,7 +154,7 @@ class BeamPlan extends Plan { planElement match { case tour: Tour => - strategy.tripStrategies(tour).foreach { case (trip, strategy) => + strategy.tripStrategies(tour, this).foreach { case (trip, strategy) => putStrategy(trip, strategy) } case trip: Trip => diff --git a/src/main/scala/beam/agentsim/agents/planning/Strategy.scala b/src/main/scala/beam/agentsim/agents/planning/Strategy.scala index 561e72166ec..db1f589fcde 100755 --- a/src/main/scala/beam/agentsim/agents/planning/Strategy.scala +++ b/src/main/scala/beam/agentsim/agents/planning/Strategy.scala @@ -1,6 +1,7 @@ package beam.agentsim.agents.planning import beam.router.Modes.BeamMode +import beam.router.Modes.BeamMode._ /** * BEAM @@ -8,9 +9,44 @@ import beam.router.Modes.BeamMode object Strategy { trait Strategy { - def tripStrategies(tour: Tour): Seq[(Trip, Strategy)] = Seq.empty + + /** + * When a tour strategy is set this method of that strategy is called. It allows to set strategies for the trips + * that this tour consists of + * @param tour the tour that this strategy is set for + * @param beamPlan the whole beam plan + * @return trips with strategies that needs to be set + */ + def tripStrategies(tour: Tour, beamPlan: BeamPlan): Seq[(Trip, Strategy)] = Seq.empty } - case class ModeChoiceStrategy(mode: Option[BeamMode]) extends Strategy + case class ModeChoiceStrategy(mode: Option[BeamMode]) extends Strategy { + + override def tripStrategies(tour: Tour, beamPlan: BeamPlan): Seq[(Trip, Strategy)] = { + val tourStrategy = this + + mode match { + case Some(CAR | BIKE) => + tour.trips.zip(Seq.fill(tour.trips.size)(tourStrategy)) + case Some(DRIVE_TRANSIT | BIKE_TRANSIT) => + //replace both ends with DRIVE_TRANSIT or BIKE_TRANSIT + val firstTrip = tour.trips.head + val lastTrip = tour.trips.last + Seq(firstTrip -> tourStrategy, lastTrip -> tourStrategy) + case Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION) => + //we need to replace all CAR_HOV modes to this tour mode + //because these CAR_HOV modes shouldn't be used in this type of tour + tour.trips + .withFilter { trip => + beamPlan.getStrategy[ModeChoiceStrategy](trip).flatMap(_.mode) match { + case Some(CAR_HOV2 | CAR_HOV3) => true + case _ => false + } + } + .map(_ -> tourStrategy) + case _ => super.tripStrategies(tour, beamPlan) + } + } + } }