Long Agent Library

1. What are long agents

LongAgent is a subtype of GraphAgent class, which is a type of agent that exists in an environment consisting of a directed graph with a geometric representation in 2D space. The environment that long agent exists in is represented with LongAgentsEnvironment, which is a subtype of the GraphEnvironment class. The long agent environment inherits the properties of the graph environment, while also introducing specific features that are unique to the long agent subtype.

The main difference between the LongAgent and the generic GraphAgent is that the long agent is not a geometric point, but has a non-zero length. To account for the interaction of long agents with the environment and with each other, each agent actually consists of two agents: a head and a tail, which also determine the orientation of the agent in the "tail to head" direction.

The LongAgent type agent is the head, and the LongAgentTail type agent is the tail (also a subclass of GraphAgent). Instance of LongAgentTail belonging to some LongAgent is considered as part of this LongAgent instance. LongAgent and LongAgentTail are an integral part of each other and cannot exist separately.

Examples of applied tasks where long agents can be used for modeling include:

  • Movement and interaction of railway trains

  • Logic of conveyor belts

  • Movement of materials through pipes/conveyors

  • Queues and various simulation experiments where the level of abstraction requires the consideration of agents' length

2. Creating LongAgentsEnvironment

To demonstrate the functionality of the library, let us create the following graph:

Example of graph

First, we declare the instance of LongAgentsEnvironment:

LongAgentsEnvironment<LongAgentGraphNodeImpl, LongAgentGraphArcImpl, LongAgentWeightKey> longAgentsEnvironment = new LongAgentsEnvironment<>();

Then, we create the points in the 2D-space for the graph nodes:

var p1 = new Point(100, 300);
var p2 = new Point(200, 300);
var p3 = new Point(300, 300);
var p4 = new Point(200, 200);
var p5 = new Point(400, 200);
var p6 = new Point(100, 100);
var p7 = new Point(300, 100);
var p8 = new Point(500, 100);
var p9 = new Point(-800, 100);
var p10 = new Point(100, 400);
var p11 = new Point(300, 400);

After that, we create and add the nodes to our graph environment:

var n1 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p1));
var n2 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p2));
var n3 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p3));
var n4 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p4));
var n5 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p5));
var n6 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p6));
var n7 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p7));
var n8 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p8));	
var n9 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p9));	
var n10 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p10));	
var n11 = longAgentsEnvironment.addNode(new LongAgentGraphNodeImpl(p11));	

On the next step, we create the polylines for our graph arcs:

var polyline12 = new Polyline(p1, p2);
var polyline21 = polyline12.getReversed();
var polyline23 = new Polyline(p2, p3);
var polyline32 = polyline23.getReversed();
var polyline14 = new Polyline(p1, p4);
var polyline41 = polyline14.getReversed();
var polyline34  = new Polyline(p3, p4);
var polyline43 = polyline34.getReversed();
var polyline35  = new Polyline(p3, p5);
var polyline53 = polyline35.getReversed();
var polyline64  = new Polyline(p6, p4);
var polyline46 = polyline64.getReversed();
var polyline74  = new Polyline(p7, p4);
var polyline75  = new Polyline(p7, p5);
var polyline57 = polyline75.getReversed();
var polyline69  = new Polyline(p6, p9);
var polyline96 = polyline69.getReversed();
var polyline110  = new Polyline(p1, p10);
var polyline101 = polyline110.getReversed();
var polyline1011 = new Polyline(p10, p11);
var polyline1110 = polyline1011.getReversed();
var polyline113 = new Polyline(p11, p3);
var polyline311 = polyline113.getReversed();

Then, we create the arcs themselves and add them to the LongAgentsEnvironment graph (note that the arc a74 does not have a reverse arc):

var a12a21 = longAgentsEnvironment.addArc(n1.getValue(), n2.getValue(), new LongAgentGraphArcImpl(polyline12), new LongAgentGraphArcImpl(polyline21));		
var a12 = a12a21.first;
var a21 = a12a21.second;

var a23a32 = longAgentsEnvironment.addArc(n2.getValue(), n3.getValue(), new LongAgentGraphArcImpl(polyline23), new LongAgentGraphArcImpl(polyline32));
var a23 = a23a32.first;
var a32 = a23a32.second;

var a14a41 = longAgentsEnvironment.addArc(n1.getValue(), n4.getValue(), new LongAgentGraphArcImpl(polyline14), new LongAgentGraphArcImpl(polyline41));	
var a14 = a14a41.first;
var a41 = a14a41.second;

