diff --git a/Scripts/assignment/departure_time.py b/Scripts/assignment/departure_time.py index 0016c5a0..461d6f65 100644 --- a/Scripts/assignment/departure_time.py +++ b/Scripts/assignment/departure_time.py @@ -5,8 +5,6 @@ from datatypes.demand import Demand from datatypes.tour import Tour -from models.park_and_ride_logit import ParkAndRidePurpose -from transform.park_and_ride_transformer import ParkAndRideTransformer import utils.log as log import parameters.departure_time as param from parameters.assignment import transport_classes, assignment_classes @@ -69,38 +67,6 @@ def init_demand(self) -> Dict[str,float]: return {"rel_gap": relative_gap, "max_gap": max_gap} - def split_park_and_ride(self, demand: Union[Demand, Tour], park_and_ride_impedance:Dict[str, numpy.ndarray], park_and_ride_facility_map: Dict[int,int], pr_purpose: ParkAndRidePurpose): - log.info("Splitting park and ride demand to cars and public transport for {} facilities".format(len(park_and_ride_facility_map))) - position2 = cast(Tuple[int,int], demand.position) #type checker hint - - zone_data = pr_purpose.zone_data - share: Dict[str, Any] = param.demand_share[demand.purpose.name][demand.mode] - all_zones_len = len(zone_data.all_zone_numbers) - car_matrix = numpy.zeros((all_zones_len,all_zones_len)) - transit_matrix = numpy.zeros((all_zones_len,all_zones_len)) - - #used_facility = park_and_ride_impedance["used_facility"] - #target_cell = park_and_ride_facility_map[used_facility] - pr_purpose.calc_park_and_ride_expsum("park_and_ride", park_and_ride_impedance, zone_data) #calculate the logsum for all facilities - used_facilities_probs = pr_purpose.get_park_and_ride_routes() #probabilities of using a facility, dict[matrix] target_cell:n*n - for target_cell in used_facilities_probs: - #move car journeys to park and ride facilities - pr_facility_demand = used_facilities_probs[target_cell] * demand.matrix #demand matrix - source_zones = [j for j in range(zone_data.nr_zones_hs15)] - target_zones = [j for j in range(zone_data.nr_zones_hs15)] - - car_matrix[source_zones, target_cell] += pr_facility_demand.sum(axis=1) #for cars Park and ride is target only - transit_matrix[target_cell, target_zones] += pr_facility_demand.sum(axis=0) #for transit Park and ride is source only - - - for time_period in self.time_periods: - self._add_2d_demand( - share[time_period], "car_work", time_period, - car_matrix, position2) - self._add_2d_demand( - share[time_period], "transit_work", time_period, - transit_matrix, position2) - def add_demand(self, demand: Union[Demand, Tour]): """Add demand matrix for whole day. diff --git a/Scripts/datatypes/purpose.py b/Scripts/datatypes/purpose.py index b8073cd0..c1c69eb4 100644 --- a/Scripts/datatypes/purpose.py +++ b/Scripts/datatypes/purpose.py @@ -5,8 +5,9 @@ from datahandling.resultdata import ResultsData from datahandling.zonedata import ZoneData +from models.park_and_ride_model import ParkAndRideModel, ParkAndRidePseudoPurpose import parameters.zone as param -from parameters.destination_choice import secondary_destination_threshold +from parameters.destination_choice import secondary_destination_threshold, destination_choice import models.logit as logit import models.generation as generation from datatypes.demand import Demand @@ -59,6 +60,7 @@ def __init__(self, self.modes: List[str] = [] self.generated_tours: Dict[str, numpy.array] = {} self.attracted_tours: Dict[str, numpy.array] = {} + self.park_and_ride_model: ParkAndRideModel = None @property def zone_numbers(self): @@ -124,6 +126,12 @@ def __init__(self, specification, zone_data, resultdata): self.own_zone_aggregates = {mode: ArrayAggregator(zone_data.zone_numbers) for mode in self.modes} self.sec_dest_purpose = None + self.park_and_ride_model = None + if "park_and_ride" in destination_choice[self.name]: + self.park_and_ride_model = ParkAndRideModel( + zone_data, self) + else: + self.park_and_ride_model = None def print_data(self): Purpose.print_data(self) @@ -155,6 +163,10 @@ def calc_prob(self, impedance): Mode (car/transit/bike/walk) : dict Type (time/cost/dist) : numpy 2d matrix """ + if self.park_and_ride_model is not None: + pnr_utility = self.park_and_ride_model.get_logsum() + impedance['park_and_ride'] = {'utility': pnr_utility, + 'dist': impedance['car']['dist']} self.prob = self.model.calc_prob(impedance) self.dist = impedance["car"]["dist"] @@ -169,6 +181,10 @@ def calc_basic_prob(self, impedance): Mode (car/transit/bike/walk) : dict Type (time/cost/dist) : numpy 2d matrix """ + if self.park_and_ride_model is not None: + pnr_utility = self.park_and_ride_model.get_logsum() + impedance['park_and_ride'] = {'utility': pnr_utility, + 'dist': impedance['car']['dist']} self.model.calc_basic_prob(impedance) self.dist = impedance["car"]["dist"] @@ -189,7 +205,13 @@ def calc_demand(self): self.sec_dest_purpose.gen_model.add_tours(mtx, mode, self) except AttributeError: pass - demand[mode] = Demand(self, mode, mtx) + if mode == "park_and_ride": + car_demand, transit_demand = self.park_and_ride_model.distribute_demand(mtx) + pnr_purpose = ParkAndRidePseudoPurpose(self) + demand["pnr_car"] = Demand(pnr_purpose, "car", car_demand) + demand["pnr_transit"] = Demand(pnr_purpose, "transit", transit_demand) + else: + demand[mode] = Demand(self, mode, mtx) self.attracted_tours[mode] = mtx.sum(0) self.generated_tours[mode] = mtx.sum(1) self.histograms[mode].count_tour_dists(mtx, self.dist) diff --git a/Scripts/models/park_and_ride_logit.py b/Scripts/models/park_and_ride_logit.py deleted file mode 100644 index 0b04b2b3..00000000 --- a/Scripts/models/park_and_ride_logit.py +++ /dev/null @@ -1,134 +0,0 @@ -import numpy -import pandas -from datahandling.resultdata import ResultsData -from datahandling.zonedata import ZoneData -from datatypes.purpose import Purpose -from models.logit import LogitModel, SecDestModel - - -class ParkAndRideLogit(LogitModel): - """Logit model for park and ride facility selection. - - Multinomial choice of parking facility for park and ride tours - - Parameters - ---------- - zone_data : ZoneData - Data used for all demand calculations - purpose : TourPurpose - Tour purpose (type of tour) - resultdata : ResultData - Writer object to result directory - is_agent_model : bool (optional) - Whether the model is used for agent-based simulation - """ - - def calc_utils(self,impedance, facility): - - b = self.dest_choice_param["park_and_ride"] - utility = numpy.zeros_like(impedance.delta_time[facility]) - - #self._add_sec_zone_util(utility, b["attraction"], orig, dest) - zones_shops = self.zone_data.get_data("shops", self.bounds, generation=False) - zones_shops_sum = (impedance.one_km_radius[facility] * zones_shops).sum() #broadcasting - utility += numpy.full_like(impedance.delta_delta_impedance[facility], b["attraction"]["shops"] * zones_shops_sum) #utility of shopping - #self._add_impedance(utility, impedance, b["impedance"]) - utility += b["impedance"]["delta_time"] * impedance.delta_impedance[facility] #delta impedance of facility #TODO: Check this - self.dest_exps[facility] = numpy.exp(utility) - self.dest_expsum += self.dest_exps[facility] #defined in ParkAndRidePurpose - - -class ParkAndRidePurpose(Purpose): - """Purpose for secondary destination of tour. - - Parameters - ---------- - specification : dict - "name" : str - Tour purpose name (hoo) - "orig" : str - Origin of the tours (home) - "dest" : str - Destination of the tours (any) - "area" : str - Model area (metropolitan) - zone_data : ZoneData - Data used for all demand calculations - resultdata : ResultData - Writer object to result directory - """ - - def __init__(self, zone_data: ZoneData, resultdata: ResultsData): - specification = { - "name": "park_and_ride", - "orig": "home", - "dest": "work", - "area": "metropolitan", - } - Purpose.__init__(self, specification, zone_data, resultdata) - self.model = ParkAndRideLogit(zone_data, self, resultdata) - self.modes = self.model.dest_choice_param.keys() - - def calc_park_and_ride_expsum(self, mode, impedance, zone_data: ZoneData): - pnr_expsum = {} - #source_zones = [j for j in range(zone_data.nr_zones_hs15)] - #target_zones = [j for j in range(zone_data.nr_zones_hs15)] - self.model.dest_expsum = 0 - self.pnr_centroids = [(i, num) for i, num in enumerate(zone_data.all_zone_numbers) - if num in zone_data['pnr_capacity'].index and zone_data['pnr_capacity'].loc[num] > 0] #TODO: Check this? - for facility in self.pnr_centroids: - prob_single = self.calc_utils(mode, impedance, facility) #calc util? - for pnr in self.pnr_centroids: - pnr_expsum[pnr] += prob_single[:,pnr] - return pnr_expsum - - def get_park_and_ride_routes(self, zone_data: ZoneData): - """Decide the secondary destinations for all tours (generated - earlier) starting from one specific zone. - - Parameters - ---------- - mode : str - Mode (car/transit/bike) - impedance : dict - Type (time/cost/dist) : numpy 2d matrix - orig : int - The relative zone index from which these tours origin - orig_offset : int (optional) - Absolute zone index of orig is orig_offset + orig - - Returns - ------- - Demand - Matrix of destination -> secondary_destination pairs - The origin zone for all of these tours - """ - pnr_demand = {} - - for facility in self.pnr_centroids: - pnr_demand[facility] = self.calc_prob(facility) - return pnr_demand - - def calc_prob(self, facility): - """Calculate secondary destination probabilites. - - For tours starting in specific zone and ending in some zones. - - Parameters - ---------- - mode : str - Mode (car/transit/bike) - impedance : dict - Type (time/cost/dist) : numpy 2d matrix - orig : int - Origin zone index - dests : list or boolean array - Destination zone indices - - Returns - ------- - numpy.ndarray - Probability matrix for chosing zones as secondary destination - """ - pnr_demand = self.model.dest_exps[facility]/self.model.dest_expsum - return pnr_demand \ No newline at end of file diff --git a/Scripts/modelsystem.py b/Scripts/modelsystem.py index 2b29df92..6f02ab2e 100644 --- a/Scripts/modelsystem.py +++ b/Scripts/modelsystem.py @@ -11,8 +11,6 @@ from assignment.emme_assignment import EmmeAssignmentModel from assignment.mock_assignment import MockAssignmentModel -from models.park_and_ride_logit import ParkAndRidePurpose -from transform.park_and_ride_transformer import ParkAndRideTransformer import utils.log as log from utils.zone_interval import ArrayAggregator import assignment.departure_time as dt @@ -89,9 +87,7 @@ def __init__(self, self.ass_model.nr_zones, self.ass_model.time_periods) #init Impedance transformers - pnr_transformer = ParkAndRideTransformer(self.zdata_forecast) - self.pnr_distribution = ParkAndRidePurpose(self.zdata_forecast, self.resultdata) - self.imptrans = ImpedanceTransformer(extra_transformers=[pnr_transformer], + self.imptrans = ImpedanceTransformer(extra_transformers=[], export_path=estimation_data_path) bounds = slice(0, self.zdata_forecast.nr_zones) @@ -129,15 +125,16 @@ def _add_internal_demand(self, previous_iter_impedance, is_last_iteration): # Mode and destination probability matrices are calculated first, # as logsums from probability calculation are used in tour generation. self.dm.create_population_segments() - pnr_impedances = {} + saved_pnr_impedance = {} for purpose in self.dm.tour_purposes: if isinstance(purpose, SecDestPurpose): purpose.gen_model.init_tours() else: purpose_impedance = self.imptrans.transform( purpose, previous_iter_impedance) - if "park_and_ride" in purpose_impedance: - pnr_impedances[purpose.name] = purpose_impedance["park_and_ride"] + if purpose.park_and_ride_model is not None: + saved_pnr_impedance[purpose.name] = purpose_impedance + purpose.park_and_ride_model.set_impedance(previous_iter_impedance) purpose.calc_prob(purpose_impedance) if is_last_iteration and purpose.name not in ("sop", "so"): purpose.accessibility_model.calc_accessibility( @@ -162,19 +159,20 @@ def _add_internal_demand(self, previous_iter_impedance, is_last_iteration): else: if purpose.name != "wh": demand = purpose.calc_demand() + if purpose.park_and_ride_model is not None: + # Apply penalty for overcrowded park and ride facilities. + MAX_PNR_ITERATIONS = 5 # Maximum number of iterations. Set to 0 for no penalty + for i in range(MAX_PNR_ITERATIONS): + modified = purpose.park_and_ride_model.apply_crowding_penalty() + purpose.calc_prob(saved_pnr_impedance[purpose.name]) + demand = purpose.calc_demand() + log.debug(f"Park and ride crowding penalty iteration {i+1} modified {modified} facilities.") + if modified < 1: + break + if purpose.dest != "source": for mode in demand: - if mode == "park_and_ride": - pnr_transformer = None - for et in self.imptrans._extra_transformers: - if type(et) == ParkAndRideTransformer: - pnr_transformer = et - break - else: - log.error(f"No park and ride transformer found for {purpose.name} model") - self.dtm.split_park_and_ride(demand["park_and_ride"],pnr_impedances[purpose.name],pnr_transformer.get_pnr_map(),self.pnr_distribution) - else: - self.dtm.add_demand(demand[mode]) + self.dtm.add_demand(demand[mode]) self.travel_modes[mode] = True log.info("Demand calculation completed") @@ -572,12 +570,17 @@ def _add_internal_demand(self, previous_iter_impedance, is_last_iteration): log.info("Demand calculation started...") random.seed(None) self.dm.car_use_model.calc_basic_prob() + saved_pnr_impedance = {} for purpose in self.dm.tour_purposes: if isinstance(purpose, SecDestPurpose): purpose.init_sums() else: purpose_impedance = self.imptrans.transform( purpose, previous_iter_impedance) + if purpose.park_and_ride_model is not None: + saved_pnr_impedance[purpose.name] = purpose_impedance + purpose.park_and_ride_model.set_impedance(previous_iter_impedance) + if (purpose.area == "peripheral" or purpose.dest == "source" or purpose.name == "oop"): purpose.calc_prob(purpose_impedance) diff --git a/Scripts/parameters/destination_choice.py b/Scripts/parameters/destination_choice.py index d5137207..a4142407 100644 --- a/Scripts/parameters/destination_choice.py +++ b/Scripts/parameters/destination_choice.py @@ -7,19 +7,6 @@ # Destination choice (generated 2.9.2024) destination_choice = { - "pnr": { - "park_and_ride": { - "impedance": { - "delta_time": (-0.1,-0.1), - }, - "log": { - "size": 1.0 - }, - "attraction": { - "shops": (0.1, 0.1) - }, - }, - }, "hw": { "car": { "attraction": { @@ -84,8 +71,23 @@ } }, "park_and_ride": { + "utility": { + "facility": { + "shops": 0.01, + "cost": -0.1, + "time": -0.1 + }, + "car_impedance": { + "time": -0.019273809692544004, + "cost": -0.146013709792 + }, + "transit_impedance": { + "time": -0.146013709792, + "cost": -0.0174294749661 + } + }, "impedance": { - "cost": -0.00770353464125 + "utility": 0.00770353464125 }, "attraction": { "parking_cost_work": (0.771663871487, 0.771663871487) diff --git a/Scripts/tests/integration/test_models.py b/Scripts/tests/integration/test_models.py index 039c2e5b..4c13ffc0 100644 --- a/Scripts/tests/integration/test_models.py +++ b/Scripts/tests/integration/test_models.py @@ -2,8 +2,6 @@ import numpy from datahandling.zonedata import ZoneData -from transform.impedance_transformer import ImpedanceTransformer -from transform.park_and_ride_transformer import ParkAndRideTransformer import utils.log as log from modelsystem import ModelSystem, AgentModelSystem from assignment.mock_assignment import MockAssignmentModel @@ -56,7 +54,7 @@ def test_models(self): self._validate_impedances(impedance["iht"]) # Check that model result does not change - self.assertAlmostEquals(model.mode_share[0]["car"], 0.2115956440248976) + self.assertAlmostEquals(model.mode_share[0]["car"], 0.17738196155550767) print("Model system test done") diff --git a/Scripts/tests/unit/test_logit.py b/Scripts/tests/unit/test_logit.py index 5746ecee..70ef4dc9 100644 --- a/Scripts/tests/unit/test_logit.py +++ b/Scripts/tests/unit/test_logit.py @@ -39,14 +39,14 @@ class Purpose: }, "bike": { "dist": mtx, + "time": mtx, }, "walk": { "dist": mtx, }, "park_and_ride": { - "time": mtx, - "cost": mtx, "dist": mtx, + "utility": mtx, } } pur.bounds = slice(0, 9) diff --git a/Scripts/tests/unit/test_park_and_ride.py b/Scripts/tests/unit/test_park_and_ride.py deleted file mode 100644 index 7f695470..00000000 --- a/Scripts/tests/unit/test_park_and_ride.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import numpy -import unittest -from assignment.departure_time import DepartureTimeModel -from datahandling.zonedata import ZoneData -from datatypes.purpose import Purpose -from transform.park_and_ride_transformer import ParkAndRideTransformer -import os - -TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "test_data") -METROPOLITAN_ZONES = [102, 103, 244, 1063, 1531, 2703, 2741, 6272, 6291] -PERIPHERAL_ZONES = [19071] -EXTERNAL_ZONES = [34102, 34500] -PR_ZONES = [35039] - -class ParkAndRideTest(unittest.TestCase): - def test_park_and_ride_demand(self): - zi = numpy.array(METROPOLITAN_ZONES + PERIPHERAL_ZONES + EXTERNAL_ZONES) - zi_w_liipy = numpy.array(METROPOLITAN_ZONES + PERIPHERAL_ZONES + EXTERNAL_ZONES + PR_ZONES) #zone indices with park and ride facilities - - zone_data = ZoneData( - os.path.join(TEST_DATA_PATH, "Base_input_data", "2023_zonedata"), zi_w_liipy) - - dtm = DepartureTimeModel(len(zi_w_liipy)) #TODO: This will cause problems, make sure the zones include external and pnr zones - mtx = numpy.arange(len(zi)*len(zi)) - mtx.shape = (len(zi), len(zi)) - class Demand: - pass - - spec = { - "name": "hw", - "orig": "home", - "dest": "work", - "area": "metropolitan", - } - pur = Purpose(spec, zone_data) - - demc = Demand() - demc.purpose = pur - demc.is_car_passenger = False - - demc.purpose.name = "hw" - demc.mode = "car" - demc.dest = None - demc.matrix = numpy.array([[0 for _ in range(len(zi_w_liipy))] for _ in range(len(zi_w_liipy))]) - demc.position = (0,0) - dtm.add_demand(demc) - - demt = Demand() - demt.purpose = pur - demt.is_car_passenger = False - demt.purpose.name = "hw" - demt.mode = "transit" - demt.dest = None - demt.matrix = numpy.array([[0 for _ in range(len(zi_w_liipy))] for _ in range(len(zi_w_liipy))]) - demt.position = (0,0) - dtm.add_demand(demt) - - dem = Demand() - dem.purpose = pur - dem.is_car_passenger = False - dem.purpose.name = "hw" - dem.mode = "park_and_ride" - dem.dest = None - dem.matrix = numpy.array([[1 for _ in range(len(zi_w_liipy))] for _ in range(len(zi_w_liipy))]) - dem.position = (0,0) - - impedance = {period: {impedance: {imp_type: numpy.array([[1 for _ in range(len(zi_w_liipy))] for _ in range(len(zi_w_liipy))]) for imp_type in ["car_work","transit_work"]} for impedance in ["cost","dist","time"]} for period in ["aht","pt","iht"]} - park_and_ride_transformer = ParkAndRideTransformer(zone_data) - park_and_ride_facility_map = park_and_ride_transformer.get_pnr_map() - - park_and_ride_impedance = park_and_ride_transformer.transform(dem.purpose,impedance) - - dtm.split_park_and_ride(dem, park_and_ride_impedance["park_and_ride"], park_and_ride_facility_map, zone_data) - print(dtm.demand["pt"]["car_work"]) - self.assertIsNotNone(dtm.demand) - #park and ride should be distributed to car and transit matrices - self.assertIs(type(dtm.demand["iht"]["car_work"]), numpy.ndarray) - self.assertEquals(dtm.demand["pt"]["car_work"].ndim, 2) - self.assertGreater(numpy.sum(dtm.demand["pt"]["car_work"]), 0) - self.assertEquals(dtm.demand["aht"]["transit_work"].shape[1], 13) - #self.assertNotEquals(dtm.demand["iht"]["car"][12, 1], 12) diff --git a/Scripts/transform/impedance_transformer.py b/Scripts/transform/impedance_transformer.py index 3f22ff87..1e7e33e1 100644 --- a/Scripts/transform/impedance_transformer.py +++ b/Scripts/transform/impedance_transformer.py @@ -1,11 +1,12 @@ from abc import ABC from collections import defaultdict from pathlib import Path -from typing import Dict, List +from typing import Dict, List, TYPE_CHECKING import numpy import openmatrix as omx -from datatypes.purpose import Purpose +if TYPE_CHECKING: + from datatypes.purpose import Purpose import parameters.impedance_transformation as param from parameters.assignment import assignment_classes try: @@ -16,7 +17,7 @@ import numpy as np import openmatrix as omx -def transit_cost_to_per_day(cost: np.ndarray, purpose: Purpose) -> None: +def transit_cost_to_per_day(cost: np.ndarray, purpose: 'Purpose') -> None: """Converts monthly transit ticket cost to daily cost. Args: @@ -32,7 +33,7 @@ def transit_cost_to_per_day(cost: np.ndarray, purpose: Purpose) -> None: class ImpedanceTransformerBase(ABC): def transform(self, - purpose: Purpose, + purpose: 'Purpose', impedance: Dict[str, Dict[str, Dict[str, np.ndarray]]] ) -> Dict[str, Dict[str, np.ndarray]]: """Perform transformation from time period dependent matrices @@ -65,7 +66,7 @@ def __init__(self, self._extra_transformers = extra_transformers self._export_path = export_path - def export_day_impedance(self, purpose: Purpose, day_imp: Dict[str, Dict[str, np.ndarray]]): + def export_day_impedance(self, purpose: 'Purpose', day_imp: Dict[str, Dict[str, np.ndarray]]): """Export day impedance matrices to OMX files. Used for estimation process. Args: diff --git a/Scripts/transform/park_and_ride_transformer.py b/Scripts/transform/park_and_ride_transformer.py deleted file mode 100644 index 9afab692..00000000 --- a/Scripts/transform/park_and_ride_transformer.py +++ /dev/null @@ -1,155 +0,0 @@ -from collections import defaultdict -from typing import Dict, List, NamedTuple, Tuple -import numpy as np -from datatypes.purpose import Purpose -from parameters.zone import areas -import parameters.impedance_transformation as param -from parameters.assignment import assignment_classes -from datahandling.zonedata import ZoneData -from transform.impedance_transformer import ImpedanceTransformerBase, transit_cost_to_per_day -from parameters.assignment import vot_inv - - -class ParkAndRideFacility(NamedTuple): - zone_offset: int - zone_id: int - capacity: float - cost: float - time: float - -class ParkAndRideTransformer(ImpedanceTransformerBase): - _zone_data: ZoneData - _facilities: List[ParkAndRideFacility] - _park_and_ride_centroids: List[int] - - def __init__(self, zone_data: ZoneData): - self._zone_data = zone_data - pnr_centroids = [(i, num) for i, num in enumerate(zone_data.all_zone_numbers) - if num in zone_data['pnr_capacity'].index and zone_data['pnr_capacity'].loc[num] > 0] - self._facilities = [ParkAndRideFacility(i, - num, - zone_data['pnr_capacity'].loc[num], - zone_data['pnr_cost'].loc[num], - 0.0) - for i, num in pnr_centroids] - - def get_pnr_map(self) -> Dict[int,int]: - """ - Get mapping pnr_zone_emme_number -> pnr_zone_order_index - """ - return {f.zone_id: f.zone_offset for f in self._facilities} - - def transform(self, - purpose: Purpose, - impedance: Dict[str, Dict[str, Dict[str, np.ndarray]]] - ) -> Dict[str, Dict[str, np.ndarray]]: - """Perform transformation from time period dependent matrices - to aggregate impedance matrices for specific travel purpose. - - Transform transit costs from (eur/month) to (eur/day). - - Parameters - ---------- - purpose : TourPurpose - impedance: dict - Time period (aht/pt/iht) : dict - Type (time/cost/dist) : dict - Assignment class (car_work/transit/...) : numpy 2d matrix - Return - ------ - dict - Mode (car/transit/bike/walk) : dict - Type (time/cost/dist) : numpy 2-d matrix - """ - - impedance_share = param.impedance_share[purpose.name].get('park_and_ride', None) - if not impedance_share: - return {} # Skip purposes without park-and-ride mode - - - day_impedance = {} - assignment_class = assignment_classes[purpose.name] - car_mode = 'car_' + assignment_class - transit_mode = 'transit_' + assignment_class - inverse_value_of_time = vot_inv[assignment_class] - - types = ['cost', 'time','dist'] - rows = purpose.bounds - cols = (purpose.bounds if purpose.name == "hoo" - else slice(0, purpose.zone_data.nr_zones)) - - if len(self._facilities) == 0: #if there are no P+R facilities (mostly for testing) - rows = sum([1 for _ in purpose.zone_numbers]) - cols = purpose.zone_data.nr_zones - inf_mat = np.full((rows,cols), 999.) - return { - - 'park_and_ride': { - 'cost': inf_mat, - 'time': inf_mat, - 'dist': inf_mat, - 'gen_cost': inf_mat, - 'used_facility': np.full((rows, cols), None), - 'delta_impedance': {}, - 'one_km_radius': inf_mat, - } - } - - - day_imp = {} - for mode in [car_mode, transit_mode]: - day_imp[mode] = defaultdict(float) - for time_period in impedance: - for mtx_type in types: - imp = impedance[time_period][mtx_type][mode] - day_imp[mode][mtx_type] += impedance_share[time_period][0] * imp - day_imp[mode][mtx_type] += impedance_share[time_period][1] * imp.T - - day_imp[transit_mode]['cost'] = transit_cost_to_per_day(day_imp[transit_mode]['cost'], purpose) - - - cost = np.stack([day_imp[car_mode]['cost'][:,f.zone_offset][:,None] + - day_imp[transit_mode]['cost'][f.zone_offset, :][None,:] - for f in self._facilities]) - cost = cost[:,rows,cols] - time = np.stack([day_imp[car_mode]['time'][:,f.zone_offset][:,None] + - day_imp[transit_mode]['time'][f.zone_offset, :][None,:] - for f in self._facilities])[:, rows, cols] - time = time[:,rows,cols] - dist = np.stack([day_imp[car_mode]['dist'][:,f.zone_offset][:,None] + - day_imp[transit_mode]['dist'][f.zone_offset, :][None,:] - for f in self._facilities])[:, rows, cols] - dist = dist[:,rows,cols] - - delta_impedance = {} - for f in self._facilities: - delta_impedance[f] = day_imp[car_mode]['dist'][:,f.zone_offset][:,None] \ - + day_imp[transit_mode]['dist'][f.zone_offset, :][None,:] \ - - day_imp[car_mode]['dist'] - - one_km_mask_matrix = day_imp[car_mode]['dist']>1 #if closer than one km - - one_km_mask = {} - for f in self._facilities: - one_km_mask[f] = one_km_mask_matrix[f,:] - - #Adding the cost and time experienced at the facility - cost += np.array([f.cost for f in self._facilities])[:,None,None] - time += np.array([f.time for f in self._facilities])[:,None,None] - - generalized_cost = cost*inverse_value_of_time + time - - min_index = np.argmin(generalized_cost, axis=0)[None, :, :]#, keepdims=True) - f_zones = np.array([f.zone_id for f in self._facilities])[:,None,None] - day_impedance = { - 'park_and_ride': { - 'cost': (np.take_along_axis(cost, min_index, axis=0)[0]), - 'time': np.take_along_axis(time, min_index, axis=0)[0], - 'dist': np.take_along_axis(dist, min_index, axis=0)[0], #TODO: Fix this? - 'gen_cost': np.take_along_axis(generalized_cost, min_index, axis=0)[0], - 'used_facility': np.take_along_axis(f_zones, min_index, axis=0)[0], - 'delta_impedance': delta_impedance, - 'one_km_radius': one_km_mask, - } - } - return day_impedance