Skip to content

routing_utility

agentevac.agents.routing_utility

Route and destination utility scoring for evacuation agents.

Each agent evaluates a menu of destinations or pre-defined routes by computing an expected utility score. Higher (less negative) scores indicate safer, faster options. The utility function is:

U(option) = -[lambda_e * E(option) + lambda_t * C(option)]
where
  • lambda_e : exposure-aversion weight from the agent's profile (default 1.0).
  • lambda_t : travel-time-aversion weight from the agent's profile (default 0.1).
  • E(option) : expected exposure score (_expected_exposure).
  • C(option) : travel cost in equivalent minutes (_travel_cost).

Expected exposure combines four components (in alert_guided and advice_guided): 1. risk_density : Average edge-level fire risk (risk_sum / len_edges). Normalising by edge count ensures that the same physical road scores identically regardless of SUMO edge granularity. The density is scaled by a severity multiplier that increases with p_risky and p_danger. 2. blocked_edges : Number of edges along the route that are currently inside a fire perimeter. Each blocked edge adds a heavy fixed penalty (8.0) because a blocked edge typically makes the route impassable. 3. margin_penalty : Lookup-table penalty based on the closest fire approach along the route (see _effective_margin_penalty). 4. uncertainty_penalty : A penalty proportional to (1 - confidence) that discourages fragile choices when the agent is unsure of the hazard.

In no_notice mode, agents lack route-specific fire data. Exposure is instead estimated from the agent's general belief state scaled by route length — longer routes mean more time exposed to whatever danger the agent perceives.

Annotated menus (annotate_menu_with_expected_utility) are computed for all three scenarios so the LLM always receives a utility score. The precision of the exposure estimate varies by information regime: belief-only (no_notice), current fire state (alert_guided), or current fire state with full route-head data (advice_guided).

annotate_menu_with_expected_utility(menu, *, mode, belief, psychology, profile, scenario='advice_guided')

Annotate each menu option in-place with its expected utility and component breakdown.

For destination mode, unreachable options (reachable=False) receive expected_utility=None and a minimal component dict to signal their exclusion.

The scenario parameter controls which exposure function is used:

  • "no_notice": _observation_based_exposure — uses only the agent's belief state and route length (no route-specific fire data).
  • "alert_guided" / "advice_guided": _expected_exposure — uses route- specific fire metrics (risk_sum, blocked_edges, min_margin_m).

Parameters:

Name Type Description Default
menu List[Dict[str, Any]]

List of destination or route dicts (mutated in-place).

required
mode str

"destination" or "route" — selects the scoring function.

required
belief Dict[str, Any]

The agent's current Bayesian belief dict.

required
psychology Dict[str, Any]

The agent's current psychology dict.

required
profile Dict[str, Any]

The agent's profile dict (supplies lambda_e, lambda_t).

required
scenario str

Active information regime ("no_notice", "alert_guided", or "advice_guided"). Controls which exposure function is used.

'advice_guided'

Returns:

Type Description
List[Dict[str, Any]]

The same menu list, with each item updated to include: - expected_utility : Scalar utility score or None if unreachable. - utility_components: Dict with lambda_e, lambda_t, expected_exposure, travel_cost (and reachable=False if unreachable).

Source code in agentevac/agents/routing_utility.py
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
def annotate_menu_with_expected_utility(
    menu: List[Dict[str, Any]],
    *,
    mode: str,
    belief: Dict[str, Any],
    psychology: Dict[str, Any],
    profile: Dict[str, Any],
    scenario: str = "advice_guided",
) -> List[Dict[str, Any]]:
    """Annotate each menu option in-place with its expected utility and component breakdown.

    For *destination* mode, unreachable options (``reachable=False``) receive
    ``expected_utility=None`` and a minimal component dict to signal their exclusion.

    The ``scenario`` parameter controls which exposure function is used:

    - ``"no_notice"``: ``_observation_based_exposure`` — uses only the agent's belief
      state and route length (no route-specific fire data).
    - ``"alert_guided"`` / ``"advice_guided"``: ``_expected_exposure`` — uses route-
      specific fire metrics (risk_sum, blocked_edges, min_margin_m).

    Args:
        menu: List of destination or route dicts (mutated in-place).
        mode: ``"destination"`` or ``"route"`` — selects the scoring function.
        belief: The agent's current Bayesian belief dict.
        psychology: The agent's current psychology dict.
        profile: The agent's profile dict (supplies ``lambda_e``, ``lambda_t``).
        scenario: Active information regime (``"no_notice"``, ``"alert_guided"``,
            or ``"advice_guided"``).  Controls which exposure function is used.

    Returns:
        The same ``menu`` list, with each item updated to include:
            - ``expected_utility``  : Scalar utility score or ``None`` if unreachable.
            - ``utility_components``: Dict with lambda_e, lambda_t, expected_exposure,
                                      travel_cost (and ``reachable=False`` if unreachable).
    """
    use_observation_exposure = str(scenario).strip().lower() == "no_notice"
    exposure_fn = _observation_based_exposure if use_observation_exposure else _expected_exposure

    for item in menu:
        if mode == "destination":
            if not item.get("reachable", False):
                # Unreachable destinations get null utility to signal exclusion to the LLM.
                item["expected_utility"] = None
                item["utility_components"] = {
                    "lambda_e": max(0.0, _num(profile.get("lambda_e"), 1.0)),
                    "lambda_t": max(0.0, _num(profile.get("lambda_t"), 0.1)),
                    "reachable": False,
                }
                continue

        lambda_e = max(0.0, _num(profile.get("lambda_e"), 1.0))
        lambda_t = max(0.0, _num(profile.get("lambda_t"), 0.1))
        expected_exposure = exposure_fn(item, belief, psychology)
        travel_cost = _travel_cost(item)
        utility = -((lambda_e * expected_exposure) + (lambda_t * travel_cost))

        item["expected_utility"] = round(utility, 4)
        item["utility_components"] = {
            "lambda_e": round(lambda_e, 4),
            "lambda_t": round(lambda_t, 4),
            "expected_exposure": round(expected_exposure, 4),
            "travel_cost": round(travel_cost, 4),
        }
    return menu