var a34a43 = longAgentsEnvironment.addArc(n3.getValue(), n4.getValue(), new LongAgentGraphArcImpl(polyline34), new LongAgentGraphArcImpl(polyline43));	
var a34 = a34a43.first;
var a43 = a34a43.second;

var a35a53 = longAgentsEnvironment.addArc(n3.getValue(), n5.getValue(), new LongAgentGraphArcImpl(polyline35), new LongAgentGraphArcImpl(polyline53));	
var a35 = a35a53.first;
var a53 = a35a53.second;

var a64a46 = longAgentsEnvironment.addArc(n6.getValue(), n4.getValue(), new LongAgentGraphArcImpl(polyline64), new LongAgentGraphArcImpl(polyline46));	
var a64 = a64a46.first;
var a46 = a64a46.second;

var a74 = longAgentsEnvironment.addArc(n7.getValue(), n4.getValue(), new LongAgentGraphArcImpl(polyline74));	

var a75a57 = longAgentsEnvironment.addArc(n7.getValue(), n5.getValue(), new LongAgentGraphArcImpl(polyline75), new LongAgentGraphArcImpl(polyline57));	
var a75 = a75a57.first;
var a57 = a75a57.second;

var a69a96 = longAgentsEnvironment.addArc(n6.getValue(), n9.getValue(), new LongAgentGraphArcImpl(polyline69), new LongAgentGraphArcImpl(polyline96));	
var a69 = a69a96.first;
var a96 = a69a96.second;

var a110a101 = longAgentsEnvironment.addArc(n1.getValue(), n10.getValue(), new LongAgentGraphArcImpl(polyline110), new LongAgentGraphArcImpl(polyline101));	
var a110 = a110a101.first;
var a101 = a110a101.second;

var a1011a1110 = longAgentsEnvironment.addArc(n10.getValue(), n11.getValue(), new LongAgentGraphArcImpl(polyline1011), new LongAgentGraphArcImpl(polyline1110));		
var a1011 = a1011a1110.first;
var a1110 = a1011a1110.second;

var a113a311 = longAgentsEnvironment.addArc(n11.getValue(), n3.getValue(), new LongAgentGraphArcImpl(polyline113), new LongAgentGraphArcImpl(polyline311));		
var a113 = a113a311.first;
var a311 = a113a311.second;	

3. Creating a long agent and placing it into the environment

Note that LongAgentTail is an internal variable of the LongAgent class, so when initializing an instance of LongAgent, a corresponding instance of LongAgentTail is automatically initialized as well.

When placing a long agent into the graph environment, its tail will also be automatically placed in that same environment.

A long agent can only exist in a special graph environment - the long agent environment. Only agents of the long agent type can exist in the LongAgentsEnvironment. When placing a long agent in the long agents environment, it is necessary to specify a positive non-zero length of the agent:

var engine = new Engine();
LongAgent<LongAgentGraphNodeImpl, LongAgentGraphArcImpl> graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 250);

Since the long agent always has a positive non-zero length, it will always occupy at least part of one arc in the graph it is located in. As the long agent is an oriented agent, it can only be located on an oriented arc of the graph in the same direction as the arc, meaning that the tail of the agent can only be located at the starting node of the arc, and the head of the agent can only be located at the ending node of the arc. The jumpTo method is used to place our instance of LongAgent into the graph environment, and can only be successfully called with arguments that reflect the possible position of the agent in the graph - the arc and the offset of the head of the agent from the beginning of the arc:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentRed.jumpTo(new GeometricGraphPosition<>(a12, 30));
Example of jumping

If, as a result of calling the jumpTo method, the long agent needs to occupy several arcs due to its length being greater than the length of a single arc in the graph, in addition to passing the arc and the offset of the agent’s head from the beginning of the arc as arguments, it is also necessary to pass a sequence of arcs on which the body of the agent will be located:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 250);
graphAgentRed.jumpTo(a34, 100, List.of(a23, a12));		
Example of jumping

All arcs passed to the jumpTo method must represent a continuous sequence and must not contradict the principle of the impossibility of self-overlapping of the agent on the arcs segments of the graph.

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 250);

Calling the following code will throw an exception, because arcs a23 and a21 do not represent a continuous sequence:

