没有解决方案的多背包问题,但找到一种方法来删除阻碍解决方案的项目

问题描述 投票:0回答:1

我正在尝试找到一种方法,让尽可能少的盒子来装化学品袋。一个箱子的重量不能超过 3.3 公斤,但必须高于 2.7 公斤。我遇到了谷歌的 OR 工具,但我正在努力对约束进行编程。

当我将我的行李重量输入 OR 工具的装箱程序(如下)以及箱子重量的上限“bin_capacity_upper_limit”和“bin_capacity_lower_limit”时,我没有得到解决方案。

有什么办法可以得到近似解吗?也许通过隔离阻止获得最佳解决方案的袋子。我是 OR 工具或一般优化的新手。我将不胜感激任何帮助或建议。

from ortools.linear_solver import pywraplp

def create_data_model():
    """Create the data for the example."""
    data = {}
    weights = [1.0, 1.001, 1.09, 1.002, 1.006, 1.007, 1.0, .674, 1.002 , .22, .36, .24, 1.20, .4, .947, .987, .456]
    data['weights'] = weights
    data['items'] = list(range(len(weights)))
    data['bins'] = data['items']
    data['bin_capacity_upper_limit'] = 3.3
    data['bin_capacity_lower_limit'] = 2.7
    return data


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

data = create_data_model()

# 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_upper_limit'])
    
    solver.Add(
        sum(x[(i, j)] * data['weights'][i] for i in data['items']) >= y[j] *
        data['bin_capacity_lower_limit'])
    

# 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_items:
                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.')

mathematical-optimization knapsack-problem or-tools scip bin-packing
1个回答
1
投票

首先,值得注意的是:您的实施速度非常慢,因为它没有利用您的垃圾箱容量限制对使用的垃圾箱数量设置下限和上限这一事实。引入这个将我的可行性检查从“几分钟所以我决定终止它”移动到 0.21 秒。

下一步:正如@sascha 所说,您需要选择您所说的近似解决方案以及您打算如何牺牲物品。我在这里提出一种方法,只需要牺牲一个选项并在 0.03 秒内完成。我没有 or-tools 所以我在 PuLP 中演示。

import pulp
import numpy as np

weights = np.array((
    1.   , 1.001, 1.09 , 1.002, 1.006, 1.007, 1.   , 0.674, 1.002,
    0.22 , 0.36 , 0.24 , 1.2  , 0.4  , 0.947, 0.987, 0.456))
items = np.arange(len(weights))
bin_capacity_upper_limit = 3.3
bin_capacity_lower_limit = 2.7
absolute_lower = round(np.ceil(weights.sum() / bin_capacity_upper_limit))
absolute_upper = round(np.ceil(weights.sum() / bin_capacity_lower_limit))
bins = np.arange(absolute_upper)


def prove_infeasible():
    solver = pulp.LpProblem(name='chemical_knapsack_exact', sense=pulp.LpMinimize)

    # x[i, j] = 1 if item i is packed in bin j.
    assignments = pulp.LpVariable.dicts(
        name='assign', cat=pulp.LpBinary,
        indices=[(i, j) for i in items for j in bins])

    # y[j] = 1 if bin j is used. All bins below the lower bound are used (those vars are fixed).
    bins_used = [
        pulp.LpVariable(
            name=f'bin_{j}', cat=pulp.LpBinary,
            lowBound=1 if j < absolute_lower else 0)
        for j in bins]

    for i in items:
        # Each item must be in exactly one bin.
        solver.addConstraint(
            name=f'assign_excl_{i}',
            constraint=pulp.lpSum([
                assignments[i, j]
                for j in bins
            ]) == 1)

    for j in bins:
        # The amount packed in each bin cannot exceed its capacity.
        weight = pulp.lpDot(weights,
            [assignments[i, j] for i in items])
        used = bins_used[j]
        solver.addConstraint(
            name=f'weight_hi_{j}',
            constraint=weight <= used*bin_capacity_upper_limit)
        solver.addConstraint(
            name=f'weight_lo_{j}',
            constraint=weight >= used*bin_capacity_lower_limit)

    # Objective: minimize the number of bins used.
    solver.objective = pulp.lpSum(bins_used)
    solver.solve()
    assert solver.status == pulp.LpStatusInfeasible


