ForkliftTravelDistanceConstraint.java

package com.v1rex.liftnexus.planning.constraints;

import ai.timefold.solver.core.api.score.HardSoftScore;
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import com.v1rex.liftnexus.forklift.domain.Forklift;
import com.v1rex.liftnexus.storagebin.domain.Coordinate3D;
import com.v1rex.liftnexus.transportorder.domain.TransportOrder;

public class ForkliftTravelDistanceConstraint {

  // TODO: [PERFORMANCE OPTIMIZATION - MILESTONE 2 PREPARATION]
  // This O(N) loop on Forklift.class works perfectly for the MVP's small datasets.
  // However, it triggers a full chain recalculation on every micro-move
  public static Constraint forkliftTravelDistance(ConstraintFactory factory) {
    return factory
        .forEach(Forklift.class)
        .filter(
            forklift ->
                forklift.getTransportOrders() != null && !forklift.getTransportOrders().isEmpty())
        .penalize(
            HardSoftScore.ONE_SOFT, ForkliftTravelDistanceConstraint::calculateTotalTravelDistance)
        .asConstraint("Forklift travel distance");
  }

  private static int calculateTotalTravelDistance(Forklift forklift) {
    int totalTraveledDistance = 0;

    Coordinate3D currentLocationForklift = forklift.getCurrentStorageBin().getCoordinate();

    for (TransportOrder order : forklift.getTransportOrders()) {
      Coordinate3D sourceLocation = order.getSourceBin().getCoordinate();
      Coordinate3D targetLocation = order.getTargetBin().getCoordinate();

      totalTraveledDistance += (int) currentLocationForklift.calculateDistance(sourceLocation);

      totalTraveledDistance += (int) sourceLocation.calculateDistance(targetLocation);

      currentLocationForklift = targetLocation;
    }
    return totalTraveledDistance;
  }
}