From aa38eed9795e40c652f69ff28ac41c8a8c4cd231 Mon Sep 17 00:00:00 2001 From: jlaz Date: Thu, 16 Feb 2023 15:46:15 -0800 Subject: [PATCH 1/5] Enabled min-utility best response from rideHailMaster. --- .../agents/ridehail/RideHailMaster.scala | 49 +++++++++++++++- .../scala/beam/sim/config/BeamConfig.scala | 14 +++-- .../MultipleRideHailManagerSpec.scala | 2 +- test/input/beamville/beam-multiple-rhm.conf | 56 ++++++++++++++++++- test/input/beamville/populationAttributes.xml | 2 +- 5 files changed, 112 insertions(+), 11 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala index 9eaf8f13bc9..111ed41f7b2 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala @@ -3,10 +3,14 @@ package beam.agentsim.agents.ridehail import akka.actor.{ActorRef, Props, Terminated} import beam.agentsim.agents.BeamAgent.Finish import beam.agentsim.agents.InitializeTrigger +import beam.agentsim.agents.choice.logit.{MultinomialLogit, UtilityFunctionOperation} import beam.agentsim.agents.ridehail.RideHailManager.ResponseCache +import beam.agentsim.agents.ridehail.RideHailManager.TravelProposal import beam.agentsim.agents.ridehail.RideHailMaster.RequestWithResponses import beam.agentsim.agents.vehicles.AccessErrorCodes.UnknownInquiryIdError -import beam.agentsim.agents.vehicles.VehicleManager +import beam.agentsim.agents.vehicles.{PersonIdWithActorRef, VehicleManager} +import beam.sim.population.AttributesOfIndividual +import beam.sim.population.PopulationAdjustment._ import beam.agentsim.scheduler.BeamAgentScheduler.{CompletionNotice, ScheduleTrigger} import beam.agentsim.scheduler.Trigger.TriggerWithId import beam.router.RouteHistory @@ -84,6 +88,7 @@ class RideHailMaster( private val inquiriesWithResponses: mutable.Map[Int, RequestWithResponses] = mutable.Map.empty private val rideHailResponseCache = new ResponseCache val rand: Random = new Random(beamScenario.beamConfig.matsim.modules.global.randomSeed) + private val bestResponseType: String = beamServices.beamConfig.beam.agentsim.agents.rideHail.bestResponseType override def loggedReceive: Receive = { case TriggerWithId(trigger: InitializeTrigger, triggerId) => @@ -138,7 +143,47 @@ class RideHailMaster( val responsesInRandomOrder = rand.shuffle(responses) val withProposals = responsesInRandomOrder.filter(_.travelProposal.isDefined) if (withProposals.isEmpty) responsesInRandomOrder.head - else withProposals.minBy(_.travelProposal.get.estimatedPrice(customer)) + else bestResponseType match { + case "MIN_COST" => withProposals.minBy(_.travelProposal.get.estimatedPrice(customer)) + case "MIN_UTILITY" => sampleProposals(customer, withProposals) + } + } + + private def sampleProposals(customer: Id[Person], responses: IndexedSeq[RideHailResponse]): RideHailResponse = { + val proposalsToSample: Map[RideHailResponse, Map[String, Double]] = proposalsToResponseAlternatives(customer, responses) + val mnlParams = Map("cost" -> UtilityFunctionOperation.Multiplier(-1.0), "subscription" -> UtilityFunctionOperation.Multiplier(1.0)) + val mnl: MultinomialLogit[RideHailResponse, String] = MultinomialLogit(Map.empty, mnlParams) + val proposalsWithUtility = mnl.calcAlternativesWithUtility(proposalsToSample) + val chosenProposal = mnl.sampleAlternative(proposalsWithUtility, rand) + chosenProposal.get.alternativeType + } + + private def proposalsToResponseAlternatives(customer: Id[Person], responses: IndexedSeq[RideHailResponse]): Map[RideHailResponse, Map[String,Double]] = { + val person = beamServices.matsimServices.getScenario.getPopulation.getPersons.get(customer) + val customerAttributes = person.getCustomAttributes.get(BEAM_ATTRIBUTES).asInstanceOf[AttributesOfIndividual] + responses.zipWithIndex.map{ alt => + val cost: Double = alt._1.travelProposal.get.estimatedPrice.getOrElse(customer,0.0) + val scaledTime: Double = customerAttributes.getVOT( + getGeneralizedTimeOfProposalInHours(alt._1.request.customer, alt._1.travelProposal) + ) + val hasSubscription = if(alt._1.request.rideHailServiceSubscription.contains(alt._1.rideHailManagerName)) 1.0 else 0.0 + + alt._1-> + Map( "cost" -> (cost + scaledTime), "subscription" -> hasSubscription*beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_subscription + ) + + }.toMap + } + private def getGeneralizedTimeOfProposalInHours(passenger: PersonIdWithActorRef, proposal: Option[TravelProposal]): Double = { + // TODO: add walking time once walk-to-point service is implemented + proposal match { + case Some(proposal) => + val wait = proposal.maxWaitingTimeInSec + val duration = proposal.travelTimeForCustomer(passenger) + (duration + (wait * beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.modeVotMultiplier.waiting) ) / 3600 + case _ => 0.0 + } + } } diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index e9134a7d736..f8136bd7719 100644 --- a/src/main/scala/beam/sim/config/BeamConfig.scala +++ b/src/main/scala/beam/sim/config/BeamConfig.scala @@ -641,7 +641,8 @@ object BeamConfig { transit_crowding_VOT_threshold: scala.Double, transit_crowding_percentile: scala.Double, walk_intercept: scala.Double, - walk_transit_intercept: scala.Double + walk_transit_intercept: scala.Double, + ride_hail_subscription: Double ) object Params { @@ -679,7 +680,8 @@ object BeamConfig { else 90.0, walk_intercept = if (c.hasPathOrNull("walk_intercept")) c.getDouble("walk_intercept") else 0.0, walk_transit_intercept = - if (c.hasPathOrNull("walk_transit_intercept")) c.getDouble("walk_transit_intercept") else 0.0 + if (c.hasPathOrNull("walk_transit_intercept")) c.getDouble("walk_transit_intercept") else 0.0, + ride_hail_subscription = if (c.hasPathOrNull("ride_hail_subscription")) c.getDouble("ride_hail_subscription") else 0.0 ) } } @@ -972,7 +974,8 @@ object BeamConfig { linkFleetStateAcrossIterations: scala.Boolean, managers: scala.List[BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm], rangeBufferForDispatchInMeters: scala.Int, - surgePricing: BeamConfig.Beam.Agentsim.Agents.RideHail.SurgePricing + surgePricing: BeamConfig.Beam.Agentsim.Agents.RideHail.SurgePricing, + bestResponseType: java.lang.String ) object RideHail { @@ -1528,7 +1531,10 @@ object BeamConfig { surgePricing = BeamConfig.Beam.Agentsim.Agents.RideHail.SurgePricing( if (c.hasPathOrNull("surgePricing")) c.getConfig("surgePricing") else com.typesafe.config.ConfigFactory.parseString("surgePricing{}") - ) + ), + bestResponseType = + if (c.hasPathOrNull("bestResponseType")) c.getString("bestResponseType") + else "MIN_COST" ) } diff --git a/src/test/scala/beam/integration/ridehail/MultipleRideHailManagerSpec.scala b/src/test/scala/beam/integration/ridehail/MultipleRideHailManagerSpec.scala index 30123cd97e0..995afc01c95 100755 --- a/src/test/scala/beam/integration/ridehail/MultipleRideHailManagerSpec.scala +++ b/src/test/scala/beam/integration/ridehail/MultipleRideHailManagerSpec.scala @@ -24,7 +24,7 @@ class MultipleRideHailManagerSpec extends AnyWordSpecLike with Matchers with Bea case pte: PathTraversalEvent if pte.vehicleId.toString.startsWith("rideHail") => pte } val groupedByFleetId = rhPTE.groupBy(pte => getFleetId(pte.vehicleId)) - groupedByFleetId.keySet shouldBe Set("Uber", "Lyft") + groupedByFleetId.keySet shouldBe Set("Uber", "Lyft","Cruise") } } diff --git a/test/input/beamville/beam-multiple-rhm.conf b/test/input/beamville/beam-multiple-rhm.conf index 4881cc30389..692dca368d4 100644 --- a/test/input/beamville/beam-multiple-rhm.conf +++ b/test/input/beamville/beam-multiple-rhm.conf @@ -5,6 +5,8 @@ beam.agentsim.simulationName = "multiplerhm" beam.agentsim.firstIteration = 0 beam.agentsim.lastIteration = 0 +beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_subscription = 2 + beam.debug.messageLogging = true beam.debug.maxSimulationStepTimeBeforeConsideredStuckMin = 50 beam.outputs.events.fileOutputFormats = "csv.gz,xml" # valid options: xml(.gz) , csv(.gz), none - DEFAULT: csv.gz @@ -13,6 +15,8 @@ beam.physsim.skipPhysSim = true beam.debug.stuckAgentDetection.enabled = false +beam.agentsim.agents.rideHail.bestResponseType = "MIN_UTILITY" + beam.agentsim.agents.rideHail.managers = [ { name = "Uber" @@ -59,8 +63,12 @@ beam.agentsim.agents.rideHail.managers = [ initialization.initType = "FILE" # If FILE, use this param initialization.filePath = ${beam.inputDirectory}"/rideHailFleet.csv" - defaultCostPerMile = 1.25 - defaultCostPerMinute = 0.75 + defaultBaseCost = 1.0 + defaultCostPerMile = 0.5 + defaultCostPerMinute = 0.14 + pooledBaseCost = 1.1 + pooledCostPerMile = 0.6 + pooledCostPerMinute = 0.01 rideHailManager.radiusInMeters = 5000 # allocationManager(DEFAULT_MANAGER | EV_MANAGER | POOLING_ALONSO_MORA) allocationManager.name = "POOLING_ALONSO_MORA" @@ -91,5 +99,47 @@ beam.agentsim.agents.rideHail.managers = [ allocationManager.repositionLowWaitingTimes.waitingTimeWeight = 4.0 allocationManager.repositionLowWaitingTimes.demandWeight = 4.0 allocationManager.repositionLowWaitingTimes.produceDebugImages = true - } + }, + { + name = "Cruise" + iterationStats.timeBinSizeInSec = 3600 + # Initialization Type(PROCEDURAL | FILE) + initialization.initType = "PROCEDURAL" + # If PROCEDURAL, use these params + # initialization.procedural.initialLocation.name(INITIAL_RIDE_HAIL_LOCATION_HOME | INITIAL_RIDE_HAIL_LOCATION_UNIFORM_RANDOM | INITIAL_RIDE_HAIL_LOCATION_ALL_AT_CENTER | INITIAL_RIDE_HAIL_LOCATION_ALL_IN_CORNER) + initialization.procedural.initialLocation.name = "HOME" + initialization.procedural.initialLocation.home.radiusInMeters = 500 + initialization.procedural.fractionOfInitialVehicleFleet = 0.5 + initialization.procedural.vehicleTypeId = "beamVilleCar" + defaultBaseCost = 10.0 + defaultCostPerMile = 0.5 + defaultCostPerMinute = 0.14 + pooledBaseCost = 1.1 + pooledCostPerMile = 0.6 + pooledCostPerMinute = 0.01 + rideHailManager.radiusInMeters = 5000 + # allocationManager(DEFAULT_MANAGER | EV_MANAGER | POOLING_ALONSO_MORA) + allocationManager.name = "POOLING_ALONSO_MORA" + allocationManager.requestBufferTimeoutInSeconds = 200 + allocationManager.maxWaitingTimeInSec = 900 + allocationManager.maxExcessRideTime = 0.5 # up to +50% + # repositioningManager can be DEFAULT_REPOSITIONING_MANAGER | DEMAND_FOLLOWING_REPOSITIONING_MANAGER | REPOSITIONING_LOW_WAITING_TIMES | INVERSE_SQUARE_DISTANCE_REPOSITIONING_FACTOR + repositioningManager.name = "REPOSITIONING_LOW_WAITING_TIMES" + repositioningManager.timeout = 300 + # REPOSITIONING_LOW_WAITING_TIMES + allocationManager.repositionLowWaitingTimes.percentageOfVehiclesToReposition = 1.0 + allocationManager.repositionLowWaitingTimes.repositionCircleRadiusInMeters = 3000 + allocationManager.repositionLowWaitingTimes.timeWindowSizeInSecForDecidingAboutRepositioning = 1200 + allocationManager.repositionLowWaitingTimes.allowIncreasingRadiusIfDemandInRadiusLow = true + allocationManager.repositionLowWaitingTimes.minDemandPercentageInRadius = 0.1 + allocationManager.repositionLowWaitingTimes.minimumNumberOfIdlingVehiclesThresholdForRepositioning = 1 + # repositioningMethod(TOP_SCORES | KMEANS) + allocationManager.repositionLowWaitingTimes.repositioningMethod = "TOP_SCORES" + allocationManager.repositionLowWaitingTimes.keepMaxTopNScores = 5 + allocationManager.repositionLowWaitingTimes.minScoreThresholdForRepositioning = 0.00001 + allocationManager.repositionLowWaitingTimes.distanceWeight = 0.01 + allocationManager.repositionLowWaitingTimes.waitingTimeWeight = 4.0 + allocationManager.repositionLowWaitingTimes.demandWeight = 4.0 + allocationManager.repositionLowWaitingTimes.produceDebugImages = true + } ] \ No newline at end of file diff --git a/test/input/beamville/populationAttributes.xml b/test/input/beamville/populationAttributes.xml index 138d5f7821d..e714134da2f 100644 --- a/test/input/beamville/populationAttributes.xml +++ b/test/input/beamville/populationAttributes.xml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:227f90cfcc3354e8ee301abe437ba838254f6f94ea3639be7366c5cbfd66649a +oid sha256:92517b8663ae3a7dc40799bdd323de30fee1b5d066e8b10ebdb6869aa13a4be1 size 13436 From 08cf8ee7cfd9109f977a486cbb99b92c393b343e Mon Sep 17 00:00:00 2001 From: jlaz Date: Fri, 17 Feb 2023 14:00:31 -0800 Subject: [PATCH 2/5] Scala fmt --- .../agents/ridehail/RideHailMaster.scala | 45 ++++++++++++------- .../scala/beam/sim/config/BeamConfig.scala | 3 +- .../MultipleRideHailManagerSpec.scala | 2 +- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala index 111ed41f7b2..61e374918e9 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala @@ -143,44 +143,59 @@ class RideHailMaster( val responsesInRandomOrder = rand.shuffle(responses) val withProposals = responsesInRandomOrder.filter(_.travelProposal.isDefined) if (withProposals.isEmpty) responsesInRandomOrder.head - else bestResponseType match { - case "MIN_COST" => withProposals.minBy(_.travelProposal.get.estimatedPrice(customer)) - case "MIN_UTILITY" => sampleProposals(customer, withProposals) - } + else + bestResponseType match { + case "MIN_COST" => withProposals.minBy(_.travelProposal.get.estimatedPrice(customer)) + case "MIN_UTILITY" => sampleProposals(customer, withProposals) + } } private def sampleProposals(customer: Id[Person], responses: IndexedSeq[RideHailResponse]): RideHailResponse = { - val proposalsToSample: Map[RideHailResponse, Map[String, Double]] = proposalsToResponseAlternatives(customer, responses) - val mnlParams = Map("cost" -> UtilityFunctionOperation.Multiplier(-1.0), "subscription" -> UtilityFunctionOperation.Multiplier(1.0)) + val proposalsToSample: Map[RideHailResponse, Map[String, Double]] = + proposalsToResponseAlternatives(customer, responses) + val mnlParams = Map( + "cost" -> UtilityFunctionOperation.Multiplier(-1.0), + "subscription" -> UtilityFunctionOperation.Multiplier(1.0) + ) val mnl: MultinomialLogit[RideHailResponse, String] = MultinomialLogit(Map.empty, mnlParams) val proposalsWithUtility = mnl.calcAlternativesWithUtility(proposalsToSample) val chosenProposal = mnl.sampleAlternative(proposalsWithUtility, rand) chosenProposal.get.alternativeType } - private def proposalsToResponseAlternatives(customer: Id[Person], responses: IndexedSeq[RideHailResponse]): Map[RideHailResponse, Map[String,Double]] = { + private def proposalsToResponseAlternatives( + customer: Id[Person], + responses: IndexedSeq[RideHailResponse] + ): Map[RideHailResponse, Map[String, Double]] = { val person = beamServices.matsimServices.getScenario.getPopulation.getPersons.get(customer) val customerAttributes = person.getCustomAttributes.get(BEAM_ATTRIBUTES).asInstanceOf[AttributesOfIndividual] - responses.zipWithIndex.map{ alt => - val cost: Double = alt._1.travelProposal.get.estimatedPrice.getOrElse(customer,0.0) + responses.zipWithIndex.map { alt => + val cost: Double = alt._1.travelProposal.get.estimatedPrice.getOrElse(customer, 0.0) val scaledTime: Double = customerAttributes.getVOT( getGeneralizedTimeOfProposalInHours(alt._1.request.customer, alt._1.travelProposal) ) - val hasSubscription = if(alt._1.request.rideHailServiceSubscription.contains(alt._1.rideHailManagerName)) 1.0 else 0.0 + val hasSubscription = + if (alt._1.request.rideHailServiceSubscription.contains(alt._1.rideHailManagerName)) 1.0 else 0.0 - alt._1-> - Map( "cost" -> (cost + scaledTime), "subscription" -> hasSubscription*beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_subscription - ) + alt._1 -> + Map( + "cost" -> (cost + scaledTime), + "subscription" -> hasSubscription * beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_subscription + ) }.toMap } - private def getGeneralizedTimeOfProposalInHours(passenger: PersonIdWithActorRef, proposal: Option[TravelProposal]): Double = { + + private def getGeneralizedTimeOfProposalInHours( + passenger: PersonIdWithActorRef, + proposal: Option[TravelProposal] + ): Double = { // TODO: add walking time once walk-to-point service is implemented proposal match { case Some(proposal) => val wait = proposal.maxWaitingTimeInSec val duration = proposal.travelTimeForCustomer(passenger) - (duration + (wait * beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.modeVotMultiplier.waiting) ) / 3600 + (duration + (wait * beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.modeVotMultiplier.waiting)) / 3600 case _ => 0.0 } diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index f8136bd7719..fc9147fdfc6 100644 --- a/src/main/scala/beam/sim/config/BeamConfig.scala +++ b/src/main/scala/beam/sim/config/BeamConfig.scala @@ -681,7 +681,8 @@ object BeamConfig { walk_intercept = if (c.hasPathOrNull("walk_intercept")) c.getDouble("walk_intercept") else 0.0, walk_transit_intercept = if (c.hasPathOrNull("walk_transit_intercept")) c.getDouble("walk_transit_intercept") else 0.0, - ride_hail_subscription = if (c.hasPathOrNull("ride_hail_subscription")) c.getDouble("ride_hail_subscription") else 0.0 + ride_hail_subscription = + if (c.hasPathOrNull("ride_hail_subscription")) c.getDouble("ride_hail_subscription") else 0.0 ) } } diff --git a/src/test/scala/beam/integration/ridehail/MultipleRideHailManagerSpec.scala b/src/test/scala/beam/integration/ridehail/MultipleRideHailManagerSpec.scala index 995afc01c95..2e6f6452272 100755 --- a/src/test/scala/beam/integration/ridehail/MultipleRideHailManagerSpec.scala +++ b/src/test/scala/beam/integration/ridehail/MultipleRideHailManagerSpec.scala @@ -24,7 +24,7 @@ class MultipleRideHailManagerSpec extends AnyWordSpecLike with Matchers with Bea case pte: PathTraversalEvent if pte.vehicleId.toString.startsWith("rideHail") => pte } val groupedByFleetId = rhPTE.groupBy(pte => getFleetId(pte.vehicleId)) - groupedByFleetId.keySet shouldBe Set("Uber", "Lyft","Cruise") + groupedByFleetId.keySet shouldBe Set("Uber", "Lyft", "Cruise") } } From da819da2ed4c2912005b850dc42b1257985d1f13 Mon Sep 17 00:00:00 2001 From: jlaz Date: Thu, 16 Mar 2023 11:03:15 -0700 Subject: [PATCH 3/5] Suggested modifications --- src/main/resources/beam-template.conf | 4 ++++ .../agents/ridehail/RideHailMaster.scala | 10 ++++----- .../scala/beam/sim/config/BeamConfig.scala | 21 +++++++++---------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/resources/beam-template.conf b/src/main/resources/beam-template.conf index adb29453d2e..bc13ca51cec 100755 --- a/src/main/resources/beam-template.conf +++ b/src/main/resources/beam-template.conf @@ -124,6 +124,7 @@ beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_pooled_int beam.agentsim.agents.modalBehaviors.multinomialLogit.params.walk_intercept = "double | 0.0" beam.agentsim.agents.modalBehaviors.multinomialLogit.params.bike_intercept = "double | 0.0" beam.agentsim.agents.modalBehaviors.multinomialLogit.params.bike_transit_intercept = "double | 0.0" +beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_subscription = "double | 0.0" beam.agentsim.agents.modalBehaviors.multinomialLogit.utility_scale_factor = "double | 1.0" beam.agentsim.agents.modalBehaviors.lccm.filePath = ${beam.inputDirectory}"/lccm-long.csv" @@ -193,6 +194,9 @@ beam.agentsim.taz.parkingManager.parallel.numberOfClusters = "int | 8" beam.agentsim.toll.filePath = ${beam.inputDirectory}"/toll-prices.csv" # Ride Hailing Params: Options are ALL, MASS, or the individual modes comma separate, e.g. BUS,TRAM beam.agentsim.agents.rideHailTransit.modesToConsider = "MASS" +# Ride Hailing Params: Options are MIN_COST, MIN_UTILITY +beam.agentsim.agents.rideHail.bestResponseType = "MIN_COST" + # Ride Hailing Params beam.agentsim.agents.rideHail.managers = [{ diff --git a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala index 61e374918e9..04e7b8ace94 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala @@ -169,15 +169,15 @@ class RideHailMaster( ): Map[RideHailResponse, Map[String, Double]] = { val person = beamServices.matsimServices.getScenario.getPopulation.getPersons.get(customer) val customerAttributes = person.getCustomAttributes.get(BEAM_ATTRIBUTES).asInstanceOf[AttributesOfIndividual] - responses.zipWithIndex.map { alt => - val cost: Double = alt._1.travelProposal.get.estimatedPrice.getOrElse(customer, 0.0) + responses.map { alt => + val cost: Double = alt.travelProposal.get.estimatedPrice.getOrElse(customer, 0.0) val scaledTime: Double = customerAttributes.getVOT( - getGeneralizedTimeOfProposalInHours(alt._1.request.customer, alt._1.travelProposal) + getGeneralizedTimeOfProposalInHours(alt.request.customer, alt.travelProposal) ) val hasSubscription = - if (alt._1.request.rideHailServiceSubscription.contains(alt._1.rideHailManagerName)) 1.0 else 0.0 + if (alt.request.rideHailServiceSubscription.contains(alt.rideHailManagerName)) 1.0 else 0.0 - alt._1 -> + alt -> Map( "cost" -> (cost + scaledTime), "subscription" -> hasSubscription * beamServices.beamConfig.beam.agentsim.agents.modalBehaviors.multinomialLogit.params.ride_hail_subscription diff --git a/src/main/scala/beam/sim/config/BeamConfig.scala b/src/main/scala/beam/sim/config/BeamConfig.scala index fc9147fdfc6..10fffc520d3 100644 --- a/src/main/scala/beam/sim/config/BeamConfig.scala +++ b/src/main/scala/beam/sim/config/BeamConfig.scala @@ -634,6 +634,7 @@ object BeamConfig { drive_transit_intercept: scala.Double, ride_hail_intercept: scala.Double, ride_hail_pooled_intercept: scala.Double, + ride_hail_subscription: scala.Double, ride_hail_transit_intercept: scala.Double, transfer: scala.Double, transit_crowding: scala.Double, @@ -641,8 +642,7 @@ object BeamConfig { transit_crowding_VOT_threshold: scala.Double, transit_crowding_percentile: scala.Double, walk_intercept: scala.Double, - walk_transit_intercept: scala.Double, - ride_hail_subscription: Double + walk_transit_intercept: scala.Double ) object Params { @@ -663,6 +663,8 @@ object BeamConfig { ride_hail_pooled_intercept = if (c.hasPathOrNull("ride_hail_pooled_intercept")) c.getDouble("ride_hail_pooled_intercept") else 0.0, + ride_hail_subscription = + if (c.hasPathOrNull("ride_hail_subscription")) c.getDouble("ride_hail_subscription") else 0.0, ride_hail_transit_intercept = if (c.hasPathOrNull("ride_hail_transit_intercept")) c.getDouble("ride_hail_transit_intercept") else 0.0, @@ -680,9 +682,7 @@ object BeamConfig { else 90.0, walk_intercept = if (c.hasPathOrNull("walk_intercept")) c.getDouble("walk_intercept") else 0.0, walk_transit_intercept = - if (c.hasPathOrNull("walk_transit_intercept")) c.getDouble("walk_transit_intercept") else 0.0, - ride_hail_subscription = - if (c.hasPathOrNull("ride_hail_subscription")) c.getDouble("ride_hail_subscription") else 0.0 + if (c.hasPathOrNull("walk_transit_intercept")) c.getDouble("walk_transit_intercept") else 0.0 ) } } @@ -968,6 +968,7 @@ object BeamConfig { } case class RideHail( + bestResponseType: java.lang.String, cav: BeamConfig.Beam.Agentsim.Agents.RideHail.Cav, charging: BeamConfig.Beam.Agentsim.Agents.RideHail.Charging, human: BeamConfig.Beam.Agentsim.Agents.RideHail.Human, @@ -975,8 +976,7 @@ object BeamConfig { linkFleetStateAcrossIterations: scala.Boolean, managers: scala.List[BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm], rangeBufferForDispatchInMeters: scala.Int, - surgePricing: BeamConfig.Beam.Agentsim.Agents.RideHail.SurgePricing, - bestResponseType: java.lang.String + surgePricing: BeamConfig.Beam.Agentsim.Agents.RideHail.SurgePricing ) object RideHail { @@ -1507,6 +1507,8 @@ object BeamConfig { def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Agentsim.Agents.RideHail = { BeamConfig.Beam.Agentsim.Agents.RideHail( + bestResponseType = + if (c.hasPathOrNull("bestResponseType")) c.getString("bestResponseType") else "MIN_COST", cav = BeamConfig.Beam.Agentsim.Agents.RideHail.Cav( if (c.hasPathOrNull("cav")) c.getConfig("cav") else com.typesafe.config.ConfigFactory.parseString("cav{}") @@ -1532,10 +1534,7 @@ object BeamConfig { surgePricing = BeamConfig.Beam.Agentsim.Agents.RideHail.SurgePricing( if (c.hasPathOrNull("surgePricing")) c.getConfig("surgePricing") else com.typesafe.config.ConfigFactory.parseString("surgePricing{}") - ), - bestResponseType = - if (c.hasPathOrNull("bestResponseType")) c.getString("bestResponseType") - else "MIN_COST" + ) ) } From 59ae929834711af8a682d8b448b45325e100c656 Mon Sep 17 00:00:00 2001 From: jlaz Date: Thu, 11 May 2023 13:38:50 -0700 Subject: [PATCH 4/5] Update price estimates in ridehailMaster to include poolingInfo --- .../agents/ridehail/RideHailMaster.scala | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala index 04e7b8ace94..cb2223aa040 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala @@ -145,7 +145,14 @@ class RideHailMaster( if (withProposals.isEmpty) responsesInRandomOrder.head else bestResponseType match { - case "MIN_COST" => withProposals.minBy(_.travelProposal.get.estimatedPrice(customer)) + case "MIN_COST" => withProposals.minBy { response => + val travelProposal = response.travelProposal.get + val price = travelProposal.estimatedPrice(customer) + if (travelProposal.poolingInfo.isDefined && response.request.asPooled) + Math.min(price, price * travelProposal.poolingInfo.get.costFactor) + else + price + } case "MIN_UTILITY" => sampleProposals(customer, withProposals) } } @@ -154,7 +161,7 @@ class RideHailMaster( val proposalsToSample: Map[RideHailResponse, Map[String, Double]] = proposalsToResponseAlternatives(customer, responses) val mnlParams = Map( - "cost" -> UtilityFunctionOperation.Multiplier(-1.0), + "cost" -> UtilityFunctionOperation.Multiplier(-1.0), "subscription" -> UtilityFunctionOperation.Multiplier(1.0) ) val mnl: MultinomialLogit[RideHailResponse, String] = MultinomialLogit(Map.empty, mnlParams) @@ -164,13 +171,20 @@ class RideHailMaster( } private def proposalsToResponseAlternatives( - customer: Id[Person], - responses: IndexedSeq[RideHailResponse] - ): Map[RideHailResponse, Map[String, Double]] = { + customer: Id[Person], + responses: IndexedSeq[RideHailResponse] + ): Map[RideHailResponse, Map[String, Double]] = { val person = beamServices.matsimServices.getScenario.getPopulation.getPersons.get(customer) val customerAttributes = person.getCustomAttributes.get(BEAM_ATTRIBUTES).asInstanceOf[AttributesOfIndividual] responses.map { alt => - val cost: Double = alt.travelProposal.get.estimatedPrice.getOrElse(customer, 0.0) + val cost: Double = { + val travelProposal = alt.travelProposal.get + val price = travelProposal.estimatedPrice(customer) + if (travelProposal.poolingInfo.isDefined && alt.request.asPooled) + Math.min(price, price * travelProposal.poolingInfo.get.costFactor) + else + price + } val scaledTime: Double = customerAttributes.getVOT( getGeneralizedTimeOfProposalInHours(alt.request.customer, alt.travelProposal) ) From a3f887b5626a31cc41c4a48bb6f1c4ba53a62269 Mon Sep 17 00:00:00 2001 From: jlaz Date: Thu, 11 May 2023 13:41:41 -0700 Subject: [PATCH 5/5] scalafmt --- .../agents/ridehail/RideHailMaster.scala | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala index cb2223aa040..4c22e080f4b 100644 --- a/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala +++ b/src/main/scala/beam/agentsim/agents/ridehail/RideHailMaster.scala @@ -145,14 +145,15 @@ class RideHailMaster( if (withProposals.isEmpty) responsesInRandomOrder.head else bestResponseType match { - case "MIN_COST" => withProposals.minBy { response => - val travelProposal = response.travelProposal.get - val price = travelProposal.estimatedPrice(customer) - if (travelProposal.poolingInfo.isDefined && response.request.asPooled) - Math.min(price, price * travelProposal.poolingInfo.get.costFactor) - else - price - } + case "MIN_COST" => + withProposals.minBy { response => + val travelProposal = response.travelProposal.get + val price = travelProposal.estimatedPrice(customer) + if (travelProposal.poolingInfo.isDefined && response.request.asPooled) + Math.min(price, price * travelProposal.poolingInfo.get.costFactor) + else + price + } case "MIN_UTILITY" => sampleProposals(customer, withProposals) } } @@ -161,7 +162,7 @@ class RideHailMaster( val proposalsToSample: Map[RideHailResponse, Map[String, Double]] = proposalsToResponseAlternatives(customer, responses) val mnlParams = Map( - "cost" -> UtilityFunctionOperation.Multiplier(-1.0), + "cost" -> UtilityFunctionOperation.Multiplier(-1.0), "subscription" -> UtilityFunctionOperation.Multiplier(1.0) ) val mnl: MultinomialLogit[RideHailResponse, String] = MultinomialLogit(Map.empty, mnlParams) @@ -171,9 +172,9 @@ class RideHailMaster( } private def proposalsToResponseAlternatives( - customer: Id[Person], - responses: IndexedSeq[RideHailResponse] - ): Map[RideHailResponse, Map[String, Double]] = { + customer: Id[Person], + responses: IndexedSeq[RideHailResponse] + ): Map[RideHailResponse, Map[String, Double]] = { val person = beamServices.matsimServices.getScenario.getPopulation.getPersons.get(customer) val customerAttributes = person.getCustomAttributes.get(BEAM_ATTRIBUTES).asInstanceOf[AttributesOfIndividual] responses.map { alt =>