8.6. Python API

本章节提供MindOpt的Python API手册,内容见下文。

8.6.24. Examples

定制食谱

有许多种不同的食物,每种食物可以提供不同的营养元素。请问我们该如何做出食物摄入的决策,以使得在满足人体每天所需的各种营养的前提下,能够最小化购买食物所花费的金额?

集合

  • 食物集合 \(F\)

  • 营养集合 \(N\)

参数

  • 每单位1的食物 \(j\) 中关于营养 \(i \in N\) 的含量为 \(a_{ij}\)

  • 获得单位1的食物 \(j\in F\) 的代价为 \(c_j\)

决策变量

  • \(x_j\) 是某人每天对食物 \(j\in F\) 的摄入量

目标函数

  • 最小化食物的总采购金额: \(\text{minimize}~ \sum_{j\in F} c_{j}x_j\)

约束

  • 每人对营养 \(i\in N\) 的需求介于最小值 \(n_{\min,i}\) 和最大值 \(n_{\max,i}\) 之间: \(n_{\min,i} \le \sum_{j\in F} a_{ij} x_j \le n_{\max,i},~~ \forall\, i\in N\)


from mindoptpy import *

req = \
{
    # requirement: ( lower bound,   upper bound)
    "Cal"        : (         2000, MDO.INFINITY),
    "Carbo"      : (          350,          375),
    "Protein"    : (           55, MDO.INFINITY),
    "VitA"       : (          100, MDO.INFINITY),
    "VitC"       : (          100, MDO.INFINITY),
    "Calc"       : (          100, MDO.INFINITY),
    "Iron"       : (          100, MDO.INFINITY),
    "Volume"     : (-MDO.INFINITY,           75)
}

food = \
{
    # food            : ( lower bound,  upper bound, cost)
    "Cheeseburger"    : (           0, MDO.INFINITY, 1.84),
    "HamSandwich"     : (           0, MDO.INFINITY, 2.19),
    "Hamburger"       : (           0, MDO.INFINITY, 1.84),
    "FishSandwich"    : (           0, MDO.INFINITY, 1.44),
    "ChickenSandwich" : (           0, MDO.INFINITY, 2.29),
    "Fries"           : (           0, MDO.INFINITY, 0.77),
    "SausageBiscuit"  : (           0, MDO.INFINITY, 1.29),
    "LowfatMilk"      : (           0, MDO.INFINITY, 0.60),
    "OrangeJuice"     : (           0, MDO.INFINITY, 0.72)
}