graphAgentRed.jumpTo(a34, 100, List.of(a23, a21));
Exception text
Wrong order arcs are not allowed, such as: [Arc(X=200.0 Y=300.0 -> X=300.0 Y=300.0 0 agents): Node(X=200.0 Y=300.0 0 agents) -> Node(X=300.0 Y=300.0 0 agents) Arc(X=200.0 Y=300.0 -> X=100.0 Y=300.0 0 agents): Node(X=200.0 Y=300.0 0 agents) -> Node(X=100.0 Y=300.0 0 agents)]

A long agent cannot be located on arcs that are reverse arcs of each other.

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 250);

The following code will throw an exception because the arcs a23 and a32 are reverse with respect to each other:s

graphAgentRed.jumpTo(a34, 100, List.of(a23, a32));
Exception text
Reverse arcs are not allowed, such as: {Arc(X=200.0 Y=300.0 -> X=300.0 Y=300.0 0 agents): Node(X=200.0 Y=300.0 0 agents) -> Node(X=300.0 Y=300.0 0 agents)=Arc(X=300.0 Y=300.0 -> X=200.0 Y=300.0 0 agents): Node(X=300.0 Y=300.0 0 agents) -> Node(X=200.0 Y=300.0 0 agents)}

A long agent cannot occupy the same segment of an arc in the graph multiple times simultaneously. In other words, the long agent cannot have overlaps with a non-zero length, but it can intersect itself at a node in the graph. The following code can be executed because the length of the self-intersection of the agent is equal to zero:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 700);
graphAgentRed.jumpTo(a41, 50, List.of(a74, a57, a35, a43, a64));		
Example of jumping with node overlay
clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 500);

The following code will throw an exception because the agent will have a non-zero overlap on the arc a12:

graphAgentRed.jumpTo(a12, 100, List.of(a41, a34, a23, a12));
Exception text
Overlay arcs are not allowed, such as: [Arc(X=100.0 Y=300.0 -> X=200.0 Y=300.0 0 agents): Node(X=100.0 Y=300.0 0 agents) -> Node(X=200.0 Y=300.0 0 agents)]

Multiple long agents cannot occupy the same arc segment or a segment of a reverse arc, but they can occupy the same graph node. The execution of the following code is possible because the agents have zero overlap lengths:

clearGraphEnvironment(longAgentsEnvironment, engine);

LongAgent<LongAgentGraphNodeImpl, LongAgentGraphArcImpl>  graphAgentGreen = new LongAgent<>(engine);
graphAgentGreen.setGraphEnvironment(longAgentsEnvironment, 100);
graphAgentGreen.jumpTo(a41, 50, List.of(a74));		

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 100);
graphAgentRed.jumpTo(a43, 50, List.of(a64));		
Example of jumping with node overlay
clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentGreen = new LongAgent<>(engine);
graphAgentGreen.setGraphEnvironment(longAgentsEnvironment, 100);
graphAgentGreen.jumpTo(a43, 50, List.of(a64));		

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 100);

The following code will throw an exception because the agents will have non-zero overlap on arcs a43 and a65:

graphAgentRed.jumpTo(a43, 50, List.of(a64));
Exception text
Can not jump to arcs with other agents overlay, such as: [ArcFragment [X=200.0 Y=200.0 -> X=300.0 Y=300.0 1 agents (0.0; 50.0)], ArcFragment [X=100.0 Y=100.0 -> X=200.0 Y=200.0 1 agents (91.42135623730951; 141.4213562373095)]]

A long agent can change its orientation by calling the turnAround method, in which case the head and tail of the agent will change their positions in the graph. Thus, the head will be located where the tail was, and the tail — where the head was:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 250);
graphAgentRed.jumpTo(a34, 100, List.of(a23, a12));		
graphAgentRed.turnAround();
Example of turning around

So, to change the orientation of a long agent in a graph, you can use the turnAround method, which swaps the head and tail positions of the agent. However, you can only do this if every arc of the graph that the long agent is occupying has a reverse arc. In this case, the long agent will be located on the reverse arcs, relative to the arcs it was originally on:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 200);
graphAgentRed.jumpTo(a74, 100, List.of(a57));		

The following code will throw an error, as the arc a74 doesn’t have a reverse arc:

graphAgentRed.turnAround();
Exception text
Trying to turn the long agent around when it is on a monodirectional arc: Arc(X=300.0 Y=100.0 -> X=200.0 Y=200.0 1 agents): Node(X=300.0 Y=100.0 0 agents) -> Node(X=200.0 Y=200.0 0 agents)

