Step 3: Pallet Position Simulation

The warehouse is designed for storing goods on pallets. Let’s simulate this.

The PalletPosition class will represent the place where a pallet can be stored. After a while, we’ll have one more entity that is capable of storing pallets - a truck. Thus, let’s create an interface to generalize a pallet container.

PalletContainer.java
package com.company.warehouse.simulation;

import com.company.warehouse.simulation.graph.Node;

/**
 * A container that can store pallets. Pallets could be put or taken to/from a container.
 * Is located in a graph node.
 */
public interface PalletContainer {

    /**
     * @param loading  do we check loading (<code>true</code>) or unloading (<code>false</code>) availability
     * @return  if it is possible to load (or unload) one pallet to (or from) the container.
     */
    boolean isAvailableFor(boolean loading);

    /**
     * Loads (or unloads) one pallet to (or from) the container.
     * @param loading  perform loading (<code>true</code>) or unloading
     */
    void placePallet(boolean loading);

    Node getNode();

}

PalletPosition resides in a graph node.

PalletPosition.java
package com.company.warehouse.simulation;

import com.company.warehouse.simulation.graph.Node;

public class PalletPosition implements PalletContainer {

    private final Node node;

    /**
     * Whether a pallet is present here.
     */
    private boolean busy;

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

    @Override
    public Node getNode() {
        return node;
    }

    public boolean isBusy() {
        return busy;
    }

    @Override
    public boolean isAvailableFor(boolean loading) {
        return busy != loading;
    }

    @Override
    public void placePallet(boolean loading) {
        if (busy == loading) {
            throw new RuntimeException("Can't " + (busy ? "put to a busy" : "take from a free") + " position");
        }
        busy = loading;
    }

    @Override
    public String toString() {
        return "PalletPosition [" + node + ", busy=" + busy + "]";
    }
}

Like we did for the forklifts, we will design a shape for pallet positions.

PalletPositionShape.java
package com.company.warehouse.application.animation;

import java.awt.Color;

import com.amalgamasimulation.animation.shapes.shapes2d.GroupShape;
import com.amalgamasimulation.animation.shapes.shapes2d.RectangleShape;
import com.amalgamasimulation.geometry.Point;
import com.company.warehouse.simulation.PalletPosition;

public class PalletPositionShape extends GroupShape {

    public PalletPositionShape(PalletPosition p) {
        super(p.getNode().getPoint());
        withShape(new RectangleShape(() -> new Point(-5, -5), () -> 10.0, () -> 10.0)
                .withLineColor(Color.gray)
                .withFillColor(() -> p.isAvailableFor(false) ? Color.green : Color.gray)
                );
    }

}

Use the new shapes in the SimulationPart.onShowModel() method before the forklifts:

SimulationPart.java
    private void onShowModel(Model model) {
...
            model.getArcs().forEach(r -> animationView.addShape(new ArcShape(r)));      
            model.getAllPositions().forEach(p -> animationView.addShape(new PalletPositionShape(p)));
            model.getForklifts().forEach(f -> animationView.addShape(new ForkliftShape(f)));
        }
        animationView.adjustWindow();
    }

Let’s set our pallet positions in the model.

Model.java, new fields
    private List<PalletPosition> allPositions = new ArrayList<>();
    public Iterable<PalletPosition> getAllPositions() {
        return allPositions;
    }

Here we added a container property for storing all the positions.

Model.java, new methods
    /**
     * @param probability  requested probability of the <code>true</code> value.
     * @return  pseudo-random boolean
     */
    public boolean randomTrue(double probability){
        return random.nextDouble() < probability;
    }

    private PalletPosition newPalletPosition(com.company.warehouse.datamodel.Node scenarioNode, boolean busy) {
        var node = mapping.nodesMap.get(scenarioNode);
        var p = new PalletPosition(node);
        if (busy) {
            p.placePallet(true);
        }
        allPositions.add(p);
        return p;
    }

    private void initializeMainStorage() {
        scenario.getStoragePlaces().stream()
                .forEach(scenarioNode -> newPalletPosition(scenarioNode, randomTrue(0.5)));
    }

    private void initializeGates() {
        for (var scenarioGate : scenario.getGates()) {
            scenarioGate.getPlaces().stream()
                    .forEach(scenarioNode -> newPalletPosition(scenarioNode, randomTrue(0.5)));
        }
    }

Randomly busy pallet positions are created in the main storage area and at every gate.

Invoke these methods from the constructor.

    public Model(Engine engine, Scenario scenario) {
...
        graphEnvironment = new GraphEnvironment<>();
//      engine.scheduleRelative(0, () -> getAgents().forEach(a -> dispatchAgent(a)));
        initializeNodes();
        initializeArcs();
        initializeMainStorage();
        initializeGates();
        initializeForklifts();
        
        makeAssignments();
    }

Let’s launch the simulation and see many cute green boxes scattered chaotically around the warehouse.

Simulation

But what is this … ? The forklifts are passing through the boxes:

Forklift ignores occupied pallet places

In this screenshot, the forklift marked with the red circle is moving upwards, so it has just passed through a node occupied by a (green) pallet.

The forklift right below the marked one is OK - it has arrived at the node with a pallet but has not passed through it.

This behavior does not look realistic. What can we do about it? …