Skip to content

Commit fb89719

Browse files
committed
Add event hooks
1 parent f298c1a commit fb89719

File tree

4 files changed

+46
-12
lines changed

4 files changed

+46
-12
lines changed

Scripts/assignment/emme_assignment.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
2-
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, cast
2+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
33
import pandas
44
from math import log10
55

@@ -10,12 +10,14 @@
1010
import parameters.zone as zone_param
1111
from assignment.abstract_assignment import AssignmentModel
1212
from assignment.assignment_period import AssignmentPeriod
13+
from events.model_system_event_listener import EventHandler
1314
if TYPE_CHECKING:
1415
from assignment.emme_bindings.emme_project import EmmeProject
1516
from assignment.datatypes.transit_fare import TransitFareZoneSpecification
1617
from datahandling.resultdata import ResultsData
1718
from inro.emme.database.scenario import Scenario # type: ignore
1819
from inro.emme.network.Network import Network # type: ignore
20+
import numpy
1921

2022

2123
class EmmeAssignmentModel(AssignmentModel):
@@ -46,6 +48,7 @@ class EmmeAssignmentModel(AssignmentModel):
4648
def __init__(self,
4749
emme_context: EmmeProject,
4850
first_scenario_id: int,
51+
event_handler: EventHandler=None,
4952
separate_emme_scenarios: bool=False,
5053
save_matrices: bool=False,
5154
time_periods: List[str]=param.time_periods,
@@ -57,6 +60,7 @@ def __init__(self,
5760
self.emme_project = emme_context
5861
self.mod_scenario = self.emme_project.modeller.emmebank.scenario(
5962
first_scenario_id)
63+
self._event_handler = event_handler
6064

6165
def prepare_network(self,
6266
car_dist_unit_cost: Optional[float]=None):
@@ -112,7 +116,7 @@ def prepare_network(self,
112116
self.emme_project.create_extra_function_parameters(el1="@kaltevuus")
113117

114118
def init_assign(self,
115-
demand: Dict[str,List[numpy.ndarray]]):
119+
demand: Dict[str,List['numpy.ndarray']]):
116120
"""??? types"""
117121
ap0 = self.assignment_periods[0]
118122
ap0.assign(demand, iteration="init")
@@ -253,8 +257,8 @@ def aggregate_results(self, resultdata: ResultsData):
253257

254258
def calc_transit_cost(self,
255259
fares: TransitFareZoneSpecification,
256-
peripheral_cost: numpy.ndarray,
257-
default_cost: numpy.ndarray = None):
260+
peripheral_cost: 'numpy.ndarray',
261+
default_cost: 'numpy.ndarray' = None):
258262
"""Calculate transit zone cost matrix.
259263
260264
Perform multiple transit assignments.
@@ -391,7 +395,6 @@ def _create_attributes(self,
391395
# Create link attributes
392396
ass_classes = list(param.emme_matrices) + ["bus"]
393397
ass_classes.remove("walk")
394-
if TYPE_CHECKING: scenario = cast(Scenario, scenario)
395398
for ass_class in ass_classes:
396399
self.emme_project.create_extra_attribute(
397400
"LINK", extra(ass_class), ass_class + " volume",

Scripts/helmet.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import utils.log as log
99
from assignment.emme_assignment import EmmeAssignmentModel
1010
from assignment.mock_assignment import MockAssignmentModel
11+
from events.model_system_event_listener import EventHandler
1112
from modelsystem import ModelSystem, AgentModelSystem
1213
from datahandling.matrixdata import MatrixData
1314

@@ -56,6 +57,11 @@ def main(args):
5657
estimation_data_path = Path(results_path) / args.scenario_name / 'estimation'
5758
estimation_data_path.mkdir(parents=True, exist_ok=True)
5859

60+
# Initialize event handler and load event listeners
61+
event_handler = EventHandler()
62+
# Load event listeners from 'events/examples' folder
63+
event_handler.load_listeners(Path(__file__).parent / 'events' / 'examples')
64+
5965
# Choose and initialize the Traffic Assignment (supply)model
6066
if args.do_not_use_emme:
6167
log.info("Initializing MockAssignmentModel...")
@@ -76,6 +82,7 @@ def main(args):
7682
ass_model = EmmeAssignmentModel(
7783
EmmeProject(emme_project_path),
7884
first_scenario_id=args.first_scenario_id,
85+
event_handler=event_handler,
7986
separate_emme_scenarios=args.separate_emme_scenarios,
8087
save_matrices=args.save_matrices,
8188
first_matrix_id=args.first_matrix_id)
@@ -90,7 +97,8 @@ def main(args):
9097
else:
9198
model = ModelSystem(
9299
forecast_zonedata_path, base_zonedata_path, base_matrices_path,
93-
results_path, ass_model, args.scenario_name, estimation_data_path)
100+
results_path, ass_model, args.scenario_name, event_handler,
101+
estimation_data_path)
94102
log_extra["status"]["results"] = model.mode_share
95103

96104
# Run traffic assignment simulation for N iterations,
@@ -128,7 +136,8 @@ def main(args):
128136
log_extra["status"]["converged"] = 1
129137
i += 1
130138

131-
if not log_extra["status"]["converged"]: log.warn("Model has not converged")
139+
if not log_extra["status"]["converged"]:
140+
log.warn("Model has not converged")
132141

133142
# delete emme strategy files for scenarios
134143
if args.del_strat_files:

Scripts/modelsystem.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from datatypes.tour import Tour
2727
from transform.impedance_transformer import ImpedanceTransformer
2828
from models.linear import CarDensityModel
29+
from events.model_system_event_listener import EventHandler
2930
import parameters.assignment as param
3031
import parameters.zone as zone_param
3132
import parameters.tour_generation as gen_param
@@ -58,7 +59,9 @@ def __init__(self,
5859
results_path: str,
5960
assignment_model: AssignmentModel,
6061
name: str,
62+
event_handler: EventHandler,
6163
estimation_data_path: Path = None):
64+
self.event_handler = event_handler
6265
self.ass_model = cast(Union[MockAssignmentModel,EmmeAssignmentModel], assignment_model) #type checker hint
6366
self.zone_numbers: numpy.array = self.ass_model.zone_numbers
6467
self.travel_modes: Dict[str, bool] = {} # Dict instead of set, to preserve order
@@ -73,7 +76,8 @@ def __init__(self,
7376
if estimation_data_path:
7477
self.zdata_base.export_data(estimation_data_path / 'zonedata_base.csv')
7578
self.zdata_forecast.export_data(estimation_data_path / 'zonedata_forecast.csv')
76-
79+
self.event_handler.on_zone_data_loaded(self.zdata_base, self.zdata_forecast)
80+
7781
# Output data
7882
self.resultmatrices = MatrixData(
7983
os.path.join(results_path, name, "Matrices"))
@@ -98,6 +102,7 @@ def __init__(self,
98102
self.convergence = []
99103
self.trucks = self.fm.calc_freight_traffic("truck")
100104
self.trailer_trucks = self.fm.calc_freight_traffic("trailer_truck")
105+
self.event_handler.on_model_system_initialized(self)
101106

102107
def _init_demand_model(self):
103108
return DemandModel(self.zdata_forecast, self.resultdata, is_agent_model=False)
@@ -127,6 +132,7 @@ def _add_internal_demand(self, previous_iter_impedance, is_last_iteration, estim
127132
# as logsums from probability calculation are used in tour generation.
128133
self.dm.create_population_segments()
129134
saved_pnr_impedance = {}
135+
self.event_handler.on_population_segments_created(self.dm)
130136
for purpose in self.dm.tour_purposes:
131137
if isinstance(purpose, SecDestPurpose):
132138
purpose.gen_model.init_tours()
@@ -143,6 +149,7 @@ def _add_internal_demand(self, previous_iter_impedance, is_last_iteration, estim
143149

144150
# Tour generation
145151
self.dm.generate_tours()
152+
self.event_handler.on_demand_model_tours_generated(self.dm)
146153

147154
# Assigning of tours to mode, destination and time period
148155
for purpose in self.dm.tour_purposes:
@@ -172,6 +179,7 @@ def _add_internal_demand(self, previous_iter_impedance, is_last_iteration, estim
172179
break
173180
log.debug(f"Park and ride demand calculation completed.")
174181

182+
self.event_handler.on_purpose_demand_calculated(purpose, demand)
175183
if purpose.dest != "source":
176184
for mode in demand:
177185
self.dtm.add_demand(demand[mode])
@@ -249,9 +257,13 @@ def assign_base_demand(self,
249257
self._calculate_noise_areas()
250258
self.resultdata.flush()
251259
self.dtm.init_demand()
260+
self.event_handler.on_base_demand_assigned(impedance)
252261
return impedance
253262

254-
def run_iteration(self, previous_iter_impedance, iteration=None, estimation_mode=False):
263+
def run_iteration(self,
264+
previous_iter_impedance: Dict[str, Dict[str, numpy.ndarray]],
265+
iteration: Union[int, str] = None,
266+
estimation_mode=False):
255267
"""Calculate demand and assign to network.
256268
257269
Parameters
@@ -281,6 +293,7 @@ def run_iteration(self, previous_iter_impedance, iteration=None, estimation_mode
281293
value : numpy.ndarray
282294
Impedance (float 2-d matrix)
283295
"""
296+
self.event_handler.on_iteration_started(iteration, previous_iter_impedance)
284297
impedance = {}
285298

286299
# Add truck and trailer truck demand, to time-period specific
@@ -292,9 +305,11 @@ def run_iteration(self, previous_iter_impedance, iteration=None, estimation_mode
292305
prediction = self.cdm.predict()
293306
self.zdata_forecast["car_density"] = prediction
294307
self.zdata_forecast["cars_per_1000"] = 1000 * prediction
308+
self.event_handler.on_car_density_updated(iteration, prediction)
295309

296310
# Calculate internal demand
297311
self._add_internal_demand(previous_iter_impedance, iteration=="last", estimation_mode)
312+
self.event_handler.on_internal_demand_added(self.dtm)
298313

299314
# Calculate external demand
300315
for mode in param.external_modes:
@@ -310,8 +325,9 @@ def run_iteration(self, previous_iter_impedance, iteration=None, estimation_mode
310325
else:
311326
int_demand = self._sum_trips_per_zone(mode)
312327
ext_demand = self.em.calc_external(mode, int_demand)
328+
self.event_handler.on_external_demand_calculated(ext_demand)
313329
self.dtm.add_demand(ext_demand)
314-
330+
315331
# Calculate tour sums and mode shares
316332
tour_sum = {mode: self._sum_trips_per_zone(mode, include_dests=False)
317333
for mode in self.travel_modes}
@@ -343,11 +359,14 @@ def run_iteration(self, previous_iter_impedance, iteration=None, estimation_mode
343359
if iteration=="last":
344360
self._save_demand_to_omx(ap.name)
345361

362+
self.event_handler.on_demand_calculated(iteration, self.dtm)
363+
346364
# Calculate and return traffic impedance
347365
for ap in self.ass_model.assignment_periods:
348366
tp = ap.name
349367
log.info("Assigning period " + tp)
350368
impedance[tp] = ap.assign(self.dtm.demand[tp], iteration)
369+
self.event_handler.on_time_period_assigned(iteration, ap, impedance[tp])
351370
if tp == "aht":
352371
self._update_ratios(impedance[tp], tp)
353372
if iteration=="last": # TODO: Get uncongested time from assignment results
@@ -373,6 +392,7 @@ def run_iteration(self, previous_iter_impedance, iteration=None, estimation_mode
373392
self.convergence.append(gap)
374393
self.resultdata._df_buffer["demand_convergence.txt"] = pandas.DataFrame(self.convergence)
375394
self.resultdata.flush()
395+
self.event_handler.on_iteration_complete(iteration, impedance, gap)
376396
return impedance
377397

378398
def _save_demand_to_omx(self, tp):

Scripts/tests/integration/test_models.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import numpy
33

44
from datahandling.zonedata import ZoneData
5+
from events.model_system_event_listener import EventHandler
56
import utils.log as log
67
from modelsystem import ModelSystem, AgentModelSystem
78
from assignment.mock_assignment import MockAssignmentModel
@@ -35,7 +36,8 @@ def test_models(self):
3536
TEST_DATA_PATH, "Base_input_data", "base_matrices")
3637
model = ModelSystem(
3738
zone_data_path, base_zone_data_path, base_matrices_path,
38-
results_path, ass_model, "test")
39+
results_path, ass_model, "test",
40+
EventHandler())
3941
impedance = model.assign_base_demand()
4042
for ap in ass_model.assignment_periods:
4143
tp = ap.name
@@ -71,7 +73,7 @@ def test_agent_model(self):
7173
TEST_DATA_PATH, "Base_input_data", "base_matrices")
7274
model = AgentModelSystem(
7375
zone_data_path, base_zone_data_path, base_matrices_path,
74-
results_path, ass_model, "test")
76+
results_path, ass_model, "test", EventHandler())
7577
impedance = model.assign_base_demand()
7678
impedance = model.run_iteration(impedance)
7779
impedance = model.run_iteration(impedance, "last")

0 commit comments

Comments
 (0)