Skip to content

Commit

Permalink
Merge pull request #6 from SchweizerischeBundesbahnen/feature/documen…
Browse files Browse the repository at this point in the history
…tation_sbb

Feature/documentation sbb
  • Loading branch information
manserpa authored Jun 7, 2021
2 parents 28759ad + 1714dc1 commit 24b887f
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The repository ``ABM-in-Visum`` targets to offer a collection of algorithms and
The work on this project started back in 2018 as a collaboration between `Chetan Joshi` from `PTV Group` and `SBB` (`Wolfgang Scherr` and `Patrick Manser`). After SBB released their first fully calibrated activity-based model in 2019, `PTV Group` (initiated and guided by `Klaus Nökel` and `Martin Snethlage`) implemented an efficient data structure for an activity-based demand model in the PTV Visum software, which was released in version 2020. In addition to the data structure, PTV offers the necessary scripts to run a small example within PTV Visum as well as an efficient solution to simulate the computationally expensive location choice step.


``ABM-in-Visum`` publishes the scripts and algorithms which are used in the example model of PTV and the large-scale application of SBB (Switzerland scenario with nearly 10Mio. persons and 8'000 traffic zones). Not published are parameters and inputs of SBBs' model. The goal is to make the models more accessible and it opens the possibility to make contributions for every user.
``ABM-in-Visum`` publishes the scripts and algorithms which are used in the example model of PTV and the large-scale application of SBB. Not published are parameters and inputs of SBBs' model. The goal is to make the models more accessible and it opens the possibility to make contributions for every user.


## Functionality
Expand Down
13 changes: 12 additions & 1 deletion abmvisum/model_contribs/sbb_mobi_plans/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
readme

MOBi.plans: SBBs' activity-based demand model
================

Todo: documentation
1. overview scripts
2. statistics
3. activities
4. procedure
5. example parameters

![Example Schedule](docs/abm_procedure.png "Example schedule in Visum")
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 72 additions & 15 deletions abmvisum/model_contribs/sbb_mobi_plans/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,29 @@
from .core.tour_frequency_choice import run_tour_frequency_choice
from .matrix_cache import MatrixCache

importlib.reload(choice_engine)
importlib.reload(choice_engine) # prevents Visum from caching methods


class MOBiPlansSimulator(StreamHandler):
"""
MOBiPlansSimulator is the central simulator class for an activity-based model. With the initialization, it
loads all necessary parameters which are stored within Visum. It provides all methods to generate
activity-based travel demand for all persons of a synthetic population as stored in the Persons list
in Visum.
Parameters:
Visum: Instance of the PTV Visum software.
seed_val (int): Fixes the random seed to this number.
add_skims_to_cache: Possibility to load all skim matrices as defined in the config into the Python cache.
Attributes:
Visum: Instance of the PTV Visum software.
rand: Random state of numpy.
config (Config): Config object containing all the necessary parameters.
zones: List of all zone numbers.
zoneNo_to_zoneInd: Mapping between zone number and position of the zone index in the matrices.
"""

def __init__(self, Visum, seed_val=42, add_skims_to_cache=True):
self.Visum = Visum
self.rand = np.random.RandomState(seed_val)
Expand All @@ -43,18 +62,32 @@ def initialize_data_and_config(self, add_skims_to_cache=True):
# initialize all filters
self.Visum.Filters.InitAll()

# load zones
# load zones and create mapping to come from zone numbers to indices (for matrix intexing)
self.zones = np.array(self.Visum.Net.Zones.GetMultiAttValues('No'), dtype=int)[:, 1]
self.zoneNo_to_zoneInd = dict(zip(self.zones, range(len(self.zones))))

# init config
self.config = Config(self.Visum, logging, add_skims_to_cache)

def long_term_location_choice(self, cache=None, use_IPF=True, accsib_multimodal_attribute='accsib_mul'):
"""
Simulation of the long-term location choice (work or school place).
It is a two-staged process: Firstly, a traffic zone is chosen. Secondly, the choice of one specific
location within the chosen zone is performed.
Parameters:
cache (MatrixCache): MatrixCache object containing all cached matrices.
use_IPF (bool): Possibility to use the iterative fitting approach or not.
accsib_multimodal_attribute (str): the multimodal accessibility of the long-term location's zone.
Returns:
Updated Visum Persons tables with information about their long-term locations.
"""
logging.info('--- location choice ---')
segments = self.config.load_choice_para('PrimLoc')
accessibility_multi = np.array(self.Visum.Net.Zones.GetMultiAttValues(accsib_multimodal_attribute), dtype=int)[
:, 1]
# the multimodal accessibility of the long-term location is a necessary input for the subtour choice models
accessibility_multi = np.array(self.Visum.Net.Zones.GetMultiAttValues(accsib_multimodal_attribute),
dtype=int)[:, 1]

logging.info('--- initialize long-term locations keys ---')
logging.info('remove existing long term choices')
Expand All @@ -64,18 +97,32 @@ def long_term_location_choice(self, cache=None, use_IPF=True, accsib_multimodal_
self.Visum.Net.Schedules.RemoveAll()
self.Visum.Net.Persons.SetAllAttValues("out_of_home_time", 0.0)

# we use car distances from residence to long-term location as an input for the tour frequency models.
dist_mat = self.config.skim_matrices.get_skim("car_net_distance_sym")

# simulating the long-term location choice step
run_long_term_primary_location_choice(self.Visum, self.config.skim_matrices, segments, self.zones,
self.zoneNo_to_zoneInd, accessibility_multi,
dist_mat, logging, use_IPF, cache)

def plan_generation(self, time_budget_dict=None, out_of_home_budget=18.0):
"""
This method simulates the choice dimensions activity generation, duration and secondary
destinations. It is built as an iterative process which considers time budgets.
Parameters:
time_budget_dict: Dictionary with information about time budgets in specific iterations
out_of_home_budget (float): Total out-of-home budget.
Returns:
Create Visum Tours/ Trips/ ActivityExecutions tables and fills them with all necessary information.
"""
matrix_cache = MatrixCache(logging)

nb_iters = len(time_budget_dict)

for i in range(nb_iters):
logging.info("--- plan adjustment iteration " + str(i + 1) + " ---")
logging.info("--- plan generation iteration " + str(i + 1) + " ---")

active_persons = self.Visum.Net.Persons.GetFilteredSet("[active]=1")
if i == 0:
Expand All @@ -92,6 +139,7 @@ def plan_generation(self, time_budget_dict=None, out_of_home_budget=18.0):
self.Visum.Net.AddMultiSchedules(keys)
else:
filtered_persons = active_persons.GetFilteredSet("[out_of_home_time]>=" + str(out_of_home_budget))
# initializes schedules of all persons that break the out-of-home time budget.
utilities.init_schedules(self.Visum, filtered_persons, logging)

logging.info('--- tour frequency choice ---')
Expand Down Expand Up @@ -126,20 +174,25 @@ def plan_generation(self, time_budget_dict=None, out_of_home_budget=18.0):
def tour_frequency_choice(self, filtered_persons):
segments_prim = self.config.load_choice_para('TourFreqPrim')
for segment in segments_prim:
# reset tour frequency choices
result_attr = segment['ResAttr']
filtered_persons.SetAllAttValues(result_attr, 0)

segments_sec = self.config.load_choice_para('TourFreqSec')
for segment in segments_sec:
# reset tour frequency choices
result_attr = segment['ResAttr']
filtered_persons.SetAllAttValues(result_attr, 0)

# run primary tour frequency
run_tour_frequency_choice(self.Visum, self.rand, filtered_persons, segments_prim, is_primary=True)
# run secondary tour frequency
run_tour_frequency_choice(self.Visum, self.rand, filtered_persons, segments_sec, is_primary=False)

def stop_frequency_choice(self, filtered_tours):
segments = self.config.load_choice_para('TourStopFreq')
for segment in segments:
# reset stop frequency
result_attr = segment['ResAttr']
filtered_tours.SetAllAttValues(result_attr, 0)

Expand All @@ -150,6 +203,7 @@ def stop_frequency_choice(self, filtered_tours):
def subtour_choice(self, filtered_persons, filtered_tours):
segments = self.config.load_choice_para('SubtourFreq')
for segment in segments:
# reset subtour frequency
result_attr = segment['ResAttr']
filtered_tours.SetAllAttValues(result_attr, 0)

Expand All @@ -158,6 +212,7 @@ def subtour_choice(self, filtered_persons, filtered_tours):
choice_engine.run_simple_choice(filtered_tours, segment, self.rand)

logging.info('--- create trips ---')
# create trips and activity excecutions
create_trips(self.Visum, filtered_persons, filtered_tours, self.rand, logging)

def activity_choice(self, filtered_act_ex):
Expand Down Expand Up @@ -196,20 +251,22 @@ def railaccess_choiceset(self):
betas_act = [b for (a, b) in zip(segment['AttrExpr'], segment['Beta']) if a == '0']
acts = [b for (a, b) in zip(segment['AttrExpr'], segment['Comments']) if a == '0']
filtered_subjects = utilities.get_filtered_subjects(subjects, segment['Filter'])
if segment['ResAttr'][-4:]=="_act":
choice_per_subject = [0]*len(filtered_subjects)
if segment['ResAttr'][-4:] == "_act":
choice_per_subject = [0] * len(filtered_subjects)
for (activity, beta) in zip(acts, betas_act):
assert activity in activities, f"{segment['Specification']}, activity {activity} not defined"
bit = 2**activities.index(activity)
choice_per_subject = choice_per_subject + choice_engine.calc_binary_probabilistic_choice_per_subject(filtered_subjects,
['1'],
[beta[0]],
segment['Choices'], self.rand)*bit
bit = 2 ** activities.index(activity)
choice_per_subject = (choice_per_subject +
choice_engine.calc_binary_probabilistic_choice_per_subject(filtered_subjects,
['1'], [beta[0]],
segment['Choices'],
self.rand) * bit)
else:
choice_per_subject = choice_engine.calc_binary_probabilistic_choice_per_subject(filtered_subjects,
att_expr,
[b[0] for b in betas],
segment['Choices'], self.rand)
att_expr,
[b[0] for b in betas],
segment['Choices'],
self.rand)
result_attr = segment['ResAttr']
filtered_subjects.SetAllAttValues(result_attr, 0)
utilities.SetMulti(filtered_subjects, result_attr, choice_per_subject)
Expand Down

0 comments on commit 24b887f

Please sign in to comment.