2017-05-31 101 views
1

我是线性编程的新手,并且正在通过一个最小化工厂生产成本的例子。这个例子中包含了打开和关闭工厂的能力。如何在PuLP中添加约束条件以保持工厂开启或关闭一段时间?

我的问题是如何添加额外的限制,如果工厂关闭,那么它需要停留3个月,如果它切换回来,那么它需要保持4个月?

代码:

import pandas as pd 
import pulp 

factories = pd.DataFrame.from_csv('csv/factory_variables.csv', index_col=['Month', 'Factory']) 


demand = pd.DataFrame.from_csv('csv/monthly_demand.csv', index_col=['Month']) 


# Production 
production = pulp.LpVariable.dicts("production", 
            ((month, factory) for month, factory in factories.index), 
            lowBound=0, 
            cat='Integer') 

# Factory Status, On or Off 
factory_status = pulp.LpVariable.dicts("factory_status", 
            ((month, factory) for month, factory in factories.index), 
            cat='Binary') 

# Factory switch on or off 
switch_on = pulp.LpVariable.dicts("switch_on", 
            ((month, factory) for month, factory in factories.index), 
            cat='Binary') 

# Instantiate the model 
model = pulp.LpProblem("Cost minimising scheduling problem", pulp.LpMinimize) 

# Select index on factory A or B 
factory_A_index = [tpl for tpl in factories.index if tpl[1] == 'A'] 
factory_B_index = [tpl for tpl in factories.index if tpl[1] == 'B'] 

# Define objective function 
model += pulp.lpSum(
    [production[m, f] * factories.loc[(m, f), 'Variable_Costs'] for m, f in factories.index] 
    + [factory_status[m, f] * factories.loc[(m, f), 'Fixed_Costs'] for m, f in factories.index] 
    + [switch_on[m, f] * 20000 for m, f in factory_A_index] 
    + [switch_on[m, f] * 400000 for m, f in factory_B_index] 
) 

# Production in any month must be equal to demand 
months = demand.index 
for month in months: 
    model += production[(month, 'A')] + production[(month, 'B')] == demand.loc[month, 'Demand'] 

# Production in any month must be between minimum and maximum capacity, or zero. 
for month, factory in factories.index: 
    min_production = factories.loc[(month, factory), 'Min_Capacity'] 
    max_production = factories.loc[(month, factory), 'Max_Capacity'] 
    model += production[(month, factory)] >= min_production * factory_status[month, factory] 
    model += production[(month, factory)] <= max_production * factory_status[month, factory] 

# Factory B is off in May 
model += factory_status[5, 'B'] == 0 
model += production[5, 'B'] == 0 

#Constraints for switching factory on and off 
for month, factory in factories.index: 
    # In month 1, if the factory ison, we assume it turned on 
    if month == 1: 
     model += switch_on[month, factory] == factory_status[month, factory] 

    # In other months, if the factory is on in the current month AND off in the previous month, switch on = 1 
    else: 
     model += switch_on[month, factory] >= factory_status[month, factory] - factory_status[month-1, factory] 
     model += switch_on[month, factory] <= 1 - factory_status[month-1, factory] 
     model += switch_on[month, factory] <= factory_status[month, factory] 


model.solve() 
pulp.LpStatus[model.status] 

output = [] 
for month, factory in production: 
    var_output = { 
     'Month': month, 
     'Factory': factory, 
     'Production': production[(month, factory)].varValue, 
     'Factory Status': factory_status[(month, factory)].varValue, 
     'Switch On': switch_on[(month, factory)].varValue 
    } 
    output.append(var_output) 
output_df = pd.DataFrame.from_records(output).sort_values(['Month', 'Factory']) 
output_df.set_index(['Month', 'Factory'], inplace=True) 
output_df 

回答

1

为了模拟第一组约束,你需要一个switch_off变量,定义类似于switch_on。这是必要的原因是因为当switch_on变量没有办法不知道是否没有切换或关闭(我们当然可以检查factory_status,但没有表示该变化的单个变量)。

然后,你可以这样做:

model += factory_status[month, factory] + factory_status[month+1, factory] + \ 
factory_status[month+2, factory] <= 3 * (1 - switch_off[month, factory]) 

为各工厂在{1, ... , TotalMonths - 2}所有月份。

然后,在{TotalMonths-1, TotalMonths}的月份中,为所有工厂设置switch_on[month, factory] = 0(甚至根本不定义这些变量)。这些是边界条件

接通约束是类似的:

factory_status[month, factory] + factory_status[month + 1, factory]+ \ 
factory_status[month + 2, factory] + factory_status[month + 3, factory] >= 
4 * switch_on[month, factory] 

不需要额外的边界条件是必要的。

这些约束是正确的,它们的优点是它只有两个(+边界条件)。然而,它们并不紧密,因为模型的线性规划(LP)松弛可能远离最优解。可以获得更高目标的LP放松的版本是这一个:

factory_status[month + u, factory] <= 1 - switch_off[month, factory], for u in {0, 1, 2}, 

factory_status[month + u, factory] >= switch_on[month, factory], for u in {0, 1, 2, 3} 

这可能对您的示例更好。对于非常大的模型,第一个版本可能是首选,但它确实是问题(也许是实例)特定的。在我看来,最好的选择是将第二个版本的约束放在懒惰池中,并将其管理委托给解算器,这通常更有效。并非所有求解器和接口都可以实现这一点,但它确实对困难模型有所帮助。

我希望这有助于!

+0

感谢您的帮助和快速回复我的问题Ioannis!我从来没有考虑过使用'switch_off'变量。我的研究还没有扩展到LP放松或Lazy Pool限制的话题。你能解释他们还是推荐阅读材料?我想我会首先尝试第一个选项,因为我知道那里发生了什么 - 我会随着我的进度更新此帖。干杯! – usermw

+0

在评论中解释整数编程有点困难。简而言之,在实践中解决比线性编程要困难得多,并且有理论上的理由。 [This](http://inside.mines.edu/~anewman/MIP_practice120212.pdf)是开始阅读更多细节的好地方。祝你好运,享受! – Ioannis

0

我假设“停留3个月”和“停留4个月”并不完全意味着这一点,而是“停留至少3个月”和“至少停留4个月”。

这里是没有额外的变量的替代制剂:

  • 禁止不图案101:x[t,i]-x[t+1,i]+x[t+2,i] <= 1
  • 禁止不图案1001:x[t,i]-x[t+1,i]-x[t+2,i]+x[t+3,i] <= 1
  • 禁止不图案010:-x[t,i]+x[t+1,i]-x[t+2,i] <= 0
  • 禁止不图案0110 -x[t,i]+x[t+1,i]+x[t+2,i]-x[t+3,i] <= 1
  • 禁止图案01110 -x[t,i]+x[t+1,i]+x[t+2,i]+x[t+3,i]-x[t+4,i] <= 2

其中xfactory_status的较短的名称。

+0

感谢您的回答Erwin!是的,你是对的,这正是我的意思。有意义且易于实施,所以我也会放弃这一点。我的问题的答案看起来相对容易,现在我已经看到了一些解决方案。队友的欢呼声 – usermw