diff --git a/production/austin b/production/austin index f9bee5d3989..24906fcf1f2 160000 --- a/production/austin +++ b/production/austin @@ -1 +1 @@ -Subproject commit f9bee5d398917a25a59ea826777c6a985a9d49ab +Subproject commit 24906fcf1f20b24a6fbcbe82526652114deb29c2 diff --git a/production/seattle b/production/seattle index ad7f89b5e59..d34d4121c29 160000 --- a/production/seattle +++ b/production/seattle @@ -1 +1 @@ -Subproject commit ad7f89b5e596d79e26ff0fc8826543a6f0e61c93 +Subproject commit d34d4121c297009187493ec364146b9bbcf4fe6c diff --git a/production/sfbay b/production/sfbay index f5199990ead..9c9f3f57339 160000 --- a/production/sfbay +++ b/production/sfbay @@ -1 +1 @@ -Subproject commit f5199990eadf1a8115ac00ded3ae85bbf9fd0d7d +Subproject commit 9c9f3f57339308762b430b0e090585839b24d92f diff --git a/src/main/java/beam/agentsim/events/TourModeChoiceEvent.java b/src/main/java/beam/agentsim/events/TourModeChoiceEvent.java new file mode 100755 index 00000000000..609dade7348 --- /dev/null +++ b/src/main/java/beam/agentsim/events/TourModeChoiceEvent.java @@ -0,0 +1,172 @@ +package beam.agentsim.events; + +import beam.agentsim.agents.modalbehaviors.DrivesVehicle; +import beam.agentsim.agents.planning.Tour; +import beam.agentsim.agents.planning.Trip; +import beam.router.Modes; +import beam.router.TourModes; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.population.Activity; +import org.matsim.api.core.v01.population.Person; +import org.matsim.core.api.internal.HasPersonId; +import scala.collection.JavaConverters; +import scala.collection.Seq; +import scala.collection.immutable.Vector; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * BEAM + */ + + + +public class TourModeChoiceEvent extends Event implements HasPersonId { + public final static String EVENT_TYPE = "TourModeChoice"; + public final static String ATTRIBUTE_PERSON_ID = "person"; + + public final static String ATTRIBUTE_TOUR_MODE = "tourMode"; + public final static String ATTRIBUTE_MODE_TO_TOUR_MODE = "modeToTourMode"; + public final static String ATTRIBUTE_AVAILABLE_VEHICLES = "availableVehicles"; + public final static String ATTRIBUTE_TOUR_ACTIVITIES = "tourActivities"; + public final static String ATTRIBUTE_AVAILABLE_MODES = "availableModes"; + public final static String ATTRIBUTE_TOUR_MODE_UTILITY = "tourModeUtility"; + public final static String ATTRIBUTE_CURRENT_ACTIVITY = "startActivity"; + public final static String ATTRIBUTE_CURRENT_ACTIVITY_X = "startX"; + public final static String ATTRIBUTE_CURRENT_ACTIVITY_Y = "startY"; + + public final Id personId; + public final String tourMode; + public final Tour currentTour; + public final String modeToTourModeString; + public final String availablePersonalStreetVehiclesString; + public final String tourActivitiesString; + public final String availableModesString; + public final String tourModeToUtilityString; + public final String startActivityType; + public final Double startX; + public final Double startY; + + + + public TourModeChoiceEvent(double time, + Id personId, + String tourMode, + Tour currentTour, + Vector availablePersonalStreetVehicles, + scala.collection.immutable.Map> modeToTourMode, + scala.collection.immutable.Map tourModeUtils, + Vector availableModes, + Activity startActivity) { + this(time, + personId, + tourMode, + currentTour, + modeToTourMode.mkString("-"), + stringifyVehicles(availablePersonalStreetVehicles), + currentTour == null ? "" : stringifyActivities(currentTour), + availableModes.mkString("-"), + tourModeUtils.mkString("; "), + startActivity.getType(), + startActivity.getCoord().getX(), + startActivity.getCoord().getY()); + } + + public TourModeChoiceEvent(double time, + Id personId, + String tourMode, + Tour currentTour, + String modeToTourModeString, + String availablePersonalStreetVehiclesString, + String tourActivitiesString, + String availableModesString, + String tourModeToUtilityString, + String startActivityType, + Double startX, + Double startY) { + super(time); + + this.personId = personId; + this.tourMode = tourMode; + this.currentTour = currentTour; + this.modeToTourModeString = modeToTourModeString; + this.availablePersonalStreetVehiclesString = availablePersonalStreetVehiclesString; + this.tourActivitiesString = tourActivitiesString; + this.availableModesString = availableModesString; + this.tourModeToUtilityString = tourModeToUtilityString; + this.startActivityType = startActivityType; + this.startX = startX; + this.startY = startY; + } + + public static TourModeChoiceEvent apply(Event event) { + if (!(event instanceof TourModeChoiceEvent) && EVENT_TYPE.equalsIgnoreCase(event.getEventType())) { + Map attr = event.getAttributes(); + return new TourModeChoiceEvent(event.getTime(), + Id.createPersonId(attr.get(ATTRIBUTE_PERSON_ID)), + attr.get(ATTRIBUTE_TOUR_MODE), + null, + attr.get(ATTRIBUTE_MODE_TO_TOUR_MODE), + attr.get(ATTRIBUTE_AVAILABLE_VEHICLES), + attr.get(ATTRIBUTE_TOUR_ACTIVITIES), + attr.get(ATTRIBUTE_AVAILABLE_MODES), + attr.get(ATTRIBUTE_TOUR_MODE_UTILITY), + attr.get(ATTRIBUTE_CURRENT_ACTIVITY), + Double.parseDouble(attr.get(ATTRIBUTE_CURRENT_ACTIVITY_Y)), + Double.parseDouble(attr.get(ATTRIBUTE_CURRENT_ACTIVITY_X)) + ); + } + return (TourModeChoiceEvent) event; + } + + + @Override + public Map getAttributes() { + Map attr = super.getAttributes(); + attr.put(ATTRIBUTE_PERSON_ID, personId.toString()); + attr.put(ATTRIBUTE_TOUR_MODE, tourMode); + attr.put(ATTRIBUTE_MODE_TO_TOUR_MODE, modeToTourModeString); + attr.put(ATTRIBUTE_AVAILABLE_VEHICLES, availablePersonalStreetVehiclesString); + attr.put(ATTRIBUTE_TOUR_ACTIVITIES, tourActivitiesString); + attr.put(ATTRIBUTE_AVAILABLE_MODES, availableModesString); + attr.put(ATTRIBUTE_TOUR_MODE_UTILITY, tourModeToUtilityString); + attr.put(ATTRIBUTE_CURRENT_ACTIVITY, startActivityType); + attr.put(ATTRIBUTE_CURRENT_ACTIVITY_X, Double.toString(startX)); + attr.put(ATTRIBUTE_CURRENT_ACTIVITY_Y, Double.toString(startY)); + return attr; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getPersonId() { + return personId; + } + + + public static String stringifyVehicles(Vector vehicles) { + List out = new ArrayList<>(); + List javaVehicles = JavaConverters.seqAsJavaList(vehicles.toSeq()); + for (DrivesVehicle.VehicleOrToken veh : javaVehicles) { + out.add(veh.vehicle().beamVehicleType().toString()); + } + return String.join("-", out); + } + + public static String stringifyActivities(Tour tour) { + List out = new ArrayList<>(); + List javaActivities = JavaConverters.seqAsJavaList(tour.activities().toSeq()); + for (Activity act : javaActivities) { + out.add(act.getType()); + } + return String.join("->", out); + } +} diff --git a/src/main/java/beam/agentsim/events/handling/BeamEventsLogger.java b/src/main/java/beam/agentsim/events/handling/BeamEventsLogger.java index 5db5c6795ad..5cbf19f1b79 100755 --- a/src/main/java/beam/agentsim/events/handling/BeamEventsLogger.java +++ b/src/main/java/beam/agentsim/events/handling/BeamEventsLogger.java @@ -128,6 +128,9 @@ private void overrideDefaultLoggerSetup(String eventsToWrite) { case "ModeChoiceEvent": eventClass = ModeChoiceEvent.class; break; + case "TourModeChoiceEvent": + eventClass = TourModeChoiceEvent.class; + break; case "ParkingEvent": eventClass = ParkingEvent.class; break; diff --git a/src/main/resources/beam-template.conf b/src/main/resources/beam-template.conf index 0c424c54295..dc802c78500 100755 --- a/src/main/resources/beam-template.conf +++ b/src/main/resources/beam-template.conf @@ -964,9 +964,9 @@ beam.routing { accessBufferTimeSeconds { bike = "int | 60" bike_rent = "int | 180" - walk = "int | 0" + walk = "int | 1" car = "int | 300" - ride_hail = "int | 0" + ride_hail = "int | 30" } } gh { diff --git a/src/main/scala/beam/agentsim/agents/MobilityRequest.scala b/src/main/scala/beam/agentsim/agents/MobilityRequest.scala index 7a7842f70f5..530fadad196 100644 --- a/src/main/scala/beam/agentsim/agents/MobilityRequest.scala +++ b/src/main/scala/beam/agentsim/agents/MobilityRequest.scala @@ -71,7 +71,7 @@ object MobilityRequest { person, act, -1, - Trip(act, None, new Tour()), + Trip(act, None, new Tour(0)), BeamMode.CAR, requestType, -1, diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala old mode 100755 new mode 100644 index 5139b18b8d0..869581141ac --- a/src/main/scala/beam/agentsim/agents/PersonAgent.scala +++ b/src/main/scala/beam/agentsim/agents/PersonAgent.scala @@ -5,6 +5,7 @@ import akka.actor.{ActorRef, FSM, Props, Stash, Status} import beam.agentsim.Resource._ import beam.agentsim.agents.BeamAgent._ import beam.agentsim.agents.PersonAgent._ +import beam.agentsim.agents.choice.mode.TourModeChoiceMultinomialLogit import beam.agentsim.agents.freight.input.FreightReader.PAYLOAD_WEIGHT_IN_KG import beam.agentsim.agents.household.HouseholdActor.ReleaseVehicle import beam.agentsim.agents.household.HouseholdCAVDriverAgent @@ -12,7 +13,13 @@ import beam.agentsim.agents.modalbehaviors.ChoosesMode.ChoosesModeData import beam.agentsim.agents.modalbehaviors.DrivesVehicle._ import beam.agentsim.agents.modalbehaviors.{ChoosesMode, DrivesVehicle, ModeChoiceCalculator} import beam.agentsim.agents.parking.ChoosesParking -import beam.agentsim.agents.parking.ChoosesParking.{ChoosingParkingSpot, ReleasingParkingSpot} +import beam.agentsim.agents.parking.ChoosesParking.{ +// handleReleasingParkingSpot, + ChoosingParkingSpot, + ReleasingParkingSpot +} +import beam.agentsim.agents.planning.BeamPlan.atHome +import beam.agentsim.agents.planning.Strategy.{TourModeChoiceStrategy, TripModeChoiceStrategy} import beam.agentsim.agents.planning.{BeamPlan, Tour} import beam.agentsim.agents.ridehail.RideHailManager.TravelProposal import beam.agentsim.agents.ridehail._ @@ -31,6 +38,7 @@ import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, IllegalTrig import beam.agentsim.scheduler.Trigger.TriggerWithId import beam.agentsim.scheduler.{BeamAgentSchedulerTimer, Trigger} import beam.router.Modes.BeamMode +import beam.router.TourModes.BeamTourMode import beam.router.Modes.BeamMode.{ CAR, CAV, @@ -81,6 +89,7 @@ object PersonAgent { services: BeamServices, beamScenario: BeamScenario, modeChoiceCalculator: ModeChoiceCalculator, + tourModeChoiceCalculator: TourModeChoiceMultinomialLogit, transportNetwork: TransportNetwork, tollCalculator: TollCalculator, router: ActorRef, @@ -102,6 +111,7 @@ object PersonAgent { services, beamScenario, modeChoiceCalculator, + tourModeChoiceCalculator, transportNetwork, router, rideHailManager, @@ -185,8 +195,9 @@ object PersonAgent { currentTrip: Option[EmbodiedBeamTrip] = None, restOfCurrentTrip: List[EmbodiedBeamLeg] = List(), currentVehicle: VehicleStack = Vector(), - currentTourMode: Option[BeamMode] = None, - currentTourPersonalVehicle: Option[Id[BeamVehicle]] = None, + currentTripMode: Option[BeamMode] = None, // We might not need this here any more if it's kept in the plan + currentTourMode: Option[BeamTourMode] = None, // "" + currentTourPersonalVehicle: Option[Id[BeamVehicle]] = None, // "" passengerSchedule: PassengerSchedule = PassengerSchedule(), currentLegPassengerScheduleIndex: Int = 0, hasDeparted: Boolean = false, @@ -195,7 +206,16 @@ object PersonAgent { failedTrips: IndexedSeq[EmbodiedBeamTrip] = IndexedSeq.empty, lastUsedParkingStall: Option[ParkingStall] = None, enrouteData: EnrouteData = EnrouteData() - ) extends PersonData { + ) extends PersonData + with ExponentialLazyLogging { + + if (currentTripMode.exists(_.isTeleportation) & currentTourPersonalVehicle.isDefined) { + logger.warn( + s"Agent on tour with mode ${currentTourMode.getOrElse("N/A")} " + + s"is on a teleportation trip but " + + s"has a tour vehicle defined from a previous trip -- this is bad" + ) + } override def withPassengerSchedule(newPassengerSchedule: PassengerSchedule): DrivingData = copy(passengerSchedule = newPassengerSchedule) @@ -289,6 +309,7 @@ class PersonAgent( val beamServices: BeamServices, val beamScenario: BeamScenario, val modeChoiceCalculator: ModeChoiceCalculator, + val tourModeChoiceCalculator: TourModeChoiceMultinomialLogit, val transportNetwork: TransportNetwork, val router: ActorRef, val rideHailManager: ActorRef, @@ -408,7 +429,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 @@ -471,6 +492,34 @@ class PersonAgent( } } + def isFirstTripWithinTour(nextAct: Activity): Boolean = { + val (tripIndexOfElement: Int, _) = currentTripIndexWithinTour(nextAct) + tripIndexOfElement == 0 + } + + def getParentTour(nextAct: Activity): Option[Tour] = { + _experiencedBeamPlan.getTourContaining(nextAct).originActivity.map(_experiencedBeamPlan.getTourContaining(_)) + } + + def isLastTripWithinTour(nextAct: Activity): Boolean = { + val (tripIndexOfElement: Int, lastTripIndex: Int) = currentTripIndexWithinTour(nextAct) + tripIndexOfElement == lastTripIndex + } + + def isFirstOrLastTripWithinTour(nextAct: Activity): Boolean = { + val (tripIndexOfElement: Int, lastTripIndex: Int) = currentTripIndexWithinTour(nextAct) + tripIndexOfElement == 0 || tripIndexOfElement == lastTripIndex + } + + def currentTripIndexWithinTour(nextAct: Activity): (Int, Int) = { + val tour = _experiencedBeamPlan.getTourContaining(nextAct) + val lastTripIndex = tour.trips.size - 1 + val tripIndexOfElement = tour + .tripIndexOfElement(nextAct) + .getOrElse(throw new IllegalArgumentException(s"Element [$nextAct] not found")) + (tripIndexOfElement, lastTripIndex) + } + def currentActivity(data: BasePersonData): Activity = _experiencedBeamPlan.activities(data.currentActivityIndex) @@ -573,24 +622,17 @@ 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[TripModeChoiceStrategy](nextAct).mode + val currentTourModeChoiceStrategy = _experiencedBeamPlan.getTourStrategy[TourModeChoiceStrategy](nextAct) val currentCoord = currentActivity(data).getCoord 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 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, + currentTourMode = currentTourModeChoiceStrategy.tourMode, // NOTE: Not sure why this is needed... numberOfReplanningAttempts = 0, failedTrips = IndexedSeq.empty, enrouteData = EnrouteData() @@ -607,7 +649,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) @@ -621,7 +663,7 @@ class PersonAgent( case Event( TriggerWithId(TeleportationEndsTrigger(tick), triggerId), - data @ BasePersonData(_, Some(currentTrip), _, _, maybeCurrentTourMode, _, _, _, true, _, _, _, _, _) + data @ BasePersonData(_, Some(currentTrip), _, _, maybeCurrentTripMode, _, _, _, _, true, _, _, _, _, _) ) => holdTickAndTriggerId(tick, triggerId) @@ -634,7 +676,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) + currentTripMode = data.currentTripMode.map(_.value) ) eventsManager.processEvent(teleportationEvent) @@ -653,7 +695,7 @@ class PersonAgent( */ case Event( TriggerWithId(PersonDepartureTrigger(tick), triggerId), - data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, false, _, _, _, _, _) + data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, _, false, _, _, _, _, _) ) => endActivityAndDepart(tick, currentTrip, data) @@ -662,7 +704,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}") @@ -727,7 +769,7 @@ class PersonAgent( ) 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 @@ -746,7 +788,7 @@ class PersonAgent( // TRANSIT FAILURE case Event( ReservationResponse(Left(firstErrorResponse), _), - data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _) + data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _, _) ) => logDebug(s"replanning because ${firstErrorResponse.errorCode}") @@ -837,7 +879,7 @@ class PersonAgent( // RIDE HAIL FAILURE case Event( response @ RideHailResponse(_, _, _, Some(error), _, _), - data @ BasePersonData(_, _, _, _, _, _, _, _, _, _, _, _, _, _) + data @ BasePersonData(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _) ) => handleFailedRideHailReservation(error, response, data) } @@ -848,7 +890,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 +923,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,7 +975,7 @@ class PersonAgent( if (currentBeamVehicle.beamVehicleType.vehicleCategory != Bike) { if (currentBeamVehicle.stall.isEmpty) logWarn("Expected currentBeamVehicle.stall to be defined.") } - if (!currentBeamVehicle.isMustBeDrivenHome) { + if (currentBeamVehicle.isSharedVehicle) { // Is a shared vehicle. Give it up. currentBeamVehicle.getManager.get ! ReleaseVehicle(currentBeamVehicle, triggerId) beamVehicles -= data.currentVehicle.head @@ -958,6 +1000,7 @@ class PersonAgent( _, _, _, + _, currentCost, _, _, @@ -991,7 +1034,7 @@ class PersonAgent( potentiallyChargingBeamVehicles.remove(vehicle.id) goto(ProcessingNextLegOrStartActivity) case Event(NotAvailable(_), basePersonData: BasePersonData) => - log.debug("{} replanning because vehicle not available when trying to board") + log.warning("{} replanning because vehicle not available when trying to board") val replanningReason = getReplanningReasonFrom(basePersonData, ReservationErrorCode.ResourceUnavailable.entryName) val currentCoord = beamServices.geo.wgs2Utm(basePersonData.restOfCurrentTrip.head.beamLeg.travelPath.startPoint).loc @@ -1005,11 +1048,18 @@ class PersonAgent( ) ) - val nextCoord = nextActivity(basePersonData).get.getCoord + val nextAct = nextActivity(basePersonData).get + val nextCoord = nextAct.getCoord + // Have to give up my mode as well, perhaps there's no option left for driving. + _experiencedBeamPlan.putStrategy(nextAct, TripModeChoiceStrategy(mode = None)) + val (updatedTourMode, updatedTourPersonalVehicle): (Option[BeamTourMode], Option[Id[BeamVehicle]]) = + if (nextAct.getType.equalsIgnoreCase("Home")) { (None, None) } + else { (basePersonData.currentTourMode, basePersonData.currentTourPersonalVehicle) } goto(ChoosingMode) using ChoosesModeData( basePersonData.copy( - currentTourMode = None, // Have to give up my mode as well, perhaps there's no option left for driving. - currentTourPersonalVehicle = None, + currentTripMode = None, + currentTourMode = updatedTourMode, + currentTourPersonalVehicle = updatedTourPersonalVehicle, numberOfReplanningAttempts = basePersonData.numberOfReplanningAttempts + 1 ), SpaceTime(currentCoord, _currentTick.get), @@ -1091,6 +1141,7 @@ class PersonAgent( _, _, _, + _, _ ) ) if nextLeg.asDriver => @@ -1185,12 +1236,17 @@ 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 + // initial inquiry). So we replan but change trip mode to WALK_TRANSIT since we've already done our non-transit // portion. - log.debug("Missed transit pickup, late by {} sec", _currentTick.get - nextLeg.beamLeg.startTime) + log.debug( + "Agent {} missed transit pickup on {} trip, late by {} sec", + id.toString, + data.currentTripMode.map(_.value).getOrElse("None"), + _currentTick.get - nextLeg.beamLeg.startTime + ) val replanningReason = getReplanningReasonFrom(data, ReservationErrorCode.MissedTransitPickup.entryName) val currentCoord = beamServices.geo.wgs2Utm(nextLeg.beamLeg.travelPath.startPoint).loc @@ -1207,7 +1263,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 = @@ -1215,7 +1271,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, @@ -1226,7 +1282,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) @@ -1258,7 +1314,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 @@ -1280,7 +1336,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 = @@ -1289,7 +1345,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, @@ -1309,7 +1365,8 @@ class PersonAgent( _, _, _, - currentTourMode @ Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION), + Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION), + _, _, _, _, @@ -1357,12 +1414,18 @@ class PersonAgent( triggerId, Vector(ScheduleTrigger(ActivityEndTrigger(nextLegDepartureTime), self)) ) + + val nextTripTourPersonalVehicle = if (activity.getType.equalsIgnoreCase("Home")) { + None + } else { + data.currentTourPersonalVehicle + } goto(PerformingActivity) using data.copy( currentActivityIndex = currentActivityIndex + 1, currentTrip = None, restOfCurrentTrip = List(), - currentTourPersonalVehicle = None, - currentTourMode = if (activity.getType.equals("Home")) None else currentTourMode, + currentTourPersonalVehicle = nextTripTourPersonalVehicle, + currentTripMode = None, hasDeparted = false ) case None => @@ -1379,7 +1442,8 @@ class PersonAgent( Some(currentTrip), _, _, - currentTourMode, + _, + _, currentTourPersonalVehicle, _, _, @@ -1475,9 +1539,9 @@ class PersonAgent( currentTrip = None, restOfCurrentTrip = List(), currentTourPersonalVehicle = currentTourPersonalVehicle match { - case Some(personalVehId) => + case Some(personalVehId) if beamVehicles.contains(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) @@ -1485,10 +1549,13 @@ class PersonAgent( } else { currentTourPersonalVehicle } + case Some(personalVehId) => + logger.error(s"Vehicle ${personalVehId.toString} seems to have disappeared") + None case None => None }, - currentTourMode = if (activity.getType.equals("Home")) None else currentTourMode, + currentTripMode = None, hasDeparted = false ) case None => @@ -1588,7 +1655,7 @@ class PersonAgent( } def getReplanningReasonFrom(data: BasePersonData, prefix: String): String = { - data.currentTourMode + data.currentTripMode .collect { case mode => s"$prefix $mode" } @@ -1662,7 +1729,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/Population.scala b/src/main/scala/beam/agentsim/agents/Population.scala index 4b891b069ba..5e2eb1f9e3c 100755 --- a/src/main/scala/beam/agentsim/agents/Population.scala +++ b/src/main/scala/beam/agentsim/agents/Population.scala @@ -13,6 +13,7 @@ import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTri import beam.agentsim.scheduler.Trigger.TriggerWithId import beam.router.RouteHistory import beam.router.osm.TollCalculator +import beam.sim.config.BeamConfigHolder import beam.sim.vehicles.VehiclesAdjustment import beam.sim.{BeamScenario, BeamServices} import beam.utils.MathUtils @@ -40,7 +41,8 @@ class Population( val chargingNetworkManager: ActorRef, val sharedVehicleFleets: Seq[ActorRef], val eventsManager: EventsManager, - val routeHistory: RouteHistory + val routeHistory: RouteHistory, + beamConfigHolder: BeamConfigHolder ) extends LoggingMessageActor with ActorLogging { @@ -153,7 +155,8 @@ class Population( sharedVehicleFleets, sharedVehicleTypes, routeHistory, - vehicleAdjustment + vehicleAdjustment, + beamConfigHolder ), household.getId.toString ) @@ -210,7 +213,8 @@ object Population { chargingNetworkManager: ActorRef, sharedVehicleFleets: Seq[ActorRef], eventsManager: EventsManager, - routeHistory: RouteHistory + routeHistory: RouteHistory, + beamConfigHolder: BeamConfigHolder ): Props = { Props( new Population( @@ -226,7 +230,8 @@ object Population { chargingNetworkManager, sharedVehicleFleets, eventsManager, - routeHistory + routeHistory, + beamConfigHolder ) ) } diff --git a/src/main/scala/beam/agentsim/agents/choice/logit/MultinomialLogit.scala b/src/main/scala/beam/agentsim/agents/choice/logit/MultinomialLogit.scala index 4388d18a812..7616a8d6efe 100644 --- a/src/main/scala/beam/agentsim/agents/choice/logit/MultinomialLogit.scala +++ b/src/main/scala/beam/agentsim/agents/choice/logit/MultinomialLogit.scala @@ -59,6 +59,9 @@ class MultinomialLogit[A, T]( thisUtility * scale_factor, math.exp(thisUtility * scale_factor) ) + } else if (thisUtility.isNegInfinity) { + // utility of negative infinity means that an alternative isn't feasible, so we filter it out + accumulator } else { AlternativeWithUtility( alt, diff --git a/src/main/scala/beam/agentsim/agents/choice/logit/TourModeChoiceModel.scala b/src/main/scala/beam/agentsim/agents/choice/logit/TourModeChoiceModel.scala new file mode 100644 index 00000000000..6e507d28c13 --- /dev/null +++ b/src/main/scala/beam/agentsim/agents/choice/logit/TourModeChoiceModel.scala @@ -0,0 +1,27 @@ +package beam.agentsim.agents.choice.logit + +import beam.sim.config.BeamConfig + +object TourModeChoiceModel { + def apply(beamConfig: BeamConfig) = new TourModeChoiceModel(beamConfig) + + sealed trait TourModeParameters + + object TourModeParameters { + final case object ExpectedMaxUtility extends TourModeParameters with Serializable + final case object Intercept extends TourModeParameters with Serializable + } + + type TourModeMNLConfig = Map[TourModeParameters, UtilityFunctionOperation] +} + +class TourModeChoiceModel( + val beamConfig: BeamConfig +) { + + val DefaultMNLParameters: TourModeChoiceModel.TourModeMNLConfig = Map( + TourModeChoiceModel.TourModeParameters.ExpectedMaxUtility -> UtilityFunctionOperation.Multiplier(1.0), + TourModeChoiceModel.TourModeParameters.Intercept -> UtilityFunctionOperation.Intercept(1.0) + ) + +} diff --git a/src/main/scala/beam/agentsim/agents/choice/logit/UtilityFunctionOperation.scala b/src/main/scala/beam/agentsim/agents/choice/logit/UtilityFunctionOperation.scala index 3f1decf1574..0c914912f63 100644 --- a/src/main/scala/beam/agentsim/agents/choice/logit/UtilityFunctionOperation.scala +++ b/src/main/scala/beam/agentsim/agents/choice/logit/UtilityFunctionOperation.scala @@ -2,6 +2,7 @@ package beam.agentsim.agents.choice.logit sealed trait UtilityFunctionOperation { def apply(value: Double): Double + def toMap: Map[String, Double] } /** @@ -12,10 +13,12 @@ object UtilityFunctionOperation { case class Intercept(coefficient: Double) extends UtilityFunctionOperation { override def apply(value: Double): Double = coefficient + override def toMap: Map[String, Double] = Map("intercept" -> coefficient) } case class Multiplier(coefficient: Double) extends UtilityFunctionOperation { override def apply(value: Double): Double = coefficient * value + override def toMap: Map[String, Double] = Map("multiplier" -> coefficient) } def apply(s: String, value: Double): UtilityFunctionOperation = { @@ -26,4 +29,8 @@ object UtilityFunctionOperation { case _ => throw new RuntimeException(s"Unknown Utility Parameter Type $s") } } + + def toMap: Map[String, Double] = { + Map.empty[String, Double] + } } diff --git a/src/main/scala/beam/agentsim/agents/choice/mode/ModeChoiceMultinomialLogit.scala b/src/main/scala/beam/agentsim/agents/choice/mode/ModeChoiceMultinomialLogit.scala index fda51c6e2ed..4e2875c42be 100755 --- a/src/main/scala/beam/agentsim/agents/choice/mode/ModeChoiceMultinomialLogit.scala +++ b/src/main/scala/beam/agentsim/agents/choice/mode/ModeChoiceMultinomialLogit.scala @@ -44,6 +44,8 @@ class ModeChoiceMultinomialLogit( override lazy val beamConfig: BeamConfig = beamConfigHolder.beamConfig + override val modeChoiceLogit: MultinomialLogit[BeamMode, String] = modeModel + var expectedMaximumUtility: Double = 0.0 val modalBehaviors: ModalBehaviors = beamConfig.beam.agentsim.agents.modalBehaviors diff --git a/src/main/scala/beam/agentsim/agents/choice/mode/TourModeChoiceMultinomialLogit.scala b/src/main/scala/beam/agentsim/agents/choice/mode/TourModeChoiceMultinomialLogit.scala new file mode 100644 index 00000000000..32c2d889063 --- /dev/null +++ b/src/main/scala/beam/agentsim/agents/choice/mode/TourModeChoiceMultinomialLogit.scala @@ -0,0 +1,119 @@ +package beam.agentsim.agents.choice.mode + +import beam.agentsim.agents.choice.logit.{MultinomialLogit, TourModeChoiceModel, UtilityFunctionOperation} +import beam.agentsim.agents.choice.logit.TourModeChoiceModel.{TourModeMNLConfig, TourModeParameters} +import beam.router.Modes.BeamMode +import beam.router.TourModes.BeamTourMode +import beam.router.skim.core.ODSkimmer.ODSkimmerTimeCostTransfer +import beam.sim.population.AttributesOfIndividual +import beam.sim.config.{BeamConfig, BeamConfigHolder} + +import scala.collection.{mutable, Seq} +import scala.util.Random + +class TourModeChoiceMultinomialLogit( + val attributesOfIndividual: AttributesOfIndividual, + val tourModeChoiceModel: TourModeChoiceModel, + beamConfigHolder: BeamConfigHolder +) { + val rnd: Random = new scala.util.Random(System.currentTimeMillis()) + + val tourModeLogit = MultinomialLogit[BeamTourMode, TourModeChoiceModel.TourModeParameters]( + Map.empty[BeamTourMode, Map[TourModeParameters, UtilityFunctionOperation]], + tourModeChoiceModel.DefaultMNLParameters, + beamConfigHolder.beamConfig.beam.agentsim.agents.modalBehaviors.multinomialLogit.utility_scale_factor + ) + + def chooseTourMode( + tourModeCosts: Seq[Map[BeamMode, ODSkimmerTimeCostTransfer]], + modeLogit: MultinomialLogit[BeamMode, String], + modeToTourMode: Map[BeamTourMode, Seq[BeamMode]], + firstAndLastTripModeToTourModeOption: Option[Map[BeamTourMode, Seq[BeamMode]]] + ): Option[BeamTourMode] = { + val tourUtility = + tourExpectedMaxUtility(tourModeCosts, modeLogit, modeToTourMode, firstAndLastTripModeToTourModeOption) + tourModeChoice(tourUtility, tourModeLogit) + } + + def tripExpectedMaxUtility( + tripModeCosts: Map[BeamMode, ODSkimmerTimeCostTransfer], + modeLogit: MultinomialLogit[BeamMode, String], + modeToTourMode: Map[BeamTourMode, Seq[BeamMode]] + ): Map[BeamTourMode, Double] = { + modeToTourMode map { case (beamTourMode, beamModes) => + val modeChoice = beamModes.map { beamMode => + val skims = tripModeCosts.getOrElse(beamMode, ODSkimmerTimeCostTransfer()) + val timeCost = attributesOfIndividual.getVOT(skims.timeInHours) + val interceptMap = modeLogit.utilityFunctions(beamMode).flatMap(_.get("intercept")).map(_.toMap) + val monetaryCost = skims.cost + beamMode -> (Map("cost" -> (timeCost + monetaryCost)) ++ Map( + "transfers" -> skims.numTransfers.toDouble + ) ++ interceptMap.getOrElse(Map.empty[String, Double])) + }.toMap + beamTourMode -> modeLogit.getExpectedMaximumUtility(modeChoice).getOrElse(Double.NegativeInfinity) + } + } + + def tourExpectedMaxUtility( + tourModeCosts: Seq[Map[BeamMode, ODSkimmerTimeCostTransfer]], + modeLogit: MultinomialLogit[BeamMode, String], + modeToTourMode: Map[BeamTourMode, Seq[BeamMode]], + firstAndLastTripModeToTourModeOption: Option[Map[BeamTourMode, Seq[BeamMode]]] = None + ): Map[BeamTourMode, Double] = { + val tourModeToExpectedUtility = mutable.Map[BeamTourMode, Double]() + tourModeCosts.zipWithIndex foreach { case (modeCosts, idx) => + if (idx == 0 | idx == tourModeCosts.length - 1) { + // Allow inclusion of private vehicles in first/last trips, e.g. for DRIVE_TRANSIT + // Default to normal modeToTourMode if the other mapping isn't defined, though + tripExpectedMaxUtility(modeCosts, modeLogit, firstAndLastTripModeToTourModeOption.getOrElse(modeToTourMode)) + .map { case (tourMode, util) => + tourModeToExpectedUtility += (tourMode -> (tourModeToExpectedUtility.getOrElse(tourMode, 0.0) + util)) + } + } else { + tripExpectedMaxUtility(modeCosts, modeLogit, modeToTourMode).map { case (tourMode, util) => + tourModeToExpectedUtility += (tourMode -> (tourModeToExpectedUtility.getOrElse(tourMode, 0.0) + util)) + } + } + + } + tourModeToExpectedUtility.toMap + } + + def tourModeChoice( + tourModeUtility: Map[BeamTourMode, Double], + tourModeLogit: MultinomialLogit[BeamTourMode, TourModeParameters] + ): Option[BeamTourMode] = { + val tourModeChoiceData: Map[BeamTourMode, Map[TourModeParameters, Double]] = tourModeUtility.map { + case (tourMode, util) => + tourMode -> Map[TourModeParameters, Double]( + TourModeParameters.ExpectedMaxUtility -> util, + TourModeParameters.Intercept -> 0 + ) + } + tourModeLogit.sampleAlternative(tourModeChoiceData, rnd) match { + case Some(sample) => Some(sample.alternativeType) + case None => None + } + } + + def apply( + tourModeCosts: Seq[Map[BeamMode, ODSkimmerTimeCostTransfer]], + modeLogit: MultinomialLogit[BeamMode, String], + modeToTourMode: Map[BeamTourMode, Seq[BeamMode]], + firstAndLastTripModeToTourModeOption: Option[Map[BeamTourMode, Seq[BeamMode]]] + ): Option[BeamTourMode] = { + chooseTourMode(tourModeCosts, modeLogit, modeToTourMode, firstAndLastTripModeToTourModeOption) + } + + def apply( + tourModeCosts: Seq[Map[BeamMode, ODSkimmerTimeCostTransfer]], + modeLogit: MultinomialLogit[BeamMode, String], + modeToTourMode: Map[BeamTourMode, Seq[BeamMode]] + ): Option[BeamTourMode] = { + chooseTourMode(tourModeCosts, modeLogit, modeToTourMode, None) + } + + def apply(tourUtility: Map[BeamTourMode, Double]): Option[BeamTourMode] = { + tourModeChoice(tourUtility, tourModeLogit) + } +} diff --git a/src/main/scala/beam/agentsim/agents/household/FastHouseholdCAVScheduling.scala b/src/main/scala/beam/agentsim/agents/household/FastHouseholdCAVScheduling.scala index 10282e6c4fb..15a3cedce39 100644 --- a/src/main/scala/beam/agentsim/agents/household/FastHouseholdCAVScheduling.scala +++ b/src/main/scala/beam/agentsim/agents/household/FastHouseholdCAVScheduling.scala @@ -429,7 +429,7 @@ object HouseholdTripsHelper { var firstPickupOfTheDay: Option[MobilityRequest] = None breakable { householdPlans.foldLeft(householdNbOfVehicles) { case (counter, plan) => - val usedCarOut = plan.trips.sliding(2).foldLeft(false) { case (usedCar, Array(prevTrip, curTrip)) => + val usedCarOut = plan.trips.sliding(2).foldLeft(false) { case (usedCar, Vector(prevTrip, curTrip)) => val (pickup, dropoff, travelTime) = getPickupAndDropoff( plan, diff --git a/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala b/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala index da77792a844..34fb2a411df 100755 --- a/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala +++ b/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala @@ -7,6 +7,8 @@ import akka.util.Timeout import beam.agentsim.Resource.NotifyVehicleIdle import beam.agentsim.agents.BeamAgent.Finish import beam.agentsim.agents._ +import beam.agentsim.agents.choice.logit.TourModeChoiceModel +import beam.agentsim.agents.choice.mode.TourModeChoiceMultinomialLogit import beam.agentsim.agents.freight.input.FreightReader import beam.agentsim.agents.modalbehaviors.ChoosesMode.{CavTripLegsRequest, CavTripLegsResponse} import beam.agentsim.agents.modalbehaviors.DrivesVehicle.VehicleOrToken @@ -35,6 +37,7 @@ import beam.router.RouteHistory import beam.router.model.{BeamLeg, EmbodiedBeamLeg} import beam.router.osm.TollCalculator import beam.sim.config.BeamConfig.Beam.Debug +import beam.sim.config.BeamConfigHolder import beam.sim.population.AttributesOfIndividual import beam.sim.vehicles.VehiclesAdjustment import beam.sim.{BeamScenario, BeamServices} @@ -78,7 +81,8 @@ object HouseholdActor { sharedVehicleFleets: Seq[ActorRef] = Vector(), possibleSharedVehicleTypes: Set[BeamVehicleType], routeHistory: RouteHistory, - vehiclesAdjustment: VehiclesAdjustment + vehiclesAdjustment: VehiclesAdjustment, + beamConfigHolder: BeamConfigHolder ): Props = { Props( new HouseholdActor( @@ -100,7 +104,8 @@ object HouseholdActor { sharedVehicleFleets, possibleSharedVehicleTypes, routeHistory, - vehiclesAdjustment + vehiclesAdjustment, + beamConfigHolder ) ) } @@ -113,6 +118,8 @@ object HouseholdActor { triggerId: Long ) extends HasTriggerId + // TODO: Extend this to allow you to request a specific vehicle id + case class ReleaseVehicle(vehicle: BeamVehicle, triggerId: Long) extends HasTriggerId case class ReleaseVehicleAndReply(vehicle: BeamVehicle, tick: Option[Int] = None, triggerId: Long) @@ -156,7 +163,8 @@ object HouseholdActor { sharedVehicleFleets: Seq[ActorRef] = Vector(), possibleSharedVehicleTypes: Set[BeamVehicleType], routeHistory: RouteHistory, - vehiclesAdjustment: VehiclesAdjustment + vehiclesAdjustment: VehiclesAdjustment, + beamConfigHolder: BeamConfigHolder ) extends LoggingMessageActor with HasTickAndTrigger with ActorLogging { @@ -406,6 +414,12 @@ object HouseholdActor { household.members.foreach { person => val attributes = person.getCustomAttributes.get("beam-attributes").asInstanceOf[AttributesOfIndividual] val modeChoiceCalculator = modeChoiceCalculatorFactory(attributes) + val tourModeChoiceCalculator = + new TourModeChoiceMultinomialLogit( + attributes, + new TourModeChoiceModel(beamScenario.beamConfig), + beamConfigHolder + ) val selectedPlan = person.getSelectedPlan // Set zero endTime for plans with one activity. In other case agent sim will be started // before all InitializeTrigger's are completed @@ -422,6 +436,7 @@ object HouseholdActor { beamServices, beamScenario, modeChoiceCalculator, + tourModeChoiceCalculator, transportNetwork, tollCalculator, router, diff --git a/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala b/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala index 42241a09f86..782e0bc16fe 100644 --- a/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala +++ b/src/main/scala/beam/agentsim/agents/household/HouseholdFleetManager.scala @@ -64,7 +64,7 @@ class HouseholdFleetManager( val veh = vehiclesInternal(id) veh.setManager(Some(self)) veh.spaceTime = SpaceTime(resp.stall.locationUTM.getX, resp.stall.locationUTM.getY, 0) - veh.setMustBeDrivenHome(true) + veh.setMustBeDrivenHome(false) veh.useParkingStall(resp.stall) val parkEvent = ParkingEvent( time = 0, @@ -182,10 +182,11 @@ class HouseholdFleetManager( case None if createAnEmergencyVehicle(inquiry).nonEmpty => logger.debug(s"An emergency vehicle has been created!") case _ => - if (availableVehicles.isEmpty) - logger.warn( - s"The list of vehicles should not be empty, activate emergency personal vehicles generation as a temporary solution" - ) + // I don't think this is actually a problem???? +// if (availableVehicles.isEmpty) +// logger.warn( +// s"The list of vehicles should not be empty, activate emergency personal vehicles generation as a temporary solution" +// ) logger.debug(s"Not returning vehicle because no default for is defined") sender() ! MobilityStatusResponse(Vector(), triggerId) } @@ -238,7 +239,7 @@ class HouseholdFleetManager( // and complete initialization only when I got them all. val responseFuture = parkingManager ? ParkingInquiry.init( inquiry.whereWhen, - "wherever", + "home", VehicleManager.getReservedFor(vehicle.vehicleManagerId.get()).get, Some(vehicle), triggerId = inquiry.triggerId, @@ -246,6 +247,7 @@ class HouseholdFleetManager( ) responseFuture.collect { case ParkingInquiryResponse(stall, _, otherTriggerId) => + vehicle.setMustBeDrivenHome(false) vehicle.useParkingStall(stall) logger.debug("Vehicle {} is now taken, which was just created", vehicle.id) vehicle.becomeDriver(mobilityRequester) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala old mode 100755 new mode 100644 index 58ac9a7a312..184a4c340e0 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -8,27 +8,30 @@ 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.{TourModeChoiceStrategy, TripModeChoiceStrategy} import beam.agentsim.agents.ridehail.{RideHailInquiry, RideHailRequest, RideHailResponse} import beam.agentsim.agents.vehicles.AccessErrorCodes.RideHailNotRequestedError import beam.agentsim.agents.vehicles.EnergyEconomyAttributes.Powertrain import beam.agentsim.agents.vehicles.VehicleCategory.VehicleCategory import beam.agentsim.agents.vehicles.VehicleProtocol.StreetVehicle -import beam.agentsim.agents.vehicles._ +import beam.agentsim.agents.vehicles.{BeamVehicle, _} import beam.agentsim.events.resources.ReservationErrorCode -import beam.agentsim.events.{ModeChoiceEvent, ReplanningEvent, SpaceTime} +import beam.agentsim.events.{ModeChoiceEvent, ReplanningEvent, SpaceTime, TourModeChoiceEvent} import beam.agentsim.infrastructure.taz.TAZ import beam.agentsim.infrastructure.{ParkingInquiry, ParkingInquiryResponse, ZonalParkingManager} import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTrigger} import beam.router.BeamRouter._ import beam.router.Modes.BeamMode import beam.router.Modes.BeamMode._ +import beam.router.TourModes.BeamTourMode +import beam.router.TourModes.BeamTourMode._ import beam.router.model.{BeamLeg, EmbodiedBeamLeg, EmbodiedBeamTrip} import beam.router.skim.ActivitySimPathType.determineActivitySimPathTypesFromBeamMode import beam.router.skim.{ActivitySimPathType, ActivitySimSkimmerFailedTripEvent} import beam.router.skim.core.ODSkimmer import beam.router.skim.event.{ODSkimmerEvent, ODSkimmerFailedTripEvent} import beam.router.skim.readonly.ODSkims -import beam.router.{Modes, RoutingWorker} +import beam.router.{Modes, RoutingWorker, TourModes} import beam.sim.population.AttributesOfIndividual import beam.sim.{BeamServices, Geofence} import beam.utils.logging.pattern.ask @@ -43,6 +46,7 @@ import java.util.concurrent.atomic.AtomicReference import scala.collection.JavaConverters import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} +import scala.language.postfixOps /** * BEAM @@ -126,142 +130,91 @@ trait ChoosesMode { def bodyVehiclePersonId: PersonIdWithActorRef = PersonIdWithActorRef(id, self) onTransition { case _ -> ChoosingMode => - nextStateData match { + val choosesModeData: ChoosesModeData = nextStateData.asInstanceOf[ChoosesModeData] + val nextAct = nextActivity(choosesModeData.personData).get + val currentTourStrategy = _experiencedBeamPlan.getTourStrategy[TourModeChoiceStrategy](nextAct) + val currentTripMode = _experiencedBeamPlan.getTripStrategy[TripModeChoiceStrategy](nextAct).mode + val currentTourMode = currentTourStrategy.tourMode + + (nextStateData, currentTripMode, currentTourMode) match { // If I am already on a tour in a vehicle, only that vehicle is available to me - case ChoosesModeData( - BasePersonData(_, _, _, _, _, Some(vehicle), _, _, _, _, _, _, _, _), - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _ - ) => - 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) + // Unless it's a walk based tour and I used that vehicle for egress on my first trip + case (data: ChoosesModeData, _, tourMode @ Some(CAR_BASED | BIKE_BASED)) => + if (data.personData.currentTourPersonalVehicle.isDefined) { + val currentTourVehicle = Vector(beamVehicles(data.personData.currentTourPersonalVehicle.get)) + self ! MobilityStatusResponse( + currentTourVehicle, + getCurrentTriggerIdOrGenerate + ) + } else { + implicit val executionContext: ExecutionContext = context.system.dispatcher + requestAvailableVehicles( + fleetManagers, + data.currentLocation, + currentActivity(data.personData), + tourMode match { + case Some(CAR_BASED) => Some(VehicleCategory.Car) + case Some(BIKE_BASED) => Some(VehicleCategory.Bike) + case _ => None + } + ) pipeTo self + } + // If we're on a walk based tour but using a vehicle for access/egress + case (data: ChoosesModeData, Some(BIKE_TRANSIT | DRIVE_TRANSIT), Some(WALK_BASED)) + if data.personData.currentTourPersonalVehicle.isDefined => + self ! MobilityStatusResponse( + Vector(beamVehicles(data.personData.currentTourPersonalVehicle.get)), + getCurrentTriggerIdOrGenerate + ) + // Create teleportation vehicle if we are told to use teleportation + case (data: ChoosesModeData, Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION), _) => + 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 ChoosesModeData( - BasePersonData( - currentActivityIndex, - _, - _, - _, - plansModeOption @ (None | Some(CAR | BIKE | DRIVE_TRANSIT | BIKE_TRANSIT)), - _, - _, - _, - _, - _, - _, - _, - _, - _ - ), - currentLocation, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _, - _ - ) => + case (data: ChoosesModeData, Some(CAR | CAR_HOV2 | CAR_HOV3 | DRIVE_TRANSIT), _) => implicit val executionContext: ExecutionContext = context.system.dispatcher - plansModeOption match { - case Some(CAR | DRIVE_TRANSIT) => - requestAvailableVehicles( - vehicleFleets, - currentLocation, - _experiencedBeamPlan.activities(currentActivityIndex), - Some(VehicleCategory.Car) - ) pipeTo self - case Some(BIKE | BIKE_TRANSIT) => - requestAvailableVehicles( - vehicleFleets, - currentLocation, - _experiencedBeamPlan.activities(currentActivityIndex), - Some(VehicleCategory.Bike) - ) pipeTo self - case _ => - requestAvailableVehicles( - vehicleFleets, - currentLocation, - _experiencedBeamPlan.activities(currentActivityIndex) - ) pipeTo self - } - + requestAvailableVehicles( + vehicleFleets, + data.currentLocation, + currentActivity(data.personData), + Some(VehicleCategory.Car) + ) pipeTo self + case (data: ChoosesModeData, Some(BIKE | BIKE_TRANSIT), _) => + implicit val executionContext: ExecutionContext = context.system.dispatcher + requestAvailableVehicles( + vehicleFleets, + data.currentLocation, + currentActivity(data.personData), + Some(VehicleCategory.Bike) + ) pipeTo self + // If we're on a walk based tour and have an egress vehicle defined we NEED to bring it home + + case (_, None, Some(WALK_BASED)) if currentTourStrategy.tourVehicle.isDefined && isLastTripWithinTour(nextAct) => + self ! MobilityStatusResponse( + Vector(beamVehicles(currentTourStrategy.tourVehicle.get)), + getCurrentTriggerIdOrGenerate + ) + // Finally, if we're starting from scratch, request all available vehicles + case (data: ChoosesModeData, None, _) => + implicit val executionContext: ExecutionContext = context.system.dispatcher + requestAvailableVehicles( + vehicleFleets, + data.currentLocation, + currentActivity(data.personData) + ) pipeTo self // Otherwise, send empty list to self - case _ => + case ( + _: ChoosesModeData, + Some(CAV | RIDE_HAIL | RIDE_HAIL_POOLED | RIDE_HAIL_TRANSIT | WALK | WALK_TRANSIT), + _ + ) => + self ! MobilityStatusResponse(Vector(), getCurrentTriggerIdOrGenerate) + case (_, tripModeOption, tourModeOption) => + logger.error( + s"Actor has trip mode $tripModeOption and tour " + + s"mode $tourModeOption, which shouldn't ever happen" + ) self ! MobilityStatusResponse(Vector(), getCurrentTriggerIdOrGenerate) } } @@ -302,58 +255,232 @@ 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)) - // 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 availableModes: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes) + val personData = choosesModeData.personData + val nextAct = nextActivity(personData).get val bodyStreetVehicle = createBodyStreetVehicle(currentPersonLocation) - val nextAct = nextActivity(choosesModeData.personData).get val departTime = _currentTick.get - var availablePersonalStreetVehicles = - correctedCurrentTourMode match { - case None | Some(CAR | BIKE) => - // In these cases, a personal vehicle will be involved, but filter out teleportation vehicles - newlyAvailableBeamVehicles.filterNot(v => BeamVehicle.isSharedTeleportationVehicle(v.id)) - case Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION) => - // In these cases, also include teleportation vehicles - 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) { - newlyAvailableBeamVehicles - } else { - Vector() - } + // Note: This in theory should be duplicative of the tourModeChoiceStrategy in persondata, but for some reason + // it's not always being copied over correctly there (even though tour personal vehicle is). So, this is + // duplicative, but the long run goal should be to remove trip/tour mode and personal vehicle from persondata + // anyway and just keep it in the experiencedBeamPlan + val currentTourStrategy = _experiencedBeamPlan.getTourStrategy[TourModeChoiceStrategy](nextAct) + val currentTripStrategy = _experiencedBeamPlan.getTripStrategy[TripModeChoiceStrategy](nextAct) + if (currentTripStrategy.mode != personData.currentTripMode) { + logger.debug("Why are the trip mode from the beamPlan and personData different?") + } + + var currentTripMode = (currentTripStrategy.mode, personData.currentTripMode) match { + case (None, None) => None + case (Some(strategyMode), None) => + Some(strategyMode) + case (Some(strategyMode), Some(dataMode)) if strategyMode == dataMode => + Some(strategyMode) + case (None, Some(dataMode)) => + val updatedTripStrategy = + TripModeChoiceStrategy(Some(dataMode)) + _experiencedBeamPlan.putStrategy(_experiencedBeamPlan.getTripContaining(nextAct), updatedTripStrategy) + Some(dataMode) + case _ => + log.error( + "Unexpected behavior" + ) // This can happen during replanning where tripModeChocieStrategy is none but trip mode stays + None + } + + var availablePersonalStreetVehicles = { + currentTourStrategy.tourVehicle match { + case Some(vehId) => + newlyAvailableBeamVehicles.filter(_.id == vehId) case _ => - Vector() + currentTripMode match { + case None | Some(CAR | BIKE) => + // In these cases, a personal vehicle will be involved, but filter out teleportation vehicles + newlyAvailableBeamVehicles.filterNot(v => BeamVehicle.isSharedTeleportationVehicle(v.id)) + case Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION) => + // In these cases, also include teleportation vehicles + newlyAvailableBeamVehicles + case Some(DRIVE_TRANSIT | BIKE_TRANSIT) => + if (isFirstOrLastTripWithinTour(nextAct)) { + newlyAvailableBeamVehicles + } else { + Vector() + } + case _ => + Vector() + } + } + } + + val availableVehicleFromParentTour = currentTourStrategy.tourMode match { + case Some(_) => Vector() + case None => + personData.currentTourPersonalVehicle.map(vehId => beamVehicles(vehId)).toVector + } + //TODO: Check logic: What if we're on a new subtour, so we already have a currentTourVehicle from the parent tour + availablePersonalStreetVehicles ++= availableVehicleFromParentTour + +// var availablePersonalStreetVehicles = { +// currentTripMode match { +// case None | Some(CAR | BIKE) => +// // In these cases, a personal vehicle will be involved, but filter out teleportation vehicles +// newlyAvailableBeamVehicles.filterNot(v => BeamVehicle.isSharedTeleportationVehicle(v.id)) +// case Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION) => +// // In these cases, also include teleportation vehicles +// newlyAvailableBeamVehicles +// case Some(DRIVE_TRANSIT | BIKE_TRANSIT) => +// if (isFirstOrLastTripWithinTour(nextAct)) { +// newlyAvailableBeamVehicles +// } else { +// Vector() +// } +// case _ => +// Vector() +// } +// } + + val (chosenCurrentTourMode, chosenCurrentTourPersonalVehicle) = + currentTourStrategy.tourMode match { + case Some(tourMode) => (Some(tourMode), currentTourStrategy.tourVehicle) + case None => + currentTripMode match { + case None => + val availablePersonalVehicleModes = + availablePersonalStreetVehicles.map(x => x.streetVehicle.mode).distinct + val availableFirstAndLastLegModes = + availablePersonalVehicleModes.flatMap(x => BeamTourMode.enabledModes.get(x)).flatten + val modesToQuery = + (availablePersonalVehicleModes ++ BeamMode.nonPersonalVehicleModes ++ availableFirstAndLastLegModes).distinct + .intersect(availableModes) + val dummyVehicleType = beamScenario.vehicleTypes(dummyRHVehicle.vehicleTypeId) + val currentTour = _experiencedBeamPlan.getTourContaining(nextAct) + val tourModeCosts = beamServices.skims.od_skimmer.getTourModeCosts( + modesToQuery, + currentTour, + dummyRHVehicle.vehicleTypeId, + dummyVehicleType, + beamScenario.fuelTypePrices(dummyVehicleType.primaryFuelType) + ) + val modeToTourMode = + BeamTourMode.values + .map(tourMode => + tourMode -> tourMode + .allowedBeamModesGivenAvailableVehicles(availablePersonalStreetVehicles, false) + .intersect(modesToQuery) + ) + .toMap + val firstAndLastTripModeToTourMode = BeamTourMode.values + .map(tourMode => + tourMode -> tourMode + .allowedBeamModesGivenAvailableVehicles(availablePersonalStreetVehicles, true) + .intersect(modesToQuery) + ) + .toMap + val tourModeUtils = tourModeChoiceCalculator.tourExpectedMaxUtility( + tourModeCosts, + modeChoiceCalculator.modeChoiceLogit, + modeToTourMode, + Some(firstAndLastTripModeToTourMode) + ) + val out = tourModeChoiceCalculator(tourModeUtils) + // We need to keep track of the chosen vehicle Id in PersonData so that we can release it and + // potentially give up on our tour mode choice if a route can't be constructed + val chosenTourVehicle: Option[Id[BeamVehicle]] = out match { + case Some(CAR_BASED) => + availablePersonalStreetVehicles + .find(veh => + (veh.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Car) & (!veh.vehicle.isSharedVehicle) + ) + .map(_.id) + case Some(BIKE_BASED) => + availablePersonalStreetVehicles + .find(veh => + (veh.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Bike) & (!veh.vehicle.isSharedVehicle) + ) + .map(_.id) + case _ => + None + } + + val tourModeChoiceEvent = new TourModeChoiceEvent( + departTime.toDouble, + this.id, + out.map(_.value).getOrElse(""), + currentTour, + availablePersonalStreetVehicles, + modeToTourMode, + tourModeUtils, + modesToQuery, + currentActivity(personData) + ) + eventsManager.processEvent(tourModeChoiceEvent) + (out, chosenTourVehicle) + case Some(tripMode) => + // If trip mode is already set, determine tour mode from that and available vehicles (sticking + // with walk based tour if the only available vehicles are shared) + val (chosenTourMode, tourVehicle) = getTourMode(tripMode, availablePersonalStreetVehicles) + val updatedTourStrategy = + TourModeChoiceStrategy(chosenTourMode, tourVehicle.map(_.id)) + _experiencedBeamPlan.putStrategy(_experiencedBeamPlan.getTourContaining(nextAct), updatedTourStrategy) + + val tourModeChoiceEvent = new TourModeChoiceEvent( + departTime.toDouble, + this.id, + chosenTourMode.map(_.value).getOrElse(""), + _experiencedBeamPlan.getTourContaining(nextAct), + availablePersonalStreetVehicles, + Map.empty[BeamTourMode, Seq[BeamMode]], + Map.empty[BeamTourMode, Double], + Vector(tripMode), + currentActivity(personData) + ) + eventsManager.processEvent(tourModeChoiceEvent) + (chosenTourMode, tourVehicle.map(_.id)) + } + } + chosenCurrentTourMode match { + case Some(CAR_BASED) + if !availablePersonalStreetVehicles + .exists(_.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Car) => + logger.error("We're on a car based tour without any cars -- this is bad!") + case Some(BIKE_BASED) + if !availablePersonalStreetVehicles + .exists(_.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Bike) => + logger.error("We're on a bike based tour without any bikes -- this is bad!") + case _ => + } + + val availableModesGivenTourMode = getAvailableModesGivenTourMode( + availableModes, + availablePersonalStreetVehicles, + chosenCurrentTourMode, + nextAct, + personData.currentTourPersonalVehicle + ) + + if (availableModesGivenTourMode.length == 1) { + logger.debug( + "Only one option (${availableModesGivenTourMode.head.value}) available so let's save some effort " + + "and only query routes for that mode" + ) + currentTripMode = availableModesGivenTourMode.headOption + } + def makeRequestWith( withTransit: Boolean, vehicles: Vector[StreetVehicle], streetVehiclesIntermodalUse: IntermodalUse = Access, - possibleEgressVehicles: IndexedSeq[StreetVehicle] = IndexedSeq.empty + possibleEgressVehicles: IndexedSeq[StreetVehicle] = IndexedSeq.empty, + departureBuffer: Int = 0 ): Unit = { router ! RoutingRequest( currentPersonLocation.loc, nextAct.getCoord, - departTime, + departTime + departureBuffer, withTransit, Some(id), vehicles, @@ -405,7 +532,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) @@ -415,14 +542,13 @@ trait ChoosesMode { } } - val hasRideHail = availableModes.contains(RIDE_HAIL) + val hasRideHail = availableModesGivenTourMode.contains(RIDE_HAIL) var responsePlaceholders = ChoosesModeResponsePlaceholders() var requestId: Option[Int] = None // Form and send requests - var householdVehiclesWereNotAvailable = false // to replan when personal vehicles are not available - correctedCurrentTourMode match { + currentTripMode match { case None => if (hasRideHail) { responsePlaceholders = makeResponsePlaceholders( @@ -438,9 +564,28 @@ trait ChoosesMode { responsePlaceholders = makeResponsePlaceholders(withRouting = true) requestId = None } + + // If you dont have mode pre-chosen, you can only use personal vehicles on vehicle based tours -- if you're + // on a walk based tour, you can used shared vehicles all the time and personal vehicles for access/egress + val availableStreetVehiclesGivenTourMode = newlyAvailableBeamVehicles.map { vehicleOrToken => + val isPersonalVehicle = { + !vehicleOrToken.vehicle.isSharedVehicle && + !BeamVehicle.isSharedTeleportationVehicle(vehicleOrToken.vehicle.id) + } + + chosenCurrentTourMode match { + case Some(BIKE_BASED) if isPersonalVehicle => vehicleOrToken.streetVehicle + case Some(CAR_BASED) if isPersonalVehicle => vehicleOrToken.streetVehicle + case Some(WALK_BASED) if !isPersonalVehicle => vehicleOrToken.streetVehicle + case Some(WALK_BASED) if isPersonalVehicle && isFirstOrLastTripWithinTour(nextAct) => + vehicleOrToken.streetVehicle + case _ => dummyRHVehicle.copy(locationUTM = currentPersonLocation) + } + } :+ bodyStreetVehicle + makeRequestWith( - withTransit = availableModes.exists(_.isTransit), - newlyAvailableBeamVehicles.map(_.streetVehicle) :+ bodyStreetVehicle, + withTransit = availableModesGivenTourMode.exists(_.isTransit), + availableStreetVehiclesGivenTourMode, possibleEgressVehicles = dummySharedVehicles ) case Some(WALK) => @@ -448,10 +593,14 @@ trait ChoosesMode { makeRequestWith(withTransit = true, Vector(bodyStreetVehicle)) case Some(WALK_TRANSIT) => responsePlaceholders = makeResponsePlaceholders(withRouting = true) - makeRequestWith(withTransit = true, Vector(bodyStreetVehicle)) + makeRequestWith( + withTransit = true, + Vector(bodyStreetVehicle), + departureBuffer = personData.numberOfReplanningAttempts * 5 + ) 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) @@ -463,7 +612,7 @@ trait ChoosesMode { .map(car_vehicle => car_vehicle.copy(mode = CAR_HOV3)) makeRequestWith(withTransit = false, vehicles :+ bodyStreetVehicle) responsePlaceholders = makeResponsePlaceholders(withRouting = true) - case Some(tourMode @ (CAR | BIKE)) => + case Some(tripMode @ (CAR | BIKE)) => val maybeLeg = _experiencedBeamPlan.getPlanElements .get(_experiencedBeamPlan.getPlanElements.indexOf(nextAct) - 1) match { case l: Leg => Some(l) @@ -472,29 +621,33 @@ trait ChoosesMode { maybeLeg.map(_.getRoute) match { case Some(networkRoute: NetworkRoute) => val maybeVehicle = - filterStreetVehiclesForQuery(newlyAvailableBeamVehicles.map(_.streetVehicle), tourMode).headOption + filterStreetVehiclesForQuery(newlyAvailableBeamVehicles.map(_.streetVehicle), tripMode).headOption maybeVehicle match { - case Some(vehicle) => + case Some(vehicle) if vehicle.mode == tripMode => router ! matsimLegToEmbodyRequest( networkRoute, vehicle, departTime, - tourMode, + tripMode, beamServices, choosesModeData.currentLocation.loc, nextAct.getCoord, triggerId ) responsePlaceholders = makeResponsePlaceholders(withRouting = true) + case Some(vehicle) => + logger.error(s"Agent ${this.id} is on a ${tripMode.value} trip but has vehicle ${vehicle.toString}") + makeRequestWith(withTransit = false, Vector(bodyStreetVehicle)) + responsePlaceholders = makeResponsePlaceholders(withRouting = true) case _ => makeRequestWith(withTransit = false, Vector(bodyStreetVehicle)) responsePlaceholders = makeResponsePlaceholders(withRouting = true) } case _ => - val vehicles = filterStreetVehiclesForQuery(newlyAvailableBeamVehicles.map(_.streetVehicle), tourMode) + val vehicles = filterStreetVehiclesForQuery(newlyAvailableBeamVehicles.map(_.streetVehicle), tripMode) .map(vehicle => { vehicle.mode match { - case CAR => vehicle.copy(mode = tourMode) + case CAR => vehicle.copy(mode = tripMode) case _ => vehicle } }) @@ -524,38 +677,57 @@ 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(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) - // we do not send parking inquiry here, instead we wait for drive_transit route to come back and we use - // actual location of transit station + 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) + // we do not send parking inquiry here, instead we wait for drive_transit route to come back and we use + // actual location of transit station + makeRequestWith( + withTransit = true, + filterStreetVehiclesForQuery(newlyAvailableBeamVehicles.map(_.streetVehicle), vehicleMode) + :+ bodyStreetVehicle + ) + responsePlaceholders = makeResponsePlaceholders(withRouting = true) + } else { + // Reset available vehicles so we don't release our car that we've left during this replanning + availablePersonalStreetVehicles = Vector() + makeRequestWith(withTransit = true, Vector(bodyStreetVehicle)) + responsePlaceholders = makeResponsePlaceholders(withRouting = true) + } + 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, - filterStreetVehiclesForQuery(newlyAvailableBeamVehicles.map(_.streetVehicle), vehicleMode) - :+ bodyStreetVehicle + vehicles = Vector(bodyStreetVehicle), + streetVehiclesIntermodalUse = Access, + possibleEgressVehicles = newlyAvailableBeamVehicles + .map(_.streetVehicle) + .filter(_.id == currentTourPersonalVehicle) ) responsePlaceholders = makeResponsePlaceholders(withRouting = true) - case (LastTripIndex, Some(currentTourPersonalVehicle)) => - // At the end of the tour, only drive home a vehicle that we have also taken away from there. + case (`lastTripIndex`, None) => + // TODO: Is there a way to query egress vehicles near the destination? makeRequestWith( withTransit = true, newlyAvailableBeamVehicles - .map(_.streetVehicle) - .filter(_.id == currentTourPersonalVehicle) :+ bodyStreetVehicle, - streetVehiclesIntermodalUse = Egress + .filter(veh => (veh.streetVehicle.mode == vehicleMode) && veh.vehicle.isSharedVehicle) + .map(_.streetVehicle) :+ bodyStreetVehicle ) responsePlaceholders = makeResponsePlaceholders(withRouting = true) case _ => - // Reset available vehicles so we don't release our car that we've left during this replanning - availablePersonalStreetVehicles = Vector() - makeRequestWith(withTransit = true, Vector(bodyStreetVehicle)) + // Still go for it, because maybe there are some shared vehicles along the route + makeRequestWith( + withTransit = true, + newlyAvailableBeamVehicles + .filter(veh => (veh.streetVehicle.mode == vehicleMode) && veh.vehicle.isSharedVehicle) + .map(_.streetVehicle) + :+ bodyStreetVehicle + ) responsePlaceholders = makeResponsePlaceholders(withRouting = true) } case Some(RIDE_HAIL | RIDE_HAIL_POOLED) if choosesModeData.isWithinTripReplanning => @@ -578,8 +750,18 @@ trait ChoosesMode { logDebug(m.toString) } val newPersonData = choosesModeData.copy( - personData = choosesModeData.personData - .copy(currentTourMode = if (householdVehiclesWereNotAvailable) None else correctedCurrentTourMode), + personData = personData + .copy( + currentTripMode = currentTripMode, + currentTourMode = chosenCurrentTourMode, + currentTourPersonalVehicle = chosenCurrentTourMode match { + // if they're on a walk based tour we let them keep access to whatever personal vehicle they used on the + // first leg or in a parent tour + case Some(WALK_BASED) => choosesModeData.personData.currentTourPersonalVehicle + // Otherwise they keep track of the chosen vehicle + case _ => chosenCurrentTourPersonalVehicle + } + ), availablePersonalStreetVehicles = availablePersonalStreetVehicles, allAvailableStreetVehicles = newlyAvailableBeamVehicles, routingResponse = responsePlaceholders.routingResponse, @@ -839,6 +1021,24 @@ trait ChoosesMode { ) } using completeChoiceIfReady) + private def correctCurrentTripModeAccordingToRules( + currentTripMode: Option[BeamMode], + personData: BasePersonData, + availableModes: Seq[BeamMode] + ): Option[BeamMode] = { + val replanningIsAvailable = + personData.numberOfReplanningAttempts < beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.maximumNumberOfReplanningAttempts + currentTripMode 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 + } + } + private def makeParkingInquiries( choosesModeData: ChoosesModeData, itineraries: Seq[EmbodiedBeamTrip] @@ -1128,7 +1328,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 @@ -1165,6 +1365,41 @@ trait ChoosesMode { } } + def getAvailableModesGivenTourMode( + availableModes: Seq[BeamMode], + availablePersonalStreetVehicles: Vector[VehicleOrToken], + currentTourMode: Option[BeamTourMode], + nextActivity: Activity, + maybeTourPersonalVehicle: Option[Id[BeamVehicle]] = None + ): Seq[BeamMode] = { + availableModes.intersect(currentTourMode match { + case Some(WALK_BASED) + if availablePersonalStreetVehicles + .exists(_.vehicle.isMustBeDrivenHome) && isLastTripWithinTour(nextActivity) => + val requiredEgressModes = availablePersonalStreetVehicles.flatMap { + case veh: ActualVehicle => + maybeTourPersonalVehicle match { + case Some(tourVehicleId) if veh.id == tourVehicleId => + BeamTourMode.enabledModes.get(veh.streetVehicle.mode) + case None => + logger.warn(s"Why does vehicle ${veh.id} need to be driven home when it's not a tour vehicle?") + BeamTourMode.enabledModes.get(veh.streetVehicle.mode) ++ Some(WALK_BASED.allowedBeamModes) + } + case _ => None + }.flatten + if (requiredEgressModes.size > 1) { + logger.warn("How did we end up in the situation with multiple vehicles that must be driven home?") + } + requiredEgressModes + case Some(tourMode) => + tourMode.allowedBeamModesGivenAvailableVehicles( + availablePersonalStreetVehicles, + isFirstOrLastTripWithinTour(nextActivity) + ) + case None => BeamMode.allModes + }) + } + def mustBeDrivenHome(vehicle: VehicleOrToken): Boolean = { vehicle match { case ActualVehicle(beamVehicle) => @@ -1192,7 +1427,7 @@ trait ChoosesMode { Some(rideHail2TransitEgressResult), _, _, - _, + allAvailableStreetVehicles, _, _, Some(cavTripLegs), @@ -1221,7 +1456,7 @@ trait ChoosesMode { ) <= travelProposal.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, @@ -1261,20 +1496,28 @@ trait ChoosesMode { parkingResponses ) ++ rideHail2TransitIinerary.toVector - val availableModesForTrips: Seq[BeamMode] = availableModesForPerson(matsimPlan.getPerson) - .filterNot(mode => choosesModeData.excludeModes.contains(mode)) + def isAvailable(mode: BeamMode): Boolean = combinedItinerariesForChoice.exists(_.tripClassifier == mode) - val filteredItinerariesForChoice = choosesModeData.personData.currentTourMode match { + choosesModeData.personData.currentTripMode match { + case Some(expectedMode) if !isAvailable(expectedMode) => + eventsManager.processEvent( + createFailedODSkimmerEvent(currentActivity(personData), nextAct, expectedMode) + ) + case _ => + } + + val availableModesForTrips = getAvailableModesGivenTourMode( + availableModesForPerson(matsimPlan.getPerson, choosesModeData.excludeModes), + choosesModeData.availablePersonalStreetVehicles, + choosesModeData.personData.currentTourMode, + nextAct, + choosesModeData.personData.currentTourPersonalVehicle + ) + + 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(nextAct), personData.hasDeparted) match { + case (true, false) => combinedItinerariesForChoice.filter(_.tripClassifier == mode) case _ => combinedItinerariesForChoice.filter(trip => @@ -1296,7 +1539,13 @@ trait ChoosesMode { } val itinerariesOfCorrectMode = - filteredItinerariesForChoice.filter(itin => availableModesForTrips.contains(itin.tripClassifier)) + filteredItinerariesForChoice + .filter(itin => availableModesForTrips.contains(itin.tripClassifier)) + .filterNot(itin => + itin.vehiclesInTrip + .filterNot(_.toString.startsWith("body")) + .exists(veh => personData.failedTrips.flatMap(_.vehiclesInTrip).contains(veh)) + ) val attributesOfIndividual = matsimPlan.getPerson.getCustomAttributes @@ -1304,6 +1553,13 @@ trait ChoosesMode { .asInstanceOf[AttributesOfIndividual] val availableAlts = Some(itinerariesOfCorrectMode.map(_.tripClassifier).mkString(":")) + def gotoFinishingModeChoice(chosenTrip: EmbodiedBeamTrip) = { + goto(FinishingModeChoice) using choosesModeData.copy( + pendingChosenTrip = Some(chosenTrip), + availableAlternatives = availableAlts + ) + } + modeChoiceCalculator( itinerariesOfCorrectMode, attributesOfIndividual, @@ -1312,27 +1568,9 @@ trait ChoosesMode { Some(matsimPlan.getPerson) ) match { case Some(chosenTrip) => - filteredItinerariesForChoice.foreach { - case possibleTrip - if (possibleTrip != chosenTrip) && - beamScenario.beamConfig.beam.exchange.output.sendNonChosenTripsToSkimmer => - generateSkimData( - possibleTrip.legs.lastOption.map(_.beamLeg.endTime).getOrElse(_currentTick.get), - possibleTrip, - failedTrip = false, - personData.currentActivityIndex, - currentActivity(personData), - nextActivity(personData) - ) - case _ => - } - 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 @@ -1354,10 +1592,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, @@ -1366,82 +1601,135 @@ trait ChoosesMode { body.toStreetVehicle, geo ) - goto(FinishingModeChoice) using choosesModeData.copy( - pendingChosenTrip = Some(bushwhackingTrip), - availableAlternatives = availableAlts - ) + gotoFinishingModeChoice(bushwhackingTrip) } - case Some(mode) => - val currentAct = currentActivity(personData) - val odFailedSkimmerEvent = createFailedODSkimmerEvent(currentAct, nextAct, mode) - val possibleActivitySimModes = - determineActivitySimPathTypesFromBeamMode(choosesModeData.personData.currentTourMode, currentAct) - eventsManager.processEvent( - odFailedSkimmerEvent + case Some(CAR) if allAvailableStreetVehicles.isEmpty => + logger.warn( + "Ended up stuck without a car despite having car in plans, so sending the request " + + "back through in order to create an emergency vehicle" ) - if (beamServices.beamConfig.beam.exchange.output.activitySimSkimsEnabled) { - createFailedActivitySimSkimmerEvent(odFailedSkimmerEvent, possibleActivitySimModes).foreach(ev => - eventsManager.processEvent(ev) - ) - } - eventsManager.processEvent( - new ReplanningEvent( - _currentTick.get, - Id.createPersonId(id), - getReplanningReasonFrom( - choosesModeData.personData, - ReservationErrorCode.RouteNotAvailableForChosenMode.entryName - ), - choosesModeData.currentLocation.loc.getX, - choosesModeData.currentLocation.loc.getY, - nextAct.getCoord.getX, - nextAct.getCoord.getY + goto(ChoosingMode) + case Some(mode) => + val correctedTripMode = correctCurrentTripModeAccordingToRules(None, personData, availableModesForTrips) + if (correctedTripMode != personData.currentTripMode) { + val nextActLoc = nextActivity(choosesModeData.personData).get.getCoord + val currentAct = currentActivity(personData) + val odFailedSkimmerEvent = createFailedODSkimmerEvent(currentAct, nextAct, mode) + val possibleActivitySimModes = + determineActivitySimPathTypesFromBeamMode(choosesModeData.personData.currentTripMode, currentAct) + eventsManager.processEvent( + odFailedSkimmerEvent ) - ) - //give another chance to make a choice without predefined mode - val availableVehicles = - if (mode.isTeleportation) - //we need to remove our teleportation vehicle since we cannot use it if it's not a teleportation mode - choosesModeData.allAvailableStreetVehicles.filterNot(vehicle => - BeamVehicle.isSharedTeleportationVehicle(vehicle.id) + if (beamServices.beamConfig.beam.exchange.output.activitySimSkimsEnabled) { + createFailedActivitySimSkimmerEvent(odFailedSkimmerEvent, possibleActivitySimModes).foreach(ev => + eventsManager.processEvent(ev) ) - else choosesModeData.allAvailableStreetVehicles - self ! MobilityStatusResponse(availableVehicles, getCurrentTriggerId.get) - stay() using ChoosesModeData( - personData = personData.copy(currentTourMode = None), - currentLocation = choosesModeData.currentLocation, - excludeModes = choosesModeData.excludeModes - ) + } + eventsManager.processEvent( + new ReplanningEvent( + _currentTick.get, + Id.createPersonId(id), + getReplanningReasonFrom( + choosesModeData.personData, + ReservationErrorCode.RouteNotAvailableForChosenMode.entryName + ), + choosesModeData.currentLocation.loc.getX, + choosesModeData.currentLocation.loc.getY, + nextActLoc.getX, + nextActLoc.getY + ) + ) //give another chance to make a choice without predefined mode + //TODO: Do we need to do anything with tour mode here? + 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) = { + val (newTourVehicle, abandonedVehicle) = choosesModeData.personData.currentTourPersonalVehicle match { + case Some(id) if beamVehicles.contains(id) => + logger.warn( + s"Abandoning vehicle $id because no return ${choosesModeData.personData.currentTripMode} " + + s"itinerary is available" + ) + val vehicle = beamVehicles(id).vehicle + vehicle.setMustBeDrivenHome(false) + beamVehicles.remove(vehicle.id) + vehicle.getManager.get ! ReleaseVehicle(vehicle, getCurrentTriggerId.get) + (None, true) + case _ => (None, false) + } + if (choosesModeData.personData.currentTripMode.get.isTeleportation) { + //we need to remove our teleportation vehicle since we cannot use it if it's not a teleportation mode { + val availableVehicles = choosesModeData.allAvailableStreetVehicles.filterNot(vehicle => + BeamVehicle.isSharedTeleportationVehicle(vehicle.id) + ) + self ! MobilityStatusResponse(availableVehicles, getCurrentTriggerId.get) + stay() + } else { + goto(ChoosingMode) + } using choosesModeData.copy( + personData = choosesModeData.personData.copy( + currentTripMode = None, + currentTourMode = if (currentActivity(choosesModeData.personData).getType.equalsIgnoreCase("Home")) { + None + } else if (abandonedVehicle) { + // Give up our tour mode too so if we're on a car tour and can't find our car we don't just keep creating + // new emergency ones + val updatedTourStrategy = + TourModeChoiceStrategy(Some(WALK_BASED), None) + _experiencedBeamPlan.putStrategy( + _experiencedBeamPlan.getTourContaining(nextActivity(choosesModeData.personData).get), + updatedTourStrategy + ) + Some(WALK_BASED) + } else { + choosesModeData.personData.currentTourMode + }, + currentTourPersonalVehicle = newTourVehicle, + numberOfReplanningAttempts = choosesModeData.personData.numberOfReplanningAttempts + 1 + ), + currentLocation = choosesModeData.currentLocation, + excludeModes = choosesModeData.excludeModes ++ choosesModeData.personData.currentTripMode + ) + + } + private def createFailedODSkimmerEvent( originActivity: Activity, destinationActivity: Activity, @@ -1506,145 +1794,229 @@ trait ChoosesMode { actualVehiclesToBeParked.forall(parkingResponses.contains) } - when(FinishingModeChoice, stateTimeout = Duration.Zero) { case Event(StateTimeout, data: ChoosesModeData) => - val pendingTrip = data.pendingChosenTrip.get - val (tick, triggerId) = releaseTickAndTriggerId() - val chosenTrip = - if ( - pendingTrip.tripClassifier.isTransit - && pendingTrip.legs.head.beamLeg.startTime > tick - ) { - //we need to start trip as soon as our activity finishes (current tick) in order to - //correctly show waiting time for the transit in the OD skims - val activityEndTime = currentActivity(data.personData).getEndTime - val legStartTime = Math.max(tick, activityEndTime) - pendingTrip.updatePersonalLegsStartTime(legStartTime.toInt) - } else { - pendingTrip - } + when(FinishingModeChoice, stateTimeout = Duration.Zero) { + case Event(_: RideHailResponse, data: ChoosesModeData) => + logger.warn("Recieved a ride hail response even though we'd already moved on after choosing our mode") + stay using data + case Event(StateTimeout, data: ChoosesModeData) => + val pendingTrip = data.pendingChosenTrip.get + val (tick, triggerId) = releaseTickAndTriggerId() + val chosenTrip = + if ( + pendingTrip.tripClassifier.isTransit + && pendingTrip.legs.head.beamLeg.startTime > tick + ) { + //we need to start trip as soon as our activity finishes (current tick) in order to + //correctly show waiting time for the transit in the OD skims + val activityEndTime = currentActivity(data.personData).getEndTime + val legStartTime = Math.max(tick, activityEndTime) + pendingTrip.updatePersonalLegsStartTime(legStartTime.toInt) + } else { + pendingTrip + } - // Write start and end links of chosen route into Activities. - // We don't check yet whether the incoming and outgoing routes agree on the link an Activity is on. - // Our aim should be that every transition from a link to another link be accounted for. - val headOpt = chosenTrip.legs.headOption - .flatMap(_.beamLeg.travelPath.linkIds.headOption) - val lastOpt = chosenTrip.legs.lastOption - .flatMap(_.beamLeg.travelPath.linkIds.lastOption) - if (headOpt.isDefined && lastOpt.isDefined) { - _experiencedBeamPlan - .activities(data.personData.currentActivityIndex) - .setLinkId(Id.createLinkId(headOpt.get)) - _experiencedBeamPlan - .activities(data.personData.currentActivityIndex + 1) - .setLinkId(Id.createLinkId(lastOpt.get)) - } else { - val origin = beamServices.geo.utm2Wgs( + // Write start and end links of chosen route into Activities. + // We don't check yet whether the incoming and outgoing routes agree on the link an Activity is on. + // Our aim should be that every transition from a link to another link be accounted for. + val headOpt = chosenTrip.legs.headOption + .flatMap(_.beamLeg.travelPath.linkIds.headOption) + val lastOpt = chosenTrip.legs.lastOption + .flatMap(_.beamLeg.travelPath.linkIds.lastOption) + if (headOpt.isDefined && lastOpt.isDefined) { _experiencedBeamPlan .activities(data.personData.currentActivityIndex) - .getCoord - ) - val destination = beamServices.geo.utm2Wgs( + .setLinkId(Id.createLinkId(headOpt.get)) _experiencedBeamPlan .activities(data.personData.currentActivityIndex + 1) - .getCoord - ) - val linkRadiusMeters = beamScenario.beamConfig.beam.routing.r5.linkRadiusMeters - _experiencedBeamPlan - .activities(data.personData.currentActivityIndex) - .setLinkId( - Id.createLinkId( - beamServices.geo.getNearestR5Edge(transportNetwork.streetLayer, origin, linkRadiusMeters) - ) + .setLinkId(Id.createLinkId(lastOpt.get)) + } else { + val origin = beamServices.geo.utm2Wgs( + _experiencedBeamPlan + .activities(data.personData.currentActivityIndex) + .getCoord ) - _experiencedBeamPlan - .activities(data.personData.currentActivityIndex + 1) - .setLinkId( - Id.createLinkId( - beamServices.geo.getNearestR5Edge(transportNetwork.streetLayer, destination, linkRadiusMeters) - ) + val destination = beamServices.geo.utm2Wgs( + _experiencedBeamPlan + .activities(data.personData.currentActivityIndex + 1) + .getCoord ) - } + val linkRadiusMeters = beamScenario.beamConfig.beam.routing.r5.linkRadiusMeters + _experiencedBeamPlan + .activities(data.personData.currentActivityIndex) + .setLinkId( + Id.createLinkId( + beamServices.geo.getNearestR5Edge(transportNetwork.streetLayer, origin, linkRadiusMeters) + ) + ) + _experiencedBeamPlan + .activities(data.personData.currentActivityIndex + 1) + .setLinkId( + Id.createLinkId( + beamServices.geo.getNearestR5Edge(transportNetwork.streetLayer, destination, linkRadiusMeters) + ) + ) + } - val tripId = Option( - _experiencedBeamPlan.activities(data.personData.currentActivityIndex).getAttributes.getAttribute("trip_id") - ).getOrElse("").toString - - val modeChoiceEvent = new ModeChoiceEvent( - tick, - id, - chosenTrip.tripClassifier.value, - data.personData.currentTourMode.map(_.value).getOrElse(""), - data.expectedMaxUtilityOfLatestChoice.getOrElse[Double](Double.NaN), - _experiencedBeamPlan.activities(data.personData.currentActivityIndex).getLinkId.toString, - data.availableAlternatives.get, - data.availablePersonalStreetVehicles.nonEmpty, - chosenTrip.legs.view.map(_.beamLeg.travelPath.distanceInM).sum, - _experiencedBeamPlan.tourIndexOfElement(nextActivity(data.personData).get), - chosenTrip, - _experiencedBeamPlan.activities(data.personData.currentActivityIndex).getType, - nextActivity(data.personData).get.getType, - tripId - ) - eventsManager.processEvent(modeChoiceEvent) - - data.personData.currentTourMode match { - case Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION) => - scheduler ! CompletionNotice( - triggerId, - Vector( - ScheduleTrigger( - PersonDepartureTrigger(math.max(chosenTrip.legs.head.beamLeg.startTime, tick)), - self + val tripId = Option( + _experiencedBeamPlan.activities(data.personData.currentActivityIndex).getAttributes.getAttribute("trip_id") + ).getOrElse("").toString + + val nextAct = nextActivity(data.personData).get + + val tourMode = data.personData.currentTourMode + + val modeChoiceEvent = new ModeChoiceEvent( + tick, + id, + chosenTrip.tripClassifier.value, + tourMode.map(_.value).getOrElse(""), + data.expectedMaxUtilityOfLatestChoice.getOrElse[Double](Double.NaN), + _experiencedBeamPlan.activities(data.personData.currentActivityIndex).getLinkId.toString, + data.availableAlternatives.get, + data.availablePersonalStreetVehicles.nonEmpty, + chosenTrip.legs.view.map(_.beamLeg.travelPath.distanceInM).sum, + _experiencedBeamPlan.tourIndexOfElement(nextAct), + chosenTrip, + _experiencedBeamPlan.activities(data.personData.currentActivityIndex).getType, + nextAct.getType, + tripId + ) + eventsManager.processEvent(modeChoiceEvent) + + data.personData.currentTripMode match { + case Some(mode) if mode.isTeleportation => + scheduler ! CompletionNotice( + triggerId, + Vector( + ScheduleTrigger( + PersonDepartureTrigger(math.max(chosenTrip.legs.head.beamLeg.startTime, tick)), + self + ) ) ) - ) - goto(Teleporting) using data.personData.copy( - currentTrip = Some(chosenTrip), - restOfCurrentTrip = List() - ) + val updatedTourStrategy = + TourModeChoiceStrategy(data.personData.currentTourMode, None) + val updatedTripStrategy = + TripModeChoiceStrategy(Some(chosenTrip.tripClassifier)) + _experiencedBeamPlan.putStrategy(_experiencedBeamPlan.getTripContaining(nextAct), updatedTripStrategy) + _experiencedBeamPlan.putStrategy(_experiencedBeamPlan.getTourContaining(nextAct), updatedTourStrategy) + + goto(Teleporting) using data.personData.copy( + currentTrip = Some(chosenTrip), + currentTourPersonalVehicle = None, + restOfCurrentTrip = List() + ) - case _ => - val (vehiclesUsed, vehiclesNotUsed) = data.availablePersonalStreetVehicles - .partition(vehicle => chosenTrip.vehiclesInTrip.contains(vehicle.id)) + case _ => + val (vehiclesUsed, vehiclesNotUsed) = data.availablePersonalStreetVehicles + .partition(vehicle => chosenTrip.vehiclesInTrip.contains(vehicle.id)) - var isCurrentPersonalVehicleVoided = false - vehiclesNotUsed.collect { case ActualVehicle(vehicle) => - data.personData.currentTourPersonalVehicle.foreach { currentVehicle => - if (currentVehicle == vehicle.id) { - logError( - s"Current tour vehicle is the same as the one being removed: $currentVehicle - ${vehicle.id} - $data" - ) - isCurrentPersonalVehicleVoided = true + vehiclesUsed.foreach { + case veh if !beamVehicles.contains(veh.id) => + logger.error("Why is a vehicle that is used not in beamVehicles") + case _ => + } + + var isCurrentPersonalVehicleVoided = false + vehiclesNotUsed.collect { case ActualVehicle(vehicle) => + data.personData.currentTourPersonalVehicle.foreach { currentVehicle => + // We allow people to keep personal vehicles on walk based tours for access/egress + if (currentVehicle == vehicle.id) { + if (data.personData.currentTourMode.contains(WALK_BASED) && !isFirstTripWithinTour(nextAct)) { + logger.debug( + s"We're keeping vehicle ${vehicle.id} even though it isn't used in this trip " + + s"because we need it for egress at the end of the tour" + ) + } else { + logError( + s"Current tour vehicle is the same as the one being removed: " + + s"$currentVehicle - ${vehicle.id} - $data" + ) + isCurrentPersonalVehicleVoided = true + vehicle.setMustBeDrivenHome(false) + beamVehicles.remove(vehicle.id) + vehicle.getManager.get ! ReleaseVehicle(vehicle, triggerId) + } + } else { + // Vehicle is neither used nor reserved for a return trip + beamVehicles.remove(vehicle.id) + vehicle.getManager.get ! ReleaseVehicle(vehicle, triggerId) + } } + } - beamVehicles.remove(vehicle.id) - vehicle.getManager.get ! ReleaseVehicle(vehicle, triggerId) - } - scheduler ! CompletionNotice( - triggerId, - Vector( - ScheduleTrigger( - PersonDepartureTrigger(math.max(chosenTrip.legs.head.beamLeg.startTime, tick)), - self + scheduler ! CompletionNotice( + triggerId, + Vector( + ScheduleTrigger( + PersonDepartureTrigger(math.max(chosenTrip.legs.head.beamLeg.startTime, tick)), + self + ) ) ) - ) - goto(WaitingForDeparture) using data.personData.copy( - currentTrip = Some(chosenTrip), - restOfCurrentTrip = chosenTrip.legs.toList, - currentTourMode = data.personData.currentTourMode - .orElse(Some(chosenTrip.tripClassifier)), - currentTourPersonalVehicle = + + val currentTourPersonalVehicle = { if (isCurrentPersonalVehicleVoided) - vehiclesUsed.headOption.filter(mustBeDrivenHome).map(_.id) - else + vehiclesUsed.headOption.filter(!_.vehicle.isSharedVehicle).map(_.id) + else { data.personData.currentTourPersonalVehicle - .orElse(vehiclesUsed.headOption.filter(mustBeDrivenHome).map(_.id)), - failedTrips = data.personData.failedTrips ++ data.personData.currentTrip - ) - } + .orElse( + vehiclesUsed.view + .filter(!_.vehicle.isSharedVehicle) + .find { veh => + (chosenTrip.tripClassifier, data.personData.currentTourMode) match { + case (_, Some(CAR_BASED)) => veh.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Car + case (_, Some(BIKE_BASED)) => + veh.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Bike + case (DRIVE_TRANSIT, _) => veh.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Car + case (BIKE_TRANSIT, _) => veh.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Bike + case _ => false + } + } + .map(_.id) + ) + } + } + + // Manually set that personal bike transit vehicles must be driven home because it's not handled in parking + currentTourPersonalVehicle match { + case Some(veh) + if chosenTrip.tripClassifier == BIKE_TRANSIT && isFirstTripWithinTour(nextAct) && !beamVehicles( + veh + ).vehicle.isSharedVehicle => + beamVehicles(veh).vehicle.setMustBeDrivenHome(true) + case Some(veh) + if chosenTrip.tripClassifier == BIKE_TRANSIT && isLastTripWithinTour(nextAct) && !beamVehicles( + veh + ).vehicle.isSharedVehicle => + beamVehicles(veh).vehicle.setMustBeDrivenHome(false) + case _ => + } + + val updatedTripStrategy = + TripModeChoiceStrategy(Some(chosenTrip.tripClassifier)) + _experiencedBeamPlan.putStrategy(_experiencedBeamPlan.getTripContaining(nextAct), updatedTripStrategy) + + //TODO: Only do this on first trip of tour unless something changed (e.g. we needed to abandon a vehicle) + // ---------- + val updatedTourStrategy = + TourModeChoiceStrategy( + data.personData.currentTourMode, + currentTourPersonalVehicle.filter(vehiclesUsed.contains) + ) + _experiencedBeamPlan.putStrategy(_experiencedBeamPlan.getTourContaining(nextAct), updatedTourStrategy) + // ---------- + + goto(WaitingForDeparture) using data.personData.copy( + currentTrip = Some(chosenTrip), + restOfCurrentTrip = chosenTrip.legs.toList, + currentTripMode = Some(chosenTrip.tripClassifier), + currentTourPersonalVehicle = currentTourPersonalVehicle, + failedTrips = data.personData.failedTrips ++ data.personData.currentTrip + ) + } } } diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala index 2b329fc9e7b..010a7b1e815 100644 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/DrivesVehicle.scala @@ -221,7 +221,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.currentTripMode.contains(HOV3_TELEPORTATION) => updateLatestObservedTick(tick) val dataForNextLegOrActivity: BasePersonData = data.copy( @@ -331,7 +331,6 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon } val numberOfPassengers: Int = calculateNumberOfPassengersBasedOnCurrentTourMode(data, currentLeg, riders) - val currentTourMode: Option[String] = getCurrentTourMode(data) val pte = PathTraversalEvent( tick, currentVehicleUnderControl, @@ -339,7 +338,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, @@ -499,16 +498,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, @@ -517,7 +508,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 @@ -557,7 +548,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, @@ -565,7 +555,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/agentsim/agents/modalbehaviors/ModeChoiceCalculator.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ModeChoiceCalculator.scala index c8a6e083f99..46de8281b66 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ModeChoiceCalculator.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ModeChoiceCalculator.scala @@ -1,11 +1,14 @@ package beam.agentsim.agents.modalbehaviors -import beam.agentsim.agents.choice.logit.LatentClassChoiceModel +import beam.agentsim.agents.choice.logit +import beam.agentsim.agents.choice.logit.{LatentClassChoiceModel, UtilityFunctionOperation} import beam.agentsim.agents.choice.logit.LatentClassChoiceModel.Mandatory +import beam.agentsim.agents.choice.logit.TourModeChoiceModel.TourModeParameters import beam.agentsim.agents.choice.mode._ import beam.agentsim.agents.vehicles.BeamVehicleType import beam.router.Modes.BeamMode import beam.router.Modes.BeamMode._ +import beam.router.TourModes.BeamTourMode import beam.router.model.{EmbodiedBeamLeg, EmbodiedBeamTrip} import beam.sim.BeamServices import beam.sim.config.{BeamConfig, BeamConfigHolder} @@ -24,6 +27,15 @@ trait ModeChoiceCalculator { val beamConfig: BeamConfig + val commonUtility: Map[String, UtilityFunctionOperation] = Map( + "cost" -> UtilityFunctionOperation("multiplier", -1) + ) + + val modeChoiceLogit = new logit.MultinomialLogit[BeamMode, String]( + _ => Option.empty, + commonUtility + ) + lazy val random: Random = new Random( beamConfig.matsim.modules.global.randomSeed ) diff --git a/src/main/scala/beam/agentsim/agents/parking/ChoosesParking.scala b/src/main/scala/beam/agentsim/agents/parking/ChoosesParking.scala index 42fcb31e100..2f98c03e70c 100755 --- a/src/main/scala/beam/agentsim/agents/parking/ChoosesParking.scala +++ b/src/main/scala/beam/agentsim/agents/parking/ChoosesParking.scala @@ -63,6 +63,14 @@ object ChoosesParking { restOfTrip: Option[List[EmbodiedBeamLeg]] ): Unit = { currentBeamVehicle.reservedStall.foreach { stall: ParkingStall => + if (!currentBeamVehicle.isSharedVehicle) { + nextActivity match { + case Some(act) if act.getType.equalsIgnoreCase("Home") => + currentBeamVehicle.setMustBeDrivenHome(false) + case _ => + currentBeamVehicle.setMustBeDrivenHome(true) + } + } currentBeamVehicle.useParkingStall(stall) val parkEvent = ParkingEvent( time = tick, diff --git a/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala b/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala index 09e7974156e..c41095d9e92 100755 --- a/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala +++ b/src/main/scala/beam/agentsim/agents/planning/BeamPlan.scala @@ -1,13 +1,20 @@ 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.{Strategy, TourModeChoiceStrategy, TripModeChoiceStrategy} +import beam.agentsim.agents.vehicles.BeamVehicle +import beam.router.Modes.{isPersonalVehicleMode, BeamMode} +import beam.router.TourModes.BeamTourMode +import org.matsim.api.core.v01.Id import org.matsim.api.core.v01.population._ import org.matsim.core.population.PopulationUtils import org.matsim.utils.objectattributes.attributable.Attributes import scala.collection.JavaConverters._ import scala.collection.mutable +import scala.reflect.ClassTag /** * BeamPlan @@ -28,11 +35,19 @@ object BeamPlan { def apply(matsimPlan: Plan): BeamPlan = { val beamPlan = new BeamPlan beamPlan.setPerson(matsimPlan.getPerson) - matsimPlan.getPlanElements.asScala.foreach { - case activity: Activity => - beamPlan.addActivity(activity) - case leg: Leg => - beamPlan.addLeg(leg) + matsimPlan.getPlanElements.asScala.headOption match { + case Some(a1: Activity) => beamPlan.addActivity(a1) + case _ => + } + matsimPlan.getPlanElements.asScala.sliding(2).foreach { + case mutable.Buffer(_: Activity, a2: Activity) => + beamPlan.addLeg(PopulationUtils.createLeg("")) + beamPlan.addActivity(a2) + case mutable.Buffer(a1: Activity, l1: Leg) => + beamPlan.addLeg(l1) + case mutable.Buffer(l1: Leg, a1: Activity) => + beamPlan.addActivity(a1) + case _ => } beamPlan.setScore(matsimPlan.getScore) beamPlan.setType(matsimPlan.getType) @@ -83,6 +98,8 @@ object BeamPlan { newPlan.setScore(plan.getScore) newPlan } + + def atHome(activity: Activity): Boolean = activity.getType.equalsIgnoreCase("home") } class BeamPlan extends Plan { @@ -90,37 +107,105 @@ class BeamPlan extends Plan { ////////////////////////////////////////////////////////////////////// // Beam-Specific methods ////////////////////////////////////////////////////////////////////// - lazy val trips: Array[Trip] = tours.flatMap(_.trips) - lazy val activities: Array[Activity] = tours.flatMap(_.trips.map(_.activity)) - lazy val legs: Array[Leg] = tours.flatMap(_.trips.map(_.leg)).flatten + lazy val trips: Vector[Trip] = tours.flatMap(_.trips) private val actsLegToTrip: mutable.Map[PlanElement, Trip] = mutable.Map() + private val strategies: mutable.Map[PlanElement, mutable.Map[Class[_ <: Strategy], Strategy]] = + mutable.Map() // Beam-Specific members - var tours: Array[Tour] = Array() + var tours: Vector[Tour] = Vector() // Implementation of Legacy Interface private var person: Person = _ private var actsLegs: Vector[PlanElement] = Vector() private var score: Double = Double.NaN private var planType: String = "" + lazy val legs: Vector[Leg] = actsLegs flatMap { + case leg: Leg => Some(leg) + case _ => None + } + + lazy val activities: Vector[Activity] = actsLegs flatMap { + case act: Activity => Some(act) + case _ => None + } + + private def getTourIdFromMatsimLeg(legOption: Option[Leg]): Option[Int] = { + legOption.flatMap(leg => Option(leg.getAttributes.getAttribute("tour_id")).map(_.toString.toInt)) + } + + private def getTourModeFromMatsimLeg(leg: Leg): Option[BeamTourMode] = { + Option(leg.getAttributes.getAttribute("tour_mode")).flatMap(x => BeamTourMode.fromString(x.toString)) + } + + private def getTourVehicleFromMatsimLeg(leg: Leg): Option[Id[BeamVehicle]] = { + Option(leg.getAttributes.getAttribute("tour_vehicle")).map(x => Id.create(x.toString, classOf[BeamVehicle])) + } + def createToursFromMatsimPlan(): Unit = { - val toursBuilder = mutable.ArrayBuilder.make[Tour]() - var nextTour = new Tour - var nextLeg: Option[Leg] = None - actsLegs.foreach { - case activity: Activity => - val nextTrip = Trip(activity, nextLeg, nextTour) - nextTour.addTrip(nextTrip) - if (activity.getType.equalsIgnoreCase("home")) { - toursBuilder += nextTour - nextTour = new Tour + tours = Vector() + var currentTourIndex = -1 + var currentTour = new Tour(-1) + var previousLeg: Option[Leg] = None + actsLegs.sliding(2).foreach { + case Vector(leg: Leg, _: Activity) => + previousLeg = Some(leg) + + case Vector(activity: Activity, leg: Leg) => + val nextTrip = Trip(activity, previousLeg, currentTour) + currentTour.addTrip(nextTrip) + val startNewTour = (getTourIdFromMatsimLeg(Some(leg)), previousLeg) match { + case (_, None) => true + case (Some(nextId), currentLeg) if getTourIdFromMatsimLeg(currentLeg).exists(_ != nextId) => true + case (None, _) if atHome(activity) => true + case _ => false } - case leg: Leg => - nextLeg = Some(leg) + if (startNewTour) { + currentTourIndex += 1 + currentTour.setTourId( + getTourIdFromMatsimLeg(previousLeg).getOrElse(currentTourIndex) + ) + if (!tours.map(_.tourId).contains(currentTour.tourId)) { + tours = tours :+ currentTour + } + val previousTourWithSameId = tours.find(x => getTourIdFromMatsimLeg(Some(leg)).contains(x.tourId)) + currentTour = previousTourWithSameId match { + case Some(matchedTour) => matchedTour + case _ => new Tour(originActivity = Some(activity)) + } + putStrategy( + currentTour, + TourModeChoiceStrategy(getTourModeFromMatsimLeg(leg), getTourVehicleFromMatsimLeg(leg)) + ) + } + case Vector(onlyActivity: Activity) => + val nextTrip = Trip(onlyActivity, previousLeg, currentTour) + currentTour.addTrip(nextTrip) + case _ => + throw new IllegalArgumentException("Poorly formed input plans") + } + actsLegs.lastOption match { + case Some(lastAct: Activity) => + val nextTrip = Trip(lastAct, previousLeg, currentTour) + currentTour.addTrip(nextTrip) + case _ => + } + + if (currentTour.trips.nonEmpty) { + currentTourIndex += 1 + currentTour.setTourId( + getTourIdFromMatsimLeg(previousLeg).getOrElse(currentTourIndex) + ) + if (!tours.map(_.tourId).contains(currentTour.tourId)) { + tours = tours :+ currentTour + } } - if (nextTour.trips.nonEmpty) toursBuilder += nextTour - tours = toursBuilder.result() indexBeamPlan() + actsLegs.foreach { + case l: Leg => + putStrategy(actsLegToTrip(l), TripModeChoiceStrategy(BeamMode.fromString(l.getMode))) + case _ => + } } def indexTrip(trip: Trip): Unit = { @@ -132,10 +217,44 @@ class BeamPlan extends Plan { } } - def indexBeamPlan(): Unit = { + private def indexBeamPlan(): Unit = { tours.foreach(tour => tour.trips.foreach(indexTrip)) } + def putStrategy(planElement: PlanElement, strategy: Strategy): Unit = { + val planElementMap = strategies.getOrElseUpdate(planElement, mutable.Map.empty[Class[_ <: Strategy], Strategy]) + planElementMap.put(strategy.getClass, strategy) + + (strategy, planElement) match { + case (tripModeChoiceStrategy: TripModeChoiceStrategy, tour: Tour) => + tripModeChoiceStrategy.tripStrategies(tour, this).foreach { case (trip, _) => + putStrategy(trip, tripModeChoiceStrategy) + } + case (tripModeChoiceStrategy: TripModeChoiceStrategy, trip: Trip) => + putStrategy(trip.activity, tripModeChoiceStrategy) // I don't think this gets used + trip.leg.foreach(theLeg => putStrategy(theLeg, tripModeChoiceStrategy)) + case (_: TourModeChoiceStrategy, _: Trip) => + throw new RuntimeException("Can only set tour mode strategy from within a tour") + case _ => + } + } + + def getStrategy[T <: Strategy: ClassTag](planElement: PlanElement): T = { + val forClass: Class[T] = implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]] + strategies + .getOrElse(planElement, Map.empty[Class[_ <: Strategy], Strategy]) + .getOrElse(forClass, forClass.getConstructor().newInstance()) + .asInstanceOf[T] + } + + def getTripStrategy[T <: Strategy: ClassTag](activity: Activity): T = { + getStrategy(actsLegToTrip(activity)) + } + + def getTourStrategy[T <: Strategy: ClassTag](activity: Activity): T = { + getStrategy(getTourContaining(activity)) + } + def isLastElementInTour(planElement: PlanElement): Boolean = { val tour = getTourContaining(planElement) planElement match { @@ -175,6 +294,31 @@ class BeamPlan extends Plan { } } + def getTourContaining(index: Int): Tour = { + getTourContaining(activities(index)) + } + + def getTripContaining(index: Int): Trip = { + getTripContaining(activities(index)) + } + +// def getTourModeFromTourLegs(tour: Tour): Option[BeamTourMode] = { +// // TODO: Should this just look at the first/last mode of legs? +// var tourMode: Option[BeamTourMode] = None +//// if (tour.trips.exists(trip => trip.leg.isDefined)) { +//// tour.trips.foreach(trip => +//// trip.leg match { +//// case Some(leg) if leg.getMode.equalsIgnoreCase("car") => tourMode = Some(CAR_BASED) +//// case Some(leg) if leg.getMode.equalsIgnoreCase("bike") && !tourMode.contains(CAR_BASED) => +//// tourMode = Some(BIKE_BASED) +//// case Some(_) => tourMode = Some(WALK_BASED) +//// case _ => +//// } +//// ) +//// } +// tourMode +// } + ////////////////////////////////////////////////////////////////////// // 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 new file mode 100755 index 00000000000..bf6b0c529f3 --- /dev/null +++ b/src/main/scala/beam/agentsim/agents/planning/Strategy.scala @@ -0,0 +1,76 @@ +package beam.agentsim.agents.planning + +import beam.agentsim.agents.planning.BeamPlan.atHome +import beam.agentsim.agents.vehicles.BeamVehicle +import beam.router.Modes.BeamMode +import beam.router.Modes.BeamMode._ +import beam.router.TourModes.BeamTourMode +import org.matsim.api.core.v01.Id +import org.matsim.api.core.v01.population.Activity + +/** + * BEAM + */ +object Strategy { + + trait Strategy { + + /** + * 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 TourModeChoiceStrategy(tourMode: Option[BeamTourMode] = None, tourVehicle: Option[Id[BeamVehicle]] = None) + extends Strategy { + def this() = this(None) + } + + case class TripModeChoiceStrategy(mode: Option[BeamMode] = None) extends Strategy { + def this() = this(None) + + 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[TripModeChoiceStrategy](trip).mode match { + case Some(CAR_HOV2 | CAR_HOV3) => true + case _ => false + } + } + .map(_ -> tourStrategy) + case _ => super.tripStrategies(tour, beamPlan) + } + } + + def tourStrategy(beamPlan: BeamPlan, curAct: Activity, nextAct: Activity): TripModeChoiceStrategy = { + val currentTourModeOpt = beamPlan.getTourStrategy[TripModeChoiceStrategy](nextAct).mode + val newTourMode = currentTourModeOpt match { + case Some(_) if mode.get.isTeleportation => mode + case Some(DRIVE_TRANSIT | BIKE_TRANSIT) => currentTourModeOpt + case _ if atHome(curAct) => mode + case Some(_) => currentTourModeOpt + case None => mode + } + TripModeChoiceStrategy(newTourMode) + } + + } + +} diff --git a/src/main/scala/beam/agentsim/agents/planning/Tour.scala b/src/main/scala/beam/agentsim/agents/planning/Tour.scala index 64ce976f211..dffdeea9e1e 100755 --- a/src/main/scala/beam/agentsim/agents/planning/Tour.scala +++ b/src/main/scala/beam/agentsim/agents/planning/Tour.scala @@ -1,12 +1,20 @@ package beam.agentsim.agents.planning -import org.matsim.api.core.v01.population.PlanElement +import org.matsim.api.core.v01.population.{Activity, PlanElement} import org.matsim.utils.objectattributes.attributable.Attributes -class Tour(private var tripsInternal: Vector[Trip] = Vector()) extends PlanElement { +class Tour( + var tourId: Int = 0, + private var tripsInternal: Vector[Trip] = Vector(), + val originActivity: Option[Activity] = None +) extends PlanElement { def trips: Seq[Trip] = tripsInternal + def setTourId(newTourId: Int): Unit = { tourId = newTourId } + + def activities: Seq[Activity] = originActivity.toSeq ++ trips.map(_.activity) + override def getAttributes = new Attributes def addTrip(newTrip: Trip): Unit = tripsInternal = tripsInternal :+ newTrip diff --git a/src/main/scala/beam/agentsim/events/PathTraversalEvent.scala b/src/main/scala/beam/agentsim/events/PathTraversalEvent.scala index 721da50ce3f..1c9a33b1bed 100644 --- a/src/main/scala/beam/agentsim/events/PathTraversalEvent.scala +++ b/src/main/scala/beam/agentsim/events/PathTraversalEvent.scala @@ -41,7 +41,16 @@ case class PathTraversalEvent( amountPaid: Double, fromStopIndex: Option[Int], toStopIndex: Option[Int], - currentTourMode: Option[String], + currentTripMode: Option[String], + /*, + linkIdsToLaneOptions: IndexedSeq[(Int, Option[Int])], + linkIdsToSpeedOptions: IndexedSeq[(Int, Option[Double])], + linkIdsToGradientOptions: IndexedSeq[(Int, Option[Double])], + linkIdsToLengthOptions: IndexedSeq[(Int, Option[Double])], + linkIdsToSelectedRateOptions: IndexedSeq[(Int, Option[Double])], + linkIdsToConsumptionOptions: IndexedSeq[(Int, Option[Double])], + secondaryLinkIdsToSelectedRateOptions: IndexedSeq[(Int, Option[Double])], + secondaryLinkIdsToConsumptionOptions: IndexedSeq[(Int, Option[Double])]*/ riders: IndexedSeq[Id[Person]] = Vector() ) extends Event(time) with ScalaEvent { @@ -87,7 +96,17 @@ case class PathTraversalEvent( attr.put(ATTRIBUTE_TOLL_PAID, amountPaid.toString) attr.put(ATTRIBUTE_FROM_STOP_INDEX, fromStopIndex.map(_.toString).getOrElse("")) attr.put(ATTRIBUTE_TO_STOP_INDEX, toStopIndex.map(_.toString).getOrElse("")) - attr.put(ATTRIBUTE_CURRENT_TOUR_MODE, currentTourMode.getOrElse("")) + attr.put(ATTRIBUTE_CURRENT_TRIP_MODE, currentTripMode.getOrElse("")) + /* + attr.put(ATTRIBUTE_LINKID_WITH_LANE_MAP, linkIdsToLaneOptions.map{case ((linkId, laneOption)) => s"$linkId:${laneOption.getOrElse(0)}"}.mkString(",")) + attr.put(ATTRIBUTE_LINKID_WITH_SPEED_MAP, linkIdsToSpeedOptions.map{case ((linkId, speedOption)) => s"$linkId:${speedOption.getOrElse(0)}"}.mkString(",")) + attr.put(ATTRIBUTE_LINKID_WITH_SELECTED_GRADIENT_MAP, linkIdsToGradientOptions.map{case ((linkId, gradientOption)) => s"$linkId:${gradientOption.getOrElse(0)}"}.mkString(",")) + attr.put(ATTRIBUTE_LINKID_WITH_LENGTH_MAP, linkIdsToLengthOptions.map{case ((linkId, lengthOption)) => s"$linkId:${lengthOption.getOrElse(0)}"}.mkString(",")) + attr.put(ATTRIBUTE_LINKID_WITH_SELECTED_RATE_MAP, linkIdsToSelectedRateOptions.map{case ((linkId, rateOption)) => s"$linkId:${rateOption.getOrElse(0)}"}.mkString(",")) + attr.put(ATTRIBUTE_LINKID_WITH_FINAL_CONSUMPTION_MAP, linkIdsToConsumptionOptions.map{case ((linkId, consumptionOption)) => s"$linkId:${consumptionOption.getOrElse(0)}"}.mkString(",")) + attr.put(ATTRIBUTE_SECONDARY_LINKID_WITH_SELECTED_RATE_MAP, secondaryLinkIdsToSelectedRateOptions.map{case ((linkId, rateOption)) => s"$linkId:${rateOption.getOrElse(0)}"}.mkString(",")) + attr.put(ATTRIBUTE_SECONDARY_LINKID_WITH_FINAL_CONSUMPTION_MAP, secondaryLinkIdsToConsumptionOptions.map{case ((linkId, consumptionOption)) => s"$linkId:${consumptionOption.getOrElse(0)}"}.mkString(",")) + */ attr.put(ATTRIBUTE_RIDERS, ridersToStr(riders)) filledAttrs.set(attr) attr @@ -104,7 +123,7 @@ object PathTraversalEvent { val ATTRIBUTE_PRIMARY_FUEL: String = "primaryFuel" val ATTRIBUTE_SECONDARY_FUEL: String = "secondaryFuel" val ATTRIBUTE_NUM_PASS: String = "numPassengers" - val ATTRIBUTE_CURRENT_TOUR_MODE: String = "currentTourMode" + val ATTRIBUTE_CURRENT_TRIP_MODE: String = "currentTripMode" val ATTRIBUTE_LINK_IDS: String = "links" val ATTRIBUTE_LINK_TRAVEL_TIME: String = "linkTravelTime" @@ -144,7 +163,7 @@ object PathTraversalEvent { vehicleType: BeamVehicleType, numPass: Int, beamLeg: BeamLeg, - currentTourMode: Option[String], + currentTripMode: Option[String], primaryFuelConsumed: Double, secondaryFuelConsumed: Double, endLegPrimaryFuelLevel: Double, @@ -179,7 +198,16 @@ object PathTraversalEvent { amountPaid = amountPaid, fromStopIndex = beamLeg.travelPath.transitStops.map(_.fromIdx), toStopIndex = beamLeg.travelPath.transitStops.map(_.toIdx), - currentTourMode = currentTourMode, + currentTripMode = currentTripMode, + /* + linkIdsToLaneOptions = linkIdsToLaneOptions, + linkIdsToSpeedOptions = linkIdsToSpeedOptions, + linkIdsToGradientOptions = linkIdsToGradientOptions, + linkIdsToLengthOptions = linkIdsToLengthOptions, + linkIdsToSelectedRateOptions = linkIdsToSelectedRateOptions, + linkIdsToConsumptionOptions = linkIdsToConsumptionOptions, + secondaryLinkIdsToSelectedRateOptions = secondaryLinkIdsToSelectedRateOptions, + secondaryLinkIdsToConsumptionOptions = secondaryLinkIdsToConsumptionOptions*/ riders = riders ) } @@ -221,8 +249,42 @@ object PathTraversalEvent { attr.get(ATTRIBUTE_FROM_STOP_INDEX).flatMap(Option(_)).flatMap(x => if (x == "") None else Some(x.toInt)) val toStopIndex: Option[Int] = attr.get(ATTRIBUTE_TO_STOP_INDEX).flatMap(Option(_)).flatMap(x => if (x == "") None else Some(x.toInt)) - val currentTourMode: Option[String] = - attr.get(ATTRIBUTE_CURRENT_TOUR_MODE).flatMap(x => if (x == "") None else Some(x)) + val currentTripMode: Option[String] = + attr.get(ATTRIBUTE_CURRENT_TRIP_MODE).flatMap(x => if (x == "") None else Some(x)) + /* + val linkIdsToLaneOptions = attr(ATTRIBUTE_LINKID_WITH_LANE_MAP).split(",").map(x=>{ + val linkIdToLaneSplit = x.split(":") + (linkIdToLaneSplit(0).toInt, Some(linkIdToLaneSplit(1).toInt)) + }) + val linkIdsToSpeedOptions = attr(ATTRIBUTE_LINKID_WITH_SPEED_MAP).split(",").map(x=>{ + val linkIdToSpeedSplit = x.split(":") + (linkIdToSpeedSplit(0).toInt, Some(linkIdToSpeedSplit(1).toDouble)) + }) + val linkIdsToGradientOptions = attr(ATTRIBUTE_LINKID_WITH_SELECTED_GRADIENT_MAP).split(",").map(x=>{ + val linkIdToGradientSplit = x.split(":") + (linkIdToGradientSplit(0).toInt, Some(linkIdToGradientSplit(1).toDouble)) + }) + val linkIdsToLengthOptions = attr(ATTRIBUTE_LINKID_WITH_LENGTH_MAP).split(",").map(x=>{ + val linkIdToLengthSplit = x.split(":") + (linkIdToLengthSplit(0).toInt, Some(linkIdToLengthSplit(1).toDouble)) + }) + val linkIdsToSelectedRateOptions = attr(ATTRIBUTE_LINKID_WITH_SELECTED_RATE_MAP).split(",").map(x=>{ + val linkIdToRateSplit = x.split(":") + (linkIdToRateSplit(0).toInt, Some(linkIdToRateSplit(1).toDouble)) + }) + val linkIdsToConsumptionOptions = attr(ATTRIBUTE_LINKID_WITH_FINAL_CONSUMPTION_MAP).split(",").map(x=>{ + val linkIdToConsumptionSplit = x.split(":") + (linkIdToConsumptionSplit(0).toInt, Some(linkIdToConsumptionSplit(1).toDouble)) + }) + val secondaryLinkIdsToSelectedRateOptions = attr(ATTRIBUTE_SECONDARY_LINKID_WITH_SELECTED_RATE_MAP).split(",").map(x=>{ + val linkIdToRateSplit = x.split(":") + (linkIdToRateSplit(0).toInt, Some(linkIdToRateSplit(1).toDouble)) + }) + val secondaryLinkIdsToConsumptionOptions = attr(ATTRIBUTE_SECONDARY_LINKID_WITH_FINAL_CONSUMPTION_MAP).split(",").map(x=>{ + val linkIdToConsumptionSplit = x.split(":") + (linkIdToConsumptionSplit(0).toInt, Some(linkIdToConsumptionSplit(1).toDouble)) + }) + */ PathTraversalEvent( time, vehicleId, @@ -250,7 +312,17 @@ object PathTraversalEvent { amountPaid, fromStopIndex, toStopIndex, - currentTourMode, + currentTripMode, + /*, + linkIdsToLaneOptions, + linkIdsToSpeedOptions, + linkIdsToGradientOptions, + linkIdsToLengthOptions, + linkIdsToSelectedRateOptions, + linkIdsToConsumptionOptions, + secondaryLinkIdsToSelectedRateOptions, + secondaryLinkIdsToConsumptionOptions*/ + riders ) } diff --git a/src/main/scala/beam/agentsim/events/TeleportationEvent.scala b/src/main/scala/beam/agentsim/events/TeleportationEvent.scala index afdc5a2b980..00652e73129 100644 --- a/src/main/scala/beam/agentsim/events/TeleportationEvent.scala +++ b/src/main/scala/beam/agentsim/events/TeleportationEvent.scala @@ -16,7 +16,7 @@ case class TeleportationEvent( startY: Double, endX: Double, endY: Double, - currentTourMode: Option[String] + currentTripMode: Option[String] ) extends Event(time) with ScalaEvent { import TeleportationEvent._ @@ -38,7 +38,7 @@ case class TeleportationEvent( attr.put(ATTRIBUTE_START_COORDINATE_Y, startY.toString) attr.put(ATTRIBUTE_END_COORDINATE_X, endX.toString) attr.put(ATTRIBUTE_END_COORDINATE_Y, endY.toString) - attr.put(ATTRIBUTE_CURRENT_TOUR_MODE, currentTourMode.getOrElse("")) + attr.put(ATTRIBUTE_CURRENT_TRIP_MODE, currentTripMode.getOrElse("")) filledAttrs.set(attr) attr @@ -49,7 +49,7 @@ case class TeleportationEvent( object TeleportationEvent { val EVENT_TYPE: String = "TeleportationEvent" - val ATTRIBUTE_CURRENT_TOUR_MODE: String = "currentTourMode" + val ATTRIBUTE_CURRENT_TRIP_MODE: String = "currentTripMode" val ATTRIBUTE_DEPARTURE_TIME: String = "departureTime" val ATTRIBUTE_PERSON: String = "person" diff --git a/src/main/scala/beam/agentsim/infrastructure/HierarchicalParkingManager.scala b/src/main/scala/beam/agentsim/infrastructure/HierarchicalParkingManager.scala index 06a15d94db4..eda4918124b 100644 --- a/src/main/scala/beam/agentsim/infrastructure/HierarchicalParkingManager.scala +++ b/src/main/scala/beam/agentsim/infrastructure/HierarchicalParkingManager.scala @@ -210,10 +210,10 @@ class HierarchicalParkingManager( private def lastResortStallAndZone(location: Location) = { val boxAroundRequest = new Envelope( - location.getX + 2000, - location.getX - 2000, - location.getY + 2000, - location.getY - 2000 + location.getX + 100, + location.getX - 100, + location.getY + 100, + location.getY - 100 ) val newStall = ParkingStall.lastResortStall(boxAroundRequest, new Random(seed)) newStall -> DefaultParkingZone diff --git a/src/main/scala/beam/agentsim/infrastructure/ParkingFunctions.scala b/src/main/scala/beam/agentsim/infrastructure/ParkingFunctions.scala index 05068f94f4f..8d3b384025e 100644 --- a/src/main/scala/beam/agentsim/infrastructure/ParkingFunctions.scala +++ b/src/main/scala/beam/agentsim/infrastructure/ParkingFunctions.scala @@ -132,10 +132,10 @@ class ParkingFunctions( case _ => // didn't find any stalls, so, as a last resort, create a very expensive stall val boxAroundRequest = new Envelope( - inquiry.destinationUtm.loc.getX + 2000, - inquiry.destinationUtm.loc.getX - 2000, - inquiry.destinationUtm.loc.getY + 2000, - inquiry.destinationUtm.loc.getY - 2000 + inquiry.destinationUtm.loc.getX + 100, + inquiry.destinationUtm.loc.getX - 100, + inquiry.destinationUtm.loc.getY + 100, + inquiry.destinationUtm.loc.getY - 100 ) val newStall = ParkingStall.lastResortStall(boxAroundRequest, new Random(seed)) ParkingZoneSearch.ParkingZoneSearchResult(newStall, DefaultParkingZone) @@ -159,30 +159,33 @@ class ParkingFunctions( ): Coord = { if (parkingZone.link.isDefined) parkingZone.link.get.getCoord - else if ( - (parkingZone.reservedFor.managerType == VehicleManager.TypeEnum.Household) || - (inquiry.parkingActivityType == ParkingActivityType.Home && parkingZone.parkingType == ParkingType.Residential) || - (inquiry.parkingActivityType == ParkingActivityType.Work && parkingZone.parkingType == ParkingType.Workplace) - ) - inquiry.destinationUtm.loc - else if (tazTreeMap.tazListContainsGeoms) { - ParkingStallSampling.linkBasedSampling( - new Random(seed), - inquiry.destinationUtm.loc, - tazTreeMap.tazToLinkIdMapping.get(taz.tazId), - distanceFunction, - parkingZone.availability, - taz, - inClosestZone - ) - } else { - ParkingStallSampling.availabilityAwareSampling( - new Random(seed), - inquiry.destinationUtm.loc, - taz, - parkingZone.availability, - inClosestZone - ) + else { + val availability = if ( + (parkingZone.reservedFor.managerType == VehicleManager.TypeEnum.Household) || + (inquiry.parkingActivityType == ParkingActivityType.Home && parkingZone.parkingType == ParkingType.Residential) || + (inquiry.parkingActivityType == ParkingActivityType.Work && parkingZone.parkingType == ParkingType.Workplace) + ) { + 1.0 + } else { parkingZone.availability } + if (tazTreeMap.tazListContainsGeoms) { + ParkingStallSampling.linkBasedSampling( + new Random(seed), + inquiry.destinationUtm.loc, + tazTreeMap.tazToLinkIdMapping.get(taz.tazId), + distanceFunction, + availability, + taz, + inClosestZone + ) + } else { + ParkingStallSampling.availabilityAwareSampling( + new Random(seed), + inquiry.destinationUtm.loc, + taz, + availability, + inClosestZone + ) + } } } diff --git a/src/main/scala/beam/agentsim/infrastructure/RideHailDepotFunctions.scala b/src/main/scala/beam/agentsim/infrastructure/RideHailDepotFunctions.scala index 23803923106..59e4b5d663d 100644 --- a/src/main/scala/beam/agentsim/infrastructure/RideHailDepotFunctions.scala +++ b/src/main/scala/beam/agentsim/infrastructure/RideHailDepotFunctions.scala @@ -145,10 +145,10 @@ class RideHailDepotFunctions( case _ => // didn't find any stalls, so, as a last resort, create a very expensive stall val boxAroundRequest = new Envelope( - inquiry.destinationUtm.loc.getX + 2000, - inquiry.destinationUtm.loc.getX - 2000, - inquiry.destinationUtm.loc.getY + 2000, - inquiry.destinationUtm.loc.getY - 2000 + inquiry.destinationUtm.loc.getX + 100, + inquiry.destinationUtm.loc.getX - 100, + inquiry.destinationUtm.loc.getY + 100, + inquiry.destinationUtm.loc.getY - 100 ) val newStall = ParkingStall.lastResortStall(boxAroundRequest, new Random(seed)) ParkingZoneSearch.ParkingZoneSearchResult(newStall, DefaultParkingZone) diff --git a/src/main/scala/beam/agentsim/infrastructure/parking/ParkingStallSampling.scala b/src/main/scala/beam/agentsim/infrastructure/parking/ParkingStallSampling.scala index 9d525077cf4..7ab5d3efe44 100644 --- a/src/main/scala/beam/agentsim/infrastructure/parking/ParkingStallSampling.scala +++ b/src/main/scala/beam/agentsim/infrastructure/parking/ParkingStallSampling.scala @@ -1,7 +1,7 @@ package beam.agentsim.infrastructure.parking import scala.util.Random -import beam.agentsim.infrastructure.taz.TAZ +import beam.agentsim.infrastructure.taz.{TAZ, TAZTreeMap} import beam.router.BeamRouter.Location import beam.utils.logging.ExponentialLazyLogging import org.matsim.api.core.v01.network.Link @@ -16,7 +16,7 @@ import scala.math.pow */ object ParkingStallSampling extends ExponentialLazyLogging { - val maxOffsetDistance = 600.0 // TODO: Make this a config parameter + val maxOffsetDistance = 1000.0 // TODO: Make this a config parameter def linkBasedSampling( rand: Random, diff --git a/src/main/scala/beam/agentsim/infrastructure/taz/TAZTreeMap.scala b/src/main/scala/beam/agentsim/infrastructure/taz/TAZTreeMap.scala index a7ccafad410..36a359b2513 100755 --- a/src/main/scala/beam/agentsim/infrastructure/taz/TAZTreeMap.scala +++ b/src/main/scala/beam/agentsim/infrastructure/taz/TAZTreeMap.scala @@ -115,10 +115,14 @@ class TAZTreeMap(val tazQuadTree: QuadTree[TAZ], val useCache: Boolean = false) writer.write("linkId,count") writer.write(System.lineSeparator()) failedLinkLookups.toList.groupBy(identity).mapValues(_.size).foreach { case (linkId, count) => - writer.write(Option(linkId).mkString) - writer.write(",") - writer.write(count.toString) - writer.write(System.lineSeparator()) + try { + writer.write(Option(linkId).mkString) + writer.write(",") + writer.write(count.toString) + writer.write(System.lineSeparator()) + } catch { + case e: Throwable => logger.warn(s"Error: ${e.getMessage}. Could not write link $linkId") + } } writer.flush() writer.close() @@ -334,6 +338,28 @@ object TAZTreeMap { new Coord(taz.coord.getX + x, taz.coord.getY + y) } + def randomLocationInTAZ( + taz: TAZ, + rand: scala.util.Random, + allLinks: Iterable[Link] + ): Coord = { + if (allLinks.isEmpty) { + randomLocationInTAZ(taz, rand) + } else { + val totalLength = allLinks.foldRight(0.0)(_.getLength + _) + var currentLength = 0.0 + val stopAt = rand.nextDouble() * totalLength + allLinks + .takeWhile { lnk => + currentLength += lnk.getLength + currentLength <= stopAt + } + .lastOption + .map(_.getCoord) + .getOrElse(allLinks.head.getCoord) + } + } + def randomLocationInTAZ( taz: TAZ, rand: scala.util.Random, diff --git a/src/main/scala/beam/router/Modes.scala b/src/main/scala/beam/router/Modes.scala old mode 100755 new mode 100644 index 726ae76225a..6815583823b --- a/src/main/scala/beam/router/Modes.scala +++ b/src/main/scala/beam/router/Modes.scala @@ -1,6 +1,8 @@ package beam.router -import beam.router.Modes.BeamMode.{BIKE, CAR, CAR_HOV2, CAR_HOV3, CAV, WALK} +import beam.agentsim.agents.modalbehaviors.DrivesVehicle.{ActualVehicle, VehicleOrToken} +import beam.agentsim.agents.vehicles.{BeamVehicle, VehicleCategory} +import beam.agentsim.agents.vehicles.VehicleCategory._ import com.conveyal.r5.api.util.{LegMode, TransitModes} import com.conveyal.r5.profile.StreetMode import enumeratum.values._ @@ -125,6 +127,10 @@ object Modes { val chainBasedModes = Seq(CAR, BIKE) + val personalVehicleModes = Seq(CAR, BIKE, DRIVE_TRANSIT, BIKE_TRANSIT) + + val nonPersonalVehicleModes = Seq(WALK, RIDE_HAIL, RIDE_HAIL_POOLED, RIDE_HAIL_TRANSIT, WALK_TRANSIT) + val transitModes = Seq(BUS, FUNICULAR, GONDOLA, CABLE_CAR, FERRY, TRAM, TRANSIT, RAIL, SUBWAY) @@ -162,6 +168,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 @@ -198,11 +206,11 @@ object Modes { } def toR5StreetMode(mode: BeamMode): StreetMode = mode match { - case BIKE => StreetMode.BICYCLE - case WALK => StreetMode.WALK - case CAR => StreetMode.CAR - case CAV => StreetMode.CAR - case _ => throw new IllegalArgumentException + case BeamMode.BIKE => StreetMode.BICYCLE + case BeamMode.WALK => StreetMode.WALK + case BeamMode.CAR => StreetMode.CAR + case BeamMode.CAV => StreetMode.CAR + case _ => throw new IllegalArgumentException } def toR5StreetMode(mode: LegMode): StreetMode = mode match { @@ -241,3 +249,165 @@ object Modes { } } + +object TourModes { + import beam.router.Modes.BeamMode + import beam.router.Modes.BeamMode._ + + sealed abstract class BeamTourMode( + val value: String, + val vehicleCategory: VehicleCategory, + val allowedBeamModes: Seq[BeamMode], + val allowedBeamModesForFirstAndLastLeg: Seq[BeamMode] + ) extends StringEnumEntry { + + import BeamTourMode._ + + private def getModeFromVehicle(beamVehicle: BeamVehicle): BeamMode = { + beamVehicle.beamVehicleType.vehicleCategory match { + case VehicleCategory.Car => CAR + case VehicleCategory.Bike => BIKE + case _ => WALK + } + } + + def allowedBeamModesGivenAvailableVehicles( + vehicles: Vector[VehicleOrToken], + firstOrLastLeg: Boolean + ): Seq[BeamMode] = { + val relevantModes = if (firstOrLastLeg) { allowedBeamModesForFirstAndLastLeg } + else allowedBeamModes + if ( + vehicles + .exists { vehOrToken => + !vehOrToken.vehicle.isSharedVehicle && getModeFromVehicle(vehOrToken.vehicle).in(relevantModes) + } + ) { relevantModes } + else { Seq.empty[BeamMode] } + } + + def isVehicleBased: Boolean = this match { + case WALK_BASED => false + case _ => true + } + } + + object BeamTourMode extends StringEnum[BeamTourMode] with StringCirceEnum[BeamTourMode] { + + override val values: immutable.IndexedSeq[BeamTourMode] = findValues + + def getTourMode( + tripMode: BeamMode, + availableVehicles: Vector[VehicleOrToken] = Vector.empty[VehicleOrToken] + ): (Option[BeamTourMode], Option[BeamVehicle]) = { + tripMode match { + case CAR | CAR_HOV2 | CAR_HOV3 => + if (availableVehicles.exists(!_.vehicle.isSharedVehicle)) { + // Assume that if they have access to a personal vehicle they'll take it + // on the whole tour, otherwise they'll need to use an emergency vehicle + (Some(CAR_BASED), availableVehicles.find(!_.vehicle.isSharedVehicle).map(_.vehicle)) + } else (Some(CAR_BASED), None) + case BIKE => + if (availableVehicles.exists(!_.vehicle.isSharedVehicle)) { + // Assume that if they have access to a personal vehicle they'll take it + // on the whole tour, otherwise they'll rely on a shared vehicle // TODO: Check + (Some(BIKE_BASED), availableVehicles.find(!_.vehicle.isSharedVehicle).map(_.vehicle)) + } else (Some(WALK_BASED), None) + case DRIVE_TRANSIT => + ( + Some(WALK_BASED), + availableVehicles + .find(veh => + (veh.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Car) & !veh.vehicle.isSharedVehicle + ) + .map(_.vehicle) + ) + case BIKE_TRANSIT => + ( + Some(WALK_BASED), + availableVehicles + .find(veh => + (veh.vehicle.beamVehicleType.vehicleCategory == VehicleCategory.Bike) & !veh.vehicle.isSharedVehicle + ) + .map(_.vehicle) + ) + case _ => (Some(WALK_BASED), None) + } + } + + val enabledModes: Map[BeamMode, Seq[BeamMode]] = + Map[BeamMode, Seq[BeamMode]](CAR -> Seq(DRIVE_TRANSIT), BIKE -> Seq(BIKE_TRANSIT)) + + // TODO: Also allow use of shared bikes/cars in walk based tours + case object WALK_BASED + extends BeamTourMode( + "walk_based", + Body, + Seq[BeamMode]( + WALK, + WALK_TRANSIT, + RIDE_HAIL, + RIDE_HAIL_POOLED, + RIDE_HAIL_TRANSIT, + HOV2_TELEPORTATION, + HOV3_TELEPORTATION + ), + Seq[BeamMode]( + WALK, + WALK_TRANSIT, + RIDE_HAIL, + RIDE_HAIL_POOLED, + RIDE_HAIL_TRANSIT, + DRIVE_TRANSIT, + BIKE_TRANSIT, + HOV2_TELEPORTATION, + HOV3_TELEPORTATION + ) + ) { + + // When we're on a walk based tour, we can still use shared vehicles + override def allowedBeamModesGivenAvailableVehicles( + vehicles: Vector[VehicleOrToken], + firstOrLastLeg: Boolean + ): Seq[BeamMode] = { + vehicles.flatMap { veh => + if (firstOrLastLeg) { + if (veh.vehicle.isSharedVehicle) { + Seq(veh.streetVehicle.mode) ++ enabledModes(veh.streetVehicle.mode) + } else { + enabledModes(veh.streetVehicle.mode) + } + } else { + Seq.empty[BeamMode] + } + } ++ allowedBeamModes + } + } + + case object CAR_BASED + extends BeamTourMode( + "car_based", + Car, + Seq[BeamMode](CAR, CAR_HOV2, CAR_HOV3), + Seq[BeamMode](CAR, CAR_HOV2, CAR_HOV3) + ) + + case object BIKE_BASED extends BeamTourMode("bike_based", Bike, Seq[BeamMode](BIKE), Seq[BeamMode](BIKE)) + + def fromString(stringMode: String): Option[BeamTourMode] = { + if (stringMode.equals("") || stringMode.equals("other")) { + None + } else if (stringMode.equalsIgnoreCase("walk_based")) { + Some(WALK_BASED) + } else if (stringMode.equalsIgnoreCase("bike_based")) { + Some(BIKE_BASED) + } else if (stringMode.equalsIgnoreCase("car_based")) { + Some(CAR_BASED) + } else { + Some(BeamTourMode.withValue(stringMode)) + } + } + + } + +} diff --git a/src/main/scala/beam/router/r5/R5Wrapper.scala b/src/main/scala/beam/router/r5/R5Wrapper.scala index 84b6a42db07..eb17ed9345d 100644 --- a/src/main/scala/beam/router/r5/R5Wrapper.scala +++ b/src/main/scala/beam/router/r5/R5Wrapper.scala @@ -469,7 +469,12 @@ class R5Wrapper(workerParams: R5Parameters, travelTime: TravelTime, travelTimeNo vehicle.locationUTM.loc } val theDestination = if (mainRouteToVehicle) { - destinationVehicle.get.locationUTM.loc + destinationVehicle match { + case Some(vehicle) => vehicle.locationUTM.loc + case None => + logger.error("Route requested with egress vehicles that don't exist") + request.destinationUTM + } } else { request.destinationUTM } @@ -1023,7 +1028,7 @@ class R5Wrapper(workerParams: R5Parameters, travelTime: TravelTime, travelTimeNo beamLeg.travelPath.distanceInM, beamLeg.duration, vehicleType, - fuelTypePrices(vehicleType.primaryFuelType) + fuelTypePrices.getOrElse(vehicleType.primaryFuelType, 0.0) ) } else 0.0 EmbodiedBeamLeg( diff --git a/src/main/scala/beam/router/skim/SkimsUtils.scala b/src/main/scala/beam/router/skim/SkimsUtils.scala index 3226e31f151..b3a7c3c16c0 100644 --- a/src/main/scala/beam/router/skim/SkimsUtils.scala +++ b/src/main/scala/beam/router/skim/SkimsUtils.scala @@ -51,8 +51,10 @@ object SkimsUtils extends LazyLogging { // 12.1 mph (5.409184 meter per second), is average bus speed // source: https://www.apta.com/resources/statistics/Documents/FactBook/2017-APTA-Fact-Book.pdf // assuming for now that it includes the headway - val transitSpeedMeterPerSec: Double = 5.409184 + val transitSpeedMeterPerSec: Double = 5.0 //5.409184 val bicycleSpeedMeterPerSec: Double = 3 + val ridehailWaitTimeInSec: Double = 300 + val transitAccessEgressTimeInSec: Double = 600 // 3.1 mph -> 1.38 meter per second val walkSpeedMeterPerSec: Double = 1.38 // 940.6 Traffic Signal Spacing, Minor is 1,320 ft => 402.336 meters @@ -93,10 +95,15 @@ object SkimsUtils extends LazyLogging { case BIKE => bicycleSpeedMeterPerSec case _ => walkSpeedMeterPerSec } + val waitTime = mode match { + case TRANSIT | WALK_TRANSIT | DRIVE_TRANSIT | BIKE_TRANSIT => transitAccessEgressTimeInSec + case RIDE_HAIL_POOLED | RIDE_HAIL | RIDE_HAIL_TRANSIT => ridehailWaitTimeInSec + case _ => 0 + } val travelDistance: Int = Math.ceil(GeoUtils.minkowskiDistFormula(originUTM, destinationUTM)).toInt val travelTime: Int = Math .ceil(travelDistance / speed) - .toInt + ((travelDistance / trafficSignalSpacing).toInt * waitingTimeAtAnIntersection).toInt + .toInt + ((travelDistance / trafficSignalSpacing).toInt * waitingTimeAtAnIntersection).toInt + waitTime.toInt (travelDistance, travelTime) } diff --git a/src/main/scala/beam/router/skim/core/ODSkimmer.scala b/src/main/scala/beam/router/skim/core/ODSkimmer.scala index 1d6dfb025a5..352a45eeee5 100644 --- a/src/main/scala/beam/router/skim/core/ODSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/ODSkimmer.scala @@ -18,6 +18,7 @@ import org.matsim.core.controler.events.IterationEndsEvent import org.apache.commons.lang3.math.NumberUtils import org.matsim.api.core.v01.events.Event +import scala.util.Try import scala.util.control.NonFatal class ODSkimmer @Inject() (matsimServices: MatsimServices, beamScenario: BeamScenario, beamConfig: BeamConfig) @@ -400,6 +401,28 @@ object ODSkimmer extends LazyLogging { override def toCsv: String = hour + "," + mode + "," + rideHailName + "," + origin + "," + destination } + case class ODSkimmerTimeCostTransfer( + timeInHours: Double = 0.0, + cost: Double = 0.0, + numTransfers: Int = 0, + crowdingLevel: Double = 0.0 + ) { + + def +(other: ODSkimmerTimeCostTransfer): ODSkimmerTimeCostTransfer = { + ODSkimmerTimeCostTransfer( + this.timeInHours + other.timeInHours, + this.cost + other.cost, + this.numTransfers + other.numTransfers, + if (this.timeInHours <= 0) { other.crowdingLevel } + else if (other.timeInHours <= 0) { this.crowdingLevel } + else { + (this.crowdingLevel / this.timeInHours + other.crowdingLevel / other.timeInHours) * + (this.timeInHours + other.timeInHours) + } + ) + } + } + def fromCsv( row: scala.collection.Map[String, String] ): (AbstractSkimmerKey, AbstractSkimmerInternal) = { diff --git a/src/main/scala/beam/router/skim/core/RideHailSkimmer.scala b/src/main/scala/beam/router/skim/core/RideHailSkimmer.scala index 956fb853388..71b12422c82 100644 --- a/src/main/scala/beam/router/skim/core/RideHailSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/RideHailSkimmer.scala @@ -35,14 +35,14 @@ class RideHailSkimmer @Inject() ( tazId = line("tazId").createId, hour = line("hour").toInt, reservationType = if (line("reservationType").equalsIgnoreCase("pooled")) Pooled else Solo, - line("wheelchairRequired").toBoolean, + line.get("wheelchairRequired").exists(_.toBoolean), serviceName = line.getOrElse("serviceName", "GlobalRHM") ), RidehailSkimmerInternal( waitTime = Option(line("waitTime")).map(_.toDouble).getOrElse(Double.NaN), costPerMile = Option(line("costPerMile")).map(_.toDouble).getOrElse(Double.NaN), unmatchedRequestsPercent = line("unmatchedRequestsPercent").toDouble, - accessibleVehiclePercent = line("accessibleVehiclePercent").toDouble, + accessibleVehiclePercent = line.get("accessibleVehiclePercent").map(_.toDouble).getOrElse(0.0), observations = line("observations").toInt, iterations = line("iterations").toInt ) diff --git a/src/main/scala/beam/router/skim/readonly/ODSkims.scala b/src/main/scala/beam/router/skim/readonly/ODSkims.scala index ca26b36911b..76cdb2b21d1 100644 --- a/src/main/scala/beam/router/skim/readonly/ODSkims.scala +++ b/src/main/scala/beam/router/skim/readonly/ODSkims.scala @@ -1,6 +1,7 @@ package beam.router.skim.readonly import beam.agentsim.agents.choice.mode.DrivingCost +import beam.agentsim.agents.planning.Tour import beam.agentsim.agents.vehicles.BeamVehicleType import beam.agentsim.infrastructure.taz.TAZ import beam.router.BeamRouter @@ -17,13 +18,16 @@ import beam.router.Modes.BeamMode.{ TRANSIT, WALK_TRANSIT } +import beam.router.TourModes.BeamTourMode import beam.router.skim.SkimsUtils import beam.router.skim.SkimsUtils.{distanceAndTime, getRideHailCost, timeToBin} import beam.router.skim.core.AbstractSkimmerReadOnly +import beam.router.skim.core.ODSkimmer.{ExcerptData, ODSkimmerInternal, ODSkimmerKey, ODSkimmerTimeCostTransfer, Skim} import beam.router.skim.core.ODSkimmer.{ExcerptData, ODSkimmerInternal, ODSkimmerKey, Skim} import beam.router.skim.readonly import beam.sim.config.BeamConfig import beam.sim.{BeamHelper, BeamScenario, BeamServices} +import org.matsim.api.core.v01.population.Activity import org.matsim.api.core.v01.{Coord, Id} import scala.collection.immutable @@ -103,6 +107,48 @@ class ODSkims(beamConfig: BeamConfig, beamScenario: BeamScenario) extends Abstra (timeFactor, costFactor) } + def getTourModeCosts( + modes: Seq[BeamMode], + tour: Tour, + vehicleTypeId: Id[BeamVehicleType], + vehicleType: BeamVehicleType, + fuelPrice: Double + ): Seq[Map[BeamMode, ODSkimmerTimeCostTransfer]] = { + tour.originActivity match { + case Some(_) => + tour.activities + .sliding(2) + .map { case Seq(activity1, activity2) => + getSkimInfo(activity1, activity2, modes, vehicleTypeId, vehicleType, fuelPrice) + } + .toSeq + + case _ => Seq[Map[BeamMode, ODSkimmerTimeCostTransfer]]() + } + } + + def getSkimInfo( + activity1: Activity, + activity2: Activity, + modes: Iterable[BeamMode], + vehicleTypeId: Id[BeamVehicleType], + vehicleType: BeamVehicleType, + fuelPrice: Double + ): Map[BeamMode, ODSkimmerTimeCostTransfer] = { + modes.map { mode => + val skim = getTimeDistanceAndCost( + activity1.getCoord, + activity2.getCoord, + activity1.getEndTime.toInt, + mode, + vehicleTypeId, + vehicleType, + fuelPrice + ) + mode -> ODSkimmerTimeCostTransfer(skim.generalizedTime / 3600.0, skim.cost, 0, 0) + }.toMap + } + def getTimeDistanceAndCost( originUTM: Location, destinationUTM: Location, diff --git a/src/main/scala/beam/sim/BeamMobsim.scala b/src/main/scala/beam/sim/BeamMobsim.scala index 7c711ddfc80..a4d8e472f89 100755 --- a/src/main/scala/beam/sim/BeamMobsim.scala +++ b/src/main/scala/beam/sim/BeamMobsim.scala @@ -28,6 +28,7 @@ import beam.router.osm.TollCalculator import beam.router.skim.TAZSkimsCollector import beam.sim.common.GeoUtils import beam.sim.config.BeamConfig.Beam +import beam.sim.config.BeamConfigHolder import beam.sim.metrics.SimulationMetricCollector.SimulationTime import beam.sim.metrics.{Metrics, MetricsSupport, SimulationMetricCollector} import beam.sim.monitoring.ErrorListener @@ -68,7 +69,8 @@ class BeamMobsim @Inject() ( val geo: GeoUtils, val planCleaner: ModeIterationPlanCleaner, val networkHelper: NetworkHelper, - val rideHailFleetInitializerProvider: RideHailFleetInitializerProvider + val rideHailFleetInitializerProvider: RideHailFleetInitializerProvider, + beamConfigHolder: BeamConfigHolder ) extends Mobsim with LazyLogging with MetricsSupport { @@ -184,7 +186,8 @@ class BeamMobsim @Inject() ( rideHailSurgePricingManager, rideHailIterationHistory, routeHistory, - rideHailFleetInitializerProvider + rideHailFleetInitializerProvider, + beamConfigHolder ) ), "BeamMobsim.iteration" @@ -366,7 +369,8 @@ class BeamMobsimIteration( val rideHailSurgePricingManager: RideHailSurgePricingManager, val rideHailIterationHistory: RideHailIterationHistory, val routeHistory: RouteHistory, - val rideHailFleetInitializerProvider: RideHailFleetInitializerProvider + val rideHailFleetInitializerProvider: RideHailFleetInitializerProvider, + beamConfigHolder: BeamConfigHolder ) extends LoggingMessageActor with ActorLogging with MetricsSupport { @@ -559,7 +563,8 @@ class BeamMobsimIteration( chargingNetworkManager, sharedVehicleFleets, matsimServices.getEvents, - routeHistory + routeHistory, + beamConfigHolder ), "population" ) diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index 99b12d2592f..56abe427ac0 100644 --- a/src/main/scala/beam/sim/config/BeamConfig.scala +++ b/src/main/scala/beam/sim/config/BeamConfig.scala @@ -1862,7 +1862,7 @@ object BeamConfig { c: com.typesafe.config.Config ): BeamConfig.Beam.Agentsim.Agents.Vehicles.SharedFleets$Elm.FixedNonReservingFleetByTaz = { BeamConfig.Beam.Agentsim.Agents.Vehicles.SharedFleets$Elm.FixedNonReservingFleetByTaz( - fleetSize = if (c.hasPathOrNull("fleetSize")) c.getInt("fleetSize") else 10, + fleetSize = 60, maxWalkingDistance = if (c.hasPathOrNull("maxWalkingDistance")) c.getInt("maxWalkingDistance") else 500, vehicleTypeId = @@ -4343,8 +4343,8 @@ object BeamConfig { bike = if (c.hasPathOrNull("bike")) c.getInt("bike") else 60, bike_rent = if (c.hasPathOrNull("bike_rent")) c.getInt("bike_rent") else 180, car = if (c.hasPathOrNull("car")) c.getInt("car") else 300, - ride_hail = if (c.hasPathOrNull("ride_hail")) c.getInt("ride_hail") else 0, - walk = if (c.hasPathOrNull("walk")) c.getInt("walk") else 0 + ride_hail = if (c.hasPathOrNull("ride_hail")) c.getInt("ride_hail") else 30, + walk = if (c.hasPathOrNull("walk")) c.getInt("walk") else 1 ) } } diff --git a/src/main/scala/beam/sim/vehiclesharing/AvailabilityBasedRepositioning.scala b/src/main/scala/beam/sim/vehiclesharing/AvailabilityBasedRepositioning.scala index 4f18b3e5194..11e09ade371 100644 --- a/src/main/scala/beam/sim/vehiclesharing/AvailabilityBasedRepositioning.scala +++ b/src/main/scala/beam/sim/vehiclesharing/AvailabilityBasedRepositioning.scala @@ -10,6 +10,7 @@ import beam.sim.BeamServices import org.matsim.api.core.v01.Id import scala.collection.mutable +import scala.jdk.CollectionConverters.collectionAsScalaIterableConverter case class AvailabilityBasedRepositioning( repositionTimeBin: Int, @@ -138,7 +139,15 @@ case class AvailabilityBasedRepositioning( _, SpaceTime(org.taz.coord, now), org.taz.tazId, - SpaceTime(TAZTreeMap.randomLocationInTAZ(dst.taz, rand), arrivalTime), + SpaceTime( + TAZTreeMap + .randomLocationInTAZ( + dst.taz, + rand, + beamServices.beamScenario.tazTreeMap.tazToLinkIdMapping(dst.taz.tazId).values().asScala + ), + arrivalTime + ), dst.taz.tazId ) ) diff --git a/src/main/scala/beam/sim/vehiclesharing/AvailabilityBehaviorBasedRepositioning.scala b/src/main/scala/beam/sim/vehiclesharing/AvailabilityBehaviorBasedRepositioning.scala index e23fe5d61c9..bd86eca71ad 100644 --- a/src/main/scala/beam/sim/vehiclesharing/AvailabilityBehaviorBasedRepositioning.scala +++ b/src/main/scala/beam/sim/vehiclesharing/AvailabilityBehaviorBasedRepositioning.scala @@ -10,6 +10,7 @@ import beam.sim.BeamServices import org.matsim.api.core.v01.Id import scala.collection.mutable +import scala.jdk.CollectionConverters.collectionAsScalaIterableConverter case class AvailabilityBehaviorBasedRepositioning( repositionTimeBin: Int, @@ -138,7 +139,14 @@ case class AvailabilityBehaviorBasedRepositioning( _, SpaceTime(org.taz.coord, now), org.taz.tazId, - SpaceTime(TAZTreeMap.randomLocationInTAZ(dst.taz, rand), arrivalTime), + SpaceTime( + TAZTreeMap.randomLocationInTAZ( + dst.taz, + rand, + beamServices.beamScenario.tazTreeMap.tazToLinkIdMapping(dst.taz.tazId).values().asScala + ), + arrivalTime + ), dst.taz.tazId ) ) diff --git a/src/main/scala/beam/sim/vehiclesharing/Fleet.scala b/src/main/scala/beam/sim/vehiclesharing/Fleet.scala index fd37ee285bf..8ee5160b18c 100644 --- a/src/main/scala/beam/sim/vehiclesharing/Fleet.scala +++ b/src/main/scala/beam/sim/vehiclesharing/Fleet.scala @@ -88,8 +88,13 @@ case class FixedNonReservingFleetByTAZ( (0 until fleetShare).foreach(_ => initialLocation .append(beamServices.beamScenario.tazTreeMap.getTAZ(Id.create(idTaz, classOf[TAZ])) match { - case Some(taz) if coord.getX == 0.0 & coord.getY == 0.0 => TAZTreeMap.randomLocationInTAZ(taz, rand) - case _ => coord + case Some(taz) if coord.getX == 0.0 & coord.getY == 0.0 => + TAZTreeMap.randomLocationInTAZ( + taz, + rand, + beamServices.beamScenario.tazTreeMap.tazToLinkIdMapping(taz.tazId).values().asScala + ) + case _ => coord }) ) } @@ -100,7 +105,13 @@ case class FixedNonReservingFleetByTAZ( val tazArray = beamServices.beamScenario.tazTreeMap.getTAZs.toArray (1 to config.fleetSize).foreach { _ => val taz = tazArray(rand.nextInt(tazArray.length)) - initialLocation.prepend(TAZTreeMap.randomLocationInTAZ(taz, rand)) + initialLocation.prepend( + TAZTreeMap.randomLocationInTAZ( + taz, + rand, + beamServices.beamScenario.tazTreeMap.tazToLinkIdMapping(taz.tazId).values().asScala + ) + ) } } diff --git a/src/main/scala/beam/utils/EventReader.scala b/src/main/scala/beam/utils/EventReader.scala index 25764ad3e86..d826442b3b2 100644 --- a/src/main/scala/beam/utils/EventReader.scala +++ b/src/main/scala/beam/utils/EventReader.scala @@ -101,6 +101,8 @@ object EventReader { ParkingEvent(event) case ModeChoiceEvent.EVENT_TYPE => ModeChoiceEvent.apply(event) + case TourModeChoiceEvent.EVENT_TYPE => + TourModeChoiceEvent.apply(event) case PersonCostEvent.EVENT_TYPE => PersonCostEvent.apply(event) case ReserveRideHailEvent.EVENT_TYPE => diff --git a/src/main/scala/beam/utils/SnapCoordinateUtils.scala b/src/main/scala/beam/utils/SnapCoordinateUtils.scala index bb9b5697d0d..0d52071525e 100644 --- a/src/main/scala/beam/utils/SnapCoordinateUtils.scala +++ b/src/main/scala/beam/utils/SnapCoordinateUtils.scala @@ -56,7 +56,7 @@ object SnapCoordinateUtils extends LazyLogging { val wgsCoord = geo.utm2Wgs(utmCoord) if (streetLayer.envelope.contains(wgsCoord.getX, wgsCoord.getY)) { val snapCoordOpt = store.getOrElseUpdate( - utmCoord, + wgsCoord, Option(geo.getR5Split(streetLayer, wgsCoord, maxRadius)).map { split => val updatedPlanCoord = geo.splitToCoord(split) geo.wgs2Utm(updatedPlanCoord) diff --git a/src/main/scala/beam/utils/plan/sampling/AvailableModeUtils.scala b/src/main/scala/beam/utils/plan/sampling/AvailableModeUtils.scala index dc2298f18b1..e41a5e94ecf 100644 --- a/src/main/scala/beam/utils/plan/sampling/AvailableModeUtils.scala +++ b/src/main/scala/beam/utils/plan/sampling/AvailableModeUtils.scala @@ -63,6 +63,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 * diff --git a/src/main/scala/beam/utils/scenario/urbansim/HOVModeTransformer.scala b/src/main/scala/beam/utils/scenario/urbansim/HOVModeTransformer.scala index aad7bdeb093..114c354da5e 100644 --- a/src/main/scala/beam/utils/scenario/urbansim/HOVModeTransformer.scala +++ b/src/main/scala/beam/utils/scenario/urbansim/HOVModeTransformer.scala @@ -399,12 +399,19 @@ object HOVModeTransformer extends LazyLogging { } } - trip.map { planElement => - planElement.legMode match { - case Some(value) if isHOV2(value) => planElement.copy(legMode = Some(getHOV2CarOrTeleportation)) - case Some(value) if isHOV3(value) => planElement.copy(legMode = Some(getHOV3CarOrTeleportation)) - case _ => planElement - } + val modeForTour = trip.view.flatMap(_.legMode).headOption match { + case Some(value) if isHOV2(value) => Some(getHOV2CarOrTeleportation) + case Some(value) if isHOV3(value) => Some(getHOV3CarOrTeleportation) + case _ => None + } + + trip.map { + case planElement if planElement.planElementType == PlanElement.Leg => + modeForTour match { + case Some(value) => planElement.copy(legMode = Some(value)) + case _ => planElement + } + case planElement: PlanElement => planElement } } } diff --git a/src/test/scala/beam/agentsim/agents/PersonAgentSpec.scala b/src/test/scala/beam/agentsim/agents/PersonAgentSpec.scala index 677e27fd712..6916349c193 100644 --- a/src/test/scala/beam/agentsim/agents/PersonAgentSpec.scala +++ b/src/test/scala/beam/agentsim/agents/PersonAgentSpec.scala @@ -4,7 +4,7 @@ import akka.actor.{Actor, ActorRef, ActorSystem, PoisonPill, Props} import akka.testkit.TestActors.ForwardActor import akka.testkit.{ImplicitSender, TestActorRef, TestFSMRef, TestKitBase, TestProbe} import beam.agentsim.agents.PersonTestUtil._ -import beam.agentsim.agents.choice.mode.ModeChoiceUniformRandom +import beam.agentsim.agents.choice.mode.{ModeChoiceUniformRandom, TourModeChoiceMultinomialLogit} import beam.agentsim.agents.household.HouseholdActor.HouseholdActor import beam.agentsim.agents.modalbehaviors.DrivesVehicle.{AlightVehicleTrigger, BoardVehicleTrigger} import beam.agentsim.agents.ridehail.{RideHailRequest, RideHailResponse} @@ -21,6 +21,7 @@ import beam.router.model.RoutingModel.TransitStopsInfo import beam.router.model.{EmbodiedBeamLeg, _} import beam.router.osm.TollCalculator import beam.router.skim.core.AbstractSkimmerEvent +import beam.sim.config.BeamConfigHolder import beam.sim.vehicles.VehiclesAdjustment import beam.tags.FlakyTest import beam.utils.TestConfigUtils.testConfig @@ -68,6 +69,9 @@ class PersonAgentSpec private lazy val modeChoiceCalculator = new ModeChoiceUniformRandom(beamConfig) + private lazy val tourModeChoiceCalculator = + new TourModeChoiceMultinomialLogit(attributesOfIndividual, tourModeChoiceModel, configHolder) + // Mock a transit driver (who has to be a child of a mock router) private lazy val transitDriverProps = Props(new ForwardActor(self)) @@ -102,7 +106,7 @@ class PersonAgentSpec val parkingManager = system.actorOf(Props(new TrivialParkingManager)) val person = PopulationUtils.getFactory.createPerson(Id.createPersonId("dummyAgent")) putDefaultBeamAttributes(person, Vector(WALK)) - val homeActivity = PopulationUtils.createActivityFromLinkId("home", Id.createLinkId(1)) + val homeActivity = createActivity("home", 1) homeActivity.setStartTime(1.0) homeActivity.setEndTime(10.0) val plan = PopulationUtils.getFactory.createPlan() @@ -114,6 +118,7 @@ class PersonAgentSpec services, beamScenario, modeChoiceCalculator, + tourModeChoiceCalculator, beamScenario.transportNetwork, self, self, @@ -153,11 +158,8 @@ class PersonAgentSpec val person = PopulationUtils.getFactory.createPerson(Id.createPersonId("dummyAgent")) putDefaultBeamAttributes(person, Vector(RIDE_HAIL, RIDE_HAIL_TRANSIT, WALK)) val plan = PopulationUtils.getFactory.createPlan() - val homeActivity = PopulationUtils.createActivityFromLinkId("home", Id.createLinkId(1)) - homeActivity.setEndTime(28800) // 8:00:00 AM - plan.addActivity(homeActivity) - val workActivity = PopulationUtils.createActivityFromLinkId("work", Id.createLinkId(2)) - plan.addActivity(workActivity) + plan.addActivity(createActivity("home", 1, 28800)) + plan.addActivity(createActivity("work", 2)) person.addPlan(plan) population.addPerson(person) household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) @@ -192,7 +194,8 @@ class PersonAgentSpec Vector(), Set.empty, new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) @@ -404,7 +407,8 @@ class PersonAgentSpec Vector(), Set.empty, new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) @@ -691,7 +695,8 @@ class PersonAgentSpec Vector(), Set.empty, new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) @@ -873,6 +878,15 @@ class PersonAgentSpec } + private def createActivity(activity: String, linkId: Int, endTime: Int = -1) = { + val homeActivity = PopulationUtils.createActivityFromLinkId(activity, Id.createLinkId(linkId)) + homeActivity.setCoord(services.networkHelper.getLink(linkId).get.getCoord) + if (endTime > 0) { + homeActivity.setEndTime(endTime) + } + homeActivity + } + after { import scala.concurrent.duration._ import scala.language.postfixOps diff --git a/src/test/scala/beam/agentsim/agents/PersonAndTransitDriverSpec.scala b/src/test/scala/beam/agentsim/agents/PersonAndTransitDriverSpec.scala index 0d419c69442..8089c484186 100644 --- a/src/test/scala/beam/agentsim/agents/PersonAndTransitDriverSpec.scala +++ b/src/test/scala/beam/agentsim/agents/PersonAndTransitDriverSpec.scala @@ -21,6 +21,7 @@ import beam.router.RouteHistory import beam.router.model.RoutingModel.TransitStopsInfo import beam.router.model._ import beam.router.skim.core.AbstractSkimmerEvent +import beam.sim.config.BeamConfigHolder import beam.sim.vehicles.VehiclesAdjustment import beam.utils.TestConfigUtils.testConfig import beam.utils.{SimRunnerForTest, StuckFinder, TestConfigUtils} @@ -303,7 +304,8 @@ class PersonAndTransitDriverSpec Vector(), Set.empty, new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) diff --git a/src/test/scala/beam/agentsim/agents/PersonWithPersonalVehiclePlanSpec.scala b/src/test/scala/beam/agentsim/agents/PersonWithPersonalVehiclePlanSpec.scala index e7a7b6a0e2c..7a819930ddd 100644 --- a/src/test/scala/beam/agentsim/agents/PersonWithPersonalVehiclePlanSpec.scala +++ b/src/test/scala/beam/agentsim/agents/PersonWithPersonalVehiclePlanSpec.scala @@ -1,14 +1,22 @@ package beam.agentsim.agents import akka.actor.{ActorSystem, Props} +import akka.pattern.ask import akka.testkit.{ImplicitSender, TestActorRef, TestKitBase, TestProbe} +import akka.util.Timeout + import beam.agentsim.agents.PersonTestUtil._ import beam.agentsim.agents.choice.mode.ModeChoiceUniformRandom import beam.agentsim.agents.household.HouseholdActor.HouseholdActor import beam.agentsim.agents.vehicles.EnergyEconomyAttributes.Powertrain import beam.agentsim.agents.vehicles.{BeamVehicle, _} import beam.agentsim.events._ -import beam.agentsim.infrastructure.{AnotherTrivialParkingManager, TrivialParkingManager} +import beam.agentsim.infrastructure.{ + AnotherTrivialParkingManager, + ParkingInquiry, + ParkingInquiryResponse, + TrivialParkingManager +} import beam.agentsim.scheduler.BeamAgentScheduler import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTrigger, SchedulerProps, StartSchedule} import beam.router.BeamRouter._ @@ -30,14 +38,16 @@ import org.matsim.core.events.EventsManagerImpl import org.matsim.core.events.handler.BasicEventHandler import org.matsim.core.population.PopulationUtils import org.matsim.core.population.routes.RouteUtils -import org.matsim.households.{Household, HouseholdsFactoryImpl} +import org.matsim.households.{Household, HouseholdsFactoryImpl, Income, IncomeImpl} import org.matsim.vehicles._ import org.scalatest.matchers.should.Matchers._ import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll} import org.scalatest.funspec.AnyFunSpecLike +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference import scala.collection.{mutable, JavaConverters} +import scala.concurrent.ExecutionContext class PersonWithPersonalVehiclePlanSpec extends AnyFunSpecLike @@ -55,12 +65,15 @@ class PersonWithPersonalVehiclePlanSpec akka.actor.debug.fsm = true akka.loglevel = debug akka.test.timefactor = 2 + beam.agentsim.agents.vehicles.generateEmergencyHouseholdVehicleWhenPlansRequireIt = true """ ) .withFallback(testConfig("test/input/beamville/beam.conf")) .resolve() lazy implicit val system: ActorSystem = ActorSystem("PersonWithPersonalVehiclePlanSpec", config) + private implicit val timeout: Timeout = Timeout(60, TimeUnit.SECONDS) + private implicit val executionContext: ExecutionContext = system.dispatcher override def outputDirPath: String = TestConfigUtils.testOutputDir @@ -132,7 +145,8 @@ class PersonWithPersonalVehiclePlanSpec Vector(), Set.empty, new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) ) @@ -140,6 +154,9 @@ class PersonWithPersonalVehiclePlanSpec scheduler ! StartSchedule(0) + // Agent will first choose their tour mode before doing detailed routing + expectMsgType[TourModeChoiceEvent] + // The agent will ask for current travel times for a route it already knows. val embodyRequest = expectMsgType[EmbodyWithCurrentTravelTime] assert(services.geo.wgs2Utm(embodyRequest.leg.travelPath.startPoint.loc).getX === homeLocation.getX +- 1) @@ -370,7 +387,8 @@ class PersonWithPersonalVehiclePlanSpec Vector(), Set.empty, new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) ) @@ -378,6 +396,8 @@ class PersonWithPersonalVehiclePlanSpec scheduler ! StartSchedule(0) + expectMsgType[TourModeChoiceEvent] + // The agent will ask for current travel times for a route it already knows. val embodyRequest = expectMsgType[EmbodyWithCurrentTravelTime] assert(services.geo.wgs2Utm(embodyRequest.leg.travelPath.startPoint.loc).getX === homeLocation.getX +- 1) @@ -453,6 +473,7 @@ class PersonWithPersonalVehiclePlanSpec it("should use another car when the car that is in the plan is taken") { val modeChoiceEvents = new TestProbe(system) + val tourModeChoiceEvents = new TestProbe(system) val personEntersVehicleEvents = new TestProbe(system) val eventsManager = new EventsManagerImpl() eventsManager.addHandler( @@ -460,6 +481,7 @@ class PersonWithPersonalVehiclePlanSpec override def handleEvent(event: Event): Unit = { event match { case _: AbstractSkimmerEvent => // ignore + case _: TourModeChoiceEvent => tourModeChoiceEvents.ref ! event case _: ModeChoiceEvent => modeChoiceEvents.ref ! event case _: PersonEntersVehicleEvent => personEntersVehicleEvents.ref ! event case _ => // ignore @@ -519,7 +541,8 @@ class PersonWithPersonalVehiclePlanSpec Vector(), Set.empty, new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) @@ -552,6 +575,9 @@ class PersonWithPersonalVehiclePlanSpec } } + tourModeChoiceEvents.expectMsgType[TourModeChoiceEvent] + tourModeChoiceEvents.expectMsgType[TourModeChoiceEvent] + modeChoiceEvents.expectMsgType[ModeChoiceEvent] modeChoiceEvents.expectMsgType[ModeChoiceEvent] @@ -566,12 +592,14 @@ class PersonWithPersonalVehiclePlanSpec it("should create a last resort car if told to drive but no cars are available") { val modeChoiceEvents = new TestProbe(system) val personEntersVehicleEvents = new TestProbe(system) + val tourModeChoiceEvents = new TestProbe(system) val eventsManager = new EventsManagerImpl() eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { event match { case _: AbstractSkimmerEvent => // ignore + case _: TourModeChoiceEvent => tourModeChoiceEvents.ref ! event case _: ModeChoiceEvent => modeChoiceEvents.ref ! event case _: PersonEntersVehicleEvent => personEntersVehicleEvents.ref ! event case _ => // ignore @@ -595,6 +623,7 @@ class PersonWithPersonalVehiclePlanSpec population.addPerson(otherPerson) household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId, otherPerson.getId))) + household.setIncome(new IncomeImpl(40, Income.IncomePeriod.year)) val scheduler = TestActorRef[BeamAgentScheduler]( SchedulerProps( @@ -627,7 +656,8 @@ class PersonWithPersonalVehiclePlanSpec Vector(), Set(vehicleType), new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) @@ -665,23 +695,28 @@ class PersonWithPersonalVehiclePlanSpec isEmbodyWithCurrentTravelTime = false, triggerId = triggerId ) + case inq: ParkingInquiry => + (parkingManager ? inq).mapTo[ParkingInquiryResponse].map(x => lastSender ! x) } for (_ <- 0 to 1) { expectMsgPF()(messageResponder) } + tourModeChoiceEvents.expectMsgType[TourModeChoiceEvent] + tourModeChoiceEvents.expectMsgType[TourModeChoiceEvent] + modeChoiceEvents.expectMsgType[ModeChoiceEvent] expectMsgPF()(messageResponder) + expectMsgPF()(messageResponder) modeChoiceEvents.expectMsgType[ModeChoiceEvent] +// expectMsgPF()(messageResponder) personEntersVehicleEvents.expectMsgType[PersonEntersVehicleEvent] personEntersVehicleEvents.expectMsgType[PersonEntersVehicleEvent] personEntersVehicleEvents.expectMsgType[PersonEntersVehicleEvent] expectMsgType[CompletionNotice] - - // TODO: Testing last resort vehicle creation } it("should walk to a car that is far away (if told so by the router") { @@ -739,12 +774,15 @@ class PersonWithPersonalVehiclePlanSpec Vector(), Set.empty, new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) scheduler ! StartSchedule(0) +// expectMsgType[TourModeChoiceEvent] + val routingRequest = expectMsgType[RoutingRequest] lastSender ! RoutingResponse( itineraries = Vector( @@ -799,6 +837,7 @@ class PersonWithPersonalVehiclePlanSpec triggerId = routingRequest.triggerId ) +// expectMsgType[TourModeChoiceEvent] expectMsgType[ModeChoiceEvent] expectMsgType[ActivityEndEvent] expectMsgType[PersonDepartureEvent] diff --git a/src/test/scala/beam/agentsim/agents/PersonWithTourModeSpec.scala b/src/test/scala/beam/agentsim/agents/PersonWithTourModeSpec.scala new file mode 100644 index 00000000000..f657da2c314 --- /dev/null +++ b/src/test/scala/beam/agentsim/agents/PersonWithTourModeSpec.scala @@ -0,0 +1,1853 @@ +package beam.agentsim.agents + +import akka.actor.{ActorSystem, Props} +import akka.pattern.{ask, pipe} +import akka.testkit.{ImplicitSender, TestActorRef, TestKitBase, TestProbe} +import akka.util.Timeout + +import scala.concurrent.duration._ +import beam.agentsim.agents.PersonTestUtil._ +import beam.agentsim.agents.choice.logit.TourModeChoiceModel +import beam.agentsim.agents.choice.mode.ModeChoiceUniformRandom +import beam.agentsim.agents.household.HouseholdActor.{HouseholdActor, MobilityStatusInquiry, MobilityStatusResponse} +import beam.agentsim.agents.vehicles.EnergyEconomyAttributes.Powertrain +import beam.agentsim.agents.vehicles._ +import beam.agentsim.events._ +import beam.agentsim.infrastructure._ +import beam.agentsim.scheduler.{BeamAgentScheduler, HasTriggerId} +import beam.agentsim.scheduler.BeamAgentScheduler.{ + CompletionNotice, + ScheduleKillTrigger, + ScheduleTrigger, + SchedulerMessage, + SchedulerProps, + StartSchedule +} +import beam.router.BeamRouter._ +import beam.router.Modes.BeamMode +import beam.router.Modes.BeamMode.{BIKE, CAR, WALK} +import beam.agentsim.agents.modalbehaviors.DrivesVehicle.{ActualVehicle, Token} +import beam.router.RouteHistory +import beam.router.TourModes.BeamTourMode +import beam.router.TourModes.BeamTourMode.{CAR_BASED, WALK_BASED} +import beam.router.model._ +import beam.router.skim.core.AbstractSkimmerEvent +import beam.sim.population.{AttributesOfIndividual, HouseholdAttributes} +import beam.sim.vehicles.VehiclesAdjustment +import beam.utils.TestConfigUtils.testConfig +import beam.utils.{SimRunnerForTest, StuckFinder, TestConfigUtils} +import com.typesafe.config.{Config, ConfigFactory} +import org.matsim.api.core.v01.events._ +import org.matsim.api.core.v01.population.Person +import org.matsim.api.core.v01.{Coord, Id} +import org.matsim.core.api.experimental.events.TeleportationArrivalEvent +import org.matsim.core.config.ConfigUtils +import org.matsim.core.events.EventsManagerImpl +import org.matsim.core.events.handler.BasicEventHandler +import org.matsim.core.population.PopulationUtils +import org.matsim.core.population.routes.RouteUtils +import org.matsim.households.{Household, HouseholdsFactoryImpl} +import org.matsim.vehicles._ +import org.scalatest.funspec.AnyFunSpecLike +import org.scalatest.matchers.should.Matchers._ +import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll} + +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference +import scala.collection.{mutable, JavaConverters} +import scala.concurrent.ExecutionContext +import scala.language.postfixOps + +class PersonWithTourModeSpec + extends AnyFunSpecLike + with TestKitBase + with SimRunnerForTest + with BeforeAndAfterAll + with BeforeAndAfter + with ImplicitSender + with BeamvilleFixtures { + + private implicit val timeout: Timeout = Timeout(60, TimeUnit.SECONDS) + private implicit val executionContext: ExecutionContext = system.dispatcher + + lazy val config: Config = ConfigFactory + .parseString( + """ + akka.log-dead-letters = 10 + akka.actor.debug.fsm = true + akka.loglevel = debug + akka.test.timefactor = 6 + """ + ) + .withFallback(testConfig("test/input/beamville/beam.conf")) + .resolve() + + lazy implicit val system: ActorSystem = ActorSystem("PersonWithTourModeSpec", config) + + override def outputDirPath: String = TestConfigUtils.testOutputDir + + private val householdsFactory: HouseholdsFactoryImpl = new HouseholdsFactoryImpl() + + private lazy val modeChoiceCalculator = new ModeChoiceUniformRandom(beamConfig) + private lazy val tourModeChoiceCalculator = new TourModeChoiceModel(beamConfig) + + val homeLocation = new Coord(170308.4, 2964.6474) + val workLocation = new Coord(169346.4, 876.7536) + val otherLocation = new Coord(168346.4, 1276.7536) + + describe("A PersonAgent") { + + val hoseHoldDummyId = Id.create("dummy", classOf[Household]) + + it("should know how to take a car trip on a car_based tour when the mode is already in its plan") { + val eventsManager = new EventsManagerImpl() + eventsManager.addHandler( + new BasicEventHandler { + override def handleEvent(event: Event): Unit = { + event match { + case _: ModeChoiceEvent | _: TourModeChoiceEvent | _: ActivityEndEvent | _: ActivityStartEvent | + _: ReplanningEvent => + self ! event + case _ => + } + } + } + ) + val vehicleId = Id.createVehicleId("dummySharedCar") + val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val beamVehicle = new BeamVehicle(vehicleId, new Powertrain(0.0), vehicleType) + + val household = householdsFactory.createHousehold(hoseHoldDummyId) + val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) + + val person: Person = + createTestPerson(Id.createPersonId("dummyAgent"), vehicleId, Some(CAR), Some(CAR_BASED), Some(beamVehicle.id)) + population.addPerson(person) + + household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) + + val scheduler = TestActorRef[BeamAgentScheduler]( + SchedulerProps( + beamConfig, + stopTick = 24 * 60 * 60, + maxWindow = 10, + new StuckFinder(beamConfig.beam.debug.stuckAgentDetection) + ) + ) + val parkingLocation = new Coord(167138.4, 1117.0) + val parkingManager = system.actorOf(Props(new AnotherTrivialParkingManager(parkingLocation))) + + val householdActor = TestActorRef[HouseholdActor]( + Props( + new HouseholdActor( + services, + beamScenario, + _ => modeChoiceCalculator, + scheduler, + beamScenario.transportNetwork, + services.tollCalculator, + self, + self, + parkingManager, + self, + eventsManager, + population, + household, + Map(beamVehicle.id -> beamVehicle), + new Coord(0.0, 0.0), + Vector(), + Set.empty, + new RouteHistory(beamConfig), + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder + ) + ) + ) + scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) + + scheduler ! StartSchedule(0) + + // The agent will ask for current travel times for a route it already knows. + val embodyRequest = expectMsgType[EmbodyWithCurrentTravelTime] + assert(services.geo.wgs2Utm(embodyRequest.leg.travelPath.startPoint.loc).getX === homeLocation.getX +- 1) + assert(services.geo.wgs2Utm(embodyRequest.leg.travelPath.endPoint.loc).getY === workLocation.getY +- 1) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = embodyRequest.leg.copy( + duration = 500, + travelPath = embodyRequest.leg.travelPath + .copy( + linkTravelTime = embodyRequest.leg.travelPath.linkIds.map(_ => 50.0), + endPoint = embodyRequest.leg.travelPath.endPoint + .copy(time = embodyRequest.leg.startTime + (embodyRequest.leg.travelPath.linkIds.size - 1) * 50) + ) + ), + beamVehicleId = vehicleId, + Id.create("TRANSIT-TYPE-DEFAULT", classOf[BeamVehicleType]), + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = embodyRequest.triggerId + ) + + expectMsgType[ModeChoiceEvent] + expectMsgType[ActivityEndEvent] + + val parkingRoutingRequest = expectMsgType[RoutingRequest] + assert(parkingRoutingRequest.destinationUTM == parkingLocation) + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = parkingRoutingRequest.departureTime, + mode = BeamMode.CAR, + duration = 50, + travelPath = BeamPath( + linkIds = Array(142, 60, 58, 62, 80), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = SpaceTime( + services.geo.utm2Wgs(parkingRoutingRequest.originUTM), + parkingRoutingRequest.departureTime + ), + endPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), parkingRoutingRequest.departureTime + 200), + distanceInM = 1000d + ) + ), + beamVehicleId = Id.createVehicleId("car-1"), + Id.create("TRANSIT-TYPE-DEFAULT", classOf[BeamVehicleType]), + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = parkingRoutingRequest.triggerId + ) + + val walkFromParkingRoutingRequest = expectMsgType[RoutingRequest] + assert(walkFromParkingRoutingRequest.originUTM.getX === parkingLocation.getX +- 1) + assert(walkFromParkingRoutingRequest.originUTM.getY === parkingLocation.getY +- 1) + assert(walkFromParkingRoutingRequest.destinationUTM.getX === workLocation.getX +- 1) + assert(walkFromParkingRoutingRequest.destinationUTM.getY === workLocation.getY +- 1) + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = walkFromParkingRoutingRequest.departureTime, + mode = BeamMode.WALK, + duration = 50, + travelPath = BeamPath( + linkIds = Array(80, 62, 58, 60, 142), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), walkFromParkingRoutingRequest.departureTime), + endPoint = SpaceTime( + services.geo.utm2Wgs(walkFromParkingRoutingRequest.destinationUTM), + walkFromParkingRoutingRequest.departureTime + 200 + ), + distanceInM = 1000d + ) + ), + beamVehicleId = walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.id, + walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.vehicleTypeId, + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = walkFromParkingRoutingRequest.triggerId + ) + + expectMsgType[ActivityStartEvent] + + lastSender ! ScheduleKillTrigger(lastSender, walkFromParkingRoutingRequest.triggerId) + receiveWhile(500 millis) { + case _: SchedulerMessage => + case x: Event => println(x.toString) + case x: HasTriggerId => println(x.toString) + } + } + it("should choose a car_based tour when a car trip is already in its plan") { + val eventsManager = new EventsManagerImpl() + eventsManager.addHandler( + new BasicEventHandler { + override def handleEvent(event: Event): Unit = { + event match { + case _: ModeChoiceEvent | _: TourModeChoiceEvent | _: ActivityEndEvent | _: ActivityStartEvent | + _: ReplanningEvent => + self ! event + case _ => + } + } + } + ) + val vehicleId = Id.createVehicleId("car-dummyAgent") + val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val beamVehicle = new BeamVehicle(vehicleId, new Powertrain(0.0), vehicleType) + + val household = householdsFactory.createHousehold(hoseHoldDummyId) + val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) + + val person: Person = + createTestPerson(Id.createPersonId("dummyAgent"), vehicleId, Some(CAR), None, None) + population.addPerson(person) + + household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) + + val scheduler = TestActorRef[BeamAgentScheduler]( + SchedulerProps( + beamConfig, + stopTick = 24 * 60 * 60, + maxWindow = 10, + new StuckFinder(beamConfig.beam.debug.stuckAgentDetection) + ) + ) + val parkingLocation = new Coord(167138.4, 1117.0) + val parkingManager = system.actorOf(Props(new AnotherTrivialParkingManager(parkingLocation))) + + val householdActor = TestActorRef[HouseholdActor]( + Props( + new HouseholdActor( + services, + beamScenario, + _ => modeChoiceCalculator, + scheduler, + beamScenario.transportNetwork, + services.tollCalculator, + self, + self, + parkingManager, + self, + eventsManager, + population, + household, + Map(beamVehicle.id -> beamVehicle), + new Coord(0.0, 0.0), + Vector(), + Set.empty, + new RouteHistory(beamConfig), + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder + ) + ) + ) + scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) + + scheduler ! StartSchedule(0) + + // The agent will ask for current travel times for a route it already knows. + val tmc = expectMsgType[TourModeChoiceEvent] + // Make sure that they chose a car_based tour + assert(tmc.tourMode === "car_based") + // Make sure it didn't actually go through the process of calculating utilities b/c it didn't have to + assert(tmc.tourModeToUtilityString === "") + val embodyRequest = expectMsgType[EmbodyWithCurrentTravelTime] + assert(services.geo.wgs2Utm(embodyRequest.leg.travelPath.startPoint.loc).getX === homeLocation.getX +- 1) + assert(services.geo.wgs2Utm(embodyRequest.leg.travelPath.endPoint.loc).getY === workLocation.getY +- 1) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = embodyRequest.leg.copy( + duration = 500, + travelPath = embodyRequest.leg.travelPath + .copy( + linkTravelTime = embodyRequest.leg.travelPath.linkIds.map(_ => 50.0), + endPoint = embodyRequest.leg.travelPath.endPoint + .copy(time = embodyRequest.leg.startTime + (embodyRequest.leg.travelPath.linkIds.size - 1) * 50) + ) + ), + beamVehicleId = vehicleId, + Id.create("TRANSIT-TYPE-DEFAULT", classOf[BeamVehicleType]), + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = embodyRequest.triggerId + ) + + expectMsgType[ModeChoiceEvent] + expectMsgType[ActivityEndEvent] + + val parkingRoutingRequest = expectMsgType[RoutingRequest] + assert(parkingRoutingRequest.destinationUTM == parkingLocation) + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = parkingRoutingRequest.departureTime, + mode = BeamMode.CAR, + duration = 50, + travelPath = BeamPath( + linkIds = Array(142, 60, 58, 62, 80), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = SpaceTime( + services.geo.utm2Wgs(parkingRoutingRequest.originUTM), + parkingRoutingRequest.departureTime + ), + endPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), parkingRoutingRequest.departureTime + 200), + distanceInM = 1000d + ) + ), + beamVehicleId = Id.createVehicleId("car-1"), + Id.create("TRANSIT-TYPE-DEFAULT", classOf[BeamVehicleType]), + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = parkingRoutingRequest.triggerId + ) + + val walkFromParkingRoutingRequest = expectMsgType[RoutingRequest] + assert(walkFromParkingRoutingRequest.originUTM.getX === parkingLocation.getX +- 1) + assert(walkFromParkingRoutingRequest.originUTM.getY === parkingLocation.getY +- 1) + assert(walkFromParkingRoutingRequest.destinationUTM.getX === workLocation.getX +- 1) + assert(walkFromParkingRoutingRequest.destinationUTM.getY === workLocation.getY +- 1) + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = walkFromParkingRoutingRequest.departureTime, + mode = BeamMode.WALK, + duration = 50, + travelPath = BeamPath( + linkIds = Array(80, 62, 58, 60, 142), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), walkFromParkingRoutingRequest.departureTime), + endPoint = SpaceTime( + services.geo.utm2Wgs(walkFromParkingRoutingRequest.destinationUTM), + walkFromParkingRoutingRequest.departureTime + 200 + ), + distanceInM = 1000d + ) + ), + beamVehicleId = walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.id, + walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.vehicleTypeId, + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = walkFromParkingRoutingRequest.triggerId + ) + + expectMsgType[ActivityStartEvent] + lastSender ! ScheduleKillTrigger(lastSender, walkFromParkingRoutingRequest.triggerId) + + receiveWhile(500 millis) { + case _: SchedulerMessage => + case x: Event => println(x.toString) + case x: HasTriggerId => println(x.toString) + } + } + it("should choose a car trip when a car_based tour is already in its plan") { + val eventsManager = new EventsManagerImpl() + eventsManager.addHandler( + new BasicEventHandler { + override def handleEvent(event: Event): Unit = { + event match { + case _: ModeChoiceEvent | _: TourModeChoiceEvent | _: ActivityEndEvent | _: ActivityStartEvent | + _: ReplanningEvent => + self ! event + case _ => + } + } + } + ) + val vehicleId = Id.createVehicleId("car-dummyAgent") + val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val beamVehicle = new BeamVehicle(vehicleId, new Powertrain(0.0), vehicleType) + + val household = householdsFactory.createHousehold(hoseHoldDummyId) + val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) + + val person: Person = + createTestPerson(Id.createPersonId("dummyAgent"), vehicleId, None, Some(CAR_BASED), None, withRoute = false) + population.addPerson(person) + + household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) + + val scheduler = TestActorRef[BeamAgentScheduler]( + SchedulerProps( + beamConfig, + stopTick = 24 * 60 * 60, + maxWindow = 10, + new StuckFinder(beamConfig.beam.debug.stuckAgentDetection) + ) + ) + val parkingLocation = new Coord(167138.4, 1117.0) + val parkingManager = system.actorOf(Props(new AnotherTrivialParkingManager(parkingLocation))) + + val householdActor = TestActorRef[HouseholdActor]( + Props( + new HouseholdActor( + services, + beamScenario, + _ => modeChoiceCalculator, + scheduler, + beamScenario.transportNetwork, + services.tollCalculator, + self, + self, + parkingManager, + self, + eventsManager, + population, + household, + Map(beamVehicle.id -> beamVehicle), + new Coord(0.0, 0.0), + Vector(), + Set.empty, + new RouteHistory(beamConfig), + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder + ) + ) + ) + scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) + + scheduler ! StartSchedule(0) + + val routingRequest = expectMsgType[RoutingRequest] + assert(routingRequest.withTransit === false) + val personVehicle = routingRequest.streetVehicles.find(_.mode == WALK).get + val linkIds = Array[Int](228, 206, 180, 178, 184, 102) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + false, + services.geo.utm2Wgs(routingRequest.originUTM), + WALK, + personVehicle.vehicleTypeId + ), + createEmbodiedBeamLeg(routingRequest, beamVehicle.toStreetVehicle, linkIds, 50d), + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime + 250, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + true, + services.geo.utm2Wgs(routingRequest.destinationUTM), + WALK, + personVehicle.vehicleTypeId + ) + ) + ), + EmbodiedBeamTrip(legs = Vector(createEmbodiedBeamLeg(routingRequest, personVehicle, linkIds, 150d))) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequest.triggerId + ) + + val mce = expectMsgType[ModeChoiceEvent] + assert(mce.mode === "car") + assert(mce.currentTourMode === "car_based") + assert(mce.availableAlternatives === "CAR") + expectMsgType[ActivityEndEvent] + + val parkingRoutingRequest = expectMsgType[RoutingRequest] + assert(parkingRoutingRequest.destinationUTM == parkingLocation) + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = parkingRoutingRequest.departureTime, + mode = BeamMode.CAR, + duration = 50, + travelPath = BeamPath( + linkIds = Array(142, 60, 58, 62, 80), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = SpaceTime( + services.geo.utm2Wgs(parkingRoutingRequest.originUTM), + parkingRoutingRequest.departureTime + ), + endPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), parkingRoutingRequest.departureTime + 200), + distanceInM = 1000d + ) + ), + beamVehicleId = Id.createVehicleId("car-1"), + Id.create("TRANSIT-TYPE-DEFAULT", classOf[BeamVehicleType]), + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = parkingRoutingRequest.triggerId + ) + + val walkFromParkingRoutingRequest = expectMsgType[RoutingRequest] + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = walkFromParkingRoutingRequest.departureTime, + mode = BeamMode.WALK, + duration = 50, + travelPath = BeamPath( + linkIds = Array(80, 62, 58, 60, 142), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), walkFromParkingRoutingRequest.departureTime), + endPoint = SpaceTime( + services.geo.utm2Wgs(walkFromParkingRoutingRequest.destinationUTM), + walkFromParkingRoutingRequest.departureTime + 200 + ), + distanceInM = 1000d + ) + ), + beamVehicleId = walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.id, + walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.vehicleTypeId, + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = walkFromParkingRoutingRequest.triggerId + ) + + expectMsgType[ActivityStartEvent] + + lastSender ! ScheduleKillTrigger(lastSender, routingRequest.triggerId) + + receiveWhile(500 millis) { + case _: SchedulerMessage => + case x: Event => println(x.toString) + case x: HasTriggerId => println(x.toString) + } + } + it("should choose a walk trip when a walk_based tour is already in its plan") { + val eventsManager = new EventsManagerImpl() + eventsManager.addHandler( + new BasicEventHandler { + override def handleEvent(event: Event): Unit = { + event match { + case _: ModeChoiceEvent | _: TourModeChoiceEvent | _: ActivityEndEvent | _: ActivityStartEvent | + _: ReplanningEvent => + self ! event + case _ => + } + } + } + ) + val vehicleId = Id.createVehicleId("car-dummyAgent") + val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val beamVehicle = new BeamVehicle(vehicleId, new Powertrain(0.0), vehicleType) + + val household = householdsFactory.createHousehold(hoseHoldDummyId) + val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) + + val person: Person = + createTestPerson(Id.createPersonId("dummyAgent"), vehicleId, None, Some(WALK_BASED), None, withRoute = false) + population.addPerson(person) + + household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) + + val scheduler = TestActorRef[BeamAgentScheduler]( + SchedulerProps( + beamConfig, + stopTick = 24 * 60 * 60, + maxWindow = 10, + new StuckFinder(beamConfig.beam.debug.stuckAgentDetection) + ) + ) + val parkingLocation = new Coord(167138.4, 1117.0) + val parkingManager = system.actorOf(Props(new AnotherTrivialParkingManager(parkingLocation))) + + val householdActor = TestActorRef[HouseholdActor]( + Props( + new HouseholdActor( + services, + beamScenario, + _ => modeChoiceCalculator, + scheduler, + beamScenario.transportNetwork, + services.tollCalculator, + self, + self, + parkingManager, + self, + eventsManager, + population, + household, + Map(beamVehicle.id -> beamVehicle), + new Coord(0.0, 0.0), + Vector(), + Set.empty, + new RouteHistory(beamConfig), + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder + ) + ) + ) + scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) + + scheduler ! StartSchedule(0) + + val routingRequest = expectMsgType[RoutingRequest] + assert(routingRequest.withTransit === true) + val personVehicle = routingRequest.streetVehicles.find(_.mode == WALK).get + val linkIds = Array[Int](228, 206, 180, 178, 184, 102) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + false, + services.geo.utm2Wgs(routingRequest.originUTM), + WALK, + personVehicle.vehicleTypeId + ), + createEmbodiedBeamLeg(routingRequest, beamVehicle.toStreetVehicle, linkIds, 50d), + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime + 250, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + true, + services.geo.utm2Wgs(routingRequest.destinationUTM), + WALK, + personVehicle.vehicleTypeId + ) + ) + ), + EmbodiedBeamTrip(legs = Vector(createEmbodiedBeamLeg(routingRequest, personVehicle, linkIds, 150d))) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequest.triggerId + ) + val mce = expectMsgType[ModeChoiceEvent] + assert(mce.mode === "walk") + assert(mce.currentTourMode === "walk_based") + assert(mce.availableAlternatives === "WALK") + expectMsgType[ActivityEndEvent] + + lastSender ! ScheduleKillTrigger(lastSender, routingRequest.triggerId) + receiveWhile(500 millis) { + case _: SchedulerMessage => + case x: Event => println(x.toString) + case x: HasTriggerId => println(x.toString) + } + } + it("should choose between a walk and car trip when tour mode is not set in plan") { + val eventsManager = new EventsManagerImpl() + eventsManager.addHandler( + new BasicEventHandler { + override def handleEvent(event: Event): Unit = { + event match { + case _: ModeChoiceEvent | _: TourModeChoiceEvent | _: ActivityEndEvent | _: ActivityStartEvent | + _: ReplanningEvent => + self ! event + case _ => + } + } + } + ) + val vehicleId = Id.createVehicleId("car-dummyAgent") + val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val beamVehicle = new BeamVehicle(vehicleId, new Powertrain(0.0), vehicleType) + + val household = householdsFactory.createHousehold(hoseHoldDummyId) + val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) + + val person: Person = + createTestPerson(Id.createPersonId("dummyAgent"), vehicleId, None, None, None, withRoute = false) + population.addPerson(person) + + household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) + + val scheduler = TestActorRef[BeamAgentScheduler]( + SchedulerProps( + beamConfig, + stopTick = 24 * 60 * 60, + maxWindow = 10, + new StuckFinder(beamConfig.beam.debug.stuckAgentDetection) + ) + ) + val parkingLocation = new Coord(167138.4, 1117.0) + val parkingManager = system.actorOf(Props(new AnotherTrivialParkingManager(parkingLocation))) + + val householdActor = TestActorRef[HouseholdActor]( + Props( + new HouseholdActor( + services, + beamScenario, + _ => modeChoiceCalculator, + scheduler, + beamScenario.transportNetwork, + services.tollCalculator, + self, + self, + parkingManager, + self, + eventsManager, + population, + household, + Map(beamVehicle.id -> beamVehicle), + new Coord(0.0, 0.0), + Vector(), + Set.empty, + new RouteHistory(beamConfig), + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder + ) + ) + ) + scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) + + scheduler ! StartSchedule(0) + + val tmc = expectMsgType[TourModeChoiceEvent] + val modeUtilities = tmc.tourModeToUtilityString + .replace(" ", "") + .split("->") + .flatMap(_.split(";")) + .sliding(2, 2) + .map { x => x(0) -> x(1).toDouble } + .toMap + + val chosenTourMode = tmc.tourMode + assert(modeUtilities("CAR_BASED") > Double.NegativeInfinity) + assert(modeUtilities("WALK_BASED") > Double.NegativeInfinity) + assert(modeUtilities("BIKE_BASED") === Double.NegativeInfinity) + val routingRequest = expectMsgType[RoutingRequest] + val personVehicle = routingRequest.streetVehicles.find(_.mode == WALK).get + val linkIds = Array[Int](228, 206, 180, 178, 184, 102) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + false, + services.geo.utm2Wgs(routingRequest.originUTM), + WALK, + personVehicle.vehicleTypeId + ), + createEmbodiedBeamLeg(routingRequest, beamVehicle.toStreetVehicle, linkIds, 50d), + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime + 250, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + true, + services.geo.utm2Wgs(routingRequest.destinationUTM), + WALK, + personVehicle.vehicleTypeId + ) + ) + ), + EmbodiedBeamTrip(legs = Vector(createEmbodiedBeamLeg(routingRequest, personVehicle, linkIds, 150d))) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequest.triggerId + ) + val mce = expectMsgType[ModeChoiceEvent] + assert(mce.currentTourMode === chosenTourMode) + expectMsgType[ActivityEndEvent] + + lastSender ! ScheduleKillTrigger(lastSender, routingRequest.triggerId) + + receiveWhile(500 millis) { + case _: SchedulerMessage => + case x: Event => println(x.toString) + case x: HasTriggerId => println(x.toString) + } + } + it("should only consider walk_based tours if given only a shared car") { + val eventsManager = new EventsManagerImpl() + eventsManager.addHandler( + new BasicEventHandler { + override def handleEvent(event: Event): Unit = { + event match { + case _: ModeChoiceEvent | _: TourModeChoiceEvent | _: ActivityEndEvent | _: ActivityStartEvent | + _: ReplanningEvent => + self ! event + case _ => + } + } + } + ) + val personalVehicleId = Id.createVehicleId("car-dummyAgent") +// val personalVehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) +// val beamVehicle = new BeamVehicle(personalVehicleId, new Powertrain(0.0), personalVehicleType) + + val household = householdsFactory.createHousehold(hoseHoldDummyId) + val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) + + val person: Person = + createTestPerson(Id.createPersonId("dummyAgent"), personalVehicleId, None, None, None, withRoute = false) + population.addPerson(person) + + household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) + + val scheduler = TestActorRef[BeamAgentScheduler]( + SchedulerProps( + beamConfig, + stopTick = 24 * 60 * 60, + maxWindow = 10, + new StuckFinder(beamConfig.beam.debug.stuckAgentDetection) + ) + ) + val parkingLocation = new Coord(167138.4, 1117.0) + val parkingManager = system.actorOf(Props(new AnotherTrivialParkingManager(parkingLocation))) + val mockSharedVehicleFleet = TestProbe() + + val householdActor = TestActorRef[HouseholdActor]( + Props( + new HouseholdActor( + services, + beamScenario, + _ => modeChoiceCalculator, + scheduler, + beamScenario.transportNetwork, + services.tollCalculator, + self, + self, + parkingManager, + self, + eventsManager, + population, + household, + Map(), + new Coord(0.0, 0.0), + sharedVehicleFleets = Vector(mockSharedVehicleFleet.ref), + Set(beamScenario.vehicleTypes(Id.create("sharedVehicle-sharedCar", classOf[BeamVehicleType]))), + new RouteHistory(beamConfig), + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder + ) + ) + ) + scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) + + scheduler ! StartSchedule(0) + + val inq = mockSharedVehicleFleet.expectMsgType[MobilityStatusInquiry] + + val vehicleType = beamScenario.vehicleTypes(Id.create("sharedVehicle-sharedCar", classOf[BeamVehicleType])) + val managerId = VehicleManager.createOrGetReservedFor("shared-fleet-1", VehicleManager.TypeEnum.Shared).managerId + // I give it a car to use. + val vehicle = new BeamVehicle( + Id.create("sharedVehicle-sharedCar", classOf[BeamVehicle]), + new Powertrain(0.0), + vehicleType, + vehicleManagerId = new AtomicReference(managerId) + ) + vehicle.setManager(Some(mockSharedVehicleFleet.ref)) + + (parkingManager ? ParkingInquiry.init( + SpaceTime(0.0, 0.0, 28800), + "wherever", + triggerId = 0 + )).collect { case ParkingInquiryResponse(stall, _, triggerId) => + vehicle.useParkingStall(stall) + MobilityStatusResponse(Vector(ActualVehicle(vehicle)), triggerId) + } pipeTo mockSharedVehicleFleet.lastSender + + val tmc = expectMsgType[TourModeChoiceEvent] + val modeUtilities = tmc.tourModeToUtilityString + .replace(" ", "") + .split("->") + .flatMap(_.split(";")) + .sliding(2, 2) + .map { x => x(0) -> x(1).toDouble } + .toMap + + val chosenTourMode = tmc.tourMode + assert(modeUtilities("CAR_BASED") === Double.NegativeInfinity) + assert(modeUtilities("WALK_BASED") > Double.NegativeInfinity) + assert(modeUtilities("BIKE_BASED") === Double.NegativeInfinity) + val routingRequest = expectMsgType[RoutingRequest] + val personVehicle = routingRequest.streetVehicles.find(_.mode == WALK).get + val linkIds = Array[Int](228, 206, 180, 178, 184, 102) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + false, + services.geo.utm2Wgs(routingRequest.originUTM), + WALK, + personVehicle.vehicleTypeId + ), + createEmbodiedBeamLeg(routingRequest, vehicle.toStreetVehicle, linkIds, 50d), + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime + 250, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + true, + services.geo.utm2Wgs(routingRequest.destinationUTM), + WALK, + personVehicle.vehicleTypeId + ) + ) + ), + EmbodiedBeamTrip(legs = Vector(createEmbodiedBeamLeg(routingRequest, personVehicle, linkIds, 150d))) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequest.triggerId + ) + val mce = expectMsgType[ModeChoiceEvent] + assert(mce.currentTourMode === "walk_based") + // Make sure that they consider using the shared car, even though they are on a walk_based tour (they can do this + // because they don't need to bring the car home, so they can take any walk_based mode for the rest of the tour) + assert(mce.availableAlternatives contains "CAR") + assert(mce.availableAlternatives contains "WALK") + expectMsgType[ActivityEndEvent] + + lastSender ! ScheduleKillTrigger(lastSender, routingRequest.triggerId) + + receiveWhile(500 millis) { + case _: SchedulerMessage => + case x: Event => println(x.toString) + case x: HasTriggerId => println(x.toString) + } + } + + it("should be able to handle a plan with nested tours") { + val eventsManager = new EventsManagerImpl() + eventsManager.addHandler( + new BasicEventHandler { + override def handleEvent(event: Event): Unit = { + event match { + case _: ModeChoiceEvent | _: TourModeChoiceEvent | _: ActivityEndEvent | _: ActivityStartEvent | + _: ReplanningEvent => + self ! event + case _ => + } + } + } + ) + val vehicleId = Id.createVehicleId("car-dummyAgent") + val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val beamVehicle = new BeamVehicle(vehicleId, new Powertrain(0.0), vehicleType) + + val household = householdsFactory.createHousehold(hoseHoldDummyId) + val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) + + val person: Person = + createTestPersonWithSubtour(Id.createPersonId("dummyAgent"), Some(CAR_BASED), None, None, None, None, None) + population.addPerson(person) + + household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) + + val scheduler = TestActorRef[BeamAgentScheduler]( + SchedulerProps( + beamConfig, + stopTick = 24 * 60 * 60, + maxWindow = 10, + new StuckFinder(beamConfig.beam.debug.stuckAgentDetection) + ) + ) + val parkingLocation = new Coord(167138.4, 1117.0) + val parkingManager = system.actorOf(Props(new AnotherTrivialParkingManager(parkingLocation))) + + val householdActor = TestActorRef[HouseholdActor]( + Props( + new HouseholdActor( + services, + beamScenario, + _ => modeChoiceCalculator, + scheduler, + beamScenario.transportNetwork, + services.tollCalculator, + self, + self, + parkingManager, + self, + eventsManager, + population, + household, + Map(beamVehicle.id -> beamVehicle), + new Coord(0.0, 0.0), + Vector(), + Set.empty, + new RouteHistory(beamConfig), + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder + ) + ) + ) + scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) + + scheduler ! StartSchedule(0) + + val routingRequest = expectMsgType[RoutingRequest] + assert(routingRequest.withTransit === false) + val personVehicle = routingRequest.streetVehicles.find(_.mode == WALK).get + val linkIds = Array[Int](228, 206, 180, 178, 184, 102) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + false, + services.geo.utm2Wgs(routingRequest.originUTM), + WALK, + personVehicle.vehicleTypeId + ), + createEmbodiedBeamLeg(routingRequest, beamVehicle.toStreetVehicle, linkIds, 50d), + EmbodiedBeamLeg.dummyLegAt( + routingRequest.departureTime + 250, + routingRequest.streetVehicles.find(_.mode == WALK).get.id, + true, + services.geo.utm2Wgs(routingRequest.destinationUTM), + WALK, + personVehicle.vehicleTypeId + ) + ) + ), + EmbodiedBeamTrip(legs = Vector(createEmbodiedBeamLeg(routingRequest, personVehicle, linkIds, 150d))) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequest.triggerId + ) + + val mce = expectMsgType[ModeChoiceEvent] + assert(mce.mode === "car") + assert(mce.currentTourMode === "car_based") + assert(mce.availableAlternatives === "CAR") + expectMsgType[ActivityEndEvent] + + val parkingRoutingRequest = expectMsgType[RoutingRequest] + assert(parkingRoutingRequest.destinationUTM == parkingLocation) + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = parkingRoutingRequest.departureTime, + mode = BeamMode.CAR, + duration = 50, + travelPath = BeamPath( + linkIds = Array(142, 60, 58, 62, 80), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = SpaceTime( + services.geo.utm2Wgs(parkingRoutingRequest.originUTM), + parkingRoutingRequest.departureTime + ), + endPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), parkingRoutingRequest.departureTime + 200), + distanceInM = 1000d + ) + ), + beamVehicleId = Id.createVehicleId("car-1"), + Id.create("TRANSIT-TYPE-DEFAULT", classOf[BeamVehicleType]), + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = parkingRoutingRequest.triggerId + ) + + val walkFromParkingRoutingRequest = expectMsgType[RoutingRequest] + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = walkFromParkingRoutingRequest.departureTime, + mode = BeamMode.WALK, + duration = 50, + travelPath = BeamPath( + linkIds = Array(80, 62, 58, 60, 142), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), walkFromParkingRoutingRequest.departureTime), + endPoint = SpaceTime( + services.geo.utm2Wgs(walkFromParkingRoutingRequest.destinationUTM), + walkFromParkingRoutingRequest.departureTime + 200 + ), + distanceInM = 1000d + ) + ), + beamVehicleId = walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.id, + walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.vehicleTypeId, + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = walkFromParkingRoutingRequest.triggerId + ) + + expectMsgType[ActivityStartEvent] + val tmc = expectMsgType[TourModeChoiceEvent] + val modeUtilities = tmc.tourModeToUtilityString + .replace(" ", "") + .split("->") + .flatMap(_.split(";")) + .sliding(2, 2) + .map { x => x(0) -> x(1).toDouble } + .toMap + + assert(modeUtilities("CAR_BASED") > Double.NegativeInfinity) + assert(modeUtilities("WALK_BASED") > Double.NegativeInfinity) + assert(modeUtilities("BIKE_BASED") === Double.NegativeInfinity) + assert(tmc.availablePersonalStreetVehiclesString.contains("beamVilleCar")) + lastSender ! ScheduleKillTrigger(lastSender, routingRequest.triggerId) + + receiveWhile(500 millis) { + case _: SchedulerMessage => + case x: Event => println(x.toString) + case x: HasTriggerId => println(x.toString) + } + } + it("should be able to their personal car again after completing a walk_based subtour") { + val eventsManager = new EventsManagerImpl() + eventsManager.addHandler( + new BasicEventHandler { + override def handleEvent(event: Event): Unit = { + event match { + case _: ModeChoiceEvent | _: TourModeChoiceEvent | _: ActivityEndEvent | _: ActivityStartEvent | + _: ReplanningEvent => + self ! event + case _ => + } + } + } + ) + val vehicleId = Id.createVehicleId("car-dummyAgent") + val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val beamVehicle = new BeamVehicle(vehicleId, new Powertrain(0.0), vehicleType) + + val household = householdsFactory.createHousehold(hoseHoldDummyId) + val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) + + val person: Person = + createTestPersonWithSubtour( + Id.createPersonId("dummyAgent"), + Some(CAR_BASED), + None, + None, + Some(WALK_BASED), + None, + None + ) + population.addPerson(person) + + household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(person.getId))) + + val scheduler = TestActorRef[BeamAgentScheduler]( + SchedulerProps( + beamConfig, + stopTick = 24 * 60 * 60, + maxWindow = 10, + new StuckFinder(beamConfig.beam.debug.stuckAgentDetection) + ) + ) + val parkingLocation = new Coord(167138.4, 1117.0) + val parkingManager = system.actorOf(Props(new AnotherTrivialParkingManager(parkingLocation))) + + val householdActor = TestActorRef[HouseholdActor]( + Props( + new HouseholdActor( + services, + beamScenario, + _ => modeChoiceCalculator, + scheduler, + beamScenario.transportNetwork, + services.tollCalculator, + self, + self, + parkingManager, + self, + eventsManager, + population, + household, + Map(beamVehicle.id -> beamVehicle), + new Coord(0.0, 0.0), + Vector(), + Set.empty, + new RouteHistory(beamConfig), + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder + ) + ) + ) + scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) + + scheduler ! StartSchedule(0) + + val routingRequestForWorkTrip = expectMsgType[RoutingRequest] + assert(routingRequestForWorkTrip.withTransit === false) + val personVehicle = routingRequestForWorkTrip.streetVehicles.find(_.mode == WALK).get + val linkIds = Array[Int](228, 206, 180, 178, 184, 102) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg.dummyLegAt( + routingRequestForWorkTrip.departureTime, + routingRequestForWorkTrip.streetVehicles.find(_.mode == WALK).get.id, + false, + services.geo.utm2Wgs(routingRequestForWorkTrip.originUTM), + WALK, + personVehicle.vehicleTypeId + ), + createEmbodiedBeamLeg(routingRequestForWorkTrip, beamVehicle.toStreetVehicle, linkIds, 50d), + EmbodiedBeamLeg.dummyLegAt( + routingRequestForWorkTrip.departureTime + 250, + routingRequestForWorkTrip.streetVehicles.find(_.mode == WALK).get.id, + true, + services.geo.utm2Wgs(routingRequestForWorkTrip.destinationUTM), + WALK, + personVehicle.vehicleTypeId + ) + ) + ), + EmbodiedBeamTrip(legs = + Vector(createEmbodiedBeamLeg(routingRequestForWorkTrip, personVehicle, linkIds, 150d)) + ) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequestForWorkTrip.triggerId + ) + + val modeChoiceForWorkTrip = expectMsgType[ModeChoiceEvent] + assert(modeChoiceForWorkTrip.mode === "car") + assert(modeChoiceForWorkTrip.currentTourMode === "car_based") + assert(modeChoiceForWorkTrip.availableAlternatives === "CAR") + expectMsgType[ActivityEndEvent] + + val parkingRoutingRequest = expectMsgType[RoutingRequest] + assert(parkingRoutingRequest.destinationUTM == parkingLocation) + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = parkingRoutingRequest.departureTime, + mode = BeamMode.CAR, + duration = 50, + travelPath = BeamPath( + linkIds = Array(142, 60, 58, 62, 80), + linkTravelTime = Array(50, 50, 50, 50, 50), + transitStops = None, + startPoint = SpaceTime( + services.geo.utm2Wgs(parkingRoutingRequest.originUTM), + parkingRoutingRequest.departureTime + ), + endPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), parkingRoutingRequest.departureTime + 200), + distanceInM = 1000d + ) + ), + beamVehicleId = Id.createVehicleId("car-1"), + Id.create("TRANSIT-TYPE-DEFAULT", classOf[BeamVehicleType]), + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = parkingRoutingRequest.triggerId + ) + + val walkFromParkingRoutingRequest = expectMsgType[RoutingRequest] + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = walkFromParkingRoutingRequest.departureTime, + mode = BeamMode.WALK, + duration = 50, + travelPath = BeamPath( + linkIds = Array(80, 101), + linkTravelTime = Array(50, 50), + transitStops = None, + startPoint = + SpaceTime(services.geo.utm2Wgs(parkingLocation), walkFromParkingRoutingRequest.departureTime), + endPoint = SpaceTime( + services.geo.utm2Wgs(walkFromParkingRoutingRequest.destinationUTM), + walkFromParkingRoutingRequest.departureTime + 50 + ), + distanceInM = 100d + ) + ), + beamVehicleId = walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.id, + walkFromParkingRoutingRequest.streetVehicles.find(_.mode == WALK).get.vehicleTypeId, + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = parkingRoutingRequest.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = walkFromParkingRoutingRequest.triggerId + ) + + expectMsgType[ActivityStartEvent] + val routingRequestForLunchTrip = expectMsgType[RoutingRequest] + + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = routingRequestForLunchTrip.departureTime, + mode = BeamMode.WALK, + duration = 50, + travelPath = BeamPath( + linkIds = Array(101, 100), + linkTravelTime = Array(50, 50), + transitStops = None, + startPoint = SpaceTime( + services.geo.utm2Wgs(routingRequestForLunchTrip.originUTM), + routingRequestForLunchTrip.departureTime + ), + endPoint = SpaceTime( + services.geo.utm2Wgs(routingRequestForLunchTrip.destinationUTM), + routingRequestForLunchTrip.departureTime + 50 + ), + distanceInM = 100d + ) + ), + beamVehicleId = routingRequestForLunchTrip.streetVehicles.find(_.mode == WALK).get.id, + routingRequestForLunchTrip.streetVehicles.find(_.mode == WALK).get.vehicleTypeId, + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = routingRequestForLunchTrip.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequestForLunchTrip.triggerId + ) + + val modeChoiceEventForLunchTrip = expectMsgType[ModeChoiceEvent] + assert(modeChoiceEventForLunchTrip.mode === "walk") + assert(modeChoiceEventForLunchTrip.currentTourMode === "walk_based") + assert(modeChoiceEventForLunchTrip.availableAlternatives === "WALK") + expectMsgType[ActivityEndEvent] + + expectMsgType[ActivityStartEvent] + + val routingRequestForReturnToWork = expectMsgType[RoutingRequest] + + lastSender ! RoutingResponse( + itineraries = Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = routingRequestForReturnToWork.departureTime, + mode = BeamMode.WALK, + duration = 50, + travelPath = BeamPath( + linkIds = Array(100, 101), + linkTravelTime = Array(50, 50), + transitStops = None, + startPoint = SpaceTime( + services.geo.utm2Wgs(routingRequestForReturnToWork.originUTM), + routingRequestForReturnToWork.departureTime + ), + endPoint = SpaceTime( + services.geo.utm2Wgs(routingRequestForReturnToWork.destinationUTM), + routingRequestForReturnToWork.departureTime + 50 + ), + distanceInM = 100d + ) + ), + beamVehicleId = routingRequestForReturnToWork.streetVehicles.find(_.mode == WALK).get.id, + routingRequestForReturnToWork.streetVehicles.find(_.mode == WALK).get.vehicleTypeId, + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ) + ) + ) + ), + requestId = routingRequestForLunchTrip.requestId, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequestForLunchTrip.triggerId + ) + expectMsgType[ModeChoiceEvent] + expectMsgType[ActivityEndEvent] + expectMsgType[ActivityStartEvent] + val routingRequestForReturnToHome = expectMsgType[RoutingRequest] + assert(routingRequestForWorkTrip.withTransit === false) + lastSender ! RoutingResponse( + Vector( + EmbodiedBeamTrip( + legs = Vector( + EmbodiedBeamLeg( + beamLeg = BeamLeg( + startTime = routingRequestForReturnToHome.departureTime, + mode = BeamMode.WALK, + duration = 50, + travelPath = BeamPath( + linkIds = Array(101, 80), + linkTravelTime = Array(50, 50), + transitStops = None, + startPoint = SpaceTime( + services.geo.utm2Wgs(routingRequestForReturnToHome.originUTM), + routingRequestForReturnToHome.departureTime + ), + endPoint = SpaceTime( + services.geo.utm2Wgs(parkingLocation), + routingRequestForReturnToHome.departureTime + 50 + ), + distanceInM = 100d + ) + ), + beamVehicleId = routingRequestForReturnToHome.streetVehicles.find(_.mode == WALK).get.id, + routingRequestForReturnToHome.streetVehicles.find(_.mode == WALK).get.vehicleTypeId, + asDriver = true, + cost = 0.0, + unbecomeDriverOnCompletion = true + ), + createEmbodiedBeamLeg(routingRequestForWorkTrip, beamVehicle.toStreetVehicle, linkIds.reverse, 50d), + EmbodiedBeamLeg.dummyLegAt( + routingRequestForWorkTrip.departureTime + 300, + routingRequestForWorkTrip.streetVehicles.find(_.mode == WALK).get.id, + true, + services.geo.utm2Wgs(routingRequestForWorkTrip.destinationUTM), + WALK, + personVehicle.vehicleTypeId + ) + ) + ) + ), + requestId = 1, + request = None, + isEmbodyWithCurrentTravelTime = false, + triggerId = routingRequestForWorkTrip.triggerId + ) + + val modeChoiceForReturnFromWorkTrip = expectMsgType[ModeChoiceEvent] + assert(modeChoiceForReturnFromWorkTrip.mode === "car") + assert(modeChoiceForReturnFromWorkTrip.currentTourMode === "car_based") + assert(modeChoiceForReturnFromWorkTrip.availableAlternatives === "CAR") + + lastSender ! ScheduleKillTrigger(lastSender, routingRequestForWorkTrip.triggerId) + + receiveWhile(500 millis) { + case _: SchedulerMessage => + case x: Event => println(x.toString) + case x: HasTriggerId => println(x.toString) + } + } + } + + private def createEmbodiedBeamLeg( + routingRequest: RoutingRequest, + personVehicle: VehicleProtocol.StreetVehicle, + linkIds: Array[Int], + tt: Double + ): EmbodiedBeamLeg = { + EmbodiedBeamLeg( + BeamLeg( + routingRequest.departureTime, + personVehicle.mode, + (tt * 5).toInt, + BeamPath( + linkIds, + linkIds.map(_ => tt), + None, + SpaceTime(services.geo.utm2Wgs(routingRequest.originUTM), routingRequest.departureTime), + SpaceTime(services.geo.utm2Wgs(routingRequest.originUTM), routingRequest.departureTime + (tt * 5).toInt), + 6000 + ) + ), + personVehicle.id, + personVehicle.vehicleTypeId, + asDriver = true, + 0.0, + unbecomeDriverOnCompletion = true + ) + } + + private def createTestPerson( + personId: Id[Person], + vehicleId: Id[Vehicle], + mode: Option[BeamMode], + tourMode: Option[BeamTourMode], + tourVehicle: Option[Id[BeamVehicle]] = None, + withRoute: Boolean = true + ) = { + val person = PopulationUtils.getFactory.createPerson(personId) + val attributesOfIndividual = AttributesOfIndividual( + HouseholdAttributes("1", 200, 300, 400, 500), + None, + true, + Vector(BeamMode.CAR, BeamMode.WALK, BeamMode.BIKE, BeamMode.WALK_TRANSIT), + Seq.empty, + valueOfTime = 10000000.0, + Some(42), + Some(1234) + ) + person.getCustomAttributes.put("beam-attributes", attributesOfIndividual) + mode.foreach(x => putDefaultBeamAttributes(person, Vector(x))) + val plan = PopulationUtils.getFactory.createPlan() + val homeActivity = PopulationUtils.createActivityFromLinkId("home", Id.createLinkId(1)) + homeActivity.setEndTime(28800) // 8:00:00 AM + homeActivity.setCoord(homeLocation) + plan.addActivity(homeActivity) + val leg = PopulationUtils.createLeg(mode.map(_.matsimMode).getOrElse("")) + leg.getAttributes.putAttribute("tour_id", 100) + + tourMode.map { mode => + leg.getAttributes.putAttribute("tour_mode", mode.value) + } + tourVehicle.map { veh => + leg.getAttributes.putAttribute("tour_vehicle", veh.toString) + } + + if (withRoute) { + val route = RouteUtils.createLinkNetworkRouteImpl( + Id.createLinkId(228), + Array(206, 180, 178, 184, 102).map(Id.createLinkId(_)), + Id.createLinkId(108) + ) + route.setVehicleId(vehicleId) + leg.setRoute(route) + } + plan.addLeg(leg) + val workActivity = PopulationUtils.createActivityFromLinkId("work", Id.createLinkId(2)) + workActivity.setEndTime(61200) //5:00:00 PM + workActivity.setCoord(workLocation) + plan.addActivity(workActivity) + val leg2 = PopulationUtils.createLeg(mode.map(_.matsimMode).getOrElse("")) + leg2.getAttributes.putAttribute("tour_id", 100) + leg2.getAttributes.putAttribute("tour_mode", tourMode.map(_.value).getOrElse("")) + if (withRoute) { + val route = RouteUtils.createLinkNetworkRouteImpl( + Id.createLinkId(108), + Array(206, 180, 178, 184, 102).reverse.map(Id.createLinkId(_)), + Id.createLinkId(228) + ) + route.setVehicleId(vehicleId) + leg2.setRoute(route) + } + plan.addLeg(leg2) + val homeActivity2 = PopulationUtils.createActivityFromLinkId("home", Id.createLinkId(1)) + homeActivity2.setCoord(homeLocation) + homeActivity2.setEndTime(65200) + plan.addActivity(homeActivity2) + person.addPlan(plan) + person + } + + private def createTestPersonWithSubtour( + personId: Id[Person], + primaryTourMode: Option[BeamTourMode] = None, + primaryTourTripMode: Option[BeamMode] = None, + primaryTourVehicle: Option[Id[BeamVehicle]] = None, + secondaryTourMode: Option[BeamTourMode] = None, + secondaryTourTripMode: Option[BeamMode] = None, + secondaryTourVehicle: Option[Id[BeamVehicle]] = None + ) = { + val person = PopulationUtils.getFactory.createPerson(personId) + val attributesOfIndividual = AttributesOfIndividual( + HouseholdAttributes("1", 200, 300, 400, 500), + None, + true, + Vector(BeamMode.CAR, BeamMode.WALK, BeamMode.BIKE, BeamMode.WALK_TRANSIT), + Seq.empty, + valueOfTime = 10000000.0, + Some(42), + Some(1234) + ) + person.getCustomAttributes.put("beam-attributes", attributesOfIndividual) + + val plan = PopulationUtils.getFactory.createPlan() + val homeActivity = PopulationUtils.createActivityFromLinkId("home", Id.createLinkId(1)) + homeActivity.setEndTime(28800) // 8:00:00 AM + homeActivity.setCoord(homeLocation) + plan.addActivity(homeActivity) + val leg = PopulationUtils.createLeg(primaryTourTripMode.map(_.matsimMode).getOrElse("")) + leg.getAttributes.putAttribute("tour_id", 100) + + primaryTourMode.map { mode => + leg.getAttributes.putAttribute("tour_mode", mode.value) + } + primaryTourVehicle.map { veh => + leg.getAttributes.putAttribute("tour_vehicle", veh.toString) + } + plan.addLeg(leg) + + val workActivity = PopulationUtils.createActivityFromLinkId("work", Id.createLinkId(2)) + workActivity.setEndTime(43200) //12:00:00 PM + workActivity.setCoord(workLocation) + plan.addActivity(workActivity) + + val leg2 = PopulationUtils.createLeg(secondaryTourTripMode.map(_.matsimMode).getOrElse("")) + leg2.getAttributes.putAttribute("tour_id", 101) + + secondaryTourMode.map { mode => + leg2.getAttributes.putAttribute("tour_mode", mode.value) + } + secondaryTourVehicle.map { veh => + leg2.getAttributes.putAttribute("tour_vehicle", veh.toString) + } + plan.addLeg(leg2) + + val otherActivity = PopulationUtils.createActivityFromLinkId("other", Id.createLinkId(3)) + otherActivity.setEndTime(48600) //1:30 PM (european style lunch) + otherActivity.setCoord(workLocation) + plan.addActivity(otherActivity) + + val leg3 = PopulationUtils.createLeg(primaryTourTripMode.map(_.matsimMode).getOrElse("")) + leg3.getAttributes.putAttribute("tour_id", 101) + + primaryTourMode.map { mode => + leg3.getAttributes.putAttribute("tour_mode", mode.value) + } + primaryTourVehicle.map { veh => + leg3.getAttributes.putAttribute("tour_vehicle", veh.toString) + } + plan.addLeg(leg3) + + val workActivity2 = PopulationUtils.createActivityFromLinkId("work", Id.createLinkId(2)) + workActivity2.setEndTime(61200) //5:00:00 PM + workActivity2.setCoord(workLocation) + plan.addActivity(workActivity2) + + val leg4 = PopulationUtils.createLeg(primaryTourTripMode.map(_.matsimMode).getOrElse("")) + leg4.getAttributes.putAttribute("tour_id", 100) + + primaryTourMode.map { mode => + leg4.getAttributes.putAttribute("tour_mode", mode.value) + } + primaryTourVehicle.map { veh => + leg4.getAttributes.putAttribute("tour_vehicle", veh.toString) + } + plan.addLeg(leg4) + + val homeActivity2 = PopulationUtils.createActivityFromLinkId("home", Id.createLinkId(1)) + homeActivity2.setCoord(homeLocation) + homeActivity2.setEndTime(65200) + plan.addActivity(homeActivity2) + person.addPlan(plan) + person + } + + override def afterAll(): Unit = { + super.afterAll() + } + + after { + import scala.concurrent.duration._ + import scala.language.postfixOps + //we need to prevent getting this CompletionNotice from the Scheduler in the next test + receiveWhile(1500 millis) { case _: CompletionNotice => + } + } + +} diff --git a/src/test/scala/beam/agentsim/agents/PersonWithVehicleSharingSpec.scala b/src/test/scala/beam/agentsim/agents/PersonWithVehicleSharingSpec.scala index 87f28834b88..5c90ebfc090 100644 --- a/src/test/scala/beam/agentsim/agents/PersonWithVehicleSharingSpec.scala +++ b/src/test/scala/beam/agentsim/agents/PersonWithVehicleSharingSpec.scala @@ -26,6 +26,7 @@ import beam.router.Modes.BeamMode.{CAR, WALK} import beam.router.RouteHistory import beam.router.model.{EmbodiedBeamLeg, _} import beam.router.skim.core.AbstractSkimmerEvent +import beam.sim.config.BeamConfigHolder import beam.sim.vehicles.VehiclesAdjustment import beam.utils.TestConfigUtils.testConfig import beam.utils.{SimRunnerForTest, StuckFinder, TestConfigUtils} @@ -137,9 +138,10 @@ class PersonWithVehicleSharingSpec Map(), new Coord(0.0, 0.0), sharedVehicleFleets = Vector(mockSharedVehicleFleet.ref), - Set(beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType]))), + Set(beamScenario.vehicleTypes(Id.create("sharedVehicle-sharedCar", classOf[BeamVehicleType]))), new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) ) @@ -151,7 +153,7 @@ class PersonWithVehicleSharingSpec // since I am the manager of a shared vehicle fleet. mockSharedVehicleFleet.expectMsgType[MobilityStatusInquiry] - val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val vehicleType = beamScenario.vehicleTypes(Id.create("sharedVehicle-sharedCar", classOf[BeamVehicleType])) val managerId = VehicleManager.createOrGetReservedFor("shared-fleet-1", VehicleManager.TypeEnum.Shared).managerId // I give it a car to use. val vehicle = new BeamVehicle( @@ -200,6 +202,8 @@ class PersonWithVehicleSharingSpec triggerId = embodyRequest.triggerId ) + events.expectMsgType[TourModeChoiceEvent] + events.expectMsgType[ModeChoiceEvent] events.expectMsgType[ActivityEndEvent] events.expectMsgType[PersonDepartureEvent] @@ -251,7 +255,7 @@ class PersonWithVehicleSharingSpec } } ) - val vehicleId = Id.createVehicleId("car-dummyAgent") + val vehicleId = Id.createVehicleId("sharedVehicle:car-dummyAgent") val household = householdsFactory.createHousehold(hoseHoldDummyId) val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) @@ -294,9 +298,10 @@ class PersonWithVehicleSharingSpec Map(), new Coord(0.0, 0.0), sharedVehicleFleets = Vector(mockSharedVehicleFleet.ref), - Set(beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType]))), + Set(beamScenario.vehicleTypes(Id.create("sharedVehicle-sharedCar", classOf[BeamVehicleType]))), new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) ) @@ -308,7 +313,7 @@ class PersonWithVehicleSharingSpec // since I am the manager of a shared vehicle fleet. mockSharedVehicleFleet.expectMsgType[MobilityStatusInquiry] - val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val vehicleType = beamScenario.vehicleTypes(Id.create("sharedVehicle-sharedCar", classOf[BeamVehicleType])) // I give it a car to use. val managerId = VehicleManager.createOrGetReservedFor("shared-fleet-1", VehicleManager.TypeEnum.Shared).managerId val vehicle = new BeamVehicle( @@ -383,6 +388,8 @@ class PersonWithVehicleSharingSpec triggerId = routingRequest.triggerId ) + events.expectMsgType[TourModeChoiceEvent] + events.expectMsgType[ModeChoiceEvent] events.expectMsgType[ActivityEndEvent] events.expectMsgType[PersonDepartureEvent] @@ -480,7 +487,7 @@ class PersonWithVehicleSharingSpec it("should replan when the car that was originally offered is taken") { val population = PopulationUtils.createPopulation(ConfigUtils.createConfig()) val mockSharedVehicleFleet = TestProbe() - val vehicleType = beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) + val vehicleType = beamScenario.vehicleTypes(Id.create("sharedVehicle-sharedCar", classOf[BeamVehicleType])) val car1 = new BeamVehicle( Id.createVehicleId("car-1"), new Powertrain(0.0), @@ -557,9 +564,10 @@ class PersonWithVehicleSharingSpec Map(), new Coord(0.0, 0.0), Vector(mockSharedVehicleFleet.ref), - Set(beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType]))), + Set(beamScenario.vehicleTypes(Id.create("sharedVehicle-sharedCar", classOf[BeamVehicleType]))), new RouteHistory(beamConfig), - VehiclesAdjustment.getVehicleAdjustment(beamScenario) + VehiclesAdjustment.getVehicleAdjustment(beamScenario), + configHolder ) ) scheduler ! ScheduleTrigger(InitializeTrigger(0), householdActor) diff --git a/src/test/scala/beam/agentsim/agents/TeleportationSpec.scala b/src/test/scala/beam/agentsim/agents/TeleportationSpec.scala index 115c6366d5d..75943340dab 100644 --- a/src/test/scala/beam/agentsim/agents/TeleportationSpec.scala +++ b/src/test/scala/beam/agentsim/agents/TeleportationSpec.scala @@ -1,7 +1,7 @@ package beam.agentsim.agents import akka.actor.ActorSystem -import beam.agentsim.events.{PathTraversalEvent, TeleportationEvent} +import beam.agentsim.events.{ModeChoiceEvent, PathTraversalEvent, TeleportationEvent} import beam.router.Modes.BeamMode import beam.sim.config.{BeamConfig, MatSimBeamConfigBuilder} import beam.sim.{BeamHelper, BeamServices} @@ -51,20 +51,28 @@ class TeleportationSpec extends AnyFunSpecLike with Matchers with BeamHelper wit val carHov3passengers = mutable.Set.empty[Int] val carHov2passengers = mutable.Set.empty[Int] val activitiesOfPerson2 = ListBuffer[(String, Double, String)]() + val modeChoiceEvents = ListBuffer[(String, Double, String, String, String)]() runWithConfig( "test/input/beamville/beam-urbansimv2.conf", { case _: TeleportationEvent => teleportationEvents = teleportationEvents + 1 - case e: PathTraversalEvent if e.currentTourMode.contains("car_hov3") && e.mode == BeamMode.CAR => + case e: PathTraversalEvent if e.currentTripMode.contains("car_hov3") && e.mode == BeamMode.CAR => carHov3passengers.add(e.numberOfPassengers) - case e: PathTraversalEvent if e.currentTourMode.contains("car_hov2") && e.mode == BeamMode.CAR => + case e: PathTraversalEvent if e.currentTripMode.contains("car_hov2") && e.mode == BeamMode.CAR => carHov2passengers.add(e.numberOfPassengers) case e: ActivityStartEvent if e.getPersonId.toString == "2" => activitiesOfPerson2.append((e.getLinkId.toString, e.getTime, e.getActType)) + case e: ModeChoiceEvent => + modeChoiceEvents.append( + (e.getPersonId.toString, e.getTime, e.getMode, e.availableAlternatives, e.currentTourMode) + ) case _ => } ) + it("should not have walk mode choices") { + modeChoiceEvents.count(_._3.equalsIgnoreCase("walk")) shouldBe 0 + } it("should have teleportation events") { teleportationEvents shouldBe 12 @@ -80,7 +88,9 @@ class TeleportationSpec extends AnyFunSpecLike with Matchers with BeamHelper wit // links activitiesList.map(_._1) shouldBe List("300", "142", "300", "142", "300", "142") // times - activitiesList.map(_._2) shouldBe List(21886.0, 26425.0, 32693.0, 37226.0, 39886.0, 44279.0) + activitiesList.map(_._2).zip(List(21886.0, 26425.0, 32693.0, 37226.0, 39886.0, 44279.0)).foreach { + case (a, b) => a shouldBe (b +- 600) + } // type activitiesList.map(_._3) shouldBe List("Other", "Home", "Other", "Home", "Other", "Home") } diff --git a/src/test/scala/beam/agentsim/agents/vehicles/LinkStateOfChargeSpec.scala b/src/test/scala/beam/agentsim/agents/vehicles/LinkStateOfChargeSpec.scala index f709e8cf6fc..721adbd1da8 100644 --- a/src/test/scala/beam/agentsim/agents/vehicles/LinkStateOfChargeSpec.scala +++ b/src/test/scala/beam/agentsim/agents/vehicles/LinkStateOfChargeSpec.scala @@ -48,6 +48,8 @@ class LinkStateOfChargeSpec extends AnyWordSpecLike with Matchers with BeamHelpe beam.agentsim.agents.rideHail.linkFleetStateAcrossIterations = true beam.agentsim.agents.vehicles.linkSocAcrossIterations = true beam.physsim.skipPhysSim = true + beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_intercept = -1.0 + beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_pooled_intercept = -1.0 """) .withFallback(testConfig("test/input/beamville/beam.conf")) .resolve() diff --git a/src/test/scala/beam/agentsim/planning/BeamPlanSpec.scala b/src/test/scala/beam/agentsim/planning/BeamPlanSpec.scala index f3b68468112..896dcd8a0cf 100755 --- a/src/test/scala/beam/agentsim/planning/BeamPlanSpec.scala +++ b/src/test/scala/beam/agentsim/planning/BeamPlanSpec.scala @@ -1,6 +1,10 @@ package beam.agentsim.planning -import beam.agentsim.agents.planning.BeamPlan +import beam.agentsim.agents.planning.{BeamPlan, Tour} +import beam.agentsim.agents.planning.Strategy.{TourModeChoiceStrategy, TripModeChoiceStrategy} +import beam.router.Modes.BeamMode +import beam.router.Modes.BeamMode.CAR +import beam.router.TourModes.BeamTourMode import beam.sim.BeamHelper import org.matsim.api.core.v01.Coord import org.matsim.api.core.v01.population.{Activity, Plan} @@ -24,18 +28,20 @@ class BeamPlanSpec extends AnyWordSpecLike with Matchers with BeamHelper { PopulationUtils.createAndAddActivityFromCoord(matsimPlanOfActivities, "Home", new Coord(0.0, 0.0)) val matsimPlan: Plan = PopulationUtils.createPlan(null) PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Home", new Coord(0.0, 0.0)) - PopulationUtils.createAndAddLeg(matsimPlan, "car") + addLegToPlan(matsimPlan, BeamMode.CAR) PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Work", new Coord(0.0, 0.0)) - PopulationUtils.createAndAddLeg(matsimPlan, "car") + addLegToPlan(matsimPlan, BeamMode.CAR) PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Shop", new Coord(0.0, 0.0)) - PopulationUtils.createAndAddLeg(matsimPlan, "car") + addLegToPlan(matsimPlan, BeamMode.CAR) PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Home", new Coord(0.0, 0.0)) - PopulationUtils.createAndAddLeg(matsimPlan, "car") + addLegToPlan(matsimPlan, BeamMode.CAR) PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Eat", new Coord(0.0, 0.0)) - PopulationUtils.createAndAddLeg(matsimPlan, "car") + addLegToPlan(matsimPlan, BeamMode.CAR) PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Home", new Coord(0.0, 0.0)) - "should contain the same activities and legs as the MATSimn plan used in creation" in { + val strat = TripModeChoiceStrategy(Some(CAR)) + + "should contain the same activities and legs as the MATSim plan used in creation" in { val beamPlan = BeamPlan(matsimPlan) beamPlan.getPlanElements.asScala .zip(matsimPlan.getPlanElements.asScala) @@ -44,7 +50,57 @@ class BeamPlanSpec extends AnyWordSpecLike with Matchers with BeamHelper { .zip(beamPlan.getPlanElements.asScala) .forall(both => both._1.equals(both._2)) should be(true) } - + "should attach a strategy to an activity" in { + val beamPlan = BeamPlan(matsimPlan) + val act = beamPlan.activities.head + beamPlan.putStrategy(act, strat) + beamPlan.getStrategy[TripModeChoiceStrategy](act) should be(strat) + } + "should attach a strategy to a leg" in { + val beamPlan = BeamPlan(matsimPlan) + val leg = beamPlan.legs.head + beamPlan.putStrategy(leg, strat) + beamPlan.getStrategy[TripModeChoiceStrategy](leg) should be(strat) + } + "should attach a strategy to a trip" in { + val beamPlan = BeamPlan(matsimPlan) + val trip = beamPlan.trips.head + beamPlan.putStrategy(trip, strat) + beamPlan.getStrategy[TripModeChoiceStrategy](trip) should be(strat) + } + "should attach a strategy to a tour" in { + val beamPlan = BeamPlan(matsimPlan) + val tour = beamPlan.tours.head + beamPlan.putStrategy(tour, strat) + beamPlan.getStrategy[TripModeChoiceStrategy](tour) should be(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[TripModeChoiceStrategy](trip.activity) should be(strat) + trip.leg match { + case Some(leg) => + beamPlan.getStrategy[TripModeChoiceStrategy](leg) should be(strat) + case None => + } + } + "should not attach a strategy to tour's trips, activities, and legs" in { + val beamPlan = BeamPlan(matsimPlan) + val tour = beamPlan.tours(1) + val strategy = TripModeChoiceStrategy(None) + beamPlan.putStrategy(tour, strategy) + beamPlan.getStrategy[TripModeChoiceStrategy](tour) should be(strategy) + tour.trips.foreach { trip => + beamPlan.getStrategy[TripModeChoiceStrategy](trip) should be(strat) + beamPlan.getStrategy[TripModeChoiceStrategy](trip.activity) should be(strat) + trip.leg match { + case Some(leg) => + beamPlan.getStrategy[TripModeChoiceStrategy](leg) should be(strat) + case None => + } + } + } "should return a trip or tour containing a leg" in { val beamPlan = BeamPlan(matsimPlan) val tour = beamPlan.tours(2) @@ -74,4 +130,110 @@ class BeamPlanSpec extends AnyWordSpecLike with Matchers with BeamHelper { newPlan.getPlanElements.get(3) should be(newLeg) } } + "A BeamPlan with tour modes" must { + + val matsimPlan: Plan = PopulationUtils.createPlan(null) + PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Home", new Coord(0.0, 0.0)) + addLegToPlan(matsimPlan, BeamMode.CAR, Some(101), Some(BeamTourMode.CAR_BASED), Some("car-1")) + PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Work", new Coord(0.0, 0.0)) + addLegToPlan(matsimPlan, BeamMode.WALK, Some(103), Some(BeamTourMode.WALK_BASED), Some("car-1")) + PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Lunch", new Coord(0.0, 0.0)) + addLegToPlan(matsimPlan, BeamMode.WALK, Some(103), Some(BeamTourMode.WALK_BASED), Some("car-1")) + PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Work", new Coord(0.0, 0.0)) + addLegToPlan(matsimPlan, BeamMode.CAR, Some(101), Some(BeamTourMode.CAR_BASED), Some("car-1")) + PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Home", new Coord(0.0, 0.0)) + addLegToPlan(matsimPlan, BeamMode.WALK_TRANSIT, Some(102), Some(BeamTourMode.WALK_BASED)) + PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Shop", new Coord(0.0, 0.0)) + addLegToPlan(matsimPlan, BeamMode.WALK_TRANSIT, Some(102), Some(BeamTourMode.WALK_BASED)) + PopulationUtils.createAndAddActivityFromCoord(matsimPlan, "Home", new Coord(0.0, 0.0)) + + val strat = TripModeChoiceStrategy(Some(CAR)) + + "should contain the same activities and legs as the MATSimn plan used in creation" in { + val beamPlan = BeamPlan(matsimPlan) + beamPlan.getPlanElements.asScala + .zip(matsimPlan.getPlanElements.asScala) + .forall(both => both._1.equals(both._2)) should be(true) + matsimPlan.getPlanElements.asScala + .zip(beamPlan.getPlanElements.asScala) + .forall(both => both._1.equals(both._2)) should be(true) + } + "should define the correct tours" in { + val beamPlan = BeamPlan(matsimPlan) + val tours = beamPlan.tours + tours.length should be(4) + tours.map(_.tourId).intersect(Vector(101, 102, 103)).length should be(3) + + val mandatoryTour = beamPlan.getTourContaining(1) + val subTour = beamPlan.getTourContaining(2) + val mandatoryTourAgain = beamPlan.getTourContaining(4) + mandatoryTour should be(mandatoryTourAgain) + val firstStrategy = beamPlan.getStrategy[TourModeChoiceStrategy](mandatoryTour) + val secondStrategy = beamPlan.getStrategy[TourModeChoiceStrategy](subTour) + firstStrategy.tourMode.get should be(BeamTourMode.CAR_BASED) + secondStrategy.tourMode.get should be(BeamTourMode.WALK_BASED) + } + "should attach a strategy to a tour" in { + val beamPlan = BeamPlan(matsimPlan) + val tour = beamPlan.tours.head + beamPlan.putStrategy(tour, strat) + beamPlan.getStrategy[TripModeChoiceStrategy](tour) should be(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[TripModeChoiceStrategy](trip.activity) should be(strat) + trip.leg match { + case Some(leg) => + beamPlan.getStrategy[TripModeChoiceStrategy](leg) should be(strat) + case None => + } + } + "should not attach a strategy to tour's trips, activities, and legs" in { + val beamPlan = BeamPlan(matsimPlan) + val tour = beamPlan.tours(1) + val strategy = TripModeChoiceStrategy(None) + beamPlan.putStrategy(tour, strategy) + beamPlan.getStrategy[TripModeChoiceStrategy](tour) should be(strategy) + tour.trips.foreach { trip => + beamPlan.getStrategy[TripModeChoiceStrategy](trip) should be(strat) + beamPlan.getStrategy[TripModeChoiceStrategy](trip.activity) should be(strat) + trip.leg match { + case Some(leg) => + beamPlan.getStrategy[TripModeChoiceStrategy](leg) should be(strat) + case None => + } + } + } + "should return a trip or tour containing a leg" in { + val beamPlan = BeamPlan(matsimPlan) + val tour = beamPlan.tours(2) + val trip = tour.trips.head + beamPlan.getTripContaining(trip.activity) should be(trip) + beamPlan.getTripContaining(trip.leg.get) should be(trip) + beamPlan.getTourContaining(trip.activity) should be(tour) + } + } + + private def addLegToPlan( + matsimPlan: Plan, + mode: BeamMode, + maybeTourId: Option[Int] = None, + maybeTourMode: Option[BeamTourMode] = None, + maybeTourVehicle: Option[String] = None + ): Unit = { + val leg = PopulationUtils.createLeg(mode.matsimMode) + + maybeTourId.map { id => + leg.getAttributes.putAttribute("tour_id", id) + } + maybeTourMode.map { mode => + leg.getAttributes.putAttribute("tour_mode", mode.value) + } + maybeTourVehicle.map { veh => + leg.getAttributes.putAttribute("tour_vehicle", veh) + } + matsimPlan.addLeg(leg) + } } diff --git a/src/test/scala/beam/analysis/cartraveltime/StudyAreaTripFilterTest.scala b/src/test/scala/beam/analysis/cartraveltime/StudyAreaTripFilterTest.scala index e9a90e5f56e..660ffb575e8 100644 --- a/src/test/scala/beam/analysis/cartraveltime/StudyAreaTripFilterTest.scala +++ b/src/test/scala/beam/analysis/cartraveltime/StudyAreaTripFilterTest.scala @@ -52,7 +52,7 @@ class StudyAreaTripFilterTest extends AnyFunSuite with Matchers { vehicleType = vehicleType, numPass = 1, beamLeg = beamLeg, - currentTourMode = None, + currentTripMode = None, primaryFuelConsumed = 1.0, secondaryFuelConsumed = 0.0, endLegPrimaryFuelLevel = 1.0, diff --git a/src/test/scala/beam/integration/AgentsimWithMaximallyBadRouterSpec.scala b/src/test/scala/beam/integration/AgentsimWithMaximallyBadRouterSpec.scala index bdf3a6dc3e2..2c5ae38736a 100755 --- a/src/test/scala/beam/integration/AgentsimWithMaximallyBadRouterSpec.scala +++ b/src/test/scala/beam/integration/AgentsimWithMaximallyBadRouterSpec.scala @@ -10,6 +10,7 @@ import beam.replanning.ModeIterationPlanCleaner import beam.router.Modes.BeamMode import beam.router.RouteHistory import beam.sim.common.GeoUtilsImpl +import beam.sim.config.BeamConfigHolder import beam.sim.{BeamHelper, BeamMobsim, RideHailFleetInitializerProvider} import beam.utils.SimRunnerForTest import beam.utils.TestConfigUtils.testConfig @@ -50,6 +51,8 @@ class AgentsimWithMaximallyBadRouterSpec scenario.getPopulation.getPersons.values .forEach(p => PersonTestUtil.putDefaultBeamAttributes(p, BeamMode.allModes)) + injector = buildInjector(config, beamConfig, scenario, beamScenario) + val mobsim = new BeamMobsim( services, beamScenario, @@ -64,7 +67,8 @@ class AgentsimWithMaximallyBadRouterSpec new GeoUtilsImpl(services.beamConfig), new ModeIterationPlanCleaner(beamConfig, scenario), services.networkHelper, - new RideHailFleetInitializerProvider(services, beamScenario, scenario) + new RideHailFleetInitializerProvider(services, beamScenario, scenario), + injector.getInstance[BeamConfigHolder](classOf[BeamConfigHolder]) ) mobsim.run() } diff --git a/src/test/scala/beam/integration/BikeTransitModeSpec.scala b/src/test/scala/beam/integration/BikeTransitModeSpec.scala index cc8d824cc23..c40ea668b7a 100644 --- a/src/test/scala/beam/integration/BikeTransitModeSpec.scala +++ b/src/test/scala/beam/integration/BikeTransitModeSpec.scala @@ -10,6 +10,7 @@ import beam.router.Modes.BeamMode import beam.router.RouteHistory import beam.sflight.RouterForTest import beam.sim.common.GeoUtilsImpl +import beam.sim.config.BeamConfigHolder import beam.sim.{BeamHelper, BeamMobsim, RideHailFleetInitializerProvider} import beam.utils.SimRunnerForTest import beam.utils.TestConfigUtils.testConfig @@ -89,7 +90,8 @@ class BikeTransitModeSpec new GeoUtilsImpl(services.beamConfig), new ModeIterationPlanCleaner(beamConfig, scenario), services.networkHelper, - new RideHailFleetInitializerProvider(services, beamScenario, scenario) + new RideHailFleetInitializerProvider(services, beamScenario, scenario), + configHolder ) mobsim.run() @@ -145,7 +147,8 @@ class BikeTransitModeSpec new GeoUtilsImpl(services.beamConfig), new ModeIterationPlanCleaner(beamConfig, scenario), services.networkHelper, - new RideHailFleetInitializerProvider(services, beamScenario, scenario) + new RideHailFleetInitializerProvider(services, beamScenario, scenario), + configHolder ) mobsim.run() diff --git a/src/test/scala/beam/integration/SingleModeSpec.scala b/src/test/scala/beam/integration/SingleModeSpec.scala index f29744f7c47..ac9fdbca3fd 100755 --- a/src/test/scala/beam/integration/SingleModeSpec.scala +++ b/src/test/scala/beam/integration/SingleModeSpec.scala @@ -10,8 +10,9 @@ import beam.router.Modes.BeamMode import beam.router.RouteHistory import beam.sflight.RouterForTest import beam.sim.common.GeoUtilsImpl +import beam.sim.config.BeamConfigHolder 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} @@ -81,7 +82,8 @@ class SingleModeSpec new GeoUtilsImpl(services.beamConfig), new ModeIterationPlanCleaner(beamConfig, scenario), services.networkHelper, - new RideHailFleetInitializerProvider(services, beamScenario, scenario) + new RideHailFleetInitializerProvider(services, beamScenario, scenario), + configHolder ) mobsim.run() @@ -128,7 +130,8 @@ class SingleModeSpec new GeoUtilsImpl(services.beamConfig), new ModeIterationPlanCleaner(beamConfig, scenario), services.networkHelper, - new RideHailFleetInitializerProvider(services, beamScenario, scenario) + new RideHailFleetInitializerProvider(services, beamScenario, scenario), + configHolder ) mobsim.run() @@ -154,8 +157,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) @@ -195,7 +200,8 @@ class SingleModeSpec new GeoUtilsImpl(services.beamConfig), new ModeIterationPlanCleaner(beamConfig, scenario), services.networkHelper, - new RideHailFleetInitializerProvider(services, beamScenario, scenario) + new RideHailFleetInitializerProvider(services, beamScenario, scenario), + configHolder ) mobsim.run() @@ -203,20 +209,25 @@ class SingleModeSpec val personDepartureEvents = events.collect { case event: PersonDepartureEvent => event } personDepartureEvents should not be empty val regularPersonEvents = filterOutProfessionalDriversAndCavs(personDepartureEvents) - val (driveTransit, others) = regularPersonEvents.map(_.getLegMode).partition(_ == "drive_transit") + val eventsByMode = regularPersonEvents.groupBy(_.getLegMode) //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") { + eventsByMode("walk_transit").size should be < 2 * eventsByMode("drive_transit").size + } + // TODO: why did the number of drive transit trips decrease after implementing tour mode choice? Were the old + // drive transit trips legit? - val eventsByPerson = events.groupBy(_.getAttributes.get("person")) +// val eventsByPerson = regularPersonEvents.groupBy(_.getAttributes.get("person")) + +// eventsByPerson.map { +// _._2.span { +// case event: ActivityEndEvent if event.getActType == "Home" => +// true +// case _ => +// false +// } +// } - eventsByPerson.map { - _._2.span { - case event: ActivityEndEvent if event.getActType == "Home" => - true - case _ => - false - } - } // TODO: Test that what can be printed with the line below makes sense (chains of modes) // filteredEventsByPerson.map(_._2.mkString("--\n","\n","--\n")).foreach(print(_)) } @@ -261,7 +272,8 @@ class SingleModeSpec new GeoUtilsImpl(services.beamConfig), new ModeIterationPlanCleaner(beamConfig, scenario), services.networkHelper, - new RideHailFleetInitializerProvider(services, beamScenario, scenario) + new RideHailFleetInitializerProvider(services, beamScenario, scenario), + configHolder ) mobsim.run() @@ -269,8 +281,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) + } } } diff --git a/src/test/scala/beam/sim/BeamWarmStartRunSpec.scala b/src/test/scala/beam/sim/BeamWarmStartRunSpec.scala index 6e4071c3bbd..119030ab15a 100644 --- a/src/test/scala/beam/sim/BeamWarmStartRunSpec.scala +++ b/src/test/scala/beam/sim/BeamWarmStartRunSpec.scala @@ -75,7 +75,6 @@ class BeamWarmStartRunSpec averageCarSpeedIt0 / averageCarSpeedIt1 should equal(1.0 +- 0.50) val outputFileIdentifiers = Array( - "passengerPerTripBike.csv", "passengerPerTripBus.csv", "passengerPerTripCar.csv", "passengerPerTripRideHail.csv", diff --git a/src/test/scala/beam/utils/SimRunnerForTest.scala b/src/test/scala/beam/utils/SimRunnerForTest.scala index 0d7e68820eb..f746e0e80f6 100644 --- a/src/test/scala/beam/utils/SimRunnerForTest.scala +++ b/src/test/scala/beam/utils/SimRunnerForTest.scala @@ -1,9 +1,16 @@ package beam.utils +import java.io.File import akka.actor.ActorSystem +import beam.agentsim.agents.choice.logit.TourModeChoiceModel import beam.agentsim.agents.modalbehaviors.ModeChoiceCalculator import beam.agentsim.events.eventbuilder.{ComplexEventBuilder, EventBuilderActor} +import beam.api.{BeamCustomizationAPI, DefaultAPIImplementation} +import beam.router.Modes.BeamMode import beam.sim.config.{BeamConfig, BeamConfigHolder, MatSimBeamConfigBuilder} +import beam.sim.population.{AttributesOfIndividual, HouseholdAttributes} +import beam.sim.{BeamHelper, BeamScenario, BeamServices, BeamServicesImpl, RunBeam} +import com.conveyal.r5.api.util.{LegMode, TransitModes} import beam.sim._ import com.google.inject.Injector import org.matsim.core.api.experimental.events.EventsManager @@ -26,6 +33,18 @@ trait SimRunnerForTest extends BeamHelper with BeforeAndAfterAll with BeforeAndA // Next things are pretty cheap in initialization, so let it be non-lazy val beamConfig: BeamConfig = BeamConfig(config) + + val attributesOfIndividual: AttributesOfIndividual = AttributesOfIndividual( + householdAttributes = HouseholdAttributes("", 0.0, 0, 0, 0), + Option(""), + false, + Seq(BeamMode.CAR), + Seq.empty, + valueOfTime = 0.0, + age = Option(0), + income = Option(0) + ) + val tourModeChoiceModel: TourModeChoiceModel = TourModeChoiceModel(beamConfig) val matsimConfig: Config = new MatSimBeamConfigBuilder(config).buildMatSimConf() matsimConfig.controler.setOutputDirectory(outputDirPath) matsimConfig.controler.setOverwriteFileSetting(OverwriteFileSetting.overwriteExistingFiles) @@ -35,6 +54,7 @@ trait SimRunnerForTest extends BeamHelper with BeforeAndAfterAll with BeforeAndA var injector: Injector = _ var services: BeamServices = _ var eventsManager: EventsManager = _ + var configHolder: BeamConfigHolder = _ override protected def beforeEach(): Unit = { services.eventBuilderActor = system.actorOf( @@ -52,10 +72,11 @@ trait SimRunnerForTest extends BeamHelper with BeforeAndAfterAll with BeforeAndA injector = buildInjector(config, beamConfig, scenario, beamScenario, Some(abstractModule)) services = new BeamServicesImpl(injector) eventsManager = new EventsManagerImpl + configHolder = injector.getInstance[BeamConfigHolder](classOf[BeamConfigHolder]) services.modeChoiceCalculatorFactory = ModeChoiceCalculator( services.beamConfig.beam.agentsim.agents.modalBehaviors.modeChoiceClass, services, - injector.getInstance[BeamConfigHolder](classOf[BeamConfigHolder]), + configHolder, eventsManager ) } diff --git a/test/input/beamville/beam.conf b/test/input/beamville/beam.conf index 43e5fa8cb80..90d648e9b69 100755 --- a/test/input/beamville/beam.conf +++ b/test/input/beamville/beam.conf @@ -21,12 +21,12 @@ beam.agentsim.endTime = "30:00:00" beam.agentsim.agents.modalBehaviors.modeChoiceClass = "ModeChoiceMultinomialLogit" beam.agentsim.agents.modalBehaviors.defaultValueOfTime = 8.0 beam.agentsim.agents.modalBehaviors.multinomialLogit.params.transfer = -1.4 -beam.agentsim.agents.modalBehaviors.multinomialLogit.params.car_intercept = 2.0 +beam.agentsim.agents.modalBehaviors.multinomialLogit.params.car_intercept = 6.0 beam.agentsim.agents.modalBehaviors.multinomialLogit.params.walk_transit_intercept = 0.0 beam.agentsim.agents.modalBehaviors.multinomialLogit.params.drive_transit_intercept = 2.0 beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_transit_intercept = 0.0 -beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_intercept = 10.0 -beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_pooled_intercept = 10.0 +beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_intercept = 0.0 +beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_pooled_intercept = 0.0 beam.agentsim.agents.modalBehaviors.multinomialLogit.params.walk_intercept = 0.0 beam.agentsim.agents.modalBehaviors.multinomialLogit.params.bike_intercept = 2.0 beam.agentsim.agents.modalBehaviors.overrideAutomationLevel = 5 @@ -212,7 +212,7 @@ beam.physsim.linkStatsWriteInterval = 0 beam.outputs.events.fileOutputFormats = "csv,xml" # valid options: xml(.gz) , csv(.gz), none - DEFAULT: csv.gz # Events Writing Logging Levels: -beam.outputs.events.eventsToWrite = "PersonDepartureEvent,PersonArrivalEvent,ActivityEndEvent,ActivityStartEvent,PersonEntersVehicleEvent,PersonLeavesVehicleEvent,ModeChoiceEvent,PathTraversalEvent,ReserveRideHailEvent,ReplanningEvent,RefuelSessionEvent,TeleportationEvent,ChargingPlugInEvent,ChargingPlugOutEvent,ParkingEvent,LeavingParkingEvent" +beam.outputs.events.eventsToWrite = "TourModeChoiceEvent,PersonDepartureEvent,PersonArrivalEvent,ActivityEndEvent,ActivityStartEvent,PersonEntersVehicleEvent,PersonLeavesVehicleEvent,ModeChoiceEvent,PathTraversalEvent,ReserveRideHailEvent,ReplanningEvent,RefuelSessionEvent,TeleportationEvent,ChargingPlugInEvent,ChargingPlugOutEvent,ParkingEvent,LeavingParkingEvent" beam.outputs.stats.binSize = 3600 ################################################################## # Debugging @@ -225,7 +225,7 @@ beam.debug.stuckAgentDetection { checkIntervalMs = 200 checkMaxNumberOfMessagesEnabled = true defaultTimeoutMs = 60000 - enabled = true + enabled = false overallSimulationTimeoutMs = 100000 thresholds = [ { diff --git a/test/input/sf-light/urbansim-1k.conf b/test/input/sf-light/urbansim-1k.conf index 84c7ae64a35..c894fa46657 100755 --- a/test/input/sf-light/urbansim-1k.conf +++ b/test/input/sf-light/urbansim-1k.conf @@ -187,7 +187,7 @@ beam.outputs.events.fileOutputFormats = "csv.gz" # valid options: xml(.gz) , csv # Events Writing Logging Levels: # Any event types not explicitly listed in overrideWritingLevels take on defaultWritingLevel -beam.outputs.events.eventsToWrite = "ActivityEndEvent,ActivityStartEvent,PersonEntersVehicleEvent,PersonLeavesVehicleEvent,ModeChoiceEvent,PathTraversalEvent,ReserveRideHailEvent,ReplanningEvent,RefuelSessionEvent,ChargingPlugInEvent,ChargingPlugOutEvent,ParkingEvent,LeavingParkingEvent" +beam.outputs.events.eventsToWrite = "TourModeChoiceEvent,ActivityEndEvent,ActivityStartEvent,PersonEntersVehicleEvent,PersonLeavesVehicleEvent,ModeChoiceEvent,PathTraversalEvent,ReserveRideHailEvent,ReplanningEvent,RefuelSessionEvent,ChargingPlugInEvent,ChargingPlugOutEvent,ParkingEvent,LeavingParkingEvent" beam.outputs.stats.binSize = 3600 ##################################################################