0

I am trying to solve an optimization problem, using the CP-Sat solver of Ortools, that is very similar to a standard Pickup and Delivery example but with a twist that I cannot figure out how to solve. A simplified version of my problem would be:

I have some containers that needs to be picked up, by a single vehicle, and moved to another area with pre-allocated "parking-lots". The task is to find the minimal travel distance of the vehicle. Each pickup can be dropped off on any of the parking lots in the other area. I have so far managed to solve this by utilizing AddCircuit, on a graph with a start node with edges going out to all pickup nodes, and with each pickup node having edges going out to all parking nodes and modelling the possible edges as booleans.

Example: start node (0), two pickups (1,2) and two parking lots (3,4). See figure. Illustration of shortest path (red arrows) for pickup and parking of two containers. Possible paths are light grey.

A twist to the problem that I cannot figure out how solve, is that there should be an ordering of the parkings. Imagine the parking lot is a tunnel with one open end. I.e. I cannot park at the bottom of the tunnel if a container has already been parked in beginning of the tunnel. For the example attached, I cannot park a container on node 4 if node 3 has already had a container parked. In this case the attached minimal path solution (0->1->3->2->4) is not valid but should rather be 0->2->4->1->3->0 (also being the only feasible path for this simple example).

I would appreciate any help I could get on this.

A solution to the example without the ordering of the parkings are shown below:

import numpy as np
from ortools.sat.python import cp_model


model = cp_model.CpModel()
solver = cp_model.CpSolver()


start_node = (0, 0)
pickup_nodes = [(-1, 1), (1, 3)]
drop_off_nodes = [(-1, 2), (1, 4)]
all_nodes = [start_node] + pickup_nodes + drop_off_nodes
pickup_indices = [1, 2]
drop_off_indices = [3, 4]

scale = 100  # scale to solve rounding problem
# using euclidean distance
distances = [
    [int(scale * np.sqrt((n1[0] - n2[0]) ** 2 + (n1[1] - n2[1]) ** 2)) for n2 in all_nodes]
    for n1 in all_nodes
]

literals = {}
all_arcs = []
# start-> pickup
for i in pickup_indices:
    literals[0, i] = model.NewBoolVar(f"{0} -> {i}")  # start arc
    all_arcs.append((0, i, literals[0, i]))

# pickup -> drop off
for i in pickup_indices:
    for j in drop_off_indices:
        literals[i, j] = model.NewBoolVar(f"{i} -> {j}")
        all_arcs.append((i, j, literals[i, j]))

# drop off -> pickup
for i in drop_off_indices:
    for j in pickup_indices:
        literals[i, j] = model.NewBoolVar(f"{i} -> {j}")
        all_arcs.append((i, j, literals[i, j]))

# drop off -> start
for i in drop_off_indices:
    literals[i, 0] = model.NewBoolVar(f"{i} -> {0}")
    all_arcs.append((i, 0, literals[i, 0]))


model.AddCircuit(all_arcs)
model.Minimize(sum(literals[i, j] * distances[i][j] for i, j in literals))
solver.Solve(model)
print(f"Travel distance: {solver.ObjectiveValue()}")

# print path
start_node = 0
node_idx = 0
print(node_idx, end="")
full_circuit = False
while not full_circuit:
    for i, pos in enumerate(all_nodes):
        if (node_idx, i) in literals and solver.Value(literals[(node_idx, i)]):
            print(f" -> {i}", end="")
            node_idx = i
            break
    if node_idx == start_node:
        full_circuit = True

N.B: This question has also been posted on https://groups.google.com/g/or-tools-discuss/c/xazcgayBUok

Kaqmak
  • 11
  • 4
  • 1
    What exactly is your question? – mkrieger1 May 17 '22 at 09:07
  • I would like to impose constraints on the ordering of drop offs. E.g. dropoff on 4 should happen before drop off on 3. – Kaqmak May 18 '22 at 13:42
  • Okay, what is your question about imposing such contraints? – mkrieger1 May 18 '22 at 13:42
  • Without constraint on drop off ordering: shortest path `0->1->3->2->4->0`. With constraint on drop off ordering: shortest path `0->2->4->1->3->0` How do I implement the constraints such that I get the latter solution? – Kaqmak May 18 '22 at 13:46
  • Are you asking *which* contraints you should impose? Or are you having difficulties with some technicalities related to imposing constraints? – mkrieger1 May 18 '22 at 13:48
  • I guess I am asking how to impose the constraint. For this simple case I could do something like `model.AddBoolOr([literal[3,2].Not() , literal[2,4].Not()])` and `model.AddBoolOr([literal[3,1].Not() , literal[1,4].Not()])` , however, in the more general case of N pickups and N dropoffs I cannot figure out how to account for all the pathways going from dropoff `i` to dropoff `j` (i.e. `i` is dropped off before `j` is dropped off) – Kaqmak May 19 '22 at 13:57

0 Answers0