4. Movement of long agents

A long agent is an oriented agent that can only move in the direction corresponding to the "tail to head" direction, so when the agent moves, its head is always ahead of its tail. In other words, the agent always moves in the direction of its head.

If the agent needs to move in the opposite direction, then its orientation needs to be changed before starting to move. Such change is called turning around and is similar to the result of calling long agent’s turnAround method.

When searching for the shortest path in the methods moveTo and moveToCloset, the two shortest paths to the destination point are compared: the on from the agent’s head and the one from its tail. The shorter one is chosen, and if the shorter path is the one from the agent’s tail, then the agent changes its orientation at the beginning of the movement.

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentRed.jumpTo(a21, 30);		
graphAgentRed.moveTo(n5, 2);
Example of moving from tail

The path from the tail of the agent can only be found if it is possible for the agent to turn around, i.e. all the arcs on which the agent is currently located have reverse arcs.

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentRed.jumpTo(a74, 30);		
graphAgentRed.moveTo(n5, 2);
Example of moving from head

In the LongAgent class, there is an option to disable the search for the shortest path from the agent’s tail. In this case, the path search will only be performed from the agent’s head:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentRed.jumpTo(a21, 30);		
graphAgentRed.setPossibleStartMoveFromTail(false);
graphAgentRed.moveTo(n5, 2);
Example of moving from head

During the movement of the long agent, self-intersections with non-zero length are not allowed, but intersecting itself or another agent at a node in the graph is allowed. Let’s create a function for generating a simple path:

AgentGraphPath<LongAgentGraphNodeImpl, LongAgentGraphArcImpl> getPath(GeometricGraphPosition<LongAgentGraphNodeImpl, LongAgentGraphArcImpl> sourcePos, List<Graph<LongAgentGraphNodeImpl, LongAgentGraphArcImpl>.Arc> expectedArcs) {
	var path = expectedArcs.isEmpty() ? new  GraphPath<>(sourcePos.getNode()) : new GraphPath<LongAgentGraphNodeImpl, LongAgentGraphArcImpl>(expectedArcs);
	double weight = expectedArcs.stream().mapToDouble(a -> a.getValue().getLength()).sum();
	var destPosition = expectedArcs.isEmpty() ? sourcePos : new GeometricGraphPosition<>(expectedArcs.get(expectedArcs.size() - 1).getDest());
	var agentGraphPath = new AgentGraphPath<>(new GeometricGraphPath<>(path, sourcePos, destPosition));
	agentGraphPath.setWeight(weight);
	return agentGraphPath;
}

Movement with self-intersection of zero length:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 700);
var sourcePosition = new GeometricGraphPosition<>(a96, 701);
graphAgentRed.jumpTo(sourcePosition);		
graphAgentRed.moveAlongPath(getPath(sourcePosition, List.of(a96, a64, a43, a35, a57, a74, a41)), 10);
Example of self overlay with zero length

Movement of the agent to a position inside the body of the agent (between its tail and head) using the methods moveTo, moveToClosest, moveAlongPath is prohibited:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 150);
graphAgentRed.jumpTo(a23, 55, List.of(a12));		
Example of moving to node inside agent

The following code will throw an exception, since node n2 is located inside the agent’s body:

graphAgentRed.moveTo(n2, 2);
Exception text
Dest positions should not be inside agent, such as: [{Node(X=200.0 Y=300.0 0 agents)}]

If the long agent is forced to move through a non-zero segment of an arc that is already occupied by itself during its movement, it will stop before such a segment to prevent self-overlap, and the onOtherAgentReached callback method will be called:

clearGraphEnvironment(longAgentsEnvironment, engine);

graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 700);
sourcePosition = new GeometricGraphPosition<>(a96, 701);
graphAgentRed.jumpTo(sourcePosition);		
graphAgentRed.moveAlongPath(getPath(sourcePosition, List.of(a96, a64, a43, a35, a57, a74, a43)), 10);
Example of self overlay with non zero length

If the weight of the shortest path is equal to infinity, the path is not considered correct, and a RuntimeException will be thrown:

5. Interaction of long agents

'Agents reaching each other' is the moment when, during their movement, agents overlap each other with zero overlap length - for example, when the tail/head of one agent has the same position (node or point on an arc) as the tail, head, or body of another agent. When agents reach each other, the moving agent (or both of them) invoke the callback method onOtherAgentReached, if the other agent is located on the head side of the current one.