req_value = \
{
    # (requirement, food              ) : value
    ( "Cal",        "Cheeseburger"    ) : 510,
    ( "Cal",        "HamSandwich"     ) : 370,
    ( "Cal",        "Hamburger"       ) : 500,
    ( "Cal",        "FishSandwich"    ) : 370,
    ( "Cal",        "ChickenSandwich" ) : 400,
    ( "Cal",        "Fries"           ) : 220,
    ( "Cal",        "SausageBiscuit"  ) : 345,
    ( "Cal",        "LowfatMilk"      ) : 110,
    ( "Cal",        "OrangeJuice"     ) : 80,

    ( "Carbo",      "Cheeseburger"    ) : 34,
    ( "Carbo",      "HamSandwich"     ) : 35,
    ( "Carbo",      "Hamburger"       ) : 42,
    ( "Carbo",      "FishSandwich"    ) : 38,
    ( "Carbo",      "ChickenSandwich" ) : 42,
    ( "Carbo",      "Fries"           ) : 26,
    ( "Carbo",      "SausageBiscuit"  ) : 27,
    ( "Carbo",      "LowfatMilk"      ) : 12,
    ( "Carbo",      "OrangeJuice"     ) : 20,

    ( "Protein",    "Cheeseburger"    ) : 28,
    ( "Protein",    "HamSandwich"     ) : 24,
    ( "Protein",    "Hamburger"       ) : 25,
    ( "Protein",    "FishSandwich"    ) : 14,
    ( "Protein",    "ChickenSandwich" ) : 31,
    ( "Protein",    "Fries"           ) : 3,
    ( "Protein",    "SausageBiscuit"  ) : 15,
    ( "Protein",    "LowfatMilk"      ) : 9,
    ( "Protein",    "OrangeJuice"     ) : 1,

    ( "VitA",       "Cheeseburger"    ) : 15,
    ( "VitA",       "HamSandwich"     ) : 15,
    ( "VitA",       "Hamburger"       ) : 6,
    ( "VitA",       "FishSandwich"    ) : 2,
    ( "VitA",       "ChickenSandwich" ) : 8,
    ( "VitA",       "Fries"           ) : 0,
    ( "VitA",       "SausageBiscuit"  ) : 4,
    ( "VitA",       "LowfatMilk"      ) : 10,
    ( "VitA",       "OrangeJuice"     ) : 2,

    ( "VitC",       "Cheeseburger"    ) : 6,
    ( "VitC",       "HamSandwich"     ) : 10,
    ( "VitC",       "Hamburger"       ) : 2,
    ( "VitC",       "FishSandwich"    ) : 0,
    ( "VitC",       "ChickenSandwich" ) : 15,
    ( "VitC",       "Fries"           ) : 15,
    ( "VitC",       "SausageBiscuit"  ) : 0,
    ( "VitC",       "OrangeJuice"     ) : 4,
    ( "VitC",       "LowfatMilk"      ) : 120,

    ( "Calc",       "Cheeseburger"    ) : 30,
    ( "Calc",       "HamSandwich"     ) : 20,
    ( "Calc",       "Hamburger"       ) : 25,
    ( "Calc",       "FishSandwich"    ) : 15,
    ( "Calc",       "ChickenSandwich" ) : 15,
    ( "Calc",       "Fries"           ) : 0,
    ( "Calc",       "SausageBiscuit"  ) : 20,
    ( "Calc",       "LowfatMilk"      ) : 30,
    ( "Calc",       "OrangeJuice"     ) : 2,

    ( "Iron",       "Cheeseburger"    ) : 20,
    ( "Iron",       "HamSandwich"     ) : 20,
    ( "Iron",       "Hamburger"       ) : 20,
    ( "Iron",       "FishSandwich"    ) : 10,
    ( "Iron",       "ChickenSandwich" ) : 8,
    ( "Iron",       "Fries"           ) : 2,
    ( "Iron",       "SausageBiscuit"  ) : 15,
    ( "Iron",       "LowfatMilk"      ) : 0,
    ( "Iron",       "OrangeJuice"     ) : 2,

    ( "Volume",     "Cheeseburger"    ) : 4,
    ( "Volume",     "HamSandwich"     ) : 7.5,
    ( "Volume",     "Hamburger"       ) : 3.5,
    ( "Volume",     "FishSandwich"    ) : 5,
    ( "Volume",     "ChickenSandwich" ) : 7.3,
    ( "Volume",     "Fries"           ) : 2.6,
    ( "Volume",     "SausageBiscuit"  ) : 4.1,
    ( "Volume",     "LowfatMilk"      ) : 8,
    ( "Volume",     "OrangeJuice"     ) : 12
}

# 建立模型
m = Model()

# 添加决策变量
variable = {}
for food_name, food_data in food.items():
    variable[food_name] = m.addVar(
        lb=food_data[0], ub=food_data[1], vtype=MDO.CONTINUOUS, name=food_name
    )

# 添加约束
# 应满足每日获取的各种营养素在建议的范围内
cons = {}
for req_name, req_data in req.items():
    cons[req_name] = m.addRange(
        quicksum(
            variable[food_name] * req_value[(req_name, food_name)]
            for food_name in food.keys()
        ),
        req_data[0],
        req_data[1],
    )

# 添加目标函数
objective = quicksum(variable[i] * food[i][2] for i in food.keys())
m.setObjective(objective, MDO.MINIMIZE)
m.optimize()

# 打印结果
for i in variable:
    print("Amount of " + i + " intake :" + str(variable[i].X))
print("Total meal cost : " + str(objective.getValue()))
for req_name, req_data in req.items():
    print(
        "Final intake amount of "
        + req_name
        + ": "
        + str(
            quicksum(
                variable[food_name] * req_value[(req_name, food_name)]
                for food_name in food.keys()
            ).getValue()
        )
    )

设施选址

当前有两个商场,商场的位置已经确定。由若干建造仓库的备选位置,已知其坐标和其建造成本。我们假定从仓库到商场的运输成本与货物数量无关但与其之间的距离有关,请找到最小成本的仓库建造和运输方案。

