FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browserPrevious exampleNext example

Multi-period, multi-site production planning

Description
Multi-period production planning for multiple production facilities, including opening/closing decisions for sites. Implementation of helper routines for enumeration of arrays with multiple indices.

prodplan_cpp.zip[download all files]

Source Files
By clicking on a file name, a preview is opened at the bottom of this page.
ProductionPlanning_Index.cpp[download]





ProductionPlanning_Index.cpp

// (c) 2024-2024 Fair Isaac Corporation

/** Production planning problem. */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::sum;

int const PMAX = 2; // number of products
int const FMAX = 2; // number of factories
int const RMAX = 2; // number of raw material
int const TMAX = 4; // number of time periods

double const CPSTOCK = 2.0; // unit cost to store a product
double const CRSTOCK = 2.0; // unit cost to store a raw material
double const MAXRSTOCK =
    300; // maximum number of raw material that can be stocked in a factory

std::vector<std::vector<double>> REV{
    // REV[p][t] equals unit price for selling product p in period t
    std::vector<double>{400, 380, 405, 350},
    std::vector<double>{410, 397, 412, 397}};

std::vector<std::vector<double>> CMAK{
    // CMAK[p][f] unit cost for producing product p at factory f
    std::vector<double>{150, 153}, std::vector<double>{75, 68}};

std::vector<std::vector<double>> CBUY{
    // CBUY[r][t] unit cost to buy raw material r in period t
    std::vector<double>{100, 98, 97, 100},
    std::vector<double>{200, 195, 198, 200}};

std::vector<double> COPEN{
    // COPEN[f] fixed cost for factory f being open for one period
    50000, 63000};

std::vector<std::vector<double>> REQ{
    // REQ[p][r] raw material requirement (in units) of r to make one unit of p
    std::vector<double>{1.0, 0.5}, std::vector<double>{1.3, 0.4}};

std::vector<std::vector<double>> MAXSELL{
    // MAXSELL[p][t] maximum number of units that can be sold of product p in
    // period t
    std::vector<double>{650, 600, 500, 400},
    std::vector<double>{600, 500, 300, 250}};

std::vector<double> MAXMAKE{// MAXMAKE[f] maximum number of units (over all
                            // products) a factory can produce per period
                            400, 500};

std::vector<std::vector<double>> PSTOCK0{
    // PSTOCK0[p][f] initial stock of product p at factory f
    std::vector<double>{50, 100}, std::vector<double>{50, 50}};

std::vector<std::vector<double>> RSTOCK0{
    // RSTOCK0[r][f] initial stock of raw material r at factor f
    std::vector<double>{100, 150}, std::vector<double>{50, 100}};

/**
 * Convenience function for printing solution values stored in a 3-dimensional
 * Variable array
 *
 * @param sol      solution object, obtained via prob.getSolution()
 * @param array    3-dimensional array of Xpress Variables
 * @param max1     First index varies between 0 and max1
 * @param max2     Second index varies between 0 and max2
 * @param max3     Third index varies between 0 and max3
 * @param dimNames An array with a name for every dimension
 * @param name     The name of the array
 */
void writeSol3D(std::vector<double> const &sol,
                std::vector<std::vector<std::vector<Variable>>> const &array,
                int max1, int max2, int max3,
                std::vector<std::string> const &dimNames,
                std::string const &name) {
  for (int i1 = 0; i1 < max1; ++i1)
    for (int i2 = 0; i2 < max2; ++i2)
      for (int i3 = 0; i3 < max3; ++i3)
        std::cout << dimNames[0] << " " << i1 << "\t" << dimNames[1] << " "
                  << i2 << "\t" << dimNames[2] << " " << i3 << " : " << name
                  << " = " << array[i1][i2][i3].getValue(sol) << std::endl;
}

