Step 4: Pallet-aware environment

As you may recall, Forklift inherits the GraphAgent class. When the moveTo() method of this class is called, the optimal route for the upcoming movement through the graph is calculated.

We can inherit the GraphEnvironment class in such a way that pallets are considered as obstacles when calculating routes.

EnvironmentWithPallets.java
package com.company.warehouse.simulation.graph;

import com.amalgamasimulation.graphagent.GraphEnvironment;

public class EnvironmentWithPallets extends GraphEnvironment<Node, Arc, Void> {

    private static final double BUSY_NODE_WEIGHT = 10_000;

    @Override
    public double getNodeWeight(Node node, Void k) {
        var busy = node.getPalletPosition()
            .map(p -> p.isAvailableFor(false))
            .orElse(false);

        return busy ? BUSY_NODE_WEIGHT : 0;
    }

}

GraphAgent wants to travel the shortest path, so, by adding a weight of 10000 to the nodes with pallets, we increase the calculated length of the paths passing through these nodes. Thus, the routes that avoid obstacles are considered "shorter" than the routes passing through pallets.

Since we used node.getPalletPosition(), we have to implement it. Open the .simulation.graph.Node class.

Node.java
public class Node extends AgentGraphNodeImpl {

    private final String id;

    private PalletPosition palletPosition;

    public Node(Point point, String id) {
        super(point);
        this.id = id;
    }

    public Optional<PalletPosition> getPalletPosition() {
        return Optional.ofNullable(palletPosition);
    }

    public void setPalletPosition(PalletPosition palletPosition) {
        Objects.requireNonNull(palletPosition);

        if (this.palletPosition != null) {
            throw new RuntimeException("Can't assign " + palletPosition + " to " + this + " as " + this.palletPosition + " already assigned!");
        }

        this.palletPosition = palletPosition;
    }

    @Override
    public String toString() {
        return id;
    }
}

By the way, we also added a new id field to simplify debugging and monitoring.

We need to tell the node about the PalletPosition that resides in it.

PalletPosition.java
...
    public PalletPosition(Node node) {
        this.node = node;
        node.setPalletPosition(this);
    }
...

Finally, we need to initialize all these new things in the Model class.

Define & instantiate the environment:

Model.java
...
public class Model extends com.amalgamasimulation.engine.Model {
...
//  protected GraphEnvironment<Node, Arc, Agent> graphEnvironment;
    private EnvironmentWithPallets graphEnvironment;
    public EnvironmentWithPallets getGraphEnvironment() {
        return graphEnvironment;
    }
...
    public Model(Engine engine, Scenario scenario) {
...
//      graphEnvironment = new GraphEnvironment<>();
        graphEnvironment = new EnvironmentWithPallets();
        initializeNodes();
        initializeArcs();
        initializeMainStorage();
        initializeGates();
        initializeForklifts();
        
        makeAssignments();
    }
...

Pass id to the Node constructor:

Model.java
...
    public Node addNode(Point point, String id) {
        Node node = new Node(point, id);
        graphEnvironment.addNode(node);
        return node;        
    }
...
    private void initializeNodes() {
        for (var scenarioNode : scenario.getNodes()) {
            Node node = addNode(new Point(scenarioNode.getX(), scenarioNode.getY()), scenarioNode.getId());
            mapping.nodesMap.put(scenarioNode, node);
        }
    }
...

Now launch the simulation and take pride in the success.

See how the trajectory of the top-left forklift does not pass through any occupied 🟩 pallet positions anymore:

Forklift respects occupied pallet places