简体   繁体   English

如何使用python/PuLp建立线性规划model(运输问题)

[英]How to set up a linear programming model (transportation problem) using python/PuLp

I am working on a transportation/replenishment model wherein I need to solve for lowest cost.我正在处理运输/补货 model,其中我需要以最低成本解决问题。 The variables are:变量是:

  • Warehouses - several possible origin points of a shipment.仓库 - 货物的几个可能的起点。
  • Items - in this example I only use two items.项目 - 在这个例子中我只使用了两个项目。 Each Item-Store combination has a unique demand value.每个 Item-Store 组合都有一个独特的需求值。
  • Inventory - available inventory for each 'Item' in each 'Warehouse'库存 - 每个“仓库”中每个“项目”的可用库存
  • Stores - the destination point for each shipment.商店 - 每批货物的目的地点。 In this example I only use two Stores.在这个例子中,我只使用了两个商店。
  • Costs - unique costs for each Warehouse-Item-Store combination, which will be used to solve for lowest cost.成本 - 每个仓库-物料-商店组合的独特成本,将用于解决最低成本问题。
  • Demand - the quantity of each 'Item' that each 'Store' wants to receive;需求 - 每个“商店”想要接收的每个“项目”的数量; the model should fulfill 100% unless inventory is not available. model 应履行 100%,除非没有库存。

I am not very experienced with Python. It seems that I am somewhat close, however, I have a problem I haven't been able to fix yet: if Inventory is too low to fulfill all Demand, the model will break and return an "infeasible" result.我对 Python 不是很有经验。似乎我有点接近,但是,我有一个问题我还没有解决:如果库存太低无法满足所有需求,model 将中断并返回一个“不可行”的结果。 Instead of this, I want the model to satisfy Demand until Inventory reaches zero and then return the optimized results up to that point.而不是这个,我希望 model 满足需求,直到库存达到零,然后返回到那个点的优化结果。 I understand that the result I am getting now is because I have set fulfilled qty equal to demand in one of my constraints, but I am not sure how to modify/fix it.我知道我现在得到的结果是因为我在我的一个约束中将已完成的数量设置为等于需求,但我不确定如何修改/修复它。

Here is the code so far - this is a result of much Google searching and sort of combining bits and pieces of code together like Dr. Frankenstein - if anything in here looks stupid please let me know.这是到目前为止的代码 - 这是大量谷歌搜索的结果,有点像弗兰肯斯坦博士一样将代码的点点滴滴组合在一起 - 如果这里有任何东西看起来很愚蠢,请告诉我。 With the current inputs this will not work since Inventory does not satisfy Demand, but it seems to work if Inventory is higher (eg change Store1-SKU_B demand from 250 to 50)使用当前输入,这将不起作用,因为库存不能满足需求,但如果库存更高,它似乎可以工作(例如,将 Store1-SKU_B 需求从 250 更改为 50)

from pulp import *
import pandas as pd

# Creates a list of all the supply nodes 
warehouses = ["WHS_1","WHS_2","WHS_3"]

# Creates a dictionary for Inventory by Node-SKU
inventory = {"WHS_1": {"SKU_A":50,"SKU_B":100},
             "WHS_2": {"SKU_A":50,"SKU_B":75} , 
             "WHS_3": {"SKU_A":150,"SKU_B":25} ,
            }

# Store list
stores = ["Store1","Store2"]

# SKU list
items = ["SKU_A","SKU_B"]

# Creates a dictionary for the number of units of demand for each Store-SKU
demand = {
    "Store1": {"SKU_A":100,"SKU_B":250},
    "Store2": {"SKU_A":100,"SKU_B":50},
    }

# Creates a dictionary for the lane cost for each Node-Store-SKU
costs =  {
          "WHS_1": {"Store1": {"SKU_A":10.50,"SKU_B":3.75},
                 "Store2": {"SKU_A":15.01,"SKU_B":5.15}},
          "WHS_2": {"Store1": {"SKU_A":9.69,"SKU_B":3.45},
                 "Store2": {"SKU_A":17.50,"SKU_B":6.06}},
          "WHS_3": {"Store1": {"SKU_A":12.12,"SKU_B":5.15},
                 "Store2": {"SKU_A":16.16,"SKU_B":7.07}},
            }

# Creates the 'prob' variable to contain the problem data 
prob = LpProblem("StoreAllocation", LpMinimize)

# Creates a list of tuples containing all the possible routes for transport 
routes = [(w, s, i) for w in warehouses for s in stores for i in items]
 
# A dictionary called 'Vars' is created to contain the referenced variables(the routes) 
vars = LpVariable.dicts("Route", (warehouses, stores, items), 0, None, LpInteger) 
 
