8.5. C++ API

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

8.5.18. 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\)


#include <iostream>
#include "MindoptCpp.h"
#include <map>

using namespace std;

// 定义所需的营养素的摄入量
map<string, pair<double, double>> req = {
        {"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 }}
};

// 定义摄入的食物的下限,上限,和每单位的价格
map<string, tuple<double, double, double>> food = {
        {"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 }}
};

// 定义摄入的每单位食物的各种营养素的含量
map<pair<string, string>, double> req_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" },    20 },
        {{"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}
};

int main(int argc, char *argv[]) {
    try {
        // 建立模型
        MDOEnv env = MDOEnv();
        MDOModel m = MDOModel(env);

        // 添加决策变量
        map<string, MDOVar> variable;
        map<string, tuple<double, double, double>>::iterator food_it;
        for (food_it = food.begin(); food_it != food.end(); ++food_it) {
            string food_name = food_it->first;
            tuple<double, double, double> food_data = food_it->second;
            variable[food_name] = m.addVar(get<0>(food_data), get<1>(food_data),
                                           0.0, MDO_CONTINUOUS, food_name);
        }

        // 添加约束
        // 应满足每日获取的各种营养素在建议的范围内
        map<string, MDOConstr> cons;
        map<string, pair<double, double>>::iterator req_it;
        for (req_it = req.begin(); req_it != req.end(); ++req_it) {
            string req_name = req_it->first;
            pair<double, double> req_data = req_it->second;
            MDOLinExpr expr = 0;
            for (food_it = food.begin(); food_it != food.end(); ++food_it) {
                string food_name = food_it->first;
                tuple<double, double, double> food_data = food_it->second;
                expr += variable[food_name] * req_value[{req_name, food_name}];
            }
            cons[req_name] = m.addRange(expr, req_data.first, req_data.second);
        }

        // 添加目标函数
        MDOLinExpr objective = 0;

        for (food_it = food.begin(); food_it != food.end(); ++food_it) {
            string food_name = food_it->first;
            tuple<double, double, double> food_data = food_it->second;
            objective += variable[food_name] * get<2>(food_data);
        }
        m.setObjective(objective, 1);
        m.write("Test_cpp.mps");

        // 开始优化
        m.optimize();

        // 打印结果
        map<string, MDOVar>::iterator it;
        for (it = variable.begin(); it != variable.end(); ++it) {
            string food_name = it->first;
            MDOVar var = it->second;
            cout << "Amount of " << food_name << " intake: " << var.get(MDO_DoubleAttr_X) << endl;
        }
        cout << "Total meal cost: " << m.get(MDO_DoubleAttr_ObjVal) << endl;
        for (req_it = req.begin(); req_it != req.end(); ++req_it) {
            string req_name = req_it->first;
            pair<double, double> req_data = req_it->second;
            MDOLinExpr expr = 0;
            for (food_it = food.begin(); food_it != food.end(); ++food_it) {
                string food_name = food_it->first;
                expr += variable[food_name] * req_value[{req_name, food_name}];
            }
        }
    } catch (MDOException &e) {
        cout << "Error code = " << e.getErrorCode() << endl;
        cout << e.getMessage() << endl;
    } catch (...) {
        cout << "Error during optimization." << endl;
    }
    return 0;
}

设施选址

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

集合

  • 备选仓库 \(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\)


#include <iostream>
#include "MindoptCpp.h"
#include <map>
#include <vector>
#include <cmath>

using namespace std;
// 本例子的目标是为了找到最小成本的仓库建造和运输方案

// 有两个商场,商场的位置已经确定,分别是(0, 1.7)和(1.4, 2.9), 所需要的货物重量为100单位和200单位
map<vector<double>, int> marketInfo = {
        {vector<double>{0.0, 1.7}, 100},
        {vector<double>{1.4, 2.9}, 200}
};

vector<vector<double>> marketKeys = vector<vector<double>>{
        vector<double>{0.0, 1.7},
        vector<double>{1.4, 2.9},
};
size_t marketNum = marketInfo.size();

// 仓库位置和建造成本
map<vector<int>, double> facilitiesInfo = {
        {vector<int>{0, 1}, 3.0},
        {vector<int>{0, 2}, 1.0},
        {vector<int>{1, 0}, 1.5},
        {vector<int>{1, 1}, 1.3},
        {vector<int>{1, 2}, 1.8},
        {vector<int>{2, 0}, 1.6},
        {vector<int>{2, 1}, 1.1},
        {vector<int>{2, 2}, 1.9},
};

vector<vector<int>> facilitiesKeys = {
        vector<int>{0, 1},
        vector<int>{0, 2},
        vector<int>{1, 0},
        vector<int>{1, 1},
        vector<int>{1, 2},
        vector<int>{2, 0},
        vector<int>{2, 1},
        vector<int>{2, 2}
};
int facilitiesNum = facilitiesInfo.size();

double transportFeePerM = 1.23;

int main(int argc, char *argv[]) {
    // Define requirements
    try {
        MDOEnv env;
        MDOModel model(env);
        model.set(MDO_StringAttr_ModelName, "Facility");
        // 添加决策变量
        vector<MDOVar> xVars(facilitiesNum);
        for (int j = 0; j < facilitiesNum; j++) {
            xVars[j] = model.addVar(0, MDO_INFINITY, 0, MDO_BINARY, "Facility" + to_string(j));
        }
        vector<vector<MDOVar>> yVars(marketNum, vector<MDOVar>(facilitiesNum));
        for (int i = 0; i < marketNum; i++) {
            for (int j = 0; j < facilitiesNum; j++) {
                yVars[i][j] = model.addVar(0, MDO_INFINITY, 0, MDO_CONTINUOUS, to_string(i) + to_string(j));
            }
        }

        // 增加约束
        for (int i = 0; i < marketNum; i++) {
            // 约束1 已经决定建造的仓库必须满足所有商场的货物需求
            MDOLinExpr linExpr;
            vector<double> coeffs;
            vector<MDOVar> vars;
            for (int j = 0; j < facilitiesNum; j++) {
                coeffs.push_back(1);
                vars.push_back(yVars[i][j]);
                MDOLinExpr lhe = 1.0 / marketInfo[marketKeys[i]] * yVars[i][j];
                model.addConstr(lhe - xVars[j], MDO_LESS_EQUAL, 0,
                                "is_built[" + to_string(i) + "," + to_string(j) + "]");
            }
            linExpr.addTerms(coeffs.data(), vars.data(), coeffs.size());
            // 约束2 如果不建仓库,则此仓库位置运送给所有商场的货物为0
            model.addConstr(linExpr, MDO_EQUAL, marketInfo[marketKeys[i]], "is_satisfy_" + to_string(i));
        }

        // 增加目标函数: 最小化运输费用和建造仓库的费用的总和
        // 假设从a地运往b地的运输费用只和距离有关,和货物重量无关
        MDOLinExpr objective;
        vector<double> coeffs;
        vector<MDOVar> vars;
        for (int j = 0; j < facilitiesNum; j++) {
            coeffs.push_back(facilitiesInfo[facilitiesKeys[j]]);
            vars.push_back(xVars[j]);
        }
        for (int j = 0; j < facilitiesNum; j++) {
            for (int i = 0; i < marketNum; i++) {
                double x1 = marketKeys[i][0] - facilitiesKeys[j][0];
                double x2 = marketKeys[i][1] - facilitiesKeys[j][1];
                coeffs.push_back(sqrt(x1 * x1 + x2 * x2) * transportFeePerM);
                vars.push_back(yVars[i][j]);
            }
        }
        objective.addTerms(coeffs.data(), vars.data(), coeffs.size());
        model.setObjective(objective, MDO_MINIMIZE);

        // 开始优化
        model.optimize();

        // 打印结果
        for (int i = 0; i < facilitiesNum; i++) {
            MDOVar x = xVars[i];
            if (x.get(MDO_DoubleAttr_X) == 1) {
                cout << "The No." << i << " warehouse should be built at (" << facilitiesKeys[i][0] << ", "
                     << facilitiesKeys[i][1] << ")" << endl;
            }
        }
        model.write("TestFacility.mps");
        cout << model.get(MDO_DoubleAttr_ObjVal);
    } catch (MDOException &e) {
        cout << "Error code = " << e.getErrorCode() << endl;
        cout << e.getMessage() << endl;
    } catch (...) {
        cout << "Error during optimization." << endl;
    }
    return 0;
}

人力分配

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

集合

  • 一周日期 \(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\)


#include <iostream>
#include "MindoptCpp.h"
#include "map"

using namespace std;

// 每天需要的人力数
map<string, int> workers_per_day = {
        { "Monday",    3 },
        { "Tuesday",   1 },
        { "Wednesday", 4 },
        { "Thursday",  2 },
        { "Friday",    1 },
        { "Saturday",  3 },
        { "Sunday",    3 }
 };

// 每个工人一天的工资
map<string, int> pay = {
        { "Xiaoming", 13 },
        { "Huahua",   10 },
        { "HongHong", 11 },
        { "Dahua",    8 },
        { "Lihua",    9 },
        { "Niuniu",   14 },
        { "Gouzi",    14 }
 };

// 每个工人可以出勤的时间
vector<tuple<string, string>> availability = {
        { "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" }
 };


int main(int argc, char *argv[]) {
    try {
        MDOEnv env = MDOEnv();
        MDOModel model = MDOModel(env);

        // 添加决策变量
        // x[(worker, day)]这个变量代表该工人是否在当天工作
        // 用(worker, day)来初始化决策变量,可以保证每个工人只在他们允许的天内出勤
        map<pair<string, string>, MDOVar> x;
        vector<tuple<string, string>>::iterator availability_it;
        for (availability_it = availability.begin(); availability_it != availability.end(); ++availability_it) {
            string worker = get<0>(*availability_it);
            string day = get<1>(*availability_it);
            x[{ worker, day }] = model.addVar(0.0, 1.0, 0.0, MDO_BINARY, "schedule");
         }

        // 增加约束
        // 约束: 满足每天的人力需求
        map<string, int>::iterator workers_per_day_it;
        for (workers_per_day_it = workers_per_day.begin(); workers_per_day_it != workers_per_day.end(); ++workers_per_day_it) {
            string day = workers_per_day_it->first;
            int num_workers = workers_per_day_it->second;
            MDOLinExpr expr = 0;

            for (availability_it = availability.begin(); availability_it != availability.end(); ++availability_it) {
                string worker = get<0>(*availability_it);
                string d = get<1>(*availability_it);
                if (d == day) {
                    expr += x[{ worker, day }];
                 }
             }
            model.addConstr(expr == num_workers);
         }

        // 增加目标函数
        MDOLinExpr objective = 0;
        for (availability_it = availability.begin(); availability_it != availability.end(); ++availability_it) {
            string worker = get<0>(*availability_it);
            string day = get<1>(*availability_it);
            objective += pay[worker] * x[{ worker, day }];
         }
        model.setObjective(objective, MDO_MINIMIZE);

        // 开始优化
        model.optimize();
        model.write("test_cpp.mps");

        // 打印结果
        for (availability_it = availability.begin(); availability_it != availability.end(); ++availability_it) {
            string worker = get<0>(*availability_it);
            string day = get<1>(*availability_it);
            if (x[{ worker, day }].get(MDO_DoubleAttr_X) > 0.5) {
                cout << worker << " should work at " << day << endl;
             }
         }
        cout << "The total cost is " << model.get(MDO_DoubleAttr_ObjVal) << endl;

     } catch (MDOException& e) {
        cout << "Error code = " << e.getErrorCode() << endl;
        cout << e.getMessage() << endl;
     } catch (...) {
        cout << "Error during optimization." << endl;
     }
    return 0;
 }