Search code examples
pythonor-toolsscipy-optimizegekkobin-packing

How to put a fixed specific quantity of items to be allowed in a bin


I have found this code for a bin packing problem in Google OR-tools. https://developers.google.com/optimization/bin/bin_packing#python_3 However, it does not have a volume constraint. What if the total quantity/number of items packed in a bin (which is printed in the output of the program) is only limited to 2, what code should be added? Re: the volume of the items are all the same, items just have different weights

from ortools.linear_solver import pywraplp

def create_data_model():
    """Create the data for the example."""
    data = {}
    weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]
    data['weights'] = weights
    data['items'] = list(range(len(weights)))
    data['bins'] = data['items']
    data['bin_capacity'] = 100
    return data

def main():
    data = create_data_model()

    # Create the mip solver with the SCIP backend.
    solver = pywraplp.Solver.CreateSolver('SCIP')


    # Variables
    # x[i, j] = 1 if item i is packed in bin j.
    x = {}
    for i in data['items']:
        for j in data['bins']:
            x[(i, j)] = solver.IntVar(0, 1, 'x_%i_%i' % (i, j))

    # y[j] = 1 if bin j is used.
    y = {}
    for j in data['bins']:
        y[j] = solver.IntVar(0, 1, 'y[%i]' % j)

    # Constraints
    # Each item must be in exactly one bin.
    for i in data['items']:
        solver.Add(sum(x[i, j] for j in data['bins']) == 1)

    # The amount packed in each bin cannot exceed its capacity.
    for j in data['bins']:
        solver.Add(
            sum(x[(i, j)] * data['weights'][i] for i in data['items']) <= y[j] *
            data['bin_capacity'])

    # Objective: minimize the number of bins used.
    solver.Minimize(solver.Sum([y[j] for j in data['bins']]))

    status = solver.Solve()

    if status == pywraplp.Solver.OPTIMAL:
        num_bins = 0.
        for j in data['bins']:
            if y[j].solution_value() == 1:
                bin_items = []
                bin_weight = 0
                for i in data['items']:
                    if x[i, j].solution_value() > 0:
                        bin_items.append(i)
                        bin_weight += data['weights'][i]
                if bin_weight > 0:
                    num_bins += 1
                    print('Bin number', j)
                    print('  Items packed:', bin_items)
                    print('  Total weight:', bin_weight)
                    print()
        print()
        print('Number of bins used:', num_bins)
        print('Time = ', solver.WallTime(), ' milliseconds')
    else:
        print('The problem does not have an optimal solution.')


if __name__ == '__main__':
    main()

OUTPUT OF THE PROGRAM:

Bin number 0
  Items packed: [1, 5, 10]
  Total weight: 87

Bin number 1
  Items packed: [0, 6]
  Total weight: 90

Bin number 2
  Items packed: [2, 4, 7]
  Total weight: 97

Bin number 3
  Items packed: [3, 8, 9]
  Total weight: 96


Number of bins used: 4

