I am trying to understand how OR-TOOLS works and wanted to solve the assignment problem but with number of tasks (2 out of 4) as constraints. But my code does not work as expectected.
I have tried:
from ortools.sat.python import cp_model
costs = [
[90, 80, 75, 70],
[35, 85, 55, 65],
[125, 95, 90, 95],
[45, 110, 95, 115],
[50, 100, 90, 100],
]
num_workers = len(costs)
num_tasks = len(costs[0])
# Model
model = cp_model.CpModel()
# Variables
x = []
for i in range(num_workers):
t = []
for j in range(num_tasks):
t.append(model.NewBoolVar(f'x[{i},{j}]'))
x.append(t)
# Constraints
# Each worker is assigned to exactly one task.
for worker in range(num_workers):
model.AddExactlyOne(x[worker][task] for task in range(num_tasks))
# Choosing number of tasks.
for worker in range(num_workers):
model.Add(sum([x[worker][task] for task in range(num_tasks)]) == 2)
# Objective
objective_terms = []
for i in range(num_workers):
for j in range(num_tasks):
objective_terms.append(costs[i][j] * x[i][j])
model.Minimize(sum(objective_terms))
# Solve
solver = cp_model.CpSolver()
status = solver.Solve(model)
# Print solution.
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
print(f'Total cost = {solver.ObjectiveValue()}')
print()
for i in range(num_workers):
for j in range(num_tasks):
if solver.BooleanValue(x[i][j]):
print(
f'Worker {i} assigned to task {j} Cost = {costs[i][j]}')
else:
print('No solution found.')
So how do I define a number of tasks to use as a constraint?
I get "No solution found" as output. But I expect that task 0 and task 3 will be selected and output should look like this
Worker 0 assigned to task 1 Cost = 70
Worker 1 assigned to task 0 Cost = 35
Worker 2 assigned to task 1 Cost = 95
Worker 3 assigned to task 0 Cost = 45
Worker 4 assigned to task 0 Cost = 50
If we assign every worker to only one task and choose two tasks, this should be the optimal solution.
This can be achieved using channeling constraint
Expressing @AirSquid's idea in CP-SAT syntax:
task_assigned = [model.NewBoolVar(f"task_assigned{i}") for i in range(num_tasks)]
for task_id in range(num_tasks):
sum_expr = sum([x[worker][task_id] for worker in range(num_workers)])
model.Add(sum_expr >= 1).OnlyEnforceIf(task_assigned[task_id])
model.Add(sum_expr == 0).OnlyEnforceIf(task_assigned[task_id].Not())
model.Add(sum(task_assigned) == 2)
This gives the output:
Total cost = 295.0
Worker 0 assigned to task 2 Cost = 75
Worker 1 assigned to task 0 Cost = 35
Worker 2 assigned to task 2 Cost = 90
Worker 3 assigned to task 0 Cost = 45
Worker 4 assigned to task 0 Cost = 50