-1

I'm trying to play a little bit with Optaplanner and created a simple project using Optaplanner spring-boot-starter to solve a simple VRP problem with only one hard constraint for the moment.

My domain model is pretty simple, a vehicle class with volume, weight capacity and order class with volume and weight values.

@Getter
@Setter
@ToString
@PlanningEntity
public class Vehicle {

    @PlanningId
    private Long id;

    private int weightCapacity;
    private int volumeCapacity;
    private double guaranteeCheck;

    @PlanningListVariable(valueRangeProviderRefs = "orderRange")
    private List<Order> orders;

    public Vehicle() {
    }

    public Vehicle(long id, int volume, int weight) {
        this.id = id;
        this.volumeCapacity=volume;
        this.weightCapacity=weight;
    }

    public int getTotalVolumeCapacity() {
        int totalDemand = 0;
        for (Order order : orders) {
            totalDemand += order.getVolume();
        }
        return totalDemand;
    }

    public int getTotalWeightCapacity(){
        int totalDemand = 0;
        for (Order order : orders) {
            totalDemand += order.getWeight();
        }
        return totalDemand;
    }
}

@Getter
@Setter
@ToString
public class Order {
    private Long id;
    private String reference;
    private double weight;
    private double volume;
//    private double amount;
    private Location location;

    public Order() {
    }

    public Order(long id, int volume, int weight, Location location) {
        this.id = id;
        this.reference = this.id.toString();
        this.volume = volume;
        this.weight = weight;
        this.location = location;
    }
}

I've created a vrp solution class as follow:

@NoArgsConstructor
@AllArgsConstructor
@PlanningSolution
@Getter
@Setter
public class VehicleRoutingSolution {

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "orderRange")
    private List<Order> orders;

    @ValueRangeProvider
    @PlanningEntityCollectionProperty
    private List<Vehicle> vehicles;

    @PlanningScore
    private HardSoftLongScore score;

    public VehicleRoutingSolution(List<Order> orders, List<Vehicle> vehicles) {
        this.orders = orders;
        this.vehicles = vehicles;
    }
}

The problem here is pretty simple, I'm trying to assign orders to vehicles based on a hard constraint formulated as follow based on the quarkus quickstart official example:

public class VehicleRoutingConstraintProvider implements ConstraintProvider {
    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[]{
                // Hard constraints
                vehicleCapacity(constraintFactory),
                // Soft constraints
        };
    }



    protected Constraint vehicleCapacity(ConstraintFactory factory) {
        return factory.forEach(Vehicle.class)
                .filter(vehicle -> vehicle.getTotalVolumeCapacity() > vehicle.getVolumeCapacity())
                .penalizeLong(HardSoftLongScore.ONE_HARD,
                              vehicle -> vehicle.getTotalVolumeCapacity() - vehicle.getVolumeCapacity())
                .asConstraint("vehicleCapacity");
    }
}

When I launch the solver to find a solution on my dataset, it displays the following issue:

Cannot invoke "java.util.List.size()" because the return value of "org.optaplanner.core.impl.domain.variable.descriptor.ListVariableDescriptor.getListVariable(Object)" is null

I'm thinking about either a non initialization of one of my solution variables correctly or something did changed with the latest version (available since 22 April).

The solver execution code is as follow:

List<Order> orders = createOrdersDataSet();
        List<Vehicle> vehicles = createVehicleDataSet();

        UUID problemId = UUID.randomUUID();
        VehicleRoutingSolution problem = new VehicleRoutingSolution(orders, vehicles);
        // Submit the problem to start solving
        SolverJob<VehicleRoutingSolution, UUID> solverJob = solverManager.solve(problemId, problem);
        VehicleRoutingSolution solution;
        try {
            // Wait until the solving ends
            solution = solverJob.getFinalBestSolution();
        } catch (InterruptedException | ExecutionException e) {
            throw new IllegalStateException("Solving failed.", e);
        }

        log.info("Your best score is: {}", solution.getScore());
Habchi
  • 1,921
  • 2
  • 22
  • 50
  • @HovercraftFullOfEels Yes. Sorry for that. The problem actually that I'm facing is a little bit weird cause the error is not explicit enough to give any possible indication on the code I've wrote. This is why I didn't knew what block code to share here directly and though about sharing a complete example. Let me know what modifications to do and I'll gladly apply them. – Habchi Apr 23 '23 at 23:46
  • Believe me it's been two days that I'm reading examples all over the net. The version that I'm using in my code also was posted two days ago, so it might also be a problem with the new version itself and I already created an issue on the library repository issue tracker. I'll remove the link and provide it afterwards if anyone asks for it and reformulate my answer. Thanks – Habchi Apr 23 '23 at 23:51
  • You need to add the full stack trace of the exception to your question. – tgdavies Apr 24 '23 at 03:35

1 Answers1

2

The error message is, in fact, quite self-explanatory - your list variable is null. The issue will go away if you declare your planning variable like so:

@PlanningListVariable(valueRangeProviderRefs = "orderRange")
private List<Order> orders = new ArrayList<>();

Alternatively, you can set the field at runtime before starting your solver.

Lukáš Petrovický
  • 3,945
  • 1
  • 11
  • 20
  • You're absolutely right. Weird however because the same code with quarkus doesn't initialize the planningListVariable and still works like a charm. Now the problem is that the solution returned is always 0Hard/0Soft with all orders as init – Habchi Apr 25 '23 at 01:42