diff --git a/build.gradle b/build.gradle index dbd621cba44..97e70f05a80 100755 --- a/build.gradle +++ b/build.gradle @@ -175,6 +175,8 @@ dependencies { compile "com.sigopt:sigopt-java:4.9.0" + compile("com.uber:h3:3.4.1") + testCompile group: 'junit', name: 'junit', version: '4.8' testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.27.0' testCompile group: "org.mockito", name: "mockito-core", version: "2.+" diff --git a/src/main/resources/beam-template.conf b/src/main/resources/beam-template.conf index 8e56c707937..750b87577b2 100755 --- a/src/main/resources/beam-template.conf +++ b/src/main/resources/beam-template.conf @@ -155,8 +155,8 @@ beam.agentsim.agents.rideHail.allocationManager.repositionLowWaitingTimes.waitin beam.agentsim.agents.rideHail.allocationManager.repositionLowWaitingTimes.demandWeight = "double | 4.0" beam.agentsim.agents.rideHail.allocationManager.repositionLowWaitingTimes.produceDebugImages = true beam.agentsim.agents.rideHail.allocationManager.alonsoMora.waitingTimeInSec = "int | 360" -beam.agentsim.agents.rideHail.allocationManager.alonsoMora.travelTimeDelayAsFraction= "double | 0.2" -beam.agentsim.agents.rideHail.allocationManager.alonsoMora.solutionSpaceSizePerVehicle = "int | 5" +beam.agentsim.agents.rideHail.allocationManager.alonsoMora.excessRideTimeAsFraction= "double | 0.2" +beam.agentsim.agents.rideHail.allocationManager.alonsoMora.numRequestsPerVehicle = "int | 5" beam.agentsim.agents.rideHail.pooledToRegularRideCostRatio = 0.6 # human value of time taken from # https://theicct.org/sites/default/files/publications/Electric_shared_mobility_20190114.pdf @@ -286,11 +286,30 @@ beam.replanning.ModuleProbability_4 = 0.0 beam.replanning.fractionOfIterationsToDisableInnovation = "double | Double.PositiveInfinity" beam.replanning.cleanNonCarModesInIteration = "int | 0" -#Skimmer -beam.beamskimmer.writeObservedSkimsInterval = "int | 0" -beam.beamskimmer.writeAllModeSkimsForPeakNonPeakPeriodsInterval = "int | 0" -beam.beamskimmer.writeObservedSkimsPlusInterval = "int | 0" # Generic skimmer with a "label" and a "value" of type Double -beam.beamskimmer.writeFullSkimsInterval = "int | 0" +#h3 +beam.h3.resolution = "int | 10" +beam.h3.lowerBoundResolution = "int | 10" + +#skims +beam.router.skim = { + keepKLatestSkims = "int | 1" + writeSkimsInterval = "int | 0" + writeAggregatedSkimsInterval = "int | 0" + drive-time-skimmer { + name = "drive-time-skimmer" + fileBaseName = "String | skimsTravelTimeObservedVsSimulated" + } + origin-destination-skimmer { + name = "od-skimmer" + fileBaseName = "String | skimsOD" + writeAllModeSkimsForPeakNonPeakPeriodsInterval = "int | 0" + writeFullSkimsInterval = "int | 0" + } + taz-skimmer { + name = "taz-skimmer" + fileBaseName = "String | skimsTAZ" + } +} ################################################################## # Warm Mode diff --git a/src/main/scala/beam/agentsim/agents/PersonAgent.scala b/src/main/scala/beam/agentsim/agents/PersonAgent.scala index bc44b1140bd..45da1b8fe56 100755 --- a/src/main/scala/beam/agentsim/agents/PersonAgent.scala +++ b/src/main/scala/beam/agentsim/agents/PersonAgent.scala @@ -1,6 +1,5 @@ package beam.agentsim.agents -import scala.annotation.tailrec import akka.actor.FSM.Failure import akka.actor.{ActorRef, FSM, Props, Stash, Status} import beam.agentsim.Resource._ @@ -22,17 +21,20 @@ import beam.agentsim.agents.vehicles.VehicleCategory.Bike import beam.agentsim.agents.vehicles._ import beam.agentsim.events._ import beam.agentsim.events.resources.{ReservationError, ReservationErrorCode} +import beam.agentsim.infrastructure.parking.ParkingMNL import beam.agentsim.infrastructure.{ParkingInquiryResponse, ParkingStall} import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, IllegalTriggerGoToError, ScheduleTrigger} import beam.agentsim.scheduler.Trigger import beam.agentsim.scheduler.Trigger.TriggerWithId import beam.router.Modes.BeamMode import beam.router.Modes.BeamMode.{CAR, CAV, RIDE_HAIL, RIDE_HAIL_POOLED, RIDE_HAIL_TRANSIT, WALK, WALK_TRANSIT} +import beam.router.RouteHistory import beam.router.model.{EmbodiedBeamLeg, EmbodiedBeamTrip} import beam.router.osm.TollCalculator -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} +import beam.router.skim.{DriveTimeSkimmerEvent, ODSkimmerEvent, ODSkims, Skims} import beam.sim.population.AttributesOfIndividual import beam.sim.{BeamScenario, BeamServices, Geofence} +import beam.utils.logging.ExponentialLazyLogging import com.conveyal.r5.transit.TransportNetwork import com.vividsolutions.jts.geom.Envelope import org.matsim.api.core.v01.Id @@ -42,9 +44,8 @@ import org.matsim.core.api.experimental.events.{EventsManager, TeleportationArri import org.matsim.core.utils.misc.Time import org.matsim.vehicles.Vehicle +import scala.annotation.tailrec import scala.concurrent.duration._ -import beam.agentsim.infrastructure.parking.ParkingMNL -import beam.utils.logging.ExponentialLazyLogging /** */ @@ -67,9 +68,7 @@ object PersonAgent { householdRef: ActorRef, plan: Plan, sharedVehicleFleets: Seq[ActorRef], - beamSkimmer: BeamSkimmer, routeHistory: RouteHistory, - travelTimeObserved: TravelTimeObserved, boundingBox: Envelope ): Props = { Props( @@ -88,9 +87,7 @@ object PersonAgent { tollCalculator, householdRef, sharedVehicleFleets, - beamSkimmer, routeHistory, - travelTimeObserved, boundingBox ) ) @@ -245,9 +242,7 @@ class PersonAgent( val tollCalculator: TollCalculator, val householdRef: ActorRef, val vehicleFleets: Seq[ActorRef] = Vector(), - val beamSkimmer: BeamSkimmer, val routeHistory: RouteHistory, - val travelTimeObserved: TravelTimeObserved, val boundingBox: Envelope ) extends DrivesVehicle[PersonData] with ChoosesMode @@ -344,13 +339,14 @@ class PersonAgent( .foldLeft(tomorrowFirstLegDistance) { (sum, pair) => sum + Math .ceil( - beamSkimmer + Skims.od_skimmer .getTimeDistanceAndCost( pair.head.activity.getCoord, pair.last.activity.getCoord, 0, CAR, - currentBeamVehicle.beamVehicleType.id + currentBeamVehicle.beamVehicleType.id, + beamServices ) .distance ) @@ -964,18 +960,22 @@ class PersonAgent( val generalizedCost = modeChoiceCalculator.getNonTimeCost(correctedTrip) + attributes .getVOT(generalizedTime) // Correct the trip to deal with ride hail / disruptions and then register to skimmer - beamSkimmer.observeTrip( - correctedTrip, - generalizedTime, - generalizedCost, - curFuelConsumed.primaryFuel + curFuelConsumed.secondaryFuel - ) - travelTimeObserved.observeTrip( - correctedTrip, - generalizedTime, - generalizedCost, - curFuelConsumed.primaryFuel + curFuelConsumed.secondaryFuel + eventsManager.processEvent( + ODSkimmerEvent( + tick, + beamServices, + correctedTrip, + generalizedTime, + generalizedCost, + curFuelConsumed.primaryFuel + curFuelConsumed.secondaryFuel + ) ) + + correctedTrip.legs.filter(x => x.beamLeg.mode == BeamMode.CAR || x.beamLeg.mode == BeamMode.CAV).foreach { + carLeg => + eventsManager.processEvent(DriveTimeSkimmerEvent(tick, beamServices, carLeg)) + } + resetFuelConsumed() eventsManager.processEvent( diff --git a/src/main/scala/beam/agentsim/agents/Population.scala b/src/main/scala/beam/agentsim/agents/Population.scala index 669ef422794..0501d39799c 100755 --- a/src/main/scala/beam/agentsim/agents/Population.scala +++ b/src/main/scala/beam/agentsim/agents/Population.scala @@ -7,8 +7,8 @@ import beam.agentsim.agents.household.HouseholdActor import beam.agentsim.agents.vehicles.BeamVehicle import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTrigger} import beam.agentsim.scheduler.Trigger.TriggerWithId +import beam.router.RouteHistory import beam.router.osm.TollCalculator -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} import beam.sim.{BeamScenario, BeamServices} import com.conveyal.r5.transit.TransportNetwork import com.vividsolutions.jts.geom.Envelope @@ -32,8 +32,6 @@ class Population( val sharedVehicleFleets: Seq[ActorRef], val eventsManager: EventsManager, val routeHistory: RouteHistory, - val beamSkimmer: BeamSkimmer, - val travelTimeObserved: TravelTimeObserved, boundingBox: Envelope ) extends Actor with ActorLogging { @@ -119,8 +117,6 @@ class Population( homeCoord, sharedVehicleFleets, routeHistory, - beamSkimmer, - travelTimeObserved, boundingBox ), household.getId.toString @@ -165,8 +161,6 @@ object Population { sharedVehicleFleets: Seq[ActorRef], eventsManager: EventsManager, routeHistory: RouteHistory, - beamSkimmer: BeamSkimmer, - travelTimeObserved: TravelTimeObserved, boundingBox: Envelope ): Props = { Props( @@ -183,8 +177,6 @@ object Population { sharedVehicleFleets, eventsManager, routeHistory, - beamSkimmer, - travelTimeObserved, boundingBox ) ) diff --git a/src/main/scala/beam/agentsim/agents/household/FastHouseholdCAVScheduling.scala b/src/main/scala/beam/agentsim/agents/household/FastHouseholdCAVScheduling.scala index baa314f6be8..fe1ad14f409 100644 --- a/src/main/scala/beam/agentsim/agents/household/FastHouseholdCAVScheduling.scala +++ b/src/main/scala/beam/agentsim/agents/household/FastHouseholdCAVScheduling.scala @@ -9,7 +9,8 @@ import beam.agentsim.events.SpaceTime import beam.router.BeamRouter.{EmbodyWithCurrentTravelTime, RoutingRequest} import beam.router.Modes.BeamMode import beam.router.Modes.BeamMode.CAV -import beam.router.{BeamRouter, BeamSkimmer, Modes, RouteHistory} +import beam.router.skim.Skims +import beam.router.{BeamRouter, Modes, RouteHistory} import beam.sim.BeamServices import beam.utils.logging.ExponentialLoggerWrapperImpl import com.conveyal.r5.transit.TransportNetwork @@ -26,17 +27,15 @@ import scala.util.control.Breaks._ class FastHouseholdCAVScheduling( val household: Household, val householdVehicles: List[BeamVehicle], - val skimmer: BeamSkimmer, - val beamServices: Option[BeamServices] = None -)(implicit val population: org.matsim.api.core.v01.population.Population) { - + val beamServices: BeamServices +) { + implicit val population: org.matsim.api.core.v01.population.Population = + beamServices.matsimServices.getScenario.getPopulation var waitingTimeInSec: Int = 5 * 60 var delayToArrivalInSec: Int = waitingTimeInSec + waitingTimeInSec var stopSearchAfterXSolutions: Int = 100 var limitCavToXPersons: Int = 3 - import scala.collection.mutable.{ListBuffer => MListBuffer} - def getKLowestSumOfDelaysSchedules(k: Int): List[List[CAVSchedule]] = { getAllFeasibleSchedules .sortBy(_.householdScheduleCost.sumOfDelays.foldLeft(0)(_ + _._2)) @@ -84,10 +83,10 @@ class FastHouseholdCAVScheduling( household, householdVehicles, householdVehicles.size, - skimmer, waitingTimeInSec, delayToArrivalInSec, - limitCavToXPersons + limitCavToXPersons, + beamServices ) match { case Some(householdTrips) if householdTrips.cavVehicles.nonEmpty => val householdSchedules = mutable.ListBuffer.empty[HouseholdSchedule] @@ -126,7 +125,7 @@ class FastHouseholdCAVScheduling( ) { def check(requests: List[MobilityRequest]): List[HouseholdSchedule] = { - val outHouseholdSchedule = MListBuffer.empty[HouseholdSchedule] + val outHouseholdSchedule = mutable.ListBuffer.empty[HouseholdSchedule] breakable { for ((cav, cavSchedule) <- schedulesMap.toArray.sortBy(_._2.schedule.size)(Ordering[Int].reverse)) { // prioritizing CAVs with high usage @@ -152,18 +151,19 @@ class FastHouseholdCAVScheduling( val sortedRequests = (cavSchedule.schedule ++ requests).filter(_.tag != Relocation).sortBy(_.baselineNonPooledTime) val startRequest = sortedRequests.head - val newHouseholdSchedule = MListBuffer(startRequest.copy()) + val newHouseholdSchedule = mutable.ListBuffer(startRequest.copy()) var newHouseholdScheduleCost = householdScheduleCost.copy() var newOccupancy: Int = cavSchedule.occupancy sortedRequests.drop(1).foreach { curReq => val prevReq = newHouseholdSchedule.last - val metric = skimmer.getTimeDistanceAndCost( + val metric = Skims.od_skimmer.getTimeDistanceAndCost( prevReq.activity.getCoord, curReq.activity.getCoord, prevReq.baselineNonPooledTime, BeamMode.CAR, - cav.beamVehicleType.id + cav.beamVehicleType.id, + beamServices ) var serviceTime = prevReq.serviceTime + metric.time val ubTime = curReq.upperBoundTime @@ -221,7 +221,7 @@ class FastHouseholdCAVScheduling( ) } - private def computeSharedTravelTime(requestsSeq: MListBuffer[MobilityRequest]): Int = { + private def computeSharedTravelTime(requestsSeq: mutable.ListBuffer[MobilityRequest]): Int = { val waitTime = requestsSeq.head.serviceTime - requestsSeq.head.baselineNonPooledTime requestsSeq.filter(x => x.isPickup || x.isDropoff).sliding(2).foldLeft(waitTime) { case (acc, Seq(prevReq, nextReq)) => @@ -349,12 +349,14 @@ object HouseholdTrips { household: Household, householdVehicles: List[BeamVehicle], householdNbOfVehicles: Int, - skim: BeamSkimmer, waitingTimeInSec: Int, delayToArrivalInSec: Int, - limitCavToXPersons: Int - )(implicit population: org.matsim.api.core.v01.population.Population): Option[HouseholdTrips] = { + limitCavToXPersons: Int, + beamServices: BeamServices + ): Option[HouseholdTrips] = { import beam.agentsim.agents.memberships.Memberships.RankedGroup._ + implicit val population: org.matsim.api.core.v01.population.Population = + beamServices.matsimServices.getScenario.getPopulation val householdPlans = household.members .take(limitCavToXPersons) .map( @@ -366,10 +368,10 @@ object HouseholdTrips { HouseholdTripsHelper.getListOfPickupsDropoffs( householdPlans, householdNbOfVehicles, - skim, vehicleTypeForSkimmer, waitingTimeInSec, - delayToArrivalInSec + delayToArrivalInSec, + beamServices ) firstPickupOfTheDay map ( homePickup => @@ -393,7 +395,6 @@ case class HouseholdTripsLogger(name: String) extends ExponentialLoggerWrapperIm object HouseholdTripsHelper { - import scala.collection.mutable.{ListBuffer => MListBuffer, Map => MMap} import scala.util.control.Breaks._ val logger = HouseholdTripsLogger(getClass.getName) @@ -406,14 +407,14 @@ object HouseholdTripsHelper { def getListOfPickupsDropoffs( householdPlans: Seq[BeamPlan], householdNbOfVehicles: Int, - skim: BeamSkimmer, beamVehicleType: BeamVehicleType, waitingTimeInSec: Int, - delayToArrivalInSec: Int - ): (List[List[MobilityRequest]], Option[MobilityRequest], MMap[Trip, Int], Int) = { - val requests = MListBuffer.empty[List[MobilityRequest]] - val tours = MListBuffer.empty[MobilityRequest] - val tripTravelTime = MMap[Trip, Int]() + delayToArrivalInSec: Int, + beamServices: BeamServices + ): (List[List[MobilityRequest]], Option[MobilityRequest], mutable.Map[Trip, Int], Int) = { + val requests = mutable.ListBuffer.empty[List[MobilityRequest]] + val tours = mutable.ListBuffer.empty[MobilityRequest] + val tripTravelTime = mutable.Map[Trip, Int]() var totTravelTime = 0 var firstPickupOfTheDay: Option[MobilityRequest] = None breakable { @@ -427,10 +428,10 @@ object HouseholdTripsHelper { curTrip, prevTrip, counter, - skim, beamVehicleType, waitingTimeInSec, - delayToArrivalInSec + delayToArrivalInSec, + beamServices ) if (firstPickupOfTheDay.isEmpty || firstPickupOfTheDay.get.baselineNonPooledTime > pickup.baselineNonPooledTime) firstPickupOfTheDay = Some(pickup) @@ -457,21 +458,22 @@ object HouseholdTripsHelper { curTrip: Trip, prevTrip: Trip, counter: Int, - skimmer: BeamSkimmer, beamVehicleType: BeamVehicleType, waitingTimeInSec: Int, - delayToArrivalInSec: Int + delayToArrivalInSec: Int, + beamServices: BeamServices ): (MobilityRequest, MobilityRequest, Int) = { val legTrip = curTrip.leg val defaultMode = getDefaultMode(legTrip, counter) - val skim = skimmer - .getTimeDistanceAndCost( - prevTrip.activity.getCoord, - curTrip.activity.getCoord, - 0, - defaultMode, - beamVehicleType.id - ) + + val skim = Skims.od_skimmer.getTimeDistanceAndCost( + prevTrip.activity.getCoord, + curTrip.activity.getCoord, + 0, + defaultMode, + beamVehicleType.id, + beamServices + ) val startTime = prevTrip.activity.getEndTime.toInt val arrivalTime = startTime + skim.time diff --git a/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala b/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala index 34ae03152e6..6bf9e1a261f 100755 --- a/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala +++ b/src/main/scala/beam/agentsim/agents/household/HouseholdActor.scala @@ -2,7 +2,6 @@ package beam.agentsim.agents.household import java.util.concurrent.TimeUnit -import akka.actor.FSM.Failure import akka.actor.SupervisorStrategy.Stop import akka.actor.{Actor, ActorLogging, ActorRef, OneForOneStrategy, Props, Status, Terminated} import akka.pattern._ @@ -27,9 +26,9 @@ import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTri import beam.agentsim.scheduler.Trigger.TriggerWithId import beam.router.BeamRouter.RoutingResponse import beam.router.Modes.BeamMode.CAV +import beam.router.RouteHistory import beam.router.model.{BeamLeg, EmbodiedBeamLeg} import beam.router.osm.TollCalculator -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} import beam.sim.population.AttributesOfIndividual import beam.sim.{BeamScenario, BeamServices} import com.conveyal.r5.transit.TransportNetwork @@ -69,8 +68,6 @@ object HouseholdActor { homeCoord: Coord, sharedVehicleFleets: Seq[ActorRef] = Vector(), routeHistory: RouteHistory, - beamSkimmer: BeamSkimmer, - travelTimeObserved: TravelTimeObserved, boundingBox: Envelope ): Props = { Props( @@ -91,8 +88,6 @@ object HouseholdActor { homeCoord, sharedVehicleFleets, routeHistory, - beamSkimmer, - travelTimeObserved, boundingBox ) ) @@ -133,8 +128,6 @@ object HouseholdActor { homeCoord: Coord, sharedVehicleFleets: Seq[ActorRef] = Vector(), routeHistory: RouteHistory, - beamSkimmer: BeamSkimmer, - travelTimeObserved: TravelTimeObserved, boundingBox: Envelope ) extends Actor with HasTickAndTrigger @@ -216,13 +209,7 @@ object HouseholdActor { } } - val cavScheduler = new FastHouseholdCAVScheduling( - household, - cavs, - beamServices = Some(beamServices), - skimmer = beamSkimmer - )(beamServices.matsimServices.getScenario.getPopulation) - + val cavScheduler = new FastHouseholdCAVScheduling(household, cavs, beamServices) //val optimalPlan = cavScheduler.getKBestCAVSchedules(1).headOption.getOrElse(List.empty) val optimalPlan = cavScheduler.getBestProductiveSchedule if (optimalPlan.isEmpty || !optimalPlan.exists(_.schedule.size > 1)) { @@ -291,9 +278,7 @@ object HouseholdActor { self, person.getSelectedPlan, fleetManagers ++: sharedVehicleFleets, - beamSkimmer, routeHistory, - travelTimeObserved, boundingBox ), person.getId.toString diff --git a/src/main/scala/beam/agentsim/agents/ridehail/AlonsoMoraPoolingAlgForRideHail.scala b/src/main/scala/beam/agentsim/agents/ridehail/AlonsoMoraPoolingAlgForRideHail.scala index 7a18a0c18f7..c8ed867caa3 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/AlonsoMoraPoolingAlgForRideHail.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/AlonsoMoraPoolingAlgForRideHail.scala @@ -7,10 +7,10 @@ import beam.agentsim.agents.vehicles.EnergyEconomyAttributes.Powertrain import beam.agentsim.agents.vehicles.{BeamVehicle, BeamVehicleType, PersonIdWithActorRef} import beam.agentsim.agents.{MobilityRequest, _} import beam.router.BeamRouter.Location -import beam.router.BeamSkimmer -import beam.router.BeamSkimmer.Skim import beam.router.Modes.BeamMode +import beam.router.skim.{ODSkims, Skims, SkimsUtils} import beam.sim.common.GeoUtils +import beam.sim.config.BeamConfig.Beam.Agentsim.Agents.RideHail.AllocationManager import beam.sim.{BeamServices, Geofence} import org.jgrapht.graph.{DefaultEdge, DefaultUndirectedWeightedGraph} import org.matsim.api.core.v01.Id @@ -21,19 +21,17 @@ import org.matsim.core.utils.collections.QuadTree import scala.collection.JavaConverters._ import scala.collection.immutable.List import scala.collection.mutable.ListBuffer -import beam.sim.config.BeamConfig.Beam.Agentsim.Agents.RideHail.AllocationManager class AlonsoMoraPoolingAlgForRideHail( spatialDemand: QuadTree[CustomerRequest], supply: List[VehicleAndSchedule], - beamServices: BeamServices, - skimmer: BeamSkimmer + beamServices: BeamServices ) { // Methods below should be kept as def (instead of val) to allow automatic value updating private def alonsoMora: AllocationManager.AlonsoMora = beamServices.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora - private def solutionSpaceSizePerVehicle: Int = alonsoMora.solutionSpaceSizePerVehicle + private def solutionSpaceSizePerVehicle: Int = alonsoMora.numRequestsPerVehicle private def waitingTimeInSec: Int = alonsoMora.waitingTimeInSec val rvG = RVGraph(classOf[RideHailTrip]) @@ -46,7 +44,7 @@ class AlonsoMoraPoolingAlgForRideHail( .getDisk( r1.pickup.activity.getCoord.getX, r1.pickup.activity.getCoord.getY, - waitingTimeInSec * BeamSkimmer.speedMeterPerSec(BeamMode.CAV) + waitingTimeInSec * SkimsUtils.speedMeterPerSec(BeamMode.CAV) ) .asScala .withFilter(x => r1 != x && !rvG.containsEdge(r1, x)) @@ -55,7 +53,7 @@ class AlonsoMoraPoolingAlgForRideHail( List.empty[MobilityRequest], List(r1.pickup, r1.dropoff, r2.pickup, r2.dropoff), Integer.MAX_VALUE, - skimmer + beamServices ).map { schedule => rvG.addVertex(r2) rvG.addVertex(r1) @@ -69,17 +67,17 @@ class AlonsoMoraPoolingAlgForRideHail( .getDisk( v.getRequestWithCurrentVehiclePosition.activity.getCoord.getX, v.getRequestWithCurrentVehiclePosition.activity.getCoord.getY, - waitingTimeInSec * BeamSkimmer.speedMeterPerSec(BeamMode.CAV) + waitingTimeInSec * SkimsUtils.speedMeterPerSec(BeamMode.CAV) ) .asScala .take(solutionSpaceSizePerVehicle) } yield { - getRidehailSchedule(v.schedule, List(r.pickup, r.dropoff), v.vehicleRemainingRangeInMeters.toInt, skimmer).map { - schedule => + getRidehailSchedule(v.schedule, List(r.pickup, r.dropoff), v.vehicleRemainingRangeInMeters.toInt, beamServices) + .map { schedule => rvG.addVertex(v) rvG.addVertex(r) rvG.addEdge(v, r, RideHailTrip(List(r), schedule)) - } + } } } @@ -110,7 +108,7 @@ class AlonsoMoraPoolingAlgForRideHail( v.schedule, (t1.requests ++ t2.requests).flatMap(x => List(x.pickup, x.dropoff)), v.vehicleRemainingRangeInMeters.toInt, - skimmer + beamServices ) map { schedule => val t = RideHailTrip(t1.requests ++ t2.requests, schedule) pairRequestsList append t @@ -136,7 +134,7 @@ class AlonsoMoraPoolingAlgForRideHail( v.schedule, (t1.requests ++ t2.requests).flatMap(x => List(x.pickup, x.dropoff)), v.vehicleRemainingRangeInMeters.toInt, - skimmer + beamServices ).map { schedule => val t = RideHailTrip(t1.requests ++ t2.requests, schedule) kRequestsList.append(t) @@ -213,16 +211,17 @@ object AlonsoMoraPoolingAlgForRideHail { } // ************ Helper functions ************ - def getTimeDistanceAndCost(src: MobilityRequest, dst: MobilityRequest, skimmer: BeamSkimmer): Skim = { - skimmer.getTimeDistanceAndCost( + def getTimeDistanceAndCost(src: MobilityRequest, dst: MobilityRequest, beamServices: BeamServices) = { + Skims.od_skimmer.getTimeDistanceAndCost( src.activity.getCoord, dst.activity.getCoord, src.baselineNonPooledTime, BeamMode.CAR, Id.create( - skimmer.beamScenario.beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, + beamServices.beamScenario.beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, classOf[BeamVehicleType] - ) + ), + beamServices ) } @@ -230,7 +229,7 @@ object AlonsoMoraPoolingAlgForRideHail { schedule: List[MobilityRequest], newRequests: List[MobilityRequest], remainingVehicleRangeInMeters: Int, - skimmer: BeamSkimmer + beamServices: BeamServices ): Option[List[MobilityRequest]] = { val newPoolingList = scala.collection.mutable.ListBuffer.empty[MobilityRequest] val reversedSchedule = schedule.reverse @@ -260,7 +259,7 @@ object AlonsoMoraPoolingAlgForRideHail { } sortedRequests.foreach { curReq => val prevReq = newPoolingList.lastOption.getOrElse(newPoolingList.last) - val tdc = getTimeDistanceAndCost(prevReq, curReq, skimmer) + val tdc = getTimeDistanceAndCost(prevReq, curReq, beamServices) val serviceTime = prevReq.serviceTime + tdc.time val serviceDistance = prevReq.serviceDistance + tdc.distance.toInt if (serviceTime <= curReq.upperBoundTime && serviceDistance <= remainingVehicleRangeInMeters) { @@ -278,27 +277,26 @@ object AlonsoMoraPoolingAlgForRideHail { departureTime: Int, dst: Location, beamServices: BeamServices - )( - implicit skimmer: BeamSkimmer ): CustomerRequest = { val alonsoMora: AllocationManager.AlonsoMora = beamServices.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora val waitingTimeInSec = alonsoMora.waitingTimeInSec - val travelTimeDelayAsFraction = alonsoMora.travelTimeDelayAsFraction + val travelTimeDelayAsFraction = alonsoMora.excessRideTimeAsFraction val p1Act1: Activity = PopulationUtils.createActivityFromCoord(s"${vehiclePersonId.personId}Act1", src) p1Act1.setEndTime(departureTime) val p1Act2: Activity = PopulationUtils.createActivityFromCoord(s"${vehiclePersonId.personId}Act2", dst) - val skim = skimmer + val skim = Skims.od_skimmer .getTimeDistanceAndCost( p1Act1.getCoord, p1Act2.getCoord, departureTime, BeamMode.CAR, Id.create( - skimmer.beamScenario.beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, + beamServices.beamScenario.beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, classOf[BeamVehicleType] - ) + ), + beamServices ) CustomerRequest( vehiclePersonId, @@ -347,7 +345,7 @@ object AlonsoMoraPoolingAlgForRideHail { val alonsoMora: AllocationManager.AlonsoMora = beamServices.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora val waitingTimeInSec = alonsoMora.waitingTimeInSec - val travelTimeDelayAsFraction = alonsoMora.travelTimeDelayAsFraction + val travelTimeDelayAsFraction = alonsoMora.excessRideTimeAsFraction veh.currentPassengerSchedule.foreach { _.schedule.foreach { diff --git a/src/main/scala/beam/agentsim/agents/ridehail/AsyncAlonsoMoraAlgForRideHail.scala b/src/main/scala/beam/agentsim/agents/ridehail/AsyncAlonsoMoraAlgForRideHail.scala index a6b881fcd5c..9e59d5adef5 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/AsyncAlonsoMoraAlgForRideHail.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/AsyncAlonsoMoraAlgForRideHail.scala @@ -1,9 +1,9 @@ package beam.agentsim.agents.ridehail -import beam.agentsim.agents.{Dropoff, EnRoute, MobilityRequest, Pickup} +import beam.agentsim.agents.EnRoute import beam.agentsim.agents.ridehail.AlonsoMoraPoolingAlgForRideHail._ -import beam.router.BeamSkimmer import beam.router.Modes.BeamMode +import beam.router.skim.{ODSkims, Skims, SkimsUtils} import beam.sim.BeamServices import beam.sim.common.GeoUtils import org.jgrapht.graph.DefaultEdge @@ -18,12 +18,11 @@ import scala.concurrent.Future class AsyncAlonsoMoraAlgForRideHail( spatialDemand: QuadTree[CustomerRequest], supply: List[VehicleAndSchedule], - beamServices: BeamServices, - skimmer: BeamSkimmer + beamServices: BeamServices ) { private val solutionSpaceSizePerVehicle = - beamServices.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora.solutionSpaceSizePerVehicle + beamServices.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora.numRequestsPerVehicle private val waitingTimeInSec = beamServices.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora.waitingTimeInSec @@ -35,7 +34,7 @@ class AsyncAlonsoMoraAlgForRideHail( val finalRequestsList = MListBuffer.empty[RideHailTrip] val requestWithCurrentVehiclePosition = v.getRequestWithCurrentVehiclePosition val center = requestWithCurrentVehiclePosition.activity.getCoord - val searchRadius = waitingTimeInSec * BeamSkimmer.speedMeterPerSec(BeamMode.CAV) + val searchRadius = waitingTimeInSec * SkimsUtils.speedMeterPerSec(BeamMode.CAV) var requests = v.geofence match { case Some(gf) => val gfCenter = new Coord(gf.geofenceX, gf.geofenceY) @@ -64,7 +63,7 @@ class AsyncAlonsoMoraAlgForRideHail( v.schedule, List(r.pickup, r.dropoff), v.vehicleRemainingRangeInMeters.toInt, - skimmer + beamServices ) match { case Some(schedule) => val t = RideHailTrip(List(r), schedule) @@ -90,7 +89,7 @@ class AsyncAlonsoMoraAlgForRideHail( v.schedule, (t1.requests ++ t2.requests).flatMap(x => List(x.pickup, x.dropoff)), v.vehicleRemainingRangeInMeters.toInt, - skimmer + beamServices ) match { case Some(schedule) => val t = RideHailTrip(t1.requests ++ t2.requests, schedule) diff --git a/src/main/scala/beam/agentsim/agents/ridehail/RideHailManager.scala b/src/main/scala/beam/agentsim/agents/ridehail/RideHailManager.scala index 50e6a315b02..6ccc62c353a 100755 --- a/src/main/scala/beam/agentsim/agents/ridehail/RideHailManager.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/RideHailManager.scala @@ -14,6 +14,7 @@ import beam.agentsim import beam.agentsim.Resource._ import beam.agentsim.agents.BeamAgent.Finish import beam.agentsim.agents.choice.logit.UtilityFunctionOperation +import beam.agentsim.agents.choice.mode.DrivingCost import beam.agentsim.agents.household.CAVSchedule.RouteOrEmbodyRequest import beam.agentsim.agents.modalbehaviors.DrivesVehicle._ import beam.agentsim.agents.ridehail.RideHailAgent._ @@ -40,7 +41,7 @@ import beam.router.BeamRouter.{Location, RoutingRequest, RoutingResponse, _} import beam.router.Modes.BeamMode._ import beam.router.model.{BeamLeg, EmbodiedBeamLeg, EmbodiedBeamTrip} import beam.router.osm.TollCalculator -import beam.router.{BeamRouter, BeamSkimmer, RouteHistory} +import beam.router.{BeamRouter, RouteHistory} import beam.sim.RideHailFleetInitializer.RideHailAgentInputData import beam.sim._ import beam.sim.vehicles.VehiclesAdjustment @@ -63,11 +64,7 @@ import scala.collection.mutable.ArrayBuffer import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.math.{max, min} -import scala.util.{Failure, Random, Success, Try} -import beam.agentsim.agents.choice.logit.{MultinomialLogit, UtilityFunctionOperation} -import beam.agentsim.agents.choice.mode.DrivingCost -import beam.agentsim.infrastructure.parking.ParkingMNL.RemainingTripData -import beam.agentsim.infrastructure.parking.ParkingZoneSearch.ParkingAlternative +import scala.util.Random object RideHailManager { val INITIAL_RIDE_HAIL_LOCATION_HOME = "HOME" @@ -214,7 +211,6 @@ class RideHailManager( val activityQuadTreeBounds: QuadTreeBounds, val surgePricingManager: RideHailSurgePricingManager, val tncIterationStats: Option[TNCIterationStats], - val beamSkimmer: BeamSkimmer, val routeHistory: RouteHistory ) extends Actor with ActorLogging diff --git a/src/main/scala/beam/agentsim/agents/ridehail/VehicleCentricMatchingForRideHail.scala b/src/main/scala/beam/agentsim/agents/ridehail/VehicleCentricMatchingForRideHail.scala index 5edc4442fbf..14c9181d90d 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/VehicleCentricMatchingForRideHail.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/VehicleCentricMatchingForRideHail.scala @@ -2,8 +2,8 @@ package beam.agentsim.agents.ridehail import beam.agentsim.agents.EnRoute import beam.agentsim.agents.ridehail.AlonsoMoraPoolingAlgForRideHail._ -import beam.router.BeamSkimmer import beam.router.Modes.BeamMode +import beam.router.skim.SkimsUtils import beam.sim.BeamServices import beam.sim.common.GeoUtils import org.matsim.api.core.v01.Coord @@ -18,14 +18,13 @@ import scala.concurrent.Future class VehicleCentricMatchingForRideHail( demand: QuadTree[CustomerRequest], supply: List[VehicleAndSchedule], - services: BeamServices, - skimmer: BeamSkimmer + services: BeamServices ) { private val solutionSpaceSizePerVehicle = - services.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora.solutionSpaceSizePerVehicle + services.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora.numRequestsPerVehicle private val waitingTimeInSec = services.beamConfig.beam.agentsim.agents.rideHail.allocationManager.alonsoMora.waitingTimeInSec - private val searchRadius = waitingTimeInSec * BeamSkimmer.speedMeterPerSec(BeamMode.CAV) + private val searchRadius = waitingTimeInSec * SkimsUtils.speedMeterPerSec(BeamMode.CAV) type AssignmentKey = (RideHailTrip, VehicleAndSchedule, Double) @@ -93,7 +92,7 @@ class VehicleCentricMatchingForRideHail( .take(solutionSpaceSizePerVehicle) .flatten( c => - getRidehailSchedule(v.schedule, List(c.pickup, c.dropoff), v.vehicleRemainingRangeInMeters.toInt, skimmer) + getRidehailSchedule(v.schedule, List(c.pickup, c.dropoff), v.vehicleRemainingRangeInMeters.toInt, services) .map(schedule => (c, schedule)) ) .foreach { @@ -124,7 +123,7 @@ class VehicleCentricMatchingForRideHail( v.schedule, (t1.requests ++ t2.requests).flatMap(x => List(x.pickup, x.dropoff)), v.vehicleRemainingRangeInMeters.toInt, - skimmer + services ) match { case Some(schedule) => val t = RideHailTrip(t1.requests ++ t2.requests, schedule) diff --git a/src/main/scala/beam/agentsim/agents/ridehail/allocation/PoolingAlonsoMora.scala b/src/main/scala/beam/agentsim/agents/ridehail/allocation/PoolingAlonsoMora.scala index 5c8cc31d419..fdc401079b3 100755 --- a/src/main/scala/beam/agentsim/agents/ridehail/allocation/PoolingAlonsoMora.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/allocation/PoolingAlonsoMora.scala @@ -8,8 +8,8 @@ import beam.agentsim.agents.vehicles.BeamVehicleType import beam.agentsim.agents.vehicles.VehicleProtocol.StreetVehicle import beam.agentsim.events.SpaceTime import beam.router.BeamRouter.RoutingRequest -import beam.router.BeamSkimmer import beam.router.Modes.BeamMode.CAR +import beam.router.skim.{ODSkims, Skims} import beam.sim.BeamServices import beam.sim.vehiclesharing.VehicleManager import org.matsim.api.core.v01.Id @@ -46,11 +46,12 @@ class PoolingAlonsoMora(val rideHailManager: RideHailManager) inquiry.departAt ) match { case Some(agentETA) => - val timeCostFactors = rideHailManager.beamSkimmer.getRideHailPoolingTimeAndCostRatios( + val timeCostFactors = Skims.od_skimmer.getRideHailPoolingTimeAndCostRatios( inquiry.pickUpLocationUTM, inquiry.destinationUTM, inquiry.departAt, - defaultBeamVehilceTypeId + defaultBeamVehilceTypeId, + rideHailManager.beamServices ) SingleOccupantQuoteAndPoolingInfo( agentETA.agentLocation, @@ -130,7 +131,6 @@ class PoolingAlonsoMora(val rideHailManager: RideHailManager) } } if (toAllocate.nonEmpty) { - implicit val skimmer: BeamSkimmer = rideHailManager.beamSkimmer val pooledAllocationReqs = toAllocate.filter(_.asPooled) val customerIdToReqs = toAllocate.map(rhr => rhr.customer.personId -> rhr).toMap val vehiclePoolToUse = @@ -184,8 +184,7 @@ class PoolingAlonsoMora(val rideHailManager: RideHailManager) new VehicleCentricMatchingForRideHail( spatialPoolCustomerReqs, availVehicles, - rideHailManager.beamServices, - skimmer + rideHailManager.beamServices ) import scala.concurrent.duration._ val assignment = try { @@ -297,20 +296,8 @@ class PoolingAlonsoMora(val rideHailManager: RideHailManager) case res @ RoutingRequiredToAllocateVehicle(_, routes) => allocResponses = allocResponses :+ res alreadyAllocated = alreadyAllocated + routes.head.streetVehicles.head.id - skimmer.countEventsByTAZ( - tick, - req.pickUpLocationUTM, - Id.create("pooling-alonso-mora", classOf[VehicleManager]), - "rd-solo-matched" - ) case res => allocResponses = allocResponses :+ res - skimmer.countEventsByTAZ( - tick, - req.pickUpLocationUTM, - Id.create("pooling-alonso-mora", classOf[VehicleManager]), - "rd-solo-unmatched" - ) } } e = System.currentTimeMillis() diff --git a/src/main/scala/beam/agentsim/agents/ridehail/repositioningmanager/DemandFollowingRepositioningManager.scala b/src/main/scala/beam/agentsim/agents/ridehail/repositioningmanager/DemandFollowingRepositioningManager.scala index c03348048a6..9b912e0eb1f 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/repositioningmanager/DemandFollowingRepositioningManager.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/repositioningmanager/DemandFollowingRepositioningManager.scala @@ -4,6 +4,7 @@ import beam.agentsim.agents.ridehail.RideHailManager import beam.agentsim.agents.ridehail.RideHailVehicleManager.RideHailAgentLocation import beam.router.BeamRouter.Location import beam.router.Modes.BeamMode.CAR +import beam.router.skim.{ODSkims, Skims} import beam.sim.BeamServices import beam.utils.{ActivitySegment, ProfilingUtils} import com.typesafe.scalalogging.LazyLogging @@ -115,13 +116,14 @@ class DemandFollowingRepositioningManager(val beamServices: BeamServices, val ri // Filter out vehicles that don't have enough range newPositions .filter { vehAndNewLoc => - rideHailManager.beamSkimmer + Skims.od_skimmer .getTimeDistanceAndCost( vehAndNewLoc._1.currentLocationUTM.loc, vehAndNewLoc._2, tick, CAR, - vehAndNewLoc._1.vehicleType.id + vehAndNewLoc._1.vehicleType.id, + beamServices ) .distance <= rideHailManager.vehicleManager .getVehicleState(vehAndNewLoc._1.vehicleId) diff --git a/src/main/scala/beam/agentsim/infrastructure/taz/H3TAZ.scala b/src/main/scala/beam/agentsim/infrastructure/taz/H3TAZ.scala new file mode 100644 index 00000000000..66d2bf74f29 --- /dev/null +++ b/src/main/scala/beam/agentsim/infrastructure/taz/H3TAZ.scala @@ -0,0 +1,131 @@ +package beam.agentsim.infrastructure.taz + +import beam.agentsim.infrastructure.taz.H3TAZ.{fillBox, HexIndex} +import beam.sim.config.BeamConfig +import beam.utils.matsim_conversion.ShapeUtils.QuadTreeBounds +import com.uber.h3core.util.GeoCoord +import com.vividsolutions.jts.geom.{Coordinate, Geometry, GeometryFactory} +import org.matsim.api.core.v01.network.Network +import org.matsim.api.core.v01.{Coord, Id} +import org.matsim.core.utils.geometry.geotools.MGC +import org.matsim.core.utils.geometry.transformations.GeotoolsTransformation +import org.matsim.core.utils.gis.{PolygonFeatureFactory, ShapeFileWriter} + +import scala.collection.JavaConverters._ +import scala.collection.mutable + +case class H3TAZ(network: Network, tazTreeMap: TAZTreeMap, beamConfig: BeamConfig) { + private val transformToH3Proj = + new GeotoolsTransformation(beamConfig.matsim.modules.global.coordinateSystem, H3TAZ.H3Projection) + + private val boundingBox: QuadTreeBounds = H3TAZ.quadTreeExtentFromShapeFile( + network.getNodes.values().asScala.map(n => transformToH3Proj.transform(n.getCoord)) + ) + private val resolution = beamConfig.beam.h3.resolution + private val lowerBoundResolution = beamConfig.beam.h3.lowerBoundResolution + private val tazToH3TAZMapping: mutable.HashMap[HexIndex, Id[TAZ]] = mutable.HashMap() + fillBox(boundingBox, resolution).foreach { hex => + val hexCentroid = H3TAZ.hexToCoord(hex) + val hexCentroidBis = + new GeotoolsTransformation(H3TAZ.H3Projection, beamConfig.matsim.modules.global.coordinateSystem) + .transform(hexCentroid) + val tazId = tazTreeMap.getTAZ(hexCentroidBis.getX, hexCentroidBis.getY).tazId + tazToH3TAZMapping.put(hex, tazId) + } + + def getAll: Iterable[HexIndex] = { + tazToH3TAZMapping.keys + } + + def getHRHex(x: Double, y: Double): HexIndex = { + val coord = H3TAZ.toGeoCoord(transformToH3Proj.transform(new Coord(x, y))) + H3TAZ.H3.geoToH3Address(coord.lat, coord.lng, resolution) + } + + def getHRHex(tazId: Id[TAZ]): Iterable[HexIndex] = { + tazToH3TAZMapping.filter(_._2 == tazId).keys + } + + def getTAZ(hex: HexIndex): Id[TAZ] = { + tazToH3TAZMapping.getOrElse(hex, TAZTreeMap.emptyTAZId) + } + +} + +object H3TAZ { + type HexIndex = String + private val H3 = com.uber.h3core.H3Core.newInstance + val H3Projection = "EPSG:4326" + + def writeToShp(filename: String, h3Tazs: Iterable[(HexIndex, String, Double)]): Unit = { + val gf = new GeometryFactory() + val hexagons = h3Tazs.map { + case (h, taz, v) => + val boundary = H3.h3ToGeoBoundary(h).asScala + (h, taz, v, gf.createPolygon(boundary.map(toJtsCoordinate).toArray :+ toJtsCoordinate(boundary.head))) + } + val pf: PolygonFeatureFactory = new PolygonFeatureFactory.Builder() + .setCrs(MGC.getCRS("EPSG:4326")) + .setName("nodes") + .addAttribute("ID", classOf[String]) + .addAttribute("TAZ", classOf[String]) + .addAttribute("VALUE", classOf[java.lang.Double]) + .create() + val shpPolygons = hexagons.map { + case (hex, taz, value, hexagon) => + pf.createPolygon(hexagon.getCoordinates, Array[Object](hex, taz, value.toString), null) + } + ShapeFileWriter.writeGeometries(shpPolygons.asJavaCollection, filename) + } + + // private utilities + private def hexToCoord(hexAddress: String): Coord = { + val coordinate = toJtsCoordinate(H3.h3ToGeo(hexAddress)) + new Coord(coordinate.x, coordinate.y) + } + private def toJtsCoordinate(in: GeoCoord): com.vividsolutions.jts.geom.Coordinate = { + new com.vividsolutions.jts.geom.Coordinate(in.lng, in.lat) + } + private def toGeoCoord(in: Coord): GeoCoord = { + new GeoCoord(in.getY, in.getX) + } + + private def quadTreeExtentFromShapeFile(coords: Iterable[Coord]): QuadTreeBounds = { + var minX: Double = Double.MaxValue + var maxX: Double = Double.MinValue + var minY: Double = Double.MaxValue + var maxY: Double = Double.MinValue + for (c <- coords) { + minX = Math.min(minX, c.getX) + minY = Math.min(minY, c.getY) + maxX = Math.max(maxX, c.getX) + maxY = Math.max(maxY, c.getY) + } + val gf = new GeometryFactory() + val box = gf + .createPolygon( + Array( + new Coordinate(minX, minY), + new Coordinate(minX, maxY), + new Coordinate(maxX, maxY), + new Coordinate(maxX, minY), + new Coordinate(minX, minY) + ) + ) + .asInstanceOf[Geometry] + .getEnvelopeInternal + QuadTreeBounds(box.getMinX - 0.01, box.getMinY - 0.01, box.getMaxX + 0.01, box.getMaxY + 0.01) + } + + private def fillBox(box: QuadTreeBounds, resolution: Int): Iterable[String] = { + val points = List( + new GeoCoord(box.miny, box.minx), + new GeoCoord(box.maxy, box.minx), + new GeoCoord(box.maxy, box.maxx), + new GeoCoord(box.miny, box.maxx) + ).asJava + val holes = List.empty[java.util.List[GeoCoord]].asJava + H3.polyfillAddress(points, holes, resolution).asScala + } + +} diff --git a/src/main/scala/beam/router/BeamSkimmer.scala b/src/main/scala/beam/router/BeamSkimmer.scala deleted file mode 100644 index 07994f3d0c4..00000000000 --- a/src/main/scala/beam/router/BeamSkimmer.scala +++ /dev/null @@ -1,812 +0,0 @@ -package beam.router - -import java.io.File - -import beam.agentsim.agents.choice.mode.DrivingCost -import beam.agentsim.agents.vehicles.{BeamVehicle, BeamVehicleType} -import beam.agentsim.infrastructure.taz.TAZ -import beam.router.BeamRouter.Location -import beam.router.Modes.BeamMode -import beam.router.Modes.BeamMode.{ - BIKE, - CAR, - CAV, - DRIVE_TRANSIT, - RIDE_HAIL, - RIDE_HAIL_POOLED, - RIDE_HAIL_TRANSIT, - TRANSIT, - WALK, - WALK_TRANSIT -} -import beam.router.model.{BeamLeg, BeamPath, EmbodiedBeamTrip} -import beam.sim.common.GeoUtils -import beam.sim.config.BeamConfig -import beam.sim.vehiclesharing.VehicleManager -import beam.sim.{BeamScenario, BeamServices} -import beam.utils.{FileUtils, ProfilingUtils} -import com.typesafe.scalalogging.LazyLogging -import javax.inject.Inject -import org.matsim.api.core.v01.{Coord, Id} -import org.matsim.core.controler.events.IterationEndsEvent -import org.matsim.core.utils.io.IOUtils -import org.supercsv.io.CsvMapReader -import org.supercsv.prefs.CsvPreference - -import scala.collection.concurrent.TrieMap -import scala.language.implicitConversions -import scala.util.control.NonFatal - -//TODO to be validated against google api -class BeamSkimmer @Inject()( - val beamServices: BeamServices, - val beamScenario: BeamScenario, - val geo: GeoUtils -) extends LazyLogging { - import BeamSkimmer._ - import beamScenario._ - - def beamConfig: BeamConfig = beamServices.beamConfig - - // The OD/Mode/Time Matrix - private var previousSkims: BeamSkimmerADT = initialPreviousSkims() - private var skims: BeamSkimmerADT = TrieMap() - - private def skimsFilePath: Option[String] = { - val filePath = beamConfig.beam.warmStart.skimsFilePath - if (new File(filePath).isFile) { - Some(filePath) - } else { - None - } - } - - private def initialPreviousSkims(): TrieMap[(Int, BeamMode, Id[TAZ], Id[TAZ]), SkimInternal] = { - if (beamConfig.beam.warmStart.enabled) { - try { - val previousSkims = skimsFilePath - .map(BeamSkimmer.fromCsv) - .getOrElse(TrieMap.empty) - logger.info(s"Previous skims successfully loaded from path '${skimsFilePath.getOrElse("NO PATH FOUND")}'") - previousSkims - } catch { - case NonFatal(ex) => - logger.error(s"Could not load previous skim from '$skimsFilePath': ${ex.getMessage}", ex) - TrieMap.empty - } - } else { - TrieMap.empty - } - } - - def getSkimDefaultValue( - mode: BeamMode, - originUTM: Location, - destinationUTM: Location, - departureTime: Int, - vehicleTypeId: Id[BeamVehicleType] - ): Skim = { - val (travelDistance, travelTime) = distanceAndTime(mode, originUTM, destinationUTM) - val travelCost: Double = mode match { - case CAR | CAV => - DrivingCost.estimateDrivingCost( - new BeamLeg( - departureTime, - mode, - travelTime, - new BeamPath(IndexedSeq(), IndexedSeq(), None, null, null, travelDistance) - ), - beamScenario.vehicleTypes(vehicleTypeId), - beamScenario.fuelTypePrices - ) - case RIDE_HAIL => - beamConfig.beam.agentsim.agents.rideHail.defaultBaseCost + beamConfig.beam.agentsim.agents.rideHail.defaultCostPerMile * travelDistance / 1609.0 + beamConfig.beam.agentsim.agents.rideHail.defaultCostPerMinute * travelTime / 60.0 - case RIDE_HAIL_POOLED => - beamConfig.beam.agentsim.agents.rideHail.pooledBaseCost + beamConfig.beam.agentsim.agents.rideHail.pooledCostPerMile * travelDistance / 1609.0 + beamConfig.beam.agentsim.agents.rideHail.pooledCostPerMinute * travelTime / 60.0 - case TRANSIT | WALK_TRANSIT | DRIVE_TRANSIT | RIDE_HAIL_TRANSIT => 0.25 * travelDistance / 1609 - case _ => 0.0 - } - Skim( - travelTime, - travelTime, - travelCost + travelTime * beamConfig.beam.agentsim.agents.modalBehaviors.defaultValueOfTime / 3600, - travelDistance, - travelCost, - 0, - 0.0 // TODO get default energy information - ) - } - - def getTimeDistanceAndCost( - originUTM: Location, - destinationUTM: Location, - departureTime: Int, - mode: BeamMode, - vehicleTypeId: Id[BeamVehicleType] - ): Skim = { - val origTaz = tazTreeMap.getTAZ(originUTM.getX, originUTM.getY).tazId - val destTaz = tazTreeMap.getTAZ(destinationUTM.getX, destinationUTM.getY).tazId - getSkimValue(departureTime, mode, origTaz, destTaz) match { - case Some(skimValue) => - skimValue.toSkimExternal - case None => - getSkimDefaultValue( - mode, - originUTM, - new Coord(destinationUTM.getX, destinationUTM.getY), - departureTime, - vehicleTypeId - ) - } - } - - private def getRideHailCost(mode: BeamMode, distanceInMeters: Double, timeInSeconds: Double): Double = { - mode match { - case RIDE_HAIL => - beamConfig.beam.agentsim.agents.rideHail.defaultCostPerMile * distanceInMeters / 1609.34 + beamConfig.beam.agentsim.agents.rideHail.defaultCostPerMinute * timeInSeconds / 60 + beamConfig.beam.agentsim.agents.rideHail.defaultBaseCost - case RIDE_HAIL_POOLED => - beamConfig.beam.agentsim.agents.rideHail.pooledCostPerMile * distanceInMeters / 1609.34 + beamConfig.beam.agentsim.agents.rideHail.pooledCostPerMinute * timeInSeconds / 60 + beamConfig.beam.agentsim.agents.rideHail.pooledBaseCost - case _ => - 0.0 - } - - } - - def getRideHailPoolingTimeAndCostRatios( - origin: Location, - destination: Location, - departureTime: Int, - vehicleTypeId: org.matsim.api.core.v01.Id[BeamVehicleType] - ): (Double, Double) = { - val origTaz = tazTreeMap.getTAZ(origin.getX, origin.getY).tazId - val destTaz = tazTreeMap.getTAZ(destination.getX, destination.getY).tazId - val solo = getSkimValue(departureTime, RIDE_HAIL, origTaz, destTaz) match { - case Some(skimValue) if skimValue.count > 5 => - skimValue - case _ => - val (travelDistance, travelTime) = distanceAndTime(RIDE_HAIL, origin, destination) - SkimInternal( - time = travelTime.toDouble, - generalizedTime = 0, - generalizedCost = 0, - distance = travelDistance.toDouble, - cost = getRideHailCost(RIDE_HAIL, travelDistance, travelTime), - count = 0, - energy = 0.0 - ) - } - val pooled = getSkimValue(departureTime, RIDE_HAIL_POOLED, origTaz, destTaz) match { - case Some(skimValue) if skimValue.count > 5 => - skimValue - case _ => - SkimInternal( - time = solo.time * 1.1, - generalizedTime = 0, - generalizedCost = 0, - distance = solo.distance, - cost = getRideHailCost(RIDE_HAIL_POOLED, solo.distance, solo.time), - count = 0, - energy = 0.0 - ) - } - val timeFactor = if (solo.time > 0.0) { pooled.time / solo.time } else { 1.0 } - val costFactor = if (solo.cost > 0.0) { pooled.cost / solo.cost } else { 1.0 } - (timeFactor, costFactor) - } - - private def distanceAndTime(mode: BeamMode, originUTM: Location, destinationUTM: Location) = { - val speed = mode match { - case CAR | CAV | RIDE_HAIL => carSpeedMeterPerSec - case RIDE_HAIL_POOLED => carSpeedMeterPerSec / 1.1 - case TRANSIT | WALK_TRANSIT | DRIVE_TRANSIT | RIDE_HAIL_TRANSIT => transitSpeedMeterPerSec - case BIKE => bicycleSpeedMeterPerSec - case _ => walkSpeedMeterPerSec - } - val travelDistance: Int = Math.ceil(GeoUtils.minkowskiDistFormula(originUTM, destinationUTM)).toInt - val travelTime: Int = Math - .ceil(travelDistance / speed) - .toInt + ((travelDistance / trafficSignalSpacing).toInt * waitingTimeAtAnIntersection).toInt - (travelDistance, travelTime) - } - - def getSkimValue(time: Int, mode: BeamMode, orig: Id[TAZ], dest: Id[TAZ]): Option[SkimInternal] = { - skims.get((timeToBin(time), mode, orig, dest)) match { - case someSkim @ Some(_) => - someSkim - case None => - previousSkims.get((timeToBin(time), mode, orig, dest)) - } - } - - def observeTrip( - trip: EmbodiedBeamTrip, - generalizedTimeInHours: Double, - generalizedCost: Double, - energyConsumption: Double - ): Option[SkimInternal] = { - val mode = trip.tripClassifier - val correctedTrip = mode match { - case WALK => - trip - case _ => - val legs = trip.legs.drop(1).dropRight(1) - EmbodiedBeamTrip(legs) - } - val beamLegs = correctedTrip.beamLegs - val origLeg = beamLegs.head - val origCoord = geo.wgs2Utm(origLeg.travelPath.startPoint.loc) - val origTaz = tazTreeMap - .getTAZ(origCoord.getX, origCoord.getY) - .tazId - val destLeg = beamLegs.last - val destCoord = geo.wgs2Utm(destLeg.travelPath.endPoint.loc) - val destTaz = tazTreeMap - .getTAZ(destCoord.getX, destCoord.getY) - .tazId - val timeBin = timeToBin(origLeg.startTime) - val dist = beamLegs.map(_.travelPath.distanceInM).sum - val key = (timeBin, mode, origTaz, destTaz) - val payload = - SkimInternal( - correctedTrip.totalTravelTimeInSecs.toDouble, - generalizedTimeInHours * 3600, - generalizedCost, - if (dist > 0.0) { dist } else { 1.0 }, - correctedTrip.costEstimate, - 1, - energyConsumption - ) - skims.get(key) match { - case Some(existingSkim) => - val newPayload = SkimInternal( - time = mergeAverage(existingSkim.time, existingSkim.count, payload.time), - generalizedTime = mergeAverage(existingSkim.generalizedTime, existingSkim.count, payload.generalizedTime), - generalizedCost = mergeAverage(existingSkim.generalizedCost, existingSkim.count, payload.generalizedCost), - distance = mergeAverage(existingSkim.distance, existingSkim.count, payload.distance), - cost = mergeAverage(existingSkim.cost, existingSkim.count, payload.cost), - count = existingSkim.count + 1, - energy = mergeAverage(existingSkim.energy, existingSkim.count, payload.energy) - ) - skims.put(key, newPayload) - case None => - skims.put(key, payload) - } - } - - def timeToBin(departTime: Int): Int = { - Math.floorMod(Math.floor(departTime.toDouble / 3600.0).toInt, 24) - } - - def timeToBin(departTime: Int, timeWindow: Int): Int = departTime / timeWindow - - def mergeAverage(existingAverage: Double, existingCount: Int, newValue: Double): Double = { - (existingAverage * existingCount + newValue) / (existingCount + 1) - } - - def notifyIterationEnds(event: IterationEndsEvent): Unit = { - if (beamConfig.beam.beamskimmer.writeObservedSkimsInterval > 0 && event.getIteration % beamConfig.beam.beamskimmer.writeObservedSkimsInterval == 0) { - ProfilingUtils.timed(s"writeObservedSkims on iteration ${event.getIteration}", x => logger.info(x)) { - writeObservedSkims(event) - } - } - if (beamConfig.beam.beamskimmer.writeAllModeSkimsForPeakNonPeakPeriodsInterval > 0 && event.getIteration % beamConfig.beam.beamskimmer.writeAllModeSkimsForPeakNonPeakPeriodsInterval == 0) { - ProfilingUtils.timed( - s"writeAllModeSkimsForPeakNonPeakPeriods on iteration ${event.getIteration}", - x => logger.info(x) - ) { - writeAllModeSkimsForPeakNonPeakPeriods(event) - } - } - if (beamConfig.beam.beamskimmer.writeObservedSkimsPlusInterval > 0 && event.getIteration % beamConfig.beam.beamskimmer.writeObservedSkimsPlusInterval == 0) { - ProfilingUtils.timed(s"writeObservedSkimsPlus on iteration ${event.getIteration}", x => logger.info(x)) { - writeObservedSkimsPlus(event) - } - } - if (beamConfig.beam.beamskimmer.writeFullSkimsInterval > 0 && event.getIteration % beamConfig.beam.beamskimmer.writeFullSkimsInterval == 0) { - ProfilingUtils.timed(s"writeFullSkims on iteration ${event.getIteration}", x => logger.info(x)) { - writeFullSkims(event) - } - } - previousSkims = skims - skims = new TrieMap() - previousSkimsPlus = skimsPlus - skimsPlus = new TrieMap() - trackSkimsPlusTS = -1 - } - - def getExcerptData( - timePeriodString: String, - hoursIncluded: List[Int], - origin: TAZ, - destination: TAZ, - mode: BeamMode, - dummyId: Id[BeamVehicleType] - ): ExcerptData = { - val individualSkims = hoursIncluded.map { timeBin => - getSkimValue(timeBin * 3600, mode, origin.tazId, destination.tazId) - .map(_.toSkimExternal) - .getOrElse { - val adjustedDestCoord = if (origin.equals(destination)) { - new Coord( - origin.coord.getX, - origin.coord.getY + Math.sqrt(origin.areaInSquareMeters) / 2.0 - ) - } else { - destination.coord - } - getSkimDefaultValue( - mode, - origin.coord, - adjustedDestCoord, - timeBin * 3600, - dummyId - ) - } - } - val weights = individualSkims.map(sk => Math.max(sk.count, 1).toDouble) - val sumWeights = weights.sum - val weightedDistance = individualSkims.map(_.distance).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights - val weightedTime = individualSkims.map(_.time).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights - val weightedGeneralizedTime = individualSkims - .map(_.generalizedTime) - .zip(weights) - .map(tup => tup._1 * tup._2) - .sum / sumWeights - val weightedCost = individualSkims.map(_.cost).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights - val weightedGeneralizedCost = individualSkims - .map(_.generalizedCost) - .zip(weights) - .map(tup => tup._1 * tup._2) - .sum / sumWeights - val weightedEnergy = individualSkims.map(_.energy).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights - - ExcerptData( - timePeriodString = timePeriodString, - mode = mode, - originTazId = origin.tazId, - destinationTazId = destination.tazId, - weightedTime = weightedTime, - weightedGeneralizedTime = weightedGeneralizedTime, - weightedCost = weightedCost, - weightedGeneralizedCost = weightedGeneralizedCost, - weightedDistance = weightedDistance, - sumWeights = sumWeights, - weightedEnergy = weightedEnergy - ) - } - - def writeAllModeSkimsForPeakNonPeakPeriods(event: IterationEndsEvent): Unit = { - val morningPeakHours = (7 to 8).toList - val afternoonPeakHours = (15 to 16).toList - val nonPeakHours = (0 to 6).toList ++ (9 to 14).toList ++ (17 to 23).toList - val modes = BeamMode.allModes - val fileHeader = - "period,mode,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,numObservations,energy" - val filePath = event.getServices.getControlerIO.getIterationFilename( - event.getServices.getIterationNumber, - BeamSkimmer.excerptSkimsFileBaseName + ".csv.gz" - ) - val dummyId = Id.create( - beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, - classOf[BeamVehicleType] - ) - val writer = IOUtils.getBufferedWriter(filePath) - writer.write(fileHeader) - writer.write(Eol) - - val weightedSkims = ProfilingUtils.timed("Get weightedSkims for modes", x => logger.info(x)) { - modes.toParArray.flatMap { mode => - tazTreeMap.getTAZs.flatMap { origin => - tazTreeMap.getTAZs.flatMap { destination => - val am = getExcerptData( - "AM", - morningPeakHours, - origin, - destination, - mode, - dummyId - ) - val pm = getExcerptData( - "PM", - afternoonPeakHours, - origin, - destination, - mode, - dummyId - ) - val offPeak = getExcerptData( - "OffPeak", - nonPeakHours, - origin, - destination, - mode, - dummyId - ) - List(am, pm, offPeak) - } - } - } - } - logger.info(s"weightedSkims size: ${weightedSkims.size}") - - weightedSkims.seq.foreach { ws: ExcerptData => - writer.write( - s"${ws.timePeriodString},${ws.mode},${ws.originTazId},${ws.destinationTazId},${ws.weightedTime},${ws.weightedGeneralizedTime},${ws.weightedCost},${ws.weightedGeneralizedCost},${ws.weightedDistance},${ws.sumWeights},${ws.weightedEnergy}\n" - ) - } - writer.close() - } - - def writeFullSkims(event: IterationEndsEvent): Unit = { - val filePath = event.getServices.getControlerIO.getIterationFilename( - event.getServices.getIterationNumber, - BeamSkimmer.fullSkimsFileBaseName + ".csv.gz" - ) - val uniqueModes = skims.map(keyVal => keyVal._1._2).toList.distinct - val uniqueTimeBins = 0 to 23 - - val dummyId = Id.create( - beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, - classOf[BeamVehicleType] - ) - - val writer = IOUtils.getBufferedWriter(filePath) - writer.write(CsvLineHeader) - - tazTreeMap.getTAZs - .foreach { origin => - tazTreeMap.getTAZs.foreach { destination => - uniqueModes.foreach { mode => - uniqueTimeBins - .foreach { timeBin => - val theSkim: Skim = getSkimValue(timeBin * 3600, mode, origin.tazId, destination.tazId) - .map(_.toSkimExternal) - .getOrElse { - if (origin.equals(destination)) { - val newDestCoord = new Coord( - origin.coord.getX, - origin.coord.getY + Math.sqrt(origin.areaInSquareMeters) / 2.0 - ) - getSkimDefaultValue( - mode, - origin.coord, - newDestCoord, - timeBin * 3600, - dummyId - ) - } else { - getSkimDefaultValue( - mode, - origin.coord, - destination.coord, - timeBin * 3600, - dummyId - ) - } - } - - writer.write( - s"$timeBin,$mode,${origin.tazId},${destination.tazId},${theSkim.time},${theSkim.generalizedTime},${theSkim.cost},${theSkim.generalizedTime},${theSkim.distance},${theSkim.count},${theSkim.energy}$Eol" - ) - } - } - } - } - writer.close() - } - - def writeObservedSkims(event: IterationEndsEvent): Unit = { - val filePath = event.getServices.getControlerIO.getIterationFilename( - event.getServices.getIterationNumber, - BeamSkimmer.observedSkimsFileBaseName + ".csv.gz" - ) - val writer = IOUtils.getBufferedWriter(filePath) - try { - toCsv(skims).foreach(writer.write) - } finally { - writer.close() - } - } - - // ********** - // Skim Plus - private var trackSkimsPlusTS = -1 - private var previousSkimsPlus: TrieMap[BeamSkimmerPlusKey, Double] = - initialPreviousSkimsPlus() - private var skimsPlus: TrieMap[BeamSkimmerPlusKey, Double] = TrieMap() - private def skimsPlusFilePath: Option[String] = { - val filePath = beamConfig.beam.warmStart.skimsPlusFilePath - if (new File(filePath).isFile) { - Some(filePath) - } else { - None - } - } - - def getPreviousSkimPlusValues( - fromBin: Int, - untilBin: Int, - taz: Id[TAZ], - vehicleManager: Id[VehicleManager], - label: Label - ): Vector[Double] = { - (fromBin until untilBin) - .map { i => - previousSkimsPlus.get((i, taz, vehicleManager, label)) - } - .toVector - .flatten - } - - def countEventsByTAZ( - curBin: Int, - location: Coord, - vehicleManager: Id[VehicleManager], - label: Label, - count: Int = 1 - ): Unit = { - if (curBin > trackSkimsPlusTS) trackSkimsPlusTS = curBin - val taz = tazTreeMap.getTAZ(location.getX, location.getY) - val key = (trackSkimsPlusTS, taz.tazId, vehicleManager, label) - skimsPlus.put(key, skimsPlus.getOrElse(key, 0.0) + count.toDouble) - } - - def countEvents( - curBin: Int, - tazId: Id[TAZ], - vehicleManager: Id[VehicleManager], - label: Label, - count: Int = 1 - ): Unit = { - if (curBin > trackSkimsPlusTS) trackSkimsPlusTS = curBin - val key = (trackSkimsPlusTS, tazId, vehicleManager, label) - skimsPlus.put(key, skimsPlus.getOrElse(key, 0.0) + count.toDouble) - } - - def addValue( - curBin: Int, - tazId: Id[TAZ], - vehicleManager: Id[VehicleManager], - label: Label, - value: Double - ): Option[Double] = { - if (curBin > trackSkimsPlusTS) trackSkimsPlusTS = curBin - val key = (trackSkimsPlusTS, tazId, vehicleManager, label) - skimsPlus.put(key, skimsPlus.getOrElse(key, 0.0) + value) - } - - def observeVehicleAvailabilityByTAZ( - curBin: Int, - vehicleManager: Id[VehicleManager], - label: Label, - vehicles: List[Any] - ): Unit = { - var filteredVehicles = vehicles - tazTreeMap.getTAZs.foreach { taz => - val filteredVehiclesTemp = filteredVehicles.filter( - v => - taz != tazTreeMap - .getTAZ(v.asInstanceOf[BeamVehicle].spaceTime.loc.getX, v.asInstanceOf[BeamVehicle].spaceTime.loc.getY) - ) - val count = filteredVehicles.size - filteredVehiclesTemp.size - countEventsByTAZ(curBin, taz.coord, vehicleManager, label, count) - filteredVehicles = filteredVehiclesTemp - } - } - - def writeObservedSkimsPlus(event: IterationEndsEvent): Unit = { - val filePath = event.getServices.getControlerIO.getIterationFilename( - event.getServices.getIterationNumber, - BeamSkimmer.observedSkimsPlusFileBaseName - ) - val writer = IOUtils.getBufferedWriter(filePath) - writer.write(observedSkimsPlusHeader.mkString(",")) - writer.write("\n") - - skimsPlus.foreach { - case (k, v) => - val (bin, taz, vehicleManager, label) = k - writer.write(s"$bin,$taz,$vehicleManager,$label,$v\n") - } - writer.close() - } - - private def initialPreviousSkimsPlus(): TrieMap[BeamSkimmerPlusKey, Double] = { - if (beamConfig.beam.warmStart.enabled) { - try { - skimsPlusFilePath - .map(BeamSkimmer.readSkimPlusFile) - .getOrElse(TrieMap.empty) - } catch { - case NonFatal(ex) => - logger.error(s"Could not load previous skim from '$skimsPlusFilePath': ${ex.getMessage}", ex) - TrieMap.empty - } - } else { - TrieMap.empty - } - } - // ********* -} - -object BeamSkimmer extends LazyLogging { - type BeamSkimmerKey = (Int, BeamMode, Id[TAZ], Id[TAZ]) - type BeamSkimmerADT = TrieMap[BeamSkimmerKey, SkimInternal] - - val Eol = "\n" - - val CsvLineHeader: String = - "hour,mode,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,numObservations,energy" + Eol - - val observedSkimsFileBaseName = "skims" - val fullSkimsFileBaseName = "skimsFull" - val excerptSkimsFileBaseName = "skimsExcerpt" - - // 22.2 mph (9.924288 meter per second), is the average speed in cities - //TODO better estimate can be drawn from city size - // source: https://www.mitpressjournals.org/doi/abs/10.1162/rest_a_00744 - private val carSpeedMeterPerSec: Double = 9.924288 - // 12.1 mph (5.409184 meter per second), is average bus speed - // source: https://www.apta.com/resources/statistics/Documents/FactBook/2017-APTA-Fact-Book.pdf - // assuming for now that it includes the headway - private val transitSpeedMeterPerSec: Double = 5.409184 - private val bicycleSpeedMeterPerSec: Double = 3 - // 3.1 mph -> 1.38 meter per second - private val walkSpeedMeterPerSec: Double = 1.38 - // 940.6 Traffic Signal Spacing, Minor is 1,320 ft => 402.336 meters - private val trafficSignalSpacing: Double = 402.336 - // average waiting time at an intersection is 17.25 seconds - // source: https://pumas.nasa.gov/files/01_06_00_1.pdf - private val waitingTimeAtAnIntersection: Double = 17.25 - - val speedMeterPerSec: Map[BeamMode, Double] = Map( - CAV -> carSpeedMeterPerSec, - CAR -> carSpeedMeterPerSec, - WALK -> walkSpeedMeterPerSec, - BIKE -> bicycleSpeedMeterPerSec, - WALK_TRANSIT -> transitSpeedMeterPerSec, - DRIVE_TRANSIT -> transitSpeedMeterPerSec, - RIDE_HAIL -> carSpeedMeterPerSec, - RIDE_HAIL_POOLED -> carSpeedMeterPerSec, - RIDE_HAIL_TRANSIT -> transitSpeedMeterPerSec, - TRANSIT -> transitSpeedMeterPerSec - ) - - case class SkimInternal( - time: Double, - generalizedTime: Double, - generalizedCost: Double, - distance: Double, - cost: Double, - count: Int, - energy: Double - ) { - //NOTE: All times in seconds here - def toSkimExternal: Skim = Skim(time.toInt, generalizedTime, generalizedCost, distance, cost, count, energy) - } - - case class Skim( - time: Int, - generalizedTime: Double, - generalizedCost: Double, - distance: Double, - cost: Double, - count: Int, - energy: Double - ) - - case class ExcerptData( - timePeriodString: String, - mode: BeamMode, - originTazId: Id[TAZ], - destinationTazId: Id[TAZ], - weightedTime: Double, - weightedGeneralizedTime: Double, - weightedCost: Double, - weightedGeneralizedCost: Double, - weightedDistance: Double, - sumWeights: Double, - weightedEnergy: Double - ) - - private[router] def fromCsv(filePath: String): BeamSkimmerADT = { - val mapReader = new CsvMapReader(FileUtils.readerFromFile(filePath), CsvPreference.STANDARD_PREFERENCE) - val res = TrieMap[(Int, BeamMode, Id[TAZ], Id[TAZ]), SkimInternal]() - try { - val header = mapReader.getHeader(true) - var line: java.util.Map[String, String] = mapReader.read(header: _*) - while (null != line) { - val hour = line.get("hour") - val mode = line.get("mode") - val origTazId = line.get("origTaz") - val destTazId = line.get("destTaz") - val cost = line.get("cost") - val time = line.get("travelTimeInS") - val generalizedTime = line.get("generalizedTimeInS") - val generalizedCost = line.get("generalizedCost") - val distanceInMeters = line.get("distanceInM") - val numObservations = line.get("numObservations") - val energy = line.get("energy") - - val key = ( - hour.toInt, - BeamMode.fromString(mode.toLowerCase()).get, - Id.create(origTazId, classOf[TAZ]), - Id.create(destTazId, classOf[TAZ]), - ) - val value = - SkimInternal( - time.toDouble, - generalizedTime.toDouble, - generalizedCost.toDouble, - distanceInMeters.toDouble, - cost.toDouble, - numObservations.toInt, - Option(energy).map(_.toDouble).getOrElse(0.0) - ) - res.put(key, value) - line = mapReader.read(header: _*) - } - - } finally { - if (null != mapReader) - mapReader.close() - } - res - } - - private[router] def toCsv(content: BeamSkimmerADT): Iterator[String] = { - val contentIterator = content.toIterator - .map { keyVal => - Seq( - keyVal._1._1, - keyVal._1._2, - keyVal._1._3, - keyVal._1._4, - keyVal._2.time, - keyVal._2.generalizedTime, - keyVal._2.cost, - keyVal._2.generalizedCost, - keyVal._2.distance, - keyVal._2.count, - keyVal._2.energy - ).mkString("", ",", Eol) - } - Iterator.single(CsvLineHeader) ++ contentIterator - } - - // ******* - // Skim Plus - type BeamSkimmerPlusKey = (TimeBin, Id[TAZ], Id[VehicleManager], Label) - type TimeBin = Int - type Label = String - - private val observedSkimsPlusFileBaseName = "skimsPlus.csv.gz" - private val observedSkimsPlusHeader = "time,taz,manager,label,value".split(",") - - private def readSkimPlusFile(filePath: String): TrieMap[(TimeBin, Id[TAZ], Id[VehicleManager], Label), Double] = { - val mapReader = new CsvMapReader(FileUtils.readerFromFile(filePath), CsvPreference.STANDARD_PREFERENCE) - val res = TrieMap[(TimeBin, Id[TAZ], Id[VehicleManager], Label), Double]() - try { - val header = mapReader.getHeader(true) - var line: java.util.Map[String, String] = mapReader.read(header: _*) - while (null != line) { - val time = line.get("time") - val tazId = line.get("taz") - val manager = line.get("manager") - val label = line.get("label") - val value = line.get("value").toDouble - res.put( - (time.toInt, Id.create(tazId, classOf[TAZ]), Id.create(manager, classOf[VehicleManager]), label), - value - ) - line = mapReader.read(header: _*) - } - } finally { - if (null != mapReader) - mapReader.close() - } - res - } - // ******** -} diff --git a/src/main/scala/beam/router/skim/AbstractSkimmer.scala b/src/main/scala/beam/router/skim/AbstractSkimmer.scala new file mode 100644 index 00000000000..a91798f2dd2 --- /dev/null +++ b/src/main/scala/beam/router/skim/AbstractSkimmer.scala @@ -0,0 +1,170 @@ +package beam.router.skim + +import java.io.{BufferedWriter, File} + +import beam.agentsim.events.ScalaEvent +import beam.sim.BeamServices +import beam.sim.config.BeamConfig +import beam.utils.{FileUtils, ProfilingUtils} +import com.typesafe.scalalogging.LazyLogging +import org.matsim.api.core.v01.events.Event +import org.matsim.core.controler.events.{IterationEndsEvent, IterationStartsEvent} +import org.matsim.core.controler.listener.{IterationEndsListener, IterationStartsListener} +import org.matsim.core.events.handler.BasicEventHandler +import org.supercsv.io.CsvMapReader +import org.supercsv.prefs.CsvPreference + +import scala.collection.{immutable, mutable} +import scala.util.control.NonFatal + +trait AbstractSkimmerKey { + def toCsv: String +} + +trait AbstractSkimmerInternal { + val numObservations: Int + val numIteration: Int + def toCsv: String +} + +abstract class AbstractSkimmerEvent(eventTime: Double, beamServices: BeamServices) + extends Event(eventTime) + with ScalaEvent { + protected val skimName: String + def getKey: AbstractSkimmerKey + def getSkimmerInternal: AbstractSkimmerInternal + def getEventType: String = skimName + "-event" +} + +abstract class AbstractSkimmerReadOnly(beamServices: BeamServices) extends LazyLogging { + protected[skim] val pastSkims: mutable.ListBuffer[immutable.Map[AbstractSkimmerKey, AbstractSkimmerInternal]] = + mutable.ListBuffer() + protected[skim] var aggregatedSkim: immutable.Map[AbstractSkimmerKey, AbstractSkimmerInternal] = immutable.Map() +} + +abstract class AbstractSkimmer(beamServices: BeamServices, config: BeamConfig.Beam.Router.Skim) + extends BasicEventHandler + with IterationStartsListener + with IterationEndsListener + with LazyLogging { + import beamServices._ + + protected[skim] val readOnlySkim: AbstractSkimmerReadOnly + protected val skimFileBaseName: String + protected val skimFileHeader: String + protected val skimName: String + protected lazy val currentSkim = mutable.Map.empty[AbstractSkimmerKey, AbstractSkimmerInternal] + private lazy val eventType = skimName + "-event" + + protected def fromCsv(line: immutable.Map[String, String]): (AbstractSkimmerKey, AbstractSkimmerInternal) + protected def aggregateOverIterations( + prevIteration: Option[AbstractSkimmerInternal], + currIteration: Option[AbstractSkimmerInternal] + ): AbstractSkimmerInternal + protected def aggregateWithinAnIteration( + prevObservation: Option[AbstractSkimmerInternal], + currObservation: AbstractSkimmerInternal + ): AbstractSkimmerInternal + + override def notifyIterationStarts(event: IterationStartsEvent): Unit = { + if (event.getIteration == 0 && beamConfig.beam.warmStart.enabled) { + readOnlySkim.aggregatedSkim = readAggregatedSkims + } + } + + override def notifyIterationEnds(event: IterationEndsEvent): Unit = { + // keep in memory + if (beamConfig.beam.router.skim.keepKLatestSkims > 0) { + if (readOnlySkim.pastSkims.size == beamConfig.beam.router.skim.keepKLatestSkims) { + readOnlySkim.pastSkims.dropRight(1) + } + readOnlySkim.pastSkims.prepend(currentSkim.toMap) + } + // aggregate + readOnlySkim.aggregatedSkim = (readOnlySkim.aggregatedSkim.keySet ++ currentSkim.keySet).map { key => + key -> aggregateOverIterations(readOnlySkim.aggregatedSkim.get(key), currentSkim.get(key)) + }.toMap + // write + writeToDisk(event) + // clear + currentSkim.clear() + } + + override def handleEvent(event: Event): Unit = { + event match { + case e: AbstractSkimmerEvent if e.getEventType == eventType => + currentSkim.update(e.getKey, aggregateWithinAnIteration(currentSkim.get(e.getKey), e.getSkimmerInternal)) + case _ => + } + } + + protected def writeToDisk(event: IterationEndsEvent) = { + if (beamConfig.beam.router.skim.writeSkimsInterval > 0 && event.getIteration % beamConfig.beam.router.skim.writeSkimsInterval == 0) + ProfilingUtils.timed(s"beam.router.skim.writeSkimsInterval on iteration ${event.getIteration}", logger.info(_)) { + val filePath = + beamServices.matsimServices.getControlerIO + .getIterationFilename(event.getServices.getIterationNumber, skimFileBaseName + ".csv.gz") + writeSkim(currentSkim.toMap, filePath) + } + + if (beamConfig.beam.router.skim.writeAggregatedSkimsInterval > 0 && event.getIteration % beamConfig.beam.router.skim.writeAggregatedSkimsInterval == 0) { + ProfilingUtils.timed( + s"beam.router.skim.writeAggregatedSkimsInterval on iteration ${event.getIteration}", + logger.info(_) + ) { + val filePath = + beamServices.matsimServices.getControlerIO + .getIterationFilename(event.getServices.getIterationNumber, skimFileBaseName + "_Aggregated.csv.gz") + writeSkim(readOnlySkim.aggregatedSkim, filePath) + } + } + } + + // *** + // Helpers + private def readAggregatedSkims: immutable.Map[AbstractSkimmerKey, AbstractSkimmerInternal] = { + var mapReader: CsvMapReader = null + val res = mutable.Map.empty[AbstractSkimmerKey, AbstractSkimmerInternal] + val aggregatedSkimsFilePath = skimFileBaseName + "Aggregated.csv.gz" + try { + if (new File(aggregatedSkimsFilePath).isFile) { + mapReader = + new CsvMapReader(FileUtils.readerFromFile(aggregatedSkimsFilePath), CsvPreference.STANDARD_PREFERENCE) + val header = mapReader.getHeader(true) + var line: java.util.Map[String, String] = mapReader.read(header: _*) + while (null != line) { + import scala.collection.JavaConverters._ + val newPair = fromCsv(line.asScala.toMap) + res.put(newPair._1, newPair._2) + line = mapReader.read(header: _*) + } + logger.info(s"warmStart skim successfully loaded from path '${aggregatedSkimsFilePath}'") + } else { + logger.info(s"warmStart skim NO PATH FOUND '${aggregatedSkimsFilePath}'") + } + } catch { + case NonFatal(ex) => + logger.error(s"Could not load warmStart skim from '${aggregatedSkimsFilePath}': ${ex.getMessage}", ex) + } finally { + if (null != mapReader) + mapReader.close() + } + res.toMap + } + + private def writeSkim(skim: immutable.Map[AbstractSkimmerKey, AbstractSkimmerInternal], filePath: String) = { + var writer: BufferedWriter = null + try { + writer = org.matsim.core.utils.io.IOUtils.getBufferedWriter(filePath) + writer.write(skimFileHeader + "\n") + skim.foreach(row => writer.write(row._1.toCsv + "," + row._2.toCsv + "\n")) + writer.close() + } catch { + case NonFatal(ex) => + logger.error(s"Could not write skim in '${filePath}': ${ex.getMessage}", ex) + } finally { + if (null != writer) + writer.close() + } + } +} diff --git a/src/main/scala/beam/router/skim/DriveTimeSkimmer.scala b/src/main/scala/beam/router/skim/DriveTimeSkimmer.scala new file mode 100644 index 00000000000..9e98a3b8718 --- /dev/null +++ b/src/main/scala/beam/router/skim/DriveTimeSkimmer.scala @@ -0,0 +1,139 @@ +package beam.router.skim + +import beam.agentsim.infrastructure.taz.TAZ +import beam.router.Modes.BeamMode +import beam.router.Modes.BeamMode.CAR +import beam.sim.BeamServices +import beam.sim.config.BeamConfig +import com.typesafe.scalalogging.LazyLogging +import org.jfree.data.statistics.HistogramDataset +import org.matsim.api.core.v01.Id +import org.matsim.core.controler.events.IterationEndsEvent + +import scala.collection.mutable + +class DriveTimeSkimmer(beamServices: BeamServices, config: BeamConfig.Beam.Router.Skim) + extends AbstractSkimmer(beamServices, config) { + import SkimsUtils._ + import DriveTimeSkimmer._ + import beamServices._ + + val maxDistanceFromBeamTaz: Double = 500.0 // 500 meters + val uniqueModes: List[BeamMode.CAR.type] = List(CAR) + val uniqueTimeBins: Range.Inclusive = 0 to 23 + + override protected[skim] lazy val readOnlySkim: AbstractSkimmerReadOnly = DriveTimeSkims(beamServices) + override protected val skimFileBaseName: String = config.drive_time_skimmer.fileBaseName + override protected val skimFileHeader: String = + "fromTAZId,toTAZId,hour,timeSimulated,timeObserved,counts,numIteration" + override protected val skimName: String = config.drive_time_skimmer.name + private val chartName: String = "scatterplot_simulation_vs_reference.png" + private val histogramName: String = "simulation_vs_reference_histogram.png" + private val histogramBinSize: Int = 200 + private lazy val observedTravelTimes = buildObservedODTravelTime(beamServices, maxDistanceFromBeamTaz) + + override def notifyIterationEnds(event: IterationEndsEvent): Unit = { + var series = new mutable.ListBuffer[(Int, Double, Double)]() + val categoryDataset = new HistogramDataset() + var deltasOfObservedSimulatedTimes = new mutable.ListBuffer[Double] + if (observedTravelTimes.nonEmpty) { + beamScenario.tazTreeMap.getTAZs + .foreach { origin => + beamScenario.tazTreeMap.getTAZs.foreach { destination => + uniqueModes.foreach { _ => + uniqueTimeBins.foreach { timeBin => + val key = PathCache(origin.tazId, destination.tazId, timeBin) + observedTravelTimes.get(key).foreach { timeObserved => + val theSkimKey = DriveTimeSkimmerKey(origin.tazId, destination.tazId, timeBin * 3600) + currentSkim.get(theSkimKey).map(_.asInstanceOf[DriveTimeSkimmerInternal]).foreach { theSkimInternal => + series += ((theSkimInternal.numObservations, theSkimInternal.timeSimulated, timeObserved)) + for (_ <- 1 to theSkimInternal.numObservations) + deltasOfObservedSimulatedTimes += theSkimInternal.timeSimulated - timeObserved + currentSkim.update(theSkimKey, theSkimInternal.copy(timeObserved = timeObserved)) + } + } + } + } + } + } + categoryDataset.addSeries("Simulated-Observed", deltasOfObservedSimulatedTimes.toArray, histogramBinSize) + val chartPath = + event.getServices.getControlerIO.getIterationFilename(event.getServices.getIterationNumber, chartName) + generateChart(series, chartPath) + val histogramPath = + event.getServices.getControlerIO.getIterationFilename(event.getServices.getIterationNumber, histogramName) + generateHistogram(categoryDataset, histogramPath) + } else { + logger.warn(s"the skimmer $skimName does not have access to the observed travel time for calibration") + } + + super.notifyIterationEnds(event) + } + + override protected def fromCsv(line: Map[String, String]): (AbstractSkimmerKey, AbstractSkimmerInternal) = { + ( + DriveTimeSkimmerKey( + fromTAZId = Id.create(line("fromTAZId"), classOf[TAZ]), + toTAZId = Id.create(line("toTAZId"), classOf[TAZ]), + hour = line("hour").toInt + ), + DriveTimeSkimmerInternal( + timeSimulated = line("timeSimulated").toDouble, + timeObserved = line("timeObserved").toDouble, + numObservations = line("counts").toInt, + numIteration = line("numIteration").toInt + ) + ) + } + + override protected def aggregateOverIterations( + prevIteration: Option[AbstractSkimmerInternal], + currIteration: Option[AbstractSkimmerInternal] + ): AbstractSkimmerInternal = { + val prevSkim = prevIteration + .map(_.asInstanceOf[DriveTimeSkimmerInternal]) + .getOrElse(DriveTimeSkimmerInternal(0, 0, numObservations = 0, numIteration = 0)) // no skim means no observation + val currSkim = currIteration + .map(_.asInstanceOf[DriveTimeSkimmerInternal]) + .getOrElse(DriveTimeSkimmerInternal(0, 0, numObservations = 0, numIteration = 1)) // no current skim means 0 observation + DriveTimeSkimmerInternal( + timeSimulated = (prevSkim.timeSimulated * prevSkim.numIteration + currSkim.timeSimulated * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + timeObserved = if (currSkim.timeObserved != 0) currSkim.timeObserved else prevSkim.timeObserved, + numObservations = (prevSkim.numObservations * prevSkim.numIteration + currSkim.numObservations * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + numIteration = prevSkim.numIteration + currSkim.numIteration + ) + } + + protected def aggregateWithinAnIteration( + prevObservation: Option[AbstractSkimmerInternal], + currObservation: AbstractSkimmerInternal + ): AbstractSkimmerInternal = { + val prevSkim = prevObservation + .map(_.asInstanceOf[DriveTimeSkimmerInternal]) + .getOrElse(DriveTimeSkimmerInternal(0, 0, numObservations = 0, numIteration = 0)) + val currSkim = currObservation.asInstanceOf[DriveTimeSkimmerInternal] + DriveTimeSkimmerInternal( + timeSimulated = (prevSkim.timeSimulated * prevSkim.numObservations + currSkim.timeSimulated * currSkim.numObservations) / (prevSkim.numObservations + currSkim.numObservations), + timeObserved = if (currSkim.timeObserved != 0) currSkim.timeObserved else prevSkim.timeObserved, + numObservations = prevSkim.numObservations + currSkim.numObservations, + numIteration = beamServices.matsimServices.getIterationNumber + 1 + ) + } +} + +object DriveTimeSkimmer extends LazyLogging { + + case class DriveTimeSkimmerKey(fromTAZId: Id[TAZ], toTAZId: Id[TAZ], hour: Int) extends AbstractSkimmerKey { + override def toCsv: String = fromTAZId + "," + toTAZId + "," + hour + } + + case class DriveTimeSkimmerInternal( + timeSimulated: Double, + timeObserved: Double, + numObservations: Int = 1, + numIteration: Int = 0 + ) extends AbstractSkimmerInternal { + override def toCsv: String = timeSimulated + "," + timeObserved + "," + numObservations + "," + numIteration + } + +} diff --git a/src/main/scala/beam/router/skim/DriveTimeSkimmerEvent.scala b/src/main/scala/beam/router/skim/DriveTimeSkimmerEvent.scala new file mode 100644 index 00000000000..0dbc3285036 --- /dev/null +++ b/src/main/scala/beam/router/skim/DriveTimeSkimmerEvent.scala @@ -0,0 +1,42 @@ +package beam.router.skim + +import beam.router.Modes.BeamMode +import beam.router.model.{EmbodiedBeamLeg, EmbodiedBeamTrip} +import beam.router.skim.DriveTimeSkimmer.{DriveTimeSkimmerInternal, DriveTimeSkimmerKey} +import beam.sim.BeamServices + +case class DriveTimeSkimmerEvent(eventTime: Double, beamServices: BeamServices, carLeg: EmbodiedBeamLeg) + extends AbstractSkimmerEvent(eventTime, beamServices) { + override protected val skimName: String = + beamServices.beamConfig.beam.router.skim.drive_time_skimmer.name + override def getKey: AbstractSkimmerKey = key + override def getSkimmerInternal: AbstractSkimmerInternal = skimInternal + + val (key, skimInternal) = observeTrip(carLeg) + + private def observeTrip(carLeg: EmbodiedBeamLeg): (DriveTimeSkimmerKey, DriveTimeSkimmerInternal) = { + import beamServices._ + + // In case of `CAV` we have to override its mode to `CAR` + val fixedCarLeg = if (carLeg.beamLeg.mode == BeamMode.CAV) { + carLeg.copy(beamLeg = carLeg.beamLeg.copy(mode = BeamMode.CAR)) + } else { + carLeg + } + val carTrip = EmbodiedBeamTrip(Vector(fixedCarLeg)) + val origCoord = geo.wgs2Utm(carTrip.beamLegs.head.travelPath.startPoint.loc) + val origTaz = beamScenario.tazTreeMap + .getTAZ(origCoord.getX, origCoord.getY) + .tazId + val destCoord = geo.wgs2Utm(carTrip.beamLegs.last.travelPath.endPoint.loc) + val destTaz = beamScenario.tazTreeMap + .getTAZ(destCoord.getX, destCoord.getY) + .tazId + val hour = SkimsUtils.timeToBin(carTrip.beamLegs.head.startTime) + val timeSimulated = carTrip.totalTravelTimeInSecs.toDouble + + val skimmerKey = DriveTimeSkimmerKey(origTaz, destTaz, hour) + val skimmerInternal = DriveTimeSkimmerInternal(timeSimulated = timeSimulated, timeObserved = 0) + (skimmerKey, skimmerInternal) + } +} diff --git a/src/main/scala/beam/router/skim/DriveTimeSkims.scala b/src/main/scala/beam/router/skim/DriveTimeSkims.scala new file mode 100644 index 00000000000..a691dc2e153 --- /dev/null +++ b/src/main/scala/beam/router/skim/DriveTimeSkims.scala @@ -0,0 +1,5 @@ +package beam.router.skim + +import beam.sim.BeamServices + +case class DriveTimeSkims(beamServices: BeamServices) extends AbstractSkimmerReadOnly(beamServices) {} diff --git a/src/main/scala/beam/router/skim/ODSkimmer.scala b/src/main/scala/beam/router/skim/ODSkimmer.scala new file mode 100644 index 00000000000..720faed58f1 --- /dev/null +++ b/src/main/scala/beam/router/skim/ODSkimmer.scala @@ -0,0 +1,368 @@ +package beam.router.skim +import java.io.BufferedWriter + +import beam.agentsim.agents.vehicles.BeamVehicleType +import beam.agentsim.infrastructure.taz.TAZ +import beam.router.Modes.BeamMode +import beam.sim.BeamServices +import beam.sim.config.BeamConfig +import beam.utils.ProfilingUtils +import com.typesafe.scalalogging.LazyLogging +import org.matsim.api.core.v01.{Coord, Id} +import org.matsim.core.controler.events.IterationEndsEvent + +import scala.collection.immutable +import scala.util.control.NonFatal + +class ODSkimmer(beamServices: BeamServices, config: BeamConfig.Beam.Router.Skim) + extends AbstractSkimmer(beamServices, config) { + import ODSkimmer._ + import beamServices._ + + override lazy val readOnlySkim: AbstractSkimmerReadOnly = ODSkims(beamServices) + + override protected val skimName: String = config.origin_destination_skimmer.name + override protected val skimFileBaseName: String = config.origin_destination_skimmer.fileBaseName + override protected val skimFileHeader: String = + "hour,mode,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,energy,numObservations,numIteration" + + override def writeToDisk(event: IterationEndsEvent): Unit = { + super.writeToDisk(event) + if (config.origin_destination_skimmer.writeAllModeSkimsForPeakNonPeakPeriodsInterval > 0 && event.getIteration % config.origin_destination_skimmer.writeAllModeSkimsForPeakNonPeakPeriodsInterval == 0) { + ProfilingUtils.timed(s"writeAllModeSkimsForPeakNonPeakPeriods on iteration ${event.getIteration}", logger.info(_)) { + writeAllModeSkimsForPeakNonPeakPeriods(event) + } + } + if (config.origin_destination_skimmer.writeFullSkimsInterval > 0 && event.getIteration % config.origin_destination_skimmer.writeFullSkimsInterval == 0) { + ProfilingUtils.timed(s"writeFullSkims on iteration ${event.getIteration}", logger.info(_)) { + writeFullSkims(event) + } + } + } + + override def fromCsv( + row: immutable.Map[String, String] + ): (AbstractSkimmerKey, AbstractSkimmerInternal) = { + ( + ODSkimmerKey( + hour = row("hour").toInt, + mode = BeamMode.fromString(row("mode").toLowerCase()).get, + originTaz = Id.create(row("origTaz"), classOf[TAZ]), + destinationTaz = Id.create(row("destTaz"), classOf[TAZ]) + ), + ODSkimmerInternal( + travelTimeInS = row("travelTimeInS").toDouble, + generalizedTimeInS = row("generalizedTimeInS").toDouble, + generalizedCost = row("generalizedCost").toDouble, + distanceInM = row("distanceInM").toDouble, + cost = row("cost").toDouble, + energy = Option(row("energy")).map(_.toDouble).getOrElse(0.0), + numObservations = row("numObservations").toInt, + numIteration = row("numIteration").toInt + ) + ) + } + + override protected def aggregateOverIterations( + prevIteration: Option[AbstractSkimmerInternal], + currIteration: Option[AbstractSkimmerInternal] + ): AbstractSkimmerInternal = { + val prevSkim = prevIteration + .map(_.asInstanceOf[ODSkimmerInternal]) + .getOrElse(ODSkimmerInternal(0, 0, 0, 0, 0, 0, numObservations = 0, numIteration = 0)) + val currSkim = + currIteration + .map(_.asInstanceOf[ODSkimmerInternal]) + .getOrElse(ODSkimmerInternal(0, 0, 0, 0, 0, 0, numObservations = 0, numIteration = 1)) + ODSkimmerInternal( + travelTimeInS = (prevSkim.travelTimeInS * prevSkim.numIteration + currSkim.travelTimeInS * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + generalizedTimeInS = (prevSkim.generalizedTimeInS * prevSkim.numIteration + currSkim.generalizedTimeInS * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + generalizedCost = (prevSkim.generalizedCost * prevSkim.numIteration + currSkim.generalizedCost * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + distanceInM = (prevSkim.distanceInM * prevSkim.numIteration + currSkim.distanceInM * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + cost = (prevSkim.cost * prevSkim.numIteration + currSkim.cost * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + energy = (prevSkim.energy * prevSkim.numIteration + currSkim.energy * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + numObservations = (prevSkim.numObservations * prevSkim.numIteration + currSkim.numObservations * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + numIteration = prevSkim.numIteration + currSkim.numIteration + ) + } + + override protected def aggregateWithinAnIteration( + prevObservation: Option[AbstractSkimmerInternal], + currObservation: AbstractSkimmerInternal + ): AbstractSkimmerInternal = { + val prevSkim = prevObservation + .map(_.asInstanceOf[ODSkimmerInternal]) + .getOrElse(ODSkimmerInternal(0, 0, 0, 0, 0, 0, numObservations = 0, numIteration = 0)) + val currSkim = currObservation.asInstanceOf[ODSkimmerInternal] + ODSkimmerInternal( + travelTimeInS = (prevSkim.travelTimeInS * prevSkim.numObservations + currSkim.travelTimeInS * currSkim.numObservations) / (prevSkim.numObservations + currSkim.numObservations), + generalizedTimeInS = (prevSkim.generalizedTimeInS * prevSkim.numObservations + currSkim.generalizedTimeInS * currSkim.numObservations) / (prevSkim.numObservations + currSkim.numObservations), + generalizedCost = (prevSkim.generalizedCost * prevSkim.numObservations + currSkim.generalizedCost * currSkim.numObservations) / (prevSkim.numObservations + currSkim.numObservations), + distanceInM = (prevSkim.distanceInM * prevSkim.numObservations + currSkim.distanceInM * currSkim.numObservations) / (prevSkim.numObservations + currSkim.numObservations), + cost = (prevSkim.cost * prevSkim.numObservations + currSkim.cost * currSkim.numObservations) / (prevSkim.numObservations + currSkim.numObservations), + energy = (prevSkim.energy * prevSkim.numObservations + currSkim.energy * currSkim.numObservations) / (prevSkim.numObservations + currSkim.numObservations), + numObservations = prevSkim.numObservations + currSkim.numObservations, + numIteration = beamServices.matsimServices.getIterationNumber + 1 + ) + } + + // ***** + // Helpers + private def writeAllModeSkimsForPeakNonPeakPeriods(event: IterationEndsEvent): Unit = { + val morningPeakHours = (7 to 8).toList + val afternoonPeakHours = (15 to 16).toList + val nonPeakHours = (0 to 6).toList ++ (9 to 14).toList ++ (17 to 23).toList + val modes = BeamMode.allModes + val fileHeader = + "period,mode,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,numObservations,energy" + val filePath = event.getServices.getControlerIO.getIterationFilename( + event.getServices.getIterationNumber, + skimFileBaseName + "Excerpt.csv.gz" + ) + val dummyId = Id.create( + beamScenario.beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, + classOf[BeamVehicleType] + ) + var writer: BufferedWriter = null + try { + writer = org.matsim.core.utils.io.IOUtils.getBufferedWriter(filePath) + writer.write(fileHeader) + writer.write("\n") + + val weightedSkims = ProfilingUtils.timed("Get weightedSkims for modes", x => logger.info(x)) { + modes.toParArray.flatMap { mode => + beamScenario.tazTreeMap.getTAZs.flatMap { origin => + beamScenario.tazTreeMap.getTAZs.flatMap { destination => + val am = getExcerptData( + "AM", + morningPeakHours, + origin, + destination, + mode, + dummyId + ) + val pm = getExcerptData( + "PM", + afternoonPeakHours, + origin, + destination, + mode, + dummyId + ) + val offPeak = getExcerptData( + "OffPeak", + nonPeakHours, + origin, + destination, + mode, + dummyId + ) + List(am, pm, offPeak) + } + } + } + } + logger.info(s"weightedSkims size: ${weightedSkims.size}") + + weightedSkims.seq.foreach { ws: ExcerptData => + writer.write( + s"${ws.timePeriodString},${ws.mode},${ws.originTazId},${ws.destinationTazId},${ws.weightedTime},${ws.weightedGeneralizedTime},${ws.weightedCost},${ws.weightedGeneralizedCost},${ws.weightedDistance},${ws.sumWeights},${ws.weightedEnergy}\n" + ) + } + } catch { + case NonFatal(ex) => + logger.error(s"Could not write skim in '${filePath}': ${ex.getMessage}", ex) + } finally { + if (null != writer) + writer.close() + } + } + + private def writeFullSkims(event: IterationEndsEvent): Unit = { + val filePath = event.getServices.getControlerIO.getIterationFilename( + event.getServices.getIterationNumber, + skimFileBaseName + "Full.csv.gz" + ) + val uniqueModes = currentSkim.map(keyVal => keyVal.asInstanceOf[ODSkimmerKey].mode).toList.distinct + val uniqueTimeBins = 0 to 23 + + val dummyId = Id.create( + beamScenario.beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, + classOf[BeamVehicleType] + ) + + var writer: BufferedWriter = null + try { + writer = org.matsim.core.utils.io.IOUtils.getBufferedWriter(filePath) + writer.write(skimFileHeader + "\n") + + beamScenario.tazTreeMap.getTAZs + .foreach { origin => + beamScenario.tazTreeMap.getTAZs.foreach { destination => + uniqueModes.foreach { mode => + uniqueTimeBins + .foreach { timeBin => + val theSkim: ODSkimmer.Skim = currentSkim + .get(ODSkimmerKey(timeBin, mode, origin.tazId, destination.tazId)) + .map(_.asInstanceOf[ODSkimmerInternal].toSkimExternal) + .getOrElse { + if (origin.equals(destination)) { + val newDestCoord = new Coord( + origin.coord.getX, + origin.coord.getY + Math.sqrt(origin.areaInSquareMeters) / 2.0 + ) + readOnlySkim + .asInstanceOf[ODSkims] + .getSkimDefaultValue( + mode, + origin.coord, + newDestCoord, + timeBin * 3600, + dummyId, + beamServices + ) + } else { + readOnlySkim + .asInstanceOf[ODSkims] + .getSkimDefaultValue( + mode, + origin.coord, + destination.coord, + timeBin * 3600, + dummyId, + beamServices + ) + } + } + + writer.write( + s"$timeBin,$mode,${origin.tazId},${destination.tazId},${theSkim.time},${theSkim.generalizedTime},${theSkim.cost},${theSkim.generalizedTime},${theSkim.distance},${theSkim.count},${theSkim.energy}\n" + ) + } + } + } + } + } catch { + case NonFatal(ex) => + logger.error(s"Could not write skim in '${filePath}': ${ex.getMessage}", ex) + } finally { + if (null != writer) + writer.close() + } + } + + def getExcerptData( + timePeriodString: String, + hoursIncluded: List[Int], + origin: TAZ, + destination: TAZ, + mode: BeamMode, + dummyId: Id[BeamVehicleType] + ): ExcerptData = { + import scala.language.implicitConversions + val individualSkims = hoursIncluded.map { timeBin => + currentSkim + .get(ODSkimmerKey(timeBin, mode, origin.tazId, destination.tazId)) + .map(_.asInstanceOf[ODSkimmerInternal].toSkimExternal) + .getOrElse { + val adjustedDestCoord = if (origin.equals(destination)) { + new Coord( + origin.coord.getX, + origin.coord.getY + Math.sqrt(origin.areaInSquareMeters) / 2.0 + ) + } else { + destination.coord + } + readOnlySkim + .asInstanceOf[ODSkims] + .getSkimDefaultValue( + mode, + origin.coord, + adjustedDestCoord, + timeBin * 3600, + dummyId, + beamServices + ) + } + } + val weights = individualSkims.map(sk => Math.max(sk.count, 1).toDouble) + val sumWeights = weights.sum + val weightedDistance = individualSkims.map(_.distance).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights + val weightedTime = individualSkims.map(_.time).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights + val weightedGeneralizedTime = individualSkims + .map(_.generalizedTime) + .zip(weights) + .map(tup => tup._1 * tup._2) + .sum / sumWeights + val weightedCost = individualSkims.map(_.cost).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights + val weightedGeneralizedCost = individualSkims + .map(_.generalizedCost) + .zip(weights) + .map(tup => tup._1 * tup._2) + .sum / sumWeights + val weightedEnergy = individualSkims.map(_.energy).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights + + ExcerptData( + timePeriodString = timePeriodString, + mode = mode, + originTazId = origin.tazId, + destinationTazId = destination.tazId, + weightedTime = weightedTime, + weightedGeneralizedTime = weightedGeneralizedTime, + weightedCost = weightedCost, + weightedGeneralizedCost = weightedGeneralizedCost, + weightedDistance = weightedDistance, + sumWeights = sumWeights, + weightedEnergy = weightedEnergy + ) + } +} + +object ODSkimmer extends LazyLogging { + // cases + case class ODSkimmerKey(hour: Int, mode: BeamMode, originTaz: Id[TAZ], destinationTaz: Id[TAZ]) + extends AbstractSkimmerKey { + override def toCsv: String = hour + "," + mode + "," + originTaz + "," + destinationTaz + } + case class ODSkimmerInternal( + travelTimeInS: Double, + generalizedTimeInS: Double, + generalizedCost: Double, + distanceInM: Double, + cost: Double, + energy: Double, + numObservations: Int = 1, + numIteration: Int = 0 + ) extends AbstractSkimmerInternal { + + //NOTE: All times in seconds here + def toSkimExternal: Skim = + Skim(travelTimeInS.toInt, generalizedTimeInS, generalizedCost, distanceInM, cost, numObservations, energy) + override def toCsv: String = + travelTimeInS + "," + generalizedTimeInS + "," + cost + "," + generalizedCost + "," + distanceInM + "," + energy + "," + numObservations + "," + numIteration + } + + case class Skim( + time: Int, + generalizedTime: Double, + generalizedCost: Double, + distance: Double, + cost: Double, + count: Int, + energy: Double + ) + + case class ExcerptData( + timePeriodString: String, + mode: BeamMode, + originTazId: Id[TAZ], + destinationTazId: Id[TAZ], + weightedTime: Double, + weightedGeneralizedTime: Double, + weightedCost: Double, + weightedGeneralizedCost: Double, + weightedDistance: Double, + sumWeights: Double, + weightedEnergy: Double + ) +} diff --git a/src/main/scala/beam/router/skim/ODSkimmerEvent.scala b/src/main/scala/beam/router/skim/ODSkimmerEvent.scala new file mode 100644 index 00000000000..8cc410ccc7d --- /dev/null +++ b/src/main/scala/beam/router/skim/ODSkimmerEvent.scala @@ -0,0 +1,62 @@ +package beam.router.skim + +import beam.router.Modes.BeamMode.WALK +import beam.router.model.EmbodiedBeamTrip +import beam.router.skim.ODSkimmer.{ODSkimmerInternal, ODSkimmerKey} +import beam.sim.BeamServices + +case class ODSkimmerEvent( + eventTime: Double, + beamServices: BeamServices, + trip: EmbodiedBeamTrip, + generalizedTimeInHours: Double, + generalizedCost: Double, + energyConsumption: Double +) extends AbstractSkimmerEvent(eventTime, beamServices) { + override protected val skimName: String = beamServices.beamConfig.beam.router.skim.origin_destination_skimmer.name + override def getKey: AbstractSkimmerKey = key + override def getSkimmerInternal: AbstractSkimmerInternal = skimInternal + + val (key, skimInternal) = observeTrip(trip, generalizedTimeInHours, generalizedCost, energyConsumption) + + private def observeTrip( + trip: EmbodiedBeamTrip, + generalizedTimeInHours: Double, + generalizedCost: Double, + energyConsumption: Double + ) = { + import beamServices._ + val mode = trip.tripClassifier + val correctedTrip = mode match { + case WALK => + trip + case _ => + val legs = trip.legs.drop(1).dropRight(1) + EmbodiedBeamTrip(legs) + } + 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 timeBin = SkimsUtils.timeToBin(origLeg.startTime) + val dist = beamLegs.map(_.travelPath.distanceInM).sum + val key = ODSkimmerKey(timeBin, mode, origTaz, destTaz) + val payload = + ODSkimmerInternal( + travelTimeInS = correctedTrip.totalTravelTimeInSecs.toDouble, + generalizedTimeInS = generalizedTimeInHours * 3600, + generalizedCost = generalizedCost, + distanceInM = if (dist > 0.0) { dist } else { 1.0 }, + cost = correctedTrip.costEstimate, + energy = energyConsumption + ) + (key, payload) + } +} diff --git a/src/main/scala/beam/router/skim/ODSkims.scala b/src/main/scala/beam/router/skim/ODSkims.scala new file mode 100644 index 00000000000..91ab4139811 --- /dev/null +++ b/src/main/scala/beam/router/skim/ODSkims.scala @@ -0,0 +1,213 @@ +package beam.router.skim + +import beam.agentsim.agents.choice.mode.DrivingCost +import beam.agentsim.agents.vehicles.BeamVehicleType +import beam.agentsim.infrastructure.taz.TAZ +import beam.router.BeamRouter.Location +import beam.router.Modes.BeamMode +import beam.router.Modes.BeamMode.{ + CAR, + CAV, + DRIVE_TRANSIT, + RIDE_HAIL, + RIDE_HAIL_POOLED, + RIDE_HAIL_TRANSIT, + TRANSIT, + WALK_TRANSIT +} +import beam.router.model.{BeamLeg, BeamPath} +import beam.router.skim.ODSkimmer.{ExcerptData, ODSkimmerInternal, ODSkimmerKey, Skim} +import beam.router.skim.SkimsUtils.{distanceAndTime, getRideHailCost, timeToBin} +import beam.sim.BeamServices +import org.matsim.api.core.v01.{Coord, Id} + +import scala.collection.immutable + +case class ODSkims(beamServices: BeamServices) extends AbstractSkimmerReadOnly(beamServices) { + + def getSkimDefaultValue( + mode: BeamMode, + originUTM: Location, + destinationUTM: Location, + departureTime: Int, + vehicleTypeId: Id[BeamVehicleType], + beamServices: BeamServices + ): Skim = { + val beamScenario = beamServices.beamScenario + val beamConfig = beamServices.beamConfig + val (travelDistance, travelTime) = distanceAndTime(mode, originUTM, destinationUTM) + val travelCost: Double = mode match { + case CAR | CAV => + DrivingCost.estimateDrivingCost( + new BeamLeg( + departureTime, + mode, + travelTime, + new BeamPath(IndexedSeq(), IndexedSeq(), None, null, null, travelDistance) + ), + beamScenario.vehicleTypes(vehicleTypeId), + beamScenario.fuelTypePrices + ) + case RIDE_HAIL => + beamConfig.beam.agentsim.agents.rideHail.defaultBaseCost + beamConfig.beam.agentsim.agents.rideHail.defaultCostPerMile * travelDistance / 1609.0 + beamConfig.beam.agentsim.agents.rideHail.defaultCostPerMinute * travelTime / 60.0 + case RIDE_HAIL_POOLED => + beamConfig.beam.agentsim.agents.rideHail.pooledBaseCost + beamConfig.beam.agentsim.agents.rideHail.pooledCostPerMile * travelDistance / 1609.0 + beamConfig.beam.agentsim.agents.rideHail.pooledCostPerMinute * travelTime / 60.0 + case TRANSIT | WALK_TRANSIT | DRIVE_TRANSIT | RIDE_HAIL_TRANSIT => 0.25 * travelDistance / 1609 + case _ => 0.0 + } + Skim( + travelTime, + travelTime, + travelCost + travelTime * beamConfig.beam.agentsim.agents.modalBehaviors.defaultValueOfTime / 3600, + travelDistance, + travelCost, + 0, + 0.0 // TODO get default energy information + ) + } + + def getRideHailPoolingTimeAndCostRatios( + origin: Location, + destination: Location, + departureTime: Int, + vehicleTypeId: org.matsim.api.core.v01.Id[BeamVehicleType], + beamServices: BeamServices + ): (Double, Double) = { + val tazTreeMap = beamServices.beamScenario.tazTreeMap + val beamConfig = beamServices.beamConfig + val origTaz = tazTreeMap.getTAZ(origin.getX, origin.getY).tazId + val destTaz = tazTreeMap.getTAZ(destination.getX, destination.getY).tazId + val solo = getSkimValue(departureTime, RIDE_HAIL, origTaz, destTaz) match { + case Some(skimValue) if skimValue.numObservations > 5 => + skimValue + case _ => + val (travelDistance, travelTime) = distanceAndTime(RIDE_HAIL, origin, destination) + ODSkimmerInternal( + travelTimeInS = travelTime.toDouble, + generalizedTimeInS = 0, + generalizedCost = 0, + distanceInM = travelDistance.toDouble, + cost = getRideHailCost(RIDE_HAIL, travelDistance, travelTime, beamConfig), + energy = 0.0, + numObservations = 0, + numIteration = beamServices.matsimServices.getIterationNumber + ) + } + val pooled = getSkimValue(departureTime, RIDE_HAIL_POOLED, origTaz, destTaz) match { + case Some(skimValue) if skimValue.numObservations > 5 => + skimValue + case _ => + ODSkimmerInternal( + travelTimeInS = solo.travelTimeInS * 1.1, + generalizedTimeInS = 0, + generalizedCost = 0, + distanceInM = solo.distanceInM, + cost = getRideHailCost(RIDE_HAIL_POOLED, solo.distanceInM, solo.travelTimeInS, beamConfig), + energy = 0.0, + numObservations = 0, + numIteration = beamServices.matsimServices.getIterationNumber + ) + } + val timeFactor = if (solo.travelTimeInS > 0.0) { pooled.travelTimeInS / solo.travelTimeInS } else { 1.0 } + val costFactor = if (solo.cost > 0.0) { pooled.cost / solo.cost } else { 1.0 } + (timeFactor, costFactor) + } + + def getTimeDistanceAndCost( + originUTM: Location, + destinationUTM: Location, + departureTime: Int, + mode: BeamMode, + vehicleTypeId: Id[BeamVehicleType], + beamServices: BeamServices + ): Skim = { + val origTaz = beamServices.beamScenario.tazTreeMap.getTAZ(originUTM.getX, originUTM.getY).tazId + val destTaz = beamServices.beamScenario.tazTreeMap.getTAZ(destinationUTM.getX, destinationUTM.getY).tazId + getSkimValue(departureTime, mode, origTaz, destTaz) match { + case Some(skimValue) => + skimValue.toSkimExternal + case None => + getSkimDefaultValue( + mode, + originUTM, + new Coord(destinationUTM.getX, destinationUTM.getY), + departureTime, + vehicleTypeId, + beamServices + ) + } + } + + def getExcerptData( + timePeriodString: String, + hoursIncluded: List[Int], + origin: TAZ, + destination: TAZ, + mode: BeamMode, + dummyId: Id[BeamVehicleType], + skim: immutable.Map[ODSkimmerKey, ODSkimmerInternal] + ): ExcerptData = { + val individualSkims = hoursIncluded.map { timeBin => + skim + .get(ODSkimmerKey(timeBin, mode, origin.tazId, destination.tazId)) + .map(_.toSkimExternal) + .getOrElse { + val adjustedDestCoord = if (origin.equals(destination)) { + new Coord( + origin.coord.getX, + origin.coord.getY + Math.sqrt(origin.areaInSquareMeters) / 2.0 + ) + } else { + destination.coord + } + getSkimDefaultValue( + mode, + origin.coord, + adjustedDestCoord, + timeBin * 3600, + dummyId, + beamServices + ) + } + } + val weights = individualSkims.map(sk => Math.max(sk.count, 1).toDouble) + val sumWeights = weights.sum + val weightedDistance = individualSkims.map(_.distance).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights + val weightedTime = individualSkims.map(_.time).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights + val weightedGeneralizedTime = individualSkims + .map(_.generalizedTime) + .zip(weights) + .map(tup => tup._1 * tup._2) + .sum / sumWeights + val weightedCost = individualSkims.map(_.cost).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights + val weightedGeneralizedCost = individualSkims + .map(_.generalizedCost) + .zip(weights) + .map(tup => tup._1 * tup._2) + .sum / sumWeights + val weightedEnergy = individualSkims.map(_.energy).zip(weights).map(tup => tup._1 * tup._2).sum / sumWeights + + ExcerptData( + timePeriodString = timePeriodString, + mode = mode, + originTazId = origin.tazId, + destinationTazId = destination.tazId, + weightedTime = weightedTime, + weightedGeneralizedTime = weightedGeneralizedTime, + weightedCost = weightedCost, + weightedGeneralizedCost = weightedGeneralizedCost, + weightedDistance = weightedDistance, + sumWeights = sumWeights, + weightedEnergy = weightedEnergy + ) + } + + private def getSkimValue(time: Int, mode: BeamMode, orig: Id[TAZ], dest: Id[TAZ]): Option[ODSkimmerInternal] = { + pastSkims + .map(_.get(ODSkimmerKey(timeToBin(time), mode, orig, dest))) + .headOption + .getOrElse(aggregatedSkim.get(ODSkimmerKey(timeToBin(time), mode, orig, dest))) + .map(_.asInstanceOf[ODSkimmerInternal]) + } + +} diff --git a/src/main/scala/beam/router/skim/Skims.scala b/src/main/scala/beam/router/skim/Skims.scala new file mode 100644 index 00000000000..25921d394ab --- /dev/null +++ b/src/main/scala/beam/router/skim/Skims.scala @@ -0,0 +1,43 @@ +package beam.router.skim + +import beam.router +import beam.router.skim +import beam.sim.BeamServices +import com.typesafe.scalalogging.LazyLogging + +import scala.collection.mutable + +object Skims extends LazyLogging { + lazy val od_skimmer: ODSkims = lookup(SkimType.OD_SKIMMER).asInstanceOf[ODSkims] + lazy val taz_skimmer: TAZSkims = lookup(SkimType.TAZ_SKIMMER).asInstanceOf[TAZSkims] + lazy val dt_skimmer: DriveTimeSkims = lookup(SkimType.DT_SKIMMER).asInstanceOf[DriveTimeSkims] + + def setup(implicit beamServices: BeamServices): Unit = { + val skimConfig = beamServices.beamConfig.beam.router.skim + skims.put(SkimType.OD_SKIMMER, addEvent(new ODSkimmer(beamServices, skimConfig))) + skims.put(SkimType.TAZ_SKIMMER, addEvent(new TAZSkimmer(beamServices, skimConfig))) + skims.put(SkimType.DT_SKIMMER, addEvent(new DriveTimeSkimmer(beamServices, skimConfig))) + } + + def clear(): Unit = { + skims.clear() + } + + private val skims = mutable.Map.empty[SkimType.Value, AbstractSkimmer] + + object SkimType extends Enumeration { + val OD_SKIMMER: router.skim.Skims.SkimType.Value = Value("od-skimmer") + val TAZ_SKIMMER: skim.Skims.SkimType.Value = Value("taz-skimmer") + val DT_SKIMMER: skim.Skims.SkimType.Value = Value("drive-time-skimmer") + } + + private def addEvent(skimmer: AbstractSkimmer)(implicit beamServices: BeamServices): AbstractSkimmer = { + beamServices.matsimServices.addControlerListener(skimmer) + beamServices.matsimServices.getEvents.addHandler(skimmer) + skimmer + } + + private def lookup(skimType: SkimType.Value): AbstractSkimmerReadOnly = { + skims.get(skimType).map(_.readOnlySkim).getOrElse(throw new RuntimeException(s"Skims $skimType does not exist")) + } +} diff --git a/src/main/scala/beam/router/TravelTimeObserved.scala b/src/main/scala/beam/router/skim/SkimsUtils.scala similarity index 59% rename from src/main/scala/beam/router/TravelTimeObserved.scala rename to src/main/scala/beam/router/skim/SkimsUtils.scala index bf876a5f86d..f2b45e8214e 100644 --- a/src/main/scala/beam/router/TravelTimeObserved.scala +++ b/src/main/scala/beam/router/skim/SkimsUtils.scala @@ -1,17 +1,28 @@ -package beam.router +package beam.router.skim + import java.awt.geom.Ellipse2D import java.awt.{BasicStroke, Color} -import beam.agentsim.agents.vehicles.BeamVehicleType import beam.agentsim.infrastructure.taz.{TAZ, TAZTreeMap} import beam.analysis.plots.{GraphUtils, GraphsStatsAgentSimEventsListener} +import beam.router.BeamRouter.Location import beam.router.Modes.BeamMode -import beam.router.Modes.BeamMode.{CAR, WALK} -import beam.router.model.{EmbodiedBeamLeg, EmbodiedBeamTrip} -import beam.sim.{BeamScenario, BeamServices} +import beam.router.Modes.BeamMode.{ + BIKE, + CAR, + CAV, + DRIVE_TRANSIT, + RIDE_HAIL, + RIDE_HAIL_POOLED, + RIDE_HAIL_TRANSIT, + TRANSIT, + WALK, + WALK_TRANSIT +} +import beam.sim.BeamServices import beam.sim.common.GeoUtils +import beam.sim.config.BeamConfig import beam.utils.{FileUtils, GeoJsonReader, ProfilingUtils} -import com.google.inject.Inject import com.typesafe.scalalogging.LazyLogging import com.vividsolutions.jts.geom.Geometry import org.jfree.chart.ChartFactory @@ -21,8 +32,6 @@ import org.jfree.data.statistics.{HistogramDataset, HistogramType} import org.jfree.data.xy.{XYSeries, XYSeriesCollection} import org.jfree.ui.RectangleInsets import org.matsim.api.core.v01.{Coord, Id} -import org.matsim.core.controler.events.IterationEndsEvent -import org.matsim.core.utils.io.IOUtils import org.opengis.feature.Feature import org.opengis.feature.simple.SimpleFeature import org.supercsv.io.{CsvMapReader, ICsvMapReader} @@ -30,152 +39,115 @@ import org.supercsv.prefs.CsvPreference import scala.collection.mutable -class TravelTimeObserved @Inject()( - val beamServices: BeamServices, - val beamScenario: BeamScenario, - val geo: GeoUtils -) extends LazyLogging { - import TravelTimeObserved._ - import beamScenario._ +object SkimsUtils extends LazyLogging { + + // 22.2 mph (9.924288 meter per second), is the average speed in cities + //TODO better estimate can be drawn from city size + // source: https://www.mitpressjournals.org/doi/abs/10.1162/rest_a_00744 + val carSpeedMeterPerSec: Double = 9.924288 + // 12.1 mph (5.409184 meter per second), is average bus speed + // source: https://www.apta.com/resources/statistics/Documents/FactBook/2017-APTA-Fact-Book.pdf + // assuming for now that it includes the headway + val transitSpeedMeterPerSec: Double = 5.409184 + val bicycleSpeedMeterPerSec: Double = 3 + // 3.1 mph -> 1.38 meter per second + val walkSpeedMeterPerSec: Double = 1.38 + // 940.6 Traffic Signal Spacing, Minor is 1,320 ft => 402.336 meters + val trafficSignalSpacing: Double = 402.336 + // average waiting time at an intersection is 17.25 seconds + // source: https://pumas.nasa.gov/files/01_06_00_1.pdf + val waitingTimeAtAnIntersection: Double = 17.25 + + val speedMeterPerSec: Map[BeamMode, Double] = Map( + CAV -> carSpeedMeterPerSec, + CAR -> carSpeedMeterPerSec, + WALK -> walkSpeedMeterPerSec, + BIKE -> bicycleSpeedMeterPerSec, + WALK_TRANSIT -> transitSpeedMeterPerSec, + DRIVE_TRANSIT -> transitSpeedMeterPerSec, + RIDE_HAIL -> carSpeedMeterPerSec, + RIDE_HAIL_POOLED -> carSpeedMeterPerSec, + RIDE_HAIL_TRANSIT -> transitSpeedMeterPerSec, + TRANSIT -> transitSpeedMeterPerSec + ) - @volatile - private var skimmer: BeamSkimmer = new BeamSkimmer(beamServices, beamScenario, geo) - - private val observedTravelTimesOpt: Option[Map[PathCache, Float]] = { - val zoneBoundariesFilePath = beamConfig.beam.calibration.roadNetwork.travelTimes.zoneBoundariesFilePath - val zoneODTravelTimesFilePath = beamConfig.beam.calibration.roadNetwork.travelTimes.zoneODTravelTimesFilePath + case class PathCache(from: Id[TAZ], to: Id[TAZ], hod: Int) - if (zoneBoundariesFilePath.nonEmpty && zoneODTravelTimesFilePath.nonEmpty) { - val tazToMovId: Map[TAZ, Int] = buildTAZ2MovementId( - zoneBoundariesFilePath, - geo, - tazTreeMap - ) - val movId2Taz: Map[Int, TAZ] = tazToMovId.map { case (k, v) => v -> k } - Some(buildPathCache2TravelTime(zoneODTravelTimesFilePath, movId2Taz)) - } else None + def timeToBin(departTime: Int): Int = { + Math.floorMod(Math.floor(departTime.toDouble / 3600.0).toInt, 24) } - val uniqueModes: List[BeamMode.CAR.type] = List(CAR) - - val uniqueTimeBins: Range.Inclusive = 0 to 23 - - val dummyId: Id[BeamVehicleType] = Id.create("NA", classOf[BeamVehicleType]) - - def observeTrip( - trip: EmbodiedBeamTrip, - generalizedTimeInHours: Double, - generalizedCost: Double, - energyConsumption: Double - ): Unit = { - val legs = trip.legs.filter(x => x.beamLeg.mode == BeamMode.CAR || x.beamLeg.mode == BeamMode.CAV) - legs.foreach { carLeg => - val dummyHead = EmbodiedBeamLeg.dummyLegAt( - carLeg.beamLeg.startTime, - Id.createVehicleId(""), - isLastLeg = false, - carLeg.beamLeg.travelPath.startPoint.loc, - WALK, - dummyId - ) + def timeToBin(departTime: Int, timeWindow: Int): Int = departTime / timeWindow - val dummyTail = EmbodiedBeamLeg.dummyLegAt( - carLeg.beamLeg.endTime, - Id.createVehicleId(""), - isLastLeg = true, - carLeg.beamLeg.travelPath.endPoint.loc, - WALK, - dummyId - ) - // In case of `CAV` we have to override its mode to `CAR` - val fixedCarLeg = if (carLeg.beamLeg.mode == BeamMode.CAV) { - carLeg.copy(beamLeg = carLeg.beamLeg.copy(mode = BeamMode.CAR)) - } else { - carLeg - } - val carTrip = EmbodiedBeamTrip(Vector(dummyHead, fixedCarLeg, dummyTail)) - skimmer.observeTrip(carTrip, generalizedTimeInHours, generalizedCost, energyConsumption) + def distanceAndTime(mode: BeamMode, originUTM: Location, destinationUTM: Location) = { + val speed = mode match { + case CAR | CAV | RIDE_HAIL => carSpeedMeterPerSec + case RIDE_HAIL_POOLED => carSpeedMeterPerSec / 1.1 + case TRANSIT | WALK_TRANSIT | DRIVE_TRANSIT | RIDE_HAIL_TRANSIT => transitSpeedMeterPerSec + case BIKE => bicycleSpeedMeterPerSec + case _ => walkSpeedMeterPerSec } + val travelDistance: Int = Math.ceil(GeoUtils.minkowskiDistFormula(originUTM, destinationUTM)).toInt + val travelTime: Int = Math + .ceil(travelDistance / speed) + .toInt + ((travelDistance / trafficSignalSpacing).toInt * waitingTimeAtAnIntersection).toInt + (travelDistance, travelTime) } - def notifyIterationEnds(event: IterationEndsEvent): Unit = { - writeTravelTimeObservedVsSimulated(event) - skimmer = new BeamSkimmer(beamServices, beamScenario, geo) - } - - def writeTravelTimeObservedVsSimulated(event: IterationEndsEvent): Unit = { - observedTravelTimesOpt.foreach { observedTravelTimes => - ProfilingUtils.timed( - s"writeTravelTimeObservedVsSimulated on iteration ${event.getIteration}", - x => logger.info(x) - ) { - write(event, observedTravelTimes) - } + def getRideHailCost( + mode: BeamMode, + distanceInMeters: Double, + timeInSeconds: Double, + beamConfig: BeamConfig + ): Double = { + mode match { + case RIDE_HAIL => + beamConfig.beam.agentsim.agents.rideHail.defaultCostPerMile * distanceInMeters / 1609.34 + beamConfig.beam.agentsim.agents.rideHail.defaultCostPerMinute * timeInSeconds / 60 + beamConfig.beam.agentsim.agents.rideHail.defaultBaseCost + case RIDE_HAIL_POOLED => + beamConfig.beam.agentsim.agents.rideHail.pooledCostPerMile * distanceInMeters / 1609.34 + beamConfig.beam.agentsim.agents.rideHail.pooledCostPerMinute * timeInSeconds / 60 + beamConfig.beam.agentsim.agents.rideHail.pooledBaseCost + case _ => + 0.0 } } - private def write(event: IterationEndsEvent, observedTravelTimes: Map[PathCache, Float]): Unit = { - val filePathObservedVsSimulated = event.getServices.getControlerIO.getIterationFilename( - event.getServices.getIterationNumber, - "tazODTravelTimeObservedVsSimulated.csv.gz" - ) - val writerObservedVsSimulated = IOUtils.getBufferedWriter(filePathObservedVsSimulated) - writerObservedVsSimulated.write("fromTAZId,toTAZId,hour,timeSimulated,timeObserved,counts") - writerObservedVsSimulated.write("\n") - - var series = new mutable.ListBuffer[(Int, Double, Double)]() - val categoryDataset = new HistogramDataset() - var deltasOfObservedSimulatedTimes = new mutable.ListBuffer[Double] - - tazTreeMap.getTAZs - .foreach { origin => - tazTreeMap.getTAZs.foreach { destination => - uniqueModes.foreach { mode => - uniqueTimeBins - .foreach { timeBin => - val key = PathCache(origin.tazId, destination.tazId, timeBin) - observedTravelTimes.get(key).foreach { timeObserved => - skimmer - .getSkimValue(timeBin * 3600, mode, origin.tazId, destination.tazId) - .map(_.toSkimExternal) - .foreach { theSkim => - series += ((theSkim.count, theSkim.time, timeObserved)) - for (count <- 1 to theSkim.count) - deltasOfObservedSimulatedTimes += theSkim.time - timeObserved - writerObservedVsSimulated.write( - s"${origin.tazId},${destination.tazId},${timeBin},${theSkim.time},${timeObserved},${theSkim.count}\n" - ) - } - } - } + def buildPathCache2TravelTime( + pathToAggregateFile: String, + movId2Taz: Map[Int, TAZ] + ): Map[PathCache, Float] = { + val observedTravelTimes: mutable.HashMap[PathCache, Float] = scala.collection.mutable.HashMap.empty + ProfilingUtils.timed(s"buildPathCache2TravelTime from '$pathToAggregateFile'", x => logger.info(x)) { + val mapReader: ICsvMapReader = + new CsvMapReader(FileUtils.readerFromFile(pathToAggregateFile), CsvPreference.STANDARD_PREFERENCE) + try { + val header = mapReader.getHeader(true) + var line: java.util.Map[String, String] = mapReader.read(header: _*) + while (null != line) { + val sourceid = line.get("sourceid").toInt + val dstid = line.get("dstid").toInt + val mean_travel_time = line.get("mean_travel_time").toFloat + val hod = line.get("hod").toInt + + if (movId2Taz.contains(sourceid) && movId2Taz.contains(dstid)) { + observedTravelTimes.put(PathCache(movId2Taz(sourceid).tazId, movId2Taz(dstid).tazId, hod), mean_travel_time) } + + line = mapReader.read(header: _*) } + } finally { + if (null != mapReader) + mapReader.close() } - - categoryDataset.addSeries("Simulated-Observed", deltasOfObservedSimulatedTimes.toArray, histogramBinSize) - - writerObservedVsSimulated.close() - - val chartPath = - event.getServices.getControlerIO.getIterationFilename(event.getServices.getIterationNumber, chartName) - generateChart(series, chartPath) - - val histogramPath = - event.getServices.getControlerIO.getIterationFilename(event.getServices.getIterationNumber, histogramName) - generateHistogram(categoryDataset, histogramPath) + } + logger.info(s"observedTravelTimesOpt size is ${observedTravelTimes.keys.size}") + observedTravelTimes.toMap } -} - -object TravelTimeObserved extends LazyLogging { - val chartName: String = "scatterplot_simulation_vs_reference.png" - val histogramName: String = "simulation_vs_reference_histogram.png" - val histogramBinSize: Int = 200 - val MaxDistanceFromBeamTaz: Double = 500.0 // 500 meters - - case class PathCache(from: Id[TAZ], to: Id[TAZ], hod: Int) - - def buildTAZ2MovementId(filePath: String, geo: GeoUtils, tazTreeMap: TAZTreeMap): Map[TAZ, Int] = { + def buildTAZ2MovementId( + filePath: String, + geo: GeoUtils, + tazTreeMap: TAZTreeMap, + maxDistanceFromBeamTaz: Double + ): Map[TAZ, Int] = { ProfilingUtils.timed(s"buildTAZ2MovementId from '$filePath'", x => logger.info(x)) { val mapper: Feature => (TAZ, Int, Double) = (feature: Feature) => { val centroid = feature.asInstanceOf[SimpleFeature].getDefaultGeometry.asInstanceOf[Geometry].getCentroid @@ -187,7 +159,7 @@ object TravelTimeObserved extends LazyLogging { (taz, movId, distance) } val xs: Array[(TAZ, Int, Double)] = GeoJsonReader.read(filePath, mapper) - val filterByMaxDistance = xs.filter { case (taz, movId, distance) => distance <= MaxDistanceFromBeamTaz } + val filterByMaxDistance = xs.filter { case (_, _, distance) => distance <= maxDistanceFromBeamTaz } val tazId2MovIdByMinDistance = filterByMaxDistance .groupBy { case (taz, _, _) => taz } .map { @@ -203,53 +175,21 @@ object TravelTimeObserved extends LazyLogging { } } - def buildPathCache2TravelTime(pathToAggregateFile: String, movId2Taz: Map[Int, TAZ]): Map[PathCache, Float] = { - val observedTravelTimes: mutable.HashMap[PathCache, Float] = scala.collection.mutable.HashMap.empty - ProfilingUtils.timed(s"buildPathCache2TravelTime from '$pathToAggregateFile'", x => logger.info(x)) { - val mapReader: ICsvMapReader = - new CsvMapReader(FileUtils.readerFromFile(pathToAggregateFile), CsvPreference.STANDARD_PREFERENCE) - try { - val header = mapReader.getHeader(true) - var line: java.util.Map[String, String] = mapReader.read(header: _*) - while (null != line) { - val sourceid = line.get("sourceid").toInt - val dstid = line.get("dstid").toInt - val mean_travel_time = line.get("mean_travel_time").toFloat - val hod = line.get("hod").toInt - - if (movId2Taz.contains(sourceid) && movId2Taz.contains(dstid)) { - observedTravelTimes.put(PathCache(movId2Taz(sourceid).tazId, movId2Taz(dstid).tazId, hod), mean_travel_time) - } - - line = mapReader.read(header: _*) - } - } finally { - if (null != mapReader) - mapReader.close() - } - } - logger.info(s"observedTravelTimesOpt size is ${observedTravelTimes.keys.size}") - observedTravelTimes.toMap - } - - def generateHistogram(dataset: HistogramDataset, path: String): Unit = { - dataset.setType(HistogramType.FREQUENCY) - val chart = ChartFactory.createHistogram( - "Simulated-Observed Frequency", - "Simulated-Observed", - "Frequency", - dataset, - PlotOrientation.VERTICAL, - true, - false, - false - ) - GraphUtils.saveJFreeChartAsPNG( - chart, - path, - GraphsStatsAgentSimEventsListener.GRAPH_WIDTH, - GraphsStatsAgentSimEventsListener.GRAPH_HEIGHT - ) + def buildObservedODTravelTime(beamServices: BeamServices, maxDistanceFromBeamTaz: Double): Map[PathCache, Float] = { + import beamServices._ + val zoneBoundariesFilePath = beamConfig.beam.calibration.roadNetwork.travelTimes.zoneBoundariesFilePath + val zoneODTravelTimesFilePath = beamConfig.beam.calibration.roadNetwork.travelTimes.zoneODTravelTimesFilePath + if (zoneBoundariesFilePath.nonEmpty && zoneODTravelTimesFilePath.nonEmpty) { + val tazToMovId: Map[TAZ, Int] = buildTAZ2MovementId( + zoneBoundariesFilePath, + geo, + beamScenario.tazTreeMap, + maxDistanceFromBeamTaz + ) + val movId2Taz: Map[Int, TAZ] = tazToMovId.map { case (k, v) => v -> k } + buildPathCache2TravelTime(zoneODTravelTimesFilePath, movId2Taz) + } else + Map.empty[PathCache, Float] } def generateChart(series: mutable.ListBuffer[(Int, Double, Double)], path: String): Unit = { @@ -376,4 +316,25 @@ object TravelTimeObserved extends LazyLogging { 1000 ) } + + def generateHistogram(dataset: HistogramDataset, path: String): Unit = { + dataset.setType(HistogramType.FREQUENCY) + val chart = ChartFactory.createHistogram( + "Simulated-Observed Frequency", + "Simulated-Observed", + "Frequency", + dataset, + PlotOrientation.VERTICAL, + true, + false, + false + ) + GraphUtils.saveJFreeChartAsPNG( + chart, + path, + GraphsStatsAgentSimEventsListener.GRAPH_WIDTH, + GraphsStatsAgentSimEventsListener.GRAPH_HEIGHT + ) + } + } diff --git a/src/main/scala/beam/router/skim/TAZSkimmer.scala b/src/main/scala/beam/router/skim/TAZSkimmer.scala new file mode 100644 index 00000000000..626d89604f5 --- /dev/null +++ b/src/main/scala/beam/router/skim/TAZSkimmer.scala @@ -0,0 +1,90 @@ +package beam.router.skim +import beam.agentsim.infrastructure.taz.TAZ +import beam.sim.BeamServices +import beam.sim.config.BeamConfig +import com.typesafe.scalalogging.LazyLogging +import org.matsim.api.core.v01.Id + +import scala.collection.immutable + +class TAZSkimmer(beamServices: BeamServices, config: BeamConfig.Beam.Router.Skim) + extends AbstractSkimmer(beamServices, config) { + import TAZSkimmer._ + + override lazy val readOnlySkim: AbstractSkimmerReadOnly = TAZSkims(beamServices) + + override protected val skimName: String = config.taz_skimmer.name + override protected val skimFileBaseName: String = config.taz_skimmer.fileBaseName + override protected val skimFileHeader: String = + "time,taz,hex,groupId,label,sumValue,meanValue,numObservations,numIteration" + + override def fromCsv( + line: immutable.Map[String, String] + ): (AbstractSkimmerKey, AbstractSkimmerInternal) = { + ( + TAZSkimmerKey( + line("time").toInt, + Id.create(line("taz"), classOf[TAZ]), + line("hex"), + line("groupId"), + line("label") + ), + TAZSkimmerInternal( + line("sumValue").toDouble, + line("meanValue").toDouble, + line("numObservations").toInt, + line("numIteration").toInt + ) + ) + } + + override protected def aggregateOverIterations( + prevIteration: Option[AbstractSkimmerInternal], + currIteration: Option[AbstractSkimmerInternal] + ): AbstractSkimmerInternal = { + val prevSkim = prevIteration + .map(_.asInstanceOf[TAZSkimmerInternal]) + .getOrElse(TAZSkimmerInternal(0, 0, numObservations = 0, numIteration = 0)) // no skim means no observation + val currSkim = currIteration + .map(_.asInstanceOf[TAZSkimmerInternal]) + .getOrElse(TAZSkimmerInternal(0, 0, numObservations = 0, numIteration = 1)) // no current skim means 0 observation + TAZSkimmerInternal( + sumValue = (prevSkim.sumValue * prevSkim.numIteration + currSkim.sumValue * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + meanValue = (prevSkim.meanValue * prevSkim.numIteration + currSkim.meanValue * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + numObservations = (prevSkim.numObservations * prevSkim.numIteration + currSkim.numObservations * currSkim.numIteration) / (prevSkim.numIteration + currSkim.numIteration), + numIteration = prevSkim.numIteration + currSkim.numIteration + ) + } + + override protected def aggregateWithinAnIteration( + prevObservation: Option[AbstractSkimmerInternal], + currObservation: AbstractSkimmerInternal + ): AbstractSkimmerInternal = { + val prevSkim = prevObservation + .map(_.asInstanceOf[TAZSkimmerInternal]) + .getOrElse(TAZSkimmerInternal(0, 0, numObservations = 0, numIteration = 0)) + val currSkim = currObservation.asInstanceOf[TAZSkimmerInternal] + TAZSkimmerInternal( + sumValue = prevSkim.sumValue + currSkim.sumValue, + meanValue = (prevSkim.meanValue * prevSkim.numObservations + currSkim.meanValue * currSkim.numObservations) / (prevSkim.numObservations + currSkim.numObservations), + numObservations = prevSkim.numObservations + currSkim.numObservations, + numIteration = beamServices.matsimServices.getIterationNumber + 1 + ) + } +} + +object TAZSkimmer extends LazyLogging { + case class TAZSkimmerKey( + time: Int, + taz: Id[TAZ], + hex: String, + groupId: String, + label: String + ) extends AbstractSkimmerKey { + override def toCsv: String = time + "," + taz + "," + hex + "," + groupId + "," + label + } + case class TAZSkimmerInternal(sumValue: Double, meanValue: Double, numObservations: Int, numIteration: Int = 0) + extends AbstractSkimmerInternal { + override def toCsv: String = sumValue + "," + meanValue + "," + numObservations + "," + numIteration + } +} diff --git a/src/main/scala/beam/router/skim/TAZSkimmerEvent.scala b/src/main/scala/beam/router/skim/TAZSkimmerEvent.scala new file mode 100644 index 00000000000..8175e45229a --- /dev/null +++ b/src/main/scala/beam/router/skim/TAZSkimmerEvent.scala @@ -0,0 +1,24 @@ +package beam.router.skim + +import beam.router.skim.TAZSkimmer.{TAZSkimmerInternal, TAZSkimmerKey} +import beam.sim.BeamServices +import org.matsim.api.core.v01.Coord + +case class TAZSkimmerEvent( + eventTime: Double, + beamServices: BeamServices, + time: Int, + coord: Coord, + groupId: String = "default", + label: String, + sumValue: Double = 0, + meanValue: Double = 0, + numObservations: Int = 1 +) extends AbstractSkimmerEvent(eventTime, beamServices) { + override protected val skimName: String = beamServices.beamConfig.beam.router.skim.taz_skimmer.name + private val hexIndex = beamServices.beamScenario.h3taz.getHRHex(coord.getX, coord.getY) + private val idTaz = beamServices.beamScenario.h3taz.getTAZ(hexIndex) + override def getKey: AbstractSkimmerKey = TAZSkimmerKey(time, idTaz, hexIndex, groupId, label) + override def getSkimmerInternal: AbstractSkimmerInternal = + TAZSkimmerInternal(sumValue, meanValue, numObservations) +} diff --git a/src/main/scala/beam/router/skim/TAZSkims.scala b/src/main/scala/beam/router/skim/TAZSkims.scala new file mode 100644 index 00000000000..8c2cce924b3 --- /dev/null +++ b/src/main/scala/beam/router/skim/TAZSkims.scala @@ -0,0 +1,70 @@ +package beam.router.skim + +import beam.agentsim.infrastructure.taz.TAZ +import beam.router.skim.TAZSkimmer.{TAZSkimmerInternal, TAZSkimmerKey} +import beam.sim.BeamServices +import beam.sim.vehiclesharing.VehicleManager +import org.matsim.api.core.v01.Id + +case class TAZSkims(beamServices: BeamServices) extends AbstractSkimmerReadOnly(beamServices) { + + def getLatestSkim( + time: Int, + taz: Id[TAZ], + hex: String, + groupId: String, + label: String + ): Option[TAZSkimmerInternal] = { + pastSkims.headOption + .flatMap(_.get(TAZSkimmerKey(time, taz, hex, groupId, label))) + .asInstanceOf[Option[TAZSkimmerInternal]] + } + + def getLatestSkim( + time: Int, + hex: String, + groupId: String, + label: String + ): Option[TAZSkimmerInternal] = { + getLatestSkim(time, beamServices.beamScenario.h3taz.getTAZ(hex), hex, groupId, label) + } + + def getLatestSkimByTAZ( + time: Int, + taz: Id[TAZ], + groupId: String, + label: String + ): Option[TAZSkimmerInternal] = { + beamServices.beamScenario.h3taz + .getHRHex(taz) + .flatMap(hex => getLatestSkim(time, taz, hex, groupId, label)) + .foldLeft[Option[TAZSkimmerInternal]](None) { + case (acc, skimInternal) => + acc match { + case Some(skim) => + Some( + TAZSkimmerInternal( + sumValue = skim.sumValue + skimInternal.sumValue, + meanValue = (skim.meanValue * skim.numObservations + skimInternal.meanValue + skimInternal.numObservations) / (skim.numObservations + skimInternal.numObservations), + numObservations = skim.numObservations + skimInternal.numObservations, + numIteration = skim.numIteration + ) + ) + case _ => Some(skimInternal) + } + } + } + + def getAggregatedSkim( + time: Int, + taz: Id[TAZ], + hex: String, + groupid: String, + label: String + ): Option[TAZSkimmerInternal] = { + aggregatedSkim + .get(TAZSkimmerKey(time, taz, hex, groupid, label)) + .asInstanceOf[Option[TAZSkimmerInternal]] + } + +} diff --git a/src/main/scala/beam/sim/BeamHelper.scala b/src/main/scala/beam/sim/BeamHelper.scala index f44643c5c67..f5d3af7f278 100755 --- a/src/main/scala/beam/sim/BeamHelper.scala +++ b/src/main/scala/beam/sim/BeamHelper.scala @@ -9,7 +9,7 @@ import beam.agentsim.agents.choice.mode.{ModeIncentive, PtFares} import beam.agentsim.agents.ridehail.{RideHailIterationHistory, RideHailSurgePricingManager} import beam.agentsim.agents.vehicles._ import beam.agentsim.events.handling.BeamEventsHandling -import beam.agentsim.infrastructure.taz.TAZTreeMap +import beam.agentsim.infrastructure.taz.{H3TAZ, TAZTreeMap} import beam.analysis.ActivityLocationPlotter import beam.analysis.plots.{GraphSurgePricing, RideHailRevenueAnalysis} import beam.matsim.{CustomPlansDumpingImpl, MatsimConfigUpdater} @@ -208,8 +208,6 @@ trait BeamHelper extends LazyLogging { bind(classOf[NetworkHelper]).to(classOf[NetworkHelperImpl]).asEagerSingleton() bind(classOf[RideHailIterationHistory]).asEagerSingleton() bind(classOf[RouteHistory]).asEagerSingleton() - bind(classOf[BeamSkimmer]).asEagerSingleton() - bind(classOf[TravelTimeObserved]).asEagerSingleton() bind(classOf[FareCalculator]).asEagerSingleton() bind(classOf[TollCalculator]).asEagerSingleton() @@ -242,6 +240,7 @@ trait BeamHelper extends LazyLogging { ) val networkCoordinator = buildNetworkCoordinator(beamConfig) + val tazMap = TAZTreeMap.getTazTreeMap(beamConfig.beam.agentsim.taz.filePath) BeamScenario( readFuelTypeFile(beamConfig.beam.agentsim.agents.vehicles.fuelTypesFilePath).toMap, @@ -256,8 +255,9 @@ trait BeamHelper extends LazyLogging { PtFares(beamConfig.beam.agentsim.agents.ptFare.filePath), networkCoordinator.transportNetwork, networkCoordinator.network, - TAZTreeMap.getTazTreeMap(beamConfig.beam.agentsim.taz.filePath), - ModeIncentive(beamConfig.beam.agentsim.agents.modeIncentive.filePath) + tazMap, + ModeIncentive(beamConfig.beam.agentsim.agents.modeIncentive.filePath), + H3TAZ(networkCoordinator.network, tazMap, beamConfig) ) } diff --git a/src/main/scala/beam/sim/BeamMobsim.scala b/src/main/scala/beam/sim/BeamMobsim.scala index 61eca8ca44c..70bfd1ddc2b 100755 --- a/src/main/scala/beam/sim/BeamMobsim.scala +++ b/src/main/scala/beam/sim/BeamMobsim.scala @@ -47,8 +47,6 @@ class BeamMobsim @Inject()( val rideHailSurgePricingManager: RideHailSurgePricingManager, val rideHailIterationHistory: RideHailIterationHistory, val routeHistory: RouteHistory, - val beamSkimmer: BeamSkimmer, - val travelTimeObserved: TravelTimeObserved, val geo: GeoUtils, val networkHelper: NetworkHelper ) extends Mobsim @@ -75,9 +73,7 @@ class BeamMobsim @Inject()( beamServices, rideHailSurgePricingManager, rideHailIterationHistory, - routeHistory, - beamSkimmer, - travelTimeObserved + routeHistory ) ), "BeamMobsim.iteration" @@ -118,9 +114,7 @@ class BeamMobsimIteration( val beamServices: BeamServices, val rideHailSurgePricingManager: RideHailSurgePricingManager, val rideHailIterationHistory: RideHailIterationHistory, - val routeHistory: RouteHistory, - val beamSkimmer: BeamSkimmer, - val travelTimeObserved: TravelTimeObserved + val routeHistory: RouteHistory ) extends Actor with ActorLogging with MetricsSupport { @@ -179,7 +173,6 @@ class BeamMobsimIteration( activityQuadTreeBounds, rideHailSurgePricingManager, rideHailIterationHistory.oscillationAdjustedTNCIterationStats, - beamSkimmer, routeHistory ) ).withDispatcher("ride-hail-manager-pinned-dispatcher"), @@ -205,7 +198,7 @@ class BeamMobsimIteration( private val sharedVehicleFleets = config.agents.vehicles.sharedFleets.map { fleetConfig => context.actorOf( - Fleets.lookup(fleetConfig).props(beamServices, beamSkimmer, scheduler, parkingManager), + Fleets.lookup(fleetConfig).props(beamServices, scheduler, parkingManager), fleetConfig.name ) } @@ -245,8 +238,6 @@ class BeamMobsimIteration( sharedVehicleFleets, matsimServices.getEvents, routeHistory, - beamSkimmer, - travelTimeObserved, envelopeInUTM ), "population" diff --git a/src/main/scala/beam/sim/BeamScenario.scala b/src/main/scala/beam/sim/BeamScenario.scala index 8a0d7b37f1c..c488d1487c5 100644 --- a/src/main/scala/beam/sim/BeamScenario.scala +++ b/src/main/scala/beam/sim/BeamScenario.scala @@ -3,7 +3,7 @@ package beam.sim import beam.agentsim.agents.choice.mode.{ModeIncentive, PtFares} import beam.agentsim.agents.vehicles.FuelType.FuelTypePrices import beam.agentsim.agents.vehicles.{BeamVehicle, BeamVehicleType, VehicleEnergy} -import beam.agentsim.infrastructure.taz.TAZTreeMap +import beam.agentsim.infrastructure.taz.{H3TAZ, TAZTreeMap} import beam.router.Modes.BeamMode import beam.sim.config.BeamConfig import beam.utils.DateUtils @@ -39,7 +39,8 @@ case class BeamScenario( transportNetwork: TransportNetwork, network: Network, tazTreeMap: TAZTreeMap, - modeIncentives: ModeIncentive + modeIncentives: ModeIncentive, + h3taz: H3TAZ ) { lazy val rideHailTransitModes: Seq[BeamMode] = if (beamConfig.beam.agentsim.agents.rideHailTransit.modesToConsider.equalsIgnoreCase("all")) BeamMode.transitModes diff --git a/src/main/scala/beam/sim/BeamSim.scala b/src/main/scala/beam/sim/BeamSim.scala index 71d0b895026..feef647bf9a 100755 --- a/src/main/scala/beam/sim/BeamSim.scala +++ b/src/main/scala/beam/sim/BeamSim.scala @@ -15,7 +15,8 @@ import beam.analysis.via.ExpectedMaxUtilityHeatMap import beam.analysis.{DelayMetricAnalysis, IterationStatsProvider, RideHailUtilizationCollector} import beam.physsim.jdeqsim.AgentSimToPhysSimPlanConverter import beam.router.osm.TollCalculator -import beam.router.{BeamRouter, BeamSkimmer, RouteHistory, TravelTimeObserved} +import beam.router.skim.Skims +import beam.router.{BeamRouter, RouteHistory} import beam.sim.config.{BeamConfig, BeamConfigHolder} import beam.sim.metrics.MetricsPrinter.{Print, Subscribe} import beam.sim.metrics.{MetricsPrinter, MetricsSupport} @@ -45,8 +46,8 @@ import org.matsim.core.controler.listener.{ } import scala.collection.JavaConverters._ -import scala.collection.{immutable, mutable} import scala.collection.mutable.ListBuffer +import scala.collection.{immutable, mutable} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.Duration import scala.concurrent.{Await, Future} @@ -60,8 +61,6 @@ class BeamSim @Inject()( private val scenario: Scenario, private val networkHelper: NetworkHelper, private val beamOutputDataDescriptionGenerator: BeamOutputDataDescriptionGenerator, - private val beamSkimmer: BeamSkimmer, - private val travelTimeObserved: TravelTimeObserved, private val beamConfigChangesObservable: BeamConfigChangesObservable, private val routeHistory: RouteHistory, private val rideHailIterationHistory: RideHailIterationHistory, @@ -171,6 +170,7 @@ class BeamSim @Inject()( dumpMatsimStuffAtTheBeginningOfSimulation() FailFast.run(beamServices) + Skims.setup(beamServices) } override def notifyIterationStarts(event: IterationStartsEvent): Unit = { @@ -206,10 +206,6 @@ class BeamSim @Inject()( override def notifyIterationEnds(event: IterationEndsEvent): Unit = { val beamConfig: BeamConfig = beamConfigChangesObservable.getUpdatedBeamConfig - travelTimeObserved.notifyIterationEnds(event) - - beamSkimmer.notifyIterationEnds(event) - if (shouldWritePlansAtCurrentIteration(event.getIteration)) { PlansCsvWriter.toCsv( scenario, @@ -396,6 +392,8 @@ class BeamSim @Inject()( process.waitFor(5, TimeUnit.MINUTES) logger.info("Python process completed.") }) + + Skims.clear() } private def writeSummaryVehicleStats(summaryVehicleStatsFile: File): immutable.HashSet[String] = { diff --git a/src/main/scala/beam/sim/BeamWarmStart.scala b/src/main/scala/beam/sim/BeamWarmStart.scala index e89ae7b97cf..837eb12cf2b 100755 --- a/src/main/scala/beam/sim/BeamWarmStart.scala +++ b/src/main/scala/beam/sim/BeamWarmStart.scala @@ -204,7 +204,7 @@ object BeamWarmStart extends LazyLogging { if (beamConfig.beam.warmStart.enabled) { val matsimConfig = beamExecutionConfig.matsimConfig - if (beamConfig.beam.beamskimmer.writeObservedSkimsInterval == 0 && beamConfig.beam.warmStart.enabled) { + if (beamConfig.beam.router.skim.writeSkimsInterval == 0 && beamConfig.beam.warmStart.enabled) { logger.warn( "Beam skims are not being written out - skims will be missing for warm starting from the output of this run!" ) diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index 3046a1d6a5e..89965af01ac 100755 --- a/src/main/scala/beam/sim/config/BeamConfig.scala +++ b/src/main/scala/beam/sim/config/BeamConfig.scala @@ -11,18 +11,19 @@ case class BeamConfig( object BeamConfig { case class Beam( agentsim: BeamConfig.Beam.Agentsim, - beamskimmer: BeamConfig.Beam.Beamskimmer, calibration: BeamConfig.Beam.Calibration, cluster: BeamConfig.Beam.Cluster, debug: BeamConfig.Beam.Debug, exchange: BeamConfig.Beam.Exchange, experimental: BeamConfig.Beam.Experimental, + h3: BeamConfig.Beam.H3, inputDirectory: java.lang.String, logger: BeamConfig.Beam.Logger, metrics: BeamConfig.Beam.Metrics, outputs: BeamConfig.Beam.Outputs, physsim: BeamConfig.Beam.Physsim, replanning: BeamConfig.Beam.Replanning, + router: BeamConfig.Beam.Router, routing: BeamConfig.Beam.Routing, spatial: BeamConfig.Beam.Spatial, useLocalWorker: scala.Boolean, @@ -730,8 +731,8 @@ object BeamConfig { object AllocationManager { case class AlonsoMora( - solutionSpaceSizePerVehicle: scala.Int, - travelTimeDelayAsFraction: scala.Double, + excessRideTimeAsFraction: scala.Double, + numRequestsPerVehicle: scala.Int, waitingTimeInSec: scala.Int ) @@ -741,10 +742,10 @@ object BeamConfig { c: com.typesafe.config.Config ): BeamConfig.Beam.Agentsim.Agents.RideHail.AllocationManager.AlonsoMora = { BeamConfig.Beam.Agentsim.Agents.RideHail.AllocationManager.AlonsoMora( - solutionSpaceSizePerVehicle = - if (c.hasPathOrNull("solutionSpaceSizePerVehicle")) c.getInt("solutionSpaceSizePerVehicle") else 5, - travelTimeDelayAsFraction = - if (c.hasPathOrNull("travelTimeDelayAsFraction")) c.getDouble("travelTimeDelayAsFraction") else 0.2, + excessRideTimeAsFraction = + if (c.hasPathOrNull("excessRideTimeAsFraction")) c.getDouble("excessRideTimeAsFraction") else 0.2, + numRequestsPerVehicle = + if (c.hasPathOrNull("numRequestsPerVehicle")) c.getInt("numRequestsPerVehicle") else 5, waitingTimeInSec = if (c.hasPathOrNull("waitingTimeInSec")) c.getInt("waitingTimeInSec") else 360 ) } @@ -1543,31 +1544,6 @@ object BeamConfig { } } - case class Beamskimmer( - writeAllModeSkimsForPeakNonPeakPeriodsInterval: scala.Int, - writeFullSkimsInterval: scala.Int, - writeObservedSkimsInterval: scala.Int, - writeObservedSkimsPlusInterval: scala.Int - ) - - object Beamskimmer { - - def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Beamskimmer = { - BeamConfig.Beam.Beamskimmer( - writeAllModeSkimsForPeakNonPeakPeriodsInterval = - if (c.hasPathOrNull("writeAllModeSkimsForPeakNonPeakPeriodsInterval")) - c.getInt("writeAllModeSkimsForPeakNonPeakPeriodsInterval") - else 0, - writeFullSkimsInterval = - if (c.hasPathOrNull("writeFullSkimsInterval")) c.getInt("writeFullSkimsInterval") else 0, - writeObservedSkimsInterval = - if (c.hasPathOrNull("writeObservedSkimsInterval")) c.getInt("writeObservedSkimsInterval") else 0, - writeObservedSkimsPlusInterval = - if (c.hasPathOrNull("writeObservedSkimsPlusInterval")) c.getInt("writeObservedSkimsPlusInterval") else 0 - ) - } - } - case class Calibration( counts: BeamConfig.Beam.Calibration.Counts, meanToCountsWeightRatio: scala.Double, @@ -1913,6 +1889,21 @@ object BeamConfig { } } + case class H3( + lowerBoundResolution: scala.Int, + resolution: scala.Int + ) + + object H3 { + + def apply(c: com.typesafe.config.Config): BeamConfig.Beam.H3 = { + BeamConfig.Beam.H3( + lowerBoundResolution = if (c.hasPathOrNull("lowerBoundResolution")) c.getInt("lowerBoundResolution") else 10, + resolution = if (c.hasPathOrNull("resolution")) c.getInt("resolution") else 10 + ) + } + } + case class Logger( keepConsoleAppenderOn: scala.Boolean ) @@ -2220,6 +2211,108 @@ object BeamConfig { } } + case class Router( + skim: BeamConfig.Beam.Router.Skim + ) + + object Router { + case class Skim( + drive_time_skimmer: BeamConfig.Beam.Router.Skim.DriveTimeSkimmer, + keepKLatestSkims: scala.Int, + origin_destination_skimmer: BeamConfig.Beam.Router.Skim.OriginDestinationSkimmer, + taz_skimmer: BeamConfig.Beam.Router.Skim.TazSkimmer, + writeAggregatedSkimsInterval: scala.Int, + writeSkimsInterval: scala.Int + ) + + object Skim { + case class DriveTimeSkimmer( + fileBaseName: java.lang.String, + name: java.lang.String + ) + + object DriveTimeSkimmer { + + def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Router.Skim.DriveTimeSkimmer = { + BeamConfig.Beam.Router.Skim.DriveTimeSkimmer( + fileBaseName = + if (c.hasPathOrNull("fileBaseName")) c.getString("fileBaseName") + else "skimsTravelTimeObservedVsSimulated", + name = if (c.hasPathOrNull("name")) c.getString("name") else "drive-time-skimmer" + ) + } + } + + case class OriginDestinationSkimmer( + fileBaseName: java.lang.String, + name: java.lang.String, + writeAllModeSkimsForPeakNonPeakPeriodsInterval: scala.Int, + writeFullSkimsInterval: scala.Int + ) + + object OriginDestinationSkimmer { + + def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Router.Skim.OriginDestinationSkimmer = { + BeamConfig.Beam.Router.Skim.OriginDestinationSkimmer( + fileBaseName = if (c.hasPathOrNull("fileBaseName")) c.getString("fileBaseName") else "skimsOD", + name = if (c.hasPathOrNull("name")) c.getString("name") else "od-skimmer", + writeAllModeSkimsForPeakNonPeakPeriodsInterval = + if (c.hasPathOrNull("writeAllModeSkimsForPeakNonPeakPeriodsInterval")) + c.getInt("writeAllModeSkimsForPeakNonPeakPeriodsInterval") + else 0, + writeFullSkimsInterval = + if (c.hasPathOrNull("writeFullSkimsInterval")) c.getInt("writeFullSkimsInterval") else 0 + ) + } + } + + case class TazSkimmer( + fileBaseName: java.lang.String, + name: java.lang.String + ) + + object TazSkimmer { + + def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Router.Skim.TazSkimmer = { + BeamConfig.Beam.Router.Skim.TazSkimmer( + fileBaseName = if (c.hasPathOrNull("fileBaseName")) c.getString("fileBaseName") else "skimsTAZ", + name = if (c.hasPathOrNull("name")) c.getString("name") else "taz-skimmer" + ) + } + } + + def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Router.Skim = { + BeamConfig.Beam.Router.Skim( + drive_time_skimmer = BeamConfig.Beam.Router.Skim.DriveTimeSkimmer( + if (c.hasPathOrNull("drive-time-skimmer")) c.getConfig("drive-time-skimmer") + else com.typesafe.config.ConfigFactory.parseString("drive-time-skimmer{}") + ), + keepKLatestSkims = if (c.hasPathOrNull("keepKLatestSkims")) c.getInt("keepKLatestSkims") else 1, + origin_destination_skimmer = BeamConfig.Beam.Router.Skim.OriginDestinationSkimmer( + if (c.hasPathOrNull("origin-destination-skimmer")) c.getConfig("origin-destination-skimmer") + else com.typesafe.config.ConfigFactory.parseString("origin-destination-skimmer{}") + ), + taz_skimmer = BeamConfig.Beam.Router.Skim.TazSkimmer( + if (c.hasPathOrNull("taz-skimmer")) c.getConfig("taz-skimmer") + else com.typesafe.config.ConfigFactory.parseString("taz-skimmer{}") + ), + writeAggregatedSkimsInterval = + if (c.hasPathOrNull("writeAggregatedSkimsInterval")) c.getInt("writeAggregatedSkimsInterval") else 0, + writeSkimsInterval = if (c.hasPathOrNull("writeSkimsInterval")) c.getInt("writeSkimsInterval") else 0 + ) + } + } + + def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Router = { + BeamConfig.Beam.Router( + skim = BeamConfig.Beam.Router.Skim( + if (c.hasPathOrNull("skim")) c.getConfig("skim") + else com.typesafe.config.ConfigFactory.parseString("skim{}") + ) + ) + } + } + case class Routing( baseDate: java.lang.String, r5: BeamConfig.Beam.Routing.R5, @@ -2335,10 +2428,6 @@ object BeamConfig { if (c.hasPathOrNull("agentsim")) c.getConfig("agentsim") else com.typesafe.config.ConfigFactory.parseString("agentsim{}") ), - beamskimmer = BeamConfig.Beam.Beamskimmer( - if (c.hasPathOrNull("beamskimmer")) c.getConfig("beamskimmer") - else com.typesafe.config.ConfigFactory.parseString("beamskimmer{}") - ), calibration = BeamConfig.Beam.Calibration( if (c.hasPathOrNull("calibration")) c.getConfig("calibration") else com.typesafe.config.ConfigFactory.parseString("calibration{}") @@ -2359,6 +2448,8 @@ object BeamConfig { if (c.hasPathOrNull("experimental")) c.getConfig("experimental") else com.typesafe.config.ConfigFactory.parseString("experimental{}") ), + h3 = BeamConfig.Beam + .H3(if (c.hasPathOrNull("h3")) c.getConfig("h3") else com.typesafe.config.ConfigFactory.parseString("h3{}")), inputDirectory = if (c.hasPathOrNull("inputDirectory")) c.getString("inputDirectory") else "/test/input/beamville", logger = BeamConfig.Beam.Logger( @@ -2381,6 +2472,10 @@ object BeamConfig { if (c.hasPathOrNull("replanning")) c.getConfig("replanning") else com.typesafe.config.ConfigFactory.parseString("replanning{}") ), + router = BeamConfig.Beam.Router( + if (c.hasPathOrNull("router")) c.getConfig("router") + else com.typesafe.config.ConfigFactory.parseString("router{}") + ), routing = BeamConfig.Beam.Routing( if (c.hasPathOrNull("routing")) c.getConfig("routing") else com.typesafe.config.ConfigFactory.parseString("routing{}") diff --git a/src/main/scala/beam/sim/vehiclesharing/AvailabilityBasedRepositioning.scala b/src/main/scala/beam/sim/vehiclesharing/AvailabilityBasedRepositioning.scala index 7740d1722ad..7906a297346 100644 --- a/src/main/scala/beam/sim/vehiclesharing/AvailabilityBasedRepositioning.scala +++ b/src/main/scala/beam/sim/vehiclesharing/AvailabilityBasedRepositioning.scala @@ -2,8 +2,8 @@ package beam.sim.vehiclesharing import beam.agentsim.agents.vehicles.{BeamVehicle, BeamVehicleType} import beam.agentsim.events.SpaceTime import beam.agentsim.infrastructure.taz.{TAZ, TAZTreeMap} -import beam.router.BeamSkimmer import beam.router.Modes.BeamMode +import beam.router.skim.{ODSkims, Skims, TAZSkims} import beam.sim.BeamServices import org.matsim.api.core.v01.Id @@ -14,8 +14,7 @@ case class AvailabilityBasedRepositioning( statTimeBin: Int, matchLimit: Int, vehicleManager: Id[VehicleManager], - beamServices: BeamServices, - beamSkimmer: BeamSkimmer + beamServices: BeamServices ) extends RepositionAlgorithm { case class RepositioningRequest(taz: TAZ, availableVehicles: Int, shortage: Int) @@ -28,12 +27,15 @@ case class AvailabilityBasedRepositioning( (0 to 108000 / repositionTimeBin).foreach { i => val time = i * repositionTimeBin val availVal = getCollectedDataFromPreviousSimulation(time, taz.tazId, RepositionManager.availability) - val availValMin = availVal.drop(1).foldLeft(availVal.headOption.getOrElse(0.0).toInt) { (minV, cur) => - Math.min(minV, cur.toInt) + val availValMin = availVal.drop(1).foldLeft(availVal.headOption.map(_.numObservations).getOrElse(0)) { + (minV, cur) => + Math.min(minV, cur.numObservations) } minAvailabilityMap.put((i, taz.tazId), availValMin) - val inquiryVal = getCollectedDataFromPreviousSimulation(time, taz.tazId, RepositionManager.inquiry).sum.toInt - val boardingVal = getCollectedDataFromPreviousSimulation(time, taz.tazId, RepositionManager.boarded).sum.toInt + val inquiryVal = + getCollectedDataFromPreviousSimulation(time, taz.tazId, RepositionManager.inquiry).map(_.numObservations).sum + val boardingVal = + getCollectedDataFromPreviousSimulation(time, taz.tazId, RepositionManager.boarded).map(_.numObservations).sum unboardedVehicleInquiry.put((i, taz.tazId), inquiryVal - boardingVal) } } @@ -41,7 +43,10 @@ case class AvailabilityBasedRepositioning( def getCollectedDataFromPreviousSimulation(time: Int, idTAZ: Id[TAZ], label: String) = { val fromBin = time / statTimeBin val untilBin = (time + repositionTimeBin) / statTimeBin - beamSkimmer.getPreviousSkimPlusValues(fromBin, untilBin, idTAZ, vehicleManager, label) + (fromBin until untilBin) + .map(i => Skims.taz_skimmer.getLatestSkimByTAZ(i, idTAZ, vehicleManager.toString, label)) + .toVector + .flatten } override def getVehiclesForReposition( @@ -72,7 +77,7 @@ case class AvailabilityBasedRepositioning( val org = topOversuppliedTAZ.head var destTimeOpt: Option[(RepositioningRequest, Int)] = None topUndersuppliedTAZ.foreach { dst => - val skim = beamSkimmer.getTimeDistanceAndCost( + val skim = Skims.od_skimmer.getTimeDistanceAndCost( org.taz.coord, dst.taz.coord, now, @@ -80,7 +85,8 @@ case class AvailabilityBasedRepositioning( Id.create( // FIXME Vehicle type borrowed from ridehail -- pass the vehicle type of the car sharing fleet instead beamServices.beamConfig.beam.agentsim.agents.rideHail.initialization.procedural.vehicleTypeId, classOf[BeamVehicleType] - ) + ), + beamServices ) if (destTimeOpt.isEmpty || (destTimeOpt.isDefined && skim.time < destTimeOpt.get._2)) { destTimeOpt = Some((dst, skim.time)) diff --git a/src/main/scala/beam/sim/vehiclesharing/FixedNonReservingFleetManager.scala b/src/main/scala/beam/sim/vehiclesharing/FixedNonReservingFleetManager.scala index 8e9d20c85a6..e6ffa6fa3f5 100644 --- a/src/main/scala/beam/sim/vehiclesharing/FixedNonReservingFleetManager.scala +++ b/src/main/scala/beam/sim/vehiclesharing/FixedNonReservingFleetManager.scala @@ -22,7 +22,6 @@ import beam.agentsim.events.SpaceTime import beam.agentsim.infrastructure.{ParkingInquiry, ParkingInquiryResponse} import beam.agentsim.scheduler.BeamAgentScheduler.CompletionNotice import beam.agentsim.scheduler.Trigger.TriggerWithId -import beam.router.BeamSkimmer import beam.sim.BeamServices import com.vividsolutions.jts.geom.{Coordinate, Envelope} import com.vividsolutions.jts.index.quadtree.Quadtree @@ -41,7 +40,6 @@ private[vehiclesharing] class FixedNonReservingFleetManager( val vehicleType: BeamVehicleType, val mainScheduler: ActorRef, val beamServices: BeamServices, - val beamSkimmer: BeamSkimmer, val maxWalkingDistance: Int, val repositionAlgorithmType: Option[RepositionAlgorithmType] = None ) extends Actor @@ -67,7 +65,7 @@ private[vehiclesharing] class FixedNonReservingFleetManager( vehicle.id -> vehicle }).toMap - private val availableVehicles = mutable.Map[Id[BeamVehicle], BeamVehicle]() + private val availableVehicles = mutable.Map.empty[Id[BeamVehicle], BeamVehicle] private val availableVehiclesIndex = new Quadtree override def receive: Receive = super[RepositionManager].receive orElse { // Reposition @@ -129,7 +127,7 @@ private[vehiclesharing] class FixedNonReservingFleetManager( override def getScheduler: ActorRef = mainScheduler override def getServices: BeamServices = beamServices def getRepositionAlgorithmType: Option[RepositionAlgorithmType] = repositionAlgorithmType - override def getSkimmer: BeamSkimmer = beamSkimmer + override def getAvailableVehicles: Iterable[BeamVehicle] = availableVehicles.values override def makeAvailable(vehId: Id[BeamVehicle]): Boolean = { val vehicle = vehicles(vehId) diff --git a/src/main/scala/beam/sim/vehiclesharing/Fleet.scala b/src/main/scala/beam/sim/vehiclesharing/Fleet.scala index af4ad6cfba0..b82c43f0257 100644 --- a/src/main/scala/beam/sim/vehiclesharing/Fleet.scala +++ b/src/main/scala/beam/sim/vehiclesharing/Fleet.scala @@ -3,7 +3,6 @@ import akka.actor.{ActorRef, Props} import beam.agentsim.agents.Population import beam.agentsim.agents.vehicles.BeamVehicleType import beam.agentsim.infrastructure.taz.{TAZ, TAZTreeMap} -import beam.router.BeamSkimmer import beam.sim.BeamServices import beam.sim.config.BeamConfig import beam.sim.config.BeamConfig.Beam.Agentsim.Agents.Vehicles.SharedFleets$Elm @@ -19,7 +18,6 @@ trait FleetType { def props( beamServices: BeamServices, - beamSkimmer: BeamSkimmer, beamScheduler: ActorRef, parkingManager: ActorRef ): Props @@ -35,7 +33,6 @@ case class FixedNonReservingFleetByTAZ( extends Exception(message, cause) override def props( beamServices: BeamServices, - beamSkimmer: BeamSkimmer, beamScheduler: ActorRef, parkingManager: ActorRef ): Props = { @@ -82,7 +79,6 @@ case class FixedNonReservingFleetByTAZ( vehicleType, beamScheduler, beamServices, - beamSkimmer, config.maxWalkingDistance, repConfig.map(RepositionAlgorithms.lookup(_)) ) @@ -94,7 +90,6 @@ case class FixedNonReservingFleet(managerId: Id[VehicleManager], config: SharedF extends FleetType { override def props( beamServices: BeamServices, - skimmer: BeamSkimmer, beamScheduler: ActorRef, parkingManager: ActorRef ): Props = { @@ -115,7 +110,6 @@ case class FixedNonReservingFleet(managerId: Id[VehicleManager], config: SharedF vehicleType, beamScheduler, beamServices, - skimmer, config.maxWalkingDistance ) ) @@ -125,7 +119,6 @@ case class FixedNonReservingFleet(managerId: Id[VehicleManager], config: SharedF case class InexhaustibleReservingFleet(config: SharedFleets$Elm.InexhaustibleReserving) extends FleetType { override def props( beamServices: BeamServices, - skimmer: BeamSkimmer, beamScheduler: ActorRef, parkingManager: ActorRef ): Props = { diff --git a/src/main/scala/beam/sim/vehiclesharing/RepositionAlgorithms.scala b/src/main/scala/beam/sim/vehiclesharing/RepositionAlgorithms.scala index f75fea3f0ba..ae38db4c1a4 100644 --- a/src/main/scala/beam/sim/vehiclesharing/RepositionAlgorithms.scala +++ b/src/main/scala/beam/sim/vehiclesharing/RepositionAlgorithms.scala @@ -1,6 +1,5 @@ package beam.sim.vehiclesharing -import beam.router.BeamSkimmer import beam.sim.BeamServices import beam.sim.config.BeamConfig import org.matsim.api.core.v01.Id @@ -23,8 +22,7 @@ trait RepositionAlgorithmType { def getInstance( managerId: Id[VehicleManager], - beamServices: BeamServices, - beamSkimmer: BeamSkimmer + beamServices: BeamServices ): RepositionAlgorithm def getRepositionTimeBin: Int def getStatTimeBin: Int @@ -35,16 +33,14 @@ case class AvailabilityBasedRepositioningType( ) extends RepositionAlgorithmType { override def getInstance( managerId: Id[VehicleManager], - beamServices: BeamServices, - beamSkimmer: BeamSkimmer + beamServices: BeamServices ): RepositionAlgorithm = { AvailabilityBasedRepositioning( params.repositionTimeBin, params.statTimeBin, params.min_availability_undersupply_algorithm.get.matchLimit, managerId, - beamServices, - beamSkimmer + beamServices ) } def getRepositionTimeBin: Int = params.repositionTimeBin diff --git a/src/main/scala/beam/sim/vehiclesharing/RepositionManager.scala b/src/main/scala/beam/sim/vehiclesharing/RepositionManager.scala index 3889e7efacb..c61a10f066b 100644 --- a/src/main/scala/beam/sim/vehiclesharing/RepositionManager.scala +++ b/src/main/scala/beam/sim/vehiclesharing/RepositionManager.scala @@ -7,18 +7,46 @@ import beam.agentsim.infrastructure.taz.TAZ import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTrigger} import beam.agentsim.scheduler.Trigger import beam.agentsim.scheduler.Trigger.TriggerWithId -import beam.router.BeamSkimmer +import beam.router.skim.TAZSkimmerEvent import beam.sim.BeamServices import org.matsim.api.core.v01.{Coord, Id} trait RepositionManager extends Actor with ActorLogging { + var currentTick = 0 + val eos = 108000 + + val (algorithm, repTime, statTime) = getRepositionAlgorithmType match { + case Some(algorithmType) => + getScheduler ! ScheduleTrigger(REPDataCollectionTrigger(algorithmType.getStatTimeBin), self) + var alg: RepositionAlgorithm = null + if (getServices.matsimServices.getIterationNumber > 0 || getServices.beamConfig.beam.warmStart.enabled) { + alg = algorithmType.getInstance(getId, getServices) + getScheduler ! ScheduleTrigger(REPVehicleRepositionTrigger(algorithmType.getRepositionTimeBin), self) + } + (alg, algorithmType.getRepositionTimeBin, algorithmType.getStatTimeBin) + case _ => + (null, 0, 1) + } + + // **** + def getId: Id[VehicleManager] + def queryAvailableVehicles: List[BeamVehicle] + def getAvailableVehicles: Iterable[BeamVehicle] + def makeUnavailable(vehId: Id[BeamVehicle], streetVehicle: StreetVehicle): Option[BeamVehicle] + def makeAvailable(vehId: Id[BeamVehicle]): Boolean + def makeTeleport(vehId: Id[BeamVehicle], whenWhere: SpaceTime): Unit + def getScheduler: ActorRef + def getServices: BeamServices + def getRepositionAlgorithmType: Option[RepositionAlgorithmType] + + // *** override def receive: Receive = { case TriggerWithId(REPDataCollectionTrigger(tick), triggerId) => currentTick = tick val nextTick = tick + statTime if (nextTick < eos) { - collectData(tick) + queryAvailableVehicles.foreach(v => collectData(tick, v.spaceTime.loc, RepositionManager.availability)) sender ! CompletionNotice(triggerId, Vector(ScheduleTrigger(REPDataCollectionTrigger(nextTick), self))) } else { sender ! CompletionNotice(triggerId) @@ -49,46 +77,10 @@ trait RepositionManager extends Actor with ActorLogging { sender ! CompletionNotice(triggerId) } - var currentTick = 0 - val eos = 108000 - - val (algorithm, repTime, statTime) = getRepositionAlgorithmType match { - case Some(algorithmType) => - getScheduler ! ScheduleTrigger(REPDataCollectionTrigger(algorithmType.getStatTimeBin), self) - var alg: RepositionAlgorithm = null - if (getServices.matsimServices.getIterationNumber > 0 || getServices.beamConfig.beam.warmStart.enabled) { - alg = algorithmType.getInstance(getId, getServices, getSkimmer) - getScheduler ! ScheduleTrigger(REPVehicleRepositionTrigger(algorithmType.getRepositionTimeBin), self) - } - (alg, algorithmType.getRepositionTimeBin, algorithmType.getStatTimeBin) - case _ => - (null, 0, 0) - } - - // **** - def getId: Id[VehicleManager] - def queryAvailableVehicles: List[BeamVehicle] - def makeUnavailable(vehId: Id[BeamVehicle], streetVehicle: StreetVehicle): Option[BeamVehicle] - def makeAvailable(vehId: Id[BeamVehicle]): Boolean - def makeTeleport(vehId: Id[BeamVehicle], whenWhere: SpaceTime): Unit - def getScheduler: ActorRef - def getServices: BeamServices - def getSkimmer: BeamSkimmer - def getRepositionAlgorithmType: Option[RepositionAlgorithmType] - - def collectData(time: Int, coord: Coord, label: String) = { - if (statTime != 0) - getSkimmer.countEventsByTAZ(time / statTime, coord, getId, label) - } - - def collectData(time: Int) = { - if (statTime != 0) - getSkimmer.observeVehicleAvailabilityByTAZ( - time / statTime, - getId, - RepositionManager.availability, - queryAvailableVehicles - ) + protected def collectData(time: Int, loc: Coord, varLabel: String) = { + getServices.matsimServices.getEvents.processEvent( + TAZSkimmerEvent(time, getServices, time / statTime, loc, getId.toString, varLabel) + ) } } diff --git a/src/main/scala/beam/utils/map/TazTravelTimeAnalyzer.scala b/src/main/scala/beam/utils/map/TazTravelTimeAnalyzer.scala index cfca120818b..c47e7c10397 100644 --- a/src/main/scala/beam/utils/map/TazTravelTimeAnalyzer.scala +++ b/src/main/scala/beam/utils/map/TazTravelTimeAnalyzer.scala @@ -4,16 +4,14 @@ import java.io.{BufferedWriter, Closeable} import beam.agentsim.events.PathTraversalEvent import beam.agentsim.infrastructure.taz._ -import beam.router.TravelTimeObserved -import beam.router.TravelTimeObserved.PathCache -import beam.sim.BeamServices +import beam.router.skim.SkimsUtils import beam.sim.common.GeoUtils import beam.sim.config.BeamConfig import beam.utils.map.R5NetworkPlayground.prepareConfig import beam.utils.{EventReader, ProfilingUtils} import com.typesafe.scalalogging.LazyLogging -import org.matsim.api.core.v01.{Coord, Id} import org.matsim.api.core.v01.events.Event +import org.matsim.api.core.v01.{Coord, Id} import org.matsim.core.utils.io.IOUtils case class Taz2TazWithTrip( @@ -52,6 +50,8 @@ object Trip { object TazTravelTimeAnalyzer extends LazyLogging { + import SkimsUtils._ + def filter(event: Event): Boolean = { val attribs = event.getAttributes // We need only PathTraversal with mode `CAR`, no ride hail @@ -204,19 +204,22 @@ object TazTravelTimeAnalyzer extends LazyLogging { beamConfig: BeamConfig, geoUtils: GeoUtils, tazTreeMap: TAZTreeMap - ): Map[PathCache, Float] = { + ): Map[SkimsUtils.PathCache, Float] = { + + val maxDistanceFromBeamTaz: Double = 500.0 // 500 meters val zoneBoundariesFilePath = beamConfig.beam.calibration.roadNetwork.travelTimes.zoneBoundariesFilePath val zoneODTravelTimesFilePath = beamConfig.beam.calibration.roadNetwork.travelTimes.zoneODTravelTimesFilePath if (zoneBoundariesFilePath.nonEmpty && zoneODTravelTimesFilePath.nonEmpty) { - val tazToMovId: Map[TAZ, Int] = TravelTimeObserved.buildTAZ2MovementId( + val tazToMovId: Map[TAZ, Int] = buildTAZ2MovementId( zoneBoundariesFilePath, geoUtils, - tazTreeMap + tazTreeMap, + maxDistanceFromBeamTaz ) val movId2Taz: Map[Int, TAZ] = tazToMovId.map { case (k, v) => v -> k } - TravelTimeObserved.buildPathCache2TravelTime(zoneODTravelTimesFilePath, movId2Taz) + buildPathCache2TravelTime(zoneODTravelTimesFilePath, movId2Taz) } else throw new RuntimeException("check file exists") } diff --git a/src/test/scala/beam/agentsim/agents/PersonAgentSpec.scala b/src/test/scala/beam/agentsim/agents/PersonAgentSpec.scala index d0d147589c1..25301289f87 100644 --- a/src/test/scala/beam/agentsim/agents/PersonAgentSpec.scala +++ b/src/test/scala/beam/agentsim/agents/PersonAgentSpec.scala @@ -15,11 +15,12 @@ import beam.agentsim.scheduler.BeamAgentScheduler import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTrigger, SchedulerProps, StartSchedule} import beam.router.BeamRouter._ import beam.router.Modes.BeamMode -import beam.router.Modes.BeamMode.{RIDE_HAIL, RIDE_HAIL_TRANSIT, TRANSIT, WALK, WALK_TRANSIT} +import beam.router.Modes.BeamMode.{RIDE_HAIL, RIDE_HAIL_TRANSIT, WALK, WALK_TRANSIT} +import beam.router.RouteHistory import beam.router.model.RoutingModel.TransitStopsInfo import beam.router.model.{EmbodiedBeamLeg, _} import beam.router.osm.TollCalculator -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} +import beam.router.skim.AbstractSkimmerEvent import beam.utils.TestConfigUtils.testConfig import beam.utils.{SimRunnerForTest, StuckFinder, TestConfigUtils} import com.typesafe.config.{Config, ConfigFactory} @@ -77,7 +78,10 @@ class PersonAgentSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - self ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => self ! event + } } } ) @@ -115,9 +119,7 @@ class PersonAgentSpec parkingManager, services.tollCalculator, self, - beamSkimmer = new BeamSkimmer(services, beamScenario, services.geo), routeHistory = new RouteHistory(beamConfig), - travelTimeObserved = new TravelTimeObserved(services, beamScenario, services.geo), boundingBox = boundingBox ) ) @@ -134,7 +136,10 @@ class PersonAgentSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - self ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => self ! event + } } } ) @@ -180,8 +185,6 @@ class PersonAgentSpec new Coord(0.0, 0.0), Vector(), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) @@ -283,7 +286,10 @@ class PersonAgentSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - events.ref ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => events.ref ! event + } } } ) @@ -379,8 +385,6 @@ class PersonAgentSpec homeCoord = new Coord(0.0, 0.0), Vector(), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) @@ -518,7 +522,10 @@ class PersonAgentSpec val events = new TestProbe(system) eventsManager.addHandler(new BasicEventHandler { override def handleEvent(event: Event): Unit = { - events.ref ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => events.ref ! event + } } }) val transitDriverProps = Props(new ForwardActor(self)) @@ -657,8 +664,6 @@ class PersonAgentSpec new Coord(0.0, 0.0), Vector(), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) diff --git a/src/test/scala/beam/agentsim/agents/PersonAndTransitDriverSpec.scala b/src/test/scala/beam/agentsim/agents/PersonAndTransitDriverSpec.scala index 3ded199a8fa..52439962713 100644 --- a/src/test/scala/beam/agentsim/agents/PersonAndTransitDriverSpec.scala +++ b/src/test/scala/beam/agentsim/agents/PersonAndTransitDriverSpec.scala @@ -18,9 +18,10 @@ import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTri import beam.router.BeamRouter._ import beam.router.Modes.BeamMode import beam.router.Modes.BeamMode.WALK_TRANSIT +import beam.router.RouteHistory import beam.router.model.RoutingModel.TransitStopsInfo import beam.router.model.{EmbodiedBeamLeg, _} -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} +import beam.router.skim.AbstractSkimmerEvent import beam.sim.common.GeoUtilsImpl import beam.utils.TestConfigUtils.testConfig import beam.utils.{SimRunnerForTest, StuckFinder, TestConfigUtils} @@ -91,6 +92,7 @@ class PersonAndTransitDriverSpec val personEvents = new TestProbe(system) val otherEvents = new TestProbe(system) val agencyEvents = new TestProbe(system) + val skimEvents = new TestProbe(system) val eventsManager: EventsManager = new EventsManagerImpl() eventsManager.addHandler( @@ -112,6 +114,8 @@ class PersonAndTransitDriverSpec personEvents.ref ! event case agencyRevenueEvent: AgencyRevenueEvent => agencyEvents.ref ! event + case _: AbstractSkimmerEvent => + skimEvents.ref ! event case _ => otherEvents.ref ! event } @@ -345,8 +349,6 @@ class PersonAndTransitDriverSpec homeCoord = new Coord(0.0, 0.0), Vector(), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) @@ -445,6 +447,8 @@ class PersonAndTransitDriverSpec agencyEvents.expectMsgType[AgencyRevenueEvent] + skimEvents.expectMsgType[AbstractSkimmerEvent] + otherEvents.expectNoMessage() expectMsgType[CompletionNotice] diff --git a/src/test/scala/beam/agentsim/agents/PersonWithPersonalVehiclePlanSpec.scala b/src/test/scala/beam/agentsim/agents/PersonWithPersonalVehiclePlanSpec.scala index f9014223241..374273896d8 100644 --- a/src/test/scala/beam/agentsim/agents/PersonWithPersonalVehiclePlanSpec.scala +++ b/src/test/scala/beam/agentsim/agents/PersonWithPersonalVehiclePlanSpec.scala @@ -14,8 +14,9 @@ import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTri import beam.router.BeamRouter._ import beam.router.Modes.BeamMode import beam.router.Modes.BeamMode.{BIKE, CAR, WALK} +import beam.router.RouteHistory import beam.router.model.{EmbodiedBeamLeg, _} -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} +import beam.router.skim.AbstractSkimmerEvent import beam.utils.TestConfigUtils.testConfig import beam.utils.{SimRunnerForTest, StuckFinder, TestConfigUtils} import com.typesafe.config.{Config, ConfigFactory} @@ -31,8 +32,8 @@ import org.matsim.core.population.routes.RouteUtils import org.matsim.households.{Household, HouseholdsFactoryImpl} import org.matsim.vehicles._ import org.scalatest.Matchers._ -import org.scalatestplus.mockito.MockitoSugar import org.scalatest.{BeforeAndAfterAll, FunSpecLike} +import org.scalatestplus.mockito.MockitoSugar import scala.collection.{mutable, JavaConverters} @@ -74,7 +75,10 @@ class PersonWithPersonalVehiclePlanSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - self ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => self ! event + } } } ) @@ -123,8 +127,6 @@ class PersonWithPersonalVehiclePlanSpec new Coord(0.0, 0.0), Vector(), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) @@ -296,7 +298,10 @@ class PersonWithPersonalVehiclePlanSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - self ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => self ! event + } } } ) @@ -344,8 +349,6 @@ class PersonWithPersonalVehiclePlanSpec new Coord(0.0, 0.0), Vector(), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) @@ -431,11 +434,11 @@ class PersonWithPersonalVehiclePlanSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - if (event.isInstanceOf[ModeChoiceEvent]) { - modeChoiceEvents.ref ! event - } - if (event.isInstanceOf[PersonEntersVehicleEvent]) { - personEntersVehicleEvents.ref ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _: ModeChoiceEvent => modeChoiceEvents.ref ! event + case _: PersonEntersVehicleEvent => personEntersVehicleEvents.ref ! event + case _ => // ignore } } } @@ -489,8 +492,6 @@ class PersonWithPersonalVehiclePlanSpec new Coord(0.0, 0.0), Vector(), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) @@ -538,7 +539,10 @@ class PersonWithPersonalVehiclePlanSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - self ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => self ! event + } } } ) @@ -584,8 +588,6 @@ class PersonWithPersonalVehiclePlanSpec new Coord(0.0, 0.0), Vector(), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) diff --git a/src/test/scala/beam/agentsim/agents/PersonWithVehicleSharingSpec.scala b/src/test/scala/beam/agentsim/agents/PersonWithVehicleSharingSpec.scala index 4a7333fb4ca..aa5b029619a 100644 --- a/src/test/scala/beam/agentsim/agents/PersonWithVehicleSharingSpec.scala +++ b/src/test/scala/beam/agentsim/agents/PersonWithVehicleSharingSpec.scala @@ -25,8 +25,9 @@ import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTri import beam.router.BeamRouter._ import beam.router.Modes.BeamMode import beam.router.Modes.BeamMode.{CAR, WALK} +import beam.router.RouteHistory import beam.router.model.{EmbodiedBeamLeg, _} -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} +import beam.router.skim.AbstractSkimmerEvent import beam.utils.TestConfigUtils.testConfig import beam.utils.{SimRunnerForTest, StuckFinder, TestConfigUtils} import com.typesafe.config.{Config, ConfigFactory} @@ -89,7 +90,10 @@ class PersonWithVehicleSharingSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - events.ref ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => events.ref ! event + } } } ) @@ -134,8 +138,6 @@ class PersonWithVehicleSharingSpec new Coord(0.0, 0.0), sharedVehicleFleets = Vector(mockSharedVehicleFleet.ref), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) @@ -232,7 +234,10 @@ class PersonWithVehicleSharingSpec eventsManager.addHandler( new BasicEventHandler { override def handleEvent(event: Event): Unit = { - events.ref ! event + event match { + case _: AbstractSkimmerEvent => // ignore + case _ => events.ref ! event + } } } ) @@ -278,8 +283,6 @@ class PersonWithVehicleSharingSpec new Coord(0.0, 0.0), sharedVehicleFleets = Vector(mockSharedVehicleFleet.ref), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) @@ -517,8 +520,6 @@ class PersonWithVehicleSharingSpec new Coord(0.0, 0.0), Vector(mockSharedVehicleFleet.ref), new RouteHistory(beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), boundingBox ) ) diff --git a/src/test/scala/beam/agentsim/agents/household/FastHouseholdCAVSchedulingSpec.scala b/src/test/scala/beam/agentsim/agents/household/FastHouseholdCAVSchedulingSpec.scala index 564b2b3ce6b..0597e50d1a9 100644 --- a/src/test/scala/beam/agentsim/agents/household/FastHouseholdCAVSchedulingSpec.scala +++ b/src/test/scala/beam/agentsim/agents/household/FastHouseholdCAVSchedulingSpec.scala @@ -6,8 +6,7 @@ import akka.testkit.{ImplicitSender, TestKit} import akka.util.Timeout import beam.agentsim.agents.vehicles.EnergyEconomyAttributes.Powertrain import beam.agentsim.agents.vehicles.{BeamVehicle, BeamVehicleType} -import beam.router.BeamSkimmer -import beam.sim.common.GeoUtilsImpl +import beam.router.skim.Skims import beam.sim.config.{BeamConfig, MatSimBeamConfigBuilder} import beam.sim.{BeamHelper, BeamServicesImpl} import beam.utils.TestConfigUtils @@ -15,14 +14,11 @@ import beam.utils.TestConfigUtils.testConfig import com.typesafe.config.ConfigFactory import org.matsim.api.core.v01.population.{Activity, Person, Plan, Population} import org.matsim.api.core.v01.{Coord, Id} -import org.matsim.core.config.ConfigUtils import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting import org.matsim.core.population.PopulationUtils -import org.matsim.core.population.io.PopulationReader -import org.matsim.core.scenario.ScenarioUtils -import org.matsim.households.{Household, HouseholdImpl, HouseholdsFactoryImpl, HouseholdsReaderV10} -import org.scalatestplus.mockito.MockitoSugar +import org.matsim.households.{Household, HouseholdsFactoryImpl} import org.scalatest.{BeforeAndAfterAll, FunSpecLike, Matchers} +import org.scalatestplus.mockito.MockitoSugar import scala.collection.immutable.List import scala.collection.{mutable, JavaConverters} @@ -55,6 +51,7 @@ class FastHouseholdCAVSchedulingSpec private implicit val executionContext: ExecutionContext = system.dispatcher private lazy val beamCfg = BeamConfig(system.settings.config) private lazy val beamScenario = loadScenario(beamCfg) + private val householdsFactory: HouseholdsFactoryImpl = new HouseholdsFactoryImpl() private val matsimConfig = new MatSimBeamConfigBuilder(system.settings.config).buildMatSimConf() matsimConfig.controler.setOutputDirectory(TestConfigUtils.testOutputDir) @@ -64,10 +61,9 @@ class FastHouseholdCAVSchedulingSpec private val injector = buildInjector(system.settings.config, beamCfg, scenario, beamScenario) val services = new BeamServicesImpl(injector) - private lazy val skimmer: BeamSkimmer = new BeamSkimmer(services, beamScenario, new GeoUtilsImpl(beamCfg)) - describe("A Household CAV Scheduler") { it("generates two schedules") { + Skims.setup(services) val cavs = List[BeamVehicle]( new BeamVehicle( Id.createVehicleId("id1"), @@ -75,8 +71,8 @@ class FastHouseholdCAVSchedulingSpec defaultCAVBeamVehicleType ) ) - val (pop: Population, household) = scenario1(cavs) - val alg = new FastHouseholdCAVScheduling(household, cavs, skimmer = skimmer)(pop) + val household = scenario1(cavs) + val alg = new FastHouseholdCAVScheduling(household, cavs, services) alg.waitingTimeInSec = 2 alg.delayToArrivalInSec = 2 alg.stopSearchAfterXSolutions = 5000 @@ -85,9 +81,11 @@ class FastHouseholdCAVSchedulingSpec schedules should have length 1 schedules foreach (_.schedulesMap(cavs.head).schedule should have length 6) println(s"*** scenario 1 *** ${schedules.size} combinations") + Skims.clear() } it("pool two persons for both trips") { + Skims.setup(services) val cavs = List[BeamVehicle]( new BeamVehicle( Id.createVehicleId("id1"), @@ -100,12 +98,12 @@ class FastHouseholdCAVSchedulingSpec beamScenario.vehicleTypes(Id.create("beamVilleCar", classOf[BeamVehicleType])) ) ) - val (pop: Population, household) = scenario2(cavs) + val household = scenario2(cavs) val alg = new FastHouseholdCAVScheduling( household, cavs, - skimmer = skimmer - )(pop) + services + ) alg.waitingTimeInSec = 60 * 60 alg.delayToArrivalInSec = 60 * 60 alg.stopSearchAfterXSolutions = 5000 @@ -114,9 +112,11 @@ class FastHouseholdCAVSchedulingSpec schedules should have length 3 schedules foreach (_.schedulesMap(cavs.head).schedule should (have length 1 or (have length 6 or have length 10))) println(s"*** scenario 2 *** ${schedules.size} combinations") + Skims.clear() } it("pool both agents in different CAVs") { + Skims.setup(services) val cavs = List[BeamVehicle]( new BeamVehicle( Id.createVehicleId("id1"), @@ -129,12 +129,12 @@ class FastHouseholdCAVSchedulingSpec defaultCAVBeamVehicleType ) ) - val (pop: Population, household) = scenario5(cavs) + val household = scenario5(cavs) val alg = new FastHouseholdCAVScheduling( household, cavs, - skimmer = skimmer - )(pop) + services + ) alg.waitingTimeInSec = 60 * 60 alg.delayToArrivalInSec = 60 * 60 alg.stopSearchAfterXSolutions = 5000 @@ -150,170 +150,112 @@ class FastHouseholdCAVSchedulingSpec // third check val schedules3 = alg.getKBestSchedules(1) schedules3.head.foldLeft(0)(_ + _.schedule.size) shouldBe 10 - } - - it("be scalable") { - var sum = 0 - var count = 0 - val t0 = System.nanoTime() - val (pop: Population, households) = scenarioPerformance() - for ((household, vehicles) <- households) { - val alg = - new FastHouseholdCAVScheduling( - household, - vehicles, - skimmer = skimmer - )(pop) - alg.waitingTimeInSec = 5 * 60 - alg.delayToArrivalInSec = 10 * 60 - alg.stopSearchAfterXSolutions = 1000 - alg.limitCavToXPersons = 9999 - val schedules = alg.getAllFeasibleSchedules - sum += schedules.size - count += 1 - //println(s"household [${household.getId}]: ${schedules.size}") - } - val t1 = System.nanoTime() - val elapsed = ((t1 - t0) / 1E9).toInt - //elapsed shouldBe <(60) - println(s"*** scenario 6 *** ${sum / count} avg combinations per household, $elapsed sec elapsed ") + Skims.clear() } } private def defaultCAVBeamVehicleType = beamScenario.vehicleTypes(Id.create("CAV", classOf[BeamVehicleType])) - private def scenario1(vehicles: List[BeamVehicle]): (Population, Household) = { - val sc: org.matsim.api.core.v01.Scenario = ScenarioUtils.createMutableScenario(ConfigUtils.createConfig()) - ScenarioUtils.loadScenario(sc) - val household = new HouseholdsFactoryImpl().createHousehold(Id.create("dummy_scenario1", classOf[Household])) - val popFactory = sc.getPopulation.getFactory + private def scenario1(vehicles: List[BeamVehicle]): Household = { + val population = services.matsimServices.getScenario.getPopulation + val hoseHoldDummyId = Id.create("dummy1", classOf[Household]) + val household = householdsFactory.createHousehold(hoseHoldDummyId) - val p: Person = popFactory.createPerson(Id.createPersonId(household.getId + "_P1")) + val p: Person = population.getFactory.createPerson(Id.createPersonId(household.getId + "_P1")) val homeCoord = new Coord(0, 0) - val H11: Activity = popFactory.createActivityFromCoord("home", homeCoord) + val H11: Activity = population.getFactory.createActivityFromCoord("home", homeCoord) H11.setEndTime(8 * 3600 + 30 * 60) - val W1: Activity = popFactory.createActivityFromCoord("work", new Coord(30, 0)) + val W1: Activity = population.getFactory.createActivityFromCoord("work", new Coord(30, 0)) W1.setEndTime(17 * 3600) - val H12: Activity = popFactory.createActivityFromCoord("home", homeCoord) - val plan: Plan = popFactory.createPlan() + val H12: Activity = population.getFactory.createActivityFromCoord("home", homeCoord) + val plan: Plan = population.getFactory.createPlan() plan.setPerson(p) plan.addActivity(H11) plan.addActivity(W1) plan.addActivity(H12) p.addPlan(plan) - sc.getPopulation.addPerson(p) + population.addPerson(p) household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(p.getId))) household.setVehicleIds(JavaConverters.seqAsJavaList(vehicles.map(veh => veh.toStreetVehicle.id))) - (sc.getPopulation, household) + household } - private def scenario2(vehicles: List[BeamVehicle]): (Population, Household) = { - val sc: org.matsim.api.core.v01.Scenario = ScenarioUtils.createMutableScenario(ConfigUtils.createConfig()) - ScenarioUtils.loadScenario(sc) - val pop = sc.getPopulation + private def scenario2(vehicles: List[BeamVehicle]): Household = { + val population = services.matsimServices.getScenario.getPopulation val homeCoord = new Coord(0, 0) - val household = new HouseholdsFactoryImpl().createHousehold(Id.create("dummyHH_scenario2", classOf[Household])) + val hoseHoldDummyId = Id.create("dummy2", classOf[Household]) + val household = householdsFactory.createHousehold(hoseHoldDummyId) - val P1: Person = pop.getFactory.createPerson(Id.createPersonId(household.getId + "_P1")) + val P1: Person = population.getFactory.createPerson(Id.createPersonId(household.getId + "_P1")) val H11: Activity = PopulationUtils.createActivityFromCoord("home", homeCoord) H11.setEndTime(9 * 3600) val W1: Activity = PopulationUtils.createActivityFromCoord("work1", new Coord(24166, 13820)) W1.setEndTime(17 * 3600) val H12: Activity = PopulationUtils.createActivityFromCoord("home", homeCoord) - val plan1: Plan = pop.getFactory.createPlan() + val plan1: Plan = population.getFactory.createPlan() plan1.setPerson(P1) plan1.addActivity(H11) plan1.addActivity(W1) plan1.addActivity(H12) P1.addPlan(plan1) - pop.addPerson(P1) + population.addPerson(P1) - val P2: Person = pop.getFactory.createPerson(Id.createPersonId(household.getId + "_P2")) + val P2: Person = population.getFactory.createPerson(Id.createPersonId(household.getId + "_P2")) val H21: Activity = PopulationUtils.createActivityFromCoord("home", homeCoord) H21.setEndTime(9 * 3600 + 5 * 60) val W2: Activity = PopulationUtils.createActivityFromCoord("work2", new Coord(20835, 0)) W2.setEndTime(17 * 3600 + 5 * 60) val H22: Activity = PopulationUtils.createActivityFromCoord("home", homeCoord) - val plan2: Plan = pop.getFactory.createPlan() + val plan2: Plan = population.getFactory.createPlan() plan2.setPerson(P2) plan2.addActivity(H21) plan2.addActivity(W2) plan2.addActivity(H22) P2.addPlan(plan2) - pop.addPerson(P2) + population.addPerson(P2) household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(P1.getId, P2.getId))) household.setVehicleIds(JavaConverters.seqAsJavaList(vehicles.map(veh => veh.toStreetVehicle.id))) - (sc.getPopulation, household) - } - - private def scenarioPerformance(): (Population, List[(Household, List[BeamVehicle])]) = { - val sc: org.matsim.api.core.v01.Scenario = ScenarioUtils.createMutableScenario(ConfigUtils.createConfig()) - ScenarioUtils.loadScenario(sc) - new PopulationReader(sc).readFile("test/input/sf-light/sample/25k/population.xml.gz") - new HouseholdsReaderV10(sc.getHouseholds).readFile("test/input/sf-light/sample/25k/households.xml") - val households: mutable.ListBuffer[(Household, List[BeamVehicle])] = - mutable.ListBuffer.empty[(Household, List[BeamVehicle])] - import scala.collection.JavaConverters._ - for (hh: Household <- sc.getHouseholds.getHouseholds.asScala.values) { - //hh.asInstanceOf[HouseholdImpl].setMemberIds(hh.getMemberIds) - val vehicles = List[BeamVehicle]( - new BeamVehicle( - Id.createVehicleId(hh.getId.toString + "-veh1"), - new Powertrain(0.0), - defaultCAVBeamVehicleType - ), - new BeamVehicle( - Id.createVehicleId(hh.getId.toString + "-veh2"), - new Powertrain(0.0), - defaultCAVBeamVehicleType - ) - ) - hh.asInstanceOf[HouseholdImpl] - .setVehicleIds(JavaConverters.seqAsJavaList(vehicles.map(veh => veh.toStreetVehicle.id))) - households.append((hh.asInstanceOf[HouseholdImpl], vehicles)) - } - (sc.getPopulation, households.toList) + household } - private def scenario5(vehicles: List[BeamVehicle]): (Population, Household) = { - val sc: org.matsim.api.core.v01.Scenario = ScenarioUtils.createMutableScenario(ConfigUtils.createConfig()) - ScenarioUtils.loadScenario(sc) - val pop = sc.getPopulation + private def scenario5(vehicles: List[BeamVehicle]): Household = { + val population = services.matsimServices.getScenario.getPopulation val homeCoord = new Coord(0, 0) - val household = new HouseholdsFactoryImpl().createHousehold(Id.create("dummyHH_scenario5", classOf[Household])) + val hoseHoldDummyId = Id.create("dummy3", classOf[Household]) + val household = householdsFactory.createHousehold(hoseHoldDummyId) - val P1: Person = pop.getFactory.createPerson(Id.createPersonId(household.getId + "_P1")) + val P1: Person = population.getFactory.createPerson(Id.createPersonId(household.getId + "_P1")) val H11: Activity = PopulationUtils.createActivityFromCoord("home", homeCoord) H11.setEndTime(9 * 3600) val W1: Activity = PopulationUtils.createActivityFromCoord("work1", new Coord(24166, 13820)) W1.setEndTime(17 * 3600) val H12: Activity = PopulationUtils.createActivityFromCoord("home", homeCoord) - val plan1: Plan = pop.getFactory.createPlan() + val plan1: Plan = population.getFactory.createPlan() plan1.setPerson(P1) plan1.addActivity(H11) plan1.addActivity(W1) plan1.addActivity(H12) P1.addPlan(plan1) - pop.addPerson(P1) + population.addPerson(P1) - val P2: Person = pop.getFactory.createPerson(Id.createPersonId(household.getId + "_P2")) + val P2: Person = population.getFactory.createPerson(Id.createPersonId(household.getId + "_P2")) val H21: Activity = PopulationUtils.createActivityFromCoord("home", homeCoord) H21.setEndTime(9 * 3600 + 5 * 60) val W2: Activity = PopulationUtils.createActivityFromCoord("work2", new Coord(20835, 0)) W2.setEndTime(18 * 3600) val H22: Activity = PopulationUtils.createActivityFromCoord("home", homeCoord) - val plan2: Plan = pop.getFactory.createPlan() + val plan2: Plan = population.getFactory.createPlan() plan2.setPerson(P2) plan2.addActivity(H21) plan2.addActivity(W2) plan2.addActivity(H22) P2.addPlan(plan2) - pop.addPerson(P2) + population.addPerson(P2) household.setMemberIds(JavaConverters.bufferAsJavaList(mutable.Buffer(P1.getId, P2.getId))) household.setVehicleIds(JavaConverters.seqAsJavaList(vehicles.map(veh => veh.toStreetVehicle.id))) - (sc.getPopulation, household) + household } } diff --git a/src/test/scala/beam/agentsim/agents/ridehail/AlonsoMoraPoolingAlgForRideHailSpec.scala b/src/test/scala/beam/agentsim/agents/ridehail/AlonsoMoraPoolingAlgForRideHailSpec.scala index 1de6cbd08ae..963041fadf4 100644 --- a/src/test/scala/beam/agentsim/agents/ridehail/AlonsoMoraPoolingAlgForRideHailSpec.scala +++ b/src/test/scala/beam/agentsim/agents/ridehail/AlonsoMoraPoolingAlgForRideHailSpec.scala @@ -3,7 +3,6 @@ package beam.agentsim.agents.ridehail import akka.actor.ActorRef import beam.agentsim.agents.ridehail.AlonsoMoraPoolingAlgForRideHail.{CustomerRequest, RVGraph, VehicleAndSchedule, _} import beam.agentsim.agents.vehicles.{BeamVehicleType, PersonIdWithActorRef} -import beam.router.BeamSkimmer import beam.sim.config.{BeamConfig, MatSimBeamConfigBuilder} import beam.sim.{BeamHelper, BeamScenario, BeamServices, Geofence} import beam.utils.FileUtils @@ -53,15 +52,13 @@ class AlonsoMoraPoolingAlgForRideHailSpec extends FlatSpec with Matchers with Be } ) implicit val services = injector.getInstance(classOf[BeamServices]) - implicit val skimmer = injector.getInstance(classOf[BeamSkimmer]) implicit val actorRef = ActorRef.noSender val sc = AlonsoMoraPoolingAlgForRideHailSpec.scenario1 val alg: AlonsoMoraPoolingAlgForRideHail = new AlonsoMoraPoolingAlgForRideHail( AlonsoMoraPoolingAlgForRideHailSpec.demandSpatialIndex(sc._2), sc._1, - services, - skimmer + services ) val assignment = alg.matchAndAssign(0) @@ -159,7 +156,6 @@ object AlonsoMoraPoolingAlgForRideHailSpec { def scenario1()( implicit - skimmer: BeamSkimmer, services: BeamServices, beamScenario: BeamScenario, mockActorRef: ActorRef @@ -207,7 +203,6 @@ object AlonsoMoraPoolingAlgForRideHailSpec { def scenarioGeoFence()( implicit - skimmer: BeamSkimmer, services: BeamServices, beamScenario: BeamScenario, mockActorRef: ActorRef diff --git a/src/test/scala/beam/agentsim/agents/ridehail/AsyncAlonsoMoraAlgForRideHailSpec.scala b/src/test/scala/beam/agentsim/agents/ridehail/AsyncAlonsoMoraAlgForRideHailSpec.scala index 886a165b356..785fc904373 100644 --- a/src/test/scala/beam/agentsim/agents/ridehail/AsyncAlonsoMoraAlgForRideHailSpec.scala +++ b/src/test/scala/beam/agentsim/agents/ridehail/AsyncAlonsoMoraAlgForRideHailSpec.scala @@ -1,7 +1,6 @@ package beam.agentsim.agents.ridehail import akka.actor.ActorRef -import beam.router.BeamSkimmer import beam.sim.config.{BeamConfig, MatSimBeamConfigBuilder} import beam.sim.{BeamHelper, BeamServices} import beam.utils.FileUtils @@ -70,7 +69,6 @@ class AsyncAlonsoMoraAlgForRideHailSpec extends FlatSpec with Matchers with Beam } ) implicit val services = injector.getInstance(classOf[BeamServices]) - implicit val skimmer = injector.getInstance(classOf[BeamSkimmer]) implicit val actorRef = ActorRef.noSender val sc = scenarioName match { case "scenarioGeofence" => AlonsoMoraPoolingAlgForRideHailSpec.scenarioGeoFence() @@ -80,8 +78,7 @@ class AsyncAlonsoMoraAlgForRideHailSpec extends FlatSpec with Matchers with Beam new AsyncAlonsoMoraAlgForRideHail( AlonsoMoraPoolingAlgForRideHailSpec.demandSpatialIndex(sc._2), sc._1, - services, - skimmer + services ) import scala.concurrent.duration._ Await.result(alg.matchAndAssign(0), atMost = 10.minutes).toArray @@ -124,7 +121,6 @@ class AsyncAlonsoMoraAlgForRideHailSpec extends FlatSpec with Matchers with Beam } ) implicit val services = injector.getInstance(classOf[BeamServices]) - implicit val skimmer = injector.getInstance(classOf[BeamSkimmer]) implicit val actorRef = ActorRef.noSender // val t0 = System.nanoTime() // services.controler.run() diff --git a/src/test/scala/beam/integration/AgentsimWithMaximallyBadRouterSpec.scala b/src/test/scala/beam/integration/AgentsimWithMaximallyBadRouterSpec.scala index 1b33f907ef9..0033698206a 100755 --- a/src/test/scala/beam/integration/AgentsimWithMaximallyBadRouterSpec.scala +++ b/src/test/scala/beam/integration/AgentsimWithMaximallyBadRouterSpec.scala @@ -7,7 +7,7 @@ import beam.agentsim.agents.PersonTestUtil import beam.agentsim.agents.ridehail.{RideHailIterationHistory, RideHailSurgePricingManager} import beam.integration.AgentsimWithMaximallyBadRouterSpec.BadRouterForTest import beam.router.Modes.BeamMode -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} +import beam.router.RouteHistory import beam.sim.common.GeoUtilsImpl import beam.sim.{BeamHelper, BeamMobsim} import beam.utils.SimRunnerForTest @@ -56,8 +56,6 @@ class AgentsimWithMaximallyBadRouterSpec new RideHailSurgePricingManager(services), new RideHailIterationHistory(), new RouteHistory(services.beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), new GeoUtilsImpl(services.beamConfig), services.networkHelper ) diff --git a/src/test/scala/beam/integration/CarSharingSpec.scala b/src/test/scala/beam/integration/CarSharingSpec.scala index d974f6c8387..55f33f2a48a 100644 --- a/src/test/scala/beam/integration/CarSharingSpec.scala +++ b/src/test/scala/beam/integration/CarSharingSpec.scala @@ -149,6 +149,7 @@ class CarSharingSpec extends FlatSpec with Matchers with BeamHelper { |beam.outputs.events.fileOutputFormats = xml |beam.physsim.skipPhysSim = true |beam.agentsim.lastIteration = 1 + |beam.outputs.writeSkimsInterval = 1 |beam.agentsim.agents.vehicles.sharedFleets = [ | { | name = "fixed-non-reserving-fleet-by-taz" @@ -170,7 +171,7 @@ class CarSharingSpec extends FlatSpec with Matchers with BeamHelper { | } |] |beam.agentsim.agents.modalBehaviors.maximumNumberOfReplanningAttempts = 99999 - """.stripMargin) + """.stripMargin) .withFallback(testConfig("test/input/beamville/beam.conf")) .resolve() runRepositionTest(config) diff --git a/src/test/scala/beam/integration/SingleModeSpec.scala b/src/test/scala/beam/integration/SingleModeSpec.scala index a3ea3763155..799ce86d06e 100755 --- a/src/test/scala/beam/integration/SingleModeSpec.scala +++ b/src/test/scala/beam/integration/SingleModeSpec.scala @@ -6,7 +6,7 @@ import beam.agentsim.agents.PersonTestUtil import beam.agentsim.agents.ridehail.{RideHailIterationHistory, RideHailSurgePricingManager} import beam.agentsim.events.PathTraversalEvent import beam.router.Modes.BeamMode -import beam.router.{BeamSkimmer, RouteHistory, TravelTimeObserved} +import beam.router.RouteHistory import beam.sflight.RouterForTest import beam.sim.common.GeoUtilsImpl import beam.sim.{BeamHelper, BeamMobsim} @@ -76,8 +76,6 @@ class SingleModeSpec new RideHailSurgePricingManager(services), new RideHailIterationHistory(), new RouteHistory(services.beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), new GeoUtilsImpl(services.beamConfig), services.networkHelper ) @@ -129,8 +127,6 @@ class SingleModeSpec new RideHailSurgePricingManager(services), new RideHailIterationHistory(), new RouteHistory(services.beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), new GeoUtilsImpl(services.beamConfig), services.networkHelper ) @@ -201,8 +197,6 @@ class SingleModeSpec new RideHailSurgePricingManager(services), new RideHailIterationHistory(), new RouteHistory(services.beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), new GeoUtilsImpl(services.beamConfig), services.networkHelper ) @@ -279,8 +273,6 @@ class SingleModeSpec new RideHailSurgePricingManager(services), new RideHailIterationHistory(), new RouteHistory(services.beamConfig), - new BeamSkimmer(services, beamScenario, services.geo), - new TravelTimeObserved(services, beamScenario, services.geo), new GeoUtilsImpl(services.beamConfig), services.networkHelper ) diff --git a/src/test/scala/beam/router/BeamSkimmerSpec.scala b/src/test/scala/beam/router/BeamSkimmerSpec.scala deleted file mode 100644 index 5339de84af9..00000000000 --- a/src/test/scala/beam/router/BeamSkimmerSpec.scala +++ /dev/null @@ -1,80 +0,0 @@ -package beam.router - -import java.io.{File, PrintWriter} - -import beam.agentsim.infrastructure.taz.TAZ -import beam.router.BeamSkimmer.{BeamSkimmerADT, BeamSkimmerKey, SkimInternal} -import beam.router.Modes.BeamMode -import org.matsim.api.core.v01.Id -import org.scalatest.{BeforeAndAfter, FlatSpec} - -import scala.collection.concurrent.TrieMap -import scala.util.Random - -class BeamSkimmerSpec extends FlatSpec with BeforeAndAfter { - - val beamSkimmerAsObject: BeamSkimmerADT = TrieMap( - (23, BeamMode.CAR, Id.create(2, classOf[TAZ]), Id.create(1, classOf[TAZ])) -> SkimInternal( - time = 205.0, - generalizedTime = 215.0, - cost = 6.491215096413, - generalizedCost = 6.968992874190778, - distance = 4478.644999999999, - count = 1, - energy = 1.4275908092571782E7 - ), - (7, BeamMode.WALK_TRANSIT, Id.create(1, classOf[TAZ]), Id.create(1, classOf[TAZ])) -> SkimInternal( - time = 90.0, - generalizedTime = 108.99999999999999, - cost = 0.0, - generalizedCost = 1.232222222222222, - distance = 1166.869, - count = 1, - energy = 2908432.6946756938 - ) - ) - - private val beamSkimmerAsCsv = - """hour,mode,origTaz,destTaz,travelTimeInS,generalizedTimeInS,cost,generalizedCost,distanceInM,numObservations,energy - |23,CAR,2,1,205.0,215.0,6.491215096413,6.968992874190778,4478.644999999999,1,1.4275908092571782E7 - |7,WALK_TRANSIT,1,1,90.0,108.99999999999999,0.0,1.232222222222222,1166.869,1,2908432.6946756938 - |""".stripMargin - - it should "serialize not empty map to CSV" in { - val csvContent = BeamSkimmer.toCsv(beamSkimmerAsObject).mkString - - assert(csvContent === beamSkimmerAsCsv) - } - - it should "serialize empty map to CSV" in { - val emptyMap = TrieMap.empty[BeamSkimmerKey, SkimInternal] - - val csvContent = BeamSkimmer.toCsv(emptyMap).mkString - - assert(csvContent === BeamSkimmer.CsvLineHeader) - } - - it should "deserialize from a CSV file" in { - val file = File.createTempFile(Random.alphanumeric.take(10).mkString, ".csv") - try { - writeToFile(file, beamSkimmerAsCsv) - - val history: BeamSkimmerADT = BeamSkimmer.fromCsv(file.getAbsolutePath) - - assert(history === beamSkimmerAsObject) - - } finally { - file.delete() - } - } - - private def writeToFile(file: File, content: String): Unit = { - val writer = new PrintWriter(file) - try { - writer.println(content) - } finally { - writer.close() - } - } - -} diff --git a/src/test/scala/beam/router/skim/SkimmerSpec.scala b/src/test/scala/beam/router/skim/SkimmerSpec.scala new file mode 100644 index 00000000000..f07cb726497 --- /dev/null +++ b/src/test/scala/beam/router/skim/SkimmerSpec.scala @@ -0,0 +1,317 @@ +package beam.router.skim + +import java.io.File + +import beam.agentsim.agents.vehicles.BeamVehicleType +import beam.agentsim.events.{PathTraversalEvent, SpaceTime} +import beam.agentsim.infrastructure.taz.TAZ +import beam.router.Modes.BeamMode +import beam.router.model.{BeamLeg, BeamPath, EmbodiedBeamLeg, EmbodiedBeamTrip} +import beam.router.skim.TAZSkimmer.{TAZSkimmerInternal, TAZSkimmerKey} +import beam.router.skim.ODSkimmer.{ODSkimmerInternal, ODSkimmerKey} +import beam.router.skim.Skims.SkimType +import beam.router.skim.DriveTimeSkimmer.{DriveTimeSkimmerInternal, DriveTimeSkimmerKey} +import beam.sim.config.{BeamConfig, MatSimBeamConfigBuilder} +import beam.sim.population.DefaultPopulationAdjustment +import beam.sim.{BeamHelper, BeamServices} +import beam.utils.FileUtils +import beam.utils.TestConfigUtils.testConfig +import com.google.inject.Inject +import com.typesafe.config.{Config, ConfigFactory} +import com.typesafe.scalalogging.LazyLogging +import org.matsim.api.core.v01.events.Event +import org.matsim.api.core.v01.{Coord, Id} +import org.matsim.core.controler.AbstractModule +import org.matsim.core.controler.events.{IterationStartsEvent, ShutdownEvent} +import org.matsim.core.controler.listener.{IterationStartsListener, ShutdownListener} +import org.matsim.core.events.handler.BasicEventHandler +import org.matsim.core.scenario.{MutableScenario, ScenarioUtils} +import org.scalatest.{FlatSpec, Matchers} +import org.supercsv.io.CsvMapReader +import org.supercsv.prefs.CsvPreference + +import scala.collection.{immutable, mutable} +import scala.util.control.NonFatal + +class SkimmerSpec extends FlatSpec with Matchers with BeamHelper { + import SkimmerSpec._ + + "Skimmers" must "results at skims being collected written on disk" in { + val config = ConfigFactory + .parseString(""" + |beam.outputs.events.fileOutputFormats = xml + |beam.physsim.skipPhysSim = true + |beam.agentsim.lastIteration = 1 + |beam.outputs.writeSkimsInterval = 1 + |beam.h3.resolution = 10 + |beam.h3.lowerBoundResolution = 10 + |beam.router.skim = { + | keepKLatestSkims = 1 + | writeSkimsInterval = 1 + | writeAggregatedSkimsInterval = 1 + | travel-time-skimmer { + | name = "travel-time-skimmer" + | fileBaseName = "skimsTravelTimeObservedVsSimulated" + | } + | origin_destination_skimmer { + | name = "od-skimmer" + | fileBaseName = "skimsOD" + | writeAllModeSkimsForPeakNonPeakPeriodsInterval = 0 + | writeFullSkimsInterval = 0 + | } + | taz-skimmer { + | name = "taz-skimmer" + | fileBaseName = "skimsTAZ" + | } + |} + |beam.agentsim.agents.modalBehaviors.maximumNumberOfReplanningAttempts = 99999 + """.stripMargin) + .withFallback(testConfig("test/input/beamville/beam.conf")) + .resolve() + runScenarioWithSkimmer(config, classOf[CountSkimmerTester], classOf[CountSkimmerTester]) + + // TAZ_SKIMMER + val countSkimsFromDisk = readSkim(SkimType.TAZ_SKIMMER) + val countSkimsFromMem = skimsMap(SkimType.TAZ_SKIMMER) + assume( + countSkimsFromDisk.size == countSkimsFromMem.size, + s"${SkimType.TAZ_SKIMMER}: the written skim has a different size from memory" + ) + countSkimsFromMem.foreach { + case (key, value) => + assume( + value == countSkimsFromDisk(key), + s"${SkimType.TAZ_SKIMMER}: the written skim is different from memory" + ) + } + + // TAZ_SKIMMER + val ttSkimsFromDisk = readSkim(SkimType.DT_SKIMMER) + val ttSkimsFromMem = skimsMap(SkimType.DT_SKIMMER) + assume( + ttSkimsFromDisk.size == ttSkimsFromMem.size, + s"${SkimType.DT_SKIMMER}: the written skim has a different size from memory" + ) + ttSkimsFromMem.foreach { + case (key, value) => + assume(value == ttSkimsFromDisk(key), s"${SkimType.DT_SKIMMER}: the written skim is different from memory") + } + + // TAZ_SKIMMER + val odSkimsFromDisk = readSkim(SkimType.OD_SKIMMER) + val odSkimsFromMem = skimsMap(SkimType.OD_SKIMMER) + assume( + odSkimsFromDisk.size == odSkimsFromMem.size, + s"${SkimType.OD_SKIMMER}: the written skim has a different size from memory" + ) + odSkimsFromMem.foreach { + case (key, value) => + assume(value == odSkimsFromDisk(key), s"${SkimType.OD_SKIMMER}: the written skim is different from memory") + } + } + + private def runScenarioWithSkimmer( + config: Config, + eventHandlerClass: Class[_ <: BasicEventHandler], + listenerClass: Class[_ <: IterationStartsListener] + ): Unit = { + val configBuilder = new MatSimBeamConfigBuilder(config) + val matsimConfig = configBuilder.buildMatSimConf() + val beamConfig = BeamConfig(config) + FileUtils.setConfigOutputFile(beamConfig, matsimConfig) + val beamScenario = loadScenario(beamConfig) + FileUtils.setConfigOutputFile(beamConfig, matsimConfig) + val scenario = ScenarioUtils.loadScenario(matsimConfig).asInstanceOf[MutableScenario] + scenario.setNetwork(beamScenario.network) + val injector = org.matsim.core.controler.Injector.createInjector( + scenario.getConfig, + new AbstractModule() { + override def install(): Unit = { + install(module(config, beamConfig, scenario, beamScenario)) + addEventHandlerBinding().to(eventHandlerClass) + addControlerListenerBinding().to(listenerClass) + } + } + ) + val services = injector.getInstance(classOf[BeamServices]) + DefaultPopulationAdjustment(services).update(scenario) + services.controler.run() + } +} + +object SkimmerSpec extends LazyLogging { + import Skims._ + + val skimsPath = mutable.Map.empty[SkimType.Value, String] + val skimsMap = mutable.Map.empty[SkimType.Value, immutable.Map[AbstractSkimmerKey, AbstractSkimmerInternal]] + + class CountSkimmerTester @Inject()(beamServices: BeamServices) + extends BasicEventHandler + with IterationStartsListener + with ShutdownListener { + + override def handleEvent(event: Event): Unit = { + event match { + case e: PathTraversalEvent if e.mode == BeamMode.CAR => + beamServices.matsimServices.getEvents.processEvent( + TAZSkimmerEvent( + event.getTime, + beamServices, + (event.getTime / 3600).toInt * 3600, + new Coord(e.startX, e.startY), + "default", + e.mode.toString + ) + ) + case _ => + } + } + + override def notifyIterationStarts(event: IterationStartsEvent): Unit = { + if (event.getIteration == 1) { + // taz_skimmer + assume( + Skims.taz_skimmer.pastSkims.size == 1, + s"at the second iteration there should be only one ${SkimType.TAZ_SKIMMER} collected" + ) + Skims.taz_skimmer.aggregatedSkim.foreach { + case (key, value) => + assume( + value == Skims.taz_skimmer.pastSkims.head(key), + s"the aggregated skims should be equal to the first collected ${SkimType.TAZ_SKIMMER}" + ) + } + + // od_skimmer + assume( + Skims.od_skimmer.pastSkims.size == 1, + s"at the second iteration there should be only one ${SkimType.OD_SKIMMER} collected" + ) + Skims.od_skimmer.aggregatedSkim.foreach { + case (key, value) => + assume( + value == Skims.od_skimmer.pastSkims.head(key), + s"the aggregated skims should be equal to the first collected ${SkimType.OD_SKIMMER}" + ) + } + + // dt_skimmer + assume( + Skims.dt_skimmer.pastSkims.size == 1, + s"at the second iteration there should be only one ${SkimType.DT_SKIMMER} collected" + ) + Skims.dt_skimmer.aggregatedSkim.foreach { + case (key, value) => + assume( + value == Skims.dt_skimmer.pastSkims.head(key), + s"the aggregated skims should be equal to the first collected ${SkimType.DT_SKIMMER}" + ) + } + } + } + + override def notifyShutdown(event: ShutdownEvent): Unit = { + skimsPath.put( + SkimType.DT_SKIMMER, + event.getServices.getControlerIO.getIterationFilename(1, "skimsTravelTimeObservedVsSimulated_Aggregated.csv.gz") + ) + skimsPath.put( + SkimType.OD_SKIMMER, + event.getServices.getControlerIO.getIterationFilename(1, "skimsOD_Aggregated.csv.gz") + ) + skimsPath.put( + SkimType.TAZ_SKIMMER, + event.getServices.getControlerIO.getIterationFilename(1, "skimsTAZ_Aggregated.csv.gz") + ) + skimsMap.put(SkimType.DT_SKIMMER, Skims.dt_skimmer.aggregatedSkim) + skimsMap.put(SkimType.OD_SKIMMER, Skims.od_skimmer.aggregatedSkim) + skimsMap.put(SkimType.TAZ_SKIMMER, Skims.taz_skimmer.aggregatedSkim) + } + } + + private def readSkim(skimType: SkimType.Value): immutable.Map[AbstractSkimmerKey, AbstractSkimmerInternal] = { + var mapReader: CsvMapReader = null + val res = mutable.Map.empty[AbstractSkimmerKey, AbstractSkimmerInternal] + try { + if (new File(skimsPath(skimType)).isFile) { + mapReader = new CsvMapReader(FileUtils.readerFromFile(skimsPath(skimType)), CsvPreference.STANDARD_PREFERENCE) + val header = mapReader.getHeader(true) + var line: java.util.Map[String, String] = mapReader.read(header: _*) + while (null != line) { + import scala.collection.JavaConverters._ + val (key, value) = skimType match { + case SkimType.OD_SKIMMER => getODSkimPair(line.asScala.toMap) + case SkimType.TAZ_SKIMMER => getCountSkimPair(line.asScala.toMap) + case _ => getTTSkimPair(line.asScala.toMap) + } + res.put(key, value) + line = mapReader.read(header: _*) + } + } else { + logger.info(s"Could not load skim from '${skimsPath(skimType)}'") + } + } catch { + case NonFatal(ex) => + logger.error(s"Could not load skim from '${skimsPath(skimType)}': ${ex.getMessage}", ex) + } finally { + if (null != mapReader) + mapReader.close() + } + res.toMap + } + + private def getODSkimPair(row: Map[String, String]): (AbstractSkimmerKey, AbstractSkimmerInternal) = { + ( + ODSkimmerKey( + hour = row("hour").toInt, + mode = BeamMode.fromString(row("mode").toLowerCase()).get, + originTaz = Id.create(row("origTaz"), classOf[TAZ]), + destinationTaz = Id.create(row("destTaz"), classOf[TAZ]) + ), + ODSkimmerInternal( + travelTimeInS = row("travelTimeInS").toDouble, + generalizedTimeInS = row("generalizedTimeInS").toDouble, + generalizedCost = row("generalizedCost").toDouble, + distanceInM = row("distanceInM").toDouble, + cost = row("cost").toDouble, + energy = Option(row("energy")).map(_.toDouble).getOrElse(0.0), + numObservations = row("numObservations").toInt, + numIteration = row("numIteration").toInt, + ) + ) + } + + private def getTTSkimPair(row: Map[String, String]): (AbstractSkimmerKey, AbstractSkimmerInternal) = { + ( + DriveTimeSkimmerKey( + fromTAZId = Id.create(row("fromTAZId"), classOf[TAZ]), + toTAZId = Id.create(row("toTAZId"), classOf[TAZ]), + hour = row("hour").toInt + ), + DriveTimeSkimmerInternal( + timeSimulated = row("timeSimulated").toDouble, + timeObserved = row("timeObserved").toDouble, + numObservations = row("counts").toInt, + numIteration = row("numIteration").toInt + ) + ) + } + + private def getCountSkimPair(row: Map[String, String]): (AbstractSkimmerKey, AbstractSkimmerInternal) = { + ( + TAZSkimmerKey( + row("time").toInt, + Id.create(row("taz"), classOf[TAZ]), + row("hex"), + row("groupId"), + row("label") + ), + TAZSkimmerInternal( + row("sumValue").toDouble, + row("meanValue").toDouble, + row("numObservations").toInt, + row("numIteration").toInt + ) + ) + } +} diff --git a/test/input/beamville/beam.conf b/test/input/beamville/beam.conf index 5de9928d7b6..a6dee3ff8f2 100755 --- a/test/input/beamville/beam.conf +++ b/test/input/beamville/beam.conf @@ -179,6 +179,7 @@ beam.outputs.events.fileOutputFormats = "csv,xml" # valid options: xml(.gz) , cs # Events Writing Logging Levels: beam.outputs.events.eventsToWrite = "ActivityEndEvent,ActivityStartEvent,PersonEntersVehicleEvent,PersonLeavesVehicleEvent,ModeChoiceEvent,PathTraversalEvent,ReserveRideHailEvent,ReplanningEvent,RefuelSessionEvent,ChargingPlugInEvent,ChargingPlugOutEvent,ParkEvent,LeavingParkingEvent" beam.outputs.stats.binSize = 3600 + ################################################################## # Debugging ################################################################## diff --git a/test/input/beamville/link-network.csv b/test/input/beamville/link-network.csv index 715cd308e94..51340e3c17a 100644 --- a/test/input/beamville/link-network.csv +++ b/test/input/beamville/link-network.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1494b6d4e51d8a611c7f4a5322bd234cc42f6318e613b00aa0402a012b5ca50b +oid sha256:d43e1824bd06fa8752719c129e8610547a1b5adb83e5203b2923a1b4cc07ed51 size 27097 diff --git a/test/input/beamville/merge-network.csv b/test/input/beamville/merge-network.csv index a9a09e39b8c..adf79f2772e 100644 --- a/test/input/beamville/merge-network.csv +++ b/test/input/beamville/merge-network.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:321178e8e5ba6a5368e8d7d1c080f0749624bc232ec5a214c711c205bf643c44 +oid sha256:505c90fd3b671367bbfe39b157fdbc12d2a203f55fe6bed225653b997faa02f4 size 56735 diff --git a/test/input/beamville/r5/fares.dat b/test/input/beamville/r5/fares.dat deleted file mode 100644 index d6627c674a6..00000000000 --- a/test/input/beamville/r5/fares.dat +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de8c74ca5d6f0f056fb2823599dbd7dbc18211abee477cc60a3ab614651683e5 -size 61 diff --git a/test/input/beamville/r5/tolls.dat b/test/input/beamville/r5/tolls.dat deleted file mode 100644 index fd4716907b2..00000000000 --- a/test/input/beamville/r5/tolls.dat +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15aa58f1ae4f5bd01f3a354ea736db9c979268b65bd74aedca154cd0c1d5516d -size 780 diff --git a/test/input/equil-square/r5/fares.dat b/test/input/equil-square/r5/fares.dat deleted file mode 100755 index d6627c674a6..00000000000 --- a/test/input/equil-square/r5/fares.dat +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de8c74ca5d6f0f056fb2823599dbd7dbc18211abee477cc60a3ab614651683e5 -size 61 diff --git a/test/input/equil-square/r5/tolls.dat b/test/input/equil-square/r5/tolls.dat deleted file mode 100755 index e34e856d285..00000000000 --- a/test/input/equil-square/r5/tolls.dat +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:62a42e6e86f70f0d76a342aa939ca3fd8aeadecd56dc5b92064128451dbba8a4 -size 1886 diff --git a/test/input/fares/fares.dat b/test/input/fares/fares.dat deleted file mode 100755 index b839043bfde..00000000000 --- a/test/input/fares/fares.dat +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ac77a743f964a6befd33c8dd415a9d6d91fe7f20a9f67c24b5d4c505746cf18 -size 1927