From 36bfc2feee633cb424ac8b7058d8245f92d89591 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Thu, 20 Jun 2024 18:10:59 +0300 Subject: [PATCH 01/10] Fixed spelling --- src/main/scala/beam/router/skim/core/ODSkimmer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/beam/router/skim/core/ODSkimmer.scala b/src/main/scala/beam/router/skim/core/ODSkimmer.scala index f43c1f0ab15..c4162427216 100644 --- a/src/main/scala/beam/router/skim/core/ODSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/ODSkimmer.scala @@ -536,7 +536,7 @@ object ODSkimmer extends LazyLogging { cost | Average trip total cost generalizedCost | Average trip generalized cost 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 level4CavTravelTimeScalingFactor | Always 1.0 failedTrips | Number of failed trips @@ -558,7 +558,7 @@ object ODSkimmer extends LazyLogging { cost | Average trip total cost generalizedCost | Average trip generalized cost 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 level4CavTravelTimeScalingFactor | Always 1.0 failedTrips | Average number of failed trips From 43120c278385c022fc4d6a9158bae2ce4aed5127 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Thu, 20 Jun 2024 18:11:22 +0300 Subject: [PATCH 02/10] Added ODVehicleTypeSkimmer --- docs/outputs.rst | 74 ++++++++- .../beam/agentsim/agents/PersonAgent.scala | 17 +- .../agents/vehicles/BeamVehicle.scala | 4 +- src/main/scala/beam/router/skim/Skims.scala | 21 ++- .../router/skim/core/AbstractSkimmer.scala | 15 ++ .../skim/core/ODVehicleTypeSkimmer.scala | 156 ++++++++++++++++++ .../event/ODVehicleTypeSkimmerEvent.scala | 88 ++++++++++ .../router/skim/readonly/FreightSkims.scala | 13 +- .../skim/readonly/ODVehicleTypeSkims.scala | 25 +++ .../router/skim/readonly/ParkingSkims.scala | 13 +- .../router/skim/readonly/RideHailSkims.scala | 13 +- src/main/scala/beam/sim/BeamHelper.scala | 1 + src/main/scala/beam/sim/BeamMobsim.scala | 1 + .../BeamOutputDataDescriptionGenerator.scala | 2 + src/main/scala/beam/sim/BeamSim.scala | 2 + 15 files changed, 398 insertions(+), 47 deletions(-) create mode 100644 src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala create mode 100644 src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala create mode 100644 src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala diff --git a/docs/outputs.rst b/docs/outputs.rst index 3fc9d7ade62..7d141968f9b 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -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 | +----------------------------------+--------------------------------------------------------------------------------+ @@ -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 | +----------------------------------+--------------------------------------------------------------------------------+ @@ -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 --------------------------------------- diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala index 38b4347b14d..2370dc53c8a 100755 --- a/src/main/scala/beam/agentsim/agents/PersonAgent.scala +++ b/src/main/scala/beam/agentsim/agents/PersonAgent.scala @@ -49,6 +49,7 @@ import beam.router.skim.ActivitySimSkimmerEvent import beam.router.skim.event.{ DriveTimeSkimmerEvent, ODSkimmerEvent, + ODVehicleTypeSkimmerEvent, RideHailSkimmerEvent, UnmatchedRideHailRequestSkimmerEvent } @@ -1612,7 +1613,7 @@ class PersonAgent( generalizedTime, generalizedCost, maybePayloadWeightInKg, - curFuelConsumed.primaryFuel + curFuelConsumed.secondaryFuel, + curFuelConsumed.totalEnergyConsumed, failedTrip ) eventsManager.processEvent(odSkimmerEvent) @@ -1623,6 +1624,20 @@ class PersonAgent( correctedTrip.legs.filter(x => x.beamLeg.mode == BeamMode.CAR || x.beamLeg.mode == BeamMode.CAV).foreach { carLeg => eventsManager.processEvent(DriveTimeSkimmerEvent(tick, beamServices, carLeg)) } + if (!failedTrip && correctedTrip.tripClassifier == BeamMode.CAR) { + val vehicleTypeId = correctedTrip.legs.find(_.beamLeg.mode == CAR).get.beamVehicleTypeId + val odVehicleTypeEvent = ODVehicleTypeSkimmerEvent( + tick, + beamServices, + vehicleTypeId, + correctedTrip, + generalizedTime, + generalizedCost, + maybePayloadWeightInKg, + curFuelConsumed.totalEnergyConsumed + ) + eventsManager.processEvent(odVehicleTypeEvent) + } } private def getPayloadWeightFromLeg(currentActivityIndex: Int): Option[Double] = { diff --git a/src/main/scala/beam/agentsim/agents/vehicles/BeamVehicle.scala b/src/main/scala/beam/agentsim/agents/vehicles/BeamVehicle.scala index 459c1ae5348..c8a0a739f47 100755 --- a/src/main/scala/beam/agentsim/agents/vehicles/BeamVehicle.scala +++ b/src/main/scala/beam/agentsim/agents/vehicles/BeamVehicle.scala @@ -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" diff --git a/src/main/scala/beam/router/skim/Skims.scala b/src/main/scala/beam/router/skim/Skims.scala index e642801be34..68ce586a222 100644 --- a/src/main/scala/beam/router/skim/Skims.scala +++ b/src/main/scala/beam/router/skim/Skims.scala @@ -19,6 +19,7 @@ class Skims @Inject() ( driveTimeSkimmer: DriveTimeSkimmer, transitCrowdingSkimmer: TransitCrowdingSkimmer, rideHailSkimmer: RideHailSkimmer, + odVehicleTypeSkimmer: ODVehicleTypeSkimmer, freightSkimmer: FreightSkimmer, parkingSkimmer: ParkingSkimmer, asSkimmer: ActivitySimSkimmer @@ -30,6 +31,9 @@ class Skims @Inject() ( lazy val dt_skimmer: DriveTimeSkims = lookup(SkimType.DT_SKIMMER).asInstanceOf[DriveTimeSkims] lazy val tc_skimmer: TransitCrowdingSkims = lookup(SkimType.TC_SKIMMER).asInstanceOf[TransitCrowdingSkims] lazy val rh_skimmer: RideHailSkims = lookup(SkimType.RH_SKIMMER).asInstanceOf[RideHailSkims] + + lazy val od_vehicle_type_skimmer: ODVehicleTypeSkims = + lookup(SkimType.OD_VEHICLE_TYPE_SKIMMER).asInstanceOf[ODVehicleTypeSkims] lazy val freight_skimmer: FreightSkims = lookup(SkimType.FREIGHT_SKIMMER).asInstanceOf[FreightSkims] lazy val parking_skimmer: ParkingSkims = lookup(SkimType.PARKING_SKIMMER).asInstanceOf[ParkingSkims] @@ -39,6 +43,7 @@ class Skims @Inject() ( skims.put(SkimType.DT_SKIMMER, addEvent(driveTimeSkimmer)) skims.put(SkimType.TC_SKIMMER, addEvent(transitCrowdingSkimmer)) skims.put(SkimType.RH_SKIMMER, addEvent(rideHailSkimmer)) + skims.put(SkimType.OD_VEHICLE_TYPE_SKIMMER, addEvent(odVehicleTypeSkimmer)) skims.put(SkimType.FREIGHT_SKIMMER, addEvent(freightSkimmer)) skims.put(SkimType.PARKING_SKIMMER, addEvent(parkingSkimmer)) skims.put(SkimType.AS_SKIMMER, addEvent(asSkimmer)) @@ -62,19 +67,21 @@ object Skims { val DT_SKIMMER: skim.Skims.SkimType.Value = Value("drive-time-skimmer") val TC_SKIMMER: skim.Skims.SkimType.Value = Value("transit-crowding-skimmer") val RH_SKIMMER: skim.Skims.SkimType.Value = Value("ridehail-skimmer") + val OD_VEHICLE_TYPE_SKIMMER: router.skim.Skims.SkimType.Value = Value("od-vehicle-type-skimmer") val FREIGHT_SKIMMER: skim.Skims.SkimType.Value = Value("freight-skimmer") val PARKING_SKIMMER: skim.Skims.SkimType.Value = Value("parking-skimmer") val AS_SKIMMER: router.skim.Skims.SkimType.Value = Value("activity-sim-skimmer") } def skimFileNames(skimCfg: Router.Skim) = IndexedSeq( - SkimType.OD_SKIMMER -> skimCfg.origin_destination_skimmer.fileBaseName, - SkimType.TAZ_SKIMMER -> skimCfg.taz_skimmer.fileBaseName, - SkimType.DT_SKIMMER -> skimCfg.drive_time_skimmer.fileBaseName, - SkimType.RH_SKIMMER -> RideHailSkimmer.fileBaseName, - SkimType.FREIGHT_SKIMMER -> FreightSkimmer.fileBaseName, - SkimType.PARKING_SKIMMER -> ParkingSkimmer.fileBaseName, - SkimType.TC_SKIMMER -> skimCfg.transit_crowding_skimmer.fileBaseName + SkimType.OD_SKIMMER -> skimCfg.origin_destination_skimmer.fileBaseName, + SkimType.TAZ_SKIMMER -> skimCfg.taz_skimmer.fileBaseName, + SkimType.DT_SKIMMER -> skimCfg.drive_time_skimmer.fileBaseName, + SkimType.RH_SKIMMER -> RideHailSkimmer.fileBaseName, + SkimType.OD_VEHICLE_TYPE_SKIMMER -> ODVehicleTypeSkimmer.fileBaseName, + SkimType.FREIGHT_SKIMMER -> FreightSkimmer.fileBaseName, + SkimType.PARKING_SKIMMER -> ParkingSkimmer.fileBaseName, + SkimType.TC_SKIMMER -> skimCfg.transit_crowding_skimmer.fileBaseName ) def skimAggregatedFileNames(skimCfg: Router.Skim): IndexedSeq[(SkimType.Value, String)] = diff --git a/src/main/scala/beam/router/skim/core/AbstractSkimmer.scala b/src/main/scala/beam/router/skim/core/AbstractSkimmer.scala index d1873387cea..074ac77945f 100644 --- a/src/main/scala/beam/router/skim/core/AbstractSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/AbstractSkimmer.scala @@ -71,6 +71,21 @@ abstract class AbstractSkimmerReadOnly extends LazyLogging { def aggregatedFromPastSkims: Map[AbstractSkimmerKey, AbstractSkimmerInternal] = aggregatedFromPastSkimsInternal def pastSkims: Map[Int, collection.Map[AbstractSkimmerKey, AbstractSkimmerInternal]] = pastSkimsInternal.toMap + def getSkimValueByKey[T <: AbstractSkimmerKey, InternalKey](key: T): Option[InternalKey] = { + val skimValue = pastSkims + .get(currentIteration - 1) + .flatMap(_.get(key)) + .orElse(aggregatedFromPastSkims.get(key)) + .asInstanceOf[Option[InternalKey]] + + if (skimValue.nonEmpty) { + numberOfSkimValueFound = numberOfSkimValueFound + 1 + } + numberOfRequests = numberOfRequests + 1 + + skimValue + } + def displaySkimStats(): Unit = { logger.info(s"Number of skim requests = $numberOfRequests") logger.info(s"Number of times actual value from skim map was returned = $numberOfSkimValueFound") diff --git a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala new file mode 100644 index 00000000000..e2fbf909b5d --- /dev/null +++ b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala @@ -0,0 +1,156 @@ +package beam.router.skim.core + +import beam.agentsim.agents.vehicles.BeamVehicleType +import beam.agentsim.infrastructure.taz.TAZ +import beam.router.skim.Skims +import beam.router.skim.readonly.ODVehicleTypeSkims +import beam.sim.config.BeamConfig +import beam.utils.matsim_conversion.MatsimPlanConversion.IdOps +import beam.utils.{OutputDataDescriptor, OutputDataDescriptorObject} +import com.google.inject.Inject +import com.typesafe.scalalogging.LazyLogging +import org.apache.commons.lang3.math.NumberUtils +import org.matsim.api.core.v01.Id +import org.matsim.core.controler.MatsimServices + +/** + * @author Dmitry Openkov + */ +class ODVehicleTypeSkimmer @Inject() ( + matsimServices: MatsimServices, + beamConfig: BeamConfig +) extends AbstractSkimmer(beamConfig, matsimServices.getControlerIO) { + import ODVehicleTypeSkimmer._ + override protected[skim] val readOnlySkim = new ODVehicleTypeSkims + override protected val skimFileBaseName: String = ODVehicleTypeSkimmer.fileBaseName + + override protected val skimFileHeader: String = + "hour,vehicleType,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,payloadWeightInKg,energy,observations,iterations" + override protected val skimName: String = ODVehicleTypeSkimmer.name + override protected val skimType: Skims.SkimType.Value = Skims.SkimType.OD_VEHICLE_TYPE_SKIMMER + + override protected def fromCsv( + line: collection.Map[String, String] + ): (AbstractSkimmerKey, AbstractSkimmerInternal) = { + ( + ODVehicleTypeSkimmerKey( + hour = line("hour").toInt, + vehicleType = line("vehicleType").createId, + origin = line("origTaz").createId, + destination = line("destTaz").createId + ), + ODVehicleTypeSkimmerInternal( + travelTimeInS = line("travelTimeInS").toDouble, + generalizedTimeInS = line("generalizedTimeInS").toDouble, + generalizedCost = line("generalizedCost").toDouble, + distanceInM = line("distanceInM").toDouble, + cost = line("cost").toDouble, + energy = Option(line("energy")).map(_.toDouble).getOrElse(0.0), + payloadWeightInKg = line.get("payloadWeightInKg").map(_.toDouble).getOrElse(0.0), + observations = NumberUtils.toInt(line("observations"), 0), + iterations = NumberUtils.toInt(line("iterations"), 1) + ) + ) + } + + override protected def aggregateOverIterations( + prevIteration: Option[AbstractSkimmerInternal], + currIteration: Option[AbstractSkimmerInternal] + ): AbstractSkimmerInternal = + AbstractSkimmer.aggregateOverIterations[ODVehicleTypeSkimmerInternal](prevIteration, currIteration) { agg => + ODVehicleTypeSkimmerInternal( + travelTimeInS = agg.aggregate(_.travelTimeInS), + generalizedTimeInS = agg.aggregate(_.generalizedTimeInS), + generalizedCost = agg.aggregate(_.generalizedCost), + distanceInM = agg.aggregate(_.distanceInM), + cost = agg.aggregate(_.cost), + energy = agg.aggregate(_.energy), + payloadWeightInKg = agg.aggregate(_.payloadWeightInKg), + observations = agg.aggregate(_.observations), + iterations = agg.aggregateObservations + ) + } + + override protected def aggregateWithinIteration( + prevObservation: Option[AbstractSkimmerInternal], + currObservation: AbstractSkimmerInternal + ): AbstractSkimmerInternal = + AbstractSkimmer.aggregateWithinIteration[ODVehicleTypeSkimmerInternal](prevObservation, currObservation) { agg => + ODVehicleTypeSkimmerInternal( + travelTimeInS = agg.aggregate(_.travelTimeInS), + generalizedTimeInS = agg.aggregate(_.generalizedTimeInS), + generalizedCost = agg.aggregate(_.generalizedCost), + distanceInM = agg.aggregate(_.distanceInM), + cost = agg.aggregate(_.cost), + energy = agg.aggregate(_.energy), + payloadWeightInKg = agg.aggregate(_.payloadWeightInKg), + observations = agg.aggregateObservations + ) + } +} + +object ODVehicleTypeSkimmer extends LazyLogging { + val name = "od-vehicle-type-skimmer" + val fileBaseName = "skimsODVehicleType" + + case class ODVehicleTypeSkimmerKey( + hour: Int, + vehicleType: Id[BeamVehicleType], + origin: Id[TAZ], + destination: Id[TAZ] + ) extends AbstractSkimmerKey { + override def toCsv: String = productIterator.mkString(",") + } + + case class ODVehicleTypeSkimmerInternal( + travelTimeInS: Double, + generalizedTimeInS: Double, + generalizedCost: Double, + distanceInM: Double, + cost: Double, + payloadWeightInKg: Double, + energy: Double, + observations: Int = 1, + iterations: Int = 1 + ) extends AbstractSkimmerInternal { + override def toCsv: String = AbstractSkimmer.toCsv(productIterator) + } + + def odVehicleTypeSkimOutputDataDescriptor: OutputDataDescriptor = + OutputDataDescriptorObject("ODVehicleTypeSkimmer", "skimsODVehicleType.csv.gz", iterationLevel = true)( + """ + 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) + """ + ) + + def aggregatedODVehicleTypeSkimOutputDataDescriptor: OutputDataDescriptor = + OutputDataDescriptorObject("ODVehicleTypeSkimmer", "skimsODVehicleType_Aggregated.csv.gz", iterationLevel = true)( + """ + 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 + """ + ) +} diff --git a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala new file mode 100644 index 00000000000..780e85c44f7 --- /dev/null +++ b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala @@ -0,0 +1,88 @@ +package beam.router.skim.event + +import beam.agentsim.agents.vehicles.BeamVehicleType +import beam.agentsim.infrastructure.taz.TAZ +import beam.router.model.EmbodiedBeamTrip +import beam.router.skim.SkimsUtils +import beam.router.skim.core.ODVehicleTypeSkimmer.{ODVehicleTypeSkimmerInternal, ODVehicleTypeSkimmerKey} +import beam.router.skim.core.{AbstractSkimmerEvent, AbstractSkimmerInternal, AbstractSkimmerKey, ODVehicleTypeSkimmer} +import beam.sim.BeamServices +import beam.utils.MathUtils.doubleToInt +import org.matsim.api.core.v01.Id + +/** + * @author Dmitry Openkov + */ +class ODVehicleTypeSkimmerEvent( + eventTime: Double, + origin: Id[TAZ], + destination: Id[TAZ], + vehicleTypeId: Id[BeamVehicleType], + distanceInM: Double, + travelTimeInS: Double, + generalizedTimeInHours: Double, + generalizedCost: Double, + cost: Double, + energyConsumption: Double, + maybePayloadWeightInKg: Option[Double] +) extends AbstractSkimmerEvent(eventTime) { + + override protected val skimName: String = ODVehicleTypeSkimmer.name + + override val getKey: AbstractSkimmerKey = + ODVehicleTypeSkimmerKey(SkimsUtils.timeToBin(doubleToInt(eventTime)), vehicleTypeId, origin, destination) + + override val getSkimmerInternal: AbstractSkimmerInternal = + ODVehicleTypeSkimmerInternal( + travelTimeInS, + generalizedTimeInHours * 3600, + generalizedCost, + distanceInM, + cost, + maybePayloadWeightInKg.getOrElse(0), + energyConsumption + ) +} + +object ODVehicleTypeSkimmerEvent { + + def apply( + eventTime: Double, + beamServices: BeamServices, + vehicleTypeId: Id[BeamVehicleType], + trip: EmbodiedBeamTrip, + generalizedTimeInHours: Double, + generalizedCost: Double, + maybePayloadWeightInKg: Option[Double], + energyConsumption: Double + ): ODVehicleTypeSkimmerEvent = { + import beamServices._ + val correctedTrip = ODSkimmerEvent.correctTrip(trip, trip.tripClassifier) + val beamLegs = correctedTrip.beamLegs + val origLeg = beamLegs.head + val origCoord = geo.wgs2Utm(origLeg.travelPath.startPoint.loc) + val origTaz = beamScenario.tazTreeMap + .getTAZ(origCoord.getX, origCoord.getY) + .tazId + val destLeg = beamLegs.last + val destCoord = geo.wgs2Utm(destLeg.travelPath.endPoint.loc) + val destTaz = beamScenario.tazTreeMap + .getTAZ(destCoord.getX, destCoord.getY) + .tazId + val distanceInM = beamLegs.map(_.travelPath.distanceInM).sum + val travelTime = correctedTrip.totalTravelTimeInSecs.toDouble + new ODVehicleTypeSkimmerEvent( + eventTime, + origTaz, + destTaz, + vehicleTypeId, + if (distanceInM > 0.0) distanceInM else 1.0, + travelTime, + generalizedTimeInHours, + generalizedCost, + correctedTrip.costEstimate, + energyConsumption, + maybePayloadWeightInKg + ) + } +} diff --git a/src/main/scala/beam/router/skim/readonly/FreightSkims.scala b/src/main/scala/beam/router/skim/readonly/FreightSkims.scala index eecce54023c..01d3417628c 100644 --- a/src/main/scala/beam/router/skim/readonly/FreightSkims.scala +++ b/src/main/scala/beam/router/skim/readonly/FreightSkims.scala @@ -16,17 +16,6 @@ class FreightSkims extends AbstractSkimmerReadOnly { ): Option[FreightSkimmerInternal] = { val key = FreightSkimmerKey(tazId, hour) - val getSkimValue = pastSkims - .get(currentIteration - 1) - .flatMap(_.get(key)) - .orElse(aggregatedFromPastSkims.get(key)) - .asInstanceOf[Option[FreightSkimmerInternal]] - - if (getSkimValue.nonEmpty) { - numberOfSkimValueFound = numberOfSkimValueFound + 1 - } - numberOfRequests = numberOfRequests + 1 - - getSkimValue + getSkimValueByKey(key) } } diff --git a/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala b/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala new file mode 100644 index 00000000000..0a8b9d374f4 --- /dev/null +++ b/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala @@ -0,0 +1,25 @@ +package beam.router.skim.readonly + +import beam.agentsim.agents.vehicles.BeamVehicleType +import beam.agentsim.infrastructure.taz.TAZ +import beam.router.skim.SkimsUtils.timeToBin +import beam.router.skim.core.AbstractSkimmerReadOnly +import beam.router.skim.core.ODVehicleTypeSkimmer.{ODVehicleTypeSkimmerInternal, ODVehicleTypeSkimmerKey} +import org.matsim.api.core.v01.Id + +/** + * @author Dmitry Openkov + */ +class ODVehicleTypeSkims extends AbstractSkimmerReadOnly { + + def getSkimValue( + time: Int, + vehicleType: Id[BeamVehicleType], + orig: Id[TAZ], + dest: Id[TAZ] + ): Option[ODVehicleTypeSkimmerInternal] = { + val key = ODVehicleTypeSkimmerKey(timeToBin(time), vehicleType, orig, dest) + + getSkimValueByKey(key) + } +} diff --git a/src/main/scala/beam/router/skim/readonly/ParkingSkims.scala b/src/main/scala/beam/router/skim/readonly/ParkingSkims.scala index c4cfc4b40bb..73851f91ede 100644 --- a/src/main/scala/beam/router/skim/readonly/ParkingSkims.scala +++ b/src/main/scala/beam/router/skim/readonly/ParkingSkims.scala @@ -17,17 +17,6 @@ class ParkingSkims extends AbstractSkimmerReadOnly { ): Option[ParkingSkimmerInternal] = { val key = ParkingSkimmerKey(tazId, hour, chargerType) - val getSkimValue = pastSkims - .get(currentIteration - 1) - .flatMap(_.get(key)) - .orElse(aggregatedFromPastSkims.get(key)) - .asInstanceOf[Option[ParkingSkimmerInternal]] - - if (getSkimValue.nonEmpty) { - numberOfSkimValueFound = numberOfSkimValueFound + 1 - } - numberOfRequests = numberOfRequests + 1 - - getSkimValue + getSkimValueByKey(key) } } diff --git a/src/main/scala/beam/router/skim/readonly/RideHailSkims.scala b/src/main/scala/beam/router/skim/readonly/RideHailSkims.scala index 3c796e3796b..0e3d9ace67f 100644 --- a/src/main/scala/beam/router/skim/readonly/RideHailSkims.scala +++ b/src/main/scala/beam/router/skim/readonly/RideHailSkims.scala @@ -20,17 +20,6 @@ class RideHailSkims extends AbstractSkimmerReadOnly { ): Option[RidehailSkimmerInternal] = { val key = RidehailSkimmerKey(tazId, hour, reservationType, wheelchairRequired, serviceName) - val getSkimValue = pastSkims - .get(currentIteration - 1) - .flatMap(_.get(key)) - .orElse(aggregatedFromPastSkims.get(key)) - .asInstanceOf[Option[RidehailSkimmerInternal]] - - if (getSkimValue.nonEmpty) { - numberOfSkimValueFound = numberOfSkimValueFound + 1 - } - numberOfRequests = numberOfRequests + 1 - - getSkimValue + getSkimValueByKey(key) } } diff --git a/src/main/scala/beam/sim/BeamHelper.scala b/src/main/scala/beam/sim/BeamHelper.scala index 567c33f9da0..340ea8316b8 100755 --- a/src/main/scala/beam/sim/BeamHelper.scala +++ b/src/main/scala/beam/sim/BeamHelper.scala @@ -249,6 +249,7 @@ trait BeamHelper extends LazyLogging with BeamValidationHelper { bind(classOf[DriveTimeSkimmer]).asEagerSingleton() bind(classOf[TransitCrowdingSkimmer]).asEagerSingleton() bind(classOf[RideHailSkimmer]).asEagerSingleton() + bind(classOf[ODVehicleTypeSkimmer]).asEagerSingleton() bind(classOf[FreightSkimmer]).asEagerSingleton() bind(classOf[ParkingSkimmer]).asEagerSingleton() bind(classOf[ActivitySimSkimmer]).asEagerSingleton() diff --git a/src/main/scala/beam/sim/BeamMobsim.scala b/src/main/scala/beam/sim/BeamMobsim.scala index 7c711ddfc80..069419b347b 100755 --- a/src/main/scala/beam/sim/BeamMobsim.scala +++ b/src/main/scala/beam/sim/BeamMobsim.scala @@ -160,6 +160,7 @@ class BeamMobsim @Inject() ( beamServices.skims.od_skimmer.displaySkimStats() beamServices.skims.parking_skimmer.displaySkimStats() beamServices.skims.rh_skimmer.displaySkimStats() + beamServices.skims.od_vehicle_type_skimmer.displaySkimStats() beamServices.skims.freight_skimmer.displaySkimStats() beamServices.skims.taz_skimmer.displaySkimStats() beamServices.skims.dt_skimmer.displaySkimStats() diff --git a/src/main/scala/beam/sim/BeamOutputDataDescriptionGenerator.scala b/src/main/scala/beam/sim/BeamOutputDataDescriptionGenerator.scala index 5c6b36aa1e2..976974a26d6 100644 --- a/src/main/scala/beam/sim/BeamOutputDataDescriptionGenerator.scala +++ b/src/main/scala/beam/sim/BeamOutputDataDescriptionGenerator.scala @@ -137,6 +137,8 @@ object BeamOutputDataDescriptionGenerator { beam.router.skim.core.FreightSkimmer.aggregatedFreightSkimOutputDataDescriptor, beam.router.skim.core.ODSkimmer.odSkimOutputDataDescriptor, beam.router.skim.core.ODSkimmer.aggregatedOdSkimOutputDataDescriptor, + beam.router.skim.core.ODVehicleTypeSkimmer.odVehicleTypeSkimOutputDataDescriptor, + beam.router.skim.core.ODVehicleTypeSkimmer.aggregatedODVehicleTypeSkimOutputDataDescriptor, beam.router.skim.core.ParkingSkimmer.parkingSkimOutputDataDescriptor, beam.router.skim.core.ParkingSkimmer.aggregatedParkingSkimOutputDataDescriptor, beam.router.skim.core.RideHailSkimmer.rideHailSkimOutputDataDescriptor, diff --git a/src/main/scala/beam/sim/BeamSim.scala b/src/main/scala/beam/sim/BeamSim.scala index ae144c9a9fa..f18d77b2086 100755 --- a/src/main/scala/beam/sim/BeamSim.scala +++ b/src/main/scala/beam/sim/BeamSim.scala @@ -339,6 +339,7 @@ class BeamSim @Inject() ( beamServices.skims.parking_skimmer.resetSkimStats() beamServices.skims.od_skimmer.resetSkimStats() beamServices.skims.rh_skimmer.resetSkimStats() + beamServices.skims.od_vehicle_type_skimmer.resetSkimStats() beamServices.skims.freight_skimmer.resetSkimStats() beamServices.skims.taz_skimmer.resetSkimStats() beamServices.skims.dt_skimmer.resetSkimStats() @@ -550,6 +551,7 @@ class BeamSim @Inject() ( beamServices.skims.parking_skimmer.displaySkimStats() beamServices.skims.od_skimmer.displaySkimStats() beamServices.skims.rh_skimmer.displaySkimStats() + beamServices.skims.od_vehicle_type_skimmer.displaySkimStats() beamServices.skims.freight_skimmer.displaySkimStats() beamServices.skims.taz_skimmer.displaySkimStats() beamServices.skims.dt_skimmer.displaySkimStats() From 024a957c19fd04d9e95cc30fd130d500821a8969 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Fri, 21 Jun 2024 14:49:09 +0300 Subject: [PATCH 03/10] Fixed regular toString representation of TAZ --- src/main/scala/beam/agentsim/agents/PersonAgent.scala | 4 ++-- .../beam/agentsim/agents/modalbehaviors/ChoosesMode.scala | 4 ++-- src/main/scala/beam/agentsim/infrastructure/taz/TAZ.scala | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala index 2370dc53c8a..5693c453554 100755 --- a/src/main/scala/beam/agentsim/agents/PersonAgent.scala +++ b/src/main/scala/beam/agentsim/agents/PersonAgent.scala @@ -1563,8 +1563,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) diff --git a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala index 150c16de8c0..3c2d7d47aa7 100755 --- a/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala +++ b/src/main/scala/beam/agentsim/agents/modalbehaviors/ChoosesMode.scala @@ -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 ) } diff --git a/src/main/scala/beam/agentsim/infrastructure/taz/TAZ.scala b/src/main/scala/beam/agentsim/infrastructure/taz/TAZ.scala index 7086f4c0066..bb362e1088f 100644 --- a/src/main/scala/beam/agentsim/infrastructure/taz/TAZ.scala +++ b/src/main/scala/beam/agentsim/infrastructure/taz/TAZ.scala @@ -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 { From 95499227754149a3fefe7c01d6f040687bc33c0f Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Fri, 21 Jun 2024 17:36:41 +0300 Subject: [PATCH 04/10] Fixed BeamWarmStartRunSpec(prepare WarmStart data) --- src/test/scala/beam/sim/BeamWarmStartRunSpec.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/scala/beam/sim/BeamWarmStartRunSpec.scala b/src/test/scala/beam/sim/BeamWarmStartRunSpec.scala index 32faf06c211..d00e455d273 100644 --- a/src/test/scala/beam/sim/BeamWarmStartRunSpec.scala +++ b/src/test/scala/beam/sim/BeamWarmStartRunSpec.scala @@ -50,6 +50,7 @@ class BeamWarmStartRunSpec "ITERS/it.2/2.skimsTAZ_Aggregated.csv.gz", "ITERS/it.2/2.skimsTravelTimeObservedVsSimulated_Aggregated.csv.gz", "ITERS/it.2/2.skimsRidehail_Aggregated.csv.gz", + "ITERS/it.2/2.skimsODVehicleType_Aggregated.csv.gz", "ITERS/it.2/2.skimsFreight_Aggregated.csv.gz", "ITERS/it.2/2.skimsParking_Aggregated.csv.gz", "ITERS/it.2/2.skimsTransitCrowding_Aggregated.csv.gz", From 6eaace3996cd768b875a42984fea1c8f788f324f Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Thu, 27 Jun 2024 18:18:39 +0300 Subject: [PATCH 05/10] Multiple types of the key on ODVehicleTypeSkimmer --- src/main/resources/beam-template.conf | 6 + .../skim/core/ODVehicleTypeSkimmer.scala | 125 ++++++++++++++++-- .../event/ODVehicleTypeSkimmerEvent.scala | 18 ++- .../skim/readonly/ODVehicleTypeSkims.scala | 11 +- .../scala/beam/sim/config/BeamConfig.scala | 23 +++- 5 files changed, 162 insertions(+), 21 deletions(-) diff --git a/src/main/resources/beam-template.conf b/src/main/resources/beam-template.conf index 56084073d2a..dcde706dd26 100755 --- a/src/main/resources/beam-template.conf +++ b/src/main/resources/beam-template.conf @@ -770,6 +770,12 @@ beam.router.skim = { name = "transit-crowding-skimmer" fileBaseName = "String | skimsTransitCrowding" } + origin-destination-vehicle-type-skimmer { + # Freight, Passenger or Freight & Passenger + tripType = "Freight" + # VehicleTypeId, VehicleCategory, or VehicleCategory+Powertrain + origin-destination-vehicle-type-skimmer.vehicleTypeKey = "VehicleTypeId" + } } #h3taz diff --git a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala index e2fbf909b5d..7351e609d3e 100644 --- a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala @@ -1,6 +1,8 @@ package beam.router.skim.core -import beam.agentsim.agents.vehicles.BeamVehicleType +import beam.agentsim.agents.vehicles.FuelType.FuelType +import beam.agentsim.agents.vehicles.VehicleCategory.VehicleCategory +import beam.agentsim.agents.vehicles.{BeamVehicleType, FuelType, VehicleCategory} import beam.agentsim.infrastructure.taz.TAZ import beam.router.skim.Skims import beam.router.skim.readonly.ODVehicleTypeSkims @@ -9,9 +11,13 @@ import beam.utils.matsim_conversion.MatsimPlanConversion.IdOps import beam.utils.{OutputDataDescriptor, OutputDataDescriptorObject} import com.google.inject.Inject import com.typesafe.scalalogging.LazyLogging +import enumeratum.{Enum, EnumEntry} import org.apache.commons.lang3.math.NumberUtils import org.matsim.api.core.v01.Id import org.matsim.core.controler.MatsimServices +import shapeless.{:+:, CNil, Coproduct, Poly1} + +import scala.collection.immutable /** * @author Dmitry Openkov @@ -19,13 +25,30 @@ import org.matsim.core.controler.MatsimServices class ODVehicleTypeSkimmer @Inject() ( matsimServices: MatsimServices, beamConfig: BeamConfig -) extends AbstractSkimmer(beamConfig, matsimServices.getControlerIO) { +) extends AbstractSkimmer(beamConfig, matsimServices.getControlerIO) + with LazyLogging { import ODVehicleTypeSkimmer._ override protected[skim] val readOnlySkim = new ODVehicleTypeSkims override protected val skimFileBaseName: String = ODVehicleTypeSkimmer.fileBaseName - override protected val skimFileHeader: String = - "hour,vehicleType,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,payloadWeightInKg,energy,observations,iterations" + private val vehicleTypeKey = VehicleTypeKey + .fromString(beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleTypeKey) + .getOrElse { + logger.error( + "Unknown vehicleTypeKey '{}', using VehicleTypeId as a key", + beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleTypeKey + ) + VehicleTypeKey.VehicleTypeIdKey + } + + override protected val skimFileHeader: String = { + val vehicleTypeKeyHeader = vehicleTypeKey match { + case VehicleTypeKey.VehicleTypeIdKey => "vehicleType" + case VehicleTypeKey.VehicleCategoryKey => "vehicleCategory" + case VehicleTypeKey.VehicleCategoryPlusPowertrainKey => "vehicleCategory,primaryFuelType,secondaryFuelType" + } + s"hour,$vehicleTypeKeyHeader,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,payloadWeightInKg,energy,observations,iterations" + } override protected val skimName: String = ODVehicleTypeSkimmer.name override protected val skimType: Skims.SkimType.Value = Skims.SkimType.OD_VEHICLE_TYPE_SKIMMER @@ -33,12 +56,29 @@ class ODVehicleTypeSkimmer @Inject() ( line: collection.Map[String, String] ): (AbstractSkimmerKey, AbstractSkimmerInternal) = { ( - ODVehicleTypeSkimmerKey( - hour = line("hour").toInt, - vehicleType = line("vehicleType").createId, - origin = line("origTaz").createId, - destination = line("destTaz").createId - ), + { + val vehicleTypePart: VehicleTypePart = + vehicleTypeKey match { + case VehicleTypeKey.VehicleTypeIdKey => + Coproduct[VehicleTypePart](line("vehicleType").createId[BeamVehicleType]) + case VehicleTypeKey.VehicleCategoryKey => + Coproduct[VehicleTypePart](VehicleCategory.fromString(line("vehicleCategory"))) + case VehicleTypeKey.VehicleCategoryPlusPowertrainKey => + Coproduct[VehicleTypePart]( + VehicleCategoryPlusPowertrain( + vehicleCategory = VehicleCategory.fromString(line("vehicleCategory")), + primaryFuelType = FuelType.fromString(line("primaryFuelType")), + secondaryFuelType = FuelType.fromString(line("secondaryFuelType")) + ) + ) + } + ODVehicleTypeSkimmerKey( + hour = line("hour").toInt, + vehicleTypePart = vehicleTypePart, + origin = line("origTaz").createId, + destination = line("destTaz").createId + ) + }, ODVehicleTypeSkimmerInternal( travelTimeInS = line("travelTimeInS").toDouble, generalizedTimeInS = line("generalizedTimeInS").toDouble, @@ -92,14 +132,75 @@ class ODVehicleTypeSkimmer @Inject() ( object ODVehicleTypeSkimmer extends LazyLogging { val name = "od-vehicle-type-skimmer" val fileBaseName = "skimsODVehicleType" + sealed abstract class VehicleTypeKey extends EnumEntry + + object VehicleTypeKey extends Enum[VehicleTypeKey] { + val values: immutable.IndexedSeq[VehicleTypeKey] = findValues + + case object VehicleTypeIdKey extends VehicleTypeKey + case object VehicleCategoryKey extends VehicleTypeKey + case object VehicleCategoryPlusPowertrainKey extends VehicleTypeKey + + def fromString(value: String): Option[VehicleTypeKey] = { + value.toLowerCase() match { + case x if x.contains("category") && x.contains("powertrain") => Some(VehicleCategoryPlusPowertrainKey) + case x if x.contains("category") => Some(VehicleCategoryKey) + case x if x.contains("type") => Some(VehicleTypeIdKey) + case _ => None + } + } + } + + case class VehicleCategoryPlusPowertrain( + vehicleCategory: VehicleCategory, + primaryFuelType: FuelType, + secondaryFuelType: FuelType + ) { + def this(vehicleType: BeamVehicleType) = + this( + vehicleType.vehicleCategory, + vehicleType.primaryFuelType, + vehicleType.secondaryFuelType.getOrElse(FuelType.Undefined) + ) + + override def toString: String = productIterator.mkString(",") + } + + // we use shapeless Coproduct to emulate "union types" (implemented in Scala 3). + // It is somewhat similar to Either but for unlimited number of types. + type VehicleTypePart = Id[BeamVehicleType] :+: VehicleCategory :+: VehicleCategoryPlusPowertrain :+: CNil + + object VehicleTypePart { + + def apply(vehicleTypeKey: VehicleTypeKey, vehicleType: BeamVehicleType): VehicleTypePart = + vehicleTypeKey match { + case VehicleTypeKey.VehicleTypeIdKey => + Coproduct[VehicleTypePart](vehicleType.id) + case VehicleTypeKey.VehicleCategoryKey => + Coproduct[VehicleTypePart](vehicleType.vehicleCategory) + case VehicleTypeKey.VehicleCategoryPlusPowertrainKey => + Coproduct[VehicleTypePart](new VehicleCategoryPlusPowertrain(vehicleType)) + } + + private object toStringHandler extends Poly1 { + implicit def vehicleTypeId = at[Id[BeamVehicleType]](_.toString) + implicit def vehicleCategory = at[VehicleCategory](_.toString) + implicit def vehicleCategoryPlusPowertrain = at[VehicleCategoryPlusPowertrain](_.toString) + } + + def toString(vehicleTypePart: VehicleTypePart): String = vehicleTypePart.fold(toStringHandler) + + } case class ODVehicleTypeSkimmerKey( hour: Int, - vehicleType: Id[BeamVehicleType], + vehicleTypePart: VehicleTypePart, origin: Id[TAZ], destination: Id[TAZ] ) extends AbstractSkimmerKey { - override def toCsv: String = productIterator.mkString(",") + + override def toCsv: String = s"$hour,${VehicleTypePart.toString(vehicleTypePart)},$origin,$destination" + } case class ODVehicleTypeSkimmerInternal( diff --git a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala index 780e85c44f7..fe29762cd10 100644 --- a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala +++ b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala @@ -4,7 +4,12 @@ import beam.agentsim.agents.vehicles.BeamVehicleType import beam.agentsim.infrastructure.taz.TAZ import beam.router.model.EmbodiedBeamTrip import beam.router.skim.SkimsUtils -import beam.router.skim.core.ODVehicleTypeSkimmer.{ODVehicleTypeSkimmerInternal, ODVehicleTypeSkimmerKey} +import beam.router.skim.core.ODVehicleTypeSkimmer.{ + ODVehicleTypeSkimmerInternal, + ODVehicleTypeSkimmerKey, + VehicleTypeKey, + VehicleTypePart +} import beam.router.skim.core.{AbstractSkimmerEvent, AbstractSkimmerInternal, AbstractSkimmerKey, ODVehicleTypeSkimmer} import beam.sim.BeamServices import beam.utils.MathUtils.doubleToInt @@ -17,7 +22,7 @@ class ODVehicleTypeSkimmerEvent( eventTime: Double, origin: Id[TAZ], destination: Id[TAZ], - vehicleTypeId: Id[BeamVehicleType], + vehicleTypePart: VehicleTypePart, distanceInM: Double, travelTimeInS: Double, generalizedTimeInHours: Double, @@ -30,7 +35,7 @@ class ODVehicleTypeSkimmerEvent( override protected val skimName: String = ODVehicleTypeSkimmer.name override val getKey: AbstractSkimmerKey = - ODVehicleTypeSkimmerKey(SkimsUtils.timeToBin(doubleToInt(eventTime)), vehicleTypeId, origin, destination) + ODVehicleTypeSkimmerKey(SkimsUtils.timeToBin(doubleToInt(eventTime)), vehicleTypePart, origin, destination) override val getSkimmerInternal: AbstractSkimmerInternal = ODVehicleTypeSkimmerInternal( @@ -71,11 +76,16 @@ object ODVehicleTypeSkimmerEvent { .tazId val distanceInM = beamLegs.map(_.travelPath.distanceInM).sum val travelTime = correctedTrip.totalTravelTimeInSecs.toDouble + val vehicleTypeKey = VehicleTypeKey + .fromString(beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleTypeKey) + .getOrElse(VehicleTypeKey.VehicleTypeIdKey) + val vehicleType = beamScenario.vehicleTypes(vehicleTypeId) + val vehicleTypePart = VehicleTypePart(vehicleTypeKey, vehicleType) new ODVehicleTypeSkimmerEvent( eventTime, origTaz, destTaz, - vehicleTypeId, + vehicleTypePart, if (distanceInM > 0.0) distanceInM else 1.0, travelTime, generalizedTimeInHours, diff --git a/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala b/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala index 0a8b9d374f4..dc44f944c45 100644 --- a/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala +++ b/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala @@ -1,10 +1,13 @@ package beam.router.skim.readonly -import beam.agentsim.agents.vehicles.BeamVehicleType import beam.agentsim.infrastructure.taz.TAZ import beam.router.skim.SkimsUtils.timeToBin import beam.router.skim.core.AbstractSkimmerReadOnly -import beam.router.skim.core.ODVehicleTypeSkimmer.{ODVehicleTypeSkimmerInternal, ODVehicleTypeSkimmerKey} +import beam.router.skim.core.ODVehicleTypeSkimmer.{ + ODVehicleTypeSkimmerInternal, + ODVehicleTypeSkimmerKey, + VehicleTypePart +} import org.matsim.api.core.v01.Id /** @@ -14,11 +17,11 @@ class ODVehicleTypeSkims extends AbstractSkimmerReadOnly { def getSkimValue( time: Int, - vehicleType: Id[BeamVehicleType], + vehicleTypePart: VehicleTypePart, orig: Id[TAZ], dest: Id[TAZ] ): Option[ODVehicleTypeSkimmerInternal] = { - val key = ODVehicleTypeSkimmerKey(timeToBin(time), vehicleType, orig, dest) + val key: ODVehicleTypeSkimmerKey = ODVehicleTypeSkimmerKey(timeToBin(time), vehicleTypePart, orig, dest) getSkimValueByKey(key) } diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index 7c581fe21b5..8501ac74196 100644 --- a/src/main/scala/beam/sim/config/BeamConfig.scala +++ b/src/main/scala/beam/sim/config/BeamConfig.scala @@ -3143,7 +3143,7 @@ object BeamConfig { iterationScripts = if (c.hasPathOrNull("iterationScripts")) scala.Some($_L$_str(c.getList("iterationScripts"))) else None, processWaitTimeInMinutes = - if (c.hasPathOrNull("processWaitTimeInMinutes")) c.getInt("processWaitTimeInMinutes") else 5, + if (c.hasPathOrNull("processWaitTimeInMinutes")) c.getInt("processWaitTimeInMinutes") else 60, simulationScripts = if (c.hasPathOrNull("simulationScripts")) scala.Some($_L$_str(c.getList("simulationScripts"))) else None ) @@ -4255,6 +4255,7 @@ object BeamConfig { drive_time_skimmer: BeamConfig.Beam.Router.Skim.DriveTimeSkimmer, keepKLatestSkims: scala.Int, origin_destination_skimmer: BeamConfig.Beam.Router.Skim.OriginDestinationSkimmer, + origin_destination_vehicle_type_skimmer: BeamConfig.Beam.Router.Skim.OriginDestinationVehicleTypeSkimmer, sendNonChosenTripsToSkimmer: scala.Boolean, taz_skimmer: BeamConfig.Beam.Router.Skim.TazSkimmer, transit_crowding_skimmer: BeamConfig.Beam.Router.Skim.TransitCrowdingSkimmer, @@ -4325,6 +4326,21 @@ object BeamConfig { } } + case class OriginDestinationVehicleTypeSkimmer( + tripType: java.lang.String, + vehicleTypeKey: java.lang.String + ) + + object OriginDestinationVehicleTypeSkimmer { + + def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Router.Skim.OriginDestinationVehicleTypeSkimmer = { + BeamConfig.Beam.Router.Skim.OriginDestinationVehicleTypeSkimmer( + tripType = if (c.hasPathOrNull("tripType")) c.getString("tripType") else "Freight", + vehicleTypeKey = if (c.hasPathOrNull("vehicleTypeKey")) c.getString("vehicleTypeKey") else "VehicleTypeId" + ) + } + } + case class TazSkimmer( fileBaseName: java.lang.String, geoHierarchy: java.lang.String, @@ -4373,6 +4389,11 @@ object BeamConfig { if (c.hasPathOrNull("origin-destination-skimmer")) c.getConfig("origin-destination-skimmer") else com.typesafe.config.ConfigFactory.parseString("origin-destination-skimmer{}") ), + origin_destination_vehicle_type_skimmer = BeamConfig.Beam.Router.Skim.OriginDestinationVehicleTypeSkimmer( + if (c.hasPathOrNull("origin-destination-vehicle-type-skimmer")) + c.getConfig("origin-destination-vehicle-type-skimmer") + else com.typesafe.config.ConfigFactory.parseString("origin-destination-vehicle-type-skimmer{}") + ), sendNonChosenTripsToSkimmer = !c.hasPathOrNull("sendNonChosenTripsToSkimmer") || c.getBoolean("sendNonChosenTripsToSkimmer"), taz_skimmer = BeamConfig.Beam.Router.Skim.TazSkimmer( From ad18ccc3d6816284a182bc81c76eb4290b72b8d3 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Tue, 2 Jul 2024 20:22:18 +0300 Subject: [PATCH 06/10] Generate ODVehicleType Skims for each leg. --- src/main/resources/beam-template.conf | 6 +- .../beam/agentsim/agents/PersonAgent.scala | 72 +++++++++++++++---- src/main/scala/beam/router/skim/Skims.scala | 18 ++--- .../skim/core/ODVehicleTypeSkimmer.scala | 9 ++- .../event/ODVehicleTypeSkimmerEvent.scala | 15 ++-- .../scala/beam/sim/config/BeamConfig.scala | 7 +- 6 files changed, 86 insertions(+), 41 deletions(-) diff --git a/src/main/resources/beam-template.conf b/src/main/resources/beam-template.conf index dcde706dd26..d919d631805 100755 --- a/src/main/resources/beam-template.conf +++ b/src/main/resources/beam-template.conf @@ -771,10 +771,10 @@ beam.router.skim = { fileBaseName = "String | skimsTransitCrowding" } origin-destination-vehicle-type-skimmer { - # Freight, Passenger or Freight & Passenger - tripType = "Freight" + # Comma separated list of VehicleCategories that the skim is generated for + vehicleCategories = "Car" # VehicleTypeId, VehicleCategory, or VehicleCategory+Powertrain - origin-destination-vehicle-type-skimmer.vehicleTypeKey = "VehicleTypeId" + vehicleTypeKey = "VehicleCategory+Powertrain" } } diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala index 5693c453554..64f2c227fad 100755 --- a/src/main/scala/beam/agentsim/agents/PersonAgent.scala +++ b/src/main/scala/beam/agentsim/agents/PersonAgent.scala @@ -202,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 @@ -1134,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. @@ -1624,20 +1631,55 @@ class PersonAgent( correctedTrip.legs.filter(x => x.beamLeg.mode == BeamMode.CAR || x.beamLeg.mode == BeamMode.CAV).foreach { carLeg => eventsManager.processEvent(DriveTimeSkimmerEvent(tick, beamServices, carLeg)) } - if (!failedTrip && correctedTrip.tripClassifier == BeamMode.CAR) { - val vehicleTypeId = correctedTrip.legs.find(_.beamLeg.mode == CAR).get.beamVehicleTypeId - val odVehicleTypeEvent = ODVehicleTypeSkimmerEvent( - tick, - beamServices, - vehicleTypeId, - correctedTrip, - generalizedTime, - generalizedCost, - maybePayloadWeightInKg, - curFuelConsumed.totalEnergyConsumed - ) - eventsManager.processEvent(odVehicleTypeEvent) + } + + 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] = { diff --git a/src/main/scala/beam/router/skim/Skims.scala b/src/main/scala/beam/router/skim/Skims.scala index 68ce586a222..1276a214f24 100644 --- a/src/main/scala/beam/router/skim/Skims.scala +++ b/src/main/scala/beam/router/skim/Skims.scala @@ -14,15 +14,15 @@ import scala.collection.mutable class Skims @Inject() ( matsimServices: MatsimServices, - odSkimmer: ODSkimmer, - tazSkimmer: TAZSkimmer, - driveTimeSkimmer: DriveTimeSkimmer, - transitCrowdingSkimmer: TransitCrowdingSkimmer, - rideHailSkimmer: RideHailSkimmer, - odVehicleTypeSkimmer: ODVehicleTypeSkimmer, - freightSkimmer: FreightSkimmer, - parkingSkimmer: ParkingSkimmer, - asSkimmer: ActivitySimSkimmer + val odSkimmer: ODSkimmer, + val tazSkimmer: TAZSkimmer, + val driveTimeSkimmer: DriveTimeSkimmer, + val transitCrowdingSkimmer: TransitCrowdingSkimmer, + val rideHailSkimmer: RideHailSkimmer, + val odVehicleTypeSkimmer: ODVehicleTypeSkimmer, + val freightSkimmer: FreightSkimmer, + val parkingSkimmer: ParkingSkimmer, + val asSkimmer: ActivitySimSkimmer ) extends LazyLogging { import Skims.SkimType diff --git a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala index 7351e609d3e..dfac0d3a2c4 100644 --- a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala @@ -31,7 +31,7 @@ class ODVehicleTypeSkimmer @Inject() ( override protected[skim] val readOnlySkim = new ODVehicleTypeSkims override protected val skimFileBaseName: String = ODVehicleTypeSkimmer.fileBaseName - private val vehicleTypeKey = VehicleTypeKey + val vehicleTypeKey: VehicleTypeKey = VehicleTypeKey .fromString(beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleTypeKey) .getOrElse { logger.error( @@ -41,6 +41,13 @@ class ODVehicleTypeSkimmer @Inject() ( VehicleTypeKey.VehicleTypeIdKey } + val vehicleCategoriesToGenerateSkim: IndexedSeq[VehicleCategory] = + beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleCategories + .split(',') + .map(_.trim) + .map(VehicleCategory.fromString) + .toIndexedSeq + override protected val skimFileHeader: String = { val vehicleTypeKeyHeader = vehicleTypeKey match { case VehicleTypeKey.VehicleTypeIdKey => "vehicleType" diff --git a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala index fe29762cd10..c3160b5948f 100644 --- a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala +++ b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala @@ -54,7 +54,7 @@ object ODVehicleTypeSkimmerEvent { def apply( eventTime: Double, beamServices: BeamServices, - vehicleTypeId: Id[BeamVehicleType], + vehicleType: BeamVehicleType, trip: EmbodiedBeamTrip, generalizedTimeInHours: Double, generalizedCost: Double, @@ -62,8 +62,7 @@ object ODVehicleTypeSkimmerEvent { energyConsumption: Double ): ODVehicleTypeSkimmerEvent = { import beamServices._ - val correctedTrip = ODSkimmerEvent.correctTrip(trip, trip.tripClassifier) - val beamLegs = correctedTrip.beamLegs + val beamLegs = trip.beamLegs val origLeg = beamLegs.head val origCoord = geo.wgs2Utm(origLeg.travelPath.startPoint.loc) val origTaz = beamScenario.tazTreeMap @@ -75,12 +74,8 @@ object ODVehicleTypeSkimmerEvent { .getTAZ(destCoord.getX, destCoord.getY) .tazId val distanceInM = beamLegs.map(_.travelPath.distanceInM).sum - val travelTime = correctedTrip.totalTravelTimeInSecs.toDouble - val vehicleTypeKey = VehicleTypeKey - .fromString(beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleTypeKey) - .getOrElse(VehicleTypeKey.VehicleTypeIdKey) - val vehicleType = beamScenario.vehicleTypes(vehicleTypeId) - val vehicleTypePart = VehicleTypePart(vehicleTypeKey, vehicleType) + val travelTime = trip.totalTravelTimeInSecs.toDouble + val vehicleTypePart = VehicleTypePart(skims.odVehicleTypeSkimmer.vehicleTypeKey, vehicleType) new ODVehicleTypeSkimmerEvent( eventTime, origTaz, @@ -90,7 +85,7 @@ object ODVehicleTypeSkimmerEvent { travelTime, generalizedTimeInHours, generalizedCost, - correctedTrip.costEstimate, + trip.costEstimate, energyConsumption, maybePayloadWeightInKg ) diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index 8501ac74196..798856f9e80 100644 --- a/src/main/scala/beam/sim/config/BeamConfig.scala +++ b/src/main/scala/beam/sim/config/BeamConfig.scala @@ -4327,7 +4327,7 @@ object BeamConfig { } case class OriginDestinationVehicleTypeSkimmer( - tripType: java.lang.String, + vehicleCategories: java.lang.String, vehicleTypeKey: java.lang.String ) @@ -4335,8 +4335,9 @@ object BeamConfig { def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Router.Skim.OriginDestinationVehicleTypeSkimmer = { BeamConfig.Beam.Router.Skim.OriginDestinationVehicleTypeSkimmer( - tripType = if (c.hasPathOrNull("tripType")) c.getString("tripType") else "Freight", - vehicleTypeKey = if (c.hasPathOrNull("vehicleTypeKey")) c.getString("vehicleTypeKey") else "VehicleTypeId" + vehicleCategories = if (c.hasPathOrNull("vehicleCategories")) c.getString("vehicleCategories") else "Car", + vehicleTypeKey = + if (c.hasPathOrNull("vehicleTypeKey")) c.getString("vehicleTypeKey") else "VehicleCategory+Powertrain" ) } } From c0be669604c216ed97ed4b21e9999c39931bf49c Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Mon, 8 Jul 2024 16:40:35 +0300 Subject: [PATCH 07/10] Removed VehicleTypeKey from ODVehicleTypeSkimmer because we only need this type of key. Correct order of values in the skim event. --- src/main/resources/beam-template.conf | 2 - .../skim/core/ODVehicleTypeSkimmer.scala | 156 ++++-------------- .../event/ODVehicleTypeSkimmerEvent.scala | 35 ++-- .../skim/readonly/ODVehicleTypeSkims.scala | 15 +- .../scala/beam/sim/config/BeamConfig.scala | 7 +- 5 files changed, 64 insertions(+), 151 deletions(-) diff --git a/src/main/resources/beam-template.conf b/src/main/resources/beam-template.conf index d919d631805..20f85e25194 100755 --- a/src/main/resources/beam-template.conf +++ b/src/main/resources/beam-template.conf @@ -773,8 +773,6 @@ beam.router.skim = { origin-destination-vehicle-type-skimmer { # Comma separated list of VehicleCategories that the skim is generated for vehicleCategories = "Car" - # VehicleTypeId, VehicleCategory, or VehicleCategory+Powertrain - vehicleTypeKey = "VehicleCategory+Powertrain" } } diff --git a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala index dfac0d3a2c4..64b5af20f80 100644 --- a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala @@ -2,7 +2,7 @@ package beam.router.skim.core import beam.agentsim.agents.vehicles.FuelType.FuelType import beam.agentsim.agents.vehicles.VehicleCategory.VehicleCategory -import beam.agentsim.agents.vehicles.{BeamVehicleType, FuelType, VehicleCategory} +import beam.agentsim.agents.vehicles.{FuelType, VehicleCategory} import beam.agentsim.infrastructure.taz.TAZ import beam.router.skim.Skims import beam.router.skim.readonly.ODVehicleTypeSkims @@ -10,14 +10,9 @@ import beam.sim.config.BeamConfig import beam.utils.matsim_conversion.MatsimPlanConversion.IdOps import beam.utils.{OutputDataDescriptor, OutputDataDescriptorObject} import com.google.inject.Inject -import com.typesafe.scalalogging.LazyLogging -import enumeratum.{Enum, EnumEntry} import org.apache.commons.lang3.math.NumberUtils import org.matsim.api.core.v01.Id import org.matsim.core.controler.MatsimServices -import shapeless.{:+:, CNil, Coproduct, Poly1} - -import scala.collection.immutable /** * @author Dmitry Openkov @@ -25,22 +20,11 @@ import scala.collection.immutable class ODVehicleTypeSkimmer @Inject() ( matsimServices: MatsimServices, beamConfig: BeamConfig -) extends AbstractSkimmer(beamConfig, matsimServices.getControlerIO) - with LazyLogging { +) extends AbstractSkimmer(beamConfig, matsimServices.getControlerIO) { import ODVehicleTypeSkimmer._ override protected[skim] val readOnlySkim = new ODVehicleTypeSkims override protected val skimFileBaseName: String = ODVehicleTypeSkimmer.fileBaseName - val vehicleTypeKey: VehicleTypeKey = VehicleTypeKey - .fromString(beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleTypeKey) - .getOrElse { - logger.error( - "Unknown vehicleTypeKey '{}', using VehicleTypeId as a key", - beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleTypeKey - ) - VehicleTypeKey.VehicleTypeIdKey - } - val vehicleCategoriesToGenerateSkim: IndexedSeq[VehicleCategory] = beamConfig.beam.router.skim.origin_destination_vehicle_type_skimmer.vehicleCategories .split(',') @@ -48,14 +32,8 @@ class ODVehicleTypeSkimmer @Inject() ( .map(VehicleCategory.fromString) .toIndexedSeq - override protected val skimFileHeader: String = { - val vehicleTypeKeyHeader = vehicleTypeKey match { - case VehicleTypeKey.VehicleTypeIdKey => "vehicleType" - case VehicleTypeKey.VehicleCategoryKey => "vehicleCategory" - case VehicleTypeKey.VehicleCategoryPlusPowertrainKey => "vehicleCategory,primaryFuelType,secondaryFuelType" - } - s"hour,$vehicleTypeKeyHeader,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,payloadWeightInKg,energy,observations,iterations" - } + override protected val skimFileHeader: String = + "hour,vehicleCategory,primaryFuelType,secondaryFuelType,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,payloadWeightInKg,energy,observations,iterations" override protected val skimName: String = ODVehicleTypeSkimmer.name override protected val skimType: Skims.SkimType.Value = Skims.SkimType.OD_VEHICLE_TYPE_SKIMMER @@ -63,37 +41,22 @@ class ODVehicleTypeSkimmer @Inject() ( line: collection.Map[String, String] ): (AbstractSkimmerKey, AbstractSkimmerInternal) = { ( - { - val vehicleTypePart: VehicleTypePart = - vehicleTypeKey match { - case VehicleTypeKey.VehicleTypeIdKey => - Coproduct[VehicleTypePart](line("vehicleType").createId[BeamVehicleType]) - case VehicleTypeKey.VehicleCategoryKey => - Coproduct[VehicleTypePart](VehicleCategory.fromString(line("vehicleCategory"))) - case VehicleTypeKey.VehicleCategoryPlusPowertrainKey => - Coproduct[VehicleTypePart]( - VehicleCategoryPlusPowertrain( - vehicleCategory = VehicleCategory.fromString(line("vehicleCategory")), - primaryFuelType = FuelType.fromString(line("primaryFuelType")), - secondaryFuelType = FuelType.fromString(line("secondaryFuelType")) - ) - ) - } - ODVehicleTypeSkimmerKey( - hour = line("hour").toInt, - vehicleTypePart = vehicleTypePart, - origin = line("origTaz").createId, - destination = line("destTaz").createId - ) - }, + ODVehicleTypeSkimmerKey( + hour = line("hour").toInt, + vehicleCategory = VehicleCategory.fromString(line("vehicleCategory")), + primaryFuelType = FuelType.fromString(line("primaryFuelType")), + secondaryFuelType = FuelType.fromString(line("secondaryFuelType")), + origin = line("origTaz").createId, + destination = line("destTaz").createId + ), ODVehicleTypeSkimmerInternal( travelTimeInS = line("travelTimeInS").toDouble, generalizedTimeInS = line("generalizedTimeInS").toDouble, + cost = line("cost").toDouble, generalizedCost = line("generalizedCost").toDouble, distanceInM = line("distanceInM").toDouble, - cost = line("cost").toDouble, - energy = Option(line("energy")).map(_.toDouble).getOrElse(0.0), payloadWeightInKg = line.get("payloadWeightInKg").map(_.toDouble).getOrElse(0.0), + energy = Option(line("energy")).map(_.toDouble).getOrElse(0.0), observations = NumberUtils.toInt(line("observations"), 0), iterations = NumberUtils.toInt(line("iterations"), 1) ) @@ -108,11 +71,11 @@ class ODVehicleTypeSkimmer @Inject() ( ODVehicleTypeSkimmerInternal( travelTimeInS = agg.aggregate(_.travelTimeInS), generalizedTimeInS = agg.aggregate(_.generalizedTimeInS), + cost = agg.aggregate(_.cost), generalizedCost = agg.aggregate(_.generalizedCost), distanceInM = agg.aggregate(_.distanceInM), - cost = agg.aggregate(_.cost), - energy = agg.aggregate(_.energy), payloadWeightInKg = agg.aggregate(_.payloadWeightInKg), + energy = agg.aggregate(_.energy), observations = agg.aggregate(_.observations), iterations = agg.aggregateObservations ) @@ -126,109 +89,54 @@ class ODVehicleTypeSkimmer @Inject() ( ODVehicleTypeSkimmerInternal( travelTimeInS = agg.aggregate(_.travelTimeInS), generalizedTimeInS = agg.aggregate(_.generalizedTimeInS), + cost = agg.aggregate(_.cost), generalizedCost = agg.aggregate(_.generalizedCost), distanceInM = agg.aggregate(_.distanceInM), - cost = agg.aggregate(_.cost), - energy = agg.aggregate(_.energy), payloadWeightInKg = agg.aggregate(_.payloadWeightInKg), + energy = agg.aggregate(_.energy), observations = agg.aggregateObservations ) } } -object ODVehicleTypeSkimmer extends LazyLogging { +object ODVehicleTypeSkimmer { val name = "od-vehicle-type-skimmer" val fileBaseName = "skimsODVehicleType" - sealed abstract class VehicleTypeKey extends EnumEntry - - object VehicleTypeKey extends Enum[VehicleTypeKey] { - val values: immutable.IndexedSeq[VehicleTypeKey] = findValues - - case object VehicleTypeIdKey extends VehicleTypeKey - case object VehicleCategoryKey extends VehicleTypeKey - case object VehicleCategoryPlusPowertrainKey extends VehicleTypeKey - - def fromString(value: String): Option[VehicleTypeKey] = { - value.toLowerCase() match { - case x if x.contains("category") && x.contains("powertrain") => Some(VehicleCategoryPlusPowertrainKey) - case x if x.contains("category") => Some(VehicleCategoryKey) - case x if x.contains("type") => Some(VehicleTypeIdKey) - case _ => None - } - } - } - - case class VehicleCategoryPlusPowertrain( - vehicleCategory: VehicleCategory, - primaryFuelType: FuelType, - secondaryFuelType: FuelType - ) { - def this(vehicleType: BeamVehicleType) = - this( - vehicleType.vehicleCategory, - vehicleType.primaryFuelType, - vehicleType.secondaryFuelType.getOrElse(FuelType.Undefined) - ) - - override def toString: String = productIterator.mkString(",") - } - - // we use shapeless Coproduct to emulate "union types" (implemented in Scala 3). - // It is somewhat similar to Either but for unlimited number of types. - type VehicleTypePart = Id[BeamVehicleType] :+: VehicleCategory :+: VehicleCategoryPlusPowertrain :+: CNil - - object VehicleTypePart { - - def apply(vehicleTypeKey: VehicleTypeKey, vehicleType: BeamVehicleType): VehicleTypePart = - vehicleTypeKey match { - case VehicleTypeKey.VehicleTypeIdKey => - Coproduct[VehicleTypePart](vehicleType.id) - case VehicleTypeKey.VehicleCategoryKey => - Coproduct[VehicleTypePart](vehicleType.vehicleCategory) - case VehicleTypeKey.VehicleCategoryPlusPowertrainKey => - Coproduct[VehicleTypePart](new VehicleCategoryPlusPowertrain(vehicleType)) - } - - private object toStringHandler extends Poly1 { - implicit def vehicleTypeId = at[Id[BeamVehicleType]](_.toString) - implicit def vehicleCategory = at[VehicleCategory](_.toString) - implicit def vehicleCategoryPlusPowertrain = at[VehicleCategoryPlusPowertrain](_.toString) - } - - def toString(vehicleTypePart: VehicleTypePart): String = vehicleTypePart.fold(toStringHandler) - - } case class ODVehicleTypeSkimmerKey( hour: Int, - vehicleTypePart: VehicleTypePart, + vehicleCategory: VehicleCategory, + primaryFuelType: FuelType, + secondaryFuelType: FuelType, origin: Id[TAZ], destination: Id[TAZ] ) extends AbstractSkimmerKey { - - override def toCsv: String = s"$hour,${VehicleTypePart.toString(vehicleTypePart)},$origin,$destination" - + override def toCsv: String = productIterator.mkString(",") } case class ODVehicleTypeSkimmerInternal( travelTimeInS: Double, generalizedTimeInS: Double, + cost: Double, generalizedCost: Double, distanceInM: Double, - cost: Double, payloadWeightInKg: Double, energy: Double, observations: Int = 1, iterations: Int = 1 ) extends AbstractSkimmerInternal { - override def toCsv: String = AbstractSkimmer.toCsv(productIterator) + + override def toCsv: String = + AbstractSkimmer.toCsv(productIterator) } def odVehicleTypeSkimOutputDataDescriptor: OutputDataDescriptor = OutputDataDescriptorObject("ODVehicleTypeSkimmer", "skimsODVehicleType.csv.gz", iterationLevel = true)( """ hour | Hour this statistic applies to - vehicleType | Type of the vehicle making the trip + vehicleCategory | Category of the vehicle making the trip + primaryFuelType | Primary fuel type of the vehicle making the trip + secondaryFuelType | Secondary fuel 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 @@ -247,7 +155,9 @@ object ODVehicleTypeSkimmer extends LazyLogging { OutputDataDescriptorObject("ODVehicleTypeSkimmer", "skimsODVehicleType_Aggregated.csv.gz", iterationLevel = true)( """ hour | Hour this statistic applies to - vehicleType | Trip mode + vehicleCategory | Category of the vehicle making the trip + primaryFuelType | Primary fuel type of the vehicle making the trip + secondaryFuelType | Secondary fuel type of the vehicle making the trip origTaz | TAZ id of trip origin destTaz | TAZ id of trip destination travelTimeInS | Average (over last n iterations) travel time in seconds diff --git a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala index c3160b5948f..df6fe449831 100644 --- a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala +++ b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala @@ -1,15 +1,12 @@ package beam.router.skim.event -import beam.agentsim.agents.vehicles.BeamVehicleType +import beam.agentsim.agents.vehicles.{BeamVehicleType, FuelType} +import beam.agentsim.agents.vehicles.FuelType.FuelType +import beam.agentsim.agents.vehicles.VehicleCategory.VehicleCategory import beam.agentsim.infrastructure.taz.TAZ import beam.router.model.EmbodiedBeamTrip import beam.router.skim.SkimsUtils -import beam.router.skim.core.ODVehicleTypeSkimmer.{ - ODVehicleTypeSkimmerInternal, - ODVehicleTypeSkimmerKey, - VehicleTypeKey, - VehicleTypePart -} +import beam.router.skim.core.ODVehicleTypeSkimmer.{ODVehicleTypeSkimmerInternal, ODVehicleTypeSkimmerKey} import beam.router.skim.core.{AbstractSkimmerEvent, AbstractSkimmerInternal, AbstractSkimmerKey, ODVehicleTypeSkimmer} import beam.sim.BeamServices import beam.utils.MathUtils.doubleToInt @@ -22,7 +19,9 @@ class ODVehicleTypeSkimmerEvent( eventTime: Double, origin: Id[TAZ], destination: Id[TAZ], - vehicleTypePart: VehicleTypePart, + vehicleCategory: VehicleCategory, + primaryFuelType: FuelType, + secondaryFuelType: FuelType, distanceInM: Double, travelTimeInS: Double, generalizedTimeInHours: Double, @@ -35,17 +34,24 @@ class ODVehicleTypeSkimmerEvent( override protected val skimName: String = ODVehicleTypeSkimmer.name override val getKey: AbstractSkimmerKey = - ODVehicleTypeSkimmerKey(SkimsUtils.timeToBin(doubleToInt(eventTime)), vehicleTypePart, origin, destination) + ODVehicleTypeSkimmerKey( + SkimsUtils.timeToBin(doubleToInt(eventTime)), + vehicleCategory, + primaryFuelType, + secondaryFuelType, + origin, + destination + ) override val getSkimmerInternal: AbstractSkimmerInternal = ODVehicleTypeSkimmerInternal( travelTimeInS, generalizedTimeInHours * 3600, + cost, generalizedCost, distanceInM, - cost, - maybePayloadWeightInKg.getOrElse(0), - energyConsumption + energyConsumption, + maybePayloadWeightInKg.getOrElse(0) ) } @@ -75,12 +81,13 @@ object ODVehicleTypeSkimmerEvent { .tazId val distanceInM = beamLegs.map(_.travelPath.distanceInM).sum val travelTime = trip.totalTravelTimeInSecs.toDouble - val vehicleTypePart = VehicleTypePart(skims.odVehicleTypeSkimmer.vehicleTypeKey, vehicleType) new ODVehicleTypeSkimmerEvent( eventTime, origTaz, destTaz, - vehicleTypePart, + vehicleType.vehicleCategory, + vehicleType.primaryFuelType, + vehicleType.secondaryFuelType.getOrElse(FuelType.Undefined), if (distanceInM > 0.0) distanceInM else 1.0, travelTime, generalizedTimeInHours, diff --git a/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala b/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala index dc44f944c45..aff1182bd56 100644 --- a/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala +++ b/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala @@ -1,13 +1,11 @@ package beam.router.skim.readonly +import beam.agentsim.agents.vehicles.FuelType.FuelType +import beam.agentsim.agents.vehicles.VehicleCategory.VehicleCategory import beam.agentsim.infrastructure.taz.TAZ import beam.router.skim.SkimsUtils.timeToBin import beam.router.skim.core.AbstractSkimmerReadOnly -import beam.router.skim.core.ODVehicleTypeSkimmer.{ - ODVehicleTypeSkimmerInternal, - ODVehicleTypeSkimmerKey, - VehicleTypePart -} +import beam.router.skim.core.ODVehicleTypeSkimmer.{ODVehicleTypeSkimmerInternal, ODVehicleTypeSkimmerKey} import org.matsim.api.core.v01.Id /** @@ -17,11 +15,14 @@ class ODVehicleTypeSkims extends AbstractSkimmerReadOnly { def getSkimValue( time: Int, - vehicleTypePart: VehicleTypePart, + vehicleCategory: VehicleCategory, + primaryFuelType: FuelType, + secondaryFuelType: FuelType, orig: Id[TAZ], dest: Id[TAZ] ): Option[ODVehicleTypeSkimmerInternal] = { - val key: ODVehicleTypeSkimmerKey = ODVehicleTypeSkimmerKey(timeToBin(time), vehicleTypePart, orig, dest) + val key: ODVehicleTypeSkimmerKey = + ODVehicleTypeSkimmerKey(timeToBin(time), vehicleCategory, primaryFuelType, secondaryFuelType, orig, dest) getSkimValueByKey(key) } diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index 798856f9e80..d65b9d7dd0f 100644 --- a/src/main/scala/beam/sim/config/BeamConfig.scala +++ b/src/main/scala/beam/sim/config/BeamConfig.scala @@ -4327,17 +4327,14 @@ object BeamConfig { } case class OriginDestinationVehicleTypeSkimmer( - vehicleCategories: java.lang.String, - vehicleTypeKey: java.lang.String + vehicleCategories: java.lang.String ) object OriginDestinationVehicleTypeSkimmer { def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Router.Skim.OriginDestinationVehicleTypeSkimmer = { BeamConfig.Beam.Router.Skim.OriginDestinationVehicleTypeSkimmer( - vehicleCategories = if (c.hasPathOrNull("vehicleCategories")) c.getString("vehicleCategories") else "Car", - vehicleTypeKey = - if (c.hasPathOrNull("vehicleTypeKey")) c.getString("vehicleTypeKey") else "VehicleCategory+Powertrain" + vehicleCategories = if (c.hasPathOrNull("vehicleCategories")) c.getString("vehicleCategories") else "Car" ) } } From 452b6acf5a4542817973b67b69a7515bbe2bc029 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Mon, 8 Jul 2024 18:30:35 +0300 Subject: [PATCH 08/10] Fix of energy position in ODVehicleTypeSkimmer csv --- .../beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala | 4 ++-- src/main/scala/beam/utils/FileUtils.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala index df6fe449831..6d77c121646 100644 --- a/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala +++ b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala @@ -50,8 +50,8 @@ class ODVehicleTypeSkimmerEvent( cost, generalizedCost, distanceInM, - energyConsumption, - maybePayloadWeightInKg.getOrElse(0) + maybePayloadWeightInKg.getOrElse(0), + energyConsumption ) } diff --git a/src/main/scala/beam/utils/FileUtils.scala b/src/main/scala/beam/utils/FileUtils.scala index 0c65b733d66..f47014fe0e8 100755 --- a/src/main/scala/beam/utils/FileUtils.scala +++ b/src/main/scala/beam/utils/FileUtils.scala @@ -403,7 +403,7 @@ object FileUtils extends LazyLogging { * @param atMost the expected time interval for file reading * @param loader the function that actually read data from the reader * @tparam Key the return map key - * @tparam Value the the return map value + * @tparam Value the return map value * @return a Map containing the key values returned back by the loader */ def parRead[Key, Value](dir: Path, fileNamePattern: String, atMost: Duration = 30 minutes)( From 9ea3f4e871918fcf31765ce1a5ade5ef0f935840 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Mon, 8 Jul 2024 18:31:55 +0300 Subject: [PATCH 09/10] CsvSkimReader with type parameters --- .../beam/router/skim/CsvSkimReader.scala | 20 ++++---- .../beam/router/skim/core/ODSkimmer.scala | 2 +- .../skim/core/ODVehicleTypeSkimmer.scala | 50 ++++++++++--------- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/main/scala/beam/router/skim/CsvSkimReader.scala b/src/main/scala/beam/router/skim/CsvSkimReader.scala index 4a165322d26..69089c90156 100644 --- a/src/main/scala/beam/router/skim/CsvSkimReader.scala +++ b/src/main/scala/beam/router/skim/CsvSkimReader.scala @@ -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) @@ -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) diff --git a/src/main/scala/beam/router/skim/core/ODSkimmer.scala b/src/main/scala/beam/router/skim/core/ODSkimmer.scala index c4162427216..a4bdeaf2c16 100644 --- a/src/main/scala/beam/router/skim/core/ODSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/ODSkimmer.scala @@ -402,7 +402,7 @@ object ODSkimmer extends LazyLogging { def fromCsv( row: scala.collection.Map[String, String] - ): (AbstractSkimmerKey, AbstractSkimmerInternal) = { + ): (ODSkimmerKey, ODSkimmerInternal) = { ( ODSkimmerKey( hour = row("hour").toInt, diff --git a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala index 64b5af20f80..d123604aa96 100644 --- a/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala +++ b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala @@ -39,29 +39,7 @@ class ODVehicleTypeSkimmer @Inject() ( override protected def fromCsv( line: collection.Map[String, String] - ): (AbstractSkimmerKey, AbstractSkimmerInternal) = { - ( - ODVehicleTypeSkimmerKey( - hour = line("hour").toInt, - vehicleCategory = VehicleCategory.fromString(line("vehicleCategory")), - primaryFuelType = FuelType.fromString(line("primaryFuelType")), - secondaryFuelType = FuelType.fromString(line("secondaryFuelType")), - origin = line("origTaz").createId, - destination = line("destTaz").createId - ), - ODVehicleTypeSkimmerInternal( - travelTimeInS = line("travelTimeInS").toDouble, - generalizedTimeInS = line("generalizedTimeInS").toDouble, - cost = line("cost").toDouble, - generalizedCost = line("generalizedCost").toDouble, - distanceInM = line("distanceInM").toDouble, - payloadWeightInKg = line.get("payloadWeightInKg").map(_.toDouble).getOrElse(0.0), - energy = Option(line("energy")).map(_.toDouble).getOrElse(0.0), - observations = NumberUtils.toInt(line("observations"), 0), - iterations = NumberUtils.toInt(line("iterations"), 1) - ) - ) - } + ): (AbstractSkimmerKey, AbstractSkimmerInternal) = ODVehicleTypeSkimmer.fromCsv(line) override protected def aggregateOverIterations( prevIteration: Option[AbstractSkimmerInternal], @@ -130,6 +108,32 @@ object ODVehicleTypeSkimmer { AbstractSkimmer.toCsv(productIterator) } + def fromCsv( + line: collection.Map[String, String] + ): (ODVehicleTypeSkimmerKey, ODVehicleTypeSkimmerInternal) = { + ( + ODVehicleTypeSkimmerKey( + hour = line("hour").toInt, + vehicleCategory = VehicleCategory.fromString(line("vehicleCategory")), + primaryFuelType = FuelType.fromString(line("primaryFuelType")), + secondaryFuelType = FuelType.fromString(line("secondaryFuelType")), + origin = line("origTaz").createId, + destination = line("destTaz").createId + ), + ODVehicleTypeSkimmerInternal( + travelTimeInS = line("travelTimeInS").toDouble, + generalizedTimeInS = line("generalizedTimeInS").toDouble, + cost = line("cost").toDouble, + generalizedCost = line("generalizedCost").toDouble, + distanceInM = line("distanceInM").toDouble, + payloadWeightInKg = line.get("payloadWeightInKg").map(_.toDouble).getOrElse(0.0), + energy = Option(line("energy")).map(_.toDouble).getOrElse(0.0), + observations = NumberUtils.toInt(line("observations"), 0), + iterations = NumberUtils.toInt(line("iterations"), 1) + ) + ) + } + def odVehicleTypeSkimOutputDataDescriptor: OutputDataDescriptor = OutputDataDescriptorObject("ODVehicleTypeSkimmer", "skimsODVehicleType.csv.gz", iterationLevel = true)( """ From 22796f07058f39767bf320ddd1c1795d0e1a7248 Mon Sep 17 00:00:00 2001 From: Dmitry Openkov Date: Mon, 8 Jul 2024 18:32:00 +0300 Subject: [PATCH 10/10] CsvSkimReader with type parameters --- .../skim/ODVehicleTypeSkimmerSpec.scala | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/test/scala/beam/router/skim/ODVehicleTypeSkimmerSpec.scala diff --git a/src/test/scala/beam/router/skim/ODVehicleTypeSkimmerSpec.scala b/src/test/scala/beam/router/skim/ODVehicleTypeSkimmerSpec.scala new file mode 100644 index 00000000000..55091101ca4 --- /dev/null +++ b/src/test/scala/beam/router/skim/ODVehicleTypeSkimmerSpec.scala @@ -0,0 +1,91 @@ +package beam.router.skim + +import beam.agentsim.agents.vehicles.VehicleCategory.{HeavyDutyTruck, LightDutyTruck} +import beam.router.skim.core.ODVehicleTypeSkimmer.{fromCsv, ODVehicleTypeSkimmerInternal, ODVehicleTypeSkimmerKey} +import beam.sim.BeamHelper +import beam.utils.EventReader._ +import beam.utils.TestConfigUtils.testConfig +import com.typesafe.config.ConfigFactory +import org.matsim.core.config.Config +import org.matsim.core.utils.io.IOUtils +import org.scalatest.Inspectors.forAll +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +/** + * @author Dmitry Openkov + */ +class ODVehicleTypeSkimmerSpec extends AnyWordSpecLike with Matchers with BeamHelper { + + "OD Vehicle Type Skimmer" should { + "produce correct skims for freight trips" in { + val config = BeamHelper.updateConfigToCurrentVersion( + ConfigFactory + .parseString(""" + beam.agentsim.lastIteration = 1 + beam.agentsim.agents.freight.replanning.disableAfterIteration = 0 + beam.router.skim.origin-destination-vehicle-type-skimmer.vehicleCategories = "LightDutyTruck, HeavyDutyTruck" + """) + .withFallback(testConfig("test/input/beamville/beam-freight.conf")) + .resolve() + ) + val (matSimConfig, _, _) = runBeamWithConfig(config) + val skim0: Map[ODVehicleTypeSkimmerKey, ODVehicleTypeSkimmerInternal] = + readSkims(matSimConfig, "skimsODVehicleType", 0) + skim0.size should be > 5 + skim0.keys.map(_.vehicleCategory) should contain only (LightDutyTruck, HeavyDutyTruck) + forAll(skim0.values) { value => + value.cost should be > 0.1 + value.cost should be < 15.0 + value.travelTimeInS should be > 100.0 + value.travelTimeInS should be < 500.0 + value.distanceInM should be > 2000.0 + value.distanceInM should be < 7000.0 + value.energy should be > 5e5 + value.energy should be < 3e8 + } + val skim1: Map[ODVehicleTypeSkimmerKey, ODVehicleTypeSkimmerInternal] = + readSkims(matSimConfig, "skimsODVehicleType", 1) + val skimAgg: Map[ODVehicleTypeSkimmerKey, ODVehicleTypeSkimmerInternal] = + readSkims(matSimConfig, "skimsODVehicleType_Aggregated", 1) + skimAgg.keys.map(_.vehicleCategory) should contain only (LightDutyTruck, HeavyDutyTruck) + def assertAggregation(key: ODVehicleTypeSkimmerKey, getter: ODVehicleTypeSkimmerInternal => Double) = { + val expectedAggResult = (skim0.get(key), skim1.get(key)) match { + case (Some(value), None) => getter(value) + case (None, Some(value)) => getter(value) + case (Some(value0), Some(value1)) => (getter(value0) + getter(value1)) / 2 + case (None, None) => throw new IllegalArgumentException(key.toCsv) + } + val actual = getter(skimAgg(key)) + expectedAggResult should be(actual +- math.max(0.01 * math.abs(actual), 0.01)) + } + forAll(skim0.keySet ++ skim1.keySet) { key => + assertAggregation(key, _.travelTimeInS) + assertAggregation(key, _.generalizedTimeInS) + assertAggregation(key, _.cost) + assertAggregation(key, _.generalizedCost) + assertAggregation(key, _.distanceInM) + assertAggregation(key, _.payloadWeightInKg) + assertAggregation(key, _.energy) + } + val intersectionKeys = skim0.keySet.intersect(skim1.keySet) + forAll(intersectionKeys) { key => + skimAgg(key).observations should be(1) + skimAgg(key).iterations should be(2) + } + forAll(skim0.keySet ++ skim1.keySet -- intersectionKeys) { key => + skimAgg(key).observations should be(1) + skimAgg(key).iterations should be(1) + } + } + } + + private def readSkims(matSimConfig: Config, fileName: String, iteration: Int) = { + val filePath = getEventsFilePath(matSimConfig, fileName, "csv.gz", iteration).getAbsolutePath + val reader = IOUtils.getBufferedReader(filePath) + val skims: Map[ODVehicleTypeSkimmerKey, ODVehicleTypeSkimmerInternal] = + new CsvSkimReader(filePath, fromCsv, logger).readSkims(reader) + reader.close() + skims + } +}