8.8. C# API

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

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


using Mindopt;

namespace Example
{
    public class ExampleDiet
    {
        public static void Main(string[] args)
        {
            MDOEnv env = new MDOEnv();
            MDOModel model = new MDOModel(env);
            model.Set(MDO.StringAttr.ModelName, "diet");

            Dictionary<string, double[]> requirements = new()
            {
                {"Calories",    new double[]{ 2000, MDO.INFINITY }},
                {"Carbohydrates",         new double[]{ 350, 375 }},
                {"Protein",       new double[]{ 55, MDO.INFINITY }},
                {"VitA",         new double[]{ 100, MDO.INFINITY }},
                {"VitC",         new double[]{ 100, MDO.INFINITY }},
                {"Calcium",      new double[]{ 100, MDO.INFINITY }},
                {"Iron",         new double[]{ 100, MDO.INFINITY }},
                {"Volume",        new double[]{ -MDO.INFINITY,75 }}
            };

            Dictionary<string, double[]> foods = new()
            {
                {"Cheeseburger",    new double[]{ 0, MDO.INFINITY, 1.84 }},
                {"HamSandwich",     new double[]{ 0, MDO.INFINITY, 2.19 }},
                {"Hamburger",       new double[]{ 0, MDO.INFINITY, 1.84 }},
                {"FishSandwich",    new double[]{ 0, MDO.INFINITY, 1.44 }},
                {"ChickenSandwich", new double[]{ 0, MDO.INFINITY, 2.29 }},
                {"Fries",           new double[]{ 0, MDO.INFINITY, 0.77 }},
                {"SausageBiscuit",  new double[]{ 0, MDO.INFINITY, 1.29 }},
                {"LowfatMilk",      new double[]{ 0, MDO.INFINITY, 0.60 }},
                {"OrangeJuice",     new double[]{ 0, MDO.INFINITY, 0.72 }}
            };

            double[,] values = new double[,]
            {
                { 510.0, 34.0, 28.0, 15.0,   6.0, 30.0, 20.0,  4.0 },
                { 370.0, 35.0, 24.0, 15.0,  10.0, 20.0, 20.0,  7.5 },
                { 500.0, 42.0, 25.0,  6.0,   2.0, 25.0, 20.0,  3.5 },
                { 370.0, 38.0, 14.0,  2.0,   0.0, 15.0, 10.0,  5.0 },
                { 400.0, 42.0, 31.0,  8.0,  15.0, 15.0,  8.0,  7.3 },
                { 220.0, 26.0,  3.0,  0.0,  15.0,  0.0,  2.0,  2.6 },
                { 345.0, 27.0, 15.0,  4.0,   0.0, 20.0, 15.0,  4.1 },
                { 110.0, 12.0,  9.0, 10.0, 120.0, 30.0,  0.0,  8.0 },
                {  80.0, 20.0,  1.0,  2.0,   4.0,  2.0,  2.0, 12.0 }
            };

            string[] nutritionNames = requirements.Keys.Cast<string>().ToArray();
            int numNutrition = nutritionNames.Length;
            string[] foodNames = foods.Keys.Cast<string>().ToArray();
            int numFood = foodNames.Length;

            Dictionary<string, Dictionary<string, double>> reqValues = new();

            for (int i = 0; i < foodNames.Length; i++)
            {
                reqValues[foodNames[i]] = new Dictionary<string, double>();
                for (int j = 0; j < nutritionNames.Length; j++)
                    reqValues[foodNames[i]][nutritionNames[j]] = values[i, j];
            }

            try
            {
                MDOVar[] foodVars = new MDOVar[numFood];
                for (int i = 0; i < numFood; ++i)
                {
                    double[] foodData = foods[foodNames[i]];
                    foodVars[i] = model.AddVar(foodData[0], foodData[1], 0, MDO.CONTINUOUS, foodNames[i]);
                }

                // 添加约束
                for (int i = 0; i < numNutrition; i++)
                {
                    MDOLinExpr lin = new MDOLinExpr();
                    string nutri = nutritionNames[i];
                    for (int j = 0; j < numFood; j++)
                    {
                        string food = foodNames[j];
                        lin.AddTerm(reqValues[food][nutri], foodVars[j]);
                    }

                    model.AddRange(lin, requirements[nutritionNames[i]][0], requirements[nutritionNames[i]][1], nutritionNames[i]);
                }

                // 添加目标函数
                MDOLinExpr linExpr = new MDOLinExpr();
                for (int i = 0; i < numFood; i++)
                    linExpr.AddTerm(foods[foodNames[i]][2], foodVars[i]);

                model.SetObjective(linExpr, MDO.MINIMIZE);

                model.Optimize();

                model.Write("ExmapleDiet.mps");
                // 打印结果
                foreach (MDOVar foodVar in foodVars)
                    Console.WriteLine($"You should buy {foodVar.Get(MDO.DoubleAttr.X)} unit of {foodVar.Get(MDO.StringAttr.VarName)}");

            }
            catch (MDOException e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                model.Dispose();
                env.Dispose();
            }
        }
    }
}

