fleetmind / src /delivery_dispatch /scenarios.py
Rishav
Refine dynamic task pacing
f62c8f0
Raw
History Blame Contribute Delete
15.2 kB
from __future__ import annotations
from random import Random
from .models import AgentState, GridConfig, OrderState, Scenario, ZonePhase
def _clamp(value: int, low: int, high: int) -> int:
return max(low, min(high, value))
def _shift_point(
point: tuple[int, int],
width: int,
height: int,
rng: Random,
max_shift: int,
) -> tuple[int, int]:
x, y = point
return (
_clamp(x + rng.randint(-max_shift, max_shift), 0, width - 1),
_clamp(y + rng.randint(-max_shift, max_shift), 0, height - 1),
)
def _unique_points(points: tuple[tuple[int, int], ...]) -> tuple[tuple[int, int], ...]:
seen: list[tuple[int, int]] = []
for point in points:
if point not in seen:
seen.append(point)
return tuple(seen)
def _vary_order(
order: OrderState,
grid: GridConfig,
rng: Random,
*,
time_shift: int,
spatial_shift: int,
reward_shift: int,
deadline_shift: int,
) -> OrderState:
created_at = max(0, order.created_at + rng.randint(-time_shift, time_shift))
pickup = _shift_point(order.pickup_location, grid.width, grid.height, rng, spatial_shift)
drop = _shift_point(order.drop_location, grid.width, grid.height, rng, spatial_shift)
reward_value = max(4.0, order.reward_value + rng.randint(-reward_shift, reward_shift))
deadline = max(created_at + 4, order.deadline + rng.randint(-deadline_shift, deadline_shift))
return order.model_copy(
update={
"created_at": created_at,
"pickup_location": pickup,
"drop_location": drop,
"reward_value": float(reward_value),
"deadline": deadline,
},
deep=True,
)
def _vary_scenario(
scenario: Scenario,
seed: int | None,
*,
hotspot_shift: int,
congestion_shift: int,
time_shift: int,
spatial_shift: int,
reward_shift: int,
deadline_shift: int,
) -> Scenario:
if seed is None:
return scenario
rng = Random(f"{scenario.name}:{seed}")
varied_grid = scenario.grid.model_copy(
update={
"hotspots": _unique_points(tuple(
_shift_point(point, scenario.grid.width, scenario.grid.height, rng, hotspot_shift)
for point in scenario.grid.hotspots
))
},
deep=True,
)
varied_orders = tuple(
sorted(
(
_vary_order(
order,
varied_grid,
rng,
time_shift=time_shift,
spatial_shift=spatial_shift,
reward_shift=reward_shift,
deadline_shift=deadline_shift,
)
for order in scenario.orders
),
key=lambda item: (item.created_at, item.order_id),
)
)
varied_hotspot_phases = tuple(
phase.model_copy(
update={
"points": tuple(
_unique_points(tuple(
_shift_point(point, scenario.grid.width, scenario.grid.height, rng, hotspot_shift)
for point in phase.points
)))
},
deep=True,
)
for phase in scenario.hotspot_phases
)
varied_congestion_phases = tuple(
phase.model_copy(
update={
"points": tuple(
_unique_points(tuple(
_shift_point(point, scenario.grid.width, scenario.grid.height, rng, congestion_shift)
for point in phase.points
)))
},
deep=True,
)
for phase in scenario.congestion_phases
)
return scenario.model_copy(
update={
"grid": varied_grid,
"orders": varied_orders,
"hotspot_phases": varied_hotspot_phases,
"congestion_phases": varied_congestion_phases,
},
deep=True,
)
def build_low_demand_scenario(seed: int | None = None) -> Scenario:
scenario = Scenario(
name="low_demand",
grid=GridConfig(width=8, height=8, hotspots=((5, 5), (6, 5), (5, 6))),
agents=(
AgentState(agent_id="a1", location=(1, 1)),
AgentState(agent_id="a2", location=(4, 2)),
AgentState(agent_id="a3", location=(6, 6)),
),
orders=(
OrderState(order_id="o1", created_at=0, pickup_location=(1, 2), drop_location=(3, 3), reward_value=10, deadline=10),
OrderState(order_id="o2", created_at=2, pickup_location=(5, 2), drop_location=(6, 4), reward_value=12, deadline=13),
OrderState(order_id="o3", created_at=5, pickup_location=(2, 6), drop_location=(1, 7), reward_value=8, deadline=17),
OrderState(order_id="o4", created_at=8, pickup_location=(5, 5), drop_location=(7, 6), reward_value=11, deadline=19),
OrderState(order_id="o5", created_at=12, pickup_location=(2, 1), drop_location=(4, 1), reward_value=9, deadline=23),
OrderState(order_id="o6", created_at=16, pickup_location=(6, 5), drop_location=(7, 7), reward_value=13, deadline=28),
OrderState(order_id="o7", created_at=21, pickup_location=(3, 4), drop_location=(1, 5), reward_value=8, deadline=31),
OrderState(order_id="o8", created_at=26, pickup_location=(4, 6), drop_location=(6, 7), reward_value=14, deadline=36),
),
episode_horizon=40,
default_max_decision_steps=20,
hotspot_phases=(
ZonePhase(start_time=0, points=((5, 5), (6, 5), (5, 6))),
ZonePhase(start_time=24, points=((5, 5), (6, 5), (6, 6))),
),
briefing="Sparse city with generous deadlines. Most orders are feasible, so wasted idle time and unnecessary detours matter.",
dispatch_objective="Prefer clean on-time execution and avoid leaving easy nearby work untouched.",
known_future_signal="",
)
return _vary_scenario(
scenario,
seed,
hotspot_shift=0,
congestion_shift=0,
time_shift=0,
spatial_shift=0,
reward_shift=1,
deadline_shift=0,
)
def build_high_demand_scenario(seed: int | None = None) -> Scenario:
scenario = Scenario(
name="high_demand",
grid=GridConfig(
width=10,
height=10,
congested_zones=((4, 4), (4, 5), (5, 4), (5, 5), (6, 5)),
hotspots=((7, 7), (7, 8), (8, 7), (8, 8)),
),
agents=(
AgentState(agent_id="a1", location=(1, 1)),
AgentState(agent_id="a2", location=(2, 8)),
AgentState(agent_id="a3", location=(8, 2)),
AgentState(agent_id="a4", location=(6, 6)),
),
orders=(
OrderState(order_id="o1", created_at=0, pickup_location=(7, 7), drop_location=(9, 8), reward_value=16, deadline=12),
OrderState(order_id="o2", created_at=1, pickup_location=(6, 7), drop_location=(7, 9), reward_value=12, deadline=11),
OrderState(order_id="o3", created_at=2, pickup_location=(3, 3), drop_location=(1, 5), reward_value=9, deadline=10),
OrderState(order_id="o4", created_at=4, pickup_location=(8, 7), drop_location=(9, 9), reward_value=15, deadline=15),
OrderState(order_id="o5", created_at=6, pickup_location=(2, 8), drop_location=(4, 9), reward_value=10, deadline=16),
OrderState(order_id="o6", created_at=8, pickup_location=(7, 8), drop_location=(9, 6), reward_value=18, deadline=19),
OrderState(order_id="o7", created_at=11, pickup_location=(5, 2), drop_location=(2, 2), reward_value=11, deadline=20),
OrderState(order_id="o8", created_at=13, pickup_location=(8, 8), drop_location=(6, 9), reward_value=14, deadline=23),
OrderState(order_id="o9", created_at=17, pickup_location=(1, 7), drop_location=(3, 9), reward_value=9, deadline=26),
OrderState(order_id="o10", created_at=17, pickup_location=(7, 7), drop_location=(8, 9), reward_value=17, deadline=24),
OrderState(order_id="o11", created_at=18, pickup_location=(8, 8), drop_location=(9, 7), reward_value=15, deadline=24),
OrderState(order_id="o12", created_at=22, pickup_location=(6, 6), drop_location=(8, 4), reward_value=13, deadline=31),
OrderState(order_id="o13", created_at=22, pickup_location=(7, 9), drop_location=(9, 9), reward_value=16, deadline=29),
OrderState(order_id="o14", created_at=28, pickup_location=(7, 7), drop_location=(9, 7), reward_value=12, deadline=37),
OrderState(order_id="o15", created_at=34, pickup_location=(4, 1), drop_location=(5, 3), reward_value=8, deadline=42),
OrderState(order_id="o16", created_at=36, pickup_location=(8, 7), drop_location=(9, 5), reward_value=14, deadline=43),
OrderState(order_id="o17", created_at=19, pickup_location=(1, 1), drop_location=(9, 9), reward_value=5, deadline=25),
OrderState(order_id="o18", created_at=15, pickup_location=(0, 8), drop_location=(9, 0), reward_value=6, deadline=22),
),
episode_horizon=60,
default_max_decision_steps=25,
hotspot_phases=(
ZonePhase(start_time=0, points=((7, 7), (7, 8), (8, 7), (8, 8))),
ZonePhase(start_time=26, points=((6, 7), (7, 7), (7, 8), (8, 8))),
),
congestion_phases=(
ZonePhase(start_time=0, points=((4, 4), (4, 5), (5, 4), (5, 5), (6, 5))),
ZonePhase(start_time=38, points=((5, 5), (5, 6), (6, 5), (6, 6), (7, 6))),
),
briefing="Demand arrives faster than the fleet can comfortably absorb, with clustered premium bursts and low-yield long-haul distractions.",
dispatch_objective="Maximize cumulative reward under sustained capacity pressure; serving everything greedily should no longer be obviously best.",
known_future_signal="",
)
return scenario if seed is None else scenario
def build_hotspot_congestion_scenario(seed: int | None = None) -> Scenario:
scenario = Scenario(
name="hotspot_congestion",
grid=GridConfig(
width=15,
height=15,
congested_zones=(
(6, 6), (6, 7), (6, 8),
(7, 6), (7, 7), (7, 8),
(8, 6), (8, 7), (8, 8),
(10, 10), (10, 11), (11, 10),
),
hotspots=((11, 11), (11, 12), (12, 11), (12, 12), (13, 12)),
),
agents=(
AgentState(agent_id="a1", location=(2, 2)),
AgentState(agent_id="a2", location=(3, 10)),
AgentState(agent_id="a3", location=(10, 4)),
AgentState(agent_id="a4", location=(13, 13)),
AgentState(agent_id="a5", location=(8, 12)),
),
orders=(
OrderState(order_id="o1", created_at=0, pickup_location=(11, 11), drop_location=(14, 14), reward_value=20, deadline=16),
OrderState(order_id="o2", created_at=1, pickup_location=(12, 11), drop_location=(10, 14), reward_value=18, deadline=15),
OrderState(order_id="o3", created_at=3, pickup_location=(4, 5), drop_location=(2, 9), reward_value=10, deadline=15),
OrderState(order_id="o4", created_at=5, pickup_location=(11, 12), drop_location=(13, 10), reward_value=17, deadline=18),
OrderState(order_id="o5", created_at=7, pickup_location=(9, 11), drop_location=(12, 13), reward_value=16, deadline=20),
OrderState(order_id="o6", created_at=10, pickup_location=(6, 4), drop_location=(3, 2), reward_value=11, deadline=21),
OrderState(order_id="o7", created_at=14, pickup_location=(13, 12), drop_location=(14, 9), reward_value=19, deadline=25),
OrderState(order_id="o8", created_at=18, pickup_location=(10, 11), drop_location=(7, 13), reward_value=14, deadline=28),
OrderState(order_id="o9", created_at=18, pickup_location=(12, 12), drop_location=(14, 10), reward_value=22, deadline=27),
OrderState(order_id="o10", created_at=19, pickup_location=(11, 11), drop_location=(13, 14), reward_value=18, deadline=26),
OrderState(order_id="o11", created_at=24, pickup_location=(3, 12), drop_location=(1, 14), reward_value=9, deadline=34),
OrderState(order_id="o12", created_at=31, pickup_location=(12, 12), drop_location=(14, 8), reward_value=20, deadline=41),
OrderState(order_id="o13", created_at=31, pickup_location=(10, 12), drop_location=(13, 13), reward_value=17, deadline=39),
OrderState(order_id="o14", created_at=39, pickup_location=(11, 10), drop_location=(8, 11), reward_value=13, deadline=48),
OrderState(order_id="o15", created_at=47, pickup_location=(5, 11), drop_location=(8, 14), reward_value=12, deadline=58),
OrderState(order_id="o16", created_at=47, pickup_location=(12, 11), drop_location=(14, 14), reward_value=21, deadline=56),
OrderState(order_id="o17", created_at=56, pickup_location=(13, 11), drop_location=(14, 13), reward_value=21, deadline=66),
OrderState(order_id="o18", created_at=63, pickup_location=(10, 3), drop_location=(6, 1), reward_value=10, deadline=74),
OrderState(order_id="o19", created_at=32, pickup_location=(1, 2), drop_location=(14, 14), reward_value=6, deadline=40),
OrderState(order_id="o20", created_at=26, pickup_location=(0, 0), drop_location=(14, 13), reward_value=7, deadline=35),
),
episode_horizon=80,
default_max_decision_steps=30,
hotspot_phases=(
ZonePhase(start_time=0, points=((11, 11), (11, 12), (12, 11), (12, 12), (13, 12))),
ZonePhase(start_time=18, points=((9, 10), (10, 10), (10, 11), (11, 11), (11, 12))),
ZonePhase(start_time=38, points=((7, 10), (7, 11), (8, 11), (8, 12), (9, 12))),
ZonePhase(start_time=54, points=((5, 11), (6, 11), (7, 12), (8, 12), (8, 13))),
),
congestion_phases=(
ZonePhase(
start_time=0,
points=((6, 6), (6, 7), (6, 8), (7, 6), (7, 7), (7, 8), (8, 6), (8, 7), (8, 8)),
),
ZonePhase(
start_time=22,
points=((8, 8), (8, 9), (9, 8), (9, 9), (10, 9), (10, 10), (10, 11), (11, 10)),
),
ZonePhase(
start_time=46,
points=((5, 10), (5, 11), (6, 10), (6, 11), (7, 11), (8, 11), (8, 12)),
),
),
briefing="Large city with moving hotspot pressure, congestion pockets, premium spikes, and multiple low-yield long-haul traps.",
dispatch_objective="Force strategic tradeoffs: a strong policy should balance premium hotspot work against detours, congestion, and selective rejection.",
known_future_signal="",
)
return _vary_scenario(
scenario,
seed,
hotspot_shift=1,
congestion_shift=1,
time_shift=2,
spatial_shift=1,
reward_shift=2,
deadline_shift=2,
)
SCENARIO_BUILDERS = {
"low_demand": build_low_demand_scenario,
"high_demand": build_high_demand_scenario,
"hotspot_congestion": build_hotspot_congestion_scenario,
}