Solution

  • Add a constraint to not exceed a volume capacity.

        # The amount packed in each bin cannot exceed its volume capacity.
        for j in data['bins']:
            itm_vol = [x[(i, j)] * data['volumes'][i] for i in data['items']]
            solver.Equation(solver.sum(itm_vol) <= y[j] * data['bin_volume'])
    

    It is also possible to specify how many items (or range) are in each bin.

        # Each bin must contain 1 or 2 items.
        for j in data['bins']:
            items = [x[(i, j)] for i in data['items']]
            solver.Equation(solver.sum(items) <= 2)
            solver.Equation(solver.sum(items) >= 1)
    

    Here is a translation of the problem to Gekko that gives the same answer as the original problem statement.

    from gekko import GEKKO
    
    def create_data_model():
        """Create the data for the example."""
        data = {}
        weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]
        data['weights'] = weights
        data['items'] = list(range(len(weights)))
        data['bins'] = data['items']
        data['bin_capacity'] = 100
        return data
    
    def main():
        data = create_data_model()
        solver = GEKKO(remote=False)
    
        # Variables
        # x[i, j] = 1 if item i is packed in bin j.
        x = {}
        for i in data['items']:
            for j in data['bins']:
                x[(i, j)] = solver.Var(lb=0, ub=1, integer=True)
    
        # y[j] = 1 if bin j is used.
        y = {}
        for j in data['bins']:
            y[j] = solver.Var(lb=0, ub=1, integer=True)
    
        # Constraints
        # Each item must be in exactly one bin.
        for i in data['items']:
            items = [x[(i, j)] for j in data['bins']]
            solver.Equation(sum(items) == 1)
    
        # The amount packed in each bin cannot exceed its capacity.
        for j in data['bins']:
            bin_cap = [x[(i, j)] * data['weights'][i] for i in data['items']]
            solver.Equation(sum(bin_cap) <= y[j] * data['bin_capacity'])
    
        # Objective: minimize the number of bins used.
        solver.Minimize(sum([y[j] for j in data['bins']]))
    
        solver.options.SOLVER=1
        solver.solve(disp=False)
    
        if solver.options.APPSTATUS == 1:
            num_bins = 0.
            for j in data['bins']:
                if y[j].value[0] == 1:
                    bin_items = []
                    bin_weight = 0
                    for i in data['items']:
                        if x[i, j].value[0] > 0:
                            bin_items.append(i)
                            bin_weight += data['weights'][i]
                    if bin_weight > 0:
                        num_bins += 1
                        print('Bin number', j)
                        print('  Items packed:', bin_items)
                        print('  Total weight:', bin_weight)
                        print()
            print()
            print('Number of bins used:', num_bins)
            print('Time = ', solver.options.SOLVETIME, ' seconds')
        else:
            print('The problem does not have an optimal solution.')
    
    if __name__ == '__main__':
        main()
    

    Here is a modified version with the added constraints.

    from gekko import GEKKO
    
    def create_data_model():
        """Create the data for the example."""
        data = {}
        weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 31]
        volumes = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1]
        data['weights'] = weights
        data['volumes'] = volumes
        data['items'] = list(range(len(weights)))
        data['bins'] = data['items']
        data['bin_capacity'] = 100
        data['bin_volume'] = 2
        return data
    
    def main():
        data = create_data_model()
        solver = GEKKO(remote=False)
        solver.solver_options = ['minlp_gap_tol 0.5',\
                        'minlp_maximum_iterations 10000',\
                        'minlp_max_iter_with_int_sol 500',\
                        'minlp_branch_method 1',\
                        'minlp_integer_leaves 1'\
                        'minlp_max_iter_with_int_sol 1000']
    
        # Variables
        # x[i, j] = 1 if item i is packed in bin j.
        x = {}
        for i in data['items']:
            for j in data['bins']:
                x[(i, j)] = solver.Var(lb=0, ub=1, integer=True)
    
        # y[j] = 1 if bin j is used.
        y = {}
        for j in data['bins']:
            y[j] = solver.Var(lb=0, ub=1, integer=True)
    
        # Constraints
        # Each item must be in exactly one bin.
        for i in data['items']:
            item = [x[(i, j)] for j in data['bins']]
            solver.Equation(solver.sum(item) == 1)
    
        # The amount packed in each bin cannot exceed its weight capacity.
        for j in data['bins']:
            itm_wgt = [x[(i, j)] * data['weights'][i] for i in data['items']]
            solver.Equation(solver.sum(itm_wgt) <= y[j] * data['bin_capacity'])
    
        # The amount packed in each bin cannot exceed its volume capacity.
        for j in data['bins']:
            itm_vol = [x[(i, j)] * data['volumes'][i] for i in data['items']]
            solver.Equation(solver.sum(itm_vol) <= y[j] * data['bin_volume'])
    
        # Each bin must contain at most 2 items.
        for j in data['bins']:
            items = [x[(i, j)] for i in data['items']]
            solver.Equation(solver.sum(items) <= 2)
    
        # Objective: minimize the number of bins used.
        solver.Minimize(solver.sum([y[j] for j in data['bins']]))
    
        solver.options.SOLVER=1
        solver.solve(disp=False)
    
        if solver.options.APPSTATUS == 1:
            num_bins = 0.
            for j in data['bins']:
                if y[j].value[0] == 1:
                    bin_items = []
                    bin_weight = 0
                    for i in data['items']:
                        if x[i, j].value[0] > 0:
                            bin_items.append(i)
                            bin_weight += data['weights'][i]
                    if bin_weight > 0:
                        num_bins += 1
                        print('Bin number', j)
                        print('  Items packed:', bin_items)
                        print('  Total weight:', bin_weight)
                        print()
            print()
            print('Number of bins used:', num_bins)
            print('Time = ', solver.options.SOLVETIME, ' seconds')
        else:
            print('The problem does not have an optimal solution.')
    
    if __name__ == '__main__':
        main()
    

    Here is the solution:

    Bin number 3
      Items packed: [2, 7]
      Total weight: 61
    
    Bin number 4
      Items packed: [1]
      Total weight: 30
    
    Bin number 6
      Items packed: [5, 10]
      Total weight: 58
    
    Bin number 8
      Items packed: [3, 4]
      Total weight: 72
    
    Bin number 9
      Items packed: [0, 6]
      Total weight: 90
    
    Bin number 10
      Items packed: [8, 9]
      Total weight: 60
    
    
    Number of bins used: 6.0
    Time =  6.3572  seconds
    

    You may need to adjust the constraints or the objective to get the answer that you need.