Skip to content
Open
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
1 change: 1 addition & 0 deletions mobility/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

from mobility.choice_models.population_trips import PopulationTrips
from mobility.choice_models.population_trips_parameters import PopulationTripsParameters
from .simulation_profile import ParameterProfile, SimulationStep


from mobility.transport_graphs.speed_modifier import (
Expand Down
19 changes: 12 additions & 7 deletions mobility/choice_models/destination_sequence_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from scipy.stats import norm

from mobility.choice_models.add_index import add_index
from mobility.simulation_profile import SimulationStep

class DestinationSequenceSampler:
"""Samples destination sequences for trip chains.
Expand All @@ -17,9 +18,9 @@ class DestinationSequenceSampler:
def run(
self,
motives,
step: SimulationStep,
transport_zones,
remaining_sinks,
iteration,
chains,
demand_groups,
costs,
Expand All @@ -39,7 +40,6 @@ def run(
transport_zones: Transport zone container used by motives.
remaining_sinks (pl.DataFrame): Current sink state per (motive, to),
including capacity and saturation utility penalty.
iteration (int): Iteration index (>=1).
chains (pl.DataFrame): Chain steps with
["demand_group_id","motive_seq_id","motive","is_anchor","seq_step_index"].
demand_groups (pl.DataFrame): ["demand_group_id","home_zone_id"] (merged for origins).
Expand All @@ -55,6 +55,7 @@ def run(

utilities = self.get_utilities(
motives,
step,
transport_zones,
remaining_sinks,
costs,
Expand All @@ -64,6 +65,7 @@ def run(
dest_prob = self.get_destination_probability(
utilities,
motives,
step,
parameters.dest_prob_cutoff
)

Expand Down Expand Up @@ -103,13 +105,13 @@ def run(
on=["demand_group_id", "motive_seq_id"]
)
.drop(["home_zone_id", "motive"])
.with_columns(iteration=pl.lit(iteration).cast(pl.UInt32))
.with_columns(iteration=pl.lit(step.iteration).cast(pl.UInt32))
)

return chains


def get_utilities(self, motives, transport_zones, sinks, costs, cost_uncertainty_sd):
def get_utilities(self, motives, step: SimulationStep, transport_zones, sinks, costs, cost_uncertainty_sd):

"""Assemble per-(from,to,motive) utility with cost uncertainty.

Expand All @@ -131,7 +133,7 @@ def get_utilities(self, motives, transport_zones, sinks, costs, cost_uncertainty
- cost_bin_to_dest: ["motive","from","cost_bin","to","p_to"].
"""

utilities = [(m.name, m.get_utilities(transport_zones)) for m in motives]
utilities = [(m.name, m.get_utilities(transport_zones, parameters=m.get_parameters_at_step(step))) for m in motives]
utilities = [u for u in utilities if u[1] is not None]
utilities = [u[1].with_columns(motive=pl.lit(u[0])) for u in utilities]

Expand Down Expand Up @@ -196,7 +198,7 @@ def offset_costs(costs, delta, prob):
return costs_bin, cost_bin_to_dest


def get_destination_probability(self, utilities, motives, dest_prob_cutoff):
def get_destination_probability(self, utilities, motives, step: SimulationStep, dest_prob_cutoff):

"""Compute P(destination | from, motive) via a radiation-style model.

Expand All @@ -220,7 +222,10 @@ def get_destination_probability(self, utilities, motives, dest_prob_cutoff):
costs_bin = utilities[0]
cost_bin_to_dest = utilities[1]

motives_lambda = {motive.name: motive.inputs["parameters"].radiation_lambda for motive in motives}
motives_lambda = {
motive.name: motive.get_parameters_at_step(step).radiation_lambda
for motive in motives
}

prob = (

Expand Down
30 changes: 19 additions & 11 deletions mobility/choice_models/population_trips.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
prune_tmp_artifacts,
rehydrate_congestion_snapshot,
)
from mobility.simulation_profile import SimulationStep

class PopulationTrips(FileAsset):
"""
Expand Down Expand Up @@ -183,8 +184,7 @@ def __init__(
}

super().__init__(inputs, cache_path)



def validate_motives(self, motives: List[Motive]) -> None:

if not motives:
Expand Down Expand Up @@ -303,7 +303,7 @@ def run_model(self, is_weekday: bool) -> Tuple[pl.DataFrame, pl.DataFrame, pl.Da
str(is_weekday),
str(resume_plan.resume_from_iter),
)
tmp_folders = self.prepare_tmp_folders(cache_path, resume=(resume_plan.resume_from_iter is not None))
tmp_folders = self.get_tmp_folders(cache_path, resume=(resume_plan.resume_from_iter is not None))

chains_by_motive, chains, demand_groups = self.state_initializer.get_chains(
population,
Expand All @@ -317,11 +317,15 @@ def run_model(self, is_weekday: bool) -> Tuple[pl.DataFrame, pl.DataFrame, pl.Da
chains_by_motive,
demand_groups
)

step = SimulationStep(iteration=1)
home_motive = [m for m in motives if m.name == "home"][0]

stay_home_state, current_states = self.state_initializer.get_stay_home_state(
demand_groups,
home_night_dur,
motives,
home_motive,
step,
parameters.min_activity_time_constant,
)

Expand Down Expand Up @@ -374,13 +378,14 @@ def run_model(self, is_weekday: bool) -> Tuple[pl.DataFrame, pl.DataFrame, pl.Da
logging.info(f"Iteration n°{iteration}")

seed = self.rng.getrandbits(64)
step = SimulationStep(iteration=iteration)

(
self.destination_sequence_sampler.run(
motives,
step,
population.transport_zones,
remaining_sinks,
iteration,
chains_by_motive,
demand_groups,
costs,
Expand Down Expand Up @@ -409,12 +414,12 @@ def run_model(self, is_weekday: bool) -> Tuple[pl.DataFrame, pl.DataFrame, pl.Da
costs_aggregator,
remaining_sinks,
motive_dur,
iteration,
step,
tmp_folders,
home_night_dur,
stay_home_state,
parameters,
motives
motives,
)
transition_events_per_iter.append(transition_events)

Expand All @@ -430,7 +435,8 @@ def run_model(self, is_weekday: bool) -> Tuple[pl.DataFrame, pl.DataFrame, pl.Da
remaining_sinks = self.state_updater.get_new_sinks(
current_states_steps,
sinks,
motives
motives,
step,
)

# Save per-iteration checkpoint after all state has been advanced.
Expand All @@ -449,14 +455,15 @@ def run_model(self, is_weekday: bool) -> Tuple[pl.DataFrame, pl.DataFrame, pl.Da
# If we resumed after completing all iterations (or start_iteration > n_iterations),
# rebuild step-level flows from cached artifacts for final output.
if "current_states_steps" not in locals():
step = SimulationStep(iteration=parameters.n_iterations)
possible_states_steps = self.state_updater.get_possible_states_steps(
current_states,
demand_groups,
chains_by_motive,
costs_aggregator,
remaining_sinks,
motive_dur,
parameters.n_iterations,
step,
motives,
parameters.min_activity_time_constant,
tmp_folders
Expand Down Expand Up @@ -519,11 +526,12 @@ def remove(self, remove_checkpoints: bool = True):
logging.info("Removed %s checkpoint files for run_key=%s", str(removed), str(run_key))


def prepare_tmp_folders(self, cache_path, resume: bool = False):
"""Create per-run temp folders next to the cache path.
def get_tmp_folders(self, cache_path: pathlib.Path, resume: bool = False) -> dict[str, pathlib.Path]:
"""Return per-run temp folders next to the cache path.

Args:
cache_path (pathlib.Path): Target cache file used to derive temp roots.
resume (bool): When False, clears existing temp folders before reuse.

Returns:
dict[str, pathlib.Path]: Mapping of temp folder names to paths.
Expand Down
24 changes: 19 additions & 5 deletions mobility/choice_models/state_initializer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import math
import polars as pl

from mobility.motives.motive import Motive
from mobility.simulation_profile import SimulationStep

class StateInitializer:
"""Builds initial chain demand, averages, and capacities for the model.

Expand Down Expand Up @@ -272,7 +275,8 @@ def get_stay_home_state(
self,
demand_groups,
home_night_dur,
motives,
home_motive: Motive,
step: SimulationStep,
min_activity_time_constant: float,
):

Expand All @@ -295,8 +299,8 @@ def get_stay_home_state(
- current_states: A clone of `stay_home_state` for iteration start.
"""

home_motive = [m for m in motives if m.name == "home"][0]
value_of_time_stay_home = home_motive.get_parameters_at_step(step).value_of_time_stay_home

stay_home_state = (

demand_groups.select(["demand_group_id", "csp", "n_persons"])
Expand All @@ -309,13 +313,23 @@ def get_stay_home_state(
.join(home_night_dur, on="csp")
.with_columns(
utility=(
home_motive.inputs["parameters"].value_of_time_stay_home
value_of_time_stay_home
* pl.col("mean_home_night_per_pers")
* (pl.col("mean_home_night_per_pers")/pl.col("mean_home_night_per_pers")/math.exp(-min_activity_time_constant)).log().clip(0.0)
)
)

.select(["demand_group_id", "iteration", "motive_seq_id", "mode_seq_id", "dest_seq_id", "utility", "n_persons"])
.select([
"demand_group_id",
"csp",
"mean_home_night_per_pers",
"iteration",
"motive_seq_id",
"mode_seq_id",
"dest_seq_id",
"utility",
"n_persons",
])
)

current_states = (
Expand Down
Loading
Loading