集合

  • 备选仓库 \(F\)

  • 商场 \(M\)

参数

  • 从仓库 \(i\in F\) 向商场 \(j \in M\) 运输的成本为 \(a_{ij}\)

  • 建造仓库 \(i\in F\) 的代价为 \(c_j\)

  • 商场 \(j \in M\) 的需求的货物量为 \(d_{ij}\)

决策变量

  • \(x_i\) 是是否在备选位置 \(i\in F\) 建造仓库。其取值必须为0或1,值为0时表示不建造,值为1时表示建造。

  • \(y_{ij}\) 表示是在从备选位置 \(i\in F\) 的仓库向 \(j \in M\) 的商场运输的距离货物数量。

目标函数

最小化仓库建造和商品运输的成本: \(\text{minimize}~ \sum_{i\in F} c_{i}x_i+\sum_{i\in F,j \in M} a_{ij}y_{ij}\) :

\(\text{minimize}~ \sum_{i\in F} c_{i}x_i + \sum_{i\in F,j \in M} a_{ij}y_{ij}\)

约束

\(\sum_{i\in F} y_{ij} = d_{j}, ~~ \forall j\in M\)

\(x_i d_j - \sum_{k\in M} y_{ik} = 0, ~~ \forall i \in F, j \in M\)


from mindoptpy import *

import math

# 本例子的目标是为了找到最小成本的仓库建造和运输方案

# 有两个商场,商场的位置已经确定,分别是(0, 1.7)和(1.4, 2.9), 所需要的货物重量为100单位和200单位
market_info = tupledict([((0, 1.7), 100), ((1.4, 2.9), 200)])
market_keys = list(market_info.keys())
market_num = len(market_info)

# 仓库位置和建造成本
facilities_info = tupledict(
    [
        ((0, 1),   3),
        ((0, 2),   1),
        ((1, 0), 1.5),
        ((1, 1), 1.3),
        ((1, 2), 1.8),
        ((2, 0), 1.6),
        ((2, 1), 1.1),
        ((2, 2), 1.9),
    ]
)
facilities_keys = list(facilities_info.keys())
facilities_num = len(facilities_info)
transport_fee_per_m = 1.23

# 建立模型
m = Model("Facilities")

# 添加决策变量
# x代表是否在该地建仓库
x = m.addVars(len(facilities_info), vtype=MDO.BINARY)
# y代表从j仓库运向i商场的货物量,值的类型为CONTINUOUS类型,下限为0代表不能从j仓库运送小于0单位的货物到i商场
provide_quantity = [(i, j) for j in range(facilities_num) for i in range(market_num)]
y = m.addVars(provide_quantity, lb=0, vtype=MDO.CONTINUOUS)

# 增加约束
# 约束1 已经决定建造的仓库必须满足所有商场的货物需求
m.addConstrs(
    (
        quicksum(y[(i, j)] for j in range(facilities_num))
        == market_info[market_keys[i]]
        for i in range(market_num)
    ),
    name="is_satisfy",
)
# 约束2 如果不建仓库,则此仓库位置运送给所有商场的货物为0
m.addConstrs(
    (
        y[(i, j)] / market_info[market_keys[i]] <= x[j]
        for i in range(market_num)
        for j in range(facilities_num)
    ),
    name="is_built",
)

# 增加目标函数: 最小化运输费用和建造仓库的费用的总和
# 假设从a地运往b地的运输费用只和距离有关,和货物重量无关
def transportation_fee(pos1, pos2):
    x1 = pos1[0] - pos2[0]
    x2 = pos1[1] - pos2[1]
    return math.sqrt(x1 * x1 + x2 * x2) ** 0.5 * transport_fee_per_m

objective1 = quicksum(
    x[j] * facilities_info[facilities_keys[j]] for j in range(facilities_num)
)

objective2 = quicksum(
    y[(i, j)] * transportation_fee(market_keys[i], facilities_keys[j])
    for j in range(facilities_num)
    for i in range(market_num)
)

m.setObjective(objective1+objective2, MDO.MINIMIZE)

# 开始优化
m.optimize()

# 打印结果
for i in x:
    if x[i].X:
        print(
            "A warehouse should build at No."
            + str(i + 1)
            + " location at "
            + str(facilities_keys[i])
        )
        for j in range(market_num):
            print(
                "This warehouse transport "
                + str(y[(j, i)].X)
                + " units of goods to "
                + str(j)
                + "supermarkets"
            )

