Skip to content

Commit

Permalink
Merge branch 'patsonluk:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Bohaska authored Nov 25, 2021
2 parents 74180aa + c135819 commit fbd7deb
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 149 deletions.
82 changes: 56 additions & 26 deletions airline-data/src/main/scala/com/patson/PassengerSimulation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ object PassengerSimulation {

//specializationCostModifiers : Map[(Int, Int), Double] = Map.empty, //(airlineId , airportId) -> modifier

val externalCostModifier = ExternalCostModifier(airlineCostModifiers, specializationCostModifiers)

while (consumptionCycleCount < consumptionCycleMax) {
println("Run " + consumptionCycleCount + " demand chunk count " + demandChunks.size)
Expand All @@ -148,7 +149,7 @@ object PassengerSimulation {
if (consumptionCycleCount < 3) 4
else if (consumptionCycleCount < 6) 5
else 6
val allRoutesMap = findAllRoutes(requiredRoutes.toMap, availableLinks, activeAirportIds, PassengerSimulation.countryOpenness, establishedAllianceIdByAirlineId, airlineCostModifiers, specializationCostModifiers, iterationCount)
val allRoutesMap = findAllRoutes(requiredRoutes.toMap, availableLinks, activeAirportIds, PassengerSimulation.countryOpenness, establishedAllianceIdByAirlineId, Some(externalCostModifier), iterationCount)

//start consuming routes
println()
Expand Down Expand Up @@ -396,6 +397,26 @@ object PassengerSimulation {
}
}

case class ExternalCostModifier(airlineCostModifiers : Map[Int, Double] = Map.empty,
specializationCostModifiers : Map[(Int, Int), SpecializationModifier] = Map.empty) extends CostModifier { //(airlineId , airportId) -> modifier)
override def value(link : Transport, linkClass : LinkClass) : Double = {
var modifier = 1.0
if (airlineCostModifiers.contains(link.airline.id)) {
modifier *= airlineCostModifiers(link.airline.id)
}

val airlineFromAirportTuple = (link.airline.id, link.from.id)
if (specializationCostModifiers.contains(airlineFromAirportTuple)) {
modifier *= specializationCostModifiers(airlineFromAirportTuple).value(linkClass)
}
val airlineToAirportTuple = (link.airline.id, link.to.id)
if (specializationCostModifiers.contains(airlineToAirportTuple)) {
modifier *= specializationCostModifiers(airlineToAirportTuple).value(linkClass)
}
modifier
}
}


/**
* Return all routes if available, with destination defined in the input Map's value, the Input map key indicates various Passenger Group
Expand All @@ -413,8 +434,7 @@ object PassengerSimulation {
activeAirportIds : Set[Int],
countryOpenness : Map[String, Int] = PassengerSimulation.countryOpenness,
establishedAllianceIdByAirlineId : java.util.Map[Int, Int] = Collections.emptyMap[Int, Int](),
airlineCostModifiers : Map[Int, Double] = Map.empty,
specializationCostModifiers : Map[(Int, Int), SpecializationModifier] = Map.empty, //(airlineId , airportId) -> modifier
externalCostModifier : Option[CostModifier] = None,
iterationCount : Int = 4) : Map[PassengerGroup, Map[Airport, Route]] = {
val totalRequiredRoutes = requiredRoutes.foldLeft(0){ case (currentCount, (fromAirport, toAirports)) => currentCount + toAirports.size }

Expand All @@ -428,6 +448,8 @@ object PassengerSimulation {
val progressCount = new AtomicInteger(0)
val progressChunk = requiredRoutes.size / 100



// val traceTimestampMap = new ConcurrentHashMap[Long, Long]()
// val maxTraceDuration = 60 * 1000; //1 min
// println("Agent ready? : " + AgentChecker.waitUntilAgentReady(10, TimeUnit.SECONDS))
Expand Down Expand Up @@ -461,25 +483,26 @@ object PassengerSimulation {
val airlineAwareness = Math.max(airlineAwarenessFromCity, airlineAwarenessFromReputation)

if (airlineAwareness > Random.nextInt(AirlineAppeal.MAX_AWARENESS)) {
var cost = passengerGroup.preference.computeCost(link, matchingLinkClass)

if (airlineCostModifiers.contains(link.airline.id)) {
cost *= airlineCostModifiers(link.airline.id)
}
// var cost = passengerGroup.preference.computeCost(link, matchingLinkClass)
//
// if (airlineCostModifiers.contains(link.airline.id)) {
// cost *= airlineCostModifiers(link.airline.id)
// }
//
// val airlineFromAirportTuple = (link.airline.id, link.from.id)
// if (specializationCostModifiers.contains(airlineFromAirportTuple)) {
// cost *= specializationCostModifiers(airlineFromAirportTuple).value(preferredLinkClass)
// }
// val airlineToAirportTuple = (link.airline.id, link.to.id)
// if (specializationCostModifiers.contains(airlineToAirportTuple)) {
// cost *= specializationCostModifiers(airlineToAirportTuple).value(preferredLinkClass)
// }

val airlineFromAirportTuple = (link.airline.id, link.from.id)
if (specializationCostModifiers.contains(airlineFromAirportTuple)) {
cost *= specializationCostModifiers(airlineFromAirportTuple).value(preferredLinkClass)
}
val airlineToAirportTuple = (link.airline.id, link.to.id)
if (specializationCostModifiers.contains(airlineToAirportTuple)) {
cost *= specializationCostModifiers(airlineToAirportTuple).value(preferredLinkClass)
}


//2 instance of the link, one for each direction. Take note that the underlying link is the same, hence capacity and other params is shared properly!
val linkConsideration1 = LinkConsideration(link, cost, matchingLinkClass, false)
val linkConsideration2 = LinkConsideration(link, cost, matchingLinkClass, true)
val costProvider = CostStoreProvider() //use same instance of costProvider so this is only computed once
val linkConsideration1 = LinkConsideration(link, matchingLinkClass, false, passengerGroup, externalCostModifier, costProvider)
val linkConsideration2 = LinkConsideration(link, matchingLinkClass, true, passengerGroup, externalCostModifier, costProvider)
if (hasFreedom(linkConsideration1, passengerGroup.fromAirport, countryOpenness)) {
linkConsiderations.add(linkConsideration1)
}
Expand Down Expand Up @@ -609,6 +632,8 @@ object PassengerSimulation {

val distanceMap = new java.util.HashMap[Int, Double]()
var predecessorMap = new java.util.HashMap[Int, LinkConsideration]()
var activeVertices = new java.util.HashSet[Int]()
activeVertices.add(from.id)
allVertices.foreach { vertex =>
if (vertex == from.id) {
distanceMap.put(vertex, 0)
Expand All @@ -625,8 +650,9 @@ object PassengerSimulation {
// predecessor[v] := u
for (i <- 0 until maxIteration) {
//val updatingLinks = ArrayBuffer[LinkConsideration]()
val linkConsiderationsIterator = linkConsiderations.iterator()
//val linkConsiderationsIterator = linkConsiderations.iterator()
val newPredecessorMap = new java.util.HashMap[Int, LinkConsideration](predecessorMap)
val newActiveVertices = new java.util.HashSet[Int]()
//create a clone of last run, we update this map, but for lookup we use the previous one
//this is necessary to avoid "previous leg replacement problem"
//for example on first iteration, there is F0, T1 and T2. If there are links:
Expand All @@ -642,13 +668,15 @@ object PassengerSimulation {
// This also create the shuttle from other alliance problem
//The fix for this is never use the current predecessorMap for lookup, instead, use the previous map

val linkConsiderationsIterator = linkConsiderations.iterator()
while (linkConsiderationsIterator.hasNext) {
val linkConsideration = linkConsiderationsIterator.next()
val predecessorLinkConsideration = predecessorMap.get(linkConsideration.from.id)
if (linkConsideration.from.id == from.id || predecessorLinkConsideration != null) {
val linkConsideration = linkConsiderationsIterator.next
if (activeVertices.contains(linkConsideration.from.id)) { //optimization - only need to re-run if the vertex was update in last iteration
val predecessorLinkConsideration = predecessorMap.get(linkConsideration.from.id)

var connectionCost = 0.0
var isValid : Boolean = true
if (linkConsideration.from.id != from.id) { //then it should be a connection flight
if (predecessorLinkConsideration != null) { //then it should be a connection flight
val predecessorLink = predecessorLinkConsideration.link
val previousLinkAirlineId = predecessorLink.airline.id
val currentLinkAirlineId = linkConsideration.link.airline.id
Expand Down Expand Up @@ -682,7 +710,7 @@ object PassengerSimulation {
}
connectionCost *= passengerGroup.preference.connectionCostRatio * passengerGroup.preference.preferredLinkClass.priceMultiplier //connection cost should take into consideration of preferred link class too
}

if (isValid) {
val cost = linkConsideration.cost + connectionCost
val fromCost = distanceMap.get(linkConsideration.from.id)
Expand All @@ -691,12 +719,14 @@ object PassengerSimulation {

if (newCost < distanceMap.get(linkConsideration.to.id)) {
distanceMap.put(linkConsideration.to.id, newCost)
newPredecessorMap.put(linkConsideration.to.id, linkConsideration.copy(cost = cost)) //clone it, do not modify the existing linkWithCost
newPredecessorMap.put(linkConsideration.to.id, linkConsideration.copyWithCost(cost)) //clone it, do not modify the existing linkWithCost
newActiveVertices.add(linkConsideration.to.id)
}
}
}
}
predecessorMap = newPredecessorMap
activeVertices = newActiveVertices
}

val resultMap : scala.collection.mutable.Map[Airport, Route] = scala.collection.mutable.Map[Airport, Route]()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ object ConsumptionHistorySource {
val passengerCount = resultSet.getInt("passenger_count")
val cost = resultSet.getInt("cost")
linkConsumptionById.get(linkId).foreach { linkConsumption =>
val linkConsideration = new LinkConsideration(linkConsumption.link, cost = cost, LinkClass.fromCode(resultSet.getString("link_class")), resultSet.getBoolean("inverted"))
val linkConsideration = LinkConsideration.getExplicit(linkConsumption.link, cost = cost, LinkClass.fromCode(resultSet.getString("link_class")), resultSet.getBoolean("inverted"))
val existingConsiderationsForThisRoute = linkConsiderationsByRouteId.getOrElseUpdate(routeId, ListBuffer[LinkConsideration]())

existingConsiderationsForThisRoute += linkConsideration
Expand Down Expand Up @@ -308,7 +308,7 @@ object ConsumptionHistorySource {
val relatedLinkId = relatedRouteSet.getInt("link")
val relatedLink = linkMap.getOrElse(relatedLinkId, Link.fromId(relatedLinkId))
val cost = relatedRouteSet.getInt("cost")
val linkConsideration = new LinkConsideration(relatedLink, cost = cost, LinkClass.fromCode(relatedRouteSet.getString("link_class")), relatedRouteSet.getBoolean("inverted"))
val linkConsideration = LinkConsideration.getExplicit(relatedLink, cost = cost, LinkClass.fromCode(relatedRouteSet.getString("link_class")), relatedRouteSet.getBoolean("inverted"))

val existingConsiderationsForThisRoute = linkConsiderationsByRouteId.getOrElseUpdate(routeId, ListBuffer[LinkConsideration]())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ abstract class FlightPreference(homeAirport : Airport) {
def isApplicable(fromAirport : Airport, toAirport : Airport) : Boolean //whether this flight preference is applicable to this from/to airport
def getPreferenceType : FlightPreferenceType.Value

def computeCost(link : Transport, linkClass : LinkClass) : Double = {
def computeCost(link : Transport, linkClass : LinkClass, externalCostModifier : Double = 1.0) : Double = {
val standardPrice = link.standardPrice(preferredLinkClass)
var cost = standardPrice * priceAdjustRatio(link, linkClass)

Expand All @@ -29,6 +29,8 @@ abstract class FlightPreference(homeAirport : Airport) {

cost = cost * loungeAdjustRatio(link, loungeLevelRequired, linkClass)

cost *= externalCostModifier

computeCost(cost, link, linkClass)
}

Expand All @@ -38,7 +40,7 @@ abstract class FlightPreference(homeAirport : Airport) {
* @param linkClass
* @return
*/
def computeCostBreakdown(link : Link, linkClass : LinkClass) : CostBreakdown = {
def computeCostBreakdown(link : Transport, linkClass : LinkClass) : CostBreakdown = {
val standardPrice = link.standardPrice(preferredLinkClass)
val priceAdjust = priceAdjustRatio(link, linkClass)
var cost = standardPrice * priceAdjust
Expand Down Expand Up @@ -168,7 +170,7 @@ abstract class FlightPreference(homeAirport : Airport) {
val frequencyRatioDelta = Math.max(-1, (frequencyThreshold - link.frequencyByClass(linkClass)).toDouble / frequencyThreshold) * frequencySensitivity

val flightDurationRatioDelta =
if (flightDurationSensitivity == 0) {
if (flightDurationSensitivity == 0 || link.transportType == TransportType.SHUTTLE) {
0
} else {
val flightDurationThreshold = Computation.computeStandardFlightDuration(link.distance)
Expand Down Expand Up @@ -251,7 +253,7 @@ case class SimplePreference(homeAirport : Airport, priceSensitivity : Double, pr
val noise = 0.8 + getFlatTopBellRandom(0.2, 0.1)

val finalCost = baseCost * noise

if (finalCost >= 0) {
finalCost
} else { //just to play safe - do NOT allow negative cost link
Expand Down Expand Up @@ -286,7 +288,7 @@ case class SpeedPreference(homeAirport : Airport, preferredLinkClass: LinkClass)
override val loyaltySensitivity = 0
override val frequencyThreshold = 14
override val frequencySensitivity = 0.15
override val flightDurationSensitivity = 0.4
override val flightDurationSensitivity = 0.7

def computeCost(baseCost : Double, link : Transport, linkClass : LinkClass) = {
val noise = 0.9 + getFlatTopBellRandom(0.2, 0.1)
Expand All @@ -313,7 +315,7 @@ case class AppealPreference(homeAirport : Airport, preferredLinkClass : LinkClas
override val loyaltySensitivity = loyaltyRatio
override val frequencyThreshold = 14
override val frequencySensitivity = 0.05
override val flightDurationSensitivity = 0.15
override val flightDurationSensitivity = 0.25
override val loungeSensitivity : Double = 1

def computeCost(baseCost: Double, link : Transport, linkClass : LinkClass) : Double = {
Expand Down
58 changes: 56 additions & 2 deletions airline-data/src/main/scala/com/patson/model/Link.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,21 +242,75 @@ case class StaffBreakdown(basicStaff : Int, frequencyStaff : Double, capacitySta
}
case class StaffSchemeBreakdown(basic : Int, perFrequency : Double, per1000Pax : Double)

trait CostModifier {
def value(link : Transport, linkClass : LinkClass) : Double
}

object ExplicitLinkConsideration {

}

object LinkConsideration {
val DUMMY_PASSENGER_GROUP = PassengerGroup(Airport.fromId(0), new SimplePreference(Airport.fromId(0), 1.0, ECONOMY), PassengerType.BUSINESS)
def getExplicit(link : Transport, cost : Double, linkClass : LinkClass, inverted : Boolean, id : Int = 0) : LinkConsideration = {
LinkConsideration(link, linkClass, inverted, DUMMY_PASSENGER_GROUP, None, SimpleCostProvider(cost), id)
}
}


/**
* Cost is the adjusted price
*/
case class LinkConsideration(link : Transport, cost : Double, linkClass : LinkClass, inverted : Boolean, var id : Int = 0) extends IdObject {
case class LinkConsideration(link : Transport,
linkClass : LinkClass,
inverted : Boolean,
passengerGroup : PassengerGroup,
modifier : Option[CostModifier],
costProvider : CostProvider,
var id : Int = 0) extends IdObject {
def from : Airport = if (inverted) link.to else link.from
def to : Airport = if (inverted) link.from else link.to

override def toString() : String = {
s"Consideration (${from.name} => ${to.name} ${linkClass} - $link)"
s"Consideration [${linkClass} - $link cost: $cost]"
}


lazy val cost : Double = costProvider(this)

//costSet.getOrElse()

def copyWithCost(explicitCost : Double) : LinkConsideration = {
this.copy(costProvider = SimpleCostProvider(explicitCost))
}
}

trait CostProvider {
def apply(linkConsideration: LinkConsideration) : Double
}
case class SimpleCostProvider(cost : Double) extends CostProvider{
override def apply(linkConsideration: LinkConsideration) : Double = cost
}
case class CostStoreProvider() extends CostProvider {
var computed = false
var computedValue : Double = 0
override def apply(linkConsideration: LinkConsideration) : Double = {
//this.synchronized { //no sync as it does not have to be threadsafe
if (!computed) {
computedValue = linkConsideration.passengerGroup.preference.computeCost(
linkConsideration.link,
linkConsideration.linkClass,
linkConsideration.modifier.map(_.value(linkConsideration.link, linkConsideration.linkClass)).getOrElse(1.0))
computed = true
}
//}
computedValue
}


}


sealed abstract class LinkClass(val code : String, val spaceMultiplier : Double, val resourceMultiplier : Double, val priceMultiplier : Double, val priceSensitivity : Double, val level : Int) {
def label : String //level for sorting/comparison purpose
}
Expand Down
2 changes: 1 addition & 1 deletion airline-data/src/main/scala/com/patson/model/Shuttle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ case class Shuttle(from : Airport, to : Airport, airline: Airline, distance : In

override val flightType : FlightType.Value = FlightType.SHORT_HAUL_DOMESTIC

override val cost = LinkClassValues.getInstance(economy = Pricing.computeStandardPrice(distance, FlightType.SHORT_HAUL_DOMESTIC, ECONOMY)) //hidden cost of taking shuttle
override val cost = LinkClassValues.getInstance(economy = Pricing.computeStandardPrice(distance, FlightType.SHORT_HAUL_DOMESTIC, ECONOMY)) * 0.8 //hidden cost of taking shuttle

val upkeep = capacity.total * Shuttle.UPKEEP_PER_CAPACITY
override var minorDelayCount : Int = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,11 @@ class AirportSimulationSpec extends WordSpecLike with Matchers {
val airline3 = Airline.fromId(3)
val airline4 = Airline.fromId(4)
val airline1Link1 = Link(airport1, airport2, airline1, LinkClassValues.getInstance(), 1000, LinkClassValues.getInstance(), 0, 0, 0, FlightType.SHORT_HAUL_DOMESTIC, 0, 1)
val badAirline1Link1 = LinkConsideration(airline1Link1, 100000, ECONOMY, false, 0)
val goodAirline1Link1 = LinkConsideration(airline1Link1, 0, ECONOMY, false, 0)
val badAirline1Link1 = LinkConsideration.getExplicit(airline1Link1, 100000, ECONOMY, false, 0)
val goodAirline1Link1 = LinkConsideration.getExplicit(airline1Link1, 0, ECONOMY, false, 0)
val airline2Link2 = Link(airport2, airport3, airline2, LinkClassValues.getInstance(), 1000, LinkClassValues.getInstance(), 0, 0, 0, FlightType.SHORT_HAUL_DOMESTIC, 0, 2)
val badAirline2Link2 = LinkConsideration(airline2Link2, 100000, ECONOMY, false, 0)
val goodAirline2Link2 = LinkConsideration(airline2Link2, 0, ECONOMY, false, 0)
val badAirline2Link2 = LinkConsideration.getExplicit(airline2Link2, 100000, ECONOMY, false, 0)
val goodAirline2Link2 = LinkConsideration.getExplicit(airline2Link2, 0, ECONOMY, false, 0)
val badRoute = Route(List(badAirline1Link1, badAirline2Link2), 0)
val goodRoute = Route(List(goodAirline1Link1, goodAirline2Link2), 0)
val passengerGroup = PassengerGroup(airport1, AppealPreference(airport1, ECONOMY, 0, 1, 1), PassengerType.BUSINESS)
Expand Down
Loading

0 comments on commit fbd7deb

Please sign in to comment.