int main() {
  std::cout << "Formulating the production planning problem" << std::endl;

  XpressProblem prob;
  // make[p][f][t]: Amount of product p to make at factory f in period t
  auto make = prob.addVariables(PMAX, FMAX, TMAX)
                  .withName("make_p%d_f%d_t%d")
                  .toArray();

  // sell[p][f][t]: Amount of product p to sell from factory f in period t
  auto sell = prob.addVariables(PMAX, FMAX, TMAX)
                  .withName("sell_p%d_f%d_t%d")
                  .toArray();

  // pstock[p][f][t]: Stock level of product p at factor f at start of period t
  auto pstock = prob.addVariables(PMAX, FMAX, TMAX)
                    .withName("pstock_p%d_f%d_t%d")
                    .toArray();

  // buy[r][f][t]: Amount of raw material r bought for factory f in period t
  auto buy =
      prob.addVariables(RMAX, FMAX, TMAX).withName("buy_p%d_f%d_t%d").toArray();
  // rstock[r][f][t]: Stock level of raw material r at factory f at start of
  // period t
  auto rstock = prob.addVariables(RMAX, FMAX, TMAX)
                    .withName("rstock_p%d_f%d_t%d")
                    .toArray();

  // openm[f][t]: If factory f is open in period t
  auto openm = prob.addVariables(FMAX, TMAX)
                   .withType(ColumnType::Binary)
                   .withName("openm_f%d_t%d")
                   .toArray();

  // ## Objective:
  // Maximize total profit
  // revenue from selling products
  // + REV[p][t] * sell[p][f][t]
  Expression revenue = sum(PMAX, FMAX, TMAX, [&](auto p, auto f, auto t) {
    return REV[p][t] * sell[p][f][t];
  });

  // cost for making products (must be subtracted from profit)
  // - CMAK[p, f] * make[p][f][t]
  Expression prodCost = sum(PMAX, FMAX, TMAX, [&](auto p, auto f, auto t) {
                          return -CMAK[p][f] * make[p][f][t];
                        });

  // cost for storing products (must be subtracted from profit)
  // - CPSTOCK * pstock[p][f][t]
  Expression prodStorageCost = sum(PMAX, FMAX, TMAX, [&](auto p, auto f, auto t) {
                                 return -CPSTOCK * pstock[p][f][t];
                               });

  // cost for opening a factory in a time period
  // - openm[f][t] * COPEN[f]
  Expression factoryCost = sum(FMAX, TMAX, [&](auto f, auto t) {
                             return -COPEN[f] * openm[f][t];
                           });

  // cost for buying raw material in time period t
  // - buy[r][f][t] * CBUY[r, t]
  Expression rawMaterialBuyCost = sum(PMAX, FMAX, TMAX, [&](auto r, auto f, auto t) {
                                    return -CBUY[r][t] * buy[r][f][t];
                                  });

  // cost for storing raw material (must be subtracted from profit)
  // - rstock[r][f][t] * CRSTOCK
  // an alternative way of setting an objective Expression is to pass
  // the stream directly to the sum function
  Expression rawMaterialStorageCost = sum(FMAX, RMAX, TMAX, [&](auto f, auto r, auto t) {
                                        return -CRSTOCK * rstock[r][f][t];
                                      });

  // sum up the 6 individual contributions to the overall profit
  Expression profit = revenue + prodCost + prodStorageCost + factoryCost +
                      rawMaterialStorageCost + rawMaterialBuyCost;

  // set maximization of profit as objective function
  prob.setObjective(profit, ObjSense::Maximize);

  // constraints
  // Product stock balance
  prob.addConstraints(PMAX, FMAX, TMAX, [&](auto p, auto f, auto t) {
    // for each time period except the last time period, surplus is available as
    // stock for the next time period
    if (t < TMAX - 1) {
      return (pstock[p][f][t] + make[p][f][t] ==
              sell[p][f][t] + pstock[p][f][t + 1])
          .setName(xpress::format("prod_stock_balance_p%d_f%d_t%d", p, f, t));
    } else {
      return (pstock[p][f][t] + make[p][f][t] >= sell[p][f][t])
          .setName(xpress::format("prod_stock_balance_p%d_f%d_t%d", p, f, t));
    }
  });

  // Raw material stock balance
  prob.addConstraints(PMAX, FMAX, TMAX, [&](auto r, auto f, auto t) {
    if (t < TMAX - 1) {
      return (rstock[r][f][t] + buy[r][f][t] ==
              rstock[r][f][t + 1] +
                  sum(PMAX, [&](auto p) { return REQ[p][r] * make[p][f][t]; }))
          .setName(xpress::format("raw_material_stock_balance_r%d_f%d_t%d", r,
                                  f, t));
    } else {
      return (rstock[r][f][t] + buy[r][f][t] >=
              sum(PMAX, [&](auto p) { return REQ[p][r] * make[p][f][t]; }))
          .setName(xpress::format("raw_material_stock_balance_r%d_f%d_t%d", r,
                                  f, t));
    }
  });

  // Limit on the amount of product p to be sold
  // exemplifies how to loop through multiple ranges
  prob.addConstraints(PMAX, TMAX, [&](auto p, auto t) {
    return (sum(FMAX, [&](auto f) { return sell[p][f][t]; }) <= MAXSELL[p][t])
        .setName(xpress::format("maxsell_p%d_t%d", p, t));
  });

  // Capacity limit at factory f
  // exemplifies how to loop through multiple ranges
  prob.addConstraints(FMAX, TMAX, [&](auto f, auto t) {
    return (sum(PMAX, [&](auto p) { return make[p][f][t]; }) <=
            MAXMAKE[f] * openm[f][t])
        .setName(xpress::format("capacity_f%d_t%d", f, t));
  });

  // Raw material stock limit
  // exemplifies how to loop through multiple ranges
  prob.addConstraints(FMAX, TMAX, [&](auto f, auto t) {
    return (sum(RMAX, [&](auto r) { return rstock[r][f][t]; }) <= MAXRSTOCK)
        .setName(xpress::format("raw_material_stock_limit_f%d_t%d", f, t));
  });

  // Initial product storage
  prob.addConstraints(PMAX, FMAX, [&](auto p, auto f) {
    // pstock is indexed (p, f, t), PSTOCK0 is indexed (p, f)
    return (pstock[p][f][0] == PSTOCK0[p][f])
        .setName(xpress::format("initial_product_stock_p%d_f%d", p, f));
  });

  // Initial raw material storage
  // classic for loop
  prob.addConstraints(PMAX, FMAX, [&](auto r, auto f) {
    return (rstock[r][f][0] == RSTOCK0[r][f])
        .setName(xpress::format("initial_raw_material_stock_r%d_f%d", r, f));
  });

  // write the problem in LP format for manual inspection
  std::cout << "Writing the problem to 'ProductionPlanning.lp'" << std::endl;
  prob.writeProb("ProductionPlanning.lp");

  // Solve the problem
  std::cout << "Solving the problem" << std::endl;
  prob.optimize();

  std::cout << "Problem finished with SolStatus "
            << prob.attributes.getSolStatus() << std::endl;

  if (prob.attributes.getSolStatus() != SolStatus::Optimal) {
    throw std::runtime_error("Problem not solved to optimality");
  }

  std::cout << "Solution has objective value (profit) of "
            << prob.attributes.getObjVal() << std::endl;

  std::cout << std::endl;
  std::cout << "*** Solution ***" << std::endl;
  auto sol = prob.getSolution();

  // Is factory f open at time period t?
  for (int f = 0; f < FMAX; f++) {
    for (int t = 0; t < TMAX; t++) {
      std::cout << "Factory " << f << "\tTime Period " << t
                << " : Open = " << openm[f][t].getValue(sol) << std::endl;
    }
  }
  std::cout << std::endl;

  // Production plan for producing
  writeSol3D(sol, make, PMAX, FMAX, TMAX,
             std::vector<std::string>{"Product", "Factory", "Time Period"},
             "Make");
  std::cout << std::endl;

  // Production plan for selling products
  writeSol3D(sol, sell, PMAX, FMAX, TMAX,
             std::vector<std::string>{"Product", "Factory", "Time Period"},
             "Sell");
  std::cout << std::endl;

  // Production plan for keeping products in stock
  writeSol3D(sol, pstock, PMAX, FMAX, TMAX,
             std::vector<std::string>{"Product", "Factory", "Time Period"},
             "Pstock");
  std::cout << std::endl;

  // Production plan for keeping raw material in stock
  writeSol3D(sol, rstock, RMAX, FMAX, TMAX,
             std::vector<std::string>{"Material", "Factory", "Time Period"},
             "Rstock");
  std::cout << std::endl;

  // Buying plan for raw material
  writeSol3D(sol, buy, RMAX, FMAX, TMAX,
             std::vector<std::string>{"Material", "Factory", "Time Period"},
             "Buy");
  std::cout << std::endl;


  return 0;
}

Back to examples browserPrevious exampleNext example