'Agent collision' is a moment during movement when, if agents continue moving with the same speed and direction, they will overlap on parts of the graph’s arcs that are reverse of each other. When a collision occurs, the velocity of one or both agents will be changed, and the moving agents will invoke its onCollidedWithOtherAgent callback method if the other agent is on its head side.

An agent can collide only with one other agent at a time. Collisions between agents always occur after they reach each other.

Inside each long agent there are three variables:

  • aheadAgent - an agent that collided with the current agent located on the side of the current agent’s head. Can be retrieved by calling the getAheadAgent method.

  • behindAgent - an agent that collided with the current agent located on the side of the current agent’s tail. Can be retrieved by calling the getBehindAgent method.

  • adjacentAgents - a collection that contains agents that collided with the current agent. This can be either the body or the head/tail of the agent, if they are located at a node of the graph. It is possible for an agent to be simultaneously in more than one of the above three variables of another agent. Note that adjacent agents are grouped by nodes, so for each specific node, a list of adjacent agents can be retrieved by calling the getAdjacentAgents method.

For example, in the case when agents are placed in such a way that they block each other, and then each of them needs to move forward - there will be a sequential collision of agents with each other.

The behavior of agents during collision is as follows:

If agent A and agent B collide head-on, aheadAgent of agent B is agent A, and aheadAgent of agent A is agent B. The speeds of both agents become zero until it is possible to continue moving in the same direction without causing intersection of agents with non-zero length. This, in its turn, can happen if one of the agents changes its orientation and starts moving in the opposite direction, after which the other agent’s speed returns to its original value.

clearGraphEnvironment(longAgentsEnvironment, engine);
		
graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentRed.jumpTo(new GeometricGraphPosition<>(a12, 20));		
graphAgentRed.moveTo(n3, 2);

graphAgentGreen = new LongAgent<>(engine);
graphAgentGreen.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentGreen.jumpTo(new GeometricGraphPosition<>(a32, 20));		
graphAgentGreen.moveTo(n1, 2);

var timeoutActionAgentHeadCollision = graphAgentGreen;
engine.scheduleAbsolute(70, () -> timeoutActionAgentHeadCollision.moveTo(n4, 5));
Example of head to head collision

If agent A collides with the tail of agent B, the behindAgent of agent B becomes agent A, and the aheadAgent of agent A becomes agent B. The velocity of agent B remains unchanged. The velocity of agent A becomes equal to the velocity of agent B until it is possible to continue moving in the same direction without intersecting agents of non-zero length. After that, agent A returns to its original velocity.

clearGraphEnvironment(longAgentsEnvironment, engine);
		
graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentRed.jumpTo(new GeometricGraphPosition<>(a12, 20));		
graphAgentRed.moveTo(n5, 2);

graphAgentGreen = new LongAgent<>(engine);
graphAgentGreen.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentGreen.jumpTo(new GeometricGraphPosition<>(a23, 20));		
graphAgentGreen.moveTo(n5, 2);

var timeoutActionAgentTailCollision = graphAgentGreen;
engine.scheduleAbsolute(14, () -> timeoutActionAgentTailCollision.setVelocity(15));
Example of head to tail collision

If agent A collides with the body of agent B, agent A is added to the adjacentAgents collection of agent B, and agent B is added to the aheadAgent variable of agent A. The speed of agent B remains unchanged. The speed of agent A becomes zero until there is an opportunity to continue moving in the same direction without causing intersection of agents with non-zero length, after which agent A returns to its initial speed:

clearGraphEnvironment(longAgentsEnvironment, engine);
		
graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 350);
graphAgentRed.jumpTo(new GeometricGraphPosition<>(a14, 50), List.of(a21, a32, a53));		
graphAgentRed.moveTo(n9, 0.5);

graphAgentGreen = new LongAgent<>(engine);
graphAgentGreen.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentGreen.jumpTo(new GeometricGraphPosition<>(a34, 40));		
graphAgentGreen.moveTo(n2, 2);

LongAgent<LongAgentGraphNodeImpl, LongAgentGraphArcImpl> graphAgentBlue = new LongAgent<>(engine);
graphAgentBlue.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentBlue.jumpTo(new GeometricGraphPosition<>(a113, 40));		
graphAgentBlue.moveTo(n2, 2);

