Skip to content
Merged
21 changes: 15 additions & 6 deletions examples/quickstart-fr.py.py → examples/quickstart-fr.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
dotenv.load_dotenv()

mobility.set_params(
# package_data_folder_path=os.environ["MOBILITY_PACKAGE_DATA_FOLDER"],
# project_data_folder_path=os.environ["MOBILITY_PROJECT_DATA_FOLDER"]
package_data_folder_path="D:/mobility-data",
project_data_folder_path="D:/test-09",
package_data_folder_path=os.environ["MOBILITY_PACKAGE_DATA_FOLDER"],
project_data_folder_path=os.environ["MOBILITY_PROJECT_DATA_FOLDER"]
)

# Using Foix (a small town) and a limited radius for quick results
Expand All @@ -21,10 +19,21 @@
# Creating a synthetic population of 1000 for the area
pop = mobility.Population(transport_zones, sample_size = 1000)

# Simulating the trips for this population for three modes : car, walk and bicyle, and only home and work motives (OtherMotive is mandatory)
car_mode = mobility.CarMode(transport_zones)
walk_mode = mobility.WalkMode(transport_zones)
bicycle_mode = mobility.BicycleMode(transport_zones)
mode_registry = mobility.ModeRegistry(
[car_mode, walk_mode, bicycle_mode]
)
public_transport_mode = mobility.PublicTransportMode(
transport_zones,
mode_registry=mode_registry
)

# Simulating the trips for this population for three modes : car, walk, bicyle and public transport, and only home and work motives (OtherMotive is mandatory)
pop_trips = mobility.PopulationTrips(
pop,
[mobility.CarMode(transport_zones), mobility.WalkMode(transport_zones), mobility.BicycleMode(transport_zones)],
[car_mode, walk_mode, bicycle_mode, public_transport_mode],
[mobility.HomeMotive(), mobility.WorkMotive(), mobility.OtherMotive(population=pop)],
[emp]
)
Expand Down
1 change: 1 addition & 0 deletions mobility/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from mobility.transport_modes.carpool import CarpoolMode
from mobility.transport_modes.public_transport import PublicTransportMode
from mobility.transport_modes.modal_transfer import IntermodalTransfer
from mobility.transport_modes.mode_registry import ModeRegistry

from .path_routing_parameters import PathRoutingParameters

Expand Down
3 changes: 0 additions & 3 deletions mobility/transport_modes/modal_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,3 @@ def default_intermodal_transfer_for_mode(
f"No default IntermodalTransfer is defined for mode '{mode_name}'"
+ (f" (vehicle='{vehicle}')." if vehicle is not None else ".")
)



57 changes: 57 additions & 0 deletions mobility/transport_modes/mode_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from typing import List

from mobility.transport_modes.transport_mode import TransportMode


class ModeRegistry:
"""Register transport modes by id."""

def __init__(
self,
modes: List[TransportMode],
):
"""Build a mode registry from a list of transport mode instances.

Args:
modes: Available transport modes. Mode ids are derived from
``mode.inputs['parameters'].name`` and must be unique.

Raises:
ValueError: If the list is empty or contains duplicate mode names.
TypeError: If a list item is not a ``TransportMode``.
"""
if not modes:
raise ValueError("ModeRegistry requires at least one mode.")

self._modes = {}
for mode in modes:
if not isinstance(mode, TransportMode):
raise TypeError(
"ModeRegistry expects TransportMode instances, "
f"got {type(mode)}."
)
mode_id = self._mode_id(mode)
if mode_id in self._modes:
raise ValueError(
f"Duplicate mode id '{mode_id}' in ModeRegistry input."
)
self._modes[mode_id] = mode

def get(self, mode_id: str) -> TransportMode:
"""Return a registered mode by id."""
if mode_id not in self._modes:
raise ValueError(
f"Unknown mode id '{mode_id}'. Available mode ids: {list(self._modes.keys())}."
)
return self._modes[mode_id]

@staticmethod
def _mode_id(mode: TransportMode) -> str:
"""Resolve mode id from TransportMode parameters."""
try:
return mode.inputs["parameters"].name
except Exception as exc:
raise ValueError(
f"Could not resolve mode id for {type(mode)}. Expected "
"`mode.inputs['parameters'].name`."
) from exc
133 changes: 76 additions & 57 deletions mobility/transport_modes/public_transport/public_transport_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from mobility.transport_zones import TransportZones
from mobility.transport_modes.transport_mode import TransportMode, TransportModeParameters
from mobility.transport_modes.mode_registry import ModeRegistry
from mobility.transport_modes.public_transport.public_transport_graph import PublicTransportRoutingParameters
from mobility.transport_modes.public_transport.public_transport_travel_costs import PublicTransportTravelCosts
from mobility.transport_modes.public_transport.public_transport_generalized_cost import PublicTransportGeneralizedCost
Expand All @@ -15,40 +16,55 @@
from mobility.cost_of_time_parameters import CostOfTimeParameters
from pydantic import Field


DEFAULT_PUBLIC_TRANSPORT_SURVEY_IDS = [
"4.42",
"4.43",
"5.50",
"5.51",
"5.52",
"5.53",
"5.54",
"5.55",
"5.56",
"5.57",
"5.58",
"5.59",
"6.60",
"6.61",
"6.62",
"6.63",
"6.69",
]


class PublicTransportMode(TransportMode):
"""
A class for public transport mode, including different modes for first and last legs.
Modes such as this one should be defined in the code before being called by models

Args:
transport_zones (gpd.GeoDataFrame): GeoDataFrame containing transport zone geometries.
first_leg_mode: mode for the first leg mode, such as walk, car or bicycle
last_leg_mode: mode for the last leg
first_modal_transfer: transfer parameters between the first mode and public transport
last_modal_transfer: transfer parameters between public transport and the last mode
routing_parameters: PublicTransportRoutingParameters
generalized_cost_parameters: GeneralizedCostParameters

"""

