Баночная упаковка с группировкой

У меня есть список продуктов (i), каждый с заданным весом и объемом. Оптимизация проходит в два этапа, из которых я не смог решить второй шаг.

Первая оптимизация: минимизировать количество используемых ячеек (решено)

  • Минимизируйте количество контейнеров, используемых для упаковки списка продуктов. У меня фиксированные контейнеры с ограничениями по максимальному объему и весу. Код Python:

    prob = pp.LpProblem("algorithm",pp.LpMinimize) #pp=pulp solver
    
    Y_max=10  #bins will not exceed this number
    #Y_min = minimum number of bins (calculated)
    # j = index of jth bin
    # i = index of ith product
    
    w=weights #list of weights w_i for product i
    v=volumes #list of volumes v_i for product i
    W=W_max #maximum weight of a bin
    V=V_max #maximum volume of a bin
    #len(order) = number of products
    
    x=pp.LpVariable.dicts("x_i,j", ((i, j) for i in range(len(order)) for j in range(Y_max)),0,1,pp.LpBinary) #variable indicating if product i is placed in bin j
    y=pp.LpVariable.dicts("y_j", ((j,0) for j in range(Y_max)),0,1,pp.LpBinary) #variable indicating if bin j is used or unused
    Y=pp.LpVariable('Y')
    
    prob +=Y , 'objective function'    
    prob += pp.lpSum([y[j,0] for j in range(Y_max)]) == Y, ""
    
    for i in range(len(order)):
        prob += pp.lpSum([x[i,j] for j in range(Y_max)]) == 1,''  #product i can only be placed in 1 bin
    
    for j in range(Y_max):
        prob += pp.lpSum([x[i,j]*w[i] for i in range(len(order))]) <= W*y[j,0],""  #weight constraint
    
    for j in range(Y_max):
        prob += pp.lpSum([x[i,j]*v[i] for i in range(len(order))]) <= V*y[j,0],""  #volume constraint
    
    for j in range(Y_max):
        for i in range(len(order)):
            prob += x[i,j] <= y[j,0],"" #products i may not be placed in bin j if bin j is unnused. 
    
    prob += pp.lpSum([y[j,0] for i in range(len(order))]) >=Y_min,""
    pp.LpSolverDefault.msg = 1
    prob.solve()`
    

Вторая оптимизация: минимизировать количество зон, в которые перемещается бункер (как решить в линейной оптимизации?)

  • Каждый продукт поступает из одной из двух зон (зона 1 или зона 2). У нас есть список этих зон z_i (например, Зона 1 ‹==> 1, Зона 2 ‹==> 0).

  • При условии, что количество ячеек не превышает минимального количества ячеек (определенного при первой оптимизации), могу ли я разделить продукты по ячейкам так, чтобы количество зон, в которые перемещается каждая ячейка, было минимальным?

  • Ограничения объема и веса первой оптимизации все еще применяются.

В идеале бункер никогда не должен перемещаться в две зоны, но в некоторых случаях он вынужден это делать. (например, первая оптимизация приводит к 1 бункеру, содержащему продукты зоны 1 и зоны 2).


person Jobbe van der Maesen    schedule 21.11.2018    source источник
comment
если вы моделируете с помощью переменных, сколько используемых бункеров имеет продукты из 2 зон, вы можете оптимизировать сумму функций ([y[j,0] для j в диапазоне (Y_max)]) * Y_max + бункеры из 2 зон   -  person juvian    schedule 21.11.2018


Ответы (1)


Ниже приведен один из способов сделать это — не обязательно самый аккуратный или самый эффективный. Подобно тому, что предложил @juvian

Если вы медленно уменьшите объем на бин, вы обнаружите, что, хотя он и большой, вы можете разместить небольшое количество бинов, при этом каждый бин посещает только одну зону. По мере того, как контейнеры становятся меньше, вы вынуждены перемещать их в более чем одну зону, а когда они снова становятся меньше, вы вынуждены использовать больше контейнеров.

Обратите внимание на целевую функцию: prob += Y + pp.lpSum([two_zones[j] for j in range(Y_max)])/(Y_max+1)

Мы делим нашу второстепенную цель (Количество ячеек, в которых есть продукты из обеих зон) на максимальное количество ячеек + 1. Таким образом, мы всегда отдаем приоритет основной цели количества ячеек, даже если в каждой ячейке есть товары из разных зон, вторая сумма может быть не более Y_max, поэтому, если мы разделим ее на Y_max + 1, мы получим значение меньше 1,0, поэтому предпочтительнее уменьшить количество используемых ячеек на 1. Это распространенный метод, когда вы хотите расставить приоритеты целей.

import numpy as np 
import pulp as pp

prob = pp.LpProblem("algorithm",pp.LpMinimize) #pp=pulp solver

Y_max = 5 #bins will not exceed this number
#Y_min = minimum number of bins (calculated)
# j = index of jth bin
# i = index of ith product

# Some dummy data
n_prod = 10

np.random.seed(0)
w = np.random.uniform(2.5, 10, n_prod)  # product weights
v = np.random.uniform(0.1, 1, n_prod) # product volumes
W = 25 #maximum weight of a bin
V = 1.5  #maximum volume of a bin
z = np.random.randint(0, 2, n_prod) # product zones

x=pp.LpVariable.dicts("x_i,j", ((i, j) for i in range(n_prod) for j in range(Y_max)), cat='Binary') #variable indicating if product i is placed in bin j
y=pp.LpVariable.dicts("y_j", range(Y_max), cat='Binary') #variable indicating if bin j is used or unused
Y=pp.LpVariable('Y') # No. bins used
two_zones = pp.LpVariable.dicts("two_zones,j", range(Y_max), cat='Binary')
has_zone_0 = pp.LpVariable.dicts("has_zone_0,j", range(Y_max), cat='Binary')
has_zone_1 = pp.LpVariable.dicts("has_zone_1,j", range(Y_max), cat='Binary')

# Primary objective: minm No. bins, Secondary minimize bins that visit two zones
prob += Y + pp.lpSum([two_zones[j] for j in range(Y_max)])/(Y_max+1), 'objective function'    

prob += pp.lpSum([y[j] for j in range(Y_max)]) == Y, 'set Y to No. bins'

for i in range(n_prod):
    prob += pp.lpSum([x[i,j] for j in range(Y_max)]) == 1,'each product in 1 bin %s' % i

for j in range(Y_max):
    prob += pp.lpSum([x[i,j]*w[i] for i in range(n_prod)]) <= W*y[j], 'weight constraint %s' % j

    prob += pp.lpSum([x[i,j]*v[i] for i in range(n_prod)]) <= V*y[j], 'volume constraint %s' % j

    for i in range(n_prod):
        prob += x[i,j] <= y[j], 'products only placed in used bin  %s_%s' % (j, i)

        prob += has_zone_0[j] >= x[i,j]*(z[i] == 0), 'set has_zone_0 flag %s_%s' % (j, i)
        prob += has_zone_1[j] >= x[i,j]*(z[i] == 1), 'set has_zone_1 flag %s_%s' % (j, i)

    prob += two_zones[j] >= has_zone_0[j] + has_zone_1[j] - 1, 'set two_zones flag %s' % j

prob.solve()

has_zone_0_soln = np.array([has_zone_0[j].varValue for j in range(Y_max)])
has_zone_1_soln = np.array([has_zone_1[j].varValue for j in range(Y_max)])
two_zones_soln = np.array([two_zones[j].varValue for j in range(Y_max)])
y_soln = np.array([y[j].varValue for j in range(Y_max)])

# Print some output:
print("Status:" + str(pp.LpStatus[prob.status]))
print('z: ' + str(z))
print('Y: ' + str(Y.varValue))
print('y_soln: ' + str(y_soln))
print('Objective: ' + str(pp.value(prob.objective)))
print('has_zone_0_soln: ' + str(has_zone_0_soln))
print('has_zone_1_soln: ' + str(has_zone_1_soln))
print('two_zones_soln: ' + str(two_zones_soln))
person kabdulla    schedule 21.11.2018