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/resources/beam-template.conf b/src/main/resources/beam-template.conf index 56084073d2a..20f85e25194 100755 --- a/src/main/resources/beam-template.conf +++ b/src/main/resources/beam-template.conf @@ -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 diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala index 38b4347b14d..64f2c227fad 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 } @@ -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 @@ -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. @@ -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) @@ -1612,7 +1620,7 @@ class PersonAgent( generalizedTime, generalizedCost, maybePayloadWeightInKg, - curFuelConsumed.primaryFuel + curFuelConsumed.secondaryFuel, + curFuelConsumed.totalEnergyConsumed, failedTrip ) eventsManager.processEvent(odSkimmerEvent) @@ -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()) { 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/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/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 { 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/Skims.scala b/src/main/scala/beam/router/skim/Skims.scala index e642801be34..1276a214f24 100644 --- a/src/main/scala/beam/router/skim/Skims.scala +++ b/src/main/scala/beam/router/skim/Skims.scala @@ -14,14 +14,15 @@ import scala.collection.mutable class Skims @Inject() ( matsimServices: MatsimServices, - odSkimmer: ODSkimmer, - tazSkimmer: TAZSkimmer, - driveTimeSkimmer: DriveTimeSkimmer, - transitCrowdingSkimmer: TransitCrowdingSkimmer, - rideHailSkimmer: RideHailSkimmer, - 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 @@ -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/ODSkimmer.scala b/src/main/scala/beam/router/skim/core/ODSkimmer.scala index f43c1f0ab15..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, @@ -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 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..d123604aa96 --- /dev/null +++ b/src/main/scala/beam/router/skim/core/ODVehicleTypeSkimmer.scala @@ -0,0 +1,178 @@ +package beam.router.skim.core + +import beam.agentsim.agents.vehicles.FuelType.FuelType +import beam.agentsim.agents.vehicles.VehicleCategory.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 +import beam.sim.config.BeamConfig +import beam.utils.matsim_conversion.MatsimPlanConversion.IdOps +import beam.utils.{OutputDataDescriptor, OutputDataDescriptorObject} +import com.google.inject.Inject +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 + + 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 = + "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 + + override protected def fromCsv( + line: collection.Map[String, String] + ): (AbstractSkimmerKey, AbstractSkimmerInternal) = ODVehicleTypeSkimmer.fromCsv(line) + + 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), + cost = agg.aggregate(_.cost), + generalizedCost = agg.aggregate(_.generalizedCost), + distanceInM = agg.aggregate(_.distanceInM), + payloadWeightInKg = agg.aggregate(_.payloadWeightInKg), + energy = agg.aggregate(_.energy), + 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), + cost = agg.aggregate(_.cost), + generalizedCost = agg.aggregate(_.generalizedCost), + distanceInM = agg.aggregate(_.distanceInM), + payloadWeightInKg = agg.aggregate(_.payloadWeightInKg), + energy = agg.aggregate(_.energy), + observations = agg.aggregateObservations + ) + } +} + +object ODVehicleTypeSkimmer { + val name = "od-vehicle-type-skimmer" + val fileBaseName = "skimsODVehicleType" + + case class ODVehicleTypeSkimmerKey( + hour: Int, + vehicleCategory: VehicleCategory, + primaryFuelType: FuelType, + secondaryFuelType: FuelType, + origin: Id[TAZ], + destination: Id[TAZ] + ) extends AbstractSkimmerKey { + override def toCsv: String = productIterator.mkString(",") + } + + case class ODVehicleTypeSkimmerInternal( + travelTimeInS: Double, + generalizedTimeInS: Double, + cost: Double, + generalizedCost: Double, + distanceInM: Double, + payloadWeightInKg: Double, + energy: Double, + observations: Int = 1, + iterations: Int = 1 + ) extends AbstractSkimmerInternal { + + override def toCsv: String = + 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)( + """ + hour | Hour this statistic applies to + 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 + 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 + 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 + 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..6d77c121646 --- /dev/null +++ b/src/main/scala/beam/router/skim/event/ODVehicleTypeSkimmerEvent.scala @@ -0,0 +1,100 @@ +package beam.router.skim.event + +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} +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], + vehicleCategory: VehicleCategory, + primaryFuelType: FuelType, + secondaryFuelType: FuelType, + 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)), + vehicleCategory, + primaryFuelType, + secondaryFuelType, + origin, + destination + ) + + override val getSkimmerInternal: AbstractSkimmerInternal = + ODVehicleTypeSkimmerInternal( + travelTimeInS, + generalizedTimeInHours * 3600, + cost, + generalizedCost, + distanceInM, + maybePayloadWeightInKg.getOrElse(0), + energyConsumption + ) +} + +object ODVehicleTypeSkimmerEvent { + + def apply( + eventTime: Double, + beamServices: BeamServices, + vehicleType: BeamVehicleType, + trip: EmbodiedBeamTrip, + generalizedTimeInHours: Double, + generalizedCost: Double, + maybePayloadWeightInKg: Option[Double], + energyConsumption: Double + ): ODVehicleTypeSkimmerEvent = { + import beamServices._ + val beamLegs = trip.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 = trip.totalTravelTimeInSecs.toDouble + new ODVehicleTypeSkimmerEvent( + eventTime, + origTaz, + destTaz, + vehicleType.vehicleCategory, + vehicleType.primaryFuelType, + vehicleType.secondaryFuelType.getOrElse(FuelType.Undefined), + if (distanceInM > 0.0) distanceInM else 1.0, + travelTime, + generalizedTimeInHours, + generalizedCost, + trip.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..aff1182bd56 --- /dev/null +++ b/src/main/scala/beam/router/skim/readonly/ODVehicleTypeSkims.scala @@ -0,0 +1,29 @@ +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} +import org.matsim.api.core.v01.Id + +/** + * @author Dmitry Openkov + */ +class ODVehicleTypeSkims extends AbstractSkimmerReadOnly { + + def getSkimValue( + time: Int, + vehicleCategory: VehicleCategory, + primaryFuelType: FuelType, + secondaryFuelType: FuelType, + orig: Id[TAZ], + dest: Id[TAZ] + ): Option[ODVehicleTypeSkimmerInternal] = { + val key: ODVehicleTypeSkimmerKey = + ODVehicleTypeSkimmerKey(timeToBin(time), vehicleCategory, primaryFuelType, secondaryFuelType, 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() diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index 7c581fe21b5..d65b9d7dd0f 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,19 @@ object BeamConfig { } } + case class OriginDestinationVehicleTypeSkimmer( + 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" + ) + } + } + case class TazSkimmer( fileBaseName: java.lang.String, geoHierarchy: java.lang.String, @@ -4373,6 +4387,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( 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)( 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 + } +} 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",