Search code examples
pythonor-toolscp-sat

Using ortools to select the two smallest integer values in a list


I know ortools is overkill here, but this is for my own learning-by-doing purpose.

The problem is: How to find the two smallest integer values in a list using ortools?

Here's the code I've got, based on another example I got that simply finds the single smallest value.

from __future__ import print_function
from ortools.sat.python import cp_model

# 2. A simple problem

# Select the two smallest numbers from a list of integers

# Define data

cost_data = [
    5, 4, 3, 4, 5, 6, 7, 1, 2, 3
]
num_hours = len(cost_data)
hours = range(num_hours)

# Create model

model = cp_model.CpModel()

# Create variables

cost_vars = {}
pick_vars = {}
for i in hours:
    for j in hours:
        cost_vars[(i, j)] = model.NewIntVar(0, 100, 'c_%i_%i' % (i, j))
        pick_vars[(i, j)] = model.NewBoolVar('p_%i_%i' % (i, j))

# Create constraints

# An hour can only be picked once

for i in hours:
    model.Add(sum(pick_vars[(i, j)] for j in hours) == 1)

# Only set cost if applicable

for i in hours:
    for j in hours:
        model.Add(cost_vars[(i, j)] == cost_data[i] + cost_data[j]).OnlyEnforceIf(pick_vars[(i, j)])
        model.Add(cost_vars[(i, j)] == 0).OnlyEnforceIf(pick_vars[(i, j)].Not())

# Set objective function
for i in hours:
    model.Minimize(sum([cost_vars[(i, j)] for j in hours]))

# Solve problem
solver = cp_model.CpSolver()
status = solver.Solve(model)

if status == cp_model.INFEASIBLE:
    print("INFEASIBLE")
elif status == cp_model.FEASIBLE:
    print("FEASIBLE")
elif status == cp_model.OPTIMAL:
    print("OPTIMAL")

print("ObjectiveValue()")
print(solver.ObjectiveValue())

# print(solver.Value(cost_vars[(1, 2)]))

It returns the value 4, when the actual value should be 3...


Solution

  • There are multiple problems:

    You should only call Minimize or Maximize once, only the last one will take effect, so I guess you want model.Minimize(sum(cost_vars.values()))

    I also don't understand this:

    for i in hours:
        model.Add(sum(pick_vars[(i, j)] for j in hours) == 1)
    

    Maybe you meant this instead:

    model.Add(sum(pick_vars.values()) == 1)
    

    And also here you should check whether i != j:

    for i in hours:
        for j in hours:
    

    Edit: if i != j then don't create the possibility in the first place

    for i in hours:
        for j in hours:
            if i != j:
                cost_vars[(i, j)] = model.NewIntVar(0, 100, 'c_%i_%i' % (i, j))
                pick_vars[(i, j)] = model.NewBoolVar('p_%i_%i' % (i, j))