score_destination_utility(menu_item, belief, psychology, profile)

Compute the utility score for a destination option.

Utility is the negative weighted sum of expected exposure and travel cost

U = -(lambda_e * exposure + lambda_t * travel_cost)

Higher (less negative) values indicate preferable destinations.

Parameters:

Name Type Description Default
menu_item Dict[str, Any]

A destination dict from DESTINATION_LIBRARY enriched with risk metrics (risk_sum, blocked_edges, min_margin_m, travel_time_s_fastest_path).

required
belief Dict[str, Any]

The agent's current Bayesian belief dict.

required
psychology Dict[str, Any]

The agent's current psychology dict.

required
profile Dict[str, Any]

The agent's profile dict (supplies lambda_e, lambda_t).

required

Returns:

Type Description
float

Utility score (negative float; higher = better).

Source code in agentevac/agents/routing_utility.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def score_destination_utility(
    menu_item: Dict[str, Any],
    belief: Dict[str, Any],
    psychology: Dict[str, Any],
    profile: Dict[str, Any],
) -> float:
    """Compute the utility score for a destination option.

    Utility is the negative weighted sum of expected exposure and travel cost:
        U = -(lambda_e * exposure + lambda_t * travel_cost)

    Higher (less negative) values indicate preferable destinations.

    Args:
        menu_item: A destination dict from ``DESTINATION_LIBRARY`` enriched with
            risk metrics (risk_sum, blocked_edges, min_margin_m, travel_time_s_fastest_path).
        belief: The agent's current Bayesian belief dict.
        psychology: The agent's current psychology dict.
        profile: The agent's profile dict (supplies ``lambda_e``, ``lambda_t``).

    Returns:
        Utility score (negative float; higher = better).
    """
    lambda_e = max(0.0, _num(profile.get("lambda_e"), 1.0))
    lambda_t = max(0.0, _num(profile.get("lambda_t"), 0.1))
    expected_exposure = _expected_exposure(menu_item, belief, psychology)
    travel_cost = _travel_cost(menu_item)
    return -((lambda_e * expected_exposure) + (lambda_t * travel_cost))

score_route_utility(menu_item, belief, psychology, profile)

Compute the utility score for a route option.

Identical formula to score_destination_utility; kept as a separate function to allow future divergence (e.g., additional route-specific penalty terms).

Parameters:

Name Type Description Default
menu_item Dict[str, Any]

A route dict from ROUTE_LIBRARY enriched with risk metrics.

required
belief Dict[str, Any]

The agent's current Bayesian belief dict.

required
psychology Dict[str, Any]

The agent's current psychology dict.

required
profile Dict[str, Any]

The agent's profile dict (supplies lambda_e, lambda_t).

required

Returns:

Type Description
float

Utility score (negative float; higher = better).

Source code in agentevac/agents/routing_utility.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
def score_route_utility(
    menu_item: Dict[str, Any],
    belief: Dict[str, Any],
    psychology: Dict[str, Any],
    profile: Dict[str, Any],
) -> float:
    """Compute the utility score for a route option.

    Identical formula to ``score_destination_utility``; kept as a separate function
    to allow future divergence (e.g., additional route-specific penalty terms).

    Args:
        menu_item: A route dict from ``ROUTE_LIBRARY`` enriched with risk metrics.
        belief: The agent's current Bayesian belief dict.
        psychology: The agent's current psychology dict.
        profile: The agent's profile dict (supplies ``lambda_e``, ``lambda_t``).

    Returns:
        Utility score (negative float; higher = better).
    """
    lambda_e = max(0.0, _num(profile.get("lambda_e"), 1.0))
    lambda_t = max(0.0, _num(profile.get("lambda_t"), 0.1))
    expected_exposure = _expected_exposure(menu_item, belief, psychology)
    travel_cost = _travel_cost(menu_item)
    return -((lambda_e * expected_exposure) + (lambda_t * travel_cost))