# The objective function is added to 'prob' first 
prob += (
    lpSum([vars[w][s][i] * costs[w][s][i] for (w, s, i) in routes]),
    "Sum_of_Transporting_Costs",
)

# Supply constraint, must not exceed Node Inventory
for w in warehouses:
    for i in items:
        prob += (
            lpSum([vars[w][s][i] for s in stores]) <= inventory[w][i],
            f"Sum_of_Products_out_of_Warehouse_{w}{i}",
        )

# Supply constraint, supply to equal demand
for s in stores:
    for i in items:
        prob += (
            lpSum([vars[w][s][i] for w in warehouses]) == demand[s][i],
            f"Sum_of_Products_into_Store{s}{i}",
        ) 

        
# The problem data is written to an .lp file
prob.writeLP("TestProblem.lp")

prob.solve()
# The status of the solution is printed to the screen 
print("Status:", LpStatus[prob.status])
# Each of the variables is printed with it's resolved optimum value 
for v in prob.variables():
    print(v.name, "=", v.varValue)
# The optimised objective function value is printed to the screen 
print("Total Cost of Fulfillment = ", value(prob.objective))  
 

This is good.这很好。 Your model is set up well.您的 model 已设置好。 Let's talk about supply...让我们谈谈供应...

So this is a common transshipment model and you want to minimize cost, but the default answer is to ship nothing for a cost of zero, which is not good.所以这是一个常见的转运 model 并且你想最小化成本,但默认答案是零成本不运送任何东西,这不好。 As you know, you need upward pressure on the deliveries to meet demand, or at least do the best you can with the inventory on hand if demand > inventory.如您所知,您需要对交货施加上行压力以满足需求,或者至少在需求 > 库存的情况下尽最大努力处理现有库存。

The first "cheap and easy" thing to do is to reduce the aggregate deliveries of each product to what is available... across all stores.第一件“便宜又容易”的事情是将每种产品的总交付量减少到可用的……所有商店。 In your current code you are trying to force the deliveries == demand, which may not be possible.在您当前的代码中,您试图强制交付 == 需求,这可能是不可能的。 So you can take a step back and just say "deliver the aggregate demand, or at least all of the inventory".因此,您可以退后一步,只说“提供总需求,或至少提供所有库存”。 In pseudocode that would be something like:在伪代码中类似于:

total_delivery[sku] = min(all inventory, demand)

You could do the same for the other SKU's and then just sum all of the deliveries by SKU across all warehouses and destintations and force:您可以对其他 SKU 执行相同的操作,然后仅将 SKU 在所有仓库和目的地的所有交付相加并强制执行:

for SKU in SKUs:
  sum(deliver[w, s, sku] for w in warehouses for s in stores) >= total_delivery[sku]

Realize that the parameter total_delivery is NOT a variable, it is discernible from the data before doing anything.意识到参数total_delivery不是一个变量,在做任何事情之前它可以从数据中辨别出来。

The above will make the model run, but there are issues.以上将使 model 运行,但存在问题。 The model will likely "overdeliver" to some sites because we are aggregating the demand. model 可能会“超额投放”到某些站点,因为我们正在汇总需求。 So if you have 100 of something and split demand of 50/50 in 2 sites, it will deliver 100 to the cheapest site.... not good.因此,如果您有 100 个东西,并且将 50/50 的需求分成 2 个站点,它将向最便宜的站点提供 100 个……不好。 So you need to add a constraint to limit the delivery to each site to the demand, regardless of source.因此,您需要添加一个约束来限制对每个站点的交付需求,而不管来源如何。 Something like:就像是:

for s in stores:
  for sku in skus:
    sum(deliver[w, s, sku] for w in warehouses) <= demand[s, sku]

The addition of those should make your model run.添加这些应该使您的 model 运行。 The result (if inventory is short) will be disproportionate delivery to the cheap sites.结果(如果库存不足)将不成比例地交付到廉价站点。 Perhaps that is OK.也许这没关系。 Balancing it is a little complicated.平衡它有点复杂。

... ...

Regarding your model, you have your variable constructed as a nested list... That is why you need to index it as vars[w][s][i] .关于您的 model,您将变量构造为嵌套列表......这就是为什么需要将其索引为vars[w][s][i]原因。 That is perfectly fine, but I find it much easier to tuple-index the variables and you already have the basis set routes to work with.这很好,但我发现对变量进行元组索引要容易得多,而且你已经有了可以使用的基组routes So I would:所以我会:

deliver = LpVariable.dicts("deliver", routes, 0, None, LpInteger)

then you can index it like I have in the examples above...然后你可以像我在上面的例子中那样索引它......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM