Modeling a production planning problem using Solver Foundation

In this post I am going to present two complete C# programs for modeling and solving a simple production problem using Solver Foundation. In this example we have two refineries (located in Saudi Arabia and Venzuela) that produce three products: gasoline, jet fuel, and lubricant. The goal is to minimize production costs, which depend on location. There is demand for each product which must be met. Finally, each production site has a limited production capacity.

The code for the simple case is below.  Creating a model amounts to adding the goals, decisions, and constraints using the appropriate Add method.  The signature for each is similar: the first argument is the name and the second argument is a term.  Terms are created by combining decisions or parameters using the usual operators, or by using static methods on the Model class (as we will see in our second example).  Finally, notice the call to SolverContext.Solve().  I have supplied a Directive that specifies that the Simplex solver should be used.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using Microsoft.SolverFoundation.Services;

namespace Microsoft.SolverFoundation.Samples.Petrochem {
  class Program {
    static void Main(string[] args) {
      PetrochemSimple();
    }

    private static void PetrochemSimple() {
      SolverContext context = SolverContext.GetContext();
      context.ClearModel();
      Model model = context.CreateModel();

      Decision sa = new Decision(Domain.RealRange(0, 9000), "SA");
      Decision vz = new Decision(Domain.RealRange(0, 6000), "VZ");
      model.AddDecisions(sa, vz);

      model.AddGoal("goal", GoalKind.Minimize, 20 * sa + 15 * vz);

      model.AddConstraint("demand1", 0.3 * sa + 0.4 * vz >= 1900);
      model.AddConstraint("demand2", 0.4 * sa + 0.2 * vz >= 1500);
      model.AddConstraint("demand3", 0.2 * sa + 0.3 * vz >= 500);

      Solution solution = context.Solve(new SimplexDirective());
      Report report = solution.GetReport();
      Console.WriteLine(report);
    }
  }

Now let’s reimplement the same example using data binding. Data binding is a powerful mechanism for creating large, maintainable models. Notice that in my first example, the numeric data such as the yields, the demands, and capacities were expressed directly in the terms. The first step in using Solver Foundation data binding is to lift these values into Parameters. It is often useful to create indexed parameters using Sets. In this example, there are two clearly defined Sets: the set of countries, and the set of products. In my example below I create a DataSet which contains the data for each of my parameters. (The GetData() method is just an example, it’s not pretty but it is needed to complete the example.) Then I create a series of indexed parameters. For each of them I call the SetBinding method to associate the data with the parameter. In addition to the data, SetBinding also requires the caller to indicate which property specifies the values for the parameter. If the parameter is indexed, I also need to specify the parameters of the index properties. Since I am working with DataTables, these are simply column names. Notice that I could swap in any other data source that is enumerable – in particular LINQ works really well with Solver Foundation parameters.

After the parameters are created, I define the decisions, goals, and constraints. Notice that there is only one decision – it is indexed. The Model.Sum and Model.Foreach operations allow me to define a series of constraints over one or more indexed sets in one single statement. This means that if I were to add more countries or products, my model definition would not change at all.

    private static void PetrochemDataBinding() {
      SolverContext context = SolverContext.GetContext();
      context.ClearModel(); 
      Model model = context.CreateModel();

      // Retrieve the problem data.
      DataSet data = GetData();

      Set products = new Set(Domain.Any, "products");
      Set countries = new Set(Domain.Any, "countries");
      
      Parameter demand = new Parameter(Domain.Real, "demand", products);
      demand.SetBinding(data.Tables["Demand"].AsEnumerable(), "Demand", "Product");

      Parameter yield = new Parameter(Domain.Real, "yield", products, countries);
      yield.SetBinding(data.Tables["Yield"].AsEnumerable(), "Yield", "Product", "Country");

      Parameter limit = new Parameter(Domain.Real, "limit", countries);
      limit.SetBinding(data.Tables["Limit"].AsEnumerable(), "Limit", "Country");

      Parameter cost = new Parameter(Domain.Real, "cost", countries);
      cost.SetBinding(data.Tables["Cost"].AsEnumerable(), "Cost", "Country");

      model.AddParameters(demand, yield, limit, cost);

      Decision produce = new Decision(Domain.RealNonnegative, "produce", countries);
      model.AddDecision(produce);

      model.AddGoal("Goal", GoalKind.Minimize, Model.Sum(Model.ForEach(countries, c => cost[c] * produce[c])));

      model.AddConstraint("Demand", 
        Model.ForEach(products, p => Model.Sum(Model.ForEach(countries, c => yield[p, c] * produce[c])) >= demand[p])
        );

      model.AddConstraint("ProductionLimit",
        Model.ForEach(countries, c => produce[c] <= limit[c])
        );

      Solution solution = context.Solve(new SimplexDirective());
      Report report = solution.GetReport();
      Console.WriteLine(report);
    }

