'How to get consistent ortools assignment results?

The problem is to assign items into a few regions. Each item pairs have a crossing number, and the objective is to minimize the crossing of items across the regions. Each item also has a size and there is a limitation for the size of each region.

I am able to solve the problem with the following code, but I am getting different results every time. I would like to consistently get the same results.

I have tried to set "solver.parameters.random_seed = 1" but it does not work.

    import numpy as np
    from ortools.sat.python import cp_model
    import random
    
    num_items = 15
    num_regions = 3
    max_size = 600
    
    random.seed(1)
    matrix = [[random.randint(0,99) for _ in range(num_items)] for _ in range(num_items)]
    size_array = [random.randint(0,99) for _ in range(num_items)]
    
    # Model
    model = cp_model.CpModel()
    
    # Variables
    # x[a, b] is a matrix of 0-1 variables, which will be 1
    # if item a is assigned to region b.
    x = []
    for a in range(num_items):
        t = []
        for b in range(num_regions):
            t.append(model.NewBoolVar(f'x[{a},{b}]'))
        x.append(t)
    
    # y[a_i, a_j, b_i, b_j] is a matrix of 0-1 variables, which will be 1
    # if x[a_i, b_i] = 1 AND x[a_j, b_j] = 1
    y = []
    for a_i in range(num_items):
        t1 = []
        for a_j in range(num_items):
            t2 = []
            for b_i in range(num_regions):
                t3 = []
                for b_j in range(num_regions):
                    p = model.NewBoolVar(f'y[{a_i},{a_j},{b_i},{b_j}]')
                    t3.append(p)
                    x1 = x[a_i][b_i]
                    x2 = x[a_j][b_j]
                    model.AddBoolOr([x1.Not(), x2.Not(), p])
                    model.AddImplication(p, x1)
                    model.AddImplication(p, x2)
                t2.append(t3)
            t1.append(t2)
        y.append(t1)
    
    # Constraints
    # Each item is assigned to 1 region.
    for a in range(num_items):
        model.Add(sum([x[a][b] for b in range(num_regions)]) == 1)
    
    # Each region total item's size less than max_size.
    for b in range(num_regions):
        model.Add(sum([x[a][b]*int(np.ceil(size_array[a])) for a in range(num_items)]) <= max_size)
    
    
    def crossing(b_i, b_j):
        objective_terms = []
        for a_i in range(num_items):
            for a_j in range(num_items):
                objective_terms.append(y[a_i][a_j][b_i][b_j] * int(np.ceil((matrix[a_i][a_j]+matrix[a_j][a_i]))))
        return objective_terms
    
    # Objective
    # Each item pairs has a crossing value
    # Minimize the crossing across regions
    objective_terms = []
    objective_terms.extend(crossing(0, 1))
    objective_terms.extend(crossing(1, 2))
    objective_terms.extend(2*crossing(0, 2))
    model.Minimize(sum(objective_terms))
    
    # Solve
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    solver.parameters.random_seed = 1
    status = solver.Solve(model)
    print("Status = {}".format(solver.StatusName(status)), flush=True)
    
    # Print solution
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print(f'Total cost = {solver.ObjectiveValue()}', flush=True)
        for b in range(num_regions):
            print("Region {} :".format(b), end='', flush=True)
            total_size = 0
            for a in range(num_items):
                # Test if x[a, b] is 1 (with tolerance for floating point arithmetic).
                if solver.BooleanValue(x[a][b]):
                    total_size += size_array[a]
            print(" Total size of items are {} ".format(str(total_size)), flush=True)

Result 1:

Status = OPTIMAL
Total cost = 1274.0
Region 0 : Total size of items are 581
Region 1 : Total size of items are 90
Region 2 : Total size of items are 0

Result 2:

Status = OPTIMAL
Total cost = 1274.0
Region 0 : Total size of items are 0
Region 1 : Total size of items are 90
Region 2 : Total size of items are 581


Solution 1:[1]

Setting num_search_workers = 1 works, thanks!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 yuansy