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

rjf/#2126 more encouragement for PEV home charging #2127

Merged
merged 8 commits into from
Sep 5, 2019
36 changes: 34 additions & 2 deletions src/main/scala/beam/agentsim/agents/PersonAgent.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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._
Expand Down Expand Up @@ -317,12 +319,28 @@ class PersonAgent(

val remainingTourDist: Double = nextActivity(personData) match {
case Some(nextAct) =>
// in the case that we are headed "home", we need to motivate charging.
// in order to induce range anxiety, we need to have agents consider
// their tomorrow activities. the agent's first leg of the day
// is used here to add distance to a "ghost activity" tomorrow morning
// which is used in place of our real remaining tour distance of 0.0
// which should help encourage residential end-of-day charging
val tomorrowFirstLegDistance =
if (nextAct.getType.toLowerCase == "home") {
findFirstCarLegOfTrip(personData) match {
case Some(carLeg) =>
carLeg.beamLeg.travelPath.distanceInM
case None =>
0.0
}
} else 0.0

val nextActIdx = currentTour(personData).tripIndexOfElement(nextAct) - 1
currentTour(personData).trips
.slice(nextActIdx, currentTour(personData).trips.length)
.sliding(2, 1)
.toList
.foldLeft(0d) { (sum, pair) =>
.foldLeft(tomorrowFirstLegDistance) { (sum, pair) =>
sum + Math
.ceil(
beamSkimmer
Expand All @@ -338,7 +356,7 @@ class PersonAgent(
}

case None =>
0 // if we don't have any more trips we don't need a chargingInquiry as we are @home again => assumption: charging @home always takes place
0.0
}

Some(
Expand Down Expand Up @@ -382,6 +400,20 @@ class PersonAgent(
}
}

def findFirstCarLegOfTrip(data: BasePersonData): Option[EmbodiedBeamLeg] = {
@tailrec
def _find(remaining: IndexedSeq[EmbodiedBeamLeg]): Option[EmbodiedBeamLeg] = {
if (remaining.isEmpty) None
else if (remaining.head.beamLeg.mode == CAR) Some { remaining.head } else _find(remaining.tail)
}
for {
trip <- data.currentTrip
leg <- _find(trip.legs)
} yield {
leg
}
}

when(Uninitialized) {
case Event(TriggerWithId(InitializeTrigger(_), triggerId), _) =>
goto(Initialized) replying CompletionNotice(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class RideHailDepotParkingManager(
}

for {
ParkingZoneSearch.ParkingZoneSearchResult(parkingStall, _, parkingZonesSeen, iterations) <- ParkingZoneSearch
ParkingZoneSearch.ParkingZoneSearchResult(parkingStall, _, parkingZonesSeen, parkingZonesSampled, iterations) <- ParkingZoneSearch
.incrementalParkingZoneSearch(
parkingZoneSearchConfiguration,
parkingZoneSearchParams,
Expand Down Expand Up @@ -183,7 +183,9 @@ class RideHailDepotParkingManager(
} else {
totalStallsInUse += 1
totalStallsAvailable -= 1
Some { parkingStall }
Some {
parkingStall
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,34 @@ class ZonalParkingManager(

val rangeAnxietyFactor: Double =
inquiry.remainingTripData
.map { _.rangeAnxiety(withAddedFuelInJoules = addedEnergy) }
.map {
_.rangeAnxiety(withAddedFuelInJoules = addedEnergy)
}
.getOrElse(0.0) // default no anxiety if no remaining trip data provided

val distanceFactor
: Double = (distance / ZonalParkingManager.AveragePersonWalkingSpeed / ZonalParkingManager.HourInSeconds) * inquiry.valueOfTime
val parkingCostsPriceFactor: Double = parkingAlternative.cost / ZonalParkingManager.DollarsInCents

val goingHome
: Boolean = inquiry.activityType.toLowerCase == "home" && parkingAlternative.parkingType == ParkingType.Residential
val chargingVehicle: Boolean = inquiry.beamVehicle match {
case Some(beamVehicle) =>
beamVehicle.beamVehicleType.primaryFuelType match {
case Electricity =>
true
case _ => false
}
case None => false
}
val chargingStall: Boolean = parkingAlternative.parkingZone.chargingPointType.nonEmpty

val homeActivityPrefersResidentialFactor: Double =
if (inquiry.activityType.toLowerCase == "home" && parkingAlternative.parkingType == ParkingType.Residential)
1.0
else 0.0
if (chargingVehicle) {
if (goingHome && chargingStall) 1.0 else 0.0
} else {
if (goingHome) 1.0 else 0.0
}

val params: Map[ParkingMNL.Parameters, Double] = Map(
ParkingMNL.Parameters.RangeAnxietyCost -> rangeAnxietyFactor,
Expand All @@ -202,13 +220,25 @@ class ZonalParkingManager(
ParkingMNL.Parameters.HomeActivityPrefersResidentialParking -> homeActivityPrefersResidentialFactor
)

if (log.isDebugEnabled && inquiry.activityType.toLowerCase == "home") {
log.debug(
f"tour=${inquiry.remainingTripData.map { _.remainingTourDistance }.getOrElse(0.0)}%.2f ${ParkingMNL.prettyPrintAlternatives(params)}"
)
}

params
}

///////////////////////////////////////////
// run ParkingZoneSearch for a ParkingStall
///////////////////////////////////////////
val ParkingZoneSearch.ParkingZoneSearchResult(parkingStall, parkingZone, parkingZonesSeen, iterations) =
val ParkingZoneSearch.ParkingZoneSearchResult(
parkingStall,
parkingZone,
parkingZonesSeen,
parkingZonesSampled,
iterations
) =
ParkingZoneSearch.incrementalParkingZoneSearch(
parkingZoneSearchConfiguration,
parkingZoneSearchParams,
Expand All @@ -230,7 +260,15 @@ class ZonalParkingManager(
ParkingZoneSearch.ParkingZoneSearchResult(newStall, ParkingZone.DefaultParkingZone)
}

log.debug(s"found ${parkingZonesSeen.length} parking zones over $iterations iterations")
log.debug(
s"sampled over ${parkingZonesSampled.length} (found ${parkingZonesSeen.length}) parking zones over $iterations iterations."
)
log.debug(
s"sampled stats:\n ChargerTypes: {};\n Parking Types: {};\n Costs: {};",
chargingTypeToNo(parkingZonesSampled),
parkingTypeToNo(parkingZonesSampled),
listOfCosts(parkingZonesSampled)
)

// reserveStall is false when agent is only seeking pricing information
if (inquiry.reserveStall) {
Expand Down Expand Up @@ -275,6 +313,39 @@ class ZonalParkingManager(
log.debug("ReleaseParkingStall with {} available stalls ", totalStallsAvailable)
}
}

def chargingTypeToNo(parkingZonesSampled: List[(Int, Option[ChargingPointType], ParkingType, Double)]): String = {
parkingZonesSampled
.map(
triple =>
triple._2 match {
case Some(x) => x
case None => "NoCharger"
}
)
.groupBy(identity)
.mapValues(_.size)
.map(x => x._1.toString + ": " + x._2)
.mkString(", ")
}

def parkingTypeToNo(parkingZonesSampled: List[(Int, Option[ChargingPointType], ParkingType, Double)]): String = {
parkingZonesSampled
.map(triple => triple._3)
.groupBy(identity)
.mapValues(_.size)
.map(x => x._1.toString + ": " + x._2)
.mkString(", ")
}

def listOfCosts(parkingZonesSampled: List[(Int, Option[ChargingPointType], ParkingType, Double)]): String = {
parkingZonesSampled
.map(triple => triple._4)
.groupBy(identity)
.mapValues(_.size)
.map(x => x._1.toString + ": " + x._2)
.mkString(", ")
}
}

object ZonalParkingManager extends LazyLogging {
Expand Down Expand Up @@ -357,8 +428,8 @@ object ZonalParkingManager extends LazyLogging {
* constructs a ZonalParkingManager from a string iterator (typically, for testing)
*
* @param parkingDescription line-by-line string representation of parking including header
* @param random random generator used for sampling parking locations
* @param includesHeader true if the parkingDescription includes a csv-style header
* @param random random generator used for sampling parking locations
* @param includesHeader true if the parkingDescription includes a csv-style header
* @return
*/
def apply(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ object ParkingZoneSearch {
parkingStall: ParkingStall,
parkingZone: ParkingZone,
parkingZoneIdsSeen: List[Int] = List.empty,
parkingZonesSampled: List[(Int, Option[ChargingPointType], ParkingType, Double)] = List.empty,
iterations: Int = 1
)

Expand Down Expand Up @@ -141,6 +142,7 @@ object ParkingZoneSearch {
thisInnerRadius: Double,
thisOuterRadius: Double,
parkingZoneIdsSeen: List[Int] = List.empty,
parkingZoneIdsSampled: List[(Int, Option[ChargingPointType], ParkingType, Double)] = List.empty,
iterations: Int = 1
): Option[ParkingZoneSearchResult] = {
if (thisInnerRadius > config.searchMaxRadius) None
Expand Down Expand Up @@ -186,7 +188,13 @@ object ParkingZoneSearch {

val validParkingAlternatives: Int = alternatives.count { _.isValidAlternative }
if (validParkingAlternatives == 0) {
_search(thisOuterRadius, thisOuterRadius * config.searchExpansionFactor, parkingZoneIdsSeen, iterations + 1)
_search(
thisOuterRadius,
thisOuterRadius * config.searchExpansionFactor,
parkingZoneIdsSeen,
parkingZoneIdsSampled,
iterations + 1
)
} else {

// remove any invalid parking alternatives
Expand Down Expand Up @@ -218,10 +226,21 @@ object ParkingZoneSearch {
)

val theseParkingZoneIds: List[Int] = alternatives.map { _.parkingAlternative.parkingZone.parkingZoneId }
val theseSampledParkingZoneIds: List[(Int, Option[ChargingPointType], ParkingType, Double)] =
alternativesToSample.map { altWithParams =>
(
altWithParams._1.parkingZone.parkingZoneId,
altWithParams._1.parkingZone.chargingPointType,
altWithParams._1.parkingType,
altWithParams._1.cost
)

}.toList
ParkingZoneSearchResult(
parkingStall,
parkingZone,
theseParkingZoneIds ++ parkingZoneIdsSeen,
theseSampledParkingZoneIds ++ parkingZoneIdsSampled,
iterations = iterations
)
}
Expand Down
1 change: 0 additions & 1 deletion src/main/scala/beam/sim/config/BeamConfig.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// generated by tscfg 0.9.4 on Wed Sep 04 16:07:43 PDT 2019
// source: src/main/resources/beam-template.conf

package beam.sim.config

Expand Down