设施选址

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

集合

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


using Mindopt;

namespace Example
{
    public class ExampleFacility
    {
        public static void Main(string[] args)
        {
            MDOEnv env = new MDOEnv();
            MDOModel model = new MDOModel(env);
            model.Set(MDO.StringAttr.ModelName, "Facility");

            Dictionary<Tuple<double, double>, int> marketInfo = new()
            {
                {Tuple.Create(0.0, 1.7), 100},
                {Tuple.Create(1.4, 2.9), 200},
            };

            Dictionary<Tuple<int, int>, double> facilitiesInfo = new()
            {
                {Tuple.Create(0, 1), 3.0},
                {Tuple.Create(0, 2), 1.0},
                {Tuple.Create(1, 0), 1.5},
                {Tuple.Create(1, 1), 1.3},
                {Tuple.Create(1, 2), 1.8},
                {Tuple.Create(2, 0), 1.6},
                {Tuple.Create(2, 1), 1.1},
                {Tuple.Create(2, 2), 1.9}
            };

            double transportFeePerM = 1.23;

            try
            {
                int i = 0;
                int j = 0;

                MDOVar[] xVars = model.AddVars(facilitiesInfo.Count, MDO.BINARY);
                MDOVar[,] yVars = new MDOVar[marketInfo.Count, facilitiesInfo.Count];
                for (i = 0; i < marketInfo.Count; i++)
                {
                    for (j = 0; j < facilitiesInfo.Count; j++)
                        yVars[i, j] = model.AddVar(0, MDO.INFINITY, 0, MDO.CONTINUOUS, $"{i}{j}");
                }

                i = 0;

                foreach (KeyValuePair<Tuple<double, double>, int> marketPair in marketInfo)
                {
                    MDOLinExpr linExpr = new MDOLinExpr();

                    for (j = 0; j < facilitiesInfo.Count; j++)
                    {
                        linExpr.AddTerm(1, yVars[i, j]);
                        MDOLinExpr lhe = new MDOLinExpr();
                        lhe.AddTerm(1.0 / marketPair.Value, yVars[i, j]);
                        model.AddConstr(lhe, MDO.LESS_EQUAL, xVars[j], $"is_built[{i}, {j}]");
                    }
                    model.AddConstr(linExpr, MDO.EQUAL, marketPair.Value, $"is_satisify[{i}, {j}]");
                    i++;
                }

                MDOLinExpr objective = new MDOLinExpr();

                j = 0;
                foreach (KeyValuePair<Tuple<int, int>, double> facPair in facilitiesInfo)
                {
                    objective.AddTerm(facPair.Value, xVars[j]);
                    i = 0;
                    foreach (Tuple<double, double> marketPos in marketInfo.Keys)
                    {
                        objective.AddTerm(calcTransFee(marketPos, facPair.Key, transportFeePerM), yVars[i, j]);
                        i++;
                    }
                    j++;
                }

                model.SetObjective(objective, MDO.MINIMIZE);
                model.Optimize();
                model.Write("ExampleFacility.mps");

                i = 0;
                foreach (Tuple<int, int> pos in facilitiesInfo.Keys)
                {
                    MDOVar x = xVars[i];
                    if (x.Get(MDO.DoubleAttr.X) == 1) {
                        Console.Write($"The No.{i} warehouse should be built at ");
                        Console.WriteLine($"({pos.Item1}, {pos.Item2})");
                    }
                    i++;
                }
                Console.WriteLine($"Trnasport fee is: {objective.Value} ");
            }
            catch (MDOException e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                model.Dispose();
                env.Dispose();
            }
        }

        private static double calcTransFee(Tuple<double, double> pos1, Tuple<int, int> pos2, double unitPrice)
        {
            double x1 = pos1.Item1 - pos2.Item1;
            double x2 = pos1.Item2 - pos2.Item2;
            return Math.Sqrt(x1 * x1 + x2 * x2) * unitPrice;
        }
    }
}

人力分配

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

集合

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


using Mindopt;

