Модель MILP с проблемой производительности Python PuLP - решатель очень медленный - PullRequest
0 голосов
/ 23 ноября 2018

В последнее время я начал заниматься линейным программированием на Python и создал свой первый алгоритм оптимизации с помощью PuLP.

Я имею дело с проблемой планирования производственного процесса.Цель состоит в том, чтобы минимизировать производственные затраты в день, создав идеальный график производства для каждого часа дня, и создать этот график для всех дней в году.

Проблема, с которой я сталкиваюсь, заключается в том, что выполнение алгоритма занимает очень много времени (несколько часов) и часто застревает.Кроме того, у меня есть ощущение, что со временем становится все медленнее.

Я надеюсь получить совет о том, как я могу улучшить производительность своего кода.

Мой подход к проблеме:

  • Определите мою задачу минимизации, чтобы создать оптимальное расписание для полного дня.
  • Зациклить мой код, чтобы запустить эту оптимизацию для всех 365 дней в году.
  • В конце каждого цикла я беру оптимизированное производственное расписание на день и добавляю его к кадру данных, поэтому в конце у меня есть информационный кадр, который содержит производственное расписание на все дни года.

Я имею дело с 3 активами производства ('a', 'l' и 'o'), каждый из которых имеет несколько режимов производства.Я определил каждую комбинацию актива-режима как «опцию», в результате всего было 14 опций.Каждый параметр может варьироваться каждый час и имеет целочисленное значение (объем производства) и двоичное значение (вкл / выкл), в результате чего получается около 14 x 2 x 24 = 672 переменных.Проблема содержит около 1250 ограничений.

Мой код содержит более 200 строк, поэтому я не решаюсь поделиться всем этим здесь, но я поделюсь наиболее важными моментами ниже.

Определение параметров поставки:

def set_supplyoptions():
cols = ['option', 'min_capacity', 'max_capacity']
options_list = [{'option':'o', 'min_capacity': 0, 'max_capacity':146},
                {'option':'l30', 'min_capacity': 0, 'max_capacity':30},
                {'option':'l50', 'min_capacity': 31, 'max_capacity':50},
                {'option':'l90', 'min_capacity': 51, 'max_capacity':90},
                {'option':'l150', 'min_capacity': 91, 'max_capacity':150},
                {'option':'l230', 'min_capacity': 151, 'max_capacity':230},
                {'option':'a15', 'min_capacity': 0, 'max_capacity':15},
                {'option':'a30', 'min_capacity': 0, 'max_capacity':30},
                {'option':'a45', 'min_capacity': 0, 'max_capacity':45},
                {'option':'a60_below53', 'min_capacity': 0, 'max_capacity':52},
                {'option':'a_flow_below53', 'min_capacity': 0, 'max_capacity':52},
                {'option':'a_flow_above53', 'min_capacity': 0, 'max_capacity':8},
                {'option':'l_total', 'min_capacity': 0, 'max_capacity':230},
                {'option':'a_total', 'min_capacity': 0, 'max_capacity':60}]

options = pd.DataFrame(options_list, columns=cols)  
options = options.set_index('option')

Переменные:

# production per supply option per hour
production = pulp.LpVariable.dicts("production",
                                     ((hour, option) for hour in hours for option in options.index),
                                     lowBound=0,
                                     cat='Integer')

# status of supply options per hour (in use or not in use)
options_status = pulp.LpVariable.dicts("options_status",
                                     ((hour, option) for hour in hours for option in options.index),
                                     cat='Binary')

# use status of A tranches on day (used or not used)
a_status_15 = pulp.LpVariable('a_stat_15', cat='Binary')
a_status_30 = pulp.LpVariable('a_stat_30', cat='Binary')
a_status_45 = pulp.LpVariable('a_stat_45', cat='Binary')
a_status_60 = pulp.LpVariable('a_stat_60', cat='Binary')

Функция цели:

opt_model = pulp.LpProblem("Optimizer", pulp.LpMinimize)

opt_model += pulp.lpSum(
    #O variable
    [0.2*demand.loc[hour, 'price']*production[hour, 'o'] + 
    #O fixed
    3*demand.loc[hour, 'price'] +
    #L30
    ( 12 )*options_status[hour, 'l30']*demand.loc[hour, 'price'] + 
    #L50
    ( 2*options_status[hour, 'l50']+0.13*production[hour, 'l50'] )*demand.loc[hour, 'price'] +
    #L90
    ( 15*options_status[hour, 'l90']+0.13*production[hour, 'l90'] )*demand.loc[hour, 'price'] +
    #L150
    ( 8*options_status[hour, 'l150']+0.2*production[hour, 'l150'] )*demand.loc[hour, 'price'] +
    #L230
    ( -3*options_status[hour, 'l230']+0.3*production[hour, 'l230'] )*demand.loc[hour, 'price'] +
    #L fixed
    ( 7*(1-options_status[hour, 'ltotal'])*demand.loc[hour, 'price'] ) +
    #A base unit price
    (10*production[hour, 'a_flow_below53']+8.88*production[hour, 'a_flow_above53'])*(c/20) for hour in hours]
    #A daily fee
    + (a_status_15 * 1000 + a_status_30 * 2000 + a_status_45 * 3 + a_status_60 * 4000)*(c/20))

