PyTrol is a discrete-time Python simulator for MultiAgent Patrolling (MAP). It was originally developed to generate large amounts of MAP simulation data rapidly, then progressively evolved into a research framework for implementing, comparing, and analysing patrolling strategies.
At its core, PyTrol simulates a society of agents moving over a graph. Each agent follows a strategy, observes part of the environment, optionally communicates with other agents, decides its next action, and updates its own knowledge of the environment. The simulator records execution traces that can be used for statistical evaluation or as datasets for machine-learning experiments.
PyTrol is particularly suited for research on:
- graph-based multiagent patrolling;
- centralised, coordinated, and distributed strategies;
- communication-aware patrolling;
- idleness minimisation;
- generation of simulation traces for learning;
- comparison of hand-coded strategies and learned policies.
A MAP scenario is represented as:
[ { \Pi, G, N_a } ]
where:
- ( \Pi ) is the patrolling strategy;
- ( G ) is the graph to patrol;
- ( N_a ) is the number of patrolling agents.
A concrete simulation run, also called a mission or execution, is a specific instance of such a scenario. It includes:
- the graph topology;
- the agent society;
- the type of each agent;
- the initial position of each agent;
- the simulation duration;
- optional strategy-related parameters.
The execution is provided to the simulator through a JSON or XML configuration file.
PyTrol uses a discrete temporal model.
At each time step, also called a period, every agent goes through the same logical life cycle:
- preparation;
- perception;
- communication;
- message analysis;
- decision;
- action;
- knowledge update;
- post-processing.
A period is not considered complete until all agents have completed their actions and no pending communication requires further processing.
This is important: PyTrol is not an event-driven continuous-time simulator. It is a synchronised discrete-time simulator in which agent actions are processed period by period.
PyTrol represents the environment as a graph.
Edges are discretised: an edge is divided into units, and an agent moves by one unit per simulation period. A position is represented as a three-component vector:
(vertex, edge, unit)
The convention is:
(vertex, -1, 0)
for an agent located exactly on a vertex, and:
(source_vertex, edge_id, unit)
for an agent currently travelling along an edge.
This representation allows PyTrol to model both agents standing on nodes and agents currently crossing edges.
The main graph structure is implemented by:
pytrol.model.network.Network
The Network object stores:
- the graph adjacency structure;
- edge-to-vertex mappings;
- edge lengths;
- vertex and edge dictionaries;
- vertex locations;
- edge activation information;
- shortest distances and paths;
- topological neighbours;
- Euclidean distances between agents.
PyTrol is organised into three main subpackages:
pytrol/
├── control/
├── model/
└── util/
The control package contains the active simulation components:
pytrol.control.Ananke
pytrol.control.Archivist
pytrol.control.Communicating
pytrol.control.agent.*
It is responsible for:
- running the simulation;
- instantiating agents;
- managing the simulation loop;
- handling perception;
- processing actions;
- coordinating communication;
- sending data to the logger.
The model package contains the passive data structures used by the simulator:
pytrol.model.action
pytrol.model.knowledge
pytrol.model.network
pytrol.model.AgentTypes
pytrol.model.Data
pytrol.model.Maps
pytrol.model.Metrics
pytrol.model.Paths
It defines:
- actions;
- agent types;
- graph/network data;
- environment knowledge;
- metrics;
- static path constants;
- map identifiers.
The util package contains auxiliary tools:
pytrol.util.SimPreprocessor
pytrol.util.MetricComputer
pytrol.util.graphprocessor
pytrol.util.argsparser
pytrol.util.pathformatter
pytrol.util.misc
pytrol.util.net
It provides:
- scenario generation;
- JSON/XML conversion;
- graph preprocessing;
- shortest-path computation;
- metric computation;
- path formatting;
- command-line utilities;
- communication abstractions.
The central execution architecture is:
Configuration file
|
v
SimPreprocessor
|
v
Ananke
|
+---- Network
|
+---- EnvironmentKnowledge for each agent
|
+---- Agent instances
|
+---- Archivist
The responsibilities are deliberately separated:
| Component | Responsibility |
|---|---|
SimPreprocessor |
Loads or generates missions |
Network |
Represents the graph and path structure |
EnvironmentKnowledge |
Stores each agent's local knowledge |
Agent |
Defines the strategy life cycle |
Ananke |
Runs the simulation loop |
Archivist |
Logs traces and computes metrics |
Communicating |
Provides message-passing primitives |
The class:
pytrol.control.Ananke.Ananke
is the core of the simulator.
Its responsibilities are:
- load the mission file;
- initialise the graph;
- create the
Network; - instantiate the agent societies;
- initialise real and individual idlenesses;
- run the main simulation loop;
- compute each agent's perception;
- execute the agent life cycle;
- process agent actions;
- update the environment;
- send traces to the
Archivist; - stop agents and save logs at the end of the run.
The main loop is conceptually:
for t in range(duration):
log current state
compute perception matrix
prepare agents
send perceptions to agents
while not all agents have completed their cycle:
agents communicate
agents analyse messages
agents decide
agents act
Ananke processes actions
agents update their knowledge
agents post-process
environment is updated
The simulation state managed by Ananke includes:
- current time step;
- true idleness of each node;
- current position of every agent;
- each agent's individual idleness estimates;
- optional learned-model estimates;
- perception and communication reachability.
All agent strategies extend:
pytrol.control.agent.Agent.Agent
An agent is both:
- a strategy object;
- a communicating object.
The agent life cycle is:
prepare()
perceive()
communicate()
analyse()
decide()
act()
update_knowledge()
post_process()
Resets cycle-local state before the new period begins.
Receives perceived positions of other agents. In the current simulator, this mainly concerns the positions of agents within communication or perception range.
Sends messages if the strategy requires communication.
Receives and processes messages.
Runs the strategy-specific decision algorithm. This method delegates to:
strategy_decide()
which should be implemented by subclasses.
Consumes the current plan and returns an action to Ananke.
Agents maintain a plan as a queue:
PLAN
The plan may contain high-level and low-level actions, such as:
- going to a target;
- moving along an edge;
- waiting.
Actions are defined in:
pytrol.model.action
The important action concepts are:
Action
Actions
GoingToAction
MovingToAction
A GoingToAction represents a high-level intention to reach a destination.
When executed, it is expanded into a sequence of MovingToAction objects by
using the shortest path provided by the Network.
A MovingToAction represents one elementary movement step from one discrete
position to another.
This separation is important because strategies can reason at a high level, while the simulator executes movement at the discretised graph level.
Communication is implemented through:
pytrol.control.Communicating
pytrol.util.net.Connection
pytrol.util.net.SimulatedConnection
A Communicating object owns:
- a connection;
- a message buffer;
- a mailbox;
- cycle-completion flags.
Both Agent and Ananke inherit from Communicating.
By default, PyTrol uses a simulated in-memory connection. Messages are passed by reference between Python objects. This makes simulation fast and convenient, but it is not a realistic network model.
The current communication abstraction is nevertheless valuable because it isolates the simulator from the concrete communication mechanism.
At each period, Ananke computes which agents can perceive one another.
This is done in two steps:
- compute a neighbouring matrix based on Euclidean distance and the configured depth;
- compute the transitive closure of that relation, so that agents connected through relays may share information.
Conceptually:
neighbouring relation:
agent i is close enough to agent j
connection relation:
agent i is connected to agent j, possibly through other agents
This is one of the most important architectural features of PyTrol. Although the current model is simple, it already contains the seed of a dynamic communication graph.
Each agent receives its own instance of:
pytrol.model.knowledge.EnvironmentKnowledge
This object stores what the agent knows about:
- the network;
- its own idleness estimates;
- shared idleness estimates;
- known agent positions;
- current time;
- number of agents;
- speed information.
The key distinction is between:
true idleness
managed by Ananke, and:
individual idleness
managed by each agent.
This distinction is scientifically important because it separates the real state of the world from the agent's local representation of that world.
In classical MultiAgent Patrolling, the central performance variable is idleness.
The idleness of a node is the number of time steps elapsed since that node was last visited.
For a node ( v ), its idleness at time ( t ) can be described as:
[ I_v(t + 1) = \begin{cases} 0 & \text{if node } v \text{ is visited at time } t, \ I_v(t) + 1 & \text{otherwise.} \end{cases} ]
PyTrol tracks:
- true idleness of nodes;
- individual idleness estimates held by each agent;
- optionally, estimated idlenesses produced by learned models.
These values are central to both evaluation and learning.
The class:
pytrol.control.Archivist.Archivist
records simulation traces.
It logs:
- agent positions;
- true idlenesses;
- optionally, each agent's individual idlenesses;
- optionally, learned estimates of idleness.
Logs are saved as JSON files.
The Archivist also uses:
pytrol.util.MetricComputer
to compute evaluation criteria from the idleness traces.
The output logs can be used for:
- statistical analysis;
- strategy comparison;
- visualisation;
- supervised learning;
- imitation learning;
- offline policy evaluation.
The class:
pytrol.util.SimPreprocessor
handles mission preparation.
It can:
- load JSON execution files;
- load XML execution files;
- convert XML configurations to JSON;
- generate randomised runs;
- generate societies of agents;
- inject initial agent positions into scenarios;
- load maps;
- convert graph and society descriptions into NumPy structures.
PyTrol distinguishes:
| Concept | Meaning |
|---|---|
| Configuration | A graph ( G ) and a number of agents ( N_a ) |
| Scenario | A strategy ( \Pi ), a graph ( G ), and a number of agents ( N_a ) |
| Mission / execution / run | A scenario with concrete initial conditions |
This distinction should be preserved because it is useful for reproducible experiments.
PyTrol contains several strategy implementations in:
pytrol.control.agent
The README currently mentions:
CR;HPCC;HCC;- machine-learning-based agents extending
MAPTrainerModelAgent.
New strategies should:
- extend
Agent; - implement or override the relevant life-cycle methods;
- especially implement
strategy_decide; - be added to
pytrol.control.agent; - be registered in
pytrol.model.AgentTypes.
To add a new strategy:
- create a new class in
pytrol.control.agent; - inherit from
Agent; - implement
strategy_decide; - optionally override:
communicate;process_message;analyse;post_process;
- register the new class in
AgentTypes; - reference the new type in a JSON or XML mission file.
A minimal strategy has the following shape:
from pytrol.control.agent.Agent import Agent
from pytrol.model.action.GoingToAction import GoingToAction
class MyStrategy(Agent):
def strategy_decide(self):
# Choose a target position.
target = ...
self.PLAN.append(GoingToAction(target))17. Hidden architectural assumptions
Several assumptions are implicit in the current version of PyTrol.
They are important for users and future contributors.
The current MAP model assumes agents with broadly similar capabilities. Although different strategy classes exist, the environment model does not yet deeply represent heterogeneous sensors, effectors, endurance, survivability, value, or communication equipment.
The current SimulatedConnection is efficient, but it does not model typical
realistic communication constraints.
The patrolling graph is loaded at the beginning of the mission. Dynamic changes to the environment are not yet central to the architecture.
The current simulator is designed around node idleness. This is appropriate for MAP, but future work may generalise idleness into broader information-freshness concepts.
Classical MAP assumes persistent agents. The current simulator does not treat attrition, failure, or expendability as first-class concepts.
PyTrol currently models patrolling, not an adversarial tactical environment. There is no explicit enemy model, defence model, deception model, or saturation model.
These assumptions are not defects. They reflect the original research scope.
At present, PyTrol is structured as a Python package but does not expose a
complete modern packaging interface such as pyproject.toml.
A typical development workflow is therefore:
git clone https://github.com/guibaure/pytrol.git
cd pytrol
python main.pyDepending on the environment, additional dependencies may be required, notably:
numpy
networkx
untangle
maptor
The exact dependency list should be formalised in a future
requirements.txt or pyproject.toml.
PyTrol is a research codebase. It is compact and conceptually clear, but it should be treated as an academic simulator rather than a production-ready software package.
PyTrol is distributed under the MIT Licence.
See:
LICENSE.txt
for details.