"""Public transport mode with configurable access and egress leg modes."""

def __init__(
self,
transport_zones: TransportZones,
first_leg_mode: TransportMode,
last_leg_mode: TransportMode,
first_intermodal_transfer: IntermodalTransfer = None,
last_intermodal_transfer: IntermodalTransfer = None,
first_leg_mode: TransportMode | None = None,
last_leg_mode: TransportMode | None = None,
mode_registry: ModeRegistry | None = None,
first_intermodal_transfer: IntermodalTransfer | None = None,
last_intermodal_transfer: IntermodalTransfer | None = None,
routing_parameters: PublicTransportRoutingParameters | None = None,
generalized_cost_parameters: GeneralizedCostParameters = None,
generalized_cost_parameters: GeneralizedCostParameters | None = None,
survey_ids: List[str] | None = None,
ghg_intensity: float | None = None,
parameters: "PublicTransportModeParameters | None" = None,
):

if first_leg_mode is None or last_leg_mode is None:
raise ValueError(
"PublicTransportMode requires both `first_leg_mode` and `last_leg_mode`."
)
first_leg_mode = self._resolve_leg_mode(
leg_mode=first_leg_mode,
mode_registry=mode_registry,
leg_label="first_leg_mode",
)
last_leg_mode = self._resolve_leg_mode(
leg_mode=last_leg_mode,
mode_registry=mode_registry,
leg_label="last_leg_mode",
)

if first_intermodal_transfer is None:
first_intermodal_transfer = default_intermodal_transfer_for_mode(
Expand All @@ -64,28 +80,28 @@ def __init__(

if routing_parameters is None:
routing_parameters = PublicTransportRoutingParameters()

travel_costs = PublicTransportTravelCosts(
transport_zones,
routing_parameters,
first_leg_mode,
last_leg_mode,
first_intermodal_transfer,
last_intermodal_transfer
last_intermodal_transfer,
)

congestion = (
first_leg_mode.inputs["parameters"].congestion
or last_leg_mode.inputs["parameters"].congestion
)

if generalized_cost_parameters is None:
generalized_cost_parameters = GeneralizedCostParameters(
cost_constant=0.0,
cost_of_distance=0.1,
cost_of_time=CostOfTimeParameters()
cost_of_time=CostOfTimeParameters(),
)

generalized_cost = PublicTransportGeneralizedCost(
travel_costs,
first_leg_mode_name=first_leg_mode.inputs["parameters"].name,
Expand All @@ -94,14 +110,14 @@ def __init__(
mid_parameters=generalized_cost_parameters,
last_parameters=last_leg_mode.inputs["generalized_cost"].inputs["parameters"],
)

name = (
first_leg_mode.inputs["parameters"].name
+ "/public_transport/"
+ last_leg_mode.inputs["parameters"].name
)
vehicle = first_leg_mode.inputs["parameters"].vehicle

if last_leg_mode.inputs["parameters"].name != first_leg_mode.inputs["parameters"].name:
return_mode_name = (
last_leg_mode.inputs["parameters"].name
Expand All @@ -110,7 +126,7 @@ def __init__(
)
else:
return_mode_name = None

super().__init__(
name,
travel_costs,
Expand All @@ -123,37 +139,40 @@ def __init__(
survey_ids=survey_ids,
parameters=parameters,
parameters_cls=PublicTransportModeParameters,
)
)

def audit_gtfs(self):
logging.info("Auditing GTFS for this mode")
travel_costs = self.inputs["travel_costs"].audit_gtfs()
return travel_costs

@staticmethod
def _resolve_leg_mode(
leg_mode: TransportMode | None,
mode_registry: ModeRegistry | None,
leg_label: str,
) -> TransportMode:
if leg_mode is not None:
return leg_mode

if mode_registry is None:
raise ValueError(
"PublicTransportMode requires explicit `first_leg_mode` and "
"`last_leg_mode`, or a `mode_registry` to resolve defaults "
"(example: ModeRegistry([walk_mode, ...]))."
)

resolved_mode = mode_registry.get("walk")
if resolved_mode is None:
raise ValueError(
f"Could not resolve {leg_label} with mode id 'walk'."
)
return resolved_mode


class PublicTransportModeParameters(TransportModeParameters):
"""Parameters for public transport mode."""

ghg_intensity: float = 0.05
multimodal: bool = True
survey_ids: list[str] = Field(
default_factory=lambda: [
"4.42",
"4.43",
"5.50",
"5.51",
"5.52",
"5.53",
"5.54",
"5.55",
"5.56",
"5.57",
"5.58",
"5.59",
"6.60",
"6.61",
"6.62",
"6.63",
"6.69",
]
)
survey_ids: list[str] = Field(default_factory=lambda: list(DEFAULT_PUBLIC_TRANSPORT_SURVEY_IDS))
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,18 @@ def test_008_population_trips_can_be_computed(test_data, safe_json):
sample_size=test_data["population_sample_size"],
)

car_mode = mobility.CarMode(transport_zones)
walk_mode = mobility.WalkMode(transport_zones)
bicycle_mode = mobility.BicycleMode(transport_zones)
mode_registry = mobility.ModeRegistry([car_mode, walk_mode, bicycle_mode])
public_transport_mode = mobility.PublicTransportMode(
transport_zones,
mode_registry=mode_registry,
)

pop_trips = PopulationTrips(
population=pop,
modes=[mobility.CarMode(transport_zones)],
modes=[car_mode, walk_mode, bicycle_mode, public_transport_mode],
motives=[
HomeMotive(),
WorkMotive(),
Expand Down
Loading