    private static DataSet GetData() {
      string[] products = new string[] { "Gas", "Jet Fuel", "Lubricant" };
      string[] countries = new string[] { "SA", "VZ" };

      double[][] yield = new double[][] {
        new double[] { 0.3, 0.4 }, 
        new double[] { 0.4, 0.2 }, 
        new double[] { 0.2, 0.3 } 
      };
      double[] demand = new double[] { 1900, 1500, 500 };
      double[] limit = new double[] { 9000, 6000 };
      double[] cost = new double[] { 20, 15 };

      DataSet dataSet = new DataSet();
      #region Fill DataSet
      DataTable table = new DataTable("Yield");
      dataSet.Tables.Add(table);
      table.Columns.Add("Product", typeof(string));
      table.Columns.Add("Country", typeof(string));
      table.Columns.Add("Yield", typeof(double));

      for (int p = 0; p < products.Length; p++) {
        for (int c = 0; c < countries.Length; c++) {
          DataRow row = table.NewRow();
          row[0] = products[p];
          row[1] = countries[c];
          row[2] = yield[p][c];
          table.Rows.Add(row);
        }
      }

      table = new DataTable("Demand");
      dataSet.Tables.Add(table);
      table.Columns.Add("Product", typeof(string));
      table.Columns.Add("Demand", typeof(double));
      for (int p = 0; p < products.Length; p++) {
        DataRow row = table.NewRow();
        row[0] = products[p];
        row[1] = demand[p];
        table.Rows.Add(row);
      }

      table = new DataTable("Limit");
      dataSet.Tables.Add(table);
      table.Columns.Add("Country", typeof(string));
      table.Columns.Add("Limit", typeof(double));
      for (int c = 0; c < countries.Length; c++) {
        DataRow row = table.NewRow();
        row[0] = countries[c];
        row[1] = limit[c];
        table.Rows.Add(row);
      }

      table = new DataTable("Cost");
      dataSet.Tables.Add(table);
      table.Columns.Add("Country", typeof(string));
      table.Columns.Add("Cost", typeof(double));
      for (int c = 0; c < countries.Length; c++) {
        DataRow row = table.NewRow();
        row[0] = countries[c];
        row[1] = cost[c];
        table.Rows.Add(row);
      }
      #endregion

      return dataSet;
    }

Author: natebrix

Follow me on twitter at @natebrix.

3 thoughts on “Modeling a production planning problem using Solver Foundation”

  1. Thank you for this helpful info. I have a question on loading/reading data.
    Do you know how to load or read data from a file then use that data to solve a linear programming model using Microsoft Solver Foundation in C#?

    I have a linear model using MSF in C#, but it is very long and will get longer for my next models, so instead of typing all constraints one by one, I just want to load or read the data from a file and then use it to solve my linear model.

  2. Thank you so much but I am having issues to implement it to my code.
    Here is my code in C#, a linear programming model using Microsoft Solver Foundation

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.SolverFoundation.Common;
    using Microsoft.SolverFoundation.Services;

