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

Question:

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
Asked By: Marvie Cruz

||

Answers:

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.

Answered By: John Hedengren