Skip to content

Commit

Permalink
Merge pull request #3880 from LBNL-UCB-STI/do/#3877-od-skimmer-for-ve…
Browse files Browse the repository at this point in the history
…hicle-types

OD Skimmer for vehicle types
  • Loading branch information
dimaopen authored Jul 9, 2024
2 parents 3a46a33 + 22796f0 commit ef119f4
Show file tree
Hide file tree
Showing 24 changed files with 624 additions and 76 deletions.
74 changes: 72 additions & 2 deletions docs/outputs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1395,7 +1395,7 @@ Classname: ODSkimmer
+----------------------------------+--------------------------------------------------------------------------------+
| distanceInM | Average trip distance in meters |
+----------------------------------+--------------------------------------------------------------------------------+
| payloadWeightInKg | Average if freight trip it contains paylod weight in kg |
| payloadWeightInKg | Average payload weight (if it's not a freight trip then it is zero) |
+----------------------------------+--------------------------------------------------------------------------------+
| energy | Average energy consumed in Joules |
+----------------------------------+--------------------------------------------------------------------------------+
Expand Down Expand Up @@ -1436,7 +1436,7 @@ Classname: ODSkimmer
+----------------------------------+--------------------------------------------------------------------------------+
| distanceInM | Average trip distance in meters |
+----------------------------------+--------------------------------------------------------------------------------+
| payloadWeightInKg | Average if freight trip it contains paylod weight in kg |
| payloadWeightInKg | Average payload weight (if it's not a freight trip then it is zero) |
+----------------------------------+--------------------------------------------------------------------------------+
| energy | Average energy consumed in Joules |
+----------------------------------+--------------------------------------------------------------------------------+
Expand All @@ -1449,6 +1449,76 @@ Classname: ODSkimmer
| iterations | Number of iterations |
+----------------------------------+--------------------------------------------------------------------------------+

File: /ITERS/it.0/0.skimsODVehicleType.csv.gz
---------------------------------------------

Classname: ODVehicleTypeSkimmer

+--------------------+---------------------------------------------------------------------+
| field | description |
+====================+=====================================================================+
| hour | Hour this statistic applies to |
+--------------------+---------------------------------------------------------------------+
| vehicleType | Type of the vehicle making the trip |
+--------------------+---------------------------------------------------------------------+
| origTaz | TAZ id of trip origin |
+--------------------+---------------------------------------------------------------------+
| destTaz | TAZ id of trip destination |
+--------------------+---------------------------------------------------------------------+
| travelTimeInS | Average travel time in seconds |
+--------------------+---------------------------------------------------------------------+
| generalizedTimeInS | Average generalized travel time in seconds |
+--------------------+---------------------------------------------------------------------+
| cost | Average trip total cost |
+--------------------+---------------------------------------------------------------------+
| generalizedCost | Average trip generalized cost |
+--------------------+---------------------------------------------------------------------+
| distanceInM | Average trip distance in meters |
+--------------------+---------------------------------------------------------------------+
| payloadWeightInKg | Average payload weight (if it's not a freight trip then it is zero) |
+--------------------+---------------------------------------------------------------------+
| energy | Average energy consumed in Joules |
+--------------------+---------------------------------------------------------------------+
| observations | Number of events |
+--------------------+---------------------------------------------------------------------+
| iterations | Number of iterations (always 1) |
+--------------------+---------------------------------------------------------------------+

File: /ITERS/it.0/0.skimsODVehicleType_Aggregated.csv.gz
--------------------------------------------------------

Classname: ODVehicleTypeSkimmer

+--------------------+---------------------------------------------------------------------+
| field | description |
+====================+=====================================================================+
| hour | Hour this statistic applies to |
+--------------------+---------------------------------------------------------------------+
| vehicleType | Trip mode |
+--------------------+---------------------------------------------------------------------+
| origTaz | TAZ id of trip origin |
+--------------------+---------------------------------------------------------------------+
| destTaz | TAZ id of trip destination |
+--------------------+---------------------------------------------------------------------+
| travelTimeInS | Average (over last n iterations) travel time in seconds |
+--------------------+---------------------------------------------------------------------+
| generalizedTimeInS | Average generalized travel time in seconds |
+--------------------+---------------------------------------------------------------------+
| cost | Average trip total cost |
+--------------------+---------------------------------------------------------------------+
| generalizedCost | Average trip generalized cost |
+--------------------+---------------------------------------------------------------------+
| distanceInM | Average trip distance in meters |
+--------------------+---------------------------------------------------------------------+
| payloadWeightInKg | Average payload weight (if it's not a freight trip then it is zero) |
+--------------------+---------------------------------------------------------------------+
| energy | Average energy consumed in Joules |
+--------------------+---------------------------------------------------------------------+
| observations | Number of events |
+--------------------+---------------------------------------------------------------------+
| iterations | Number of iterations which data is used here |
+--------------------+---------------------------------------------------------------------+

File: /ITERS/it.0/0.skimsParking.csv.gz
---------------------------------------

Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/beam-template.conf
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,10 @@ beam.router.skim = {
name = "transit-crowding-skimmer"
fileBaseName = "String | skimsTransitCrowding"
}
origin-destination-vehicle-type-skimmer {
# Comma separated list of VehicleCategories that the skim is generated for
vehicleCategories = "Car"
}
}

#h3taz
Expand Down
67 changes: 62 additions & 5 deletions src/main/scala/beam/agentsim/agents/PersonAgent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import beam.router.skim.ActivitySimSkimmerEvent
import beam.router.skim.event.{
DriveTimeSkimmerEvent,
ODSkimmerEvent,
ODVehicleTypeSkimmerEvent,
RideHailSkimmerEvent,
UnmatchedRideHailRequestSkimmerEvent
}
Expand Down Expand Up @@ -201,6 +202,12 @@ object PersonAgent {
def hasNextLeg: Boolean = restOfCurrentTrip.nonEmpty
def nextLeg: EmbodiedBeamLeg = restOfCurrentTrip.head

def accomplishedLegs: IndexedSeq[EmbodiedBeamLeg] = {
val allLegs: IndexedSeq[EmbodiedBeamLeg] = currentTrip.map(_.legs).getOrElse(IndexedSeq.empty)
val numberOfAccomplishedLegs = allLegs.size - restOfCurrentTrip.size
allLegs.take(numberOfAccomplishedLegs)
}

def shouldReserveRideHail(): Boolean = {
// if we are about to walk then ride-hail
// OR we are at a ride-hail leg but we didn't reserve a RH yet
Expand Down Expand Up @@ -1133,17 +1140,18 @@ class PersonAgent(

when(ProcessingNextLegOrStartActivity, stateTimeout = Duration.Zero) {
case Event(StateTimeout, data: BasePersonData) if data.shouldReserveRideHail() =>
generateLegSkimData(_currentTick.get, data.accomplishedLegs, data.currentActivityIndex, nextActivity(data))
// Doing RH reservation before we start walking to our pickup location
val ridehailTrip = data.restOfCurrentTrip.dropWhile(!_.isRideHail)
doRideHailReservation(data.nextLeg.beamLeg.startTime, data.nextLeg.beamLeg.endTime, ridehailTrip)
goto(WaitingForRideHailReservationConfirmation)
case Event(StateTimeout, _) =>
case Event(StateTimeout, data: BasePersonData) =>
generateLegSkimData(_currentTick.get, data.accomplishedLegs, data.currentActivityIndex, nextActivity(data))
goto(ActuallyProcessingNextLegOrStartActivity)
}

when(ActuallyProcessingNextLegOrStartActivity, stateTimeout = Duration.Zero) {
case Event(StateTimeout, data: BasePersonData) if data.hasNextLeg && data.nextLeg.asDriver =>
val restOfCurrentTrip = data.restOfCurrentTrip.tail
// Declaring a function here because this case is already so convoluted that I require a return
// statement from within.
// TODO: Refactor.
Expand Down Expand Up @@ -1562,8 +1570,8 @@ class PersonAgent(
(origGeo, destGeo)
} else {
(
geoMap.getTAZ(currentAct.getCoord).toString,
maybeNextAct.map(act => geoMap.getTAZ(act.getCoord).toString).getOrElse("NA")
geoMap.getTAZ(currentAct.getCoord).tazId.toString,
maybeNextAct.map(act => geoMap.getTAZ(act.getCoord).tazId.toString).getOrElse("NA")
)
}
(origin, destination)
Expand Down Expand Up @@ -1612,7 +1620,7 @@ class PersonAgent(
generalizedTime,
generalizedCost,
maybePayloadWeightInKg,
curFuelConsumed.primaryFuel + curFuelConsumed.secondaryFuel,
curFuelConsumed.totalEnergyConsumed,
failedTrip
)
eventsManager.processEvent(odSkimmerEvent)
Expand All @@ -1625,6 +1633,55 @@ class PersonAgent(
}
}

def generateLegSkimData(
tick: Int,
accomplishedLegs: IndexedSeq[EmbodiedBeamLeg],
currentActivityIndex: Int,
nextActivity: Option[Activity]
): Unit = {
if (accomplishedLegs.isEmpty)
return

val vehicleTypeIdOpt = Option(accomplishedLegs.last.beamVehicleTypeId) match {
case None if accomplishedLegs.last.beamLeg.mode.isTransit =>
//FIXME TransitVehicleInitializer can load more fine-grained transit vehicle types
// using transitVehicleTypesByRouteFile parameter. We could use those vehicle types here
Some(TransitVehicleInitializer.transitModeToBeamVehicleType(accomplishedLegs.last.beamLeg.mode))
case None => None
case some => some
}
val vehicleTypeOpt = for {
vehicleTypeId <- vehicleTypeIdOpt
vehicleType <- beamScenario.vehicleTypes.get(vehicleTypeId)
} yield vehicleType
if (vehicleTypeOpt.isEmpty)
return
val vehicleType = vehicleTypeOpt.get

if (!beamServices.skims.odVehicleTypeSkimmer.vehicleCategoriesToGenerateSkim.contains(vehicleType.vehicleCategory))
return
val leg0 = accomplishedLegs
.lift(accomplishedLegs.length - 2)
.filter(_.beamVehicleId == accomplishedLegs.last.beamVehicleId)
.toIndexedSeq
val legs: IndexedSeq[EmbodiedBeamLeg] = leg0 :+ accomplishedLegs.last
val trip = EmbodiedBeamTrip(legs)
val generalizedTime = modeChoiceCalculator.getGeneralizedTimeOfTrip(trip, Some(attributes), nextActivity)
val generalizedCost = modeChoiceCalculator.getNonTimeCost(trip) + attributes.getVOT(generalizedTime)
val maybePayloadWeightInKg = getPayloadWeightFromLeg(currentActivityIndex)
val odVehicleTypeEvent = ODVehicleTypeSkimmerEvent(
tick,
beamServices,
vehicleType,
trip,
generalizedTime,
generalizedCost,
maybePayloadWeightInKg,
curFuelConsumed.totalEnergyConsumed
)
eventsManager.processEvent(odVehicleTypeEvent)
}

private def getPayloadWeightFromLeg(currentActivityIndex: Int): Option[Double] = {
val currentLegIndex = currentActivityIndex * 2 + 1
if (currentLegIndex < matsimPlan.getPlanElements.size()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1500,8 +1500,8 @@ trait ChoosesMode {
(origGeo, destGeo)
} else {
(
geoMap.getTAZ(originActivity.getCoord).toString,
geoMap.getTAZ(destinationActivity.getCoord).toString
geoMap.getTAZ(originActivity.getCoord).tazId.toString,
geoMap.getTAZ(destinationActivity.getCoord).tazId.toString
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,9 @@ object BeamVehicle {
secondaryFuel: Double /*, fuelConsumptionData: IndexedSeq[FuelConsumptionData],
primaryLoggingData: IndexedSeq[LoggingData],
secondaryLoggingData: IndexedSeq[LoggingData]*/
)
) {
def totalEnergyConsumed: Double = primaryFuel + secondaryFuel
}

val idPrefixSharedTeleportationVehicle = "teleportationSharedVehicle"
val idPrefixRideHail = "rideHailVehicle"
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/beam/agentsim/infrastructure/taz/TAZ.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class TAZ(val tazId: Id[TAZ], val coord: Coord, val areaInSquareMeters: Double,
def this(tazIdString: String, coord: Coord, area: Double, geometry: Option[Geometry] = None) {
this(Id.create(tazIdString, classOf[TAZ]), coord, area, geometry)
}

override def toString: String = s"TAZ{$tazId}"
}

object TAZ {
Expand Down
20 changes: 10 additions & 10 deletions src/main/scala/beam/router/skim/CsvSkimReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ import scala.util.{Failure, Success, Try}
* @param logger passing logger from skimmer as this is invoked by various skimmers. This helps distinguishing in the
* log which skimmer has an issue reading skims.
*/
class CsvSkimReader(
class CsvSkimReader[Key <: AbstractSkimmerKey, Value <: AbstractSkimmerInternal](
val aggregatedSkimsFilePath: String,
fromCsv: scala.collection.Map[String, String] => (AbstractSkimmerKey, AbstractSkimmerInternal),
fromCsv: scala.collection.Map[String, String] => (Key, Value),
logger: Logger
) {

def readAggregatedSkims: Map[AbstractSkimmerKey, AbstractSkimmerInternal] = {
def readAggregatedSkims: Map[Key, Value] = {
if (!new File(aggregatedSkimsFilePath).isFile) {
logger.info(s"warmStart skim NO PATH FOUND '$aggregatedSkimsFilePath'")
Map.empty[AbstractSkimmerKey, AbstractSkimmerInternal]
Map.empty
} else {
val skimsTryMap = Try {
IOUtils.getBufferedReader(aggregatedSkimsFilePath)
Expand All @@ -37,27 +37,27 @@ class CsvSkimReader(
case Success(skimMap) => skimMap
case Failure(ex) =>
logger.warn(s"Could not load warmStart skim from '$aggregatedSkimsFilePath'", ex)
Map.empty[AbstractSkimmerKey, AbstractSkimmerInternal]
Map.empty
}
}
}

def readSkims(reader: BufferedReader): Map[AbstractSkimmerKey, AbstractSkimmerInternal] = {
def readSkims(reader: BufferedReader): Map[Key, Value] = {
tryReadSkims(reader).recover { case ex: Throwable =>
logger.warn(s"Could not read warmStart skim from '$aggregatedSkimsFilePath'", ex)
Map.empty[AbstractSkimmerKey, AbstractSkimmerInternal]
Map.empty[Key, Value]
}.get
}

private def tryReadSkims(reader: BufferedReader): Try[Map[AbstractSkimmerKey, AbstractSkimmerInternal]] = {
private def tryReadSkims(reader: BufferedReader): Try[Map[Key, Value]] = {
val csvParser: CsvParser = getCsvParser
val result: Try[Map[AbstractSkimmerKey, AbstractSkimmerInternal]] = Try {
val result: Try[Map[Key, Value]] = Try {
// Headers will be available only when parsing was started
lazy val headers = {
csvParser.getRecordMetadata.headers()
}
val mapReader = csvParser.iterateRecords(reader).asScala
val res: Map[AbstractSkimmerKey, AbstractSkimmerInternal] = mapReader
val res: Map[Key, Value] = mapReader
.map(rec => {
val a = convertRecordToMap(rec, headers)
val newPair = fromCsv(a)
Expand Down
Loading

0 comments on commit ef119f4

Please sign in to comment.