Custom Traffic Rules¶
The traffic-rule engine enforces how the agent behaves around traffic. A rule fires when the local situation matches its trigger conditions: the tile type, the agent's speed and heading, and the surrounding traffic. On a match, it runs a configurable action. You can activate a subset of the built-in rules, add your own, or turn rules off entirely.
Traffic rule parameters¶
A TrafficRule has these fields:
| Field | Type | Meaning |
|---|---|---|
name |
str |
Unique identifier (used to add/remove the rule). |
tile_type |
tuple[int, int, int, int] |
The tile exit pattern (N, E, S, W) the rule applies to. (1,1,1,1) is a four-way crossing, (1,1,1,0) a T-crossing. A 4-character string like "1111" is also accepted. |
velocity_range |
tuple[float, float] |
The rule only fires when the agent's speed is within [min, max]. |
min_traffic |
int |
Minimum number of cars in the tile for the rule to fire. |
min_matching_traffic |
int |
Minimum number of cars whose route matches the agent's maneuver. |
maneuvers |
dict[agent_dir, set[traffic_dir]] |
Which crossing traffic directions matter for each agent heading. |
action |
callable(env, ctx) |
Runs when the rule fires. Returning a truthy value halts the agent's remaining movement this step (i.e. brakes). |
Directions are LaneDirection values such as "west_to_east", "east_to_west", "north_to_south", and "south_to_north".
The agent's heading is inferred from the direction to its next subgoal.
The action¶
action is a callable (env, ctx) -> Any.
To keep rules picklable and loadable from a JSON, it is can be written as a string containing a lambda, which the engine evaluates.
numpy is available as np in that scope.
The built-in braking action is:
Returning a truthy value halts the agent's remaining substeps this step call, which is what makes it brake.
Built-in rules¶
PGTG ships two default rules, both of which force the agent to brake for cross traffic:
| Rule name | Tile | Behavior |
|---|---|---|
four_way_intersection_brake |
(1,1,1,1) |
Brake at a four-way crossing when crossing traffic is present. |
t_intersection_brake |
(1,1,1,0) |
Brake at a T-crossing when crossing traffic is present. |
Selecting which rules are active¶
rules_active chooses which of the loaded rules are active, by name:
None(default): every loaded rule is active.- a
list[str]: only the named rules are active; an empty list means no rule is active at all.
from pgtg import PGTGEnv
# All loaded rules active (the default behavior):
PGTGEnv(traffic_density=0.05)
# Only one rule active:
PGTGEnv(traffic_density=0.05, rules_active=["four_way_intersection_brake"])
# No rules active at all:
PGTGEnv(traffic_density=0.05, rules_active=[])
Adding your own rules¶
Pass a traffic_rules_config as an instance, a dict with a top-level rules list, or a path to a JSON/YAML file.
Your rules join the loaded set alongside the defaults (a name clash with a default raises an error),
and rules_active then decides which of them are active. See Selecting which rules are active.
from pgtg import PGTGEnv
my_rule = {
"name": "crossing_full_stop",
"tile_type": "1111",
"velocity_range": [0.5, 10.0],
"min_traffic": 1,
"min_matching_traffic": 1,
"maneuvers": [
{"agent": "west_to_east", "traffic": ["north_to_south", "south_to_north"]},
{"agent": "east_to_west", "traffic": ["north_to_south", "south_to_north"]},
],
"action": "lambda env, ctx: (setattr(env, 'velocity', np.array((0, 0))), True)[-1]"
),
}
env = PGTGEnv(
traffic_density=0.05,
traffic_rules_config={"rules": [my_rule]},
rules_active=["crossing_full_stop"], # only this rule active, no defaults
)
{
"rules": [
{
"name": "crossing_full_stop",
"tile_type": "1111",
"velocity_range": [0.5, 10.0],
"min_traffic": 1,
"min_matching_traffic": 1,
"maneuvers": [
{"agent": "west_to_east", "traffic": ["north_to_south", "south_to_north"]}
],
"action": "lambda env, ctx: (setattr(env, 'velocity', np.array((0, 0))), True)[-1]"
}
]
}
Adding and removing rules at runtime¶
PGTGEnv exposes helpers to modify the rule set after construction:
env.add_traffic_rule(my_rule) # add one rule from a dict
removed = env.remove_traffic_rule("crossing_full_stop") # -> True if it existed
Disable traffic rules entirely
Pass rules_active=[] to run traffic with no agent-side traffic rules at all
(no default or custom rule fires).