Skip to content

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:

"lambda env, ctx: (setattr(env, 'velocity', np.array((0, 0))), True)[-1]"

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
)
env = PGTGEnv(traffic_density=0.05, traffic_rules_config="my_rules.json")
my_rules.json
{
  "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).