Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Current trip mode basing on current tour mode #3476

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
95 changes: 62 additions & 33 deletions src/main/scala/beam/agentsim/agents/PersonAgent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ object PersonAgent {
restOfCurrentTrip: List[EmbodiedBeamLeg] = List(),
currentVehicle: VehicleStack = Vector(),
currentTourMode: Option[BeamMode] = None,
currentTripMode: Option[BeamMode] = None,
currentTourPersonalVehicle: Option[Id[BeamVehicle]] = None,
passengerSchedule: PassengerSchedule = PassengerSchedule(),
currentLegPassengerScheduleIndex: Int = 0,
Expand Down Expand Up @@ -285,6 +286,8 @@ object PersonAgent {
case basePersonData: BasePersonData => Some(basePersonData)
case _ => None
}

def atHome(activity: Activity): Boolean = activity.getType.equalsIgnoreCase("home")
}

class PersonAgent(
Expand Down Expand Up @@ -406,7 +409,7 @@ class PersonAgent(
// which is used in place of our real remaining tour distance of 0.0
// which should help encourage residential end-of-day charging
val tomorrowFirstLegDistance =
if (nextAct.getType.toLowerCase == "home") {
if (atHome(nextAct)) {
findFirstCarLegOfTrip(personData) match {
case Some(carLeg) =>
carLeg.beamLeg.travelPath.distanceInM
Expand Down Expand Up @@ -475,6 +478,30 @@ class PersonAgent(
}
}

def isFirstTripWithinTour(personData: BasePersonData, nextAct: Activity): Boolean = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we could replace this check with instead checking if the current activity == home. I'm thinking of a case where someone did something like home -> work -> home -> shop -> home. We'd want them to be able to pick up their car for the second home -> shop -> home tour, for instance

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later edit -- if we rely on the Tour class in BeamPlan this gets easier, I think

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tours are created basing on "Home" activity:

if (activity.getType.equalsIgnoreCase("home")) {

So I think it's generally the same (current activity == home and isFirstTripWithinTour).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That isFirstTripWithinTour method is not used anywhere and is kept for symmetry.

val (tripIndexOfElement: Int, _) = currentTripIndexWithinTour(personData, nextAct)
tripIndexOfElement == 0
}

def isLastTripWithinTour(personData: BasePersonData, nextAct: Activity): Boolean = {
val (tripIndexOfElement: Int, lastTripIndex: Int) = currentTripIndexWithinTour(personData, nextAct)
tripIndexOfElement == lastTripIndex
}

def isFirstOrLastTripWithinTour(personData: BasePersonData, nextAct: Activity): Boolean = {
val (tripIndexOfElement: Int, lastTripIndex: Int) = currentTripIndexWithinTour(personData, nextAct)
tripIndexOfElement == 0 || tripIndexOfElement == lastTripIndex
}

def currentTripIndexWithinTour(personData: BasePersonData, nextAct: Activity): (Int, Int) = {
val tour = currentTour(personData)
val lastTripIndex = tour.trips.size - 1
val tripIndexOfElement = tour
.tripIndexOfElement(nextAct)
.getOrElse(throw new IllegalArgumentException(s"Element [$nextAct] not found"))
(tripIndexOfElement, lastTripIndex)
}

def currentActivity(data: BasePersonData): Activity =
_experiencedBeamPlan.activities(data.currentActivityIndex)

Expand Down Expand Up @@ -586,15 +613,10 @@ class PersonAgent(
val nextCoord = nextActivity(data).get.getCoord
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I look at it, I wonder if the definition of modeOfNextLeg above can be replaced with

val modeOfNextLeg = _experiencedBeamPlan.getStrategy(nextAct, classOf[ModeChoiceStrategy])

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replayed in the comment bellow.

goto(ChoosingMode) using ChoosesModeData(
personData = data.copy(
// If the mode of the next leg is defined and is CAV, use it, otherwise,
// If we don't have a current tour mode (i.e. are not on a tour aka at home),
// use the mode of the next leg as the new tour mode.
currentTourMode = modeOfNextLeg match {
case Some(CAV) =>
Some(CAV)
case _ =>
data.currentTourMode.orElse(modeOfNextLeg)
},
// We do not stick to current tour mode
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if here we can make use of the currentTour method defined above to take advantage of the tour structure already in BeamPlan.scala. It might take some archaeology to figure out how exactly everything was intended to function, but it looks like the strategies map in a BeamPlan allows for modes to be stored both for individual trips/legs and for tours.

So, I'm wondering if something like

val tourModeOption = _experiencedBeamPlan.getStrategy(currentTour(data), classOf[ModeChoiceStrategy])

would get the current tour mode, and we could do something similar with putStrategy when the initial trip mode is chosen and we want to set the tour mode.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replayed in the comment bellow.

// If we have the currentTourPersonalVehicle then we should use it
// use the mode of the next leg as the new trip mode.
currentTripMode = modeOfNextLeg,
numberOfReplanningAttempts = 0,
failedTrips = IndexedSeq.empty,
enrouteData = EnrouteData()
Expand All @@ -611,7 +633,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)

Expand All @@ -625,7 +647,7 @@ class PersonAgent(

case Event(
TriggerWithId(TeleportationEndsTrigger(tick), triggerId),
data @ BasePersonData(_, Some(currentTrip), _, _, maybeCurrentTourMode, _, _, _, true, _, _, _, _, _)
data @ BasePersonData(_, Some(currentTrip), _, _, _, maybeCurrentTripMode, _, _, _, true, _, _, _, _, _)
) =>
holdTickAndTriggerId(tick, triggerId)

Expand All @@ -638,7 +660,7 @@ class PersonAgent(
startY = currentTrip.legs.head.beamLeg.travelPath.startPoint.loc.getY,
endX = currentTrip.legs.last.beamLeg.travelPath.endPoint.loc.getX,
endY = currentTrip.legs.last.beamLeg.travelPath.endPoint.loc.getY,
currentTourMode = maybeCurrentTourMode.map(_.value)
currentTourMode = maybeCurrentTripMode.map(_.value)
)
eventsManager.processEvent(teleportationEvent)

Expand All @@ -657,7 +679,7 @@ class PersonAgent(
*/
case Event(
TriggerWithId(PersonDepartureTrigger(tick), triggerId),
data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, false, _, _, _, _, _)
data @ BasePersonData(_, Some(currentTrip), _, _, _, _, _, _, _, false, _, _, _, _, _)
) =>
endActivityAndDepart(tick, currentTrip, data)

Expand All @@ -666,7 +688,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}")
Expand Down Expand Up @@ -722,7 +744,7 @@ class PersonAgent(
val currentCoord = beamServices.geo.wgs2Utm(data.restOfCurrentTrip.head.beamLeg.travelPath.startPoint).loc
val nextCoord = nextActivity(data).get.getCoord
goto(ChoosingMode) using ChoosesModeData(
data.copy(currentTourMode = None, numberOfReplanningAttempts = data.numberOfReplanningAttempts + 1),
data.copy(currentTripMode = None, numberOfReplanningAttempts = data.numberOfReplanningAttempts + 1),
currentLocation = SpaceTime(
currentCoord,
tick
Expand All @@ -741,7 +763,7 @@ class PersonAgent(
// TRANSIT FAILURE
case Event(
ReservationResponse(Left(firstErrorResponse), _),
data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _)
data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _, _)
) =>
logDebug(s"replanning because ${firstErrorResponse.errorCode}")

Expand Down Expand Up @@ -815,7 +837,7 @@ class PersonAgent(
// RIDE HAIL FAILURE
case Event(
response @ RideHailResponse(_, _, Some(error), _, _),
data @ BasePersonData(_, _, _, _, _, _, _, _, _, _, _, _, _, _)
data: BasePersonData
) =>
handleFailedRideHailReservation(error, response, data)
}
Expand All @@ -826,7 +848,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))
Expand Down Expand Up @@ -859,7 +881,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")
Expand Down Expand Up @@ -936,6 +958,7 @@ class PersonAgent(
_,
_,
_,
_,
currentCost,
_,
_,
Expand Down Expand Up @@ -980,6 +1003,7 @@ class PersonAgent(
goto(ChoosingMode) using ChoosesModeData(
basePersonData.copy(
currentTourMode = None, // Have to give up my mode as well, perhaps there's no option left for driving.
currentTripMode = None,
currentTourPersonalVehicle = None,
numberOfReplanningAttempts = basePersonData.numberOfReplanningAttempts + 1
),
Expand Down Expand Up @@ -1076,6 +1100,7 @@ class PersonAgent(
_,
_,
_,
_,
_
)
) if nextLeg.asDriver =>
Expand Down Expand Up @@ -1170,7 +1195,7 @@ class PersonAgent(
nextState

// TRANSIT but too late
case Event(StateTimeout, data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _))
case Event(StateTimeout, data @ BasePersonData(_, _, nextLeg :: _, _, _, _, _, _, _, _, _, _, _, _, _))
if nextLeg.beamLeg.mode.isTransit && nextLeg.beamLeg.startTime < _currentTick.get =>
// We've missed the bus. This occurs when something takes longer than planned (based on the
// initial inquiry). So we replan but change tour mode to WALK_TRANSIT since we've already done our non-transit
Expand All @@ -1185,15 +1210,15 @@ 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 =
if (canUseCars(currentCoord, nextCoord)) Vector.empty
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,
Expand All @@ -1204,7 +1229,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)

Expand Down Expand Up @@ -1232,7 +1257,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
Expand All @@ -1247,7 +1272,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 =
Expand All @@ -1256,7 +1281,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,
Expand All @@ -1276,7 +1301,8 @@ class PersonAgent(
_,
_,
_,
currentTourMode @ Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION),
_,
Some(HOV2_TELEPORTATION | HOV3_TELEPORTATION),
_,
_,
_,
Expand Down Expand Up @@ -1316,7 +1342,8 @@ class PersonAgent(
currentTrip = None,
restOfCurrentTrip = List(),
currentTourPersonalVehicle = None,
currentTourMode = if (activity.getType.equals("Home")) None else currentTourMode,
currentTourMode = if (atHome(activity)) None else data.currentTourMode,
currentTripMode = None,
hasDeparted = false
)
case None =>
Expand All @@ -1334,6 +1361,7 @@ class PersonAgent(
_,
_,
currentTourMode,
_,
currentTourPersonalVehicle,
_,
_,
Expand Down Expand Up @@ -1404,7 +1432,7 @@ class PersonAgent(
currentTourPersonalVehicle = currentTourPersonalVehicle match {
case Some(personalVehId) =>
val personalVeh = beamVehicles(personalVehId).asInstanceOf[ActualVehicle].vehicle
if (activity.getType.equals("Home")) {
if (atHome(activity)) {
potentiallyChargingBeamVehicles.put(personalVeh.id, beamVehicles(personalVeh.id))
beamVehicles -= personalVeh.id
personalVeh.getManager.get ! ReleaseVehicle(personalVeh, triggerId)
Expand All @@ -1415,7 +1443,8 @@ class PersonAgent(
case None =>
None
},
currentTourMode = if (activity.getType.equals("Home")) None else currentTourMode,
currentTourMode = if (atHome(activity)) None else currentTourMode,
currentTripMode = None,
hasDeparted = false
)
case None =>
Expand Down Expand Up @@ -1489,7 +1518,7 @@ class PersonAgent(
}

def getReplanningReasonFrom(data: BasePersonData, prefix: String): String = {
data.currentTourMode
data.currentTripMode
.collect { case mode =>
s"$prefix $mode"
}
Expand Down Expand Up @@ -1563,7 +1592,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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ object HouseholdActor {
val homeCoordFromPlans = household.members
.flatMap(person =>
person.getSelectedPlan.getPlanElements.asScala.flatMap {
case act: Activity if act.getType == "Home" => Some(act.getCoord)
case _ => None
case act: Activity if PersonAgent.atHome(act) => Some(act.getCoord)
case _ => None
}
)
.headOption
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import akka.util.Timeout
import beam.agentsim.Resource.NotifyVehicleIdle
import beam.agentsim.agents.BeamAgent.Finish
import beam.agentsim.agents.InitializeTrigger
import beam.agentsim.agents.PersonAgent.atHome
import beam.agentsim.agents.household.HouseholdActor._
import beam.agentsim.agents.household.HouseholdFleetManager.ResolvedParkingResponses
import beam.agentsim.agents.modalbehaviors.DrivesVehicle.ActualVehicle
Expand Down Expand Up @@ -104,7 +105,7 @@ class HouseholdFleetManager(
case GetVehicleTypes(triggerId) =>
sender() ! VehicleTypesResponse(vehicles.values.map(_.beamVehicleType).toSet, triggerId)

case MobilityStatusInquiry(personId, whenWhere, _, requireVehicleCategoryAvailable, triggerId) =>
case MobilityStatusInquiry(personId, whenWhere, originActivity, requireVehicleCategoryAvailable, triggerId) =>
{
for {
neededVehicleCategory <- requireVehicleCategoryAvailable
Expand Down Expand Up @@ -157,13 +158,15 @@ class HouseholdFleetManager(
}
}.getOrElse {
availableVehicles = availableVehicles match {
case firstVehicle :: rest =>
//in case of replanning because of TRANSIT failure WALK_TRANSIT is used
//but we may want to introduce maxWalkingDistance and check that the agent is close enough to the vehicle
case firstVehicle :: rest if atHome(originActivity) =>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Providing a private vehicle only when the person is at home might be a wrong idea. It may lead to increased number of "Long waling trips".

logger.debug("Vehicle {} is now taken", firstVehicle.id)
firstVehicle.becomeDriver(sender)
sender() ! MobilityStatusResponse(Vector(ActualVehicle(firstVehicle)), triggerId)
rest
case Nil =>
logger.debug(s"Not returning vehicle because no default is defined")
case _ =>
logger.debug(s"Not returning vehicle because no default is defined or agent is not at home")
sender() ! MobilityStatusResponse(Vector(), triggerId)
Nil
}
Expand Down
Loading