namespace Example
{
    public class ExampleWorkforce
    {
        public static void Main(string[] args)
        {
            // 创建MindOpt模型
            MDOEnv env = new MDOEnv();
            MDOModel model = new MDOModel(env);

            // 每天需要的工日数目
            string[] dayName = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
            int[] workersPerDay = {  3, 1, 4, 2, 1, 3, 3 };

            // 对应工人的工资
            Dictionary<string, int> workers = new()
            {
                {"Xiaoming", 13},
                {"Huahua",   10},
                {"HongHong", 11},
                {"Dahua",     8},
                {"Lihua",     9},
                {"Niuniu",   14},
                {"Gouzi",    14}
            };

            List<string> workers_name = new List<string>(workers.Keys);

            // 定义可用集合
            List<Tuple<string, string>> availability = new()
            {
                Tuple.Create( "Xiaoming",     "Tuesday" ),
                Tuple.Create( "Xiaoming",   "Wednesday" ),
                Tuple.Create( "Xiaoming",      "Friday" ),
                Tuple.Create( "Xiaoming",      "Sunday" ),
                Tuple.Create( "Huahua",        "Monday" ),
                Tuple.Create( "Huahua",       "Tuesday" ),
                Tuple.Create( "Huahua",        "Friday" ),
                Tuple.Create( "Huahua",      "Saturday" ),
                Tuple.Create( "HongHong",   "Wednesday" ),
                Tuple.Create( "HongHong",    "Thursday" ),
                Tuple.Create( "HongHong",      "Friday" ),
                Tuple.Create( "HongHong",      "Sunday" ),
                Tuple.Create( "Dahua",        "Tuesday" ),
                Tuple.Create( "Dahua",      "Wednesday" ),
                Tuple.Create( "Dahua",         "Friday" ),
                Tuple.Create( "Dahua",       "Saturday" ),
                Tuple.Create( "Lihua",         "Monday" ),
                Tuple.Create( "Lihua",        "Tuesday" ),
                Tuple.Create( "Lihua",      "Wednesday" ),
                Tuple.Create( "Lihua",       "Thursday" ),
                Tuple.Create( "Lihua",         "Friday" ),
                Tuple.Create( "Lihua",         "Sunday" ),
                Tuple.Create( "Niuniu",        "Monday" ),
                Tuple.Create( "Niuniu",       "Tuesday" ),
                Tuple.Create( "Niuniu",     "Wednesday" ),
                Tuple.Create( "Niuniu",      "Saturday" ),
                Tuple.Create( "Gouzi",         "Monday" ),
                Tuple.Create( "Gouzi",        "Tuesday" ),
                Tuple.Create( "Gouzi",      "Wednesday" ),
                Tuple.Create( "Gouzi",         "Friday" ),
                Tuple.Create( "Gouzi",       "Saturday" ),
                Tuple.Create( "Gouzi",         "Sunday" )
            };

            try
            {
                MDOVar[] x = new MDOVar[availability.Count];
                for (int i = 0; i < x.Length; i++)
                {
                    Tuple<string, string> workerDay = availability[i];
                    x[i] = model.AddVar(0, 1, 0, MDO.BINARY, $"sched({workerDay.Item1}, {workerDay.Item2})");
                }

                Dictionary<string, MDOLinExpr> dayCount = new();
                foreach (string day in dayName) dayCount[day] = new MDOLinExpr();

                for (int i = 0; i < availability.Count; i++)
                {
                    MDOLinExpr expr = new MDOLinExpr();
                    Tuple<string, string> workerDay = availability[i];
                    dayCount[workerDay.Item2].AddTerm(1, x[i]);
                }

                for (int i = 0; i < dayName.Length; i++)
                {
                    MDOLinExpr expr = dayCount[dayName[i]];
                    model.AddConstr(expr, MDO.EQUAL, workersPerDay[i], "c1_" + availability[i].Item2);
                }

                MDOLinExpr obj = new MDOLinExpr();
                for (int i = 0; i < availability.Count; i++)
                    obj.AddTerm(workers[availability[i].Item1], x[i]);

                model.SetObjective(obj, MDO.MINIMIZE);
                model.Optimize();

                for (int i = 0; i < availability.Count; i++)
                {
                    if (x[i].Get(MDO.DoubleAttr.X) > 0)
                        Console.WriteLine($"{availability[i].Item1} should work at {availability[i].Item2}");
                }
                Console.WriteLine($"The total cost is { + model.Get(MDO.DoubleAttr.ObjVal)}");
            }
            catch (MDOException e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                model.Dispose();
                env.Dispose();
            }
        }
    }
}