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

Adds tests to check if PersonCostEvents match their expected trip costs #3760

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions src/main/scala/beam/agentsim/agents/PersonAgent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,21 @@ object PersonAgent {
passengerSchedule: PassengerSchedule = PassengerSchedule(),
currentLegPassengerScheduleIndex: Int = 0,
hasDeparted: Boolean = false,
currentTripCosts: Double = 0.0,
numberOfReplanningAttempts: Int = 0,
failedTrips: IndexedSeq[EmbodiedBeamTrip] = IndexedSeq.empty,
lastUsedParkingStall: Option[ParkingStall] = None,
enrouteData: EnrouteData = EnrouteData()
) extends PersonData {

def currentTripCosts(): Double = {
currentTrip match {
case Some(trip) =>
trip.costEstimate
case _ =>
0.0
}
}

override def withPassengerSchedule(newPassengerSchedule: PassengerSchedule): DrivingData =
copy(passengerSchedule = newPassengerSchedule)

Expand Down Expand Up @@ -607,7 +615,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 @@ -621,7 +629,7 @@ class PersonAgent(

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

Expand Down Expand Up @@ -653,7 +661,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 @@ -662,7 +670,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 @@ -746,7 +754,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 @@ -837,7 +845,7 @@ class PersonAgent(
// RIDE HAIL FAILURE
case Event(
response @ RideHailResponse(_, _, _, Some(error), _, _),
data @ BasePersonData(_, _, _, _, _, _, _, _, _, _, _, _, _, _)
data @ BasePersonData(_, _, _, _, _, _, _, _, _, _, _, _, _)
) =>
handleFailedRideHailReservation(error, response, data)
}
Expand All @@ -848,7 +856,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 @@ -881,7 +889,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 All @@ -897,7 +905,7 @@ class PersonAgent(
when(PassengerScheduleEmpty) {
case Event(PassengerScheduleEmptyMessage(_, toll, triggerId, energyConsumedOption), data: BasePersonData) =>
updateFuelConsumed(energyConsumedOption)
val netTripCosts = data.currentTripCosts // This includes tolls because it comes from leg.cost
val netTripCosts = data.currentTripCosts() // This includes tolls because it comes from leg.cost
if (toll > 0.0 || netTripCosts > 0.0)
eventsManager.processEvent(
new PersonCostEvent(
Expand All @@ -912,14 +920,12 @@ class PersonAgent(
val dataForNextLegOrActivity = if (data.restOfCurrentTrip.head.unbecomeDriverOnCompletion) {
data.copy(
restOfCurrentTrip = data.restOfCurrentTrip.tail,
currentVehicle = if (data.currentVehicle.size > 1) data.currentVehicle.tail else Vector(),
currentTripCosts = 0.0
currentVehicle = if (data.currentVehicle.size > 1) data.currentVehicle.tail else Vector()
)
} else {
data.copy(
restOfCurrentTrip = data.restOfCurrentTrip.tail,
currentVehicle = Vector(body.id),
currentTripCosts = 0.0
currentVehicle = Vector(body.id)
)
}
if (data.restOfCurrentTrip.head.unbecomeDriverOnCompletion) {
Expand Down Expand Up @@ -958,26 +964,23 @@ class PersonAgent(
_,
_,
_,
currentCost,
_,
_,
enrouteData
)
) =>
val (trip, cost) = if (enrouteData.isInEnrouteState) {
val trip = if (enrouteData.isInEnrouteState) {
log.debug("ReadyToChooseParking, enroute trip: {}", currentTrip.toString())
// if enroute, keep the original trip and cost
(currentTrip, currentCost.toDouble)
currentTrip
} else {
log.debug("ReadyToChooseParking, trip: {}", restOfCurrentTrip.toString())
// "head" of the current trip is travelled, and returning rest of the trip
// adding the cost of the "head" of the trip to the current cost
(restOfCurrentTrip, currentCost.toDouble + headOfCurrentTrip.cost)
restOfCurrentTrip
}

goto(ChoosingParkingSpot) using data.copy(
restOfCurrentTrip = trip,
currentTripCosts = cost
restOfCurrentTrip = trip
)
}

Expand Down Expand Up @@ -1090,7 +1093,6 @@ class PersonAgent(
_,
_,
_,
_,
_
)
) if nextLeg.asDriver =>
Expand Down Expand Up @@ -1185,7 +1187,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 Down Expand Up @@ -1215,7 +1217,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,
Expand All @@ -1226,7 +1228,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 @@ -1258,7 +1260,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 Down Expand Up @@ -1289,7 +1291,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 Down Expand Up @@ -1317,7 +1319,6 @@ class PersonAgent(
_,
_,
_,
_,
_
)
) =>
Expand Down Expand Up @@ -1387,7 +1388,6 @@ class PersonAgent(
_,
_,
_,
_,
_
)
) =>
Expand Down Expand Up @@ -1662,7 +1662,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 @@ -129,7 +129,7 @@ trait ChoosesMode {
nextStateData match {
// If I am already on a tour in a vehicle, only that vehicle is available to me
case ChoosesModeData(
BasePersonData(_, _, _, _, _, Some(vehicle), _, _, _, _, _, _, _, _),
BasePersonData(_, _, _, _, _, Some(vehicle), _, _, _, _, _, _, _),
_,
_,
_,
Expand Down Expand Up @@ -168,7 +168,6 @@ trait ChoosesMode {
_,
_,
_,
_,
_
),
currentLocation,
Expand Down Expand Up @@ -211,7 +210,6 @@ trait ChoosesMode {
_,
_,
_,
_,
_
),
currentLocation,
Expand Down Expand Up @@ -937,9 +935,20 @@ trait ChoosesMode {
val theRouterResult = response.copy(itineraries = response.itineraries.map { it =>
it.copy(
it.legs.flatMap(embodiedLeg =>
if (legVehicleHasParkingBehavior(embodiedLeg))
EmbodiedBeamLeg.splitLegForParking(embodiedLeg, beamServices, transportNetwork)
else Vector(embodiedLeg)
if (legVehicleHasParkingBehavior(embodiedLeg)) {
val vehicleType = beamScenario.vehicleTypes(embodiedLeg.beamVehicleTypeId)
val fuelPrice =
beamScenario.fuelTypePrices(beamScenario.vehicleTypes(embodiedLeg.beamVehicleTypeId).primaryFuelType)

EmbodiedBeamLeg.splitLegForParking(
embodiedLeg,
beamServices,
transportNetwork,
tollCalculator,
vehicleType,
fuelPrice
)
} else Vector(embodiedLeg)
)
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,7 @@ trait DrivesVehicle[T <: DrivingData] extends BeamAgent[T] with Stash with Expon
updateLatestObservedTick(tick)

val dataForNextLegOrActivity: BasePersonData = data.copy(
currentVehicle = Vector(),
currentTripCosts = 0.0
currentVehicle = Vector()
)

holdTickAndTriggerId(tick, triggerId)
Expand Down
13 changes: 12 additions & 1 deletion src/main/scala/beam/agentsim/agents/parking/ChoosesParking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,18 @@ trait ChoosesParking extends {
legs
.find(_.beamLeg.mode == CAR)
.map { beamLeg =>
EmbodiedBeamLeg.splitLegForParking(beamLeg, beamServices, transportNetwork)
val vehicleType = beamScenario.vehicleTypes(beamLeg.beamVehicleTypeId)
val fuelPrice =
beamScenario.fuelTypePrices(beamScenario.vehicleTypes(beamLeg.beamVehicleTypeId).primaryFuelType)

EmbodiedBeamLeg.splitLegForParking(
beamLeg,
beamServices,
transportNetwork,
tollCalculator,
vehicleType,
fuelPrice
)
}
.getOrElse {
log.error("EnRoute: car leg not found in routing response.")
Expand Down
31 changes: 27 additions & 4 deletions src/main/scala/beam/router/model/EmbodiedBeamLeg.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package beam.router.model

import beam.agentsim.agents.choice.mode.DrivingCost
import beam.agentsim.agents.vehicles.{BeamVehicle, BeamVehicleType}
import beam.agentsim.events.SpaceTime
import beam.router.BeamRouter.Location
import beam.router.Modes.BeamMode
import beam.router.osm.TollCalculator
import beam.sim.BeamServices
import com.conveyal.r5.streets.EdgeStore
import com.conveyal.r5.transit.TransportNetwork
Expand Down Expand Up @@ -56,7 +58,10 @@ object EmbodiedBeamLeg {
def splitLegForParking(
leg: EmbodiedBeamLeg,
beamServices: BeamServices,
transportNetwork: TransportNetwork
transportNetwork: TransportNetwork,
tollCalculator: TollCalculator,
vehicleType: BeamVehicleType,
fuelCost: Double
): Vector[EmbodiedBeamLeg] = {
val theLinkIds = leg.beamLeg.travelPath.linkIds
val indexFromEnd = Math.min(
Expand Down Expand Up @@ -100,22 +105,40 @@ object EmbodiedBeamLeg {
firstPathEndpoint.copy(time = (leg.beamLeg.travelPath.startPoint.time + firstTravelTimes.tail.sum).toInt),
distanceInM = leg.beamLeg.travelPath.distanceInM - secondPath.distanceInM
)

val firstCost = DrivingCost.estimateDrivingCost(
firstPath.distanceInM,
firstPath.linkTravelTime.sum.toInt,
vehicleType,
fuelCost
) + tollCalculator.calcTollByLinkIds(firstPath)

val secondCost = DrivingCost.estimateDrivingCost(
secondPath.distanceInM,
secondPath.linkTravelTime.sum.toInt,
vehicleType,
fuelCost
) + tollCalculator.calcTollByLinkIds(secondPath)

val firstLeg = leg.copy(
beamLeg = leg.beamLeg.copy(
travelPath = firstPath,
duration = firstDuration
),
unbecomeDriverOnCompletion = false
unbecomeDriverOnCompletion = false,
cost = firstCost
)
val secondLeg = leg.copy(
beamLeg = leg.beamLeg.copy(
travelPath = secondPath,
startTime = firstLeg.beamLeg.startTime + firstLeg.beamLeg.duration,
duration = secondDuration
),
cost = 0
cost = secondCost
)
assert((firstLeg.cost + secondLeg.cost).equals(leg.cost))

// allows 0.1% error on cost because of floating point operations
assert(Math.abs(firstLeg.cost + secondLeg.cost - leg.cost) < leg.cost * 1e-3)
assert(firstLeg.beamLeg.duration + secondLeg.beamLeg.duration == leg.beamLeg.duration)
assert(
Math.abs(
Expand Down
Loading