From 16f84edd4d19a1071821b2d27af642ed6d2fcf4f Mon Sep 17 00:00:00 2001 From: Jens West Date: Tue, 4 Oct 2022 16:45:30 +0300 Subject: [PATCH 1/5] Add framework for park-and-ride network assignment --- Scripts/assignment/assignment_period.py | 43 +++++-- Scripts/assignment/datatypes/journey_level.py | 102 +++++++++++---- Scripts/assignment/datatypes/transit.py | 41 +++--- Scripts/assignment/emme_assignment.py | 45 +++++-- Scripts/parameters/assignment.py | 119 +++++++++++++++++- Scripts/test_assignment.py | 2 + .../test_data/Network/base_network_test.txt | 4 +- .../tests/test_data/Network/modes_test.txt | 1 + Scripts/tests/unit/test_assignment.py | 2 + 9 files changed, 293 insertions(+), 66 deletions(-) diff --git a/Scripts/assignment/assignment_period.py b/Scripts/assignment/assignment_period.py index 782e0210..0b6f6076 100644 --- a/Scripts/assignment/assignment_period.py +++ b/Scripts/assignment/assignment_period.py @@ -2,6 +2,7 @@ import numpy import pandas import copy +from assignment.datatypes.journey_level import JourneyLevel import utils.log as log import parameters.assignment as param @@ -46,7 +47,7 @@ def extra(self, attr): """ return "@{}_{}".format(attr, self.name) - def prepare(self, segment_results): + def prepare(self, segment_results, park_and_ride_results): """Prepare network for assignment. Calculate road toll cost, set boarding penalties, @@ -62,8 +63,15 @@ def prepare(self, segment_results): Segment result (transit_volumes/...) value : str Extra attribute name (@transit_work_vol_aht/...) + park_and_ride_results : dict + key : str + Transit class (transit_work/transit_leisure/...) + value : str or False + Extra attribute name for park-and-ride aux volume if + this is park-and-ride assignment, else False """ self._segment_results = segment_results + self._park_and_ride_results = park_and_ride_results self._calc_road_cost() self._calc_boarding_penalties() self._calc_background_traffic() @@ -177,8 +185,8 @@ def calc_transit_cost(self, fares, peripheral_cost, mapping): transit_zones = {node.label for node in network.nodes()} tc = "transit_work" spec = TransitSpecification( - self._segment_results[tc], self.extra("hw"), - self.demand_mtx[tc]["id"], + self._segment_results[tc], self._park_and_ride_results[tc], + self.extra("hw"), self.demand_mtx[tc]["id"], self.result_mtx["time"][tc]["id"], self.result_mtx["dist"][tc]["id"], self.result_mtx["trip_part_"+tc], @@ -267,6 +275,7 @@ def _set_car_and_transit_vdfs(self): for modes in param.transit_delay_funcs} main_mode = network.mode(param.main_mode) car_mode = network.mode(param.assignment_modes["car_work"]) + park_and_ride_mode = network.mode(param.park_and_ride_mode) for link in network.links(): # Car volume delay function definition linktype = link.type % 100 @@ -327,9 +336,9 @@ def _set_car_and_transit_vdfs(self): for segment in link.segments(): segment.transit_time_func = func if car_mode in link.modes: - link.modes |= {main_mode} - elif main_mode in link.modes: - link.modes -= {main_mode} + link.modes |= {main_mode, park_and_ride_mode} + else: + link.modes -= {main_mode, park_and_ride_mode} self.emme_scenario.publish_network(network) def _set_bike_vdfs(self): @@ -488,6 +497,8 @@ def _calc_background_traffic(self, include_trucks=False): "ul", "data") # calc @bus and data3 heavy = (self.extra("truck"), self.extra("trailer_truck")) + park_and_ride = [self._park_and_ride_results[direction] + for direction in param.park_and_ride_classes] for link in network.links(): if link.type > 100: # If car or bus link freq = 0 @@ -501,6 +512,8 @@ def _calc_background_traffic(self, include_trucks=False): link[background_traffic] = 0 else: link[background_traffic] = freq + for direction in park_and_ride: + link[background_traffic] += link[direction] if include_trucks: for ass_class in heavy: link[background_traffic] += link[ass_class] @@ -541,8 +554,8 @@ def _specify(self): self._car_spec = CarSpecification( self.extra, self.demand_mtx, self.result_mtx) self._transit_specs = {tc: TransitSpecification( - self._segment_results[tc], self.extra("hw"), - self.demand_mtx[tc]["id"], + self._segment_results[tc], self._park_and_ride_results[tc], + self.extra("hw"), self.demand_mtx[tc]["id"], self.result_mtx["time"][tc]["id"], self.result_mtx["dist"][tc]["id"], self.result_mtx["trip_part_"+tc]) @@ -739,11 +752,15 @@ def _assign_transit(self): log.info("Transit assignment started...") # Here we assign all transit in one class, multi-class assignment is # performed in last iteration (congested assignment) - spec = self._transit_specs["transit_work"] - self.emme_project.transit_assignment( - specification=spec.transit_spec, scenario=self.emme_scenario, - save_strategies=True) - self.emme_project.matrix_results(spec.transit_result_spec, scenario=self.emme_scenario) + transit_classes = param.park_and_ride_classes + ("transit_work",) + for i, transit_class in enumerate(transit_classes): + spec = self._transit_specs[transit_class] + self.emme_project.transit_assignment( + specification=spec.transit_spec, scenario=self.emme_scenario, + add_volumes=i, save_strategies=True, class_name=transit_class) + self.emme_project.matrix_results( + spec.transit_result_spec, scenario=self.emme_scenario, + class_name=transit_class) log.info("Transit assignment performed for scenario {}".format( str(self.emme_scenario.id))) diff --git a/Scripts/assignment/datatypes/journey_level.py b/Scripts/assignment/datatypes/journey_level.py index 09a95fde..6a3e4039 100644 --- a/Scripts/assignment/datatypes/journey_level.py +++ b/Scripts/assignment/datatypes/journey_level.py @@ -1,20 +1,84 @@ +from argparse import ArgumentError import parameters.assignment as param +NOT_BOARDED, PARKED, BOARDED, LEFT, FORBIDDEN = range(5) +DESCRIPTION = [ + "Not boarded yet", + "Parked", + "Boarded at least once", + "Left transit system", + "Forbidden", +] +DESTINATIONS_REACHABLE = [False, False, True, True, False] + + class JourneyLevel: - def __init__(self, headway_attribute, boarded, count_zone_boardings=False): - # Definition of transition rules: all modes are allowed - transitions = [] - for mode in param.transit_modes: - transitions.append({ + """ + Journey level specification for transit assignment. + + Parameters + ---------- + level : int + Journey level (0-3) + headway_attribute : str + Line attribute where headway is stored + park_and_ride : str or False (optional) + Extra attribute name for park-and-ride aux volume if + this is park-and-ride assignment, else False + count_zone_boardings : bool (optional) + Whether assignment is performed only to count fare zone boardings + """ + def __init__(self, level, headway_attribute, park_and_ride=False, + count_zone_boardings=False): + # Boarding transit modes allowed only on levels 0-2 + next = BOARDED if level <= BOARDED else FORBIDDEN + transitions = [{ "mode": mode, - "next_journey_level": 1 + "next_journey_level": next, + } for mode in param.transit_modes] + if park_and_ride: + if "first_mile" in park_and_ride: + # Park-and-ride (car) mode allowed only on level 0. + car = FORBIDDEN if level >= PARKED else NOT_BOARDED + # If we want parking to be allowed only on specific links + # (i.e., park-and-ride facilities), we should specify an + # own mode for these links. For now, parking is allowed + # on all links where walking to a stop is possible. + walk = PARKED if level == NOT_BOARDED else level + elif "last_mile" in park_and_ride: + # Transfer to park-and-ride (car) mode only allowed after first + # boarding. If we want parking to be allowed only on specific + # links, we should specify an own mode for these links. + # For now, parking is allowed on all links where walking + # from a stop is possible. + car = FORBIDDEN if level in (NOT_BOARDED, FORBIDDEN) else LEFT + walk = FORBIDDEN if level == LEFT else level + transitions.append({ + "mode": param.park_and_ride_mode, + "next_journey_level": car, }) + else: + # Walk modes do not normally affect journey level transitions + walk = level + transitions += [{ + "mode": mode, + "next_journey_level": walk, + } for mode in param.aux_modes] self.spec = { + "description": DESCRIPTION[level], + "destinations_reachable": DESTINATIONS_REACHABLE[level], "transition_rules": transitions, "boarding_time": None, - "boarding_cost": dict.fromkeys([ - "global", "at_nodes", "on_lines", "on_segments"]), + "boarding_cost": { + "global": { + "penalty": 0, + "perception_factor": 1, + }, + "at_nodes": None, + "on_lines": None, + "on_segments": None, + }, "waiting_time": { "headway_fraction": param.standard_headway_fraction, "effective_headways": headway_attribute, @@ -22,22 +86,12 @@ def __init__(self, headway_attribute, boarded, count_zone_boardings=False): "perception_factor": param.waiting_time_perception_factor, }, } - if boarded: - self.spec["description"] = "Boarded at least once" - self.spec["destinations_reachable"] = True - self.spec["boarding_cost"]["global"] = { - "penalty": param.transfer_penalty["transit"], - "perception_factor": 1, - } - else: - self.spec["description"] = "Not boarded yet" - self.spec["destinations_reachable"] = False - hdw_frac = param.first_headway_fraction - self.spec["waiting_time"]["headway_fraction"] = hdw_frac - self.spec["boarding_cost"]["global"] = { - "penalty": 0, - "perception_factor": 1, - } + if level < BOARDED: + (self.spec["waiting_time"] + ["headway_fraction"]) = param.first_headway_fraction + elif level == BOARDED: + (self.spec["boarding_cost"] + ["global"]["penalty"]) = param.transfer_penalty["transit"] if count_zone_boardings: self.spec["boarding_cost"]["global"] = None self.spec["boarding_cost"]["at_nodes"] = { diff --git a/Scripts/assignment/datatypes/transit.py b/Scripts/assignment/datatypes/transit.py index e7c927e0..1d0dc33a 100644 --- a/Scripts/assignment/datatypes/transit.py +++ b/Scripts/assignment/datatypes/transit.py @@ -1,3 +1,5 @@ +import copy + import parameters.assignment as param from assignment.datatypes.path_analysis import PathAnalysis from assignment.datatypes.journey_level import JourneyLevel @@ -19,6 +21,9 @@ class TransitSpecification: Segment result (transit_volumes/...) value : str Extra attribute name (@transit_work_vol_aht/...) + park_and_ride_results : str or False (optional) + Extra attribute name for park-and-ride aux volume if + this is park-and-ride assignment, else False headway_attribute : str Line attribute where headway is stored demand_mtx_id : str @@ -38,9 +43,9 @@ class TransitSpecification: count_zone_boardings : bool (optional) Whether assignment is performed only to count fare zone boardings """ - def __init__(self, segment_results, headway_attribute, - demand_mtx_id, time_mtx_id, dist_mtx_id, trip_part, - count_zone_boardings=False): + def __init__(self, segment_results, park_and_ride_results, + headway_attribute, demand_mtx_id, time_mtx_id, dist_mtx_id, + trip_part, count_zone_boardings=False): no_penalty = dict.fromkeys(["at_nodes", "on_lines", "on_segments"]) no_penalty["global"] = { "penalty": 0, @@ -48,7 +53,7 @@ def __init__(self, segment_results, headway_attribute, } self.transit_spec = { "type": "EXTENDED_TRANSIT_ASSIGNMENT", - "modes": param.transit_assignment_modes, + "modes": copy.copy(param.transit_assignment_modes), "demand": demand_mtx_id, "waiting_time": { "headway_fraction": param.standard_headway_fraction, @@ -84,16 +89,24 @@ def __init__(self, segment_results, headway_attribute, "journey_levels": None, "performance_settings": param.performance_settings, } + if park_and_ride_results: + self.transit_spec["modes"].append(param.park_and_ride_mode) + self.transit_spec["results"] = { + "aux_transit_volumes_by_mode": [{ + "mode": param.park_and_ride_mode, + "volume": park_and_ride_results, + }], + } + self.transit_spec["journey_levels"] = [JourneyLevel( + level, headway_attribute, park_and_ride_results, + count_zone_boardings).spec + for level in range(5)] self.ntw_results_spec = { "type": "EXTENDED_TRANSIT_NETWORK_RESULTS", "on_segments": segment_results, - } + } if count_zone_boardings: - jlevel1 = JourneyLevel( - headway_attribute, boarded=False, count_zone_boardings=True) - jlevel2 = JourneyLevel( - headway_attribute, boarded=True, count_zone_boardings=True) - mtx_results_spec = { + self.transit_result_spec = { "type": "EXTENDED_TRANSIT_MATRIX_RESULTS", "by_mode_subset": { "modes": param.transit_modes, @@ -102,9 +115,7 @@ def __init__(self, segment_results, headway_attribute, }, } else: - jlevel1 = JourneyLevel(headway_attribute, boarded=False) - jlevel2 = JourneyLevel(headway_attribute, boarded=True) - mtx_results_spec = { + self.transit_result_spec = { "type": "EXTENDED_TRANSIT_MATRIX_RESULTS", "total_impedance": time_mtx_id, "total_travel_time": trip_part["total_time"]["id"], @@ -119,7 +130,3 @@ def __init__(self, segment_results, headway_attribute, "actual_aux_transit_times": trip_part["aux_time"]["id"], }, } - - self.transit_spec["journey_levels"] = [jlevel1.spec, jlevel2.spec] - self.transit_result_spec = mtx_results_spec - diff --git a/Scripts/assignment/emme_assignment.py b/Scripts/assignment/emme_assignment.py index da002288..3d118f8f 100644 --- a/Scripts/assignment/emme_assignment.py +++ b/Scripts/assignment/emme_assignment.py @@ -95,7 +95,7 @@ def prepare_network(self, car_dist_unit_cost=None): for ap in self.assignment_periods: if car_dist_unit_cost is not None: ap.dist_unit_cost = car_dist_unit_cost - ap.prepare(self._create_attributes(ap.emme_scenario, ap.extra)) + ap.prepare(*self._create_attributes(ap.emme_scenario, ap.extra)) for idx in param.volume_delay_funcs: try: self.emme_project.modeller.emmebank.delete_function(idx) @@ -337,6 +337,23 @@ def _create_attributes(self, scenario, extra): extra : function Small helper function which modifies string (e.g., self._extra) + + Returns + ------- + dict + key : str + Transit class (transit_work/transit_leisure/...) + value : dict + key : str + Segment result (transit_volumes/...) + value : str + Extra attribute name (@transit_work_vol_aht/...) + dict + key : str + Transit class (transit_work/transit_leisure/...) + value : str or False + Extra attribute name for park-and-ride aux volume if + this is park-and-ride assignment, else False """ # Create link attributes for ass_class in list(param.emme_demand_mtx) + ["bus"]: @@ -349,18 +366,28 @@ def _create_attributes(self, scenario, extra): overwrite=True, scenario=scenario) # Create node and transit segment attributes attr = param.segment_results - seg_results = {tc: {res: extra(tc[:11]+"_"+attr[res]) - for res in param.segment_results} - for tc in param.transit_classes} + segment_results = {} + park_and_ride_results = {} for tc in param.transit_classes: + segment_results[tc] = {} for res in param.segment_results: + attr_name = extra(tc[:11] + "_" + attr[res]) + segment_results[tc][res] = attr_name self.emme_project.create_extra_attribute( - "TRANSIT_SEGMENT", seg_results[tc][res], - tc+" "+res, overwrite=True, scenario=scenario) + "TRANSIT_SEGMENT", attr_name, + tc + " " + res, overwrite=True, scenario=scenario) if res != "transit_volumes": self.emme_project.create_extra_attribute( - "NODE", extra(tc[:10]+"n_"+attr[res]), - tc+" "+res, overwrite=True, scenario=scenario) + "NODE", extra(tc[:10] + "n_" + attr[res]), + tc + " " + res, overwrite=True, scenario=scenario) + if tc in param.park_and_ride_classes: + attr_name = extra(tc[4:] + "_aux") + park_and_ride_results[tc] = attr_name + self.emme_project.create_extra_attribute( + "LINK", attr_name, tc, + overwrite=True, scenario=scenario) + else: + park_and_ride_results[tc] = False self.emme_project.create_extra_attribute( "TRANSIT_SEGMENT", param.extra_waiting_time["penalty"], "wait time st.dev.", overwrite=True, scenario=scenario) @@ -375,7 +402,7 @@ def _create_attributes(self, scenario, extra): "uncongested transit time", overwrite=True, scenario=scenario) log.debug("Created extra attributes for scenario {}".format( scenario)) - return seg_results + return segment_results, park_and_ride_results def calc_noise(self): """Calculate noise according to Road Traffic Noise Nordic 1996. diff --git a/Scripts/parameters/assignment.py b/Scripts/parameters/assignment.py index 9fbf6991..ea6d76c1 100644 --- a/Scripts/parameters/assignment.py +++ b/Scripts/parameters/assignment.py @@ -270,6 +270,8 @@ transfer_penalty = { "transit_work": 3, "transit_leisure": 5, + "car_first_mile": 5, + "car_last_mile": 5, "transit": 5, } extra_waiting_time = { @@ -321,6 +323,16 @@ "pt": 1. / 0.117, "iht": 1. / 0.373, }, + "car_first_mile": { + "aht": 1. / 0.478, + "pt": 1. / 0.109, + "iht": 1. / 0.405, + }, + "car_last_mile": { + "aht": 1. / 0.478, + "pt": 1. / 0.109, + "iht": 1. / 0.405, + }, "bike": { "aht": 1. / 0.604, "pt": 1. / 0.105, @@ -386,7 +398,11 @@ "truck", "van", ) -transit_classes = ( +park_and_ride_classes = ( + "car_first_mile", + "car_last_mile", +) +transit_classes = park_and_ride_classes + ( "transit_work", "transit_leisure", ) @@ -443,6 +459,7 @@ 'a', 's', ] +park_and_ride_mode = 'u' transit_assignment_modes = transit_modes + aux_modes external_modes = [ "car", @@ -492,6 +509,14 @@ "id": 9, "description": "van demand", }, + "car_first_mile": { + "id": 91, + "description": "park-and-ride demand", + }, + "car_last_mile": { + "id": 92, + "description": "park-and-ride demand", + }, } emme_result_mtx = { "time": { @@ -531,6 +556,14 @@ "id": 19, "description": "van time", }, + "car_first_mile": { + "id": 93, + "description": "park-and-ride time", + }, + "car_last_mile": { + "id": 94, + "description": "park-and-ride time", + }, }, "dist": { "car_work": { @@ -569,6 +602,14 @@ "id": 29, "description": "van distance", }, + "car_first_mile": { + "id": 95, + "description": "park-and-ride distance", + }, + "car_last_mile": { + "id": 96, + "description": "park-and-ride distance", + }, }, "cost": { "car_work": { @@ -599,6 +640,14 @@ "id": 39, "description": "van cost", }, + "car_first_mile": { + "id": 97, + "description": "park-and-ride cost", + }, + "car_last_mile": { + "id": 98, + "description": "park-and-ride cost", + }, }, "gen_cost": { "car_work": { @@ -690,6 +739,74 @@ "description": "transit boarding cost", }, }, + "trip_part_car_first_mile":{ + "inv_time": { + "id": 71, + "description": "transit in-vehicle time", + }, + "aux_time": { + "id": 72, + "description": "transit auxilliary time", + }, + "tw_time": { + "id": 73, + "description": "transit total waiting time", + }, + "fw_time": { + "id": 74, + "description": "transit first waiting time", + }, + "board_time": { + "id": 75, + "description": "transit boarding time", + }, + "total_time": { + "id": 76, + "description": "transit unweighted travel time", + }, + "num_board": { + "id": 77, + "description": "transit trip number of boardings", + }, + "board_cost": { + "id": 78, + "description": "transit boarding cost", + }, + }, + "trip_part_car_last_mile":{ + "inv_time": { + "id": 81, + "description": "transit in-vehicle time", + }, + "aux_time": { + "id": 82, + "description": "transit auxilliary time", + }, + "tw_time": { + "id": 83, + "description": "transit total waiting time", + }, + "fw_time": { + "id": 84, + "description": "transit first waiting time", + }, + "board_time": { + "id": 85, + "description": "transit boarding time", + }, + "total_time": { + "id": 86, + "description": "transit unweighted travel time", + }, + "num_board": { + "id": 87, + "description": "transit trip number of boardings", + }, + "board_cost": { + "id": 88, + "description": "transit boarding cost", + }, + }, } background_traffic_attr = "ul3" inactive_line_penalty_attr = "ut1" diff --git a/Scripts/test_assignment.py b/Scripts/test_assignment.py index 6be90d5d..17b9b2e3 100644 --- a/Scripts/test_assignment.py +++ b/Scripts/test_assignment.py @@ -75,6 +75,8 @@ def test_assignment(self): "car_leisure": car_matrix, "transit_work": car_matrix, "transit_leisure": car_matrix, + "car_first_mile": car_matrix, + "car_last_mile": car_matrix, "bike": car_matrix, "trailer_truck": car_matrix, "truck": car_matrix, diff --git a/Scripts/tests/test_data/Network/base_network_test.txt b/Scripts/tests/test_data/Network/base_network_test.txt index 03e1c2a2..e16fd88a 100644 --- a/Scripts/tests/test_data/Network/base_network_test.txt +++ b/Scripts/tests/test_data/Network/base_network_test.txt @@ -1183,7 +1183,7 @@ a 200843 201074 .727217 cvkybgdeaf 139 1.0 5 750 36 0 a 201026 202613 .440000 cvkybgdeaf 336 2.0 9 1000 44 38.4193 a 201026 205090 .091500 cvkybgdeaf 136 2.0 4 1000 44 48.8554 a 201074 200843 .727217 cvkybgdeaf 139 1.0 5 750 36 0 -a 201074 205050 3 cvkw 195 1.0 99 1000 100 1.1747 +a 201074 205050 3 cvkw 195 1.0 99 6 15 1.1747 a 201074 221900 1.04822 cvkbgdeaf 141 2.0 5 600 30 0 a 201142 40348 .089300 cvkybgdeaf 336 2.0 9 1000 44 70.8014 a 201142 221795 .060000 cvkybgdeaf 336 2.0 9 1000 44 33.9113 @@ -1264,7 +1264,7 @@ a 204990 221612 .100000 cvkbgdeaf 141 1.0 5 600 30 46.1448 a 204991 200287 .100000 cvkybgdeaf 141 1.0 5 600 30 147.134 a 204991 221612 .100000 af 70 1.0 0 0 0 0 a 205050 1531 .300000 cvkaf 99 1.0 99 1000 100 1.1747 -a 205050 201074 3 cvkw 195 1.0 99 1000 100 1.17383 +a 205050 201074 3 cvkw 195 1.0 99 6 15 1.17383 a 205059 199920 1.34 cvkybgde 125 2.0 1 2000 81 200.799 a 205059 212920 .430000 af 70 1.0 0 0 0 0 a 205062 199913 .420000 af 70 1.0 0 0 0 0 diff --git a/Scripts/tests/test_data/Network/modes_test.txt b/Scripts/tests/test_data/Network/modes_test.txt index b049bf00..2fb43c32 100644 --- a/Scripts/tests/test_data/Network/modes_test.txt +++ b/Scripts/tests/test_data/Network/modes_test.txt @@ -19,3 +19,4 @@ a w 'Vesiliiken' 2 1 a a 'Kavely' 3 1 0.00 0.00 0.00 0.00 5.0 a s 'Ulkokavely' 3 1 0.00 0.00 0.00 0.00 5.0 a f 'Polkupyora' 3 1 0.00 0.00 0.00 0.00 17.0 +a u 'Liitynta' 3 1 0.00 0.00 0.00 0.00 timau*2 diff --git a/Scripts/tests/unit/test_assignment.py b/Scripts/tests/unit/test_assignment.py index 03851e31..d7627931 100644 --- a/Scripts/tests/unit/test_assignment.py +++ b/Scripts/tests/unit/test_assignment.py @@ -43,6 +43,8 @@ def test_assignment(self): "car_leisure": car_matrix, "transit_work": car_matrix, "transit_leisure": car_matrix, + "car_first_mile": car_matrix, + "car_last_mile": car_matrix, "bike": car_matrix, "trailer_truck": car_matrix, "truck": car_matrix, From 1a34381dd48ba1390c8de428f5827362c6b44951 Mon Sep 17 00:00:00 2001 From: Jens West Date: Wed, 5 Oct 2022 10:12:24 +0300 Subject: [PATCH 2/5] Improve docstring and remove extra imports --- Scripts/assignment/assignment_period.py | 1 - Scripts/assignment/datatypes/journey_level.py | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts/assignment/assignment_period.py b/Scripts/assignment/assignment_period.py index 0b6f6076..618f64f3 100644 --- a/Scripts/assignment/assignment_period.py +++ b/Scripts/assignment/assignment_period.py @@ -2,7 +2,6 @@ import numpy import pandas import copy -from assignment.datatypes.journey_level import JourneyLevel import utils.log as log import parameters.assignment as param diff --git a/Scripts/assignment/datatypes/journey_level.py b/Scripts/assignment/datatypes/journey_level.py index 6a3e4039..5fe10a83 100644 --- a/Scripts/assignment/datatypes/journey_level.py +++ b/Scripts/assignment/datatypes/journey_level.py @@ -1,4 +1,3 @@ -from argparse import ArgumentError import parameters.assignment as param @@ -20,7 +19,9 @@ class JourneyLevel: Parameters ---------- level : int - Journey level (0-3) + Journey level: 0 - not boarded yet, 1 - parked, + 2 - boarded at least once, 3 - left transit system, + 4 - forbidden (virtual level) headway_attribute : str Line attribute where headway is stored park_and_ride : str or False (optional) From 0528b1823094fe8baf559c080b89262e2c6bec1e Mon Sep 17 00:00:00 2001 From: Jens West Date: Fri, 7 Oct 2022 14:30:51 +0300 Subject: [PATCH 3/5] Fix transfer penalty --- Scripts/assignment/assignment_period.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Scripts/assignment/assignment_period.py b/Scripts/assignment/assignment_period.py index 618f64f3..c6563152 100644 --- a/Scripts/assignment/assignment_period.py +++ b/Scripts/assignment/assignment_period.py @@ -8,6 +8,7 @@ import parameters.zone as zone_param from assignment.datatypes.car_specification import CarSpecification from assignment.datatypes.transit import TransitSpecification +from assignment.datatypes.journey_level import BOARDED from assignment.datatypes.path_analysis import PathAnalysis from assignment.abstract_assignment import Period @@ -779,7 +780,7 @@ def _assign_congested_transit(self): specs = self._transit_specs for transit_class in specs: spec = specs[transit_class].transit_spec - (spec["journey_levels"][1]["boarding_cost"]["global"] + (spec["journey_levels"][BOARDED]["boarding_cost"]["global"] ["penalty"]) = param.transfer_penalty[transit_class] spec["in_vehicle_cost"] = { "penalty": param.inactive_line_penalty_attr, From f212a2c47fb2038c297bddbd5331be8a8b19ea05 Mon Sep 17 00:00:00 2001 From: Jens West Date: Thu, 20 Oct 2022 13:52:33 +0300 Subject: [PATCH 4/5] Add temporary test demand matrices --- Scripts/assignment/emme_assignment.py | 10 ++++++++++ Scripts/tests/unit/test_assignment.py | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/Scripts/assignment/emme_assignment.py b/Scripts/assignment/emme_assignment.py index 3d118f8f..cc897dce 100644 --- a/Scripts/assignment/emme_assignment.py +++ b/Scripts/assignment/emme_assignment.py @@ -78,6 +78,16 @@ def prepare_network(self, car_dist_unit_cost=None): matrix_name="demand_{}_{}".format(ass_class, tag), matrix_description="{} {}".format(mtx["description"], tag), default_value=0, overwrite=True) + if ap.name == "aht": + self.emme_project.copy_matrix( + "mf401", ap.demand_mtx["car_first_mile"]["id"], + "demand_car_first_mile", + ap.demand_mtx["car_first_mile"]["description"]) + elif ap.name == "iht": + self.emme_project.copy_matrix( + "mf402", ap.demand_mtx["car_last_mile"]["id"], + "demand_car_last_mile", + ap.demand_mtx["car_last_mile"]["description"]) for mtx_type in ap.result_mtx: mtx = ap.result_mtx[mtx_type] for ass_class in mtx: diff --git a/Scripts/tests/unit/test_assignment.py b/Scripts/tests/unit/test_assignment.py index d7627931..c8ac43c1 100644 --- a/Scripts/tests/unit/test_assignment.py +++ b/Scripts/tests/unit/test_assignment.py @@ -20,6 +20,10 @@ def test_assignment(self): "..", "test_data", "Network") scenario_id = 19 context.import_scenario(scenario_dir, scenario_id, "test") + context.create_matrix( + "mf401", "demand_car_first_mile", "demand_car_first_mile") + context.create_matrix( + "mf402", "demand_car_last_mile", "demand_car_last_mile") fares = TransitFareZoneSpecification(pandas.DataFrame({ "fare": { "A": 59, From fb99745aefabdf9a00fab7c910bd4bdb1ddf1f51 Mon Sep 17 00:00:00 2001 From: Jens West Date: Fri, 19 May 2023 15:21:49 +0300 Subject: [PATCH 5/5] Add park mode --- Scripts/assignment/assignment_period.py | 2 +- Scripts/assignment/datatypes/journey_level.py | 36 ++++++++++--------- Scripts/assignment/datatypes/transit.py | 4 +-- Scripts/parameters/assignment.py | 3 +- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Scripts/assignment/assignment_period.py b/Scripts/assignment/assignment_period.py index 0510d158..4594d08b 100644 --- a/Scripts/assignment/assignment_period.py +++ b/Scripts/assignment/assignment_period.py @@ -283,7 +283,7 @@ def _set_car_and_transit_vdfs(self): for modes in param.transit_delay_funcs} main_mode = network.mode(param.main_mode) car_mode = network.mode(param.assignment_modes["car_work"]) - park_and_ride_mode = network.mode(param.park_and_ride_mode) + park_and_ride_mode = network.mode(param.drive_access_mode) for link in network.links(): # Car volume delay function definition linktype = link.type % 100 diff --git a/Scripts/assignment/datatypes/journey_level.py b/Scripts/assignment/datatypes/journey_level.py index 5fe10a83..fbecf868 100644 --- a/Scripts/assignment/datatypes/journey_level.py +++ b/Scripts/assignment/datatypes/journey_level.py @@ -32,36 +32,38 @@ class JourneyLevel: """ def __init__(self, level, headway_attribute, park_and_ride=False, count_zone_boardings=False): - # Boarding transit modes allowed only on levels 0-2 - next = BOARDED if level <= BOARDED else FORBIDDEN - transitions = [{ - "mode": mode, - "next_journey_level": next, - } for mode in param.transit_modes] + transitions = [] if park_and_ride: if "first_mile" in park_and_ride: # Park-and-ride (car) mode allowed only on level 0. car = FORBIDDEN if level >= PARKED else NOT_BOARDED - # If we want parking to be allowed only on specific links - # (i.e., park-and-ride facilities), we should specify an - # own mode for these links. For now, parking is allowed - # on all links where walking to a stop is possible. - walk = PARKED if level == NOT_BOARDED else level + park = PARKED if level == NOT_BOARDED else FORBIDDEN + walk = FORBIDDEN if level == NOT_BOARDED else level + next = BOARDED if level in (PARKED, BOARDED) else FORBIDDEN elif "last_mile" in park_and_ride: # Transfer to park-and-ride (car) mode only allowed after first - # boarding. If we want parking to be allowed only on specific - # links, we should specify an own mode for these links. - # For now, parking is allowed on all links where walking - # from a stop is possible. - car = FORBIDDEN if level in (NOT_BOARDED, FORBIDDEN) else LEFT + # boarding. walk = FORBIDDEN if level == LEFT else level + next = BOARDED if level <= BOARDED else FORBIDDEN + park = LEFT if level == BOARDED else FORBIDDEN + car = LEFT if level == LEFT else FORBIDDEN transitions.append({ - "mode": param.park_and_ride_mode, + "mode": param.drive_access_mode, "next_journey_level": car, }) + transitions.append({ + "mode": param.park_mode, + "next_journey_level": park, + }) else: # Walk modes do not normally affect journey level transitions walk = level + # Boarding transit modes allowed only on levels 0-2 + next = BOARDED if level <= BOARDED else FORBIDDEN + transitions += [{ + "mode": mode, + "next_journey_level": next, + } for mode in param.transit_modes] transitions += [{ "mode": mode, "next_journey_level": walk, diff --git a/Scripts/assignment/datatypes/transit.py b/Scripts/assignment/datatypes/transit.py index 1d0dc33a..5f6c535c 100644 --- a/Scripts/assignment/datatypes/transit.py +++ b/Scripts/assignment/datatypes/transit.py @@ -90,10 +90,10 @@ def __init__(self, segment_results, park_and_ride_results, "performance_settings": param.performance_settings, } if park_and_ride_results: - self.transit_spec["modes"].append(param.park_and_ride_mode) + self.transit_spec["modes"].append(param.drive_access_mode) self.transit_spec["results"] = { "aux_transit_volumes_by_mode": [{ - "mode": param.park_and_ride_mode, + "mode": param.drive_access_mode, "volume": park_and_ride_results, }], } diff --git a/Scripts/parameters/assignment.py b/Scripts/parameters/assignment.py index abef65a4..eb5abdb6 100644 --- a/Scripts/parameters/assignment.py +++ b/Scripts/parameters/assignment.py @@ -467,7 +467,8 @@ 'a', 's', ] -park_and_ride_mode = 'u' +drive_access_mode = 'u' +park_mode = 'x' transit_assignment_modes = transit_modes + aux_modes external_modes = [ "car",