Skip to content

Add framework for park-and-ride network assignment #470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: olusanya
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions Scripts/assignment/assignment_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -185,8 +193,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],
Expand Down Expand Up @@ -275,6 +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.drive_access_mode)
for link in network.links():
# Car volume delay function definition
linktype = link.type % 100
Expand Down Expand Up @@ -335,9 +344,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):
Expand Down Expand Up @@ -502,6 +511,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
Expand All @@ -515,6 +526,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]
Expand Down Expand Up @@ -555,8 +568,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])
Expand Down Expand Up @@ -754,11 +767,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)))

Expand All @@ -778,7 +795,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,
Expand Down
103 changes: 80 additions & 23 deletions Scripts/assignment/datatypes/journey_level.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,100 @@
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
"""
Journey level specification for transit assignment.

Parameters
----------
level : int
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)
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):
transitions = []
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
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.
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": mode,
"next_journey_level": 1
"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,
} 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,
"spread_factor": 1,
"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"] = {
Expand Down
41 changes: 24 additions & 17 deletions Scripts/assignment/datatypes/transit.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -38,17 +43,17 @@ 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,
"perception_factor": 1,
}
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,
Expand Down Expand Up @@ -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.drive_access_mode)
self.transit_spec["results"] = {
"aux_transit_volumes_by_mode": [{
"mode": param.drive_access_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,
Expand All @@ -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"],
Expand All @@ -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

Loading