LongAgent<LongAgentGraphNodeImpl, LongAgentGraphArcImpl> graphAgentYellow = new LongAgent<>(engine);
graphAgentYellow.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentYellow.jumpTo(new GeometricGraphPosition<>(a101, 40));		
graphAgentYellow.moveTo(n4, 2);
Example of head to body collision

The turnAround and jumpTo methods called on the agent clear its variables related to collisions (aheadAgent, behindAgent, and adjacentAgents) and remove this agent from similar collections of agents it collided with before.

clearGraphEnvironment(longAgentsEnvironment, engine);
		
graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 350);
graphAgentRed.jumpTo(new GeometricGraphPosition<>(a14, 50), List.of(a21, a32, a53));		
graphAgentRed.moveTo(n9, 0.5);

graphAgentGreen = new LongAgent<>(engine);
graphAgentGreen.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentGreen.jumpTo(new GeometricGraphPosition<>(a34, 40));		
graphAgentGreen.moveTo(new GeometricGraphPosition<>(a53, 10), 2);

graphAgentBlue = new LongAgent<>(engine);
graphAgentBlue.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentBlue.jumpTo(new GeometricGraphPosition<>(a113, 40));		
graphAgentBlue.moveTo(n2, 2);

graphAgentYellow = new LongAgent<>(engine);
graphAgentYellow.setGraphEnvironment(longAgentsEnvironment, 20);
graphAgentYellow.jumpTo(new GeometricGraphPosition<>(a101, 40));		
graphAgentYellow.moveTo(n4, 2);

var timeoutActionAgentBodyCollision = graphAgentRed;
engine.scheduleAbsolute(38, () -> timeoutActionAgentBodyCollision.jumpTo(a96, 350));
Example of head to body collision and jump

Adding agents to collision variables is only possible during movement: if an agent was instantly placed close to another agent by some other means (for example, using the jumpTo method), then such an event is not considered a collision and thus does not result in a change of the collision variables.

6. Special cases and exceptions

In cases where multiple agents collide with some other agent at the same node of the graph, after that node becomes available, all colliding agents will move sequentially - one after the other, taking turns passing through the node, even if they can move through it and through each other with zero overlap.

clearGraphEnvironment(longAgentsEnvironment, engine);
		
graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(longAgentsEnvironment, 350);
graphAgentRed.jumpTo(new GeometricGraphPosition<>(a14, 50), List.of(a21, a32, a53));		
graphAgentRed.moveTo(n9, 0.5);

graphAgentGreen = new LongAgent<>(engine);
graphAgentGreen.setGraphEnvironment(longAgentsEnvironment, 75);
graphAgentGreen.jumpTo(new GeometricGraphPosition<>(a43, 75));		
graphAgentGreen.moveTo(new GeometricGraphPosition<>(a53, 10), 2);

graphAgentBlue = new LongAgent<>(engine);
graphAgentBlue.setGraphEnvironment(longAgentsEnvironment, 75);
graphAgentBlue.jumpTo(new GeometricGraphPosition<>(a113, 75));		
graphAgentBlue.moveTo(n2, 2);
Example of head to body collision

To discuss the next few cases we will need to use a different long agents environment. Let us create an instance of LongAgentsEnvironment:

LongAgentsEnvironment<LongAgentGraphNodeImpl, LongAgentGraphArcImpl, LongAgentWeightKey> exceptionCasesEnvironment = new LongAgentsEnvironment<>();

Then, we create the points of the graph nodes:

var pEc1 = new Point(100, 200);
var pEc2 = new Point(300, 200);
var pEc3 = new Point(500, 200);
var pEc4 = new Point(600, 200);
var pEc5 = new Point(600, 300);
var pEc6 = new Point(500, 300);
var pEc7 = new Point(300, 100);

We create the nodes and add them to our instance of LongAgentsEnvironment:

var nEc1 = exceptionCasesEnvironment.addNode(new LongAgentGraphNodeImpl(pEc1));
var nEc2 = exceptionCasesEnvironment.addNode(new LongAgentGraphNodeImpl(pEc2));
var nEc3 = exceptionCasesEnvironment.addNode(new LongAgentGraphNodeImpl(pEc3));
var nEc4 = exceptionCasesEnvironment.addNode(new LongAgentGraphNodeImpl(pEc4));
var nEc5 = exceptionCasesEnvironment.addNode(new LongAgentGraphNodeImpl(pEc5));
var nEc6 = exceptionCasesEnvironment.addNode(new LongAgentGraphNodeImpl(pEc6));
var nEc7 = exceptionCasesEnvironment.addNode(new LongAgentGraphNodeImpl(pEc7));