    namespace PetroChem
    {
    class Program
    {
    private static string ConsoleApplication23;
    static void Main(string[] args)
    {
    SolverContext context = SolverContext.GetContext();
    Model model = context.CreateModel();

    Decision x11 = new Decision(Domain.IntegerNonnegative, “x11”);
    Decision x12 = new Decision(Domain.IntegerNonnegative, “x12”);
    Decision x13 = new Decision(Domain.IntegerNonnegative, “x13”);
    Decision x14 = new Decision(Domain.IntegerNonnegative, “x14”);
    Decision x15 = new Decision(Domain.IntegerNonnegative, “x15”);
    Decision x21 = new Decision(Domain.IntegerNonnegative, “x21”);
    Decision x22 = new Decision(Domain.IntegerNonnegative, “x22”);
    Decision x23 = new Decision(Domain.IntegerNonnegative, “x23”);
    Decision x24 = new Decision(Domain.IntegerNonnegative, “x24”);
    Decision x25 = new Decision(Domain.IntegerNonnegative, “x25”);
    Decision x31 = new Decision(Domain.IntegerNonnegative, “x31”);
    Decision x32 = new Decision(Domain.IntegerNonnegative, “x32”);
    Decision x33 = new Decision(Domain.IntegerNonnegative, “x33”);
    Decision x34 = new Decision(Domain.IntegerNonnegative, “x34”);
    Decision x35 = new Decision(Domain.IntegerNonnegative, “x35”);
    Decision x41 = new Decision(Domain.IntegerNonnegative, “x41”);
    Decision x42 = new Decision(Domain.IntegerNonnegative, “x42”);
    Decision x43 = new Decision(Domain.IntegerNonnegative, “x43”);
    Decision x44 = new Decision(Domain.IntegerNonnegative, “x44”);
    Decision x45 = new Decision(Domain.IntegerNonnegative, “x45”);
    Decision x51 = new Decision(Domain.IntegerNonnegative, “x51”);
    Decision x52 = new Decision(Domain.IntegerNonnegative, “x52”);
    Decision x53 = new Decision(Domain.IntegerNonnegative, “x53”);
    Decision x54 = new Decision(Domain.IntegerNonnegative, “x54”);
    Decision x55 = new Decision(Domain.IntegerNonnegative, “x55”);

    model.AddDecisions(x11, x12, x13, x14, x15,
    x21, x22, x23, x24, x25,
    x31, x32, x33, x34, x35,
    x41, x42, x43, x44, x45,
    x51, x52, x53, x54, x55 );

    model.AddConstraint(“Row1”, x11 + x12 + x13 + x14 + x15 >= 70);
    model.AddConstraint(“Row2”, x21 + x22 + x23 + x24 + x25 >= 80);
    model.AddConstraint(“Row3”, x31 + x32 + x33 + x34 + x35 >= 90);
    model.AddConstraint(“Row4”, x41 + x42 + x43 + x44 + x45 >= 85);
    model.AddConstraint(“Row5”, x51 + x52 + x53 + x54 + x55 >= 90);
    model.AddConstraint(“Row6”, x11 + x21 + x31 + x41 + x51 >= 110);
    model.AddConstraint(“Row7”, x12 + x22 + x32 + x42 + x52 >= 120);
    model.AddConstraint(“Row8”, x13 + x23 + x33 + x43 + x53 >= 130);
    model.AddConstraint(“Row9”, x14 + x24 + x34 + x44 + x54 >= 120);
    model.AddConstraint(“Row10”, x15 + x25 + x35 + x45 + x55 >= 100);

    model.AddGoal(“Goal”, GoalKind.Minimize, x11 + x12 + x13 + x14 + x15 +
    x21 + x22 + x23 + x24 + x25 +
    x31 + x32 + x33 + x34 + x35 +
    x41 + x42 + x43 + x44 + x45 +
    x51 + x52 + x53 + x54 + x55 );

    Solution solution = context.Solve(new SimplexDirective());

    Report report = solution.GetReport();
    Console.WriteLine(“x1: {0}, x2: {1}, x3: {2}”, x11, x12, x13, x14, x15,
    x21, x22, x23, x24, x25,
    x31, x32, x33, x34, x35,
    x41, x42, x43, x44, x45,
    x51, x52, x53, x54, x55);
    Console.Write(“{0}”, report);
    Console.ReadLine();

    }
    }
    }

    Could you help me on how I can add the codes that will load or read data from a file then use that data to solve the model?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s