From 77a7ed83170e08a70438d1f5210122286d1ef0ba Mon Sep 17 00:00:00 2001 From: Albert Cao Date: Mon, 22 Dec 2025 10:47:01 -0800 Subject: [PATCH 1/5] replace vot with betas for multi vot network --- uam_system_model/Pricing.py | 65 ++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/uam_system_model/Pricing.py b/uam_system_model/Pricing.py index 2ea9da3..a8e5070 100644 --- a/uam_system_model/Pricing.py +++ b/uam_system_model/Pricing.py @@ -27,6 +27,8 @@ def optimize( uam_distance_matrix, optimality_gap, value_of_time, + beta_time = -0.0192, + beta_cost = -0.0353, uam_transition_time=10, time_limit=1800, CASM=0.79, @@ -38,6 +40,8 @@ def optimize( pax_arr = self.network.pax_arrival_times.copy() self.time_resolution = time_resolution self.num_vehicles = num_vehicles + self.beta_time = [beta_cost * value_of_time[i] / 60 for i in range(len(self.network.vertiport_dict))] + self.beta_cost = [beta_cost for _ in range(len(self.network.vertiport_dict))] pax_arr["passenger_arrival_time_slot"] = np.ceil( pax_arr["passenger_arrival_time_s"] / self.time_resolution / 60 @@ -93,7 +97,8 @@ def optimize( t_i_uam = [] first_last_mile_cost = [] flight_cost_uam = [] - p_i_bar = [] + beta_time_i = [] + beta_cost_i = [] for idx, row in self.pax_arr_grouped.iterrows(): @@ -126,9 +131,11 @@ def optimize( od_idx = max(origin, destination) uber_fare_i.append(uber_fare[od_idx]) elif len(uber_fare.shape) == 3: + od_idx = max(origin, destination) uber_fare_i.append(uber_fare[origin, destination, time]) - p_i_bar.append(1 / value_of_time[od_idx]) + beta_time_i.append(self.beta_time[od_idx]) + beta_cost_i.append(self.beta_cost[od_idx]) distance = uam_distance_matrix[origin, destination] flight_cost_uam.append(CASM * 4 * distance) @@ -141,21 +148,11 @@ def optimize( non_zero_indices = [i for i, value in enumerate(self.di_bar) if value != 0] di_bar_selected_x = [self.di_bar[i] for i in non_zero_indices] - - if isinstance(value_of_time, float): - p_i_bar = [ - 1 / value_of_time for _ in non_zero_indices - ] # 32.63 is the VOT in dollars per minute - else: - p_i_bar = [] - for idx, row in self.pax_arr_grouped.iterrows(): - origin = row["origin_vertiport_id"] - destination = row["destination_vertiport_id"] - od_idx = max(origin, destination) - p_i_bar.append(1 / (value_of_time[od_idx] / 60)) - - v_i_bar_uber = -uber_travel_time_i - p_i_bar * uber_fare_i + beta_time_i = np.array(beta_time_i) + beta_cost_i = np.array(beta_cost_i) + + v_i_bar_uber = beta_time_i * uber_travel_time_i + beta_cost_i * uber_fare_i bins = 20 max_flights = num_vehicles @@ -245,7 +242,9 @@ def optimize( ) cost_level_of_service = quicksum( di_bar_selected_x[i_non_zero] - / p_i_bar[i_non_zero] + / beta_cost_i[i_non_zero] + * m._theta_uam[i_non_zero] + * beta_time_i[i_non_zero] * m._x_inverse_vars[self.edges[i]] * self.time_resolution / 2 @@ -253,8 +252,8 @@ def optimize( ) theta_terms = quicksum( - di_bar_selected_x[i_non_zero] - / p_i_bar[i_non_zero] + -di_bar_selected_x[i_non_zero] + / beta_cost_i[i_non_zero] * ( m._theta_ln_theta_uam[i_non_zero] - m._theta_ln_1_minus_theta_uam[i_non_zero] @@ -263,14 +262,15 @@ def optimize( ) other_terms = quicksum( - di_bar_selected_x[i_non_zero] - / p_i_bar[i_non_zero] + -di_bar_selected_x[i_non_zero] + / beta_cost_i[i_non_zero] * m._theta_uam[i_non_zero] - * (v_i_bar_uber[i_non_zero] + t_i_uam[i_non_zero]) + * (v_i_bar_uber[i_non_zero] - beta_time_i[i_non_zero] * t_i_uam[i_non_zero]) for i_non_zero in range(len(di_bar_selected_x)) ) objective = theta_terms + other_terms + operating_cost + cost_level_of_service + # objective = theta_terms + other_terms + operating_cost m.setObjective(objective, GRB.MINIMIZE) if not verbose: @@ -311,7 +311,8 @@ def optimize( output_merged["fare"] = output_merged.apply( lambda row: self.calc_fare( row, - 1/p_i_bar[int(row["flight_index"])], + beta_time_i, + beta_cost_i, v_i_bar_uber, t_i_uam, first_last_mile_cost, @@ -354,26 +355,30 @@ def optimize( return df + @staticmethod - def calc_fare( - row, value_of_time, v_i_bar_uber, t_i_uam, first_last_mile_cost, time_resolution - ): + def calc_fare(row, beta_time, beta_cost, v_i_bar_uber, t_i_uam, first_last_mile_cost, time_resolution): num_flights = row["num_flights"] theta = row["percentage_uam"] index = int(row["flight_index"]) first_last_cost = first_last_mile_cost[index] + beta_time = beta_time[index] + beta_cost = beta_cost[index] + + if theta >= 1: + theta = 0.9999 + if theta <= 0: + theta = 0.0001 - fare = -value_of_time * ( + fare = 1/beta_cost * ( math.log(theta) - math.log(1 - theta) + v_i_bar_uber[index] - + t_i_uam[index] - + time_resolution / 2 / num_flights + - beta_time * (t_i_uam[index] + time_resolution / 2 / num_flights) ) return fare - first_last_cost - class FlightTask: def __init__( self, From 571415a63bd7108206258847ea22f5a82ce6ce75 Mon Sep 17 00:00:00 2001 From: Albert Cao Date: Mon, 22 Dec 2025 14:40:54 -0800 Subject: [PATCH 2/5] modeling utility with betas --- uam_system_model/Pricing.py | 44 +++++++++++++++------- uam_system_model/utils/PricingOpSummary.py | 31 +++++++++++++++ 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/uam_system_model/Pricing.py b/uam_system_model/Pricing.py index a8e5070..cfd15ff 100644 --- a/uam_system_model/Pricing.py +++ b/uam_system_model/Pricing.py @@ -27,6 +27,7 @@ def optimize( uam_distance_matrix, optimality_gap, value_of_time, + utility_type, beta_time = -0.0192, beta_cost = -0.0353, uam_transition_time=10, @@ -156,7 +157,7 @@ def optimize( bins = 20 max_flights = num_vehicles - eps = 0.01 + eps = 0.05 m = Model("Pricing Problem") m.Params.NonConvex = 2 @@ -240,16 +241,17 @@ def optimize( m._x_vars[self.edges[i]] * flight_cost_uam[i_non_zero] for i_non_zero, i in zip(range(len(di_bar_selected_x)), non_zero_indices) ) - cost_level_of_service = quicksum( - di_bar_selected_x[i_non_zero] - / beta_cost_i[i_non_zero] - * m._theta_uam[i_non_zero] - * beta_time_i[i_non_zero] - * m._x_inverse_vars[self.edges[i]] - * self.time_resolution - / 2 - for i_non_zero, i in zip(range(len(di_bar_selected_x)), non_zero_indices) - ) + # cost_level_of_service = quicksum( + # di_bar_selected_x[i_non_zero] + # / beta_cost_i[i_non_zero] + # * m._theta_uam[i_non_zero] + # * beta_time_i[i_non_zero] + # * m._x_inverse_vars[self.edges[i]] + # * self.time_resolution + # / 2 + # for i_non_zero, i in zip(range(len(di_bar_selected_x)), non_zero_indices) + # ) + theta_terms = quicksum( -di_bar_selected_x[i_non_zero] @@ -269,10 +271,24 @@ def optimize( for i_non_zero in range(len(di_bar_selected_x)) ) - objective = theta_terms + other_terms + operating_cost + cost_level_of_service - # objective = theta_terms + other_terms + operating_cost + # objective = theta_terms + other_terms + operating_cost + cost_level_of_service + objective = theta_terms + other_terms + operating_cost m.setObjective(objective, GRB.MINIMIZE) + + max_v = max_flights + for i, idx in enumerate(non_zero_indices): + current_los_costs = [] + factor = (di_bar_selected_x[i] * beta_time_i[i] * self.time_resolution) / (2 * beta_cost_i[i]) + for k in range(max_v + 1): + if k == 0: + current_los_costs.append(1e6) # Penalty for no service + else: + current_los_costs.append(factor * (1.0 / k)) + + m.setPWLObj(m._x_vars[self.edges[idx]], range(max_v + 1), current_los_costs) + + if not verbose: m.setParam("OutputFlag", 0) m.update() @@ -410,7 +426,7 @@ def next_task(self, next_task): + reposition_time ) - if ready_time <= next_task_start_time: + if next_task_start_time - ready_time >= 0 and next_task_start_time - ready_time <= 12: return True else: return False diff --git a/uam_system_model/utils/PricingOpSummary.py b/uam_system_model/utils/PricingOpSummary.py index de0ec87..0836669 100644 --- a/uam_system_model/utils/PricingOpSummary.py +++ b/uam_system_model/utils/PricingOpSummary.py @@ -87,6 +87,7 @@ def plot_rasm_by_od(self, dpi=300): data=self.policy, x="passenger_arrival_time_slot", hue="markets", + hue_order=self.network.vertiports[1:], y="rev_per_mile", marker="o", err_style=None, @@ -107,6 +108,34 @@ def plot_rasm_by_od(self, dpi=300): return fig, ax + def plot_fare_by_od(self, dpi=300): + fig, ax = plt.subplots(figsize=(6, 4), dpi=dpi) + sns.lineplot( + data=self.policy, + x="passenger_arrival_time_slot", + y="fare", + hue="markets", + hue_order=self.network.vertiports[1:], + err_style=None, + legend=True, + marker="o", + palette=custom_colors, + ax=ax, + ) + ax.set( + xticks=np.arange(0, 49, 12), + xticklabels=[str(i) + ":00" for i in range(0, 26, 6)], + xlabel="", + ylabel="Fare ($)", + xlim=(0, 48), + title="Average Fare by ODs", + ) + ax.xaxis.set_major_locator(MultipleLocator(2)) + ax.grid(True, linestyle="--", alpha=0.5) + ax.legend(bbox_to_anchor=(1.02, 0.85), loc="upper left", borderaxespad=0.0) + + return fig, ax + def plot_revenue_by_od(self, dpi=300): fig, ax = plt.subplots(figsize=(6, 4), dpi=dpi) sns.lineplot( @@ -114,6 +143,7 @@ def plot_revenue_by_od(self, dpi=300): x="passenger_arrival_time_slot", y="total_revenue", hue="markets", + hue_order=self.network.vertiports[1:], err_style=None, legend=True, marker="o", @@ -141,6 +171,7 @@ def plot_uam_share_by_od(self, dpi=300): x="passenger_arrival_time_slot", y="percentage_uam", hue="markets", + hue_order=self.network.vertiports[1:], err_style=None, legend=True, marker="o", From 5093eb6ba815a1dce9af72b70f25072a5b22a7ec Mon Sep 17 00:00:00 2001 From: Albert Cao Date: Mon, 22 Dec 2025 14:57:58 -0800 Subject: [PATCH 3/5] integrate vot with betas --- uam_system_model/Pricing.py | 236 ++++++++++++++++------- uam_system_model/ScheduleGeneratorJFK.py | 86 ++++++--- uam_system_model/StarNetworkBJ.py | 11 +- uam_system_model/StarNetworkJFK.py | 16 +- 4 files changed, 234 insertions(+), 115 deletions(-) diff --git a/uam_system_model/Pricing.py b/uam_system_model/Pricing.py index cfd15ff..9d3c31b 100644 --- a/uam_system_model/Pricing.py +++ b/uam_system_model/Pricing.py @@ -28,20 +28,25 @@ def optimize( optimality_gap, value_of_time, utility_type, - beta_time = -0.0192, - beta_cost = -0.0353, + beta_time=-0.0192, + beta_cost=-0.0353, uam_transition_time=10, time_limit=1800, CASM=0.79, verbose=True, ): if isinstance(value_of_time, int) or isinstance(value_of_time, float): - value_of_time = [value_of_time for _ in range(len(self.network.vertiport_dict))] - + value_of_time = [ + value_of_time for _ in range(len(self.network.vertiport_dict)) + ] + pax_arr = self.network.pax_arrival_times.copy() self.time_resolution = time_resolution self.num_vehicles = num_vehicles - self.beta_time = [beta_cost * value_of_time[i] / 60 for i in range(len(self.network.vertiport_dict))] + self.beta_time = [ + beta_cost * value_of_time[i] / 60 + for i in range(len(self.network.vertiport_dict)) + ] self.beta_cost = [beta_cost for _ in range(len(self.network.vertiport_dict))] pax_arr["passenger_arrival_time_slot"] = np.ceil( @@ -100,7 +105,7 @@ def optimize( flight_cost_uam = [] beta_time_i = [] beta_cost_i = [] - + p_i_bar = [] for idx, row in self.pax_arr_grouped.iterrows(): origin = row["origin_vertiport_id"] @@ -135,6 +140,7 @@ def optimize( od_idx = max(origin, destination) uber_fare_i.append(uber_fare[origin, destination, time]) + p_i_bar.append(1 / value_of_time[od_idx]) beta_time_i.append(self.beta_time[od_idx]) beta_cost_i.append(self.beta_cost[od_idx]) @@ -150,10 +156,25 @@ def optimize( di_bar_selected_x = [self.di_bar[i] for i in non_zero_indices] - beta_time_i = np.array(beta_time_i) - beta_cost_i = np.array(beta_cost_i) - - v_i_bar_uber = beta_time_i * uber_travel_time_i + beta_cost_i * uber_fare_i + if utility_type == "vot": + if isinstance(value_of_time, float): + p_i_bar = [ + 1 / value_of_time for _ in non_zero_indices + ] # 32.63 is the VOT in dollars per minute + + else: + p_i_bar = [] + for idx, row in self.pax_arr_grouped.iterrows(): + origin = row["origin_vertiport_id"] + destination = row["destination_vertiport_id"] + od_idx = max(origin, destination) + p_i_bar.append(1 / (value_of_time[od_idx] / 60)) + + v_i_bar_uber = -uber_travel_time_i - p_i_bar * uber_fare_i + elif utility_type == "betas": + beta_time_i = np.array(beta_time_i) + beta_cost_i = np.array(beta_cost_i) + v_i_bar_uber = beta_time_i * uber_travel_time_i + beta_cost_i * uber_fare_i bins = 20 max_flights = num_vehicles @@ -241,54 +262,82 @@ def optimize( m._x_vars[self.edges[i]] * flight_cost_uam[i_non_zero] for i_non_zero, i in zip(range(len(di_bar_selected_x)), non_zero_indices) ) - # cost_level_of_service = quicksum( - # di_bar_selected_x[i_non_zero] - # / beta_cost_i[i_non_zero] - # * m._theta_uam[i_non_zero] - # * beta_time_i[i_non_zero] - # * m._x_inverse_vars[self.edges[i]] - # * self.time_resolution - # / 2 - # for i_non_zero, i in zip(range(len(di_bar_selected_x)), non_zero_indices) - # ) - - - theta_terms = quicksum( - -di_bar_selected_x[i_non_zero] - / beta_cost_i[i_non_zero] - * ( - m._theta_ln_theta_uam[i_non_zero] - - m._theta_ln_1_minus_theta_uam[i_non_zero] + if utility_type == "vot": + cost_level_of_service = quicksum( + di_bar_selected_x[i_non_zero] + / p_i_bar[i_non_zero] + * m._x_inverse_vars[self.edges[i]] + * self.time_resolution + / 2 + for i_non_zero, i in zip( + range(len(di_bar_selected_x)), non_zero_indices + ) + ) + theta_terms = quicksum( + di_bar_selected_x[i_non_zero] + / p_i_bar[i_non_zero] + * ( + m._theta_ln_theta_uam[i_non_zero] + - m._theta_ln_1_minus_theta_uam[i_non_zero] + ) + for i_non_zero in range(len(di_bar_selected_x)) ) - for i_non_zero in range(len(di_bar_selected_x)) - ) - other_terms = quicksum( - -di_bar_selected_x[i_non_zero] - / beta_cost_i[i_non_zero] - * m._theta_uam[i_non_zero] - * (v_i_bar_uber[i_non_zero] - beta_time_i[i_non_zero] * t_i_uam[i_non_zero]) - for i_non_zero in range(len(di_bar_selected_x)) - ) + other_terms = quicksum( + di_bar_selected_x[i_non_zero] + / p_i_bar[i_non_zero] + * m._theta_uam[i_non_zero] + * (v_i_bar_uber[i_non_zero] + t_i_uam[i_non_zero]) + for i_non_zero in range(len(di_bar_selected_x)) + ) - # objective = theta_terms + other_terms + operating_cost + cost_level_of_service - objective = theta_terms + other_terms + operating_cost + objective = ( + theta_terms + other_terms + operating_cost + cost_level_of_service + ) - m.setObjective(objective, GRB.MINIMIZE) + elif utility_type == "betas": + theta_terms = quicksum( + -di_bar_selected_x[i_non_zero] + / beta_cost_i[i_non_zero] + * ( + m._theta_ln_theta_uam[i_non_zero] + - m._theta_ln_1_minus_theta_uam[i_non_zero] + ) + for i_non_zero in range(len(di_bar_selected_x)) + ) + + other_terms = quicksum( + -di_bar_selected_x[i_non_zero] + / beta_cost_i[i_non_zero] + * m._theta_uam[i_non_zero] + * ( + v_i_bar_uber[i_non_zero] + - beta_time_i[i_non_zero] * t_i_uam[i_non_zero] + ) + for i_non_zero in range(len(di_bar_selected_x)) + ) - max_v = max_flights - for i, idx in enumerate(non_zero_indices): - current_los_costs = [] - factor = (di_bar_selected_x[i] * beta_time_i[i] * self.time_resolution) / (2 * beta_cost_i[i]) - for k in range(max_v + 1): - if k == 0: - current_los_costs.append(1e6) # Penalty for no service - else: - current_los_costs.append(factor * (1.0 / k)) - - m.setPWLObj(m._x_vars[self.edges[idx]], range(max_v + 1), current_los_costs) + # objective = theta_terms + other_terms + operating_cost + cost_level_of_service + objective = theta_terms + other_terms + operating_cost + m.setObjective(objective, GRB.MINIMIZE) + if utility_type == "betas": + max_v = max_flights + for i, idx in enumerate(non_zero_indices): + current_los_costs = [] + factor = ( + di_bar_selected_x[i] * beta_time_i[i] * self.time_resolution + ) / (2 * beta_cost_i[i]) + for k in range(max_v + 1): + if k == 0: + current_los_costs.append(1e6) # Penalty for no service + else: + current_los_costs.append(factor * (1.0 / k)) + + m.setPWLObj( + m._x_vars[self.edges[idx]], range(max_v + 1), current_los_costs + ) if not verbose: m.setParam("OutputFlag", 0) m.update() @@ -324,18 +373,31 @@ def optimize( self.t_i_uam = t_i_uam self.value_of_time = value_of_time - output_merged["fare"] = output_merged.apply( - lambda row: self.calc_fare( - row, - beta_time_i, - beta_cost_i, - v_i_bar_uber, - t_i_uam, - first_last_mile_cost, - self.time_resolution, - ), - axis=1, - ) + if utility_type == "vot": + output_merged["fare"] = output_merged.apply( + lambda row: self.calc_fare_vot( + row, + 1 / p_i_bar[int(row["flight_index"])], + v_i_bar_uber, + t_i_uam, + first_last_mile_cost, + self.time_resolution, + ), + axis=1, + ) + elif utility_type == "betas": + output_merged["fare"] = output_merged.apply( + lambda row: self.calc_fare_betas( + row, + beta_time_i, + beta_cost_i, + v_i_bar_uber, + t_i_uam, + first_last_mile_cost, + self.time_resolution, + ), + axis=1, + ) pax_arr_grouped_to_join = self.pax_arr_grouped.copy() pax_arr_grouped_to_join["flight_index"] = pax_arr_grouped_to_join.index @@ -371,9 +433,35 @@ def optimize( return df + @staticmethod + def calc_fare_vot( + row, value_of_time, v_i_bar_uber, t_i_uam, first_last_mile_cost, time_resolution + ): + num_flights = row["num_flights"] + theta = row["percentage_uam"] + index = int(row["flight_index"]) + first_last_cost = first_last_mile_cost[index] + + fare = -value_of_time * ( + math.log(theta) + - math.log(1 - theta) + + v_i_bar_uber[index] + + t_i_uam[index] + + time_resolution / 2 / num_flights + ) + + return fare - first_last_cost @staticmethod - def calc_fare(row, beta_time, beta_cost, v_i_bar_uber, t_i_uam, first_last_mile_cost, time_resolution): + def calc_fare_betas( + row, + beta_time, + beta_cost, + v_i_bar_uber, + t_i_uam, + first_last_mile_cost, + time_resolution, + ): num_flights = row["num_flights"] theta = row["percentage_uam"] index = int(row["flight_index"]) @@ -386,15 +474,20 @@ def calc_fare(row, beta_time, beta_cost, v_i_bar_uber, t_i_uam, first_last_mile_ if theta <= 0: theta = 0.0001 - fare = 1/beta_cost * ( - math.log(theta) - - math.log(1 - theta) - + v_i_bar_uber[index] - - beta_time * (t_i_uam[index] + time_resolution / 2 / num_flights) + fare = ( + 1 + / beta_cost + * ( + math.log(theta) + - math.log(1 - theta) + + v_i_bar_uber[index] + - beta_time * (t_i_uam[index] + time_resolution / 2 / num_flights) + ) ) return fare - first_last_cost + class FlightTask: def __init__( self, @@ -426,7 +519,10 @@ def next_task(self, next_task): + reposition_time ) - if next_task_start_time - ready_time >= 0 and next_task_start_time - ready_time <= 12: + if ( + next_task_start_time - ready_time >= 0 + and next_task_start_time - ready_time <= 12 + ): return True else: return False diff --git a/uam_system_model/ScheduleGeneratorJFK.py b/uam_system_model/ScheduleGeneratorJFK.py index 37aed74..3cf2846 100644 --- a/uam_system_model/ScheduleGeneratorJFK.py +++ b/uam_system_model/ScheduleGeneratorJFK.py @@ -1,9 +1,7 @@ -import pandas as pd - import numpy as np +import pandas as pd -from .utils.schedule_utils import (build_schedules, - auto_regressive_poisson) +from .utils.schedule_utils import auto_regressive_poisson, build_schedules class ScheduleGenerator: @@ -20,46 +18,72 @@ def get_one_day( flight_distance_matrix, fare, ): - list_of_ods = arrival_rate_by_vertiport['od'].unique() + list_of_ods = arrival_rate_by_vertiport["od"].unique() - schedule = pd.DataFrame(columns=['schedule', 'od', 'num_pax', 'revenue']) - paxArrivalDf = pd.DataFrame(columns=['passenger_arrival_time_s', 'origin_vertiport_id', 'destination_vertiport_id']) + schedule = pd.DataFrame(columns=["schedule", "od", "num_pax", "revenue"]) + paxArrivalDf = pd.DataFrame( + columns=[ + "passenger_arrival_time_s", + "origin_vertiport_id", + "destination_vertiport_id", + ] + ) for od in list_of_ods: - od_data = arrival_rate_by_vertiport[arrival_rate_by_vertiport['od']==od] + od_data = arrival_rate_by_vertiport[arrival_rate_by_vertiport["od"] == od] realized_demand = auto_regressive_poisson( - rate=od_data['total_trips'].values / 365, - alpha=auto_regressive_alpha,) + rate=od_data["total_trips"].values / 365, + alpha=auto_regressive_alpha, + ) arrival_time = [] for hour in range(24): for _ in range(realized_demand[hour]): arrival_time.append(hour * 60 + np.random.randint(0, 60)) - departure_time, num_pax = build_schedules(arrival_time, max_waiting_time=max_waiting_time, occupancy=occupancy) + departure_time, num_pax = build_schedules( + arrival_time, max_waiting_time=max_waiting_time, occupancy=occupancy + ) - schedule = pd.concat([ - schedule, - pd.DataFrame({ - 'schedule': departure_time, - 'od': od, - 'num_pax': num_pax, - 'revenue': num_pax * flight_distance_matrix[vertiport_dict[od.split('_')[0]], vertiport_dict[od.split('_')[1]]] * fare # assume flat fare of $50 - }) - ], ignore_index=True) + schedule = pd.concat( + [ + schedule, + pd.DataFrame( + { + "schedule": departure_time, + "od": od, + "num_pax": num_pax, + "revenue": num_pax + * flight_distance_matrix[ + vertiport_dict[od.split("_")[0]], + vertiport_dict[od.split("_")[1]], + ] + * fare, # assume flat fare of $50 + } + ), + ], + ignore_index=True, + ) arrival_time = np.array(arrival_time) - paxArrivalDf = pd.concat([ - paxArrivalDf, - pd.DataFrame({ - 'passenger_arrival_time_s': arrival_time * 60, - 'origin_vertiport_id': vertiport_dict[od.split('_')[0]], - 'destination_vertiport_id': vertiport_dict[od.split('_')[1]], - }) - ], ignore_index=True) + paxArrivalDf = pd.concat( + [ + paxArrivalDf, + pd.DataFrame( + { + "passenger_arrival_time_s": arrival_time * 60, + "origin_vertiport_id": vertiport_dict[od.split("_")[0]], + "destination_vertiport_id": vertiport_dict[ + od.split("_")[1] + ], + } + ), + ], + ignore_index=True, + ) - schedule = schedule.sort_values(by=['schedule', "od"]).reset_index(drop=True) + schedule = schedule.sort_values(by=["schedule", "od"]).reset_index(drop=True) paxArrivalDf = paxArrivalDf.reset_index(drop=False) - paxArrivalDf = paxArrivalDf.rename(columns={'index':'passenger_id'}) + paxArrivalDf = paxArrivalDf.rename(columns={"index": "passenger_id"}) - return schedule, paxArrivalDf \ No newline at end of file + return schedule, paxArrivalDf diff --git a/uam_system_model/StarNetworkBJ.py b/uam_system_model/StarNetworkBJ.py index ce25441..b600265 100644 --- a/uam_system_model/StarNetworkBJ.py +++ b/uam_system_model/StarNetworkBJ.py @@ -158,7 +158,6 @@ def load_demand( return self.schedule, self.pax_arrival_times def plot_flight(self, ylim=(0, 25)): - # 假定:前两个是 hub,后面都是 spoke hubs = self.vertiports[:2] spokes = self.vertiports[2:] @@ -175,8 +174,7 @@ def plot_flight(self, ylim=(0, 25)): for hub_name in hubs: # 只保留与当前 hub 有关的航班(hub <-> 任意点) sub = schedule[ - (schedule["origin"] == hub_name) - | (schedule["destination"] == hub_name) + (schedule["origin"] == hub_name) | (schedule["destination"] == hub_name) ].copy() # 构造“局部视角”: @@ -208,7 +206,6 @@ def plot_flight(self, ylim=(0, 25)): input_to_viz[dummy_idx, :, :] = np.nan input_to_viz[:, dummy_idx, :] = np.nan - # 调用底层原始的 plot_travel_time(不改它) fig, ax = plot_travel_time( input_to_viz, @@ -218,10 +215,10 @@ def plot_flight(self, ylim=(0, 25)): ) # --- 覆盖 plot_travel_time 的标题 --- - hub_short = hub_name # 例如 "PEK", "PKX" + hub_short = hub_name # 例如 "PEK", "PKX" - ax[0].set_title(f"{hub_short}-Spokes") # 左图:hub → spokes - ax[1].set_title(f"Spokes-{hub_short}") # 右图:spokes → hub + ax[0].set_title(f"{hub_short}-Spokes") # 左图:hub → spokes + ax[1].set_title(f"Spokes-{hub_short}") # 右图:spokes → hub figs.append(fig) axes_list.append(ax) diff --git a/uam_system_model/StarNetworkJFK.py b/uam_system_model/StarNetworkJFK.py index 23abf7a..67c9990 100644 --- a/uam_system_model/StarNetworkJFK.py +++ b/uam_system_model/StarNetworkJFK.py @@ -27,13 +27,15 @@ def __init__( self.demand_generator = ScheduleGenerator() - def load_demand(self, - arrival_rate_by_vertiport, - auto_regressive_alpha=0, - max_waiting_time=5, - occupancy=4, - fare=3, - seed=9): + def load_demand( + self, + arrival_rate_by_vertiport, + auto_regressive_alpha=0, + max_waiting_time=5, + occupancy=4, + fare=3, + seed=9, + ): np.random.seed(seed) ( schedule, From 0a7465e3039d5f579e2468f729d02925de62e96b Mon Sep 17 00:00:00 2001 From: Albert Cao Date: Wed, 21 Jan 2026 15:00:34 -0800 Subject: [PATCH 4/5] remove tqdm --- uam_system_model/Pricing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uam_system_model/Pricing.py b/uam_system_model/Pricing.py index 9d3c31b..5e84fad 100644 --- a/uam_system_model/Pricing.py +++ b/uam_system_model/Pricing.py @@ -234,7 +234,7 @@ def optimize( ) # Flow conservation constraints - for n in tqdm(self.nodes): + for n in self.nodes: m.addConstr( sum(m._x_vars[i, j] for i, j in self.edges if j == n) - sum(m._x_vars[i, j] for i, j in self.edges if i == n) From e1a46eba8ce2cf70a68ec87300767ce2822c90f0 Mon Sep 17 00:00:00 2001 From: Albert Cao Date: Thu, 5 Feb 2026 20:38:17 -0800 Subject: [PATCH 5/5] minor changes --- uam_system_model/utils/PricingOpSummary.py | 16 ++++++++++++---- uam_system_model/utils/visualize.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/uam_system_model/utils/PricingOpSummary.py b/uam_system_model/utils/PricingOpSummary.py index 0836669..8d1d0ec 100644 --- a/uam_system_model/utils/PricingOpSummary.py +++ b/uam_system_model/utils/PricingOpSummary.py @@ -56,11 +56,16 @@ def get_summary_statistics(self): return summary_stats - def plot_average_rasm(self, dpi=300): + def plot_average_rasm(self, ax=None, dpi=300): + if ax is None: + fig, ax = plt.subplots(figsize=(6, 4), dpi=dpi) + else: + fig = ax.figure + df_grouped = self.policy.groupby(["passenger_arrival_time_slot"])[ "rev_per_mile" ].mean() - fig, ax = plt.subplots(figsize=(6, 4), dpi=dpi) + sns.lineplot( x=np.arange(len(df_grouped)), y=df_grouped.values, @@ -164,8 +169,11 @@ def plot_revenue_by_od(self, dpi=300): return fig, ax - def plot_uam_share_by_od(self, dpi=300): - fig, ax = plt.subplots(figsize=(6, 4), dpi=dpi) + def plot_uam_share_by_od(self, ax=None, dpi=300): + if ax is None: + fig, ax = plt.subplots(figsize=(6, 4), dpi=dpi) + else: + fig = ax.figure sns.lineplot( data=self.policy, x="passenger_arrival_time_slot", diff --git a/uam_system_model/utils/visualize.py b/uam_system_model/utils/visualize.py index ed4425a..73da623 100644 --- a/uam_system_model/utils/visualize.py +++ b/uam_system_model/utils/visualize.py @@ -25,7 +25,7 @@ def plot_travel_time( travel_time_avg, vertiports, ylim=(0, 100), ylabel="TNC Trip Time (min)" ): fig, ax = plt.subplots(figsize=(12, 4), ncols=2, dpi=300) - for i in range(1, 9): + for i in range(1, len(vertiports)): ax[0].plot( np.arange(24), travel_time_avg[0, i, :],