def solve_approximate():
    """
    Preserve as exact the originally intended number of bins, and the lower and upper capacity
    limits. This implies that absolute_upper is still true.

    Sacrifice some items to achieve feasibility. This means that absolute_lower is no longer true
    because some of the weights may be unused.
    """

    solver = pulp.LpProblem(name='chemical_knapsack_approx', sense=pulp.LpMaximize)

    # x[i, j] = 1 if item i is packed in bin j.
    assignments = pulp.LpVariable.dicts(
        name='assign', cat=pulp.LpBinary,
        indices=[(i, j) for i in items for j in bins])

    # y[j] = 1 if bin j is used.
    bins_used = pulp.LpVariable.dicts(
        name=f'bin', cat=pulp.LpBinary, indices=bins)

    for i in items:
        # Each item must be in up to one bin.
        solver.addConstraint(
            name=f'assign_excl_{i}',
            constraint=pulp.lpSum([
                assignments[i, j]
                for j in bins
            ]) <= 1)

    for j in bins:
        # The amount packed in each bin cannot exceed its capacity.
        weight = pulp.lpDot(weights,
            [assignments[i, j] for i in items])
        used = bins_used[j]
        solver.addConstraint(
            name=f'weight_hi_{j}',
            constraint=weight <= used*bin_capacity_upper_limit)
        solver.addConstraint(
            name=f'weight_lo_{j}',
            constraint=weight >= used*bin_capacity_lower_limit)

    # Objective: minimize the number of bins used and maximize the number of items packed.
    # Tweak value and cost to taste.
    item_value = 1
    bin_cost = 1
    solver.objective = item_value*pulp.lpSum(assignments.values()) - bin_cost*pulp.lpSum(bins_used)

    print(solver)
    solver.solve()
    assert solver.status == pulp.LpStatusOptimal

    for j in bins:
        assigned = np.array([assignments[i,j].value() for i in items], dtype=int)
        print(f'Bin {j} is', '  used,' if bins_used[j].value() else 'unused,',
              f'{assigned.sum()} items, {assigned.dot(weights):.4f} weight')
    for i in items:
        bin_idx = next((j for j in bins if assignments[i,j].value()), None)
        print(f'Item {i} is', 'unused' if bin_idx is None else f'in bin {bin_idx}')


prove_infeasible()
solve_approximate()
Welcome to the CBC MILP Solver 
Version: 2.10.3 
Build Date: Dec 15 2019 
...
Result - Problem proven infeasible

No feasible solution found
Enumerated nodes:               0
Total iterations:               1931
Time (CPU seconds):             0.21
Time (Wallclock seconds):       0.21

chemical_knapsack_approx:
MAXIMIZE
1*assign_(0,_0) + ... + 1*assign_(9,_3) + 1*assign_(9,_4) + 1*assign_(9,_5) + -1*bin_0 + -1*bin_1 + -1*bin_2 + -1*bin_3 + -1*bin_4 + -1*bin_5 + 0
SUBJECT TO
assign_excl_0: assign_(0,_0) + assign_(0,_1) + assign_(0,_2) + assign_(0,_3)
 + assign_(0,_4) + assign_(0,_5) <= 1
...
weight_hi_0: assign_(0,_0) + 1.001 assign_(1,_0) + 0.36 assign_(10,_0)
 + 0.24 assign_(11,_0) + 1.2 assign_(12,_0) + 0.4 assign_(13,_0)
 + 0.947 assign_(14,_0) + 0.987 assign_(15,_0) + 0.456 assign_(16,_0)
 + 1.09 assign_(2,_0) + 1.002 assign_(3,_0) + 1.006 assign_(4,_0)
 + 1.007 assign_(5,_0) + assign_(6,_0) + 0.674 assign_(7,_0)
 + 1.002 assign_(8,_0) + 0.22 assign_(9,_0) - 3.3 bin_0 <= 0

weight_lo_0: assign_(0,_0) + 1.001 assign_(1,_0) + 0.36 assign_(10,_0)
 + 0.24 assign_(11,_0) + 1.2 assign_(12,_0) + 0.4 assign_(13,_0)
 + 0.947 assign_(14,_0) + 0.987 assign_(15,_0) + 0.456 assign_(16,_0)
 + 1.09 assign_(2,_0) + 1.002 assign_(3,_0) + 1.006 assign_(4,_0)
 + 1.007 assign_(5,_0) + assign_(6,_0) + 0.674 assign_(7,_0)
 + 1.002 assign_(8,_0) + 0.22 assign_(9,_0) - 2.7 bin_0 >= 0
...
Result - Optimal solution found

Objective value:                12.00000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.03
Time (Wallclock seconds):       0.03

Bin 0 is   used, 3 items, 3.0780 weight
Bin 1 is   used, 4 items, 3.1740 weight
Bin 2 is unused, 0 items, 0.0000 weight
Bin 3 is   used, 4 items, 3.0760 weight
Bin 4 is unused, 0 items, 0.0000 weight
Bin 5 is   used, 5 items, 3.2580 weight
Item 0 is in bin 3
Item 1 is in bin 0
Item 2 is in bin 0
Item 3 is in bin 5
Item 4 is unused
Item 5 is in bin 1
Item 6 is in bin 1
Item 7 is in bin 3
Item 8 is in bin 3
Item 9 is in bin 1
Item 10 is in bin 5
Item 11 is in bin 5
Item 12 is in bin 5
Item 13 is in bin 3
Item 14 is in bin 1
Item 15 is in bin 0
Item 16 is in bin 5
© www.soinside.com 2019 - 2024. All rights reserved.