Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jl/update ridehail surge #3740

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
12 changes: 6 additions & 6 deletions src/main/java/beam/analysis/plots/GraphSurgePricing.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public GraphSurgePricing(RideHailSurgePricingManager surgePricingManager) {
min = null;

numberOfTimeBins = this.surgePricingManager.numberOfTimeBins();
this.writeGraph = surgePricingManager.beamServices().beamConfig().beam().outputs().writeGraphs();
this.writeGraph = surgePricingManager.beamConfig().beam().outputs().writeGraphs();
}

@Override
Expand All @@ -85,11 +85,11 @@ public void notifyIterationEnds(IterationEndsEvent event) {

OutputDirectoryHierarchy odh = event.getServices().getControlerIO();

graphImageFile = odh.getIterationFilename(iNo, "rideHailSurgePriceLevel.png");
surgePricingCsvFileName = odh.getIterationFilename(iNo, "rideHailSurgePriceLevel.csv");
surgePricingAndRevenueWithTaz = odh.getIterationFilename(iNo, "tazRideHailSurgePriceLevel.csv.gz");
revenueGraphImageFile = odh.getIterationFilename(iNo, "rideHailRevenue.png");
revenueCsvFileName = odh.getIterationFilename(iNo, "rideHailRevenue.csv");
graphImageFile = odh.getIterationFilename(iNo, String.format("rideHailSurgePriceLevel_%s.png", this.surgePricingManager.managerName()));
surgePricingCsvFileName = odh.getIterationFilename(iNo, String.format("rideHailSurgePriceLevel_%s.csv", this.surgePricingManager.managerName()));
surgePricingAndRevenueWithTaz = odh.getIterationFilename(iNo, String.format("tazRideHailSurgePriceLevel_%s.csv.gz", this.surgePricingManager.managerName()));
revenueGraphImageFile = odh.getIterationFilename(iNo, String.format("rideHailRevenue_%s.png", this.surgePricingManager.managerName()));
revenueCsvFileName = odh.getIterationFilename(iNo, String.format("rideHailRevenue_%s.csv", this.surgePricingManager.managerName()));

this.createGraphs();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class RideHailRevenueAnalysis implements ControlerListener, IterationEnds
private final Logger log = LoggerFactory.getLogger(RideHailRevenueAnalysis.class);

private final RideHailSurgePricingManager surgePricingManager;
static final String fileBaseName = "rideHailRevenue";
static final String fileBaseName = "rideHailRevenue_%s";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's avoid putting formatting into the variable, instead let's use it in the string.format method directly, this will improve readability


private OutputDirectoryHierarchy outputDirectoryHiearchy;

Expand All @@ -53,7 +53,7 @@ public void notifyIterationEnds(IterationEndsEvent event) {
model.setSurgePricingLevelCount(surgePricingManager.surgePricingLevelCount());
model.setTotalSurgePricingLevel(surgePricingManager.totalSurgePricingLevel());
GraphUtils.RIDE_HAIL_REVENUE_MAP.put(event.getIteration(), model);
if(surgePricingManager.beamServices().beamConfig().beam().outputs().writeGraphs()){
if(surgePricingManager.beamConfig().beam().outputs().writeGraphs()){
createGraph(data);
}

Expand All @@ -75,7 +75,7 @@ private void drawRideHailRevenueGraph(DefaultCategoryDataset dataSet) {
false, true
);

String graphImageFile = outputDirectoryHiearchy.getOutputFilename(fileBaseName + ".png");
String graphImageFile = outputDirectoryHiearchy.getOutputFilename(String.format(fileBaseName, this.surgePricingManager.managerName()) + ".png");
try {
GraphUtils.saveJFreeChartAsPNG(chart, graphImageFile, GraphsStatsAgentSimEventsListener.GRAPH_WIDTH, GraphsStatsAgentSimEventsListener.GRAPH_HEIGHT);
} catch (IOException e) {
Expand All @@ -97,7 +97,7 @@ private DefaultCategoryDataset createDataset(ArrayBuffer<?> data) {

private void writeRideHailRevenueCsv(ArrayBuffer<?> data) {
try {
String fileName = outputDirectoryHiearchy.getOutputFilename(fileBaseName + ".csv");
String fileName = outputDirectoryHiearchy.getOutputFilename(String.format(fileBaseName, this.surgePricingManager.managerName()) + ".csv");
BufferedWriter outWriter = new BufferedWriter(new FileWriter(new File(fileName)));

outWriter.write("iteration #,revenue");
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/beam-template.conf
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ beam.agentsim.agents.rideHail.managers = [{
allocationManager.alonsoMora.maxRequestsPerVehicle = "int | 5"
# Reposition
allocationManager.pooledRideHailIntervalAsMultipleOfSoloRideHail = "int | 1"
# SurgePricing parameters
surgePricing.surgeLevelAdaptionStep = "double | 0.1"
surgePricing.minimumSurgeLevel = "double | 0.1"
surgePricing.priceAdjustmentStrategy = "KEEP_PRICE_LEVEL_FIXED_AT_ONE"
surgePricing.numberOfCategories = "int | 6"

repositioningManager.name = "DEFAULT_REPOSITIONING_MANAGER"
repositioningManager.timeout = "int | 0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1204,11 +1204,7 @@ class RideHailManager(
costPerMile = defaultCostPerMile
baseCost = defaultBaseCost
}
val timeFare = costPerSecond * surgePricingManager
.getSurgeLevel(
request.pickUpLocationUTM,
request.departAt
) * trip.legsWithPassenger(request.customer).map(_.duration).sum.toDouble
val timeFare = costPerSecond * trip.legsWithPassenger(request.customer).map(_.duration).sum.toDouble
val distanceFare = costPerMile * trip.schedule.keys.map(_.travelPath.distanceInM / 1609).sum

val timeFareAdjusted = beamScenario.vehicleTypes.get(rideHailVehicleTypeId) match {
Expand All @@ -1217,7 +1213,11 @@ class RideHailManager(
case _ =>
timeFare
}
val fare = distanceFare + timeFareAdjusted + additionalCost + baseCost
val fare = (distanceFare + timeFareAdjusted + additionalCost + baseCost) * surgePricingManager
.getSurgeLevel(
request.pickUpLocationUTM,
request.departAt
)
request.customer.personId -> fare
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class RideHailMaster(
val chargingNetworkManager: ActorRef,
val boundingBox: Envelope,
val activityQuadTreeBounds: QuadTreeBounds,
val surgePricingManager: RideHailSurgePricingManager,
// val surgePricingManager: RideHailSurgePricingManager,
val rideHailSurgePricingManagers: Map[String, RideHailSurgePricingManager],
val tncIterationStats: Option[TNCIterationStats],
val routeHistory: RouteHistory,
val rideHailFleetInitializerProvider: RideHailFleetInitializerProvider
Expand All @@ -52,6 +53,7 @@ class RideHailMaster(
val rideHailManagerId =
VehicleManager.createOrGetReservedFor(managerConfig.name, VehicleManager.TypeEnum.RideHail).managerId
val rideHailFleetInitializer = rideHailFleetInitializerProvider.get(managerConfig.name)
val surgePricingManager = rideHailSurgePricingManagers(managerConfig.name)
managerConfig.name -> context.actorOf(
Props(
new RideHailManager(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package beam.agentsim.agents.ridehail

import beam.router.BeamRouter.Location
import beam.sim.BeamServices
import beam.sim.{BeamScenario, BeamServices}
import beam.sim.config.BeamConfig
import beam.sim.config.BeamConfig.Beam.Agentsim.Agents
import com.google.inject.Inject
import org.matsim.core.utils.misc.Time
Expand All @@ -10,10 +11,16 @@ import scala.collection.JavaConverters._
import scala.collection.mutable.ArrayBuffer
import scala.util.Random

class RideHailSurgePricingManager @Inject() (val beamServices: BeamServices) {
class RideHailSurgePricingManager @Inject() (
val beamConfig: BeamConfig,
val beamScenario: BeamScenario,
val managerName: String
) {
Comment on lines +14 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using constant link to beamConfig will prevent automatic updates of config in case it was changed during a simulation, instead it is better to get the latest version of config from the beamServices object each time or manually subscribe to changes of beamConfig and updating it each time.

There is the same issue for the any config values that are stored in variables.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. The issue here is that beamServices is initialized after RideHailSurgePricingManager. And since I am initializing a map of multiple RideHailSurgePricingManager objects, I needed to actually initialize each one, which would error without access to beamServices. @nikolayilyin do you have any suggestions for alternative approaches?

import RideHailSurgePricingManager._

val rideHailConfig: Agents.RideHail = beamServices.beamConfig.beam.agentsim.agents.rideHail
val rideHailConfig: Option[Agents.RideHail.Managers$Elm] =
beamConfig.beam.agentsim.agents.rideHail.managers.find(_.name == managerName)
val surgePricing = rideHailConfig.map(_.surgePricing).getOrElse(throw new Exception("Ride hail config not found"))

// TODO:

Expand All @@ -26,14 +33,13 @@ class RideHailSurgePricingManager @Inject() (val beamServices: BeamServices) {

// TODO: can we allow any other class to inject taz as well, without loading multiple times? (Done)
val timeBinSize: Int =
beamServices.beamConfig.beam.agentsim.timeBinSize // TODO: does throw exception for 60min, if +1 missing below
val numberOfCategories: Int =
rideHailConfig.surgePricing.numberOfCategories // TODO: does throw exception for 0 and negative values
beamConfig.beam.agentsim.timeBinSize // TODO: does throw exception for 60min, if +1 missing below
val numberOfCategories: Int = surgePricing.numberOfCategories // TODO: does throw exception for 0 and negative values
val numberOfTimeBins: Int = Math
.floor(Time.parseTime(beamServices.beamConfig.matsim.modules.qsim.endTime) / timeBinSize)
.floor(Time.parseTime(beamConfig.matsim.modules.qsim.endTime) / timeBinSize)
.toInt + 1
val surgeLevelAdaptionStep: Double = rideHailConfig.surgePricing.surgeLevelAdaptionStep
val minimumSurgeLevel: Double = rideHailConfig.surgePricing.minimumSurgeLevel
val surgeLevelAdaptionStep: Double = surgePricing.surgeLevelAdaptionStep
val minimumSurgeLevel: Double = surgePricing.minimumSurgeLevel
// TODO: implement all cases for these surge prices properly
val CONTINUES_DEMAND_SUPPLY_MATCHING = "CONTINUES_DEMAND_SUPPLY_MATCHING"
val KEEP_PRICE_LEVEL_FIXED_AT_ONE = "KEEP_PRICE_LEVEL_FIXED_AT_ONE"
Expand All @@ -42,14 +48,14 @@ class RideHailSurgePricingManager @Inject() (val beamServices: BeamServices) {

//Scala like code
val surgePriceBins: Map[String, ArrayBuffer[SurgePriceBin]] =
beamServices.beamScenario.tazTreeMap.tazQuadTree.values.asScala.map { v =>
beamScenario.tazTreeMap.tazQuadTree.values.asScala.map { v =>
val array = (0 until numberOfTimeBins).foldLeft(new ArrayBuffer[SurgePriceBin]) { (arrayBuffer, _) =>
arrayBuffer.append(defaultBinContent)
arrayBuffer
}
(v.tazId.toString, array)
}.toMap
val rand = new Random(beamServices.beamConfig.matsim.modules.global.randomSeed)
val rand = new Random(beamConfig.matsim.modules.global.randomSeed)
var iteration = 0
var isFirstIteration = true

Expand All @@ -61,7 +67,7 @@ class RideHailSurgePricingManager @Inject() (val beamServices: BeamServices) {

// TODO: initialize all bins (price levels and iteration revenues)!
var totalSurgePricingLevel: Double = 0
var priceAdjustmentStrategy: String = rideHailConfig.surgePricing.priceAdjustmentStrategy
var priceAdjustmentStrategy: String = surgePricing.priceAdjustmentStrategy

// this should be invoked after each iteration
// TODO: initialize in BEAMSim and also reset there after each iteration?
Expand Down Expand Up @@ -118,7 +124,7 @@ class RideHailSurgePricingManager @Inject() (val beamServices: BeamServices) {
}

def getSurgeLevel(location: Location, time: Double): Double = {
val taz = beamServices.beamScenario.tazTreeMap.getTAZ(location.getX, location.getY)
val taz = beamScenario.tazTreeMap.getTAZ(location.getX, location.getY)
val timeBinIndex = getTimeBinIndex(time)
surgePriceBins
.get(taz.tazId.toString)
Expand All @@ -138,7 +144,7 @@ class RideHailSurgePricingManager @Inject() (val beamServices: BeamServices) {

def addRideCost(time: Double, cost: Double, pickupLocation: Location): Unit = {

val taz = beamServices.beamScenario.tazTreeMap.getTAZ(pickupLocation.getX, pickupLocation.getY)
val taz = beamScenario.tazTreeMap.getTAZ(pickupLocation.getX, pickupLocation.getY)
val timeBinIndex = getTimeBinIndex(time)

surgePriceBins.get(taz.tazId.toString).foreach { i =>
Expand Down
19 changes: 16 additions & 3 deletions src/main/scala/beam/sim/BeamHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import com.conveyal.r5.transit.TransportNetwork
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.google.inject
import com.google.inject.{Binder, TypeLiteral}
import com.google.inject.binder.AnnotatedBindingBuilder
import com.google.inject.Scopes
import com.google.inject.name.Names
import com.typesafe.config.{ConfigFactory, ConfigValueFactory, ConfigValueType, Config => TypesafeConfig}
Expand Down Expand Up @@ -193,16 +195,27 @@ trait BeamHelper extends LazyLogging with BeamValidationHelper {
bind(classOf[TerminationCriterion]).toProvider(classOf[TerminationCriterionProvider])

bind(classOf[PrepareForSim]).to(classOf[BeamPrepareForSim])
bind(classOf[RideHailSurgePricingManager]).asEagerSingleton()
// bind(classOf[RideHailSurgePricingManager]).asEagerSingleton()

val rideHailSurgePricingManagersMap = beamConfig.beam.agentsim.agents.rideHail.managers.map { managerConfig =>
managerConfig.name -> new RideHailSurgePricingManager(beamConfig, beamScenario, managerConfig.name)
}.toMap

bind(new TypeLiteral[Map[String, RideHailSurgePricingManager]] {}).toInstance(rideHailSurgePricingManagersMap)

// val mapBinder = binder().bind(classOf[Map[String, RideHailSurgePricingManager]])
// mapBinder.toInstance(rideHailSurgePricingManagersMap)
// mapBinder.asEagerSingleton()

addControlerListenerBinding().to(classOf[BeamSim])
addControlerListenerBinding().to(classOf[BeamScoringFunctionFactory])
addControlerListenerBinding().to(classOf[RouteHistory])

addControlerListenerBinding().to(classOf[ActivityLocationPlotter])
addControlerListenerBinding().to(classOf[GraphSurgePricing])

// addControlerListenerBinding().to(classOf[GraphSurgePricing])
bind(classOf[BeamOutputDataDescriptionGenerator])
addControlerListenerBinding().to(classOf[RideHailRevenueAnalysis])
// addControlerListenerBinding().to(classOf[RideHailRevenueAnalysis])
bind(classOf[ModeIterationPlanCleaner])

bindMobsim().to(classOf[BeamMobsim])
Expand Down
12 changes: 7 additions & 5 deletions src/main/scala/beam/sim/BeamMobsim.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class BeamMobsim @Inject() (
val scenario: Scenario,
val eventsManager: EventsManager,
val actorSystem: ActorSystem,
val rideHailSurgePricingManager: RideHailSurgePricingManager,
// val rideHailSurgePricingManager: RideHailSurgePricingManager,
val rideHailSurgePricingManagers: Map[String, RideHailSurgePricingManager],
val rideHailIterationHistory: RideHailIterationHistory,
val routeHistory: RouteHistory,
val geo: GeoUtils,
Expand Down Expand Up @@ -181,7 +182,7 @@ class BeamMobsim @Inject() (
new BeamMobsimIteration(
beamServices,
eventsManager,
rideHailSurgePricingManager,
rideHailSurgePricingManagers,
rideHailIterationHistory,
routeHistory,
rideHailFleetInitializerProvider
Expand Down Expand Up @@ -363,7 +364,8 @@ class BeamMobsim @Inject() (
class BeamMobsimIteration(
val beamServices: BeamServices,
val eventsManager: EventsManager,
val rideHailSurgePricingManager: RideHailSurgePricingManager,
// val rideHailSurgePricingManager: RideHailSurgePricingManager,
val rideHailSurgePricingManagers: Map[String, RideHailSurgePricingManager],
val rideHailIterationHistory: RideHailIterationHistory,
val routeHistory: RouteHistory,
val rideHailFleetInitializerProvider: RideHailFleetInitializerProvider
Expand Down Expand Up @@ -466,7 +468,7 @@ class BeamMobsimIteration(
chargingNetworkManager,
envelopeInUTM,
activityQuadTreeBounds,
rideHailSurgePricingManager,
rideHailSurgePricingManagers.values.head,
rideHailIterationHistory.oscillationAdjustedTNCIterationStats,
routeHistory,
rideHailFleetInitializer,
Expand All @@ -488,7 +490,7 @@ class BeamMobsimIteration(
chargingNetworkManager,
envelopeInUTM,
activityQuadTreeBounds,
rideHailSurgePricingManager,
rideHailSurgePricingManagers,
rideHailIterationHistory.oscillationAdjustedTNCIterationStats,
routeHistory,
rideHailFleetInitializerProvider
Expand Down
32 changes: 31 additions & 1 deletion src/main/scala/beam/sim/config/BeamConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,8 @@ object BeamConfig {
pooledCostPerMile: scala.Double,
pooledCostPerMinute: scala.Double,
repositioningManager: BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.RepositioningManager,
rideHailManager: BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.RideHailManager
rideHailManager: BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.RideHailManager,
surgePricing: BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.SurgePricing
)

object Managers$Elm {
Expand Down Expand Up @@ -1450,6 +1451,31 @@ object BeamConfig {
}
}

case class SurgePricing(
minimumSurgeLevel: scala.Double,
numberOfCategories: scala.Int,
priceAdjustmentStrategy: java.lang.String,
surgeLevelAdaptionStep: scala.Double
)

object SurgePricing {

def apply(
c: com.typesafe.config.Config
): BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.SurgePricing = {
BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.SurgePricing(
minimumSurgeLevel =
if (c.hasPathOrNull("minimumSurgeLevel")) c.getDouble("minimumSurgeLevel") else 0.1,
numberOfCategories = if (c.hasPathOrNull("numberOfCategories")) c.getInt("numberOfCategories") else 6,
priceAdjustmentStrategy =
if (c.hasPathOrNull("priceAdjustmentStrategy")) c.getString("priceAdjustmentStrategy")
else "KEEP_PRICE_LEVEL_FIXED_AT_ONE",
surgeLevelAdaptionStep =
if (c.hasPathOrNull("surgeLevelAdaptionStep")) c.getDouble("surgeLevelAdaptionStep") else 0.1
)
}
}

def apply(c: com.typesafe.config.Config): BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm = {
BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm(
allocationManager = BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.AllocationManager(
Expand Down Expand Up @@ -1478,6 +1504,10 @@ object BeamConfig {
rideHailManager = BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.RideHailManager(
if (c.hasPathOrNull("rideHailManager")) c.getConfig("rideHailManager")
else com.typesafe.config.ConfigFactory.parseString("rideHailManager{}")
),
surgePricing = BeamConfig.Beam.Agentsim.Agents.RideHail.Managers$Elm.SurgePricing(
if (c.hasPathOrNull("surgePricing")) c.getConfig("surgePricing")
else com.typesafe.config.ConfigFactory.parseString("surgePricing{}")
)
)
}
Expand Down