After that, we create and add the arcs:

var aEc12aEc21 = exceptionCasesEnvironment.addArc(nEc1.getValue(), nEc2.getValue(), new LongAgentGraphArcImpl(new Polyline(pEc1, pEc2)), new LongAgentGraphArcImpl(new Polyline(pEc2, pEc1)));		
var aEc12 = aEc12aEc21.first;
var aEc21 = aEc12aEc21.second;

var aEc23aEc32 = exceptionCasesEnvironment.addArc(nEc2.getValue(), nEc3.getValue(), new LongAgentGraphArcImpl(new Polyline(pEc2, pEc3)), new LongAgentGraphArcImpl(new Polyline(pEc3, pEc2)));		
var aEc23 = aEc23aEc32.first;
var aEc32 = aEc23aEc32.second;

var aEc34aEc43 = exceptionCasesEnvironment.addArc(nEc3.getValue(), nEc4.getValue(), new LongAgentGraphArcImpl(new Polyline(pEc3, pEc4)), new LongAgentGraphArcImpl(new Polyline(pEc4, pEc3)));	
var aEc34 = aEc34aEc43.first;
var aEc43 = aEc34aEc43.second;

var aEc45aEc54 = exceptionCasesEnvironment.addArc(nEc4.getValue(), nEc5.getValue(), new LongAgentGraphArcImpl(new Polyline(pEc4, pEc5)), new LongAgentGraphArcImpl(new Polyline(pEc5, pEc4)));	
var aEc45 = aEc45aEc54.first;
var aEc54 = aEc45aEc54.second;

var aEc56aEc65 = exceptionCasesEnvironment.addArc(nEc5.getValue(), nEc6.getValue(), new LongAgentGraphArcImpl(new Polyline(pEc5, pEc6)), new LongAgentGraphArcImpl(new Polyline(pEc6, pEc5)));	
var aEc56 = aEc56aEc65.first;
var aEc65 = aEc56aEc65.second;

var aEc63aEc36 = exceptionCasesEnvironment.addArc(nEc6.getValue(), nEc3.getValue(), new LongAgentGraphArcImpl(new Polyline(pEc6, pEc3)), new LongAgentGraphArcImpl(new Polyline(pEc3, pEc6)));	
var aEc63 = aEc63aEc36.first;
var aEc36 = aEc63aEc36.second;

var aEc27aEc72 = exceptionCasesEnvironment.addArc(nEc2.getValue(), nEc7.getValue(), new LongAgentGraphArcImpl(new Polyline(pEc2, pEc7)), new LongAgentGraphArcImpl(new Polyline(pEc7, pEc2)));		
var aEc27 = aEc27aEc72.first;
var aEc72 = aEc27aEc72.second;		

It is possible that during movement along the shortest path, an agent will have to overlap with itself on reversible edges of the graph, if the path contains loops. In this case, the agent will stop before the moment of overlap, and a collision of the agent with itself will occur:

clearGraphEnvironment(exceptionCasesEnvironment, engine);
		
graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(exceptionCasesEnvironment, 450);
graphAgentRed.jumpTo(new GeometricGraphPosition<>(aEc34, 80), List.of(aEc23, aEc12));		
graphAgentRed.moveTo(nEc7, 2);
Example of exception case

Consider an example of the shortest path containing the arc that is reversed to the initial long agent arc:

clearGraphEnvironment(exceptionCasesEnvironment, engine);
		
graphAgentRed = new LongAgent<>(engine);
graphAgentRed.setGraphEnvironment(exceptionCasesEnvironment, 100);
graphAgentRed.jumpTo(new GeometricGraphPosition<>(aEc23, 50), List.of(aEc12));	
Example of exception case 2

The following code will throw an exception because the possible path goes through an arc that is reversed with respect to the arc from which the movement started:

graphAgentRed.moveTo(nEc7, 2);
Exception text
Path for graph agent not found from its current position of {Arc(X=300.0 Y=200.0 -> X=500.0 Y=200.0 1 agents): Node(X=300.0 Y=200.0 0 agents) -> Node(X=500.0 Y=200.0 0 agents) [50.0]} to either of possible destination positions [{Node(X=300.0 Y=100.0 0 agents)}]