Ограничения:

# Sum of production == Demand
for hour in hours:
    opt_model += production[(hour, 'o')] + production[(hour, 'l_total')] + production[(hour, 'a_total')] == int(demand.loc[hour, 'demand'])

# Production <=  Max capacity AND => Min capacity
for hour in hours:
    for option in options.index:
        opt_model += production[(hour, option)] >= options_status[(hour, option)]
        opt_model += production[(hour, option)] >= options.loc[option, 'min_capacity'] * options_status[(hour, option)]
        opt_model += production[(hour, option)] <= options.loc[option, 'max_capacity'] * options_status[(hour, option)]

# Constraints L
for hour in hours:
    opt_model += production[(hour, 'l30')] + production[(hour, 'l0')] + production[(hour, 'l90')] + production[(hour, 'l150')] + production[(hour, 'l230')] == production[(hour, 'l_total')]
    opt_model += options_status[(hour, 'l30')] + options_status[(hour, 'l50')] + options_status[(hour, 'l90')] + options_status[(hour, 'l150')] + options_status[(hour, 'l230')] <= 1

# Constraints A
M = 999
for hour in hours:
    # total flow = sum of tranches
    opt_model += production[(hour, 'a_flow_below53')] == production[(hour, 'a_15')] + production[(hour, 'ap_30')] + production[(hour, 'a_45')] + production[(hour, 'ap_60_below53')] 
    opt_model += production[(hour, 'a_total')] == production[(hour, 'a_flow_below53')] + production[(hour, 'a_flow_above53')]

    # only 1 tranche can be active at the time
    opt_model += production[(hour, 'a_15')] <= M * a_status_15
    opt_model += production[(hour, 'a_30')] <= M * a_status_30 
    opt_model += production[(hour, 'a_45')] <= M * a_status_45
    opt_model += production[(hour, 'a_60_below53')] + production[(hour, 'a_flow_above53')] <= M * a_status_60

    # a_60_above53 can only be active if a_60_below53 == 52
    opt_model += production[(hour, 'a_flow_above53')] <= M * options_status[(hour, 'a_flow_above53')]
    opt_model += (52 - production[(hour, 'a_flow_below53')] ) <= M * (1-options_status[(hour, 'a_flow_above53')])

# only 1 'mode' can be used per day (corresponding to daily fee)
opt_model += a_status_15 + a_status_30 + a_status_45 + a_status_60 <= 1

Ответы [ 2 ]

0 голосов
/ 29 ноября 2018

Проблемы с ЛП решаются очень быстро, если у вас не очень большое количество переменных.Это целочисленные и двоичные переменные, которые замедляют вас.

Скорее всего, ваши production переменные убивают вас.Я бы порекомендовал рассматривать их как линейные / непрерывные переменные («линейная релаксация»), чтобы быстро получить решение (затем можно округлить / увеличить, чтобы получить допустимое решение).

Другой вариант может заключаться в увеличениидопустимый разрыв в оптимальности или установить предел тайм-аута.Разрыв оптимальности по умолчанию будет довольно низким, что будет означать, что решатель ветвлений и границ для MILP будет продолжать работать.Решатели часто получают хорошие решения относительно быстро, а затем тратят огромное количество времени, пытаясь доказать оптимальность решения.- Вы можете указать время для решателя, чтобы получить приблизительное решение:

prob.solve(pulp.solvers.PULP_CBC_CMD(maxSeconds=120))

0 голосов
/ 23 ноября 2018

LP проблемы NP-сложны.Python может быть слишком медленным для вашей проблемы.Вы можете рассмотреть одно или несколько из предложенных ниже предложений (в порядке дополнительных работ от вас)

  1. Уменьшите количество ограничений, и это может привести к более быстрым решениям.Исключите неправдоподобные решения.

  2. Перемоделируйте свои проблемы, комбинируя исходные данные или ограничения.

  3. Используйте другое программное обеспечение или библиотеки, которые могут быть быстрее.Вы можете рассмотреть CPLEX или GLPK.

...