EMME Manager Flow Documentation ¶
Overview ¶
The EmmeManager is tm2py’s interface to the INRO EMME API. It manages EMME project connections, database access, and provides tools for network operations.
Architecture Hierarchy ¶
RunController
└── EmmeManager (singleton per project)
├── EmmeProject (Desktop API wrapper)
├── highway_emmebank (ProxyEmmebank - lazy)
├── transit_emmebank (ProxyEmmebank - lazy)
├── active_north_emmebank (ProxyEmmebank - lazy)
└── active_south_emmebank (ProxyEmmebank - lazy)
Initialization Flow ¶
1. RunController Creation ¶
# In tests/highway_assign_skim_controller.py or tm2py/controller.py
controller = RunController(config_file, run_dir)
2. EmmeManager Initialization (Lazy) ¶
When a component first accesses controller.emme_manager:
# tm2py/controller.py line 232
@property
def emme_manager(self):
if self._emme_manager is None:
self._emme_manager = EmmeManager(self, self.config.emme)
return self._emme_manager
3. EmmeManager.init() - Project Setup ¶
# tm2py/emme/manager.py line 446
def __init__(self, controller, emme_config):
# 1. Store controller reference and config
self.controller = controller
self.config = emme_config
# 2. Get absolute project path
project_path = controller.get_abs_path(config.project_path)
# 3. Call parent EmmeProject.__init__()
# This starts the EMME Desktop application
super().__init__(project_path)
# 4. Resolve all database paths to absolute
self.highway_database_path = get_abs_path(config.highway_database_path)
self.transit_database_path = get_abs_path(config.transit_database_path)
self.active_north_database_path = get_abs_path(config.active_north_database_path)
self.active_south_database_path = get_abs_path(config.active_south_database_path)
# 5. Initialize lazy properties to None
self._highway_emmebank = None
self._transit_emmebank = None
self._active_north_emmebank = None
self._active_south_emmebank = None
4. EmmeProject.init() - EMME Desktop Startup ¶
# tm2py/emme/manager.py line 200
def __init__(self, project_path, emmebank_path=None):
# Check if project already open in _EMME_PROJECT_REGISTRY
if project_path in _EMME_PROJECT_REGISTRY:
return existing_project
# Start EMME Desktop API
from inro.emme.desktop.app import start_dedicated
self._project = start_dedicated(
project=project_path,
visible=False, # No GUI
user_initials="TM2PY"
)
# Register in global registry
_EMME_PROJECT_REGISTRY[project_path] = self
Database Access Flow (Lazy Loading) ¶
Component Requests Database ¶
# Example: tm2py/components/network/highway/highway_network.py line 101
@property
def highway_emmebank(self):
if not self._highway_emmebank:
self._highway_emmebank = self.controller.emme_manager.highway_emmebank
return self._highway_emmebank
EmmeManager Creates ProxyEmmebank (First Access Only) ¶
# tm2py/emme/manager.py line 482
@property
def highway_emmebank(self):
if self._highway_emmebank is None:
self._highway_emmebank = ProxyEmmebank(self, self.highway_database_path)
return self._highway_emmebank
ProxyEmmebank.init() - Database Connection ¶
# tm2py/emme/manager.py line 95
def __init__(self, emme_manager, path):
self.emme_manager = emme_manager
self.path = path
# Open the Emmebank database
self._emmebank = emme_manager.project.open_database(path)
# Get the desktop object for tools
self._desktop = self._emmebank.desktop
Key Concepts ¶
Lazy Initialization ¶
- Databases are only opened when first accessed
- This saves resources - if a component doesn’t need transit, that database never opens
- Pattern:
if self._database is None: self._database = connect()
ProxyEmmebank Wrapper ¶
Wraps the raw INRO Emmebank with convenience methods:
- scenario(time_period) - Get scenario by time period name (e.g., “AM”)
- tool(name) - Access EMME modeller tools
- emmebank - Access raw INRO Emmebank object
- create_zero_matrix() - Initialize zero-valued matrices
Singleton Pattern ¶
- One EmmeProject per project path (stored in
_EMME_PROJECT_REGISTRY) - One EmmeManager per RunController
- Multiple ProxyEmmebanks (one per database type)
Typical Component Flow ¶
PrepareNetwork (Highway) ¶
1. Component.__init__()
└── self.controller = controller
2. Component.run()
└── scenario = self.highway_emmebank.scenario("AM")
│
├── Triggers: self.highway_emmebank property
│ └── Triggers: controller.emme_manager property
│ ├── Creates: EmmeManager(controller, config.emme)
│ │ ├── Starts EMME Desktop API
│ │ └── Resolves database paths
│ │
│ └── Returns: emme_manager instance
│
└── Triggers: emme_manager.highway_emmebank property
├── Creates: ProxyEmmebank(manager, highway_database_path)
│ └── Opens: EMME database at path
│
└── Returns: proxy_emmebank instance
Why active_north Opens First ¶
The error message showed active_north opening first because:
1. Config validation reads ALL database paths during init
2. The paths are resolved using get_abs_path() in order
3. If a path is invalid/missing, it fails on the FIRST problematic path
4. In our case: config had relative paths like "emme_project/Database_active_north"
5. RunController tried to resolve it relative to the run directory
6. Active_north was listed before highway in the code order
Solution: Use absolute paths in config so resolution is deterministic
Database Types in tm2py ¶
| Database | Purpose | Used By |
|---|---|---|
highway |
Highway network, assignment, skims | PrepareNetwork, HighwayAssignment, HighwayMAZ |
transit |
Transit network, assignment, skims | PrepareTransitNetwork, TransitAssignment |
active_north |
Active modes (walk/bike) - North region | ActiveModes component |
active_south |
Active modes (walk/bike) - South region | ActiveModes component |
All databases are separate EMME databanks with independent networks and scenarios.
Config Requirements ¶
Minimal (Highway-Only Test) ¶
[emme]
project_path = "E:/path/to/emme_project/project.emp"
highway_database_path = "E:/path/to/emme_project/Database_highway"
# Others still required but won't be opened:
active_north_database_path = "E:/path/to/emme_project/Database_active_north"
active_south_database_path = "E:/path/to/emme_project/Database_active_south"
transit_database_path = "E:/path/to/emme_project/Database_transit"
all_day_scenario_id = 100
num_processors = 1
Path Resolution Rules ¶
- Absolute paths: Used as-is (e.g.,
"E:/Data/emme_project") - Relative paths: Resolved relative to
run_dir(the directory with config files) - Best practice: Always use absolute paths for EMME databases to avoid ambiguity
Tool Access ¶
Components access EMME tools through the manager:
# Get an EMME modeller tool
create_attribute = controller.emme_manager.tool(
"inro.emme.data.extra_attribute.create_extra_attribute"
)
# Use the tool
create_attribute(
extra_attribute_type="LINK",
extra_attribute_name="@capacity",
extra_attribute_description="Link capacity",
overwrite=True,
scenario=scenario
)
Cleanup ¶
EmmeManager handles cleanup automatically: - Desktop API connections are closed when Python exits - Databases are closed when the EmmeProject is destroyed - No manual cleanup required
Common Issues and Solutions ¶
KeyError: ‘@useclass’ ¶
- Cause: Missing
create_tod_scenarioscomponent - it copies time-period-specific attributes - Fix: Add
"create_tod_scenarios"to component list BEFORE"prepare_network_highway" - Details: Base scenario has
@useclass_am, etc. TOD scenarios need generic@useclass
KeyError: ‘am’ (scenario lookup) ¶
- Cause: Case mismatch -
scenario_dictbuilt with uppercase keys (“AM”) but lookup used.lower() - Fix: Removed
.lower()from line 146 in tm2py/emme/manager.py - Details:
scenario_dictkeys come fromconfig.time_periods[].name(uppercase “AM”)
“Invalid EMME project file” ¶
- Cause: Relative path in config, resolved to wrong location
- Fix: Use absolute paths (e.g.,
E:/2015_TM2_20250619/emme_project/Database_highway/emmebank)
Database not found ¶
- Cause: Path doesn’t exist or is incorrect
- Fix: Verify database path exists and contains
emmebankfile
Multiple processes conflict ¶
- Cause: Multiple RunControllers trying to open same project
- Fix: Use
num_processorsconfig and let tm2py manage parallelism
EMME Desktop opens all databases (slow startup) ¶
- Cause: The .emp project file lists all databases in
OpenDatabasesandProjectDatabases[] - Not fixable in Python: EMME Desktop loads databases based on .emp file, not code requests
- Workaround: Create separate .emp files for different workflows (highway-only, transit-only)