人力分配

在一周中,工厂每天需要的工人数目不同。现在已知每个工人的日工资及其在一周中可以出勤的日期。需要计算如何安排工人,才能在满足工厂运行要求的情况下做到工资成本最低。

集合

  • 一周日期 \(D = 1,2,3...,7\)

  • 工厂需要的工人数目 \(N\)

  • 工人 \(S\)

参数

  • 工厂第 \(i \in D\) 天所需的工人数目为 \(n_i\)

  • 工人 \(j\in S\) 的日工资为 \(s_j\)

  • 表示工人可工作时间的序列,共有 \(T\) 组工人-工作时间数对 \((d_i,t_i)\) ,其中 \(d_i\in S,t_i \in D, i = 1,2,3...T\)

目标函数

最小化支付的工资: \(\text{minimize}~ \sum_{i=1,2,3...T} x_is_{d_i}\)

决策变量

  • \(x_i( i = 1,2,3...T)\) 表示可工作时间序列中,当日改工人是否上班。其取值必须为0或1,值为0时表示不上班,值为1时表示上班。

约束

\(\sum_{d_i=r} x_i = n_{r}, ~~\forall r\in D\)


from mindoptpy import *

# 每天需要的人力数
day_name, workers_per_day = multidict(
    {
        "Monday":    3,
        "Tuesday":   1,
        "Wednesday": 4,
        "Thursday":  2,
        "Friday":    1,
        "Saturday":  3,
        "Sunday":    3,
    }
)

# 每个工人一天的工资
workers, pay = multidict(
    {
        "Xiaoming": 13,
        "Huahua":   10,
        "HongHong": 11,
        "Dahua":     8,
        "Lihua":     9,
        "Niuniu":   14,
        "Gouzi":    14,
    }
)

# 每个工人可以出勤的时间
availability = tuplelist(
    [
        ("Xiaoming",   "Tuesday"),
        ("Xiaoming", "Wednesday"),
        ("Xiaoming",    "Friday"),
        ("Xiaoming",    "Sunday"),
        ("Huahua",      "Monday"),
        ("Huahua",     "Tuesday"),
        ("Huahua",      "Friday"),
        ("Huahua",    "Saturday"),
        ("HongHong", "Wednesday"),
        ("HongHong",  "Thursday"),
        ("HongHong",    "Friday"),
        ("HongHong",    "Sunday"),
        ("Dahua",      "Tuesday"),
        ("Dahua",    "Wednesday"),
        ("Dahua",       "Friday"),
        ("Dahua",     "Saturday"),
        ("Lihua",       "Monday"),
        ("Lihua",      "Tuesday"),
        ("Lihua",    "Wednesday"),
        ("Lihua",     "Thursday"),
        ("Lihua",       "Friday"),
        ("Lihua",       "Sunday"),
        ("Niuniu",      "Monday"),
        ("Niuniu",     "Tuesday"),
        ("Niuniu",   "Wednesday"),
        ("Niuniu",    "Saturday"),
        ("Gouzi",       "Monday"),
        ("Gouzi",      "Tuesday"),
        ("Gouzi",    "Wednesday"),
        ("Gouzi",       "Friday"),
        ("Gouzi",     "Saturday"),
        ("Gouzi",       "Sunday"),
    ]
)

# 建立模型
m = Model('WorkForce')

# 添加决策变量
# x[(worker, day)]这个变量代表该工人是否在当天工作
# 用(worker, day)来初始化决策变量,可以保证每个工人只在他们允许的天内出勤
x = m.addVars(availability, vtype=MDO.BINARY, name="schedule")


# 增加约束
# 约束: 满足每天的人力需求
c1 = m.addConstrs((x.sum("*", day) == workers_per_day[day] for day in day_name))

# 增加目标函数
objective = quicksum(
    pay[worker_day[0]] * x[(worker_day[0], worker_day[1])]
    for worker_day in availability
)
m.setObjective(objective, MDO.MINIMIZE)

# 开始优化
m.optimize()

# 打印结果
for worker, day in availability:
    if x[(worker, day)].X:
        print(worker + " should work at " + day)

print("The total cost is " + str(objective.getValue()))