8.7. C# API

This section provides MindOpt C# API reference. Contents of the C# API are the following:

8.7.17. Examples

Diet problem

There are many different kinds of food, each of which can provide various nutrients. How should we make decisions regarding food intake to minimize the amount spent on purchasing food while ensuring that the daily nutritional requirements of the human body are met?

Sets

  • Food set \(F\)

  • Nutrient set \(N\)

Parameters

  • The content of nutrient \(i \in N\) in one unit of food \(j\) is \(a_{ij}\) .

  • The cost of obtaining one unit of food \(j \in F\) is \(c_j\) .

Decision Variables

  • \(x_j\) is the amount of food \(j \in F\) that a person consumes daily.

Objective Function

  • Minimize the total procurement cost of food: \(\text{minimize}~ \sum_{j \in F} c_{j} x_j\) .

Constraints

  • The nutritional requirement for each nutrient \(i \in N\) must lie between the minimum value \(n_{\min,i}\) and the maximum value \(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]);
                }

                // Add Constraints
                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]);
                }

                // Add Objective Function
                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");
                // Print result
                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();
            }
        }
    }
}

Facility problem

Currently, there are two shopping malls, and the locations of the malls have already been determined. There are several alternative locations for constructing warehouses, whose coordinates and construction costs are known. We assume that the transportation cost from the warehouses to the shopping malls is independent of the quantity of goods but is related to the distance between them. Please find the minimum cost scheme for warehouse construction and transportation.

Sets

  • Alternative Warehouses \(F\)

  • Shopping Malls \(M\)

Parameters

  • The transportation cost from warehouse \(i \in F\) to shopping mall \(j \in M\) is \(a_{ij}\) .

  • The cost for constructing warehouse \(i \in F\) is \(c_i\) .

  • The demand for goods at shopping mall \(j \in M\) is \(d_j\) .

Decision Variables

  • \(x_i\) indicates whether a warehouse is constructed at alternative location \(i \in F\) . It can take a value of 0 or 1, where 0 means not to build and 1 means to build.

  • \(y_{ij}\) represents the quantity of goods transported from warehouse \(i \in F\) to shopping mall \(j \in M\) .

Objective Function

Minimize the combined cost of warehouse construction and goods transportation:

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

Constraints

\(\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;
        }
    }
}

WorkForce problem

In a week, the number of workers needed by the factory varies each day. It is currently known how much each worker earns per day and the dates on which they can attend work. The goal is to calculate how to schedule the workers in order to meet the factory’s operational requirements while minimizing wage costs.

Sets:

  • Days of the week \(D = 1, 2, 3, \ldots, 7\)

  • Number of workers needed by the factory \(N\)

  • Workers \(S\)

Parameters:

  • The number of workers needed by the factory on day \(i \in D\) is \(n_i\) .

  • The daily wage of worker \(j \in S\) is \(s_j\) .

  • A sequence indicating the available working times for workers, totaling \(T\) pairs of (worker, day) denoted as \((d_i, t_i)\) , where \(d_i \in S\) and \(t_i \in D\) , for \(i = 1, 2, 3, \ldots, T\) .

Objective Function:

Minimize the total wages paid: \(\text{minimize}~ \sum_{i=1}^{T} x_i s_{d_i}\)

Decision Variables:

  • \(x_i (i = 1, 2, 3, \ldots, T)\) indicates whether the worker in the available working time sequence shows up on that day. Its values must be 0 or 1, where 0 indicates the worker does not attend, and 1 indicates the worker does attend.

Constraints:

\(\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)
        {
            // Create MindOpt model
            MDOEnv env = new MDOEnv();
            MDOModel model = new MDOModel(env);

            // Number of required workers for each day
            string[] dayName = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
            int[] workersPerDay = {  3, 1, 4, 2, 1, 3, 3 };

            // Map workers and their pay
            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);

            // Define availability set
            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();
            }
        }
    }
}