multiscenario_c++.cpp#

// Copyright 2024, Gurobi Optimization, LLC

// Facility location: a company currently ships its product from 5 plants
// to 4 warehouses. It is considering closing some plants to reduce
// costs. What plant(s) should the company close, in order to minimize
// transportation and fixed costs?
//
// Since the plant fixed costs and the warehouse demands are uncertain, a
// scenario approach is chosen.
//
// Note that this example is similar to the facility_c++.cpp example. Here
// we added scenarios in order to illustrate the multi-scenario feature.
//
// Based on an example from Frontline Systems:
// http://www.solver.com/disfacility.htm
// Used with permission.

#include "gurobi_c++.h"
#include <sstream>
#include <iomanip>
using namespace std;

int
main(int   argc,
     char *argv[])
{
  GRBEnv     *env          = 0;
  GRBVar     *open         = 0;
  GRBVar    **transport    = 0;
  GRBConstr  *demandConstr = 0;

  int transportCt = 0;

  try {
    // Number of plants and warehouses
    const int nPlants = 5;
    const int nWarehouses = 4;

    // Warehouse demand in thousands of units
    double Demand[] = { 15, 18, 14, 20 };

    // Plant capacity in thousands of units
    double Capacity[] = { 20, 22, 17, 19, 18 };

    // Fixed costs for each plant
    double FixedCosts[] =
      { 12000, 15000, 17000, 13000, 16000 };

    // Transportation costs per thousand units
    double TransCosts[][nPlants] = {
      { 4000, 2000, 3000, 2500, 4500 },
      { 2500, 2600, 3400, 3000, 4000 },
      { 1200, 1800, 2600, 4100, 3000 },
      { 2200, 2600, 3100, 3700, 3200 }
    };

    double maxFixed = -GRB_INFINITY;
    double minFixed = GRB_INFINITY;

    int p;
    for (p = 0; p < nPlants; p++) {
      if (FixedCosts[p] > maxFixed)
        maxFixed = FixedCosts[p];

      if (FixedCosts[p] < minFixed)
        minFixed = FixedCosts[p];
    }

    // Model
    env = new GRBEnv();
    GRBModel model = GRBModel(*env);
    model.set(GRB_StringAttr_ModelName, "multiscenario");

    // Plant open decision variables: open[p] == 1 if plant p is open.
    open = model.addVars(nPlants, GRB_BINARY);

    for (p = 0; p < nPlants; p++) {
      ostringstream vname;
      vname << "Open" << p;
      open[p].set(GRB_DoubleAttr_Obj, FixedCosts[p]);
      open[p].set(GRB_StringAttr_VarName, vname.str());
    }

    // Transportation decision variables: how much to transport from
    // a plant p to a warehouse w
    transport = new GRBVar* [nWarehouses];
    int w;
    for (w = 0; w < nWarehouses; w++) {
      transport[w] = model.addVars(nPlants);
      transportCt++;

      for (p = 0; p < nPlants; p++) {
        ostringstream vname;
        vname << "Trans" << p << "." << w;
        transport[w][p].set(GRB_DoubleAttr_Obj, TransCosts[w][p]);
        transport[w][p].set(GRB_StringAttr_VarName, vname.str());
      }
    }

    // The objective is to minimize the total fixed and variable costs
    model.set(GRB_IntAttr_ModelSense, GRB_MINIMIZE);

    // Production constraints
    // Note that the right-hand limit sets the production to zero if
    // the plant is closed
    for (p = 0; p < nPlants; p++) {
      GRBLinExpr ptot = 0;
      for (w = 0; w < nWarehouses; w++) {
        ptot += transport[w][p];
      }
      ostringstream cname;
      cname << "Capacity" << p;
      model.addConstr(ptot <= Capacity[p] * open[p], cname.str());
    }

    // Demand constraints
    demandConstr = new GRBConstr[nWarehouses];
    for (w = 0; w < nWarehouses; w++) {
      GRBLinExpr dtot = 0;
      for (p = 0; p < nPlants; p++)
        dtot += transport[w][p];

      ostringstream cname;
      cname << "Demand" << w;
      demandConstr[w] = model.addConstr(dtot == Demand[w], cname.str());
    }

    // We constructed the base model, now we add 7 scenarios
    //
    // Scenario 0: Represents the base model, hence, no manipulations.
    // Scenario 1: Manipulate the warehouses demands slightly (constraint right
    //             hand sides).
    // Scenario 2: Double the warehouses demands (constraint right hand sides).
    // Scenario 3: Manipulate the plant fixed costs (objective coefficients).
    // Scenario 4: Manipulate the warehouses demands and fixed costs.
    // Scenario 5: Force the plant with the largest fixed cost to stay open
    //             (variable bounds).
    // Scenario 6: Force the plant with the smallest fixed cost to be closed
    //             (variable bounds).

    model.set(GRB_IntAttr_NumScenarios, 7);

    // Scenario 0: Base model, hence, nothing to do except giving the
    //             scenario a name
    model.set(GRB_IntParam_ScenarioNumber, 0);
    model.set(GRB_StringAttr_ScenNName, "Base model");

    // Scenario 1: Increase the warehouse demands by 10%
    model.set(GRB_IntParam_ScenarioNumber, 1);
    model.set(GRB_StringAttr_ScenNName, "Increased warehouse demands");

    for (w = 0; w < nWarehouses; w++) {
      demandConstr[w].set(GRB_DoubleAttr_ScenNRHS, Demand[w] * 1.1);
    }

    // Scenario 2: Double the warehouse demands
    model.set(GRB_IntParam_ScenarioNumber, 2);
    model.set(GRB_StringAttr_ScenNName, "Double the warehouse demands");

    for (w = 0; w < nWarehouses; w++) {
      demandConstr[w].set(GRB_DoubleAttr_ScenNRHS, Demand[w] * 2.0);
    }

    // Scenario 3: Decrease the plant fixed costs by 5%
    model.set(GRB_IntParam_ScenarioNumber, 3);
    model.set(GRB_StringAttr_ScenNName, "Decreased plant fixed costs");

    for (p = 0; p < nPlants; p++) {
      open[p].set(GRB_DoubleAttr_ScenNObj, FixedCosts[p] * 0.95);
    }

    // Scenario 4: Combine scenario 1 and scenario 3 */
    model.set(GRB_IntParam_ScenarioNumber, 4);
    model.set(GRB_StringAttr_ScenNName, "Increased warehouse demands and decreased plant fixed costs");

    for (w = 0; w < nWarehouses; w++) {
      demandConstr[w].set(GRB_DoubleAttr_ScenNRHS, Demand[w] * 1.1);
    }
    for (p = 0; p < nPlants; p++) {
      open[p].set(GRB_DoubleAttr_ScenNObj, FixedCosts[p] * 0.95);
    }

    // Scenario 5: Force the plant with the largest fixed cost to stay
    //             open
    model.set(GRB_IntParam_ScenarioNumber, 5);
    model.set(GRB_StringAttr_ScenNName, "Force plant with largest fixed cost to stay open");

    for (p = 0; p < nPlants; p++) {
      if (FixedCosts[p] == maxFixed) {
        open[p].set(GRB_DoubleAttr_ScenNLB, 1.0);
        break;
      }
    }

    // Scenario 6: Force the plant with the smallest fixed cost to be
    //             closed
    model.set(GRB_IntParam_ScenarioNumber, 6);
    model.set(GRB_StringAttr_ScenNName, "Force plant with smallest fixed cost to be closed");

    for (p = 0; p < nPlants; p++) {
      if (FixedCosts[p] == minFixed) {
        open[p].set(GRB_DoubleAttr_ScenNUB, 0.0);
        break;
      }
    }

    // Guess at the starting point: close the plant with the highest
    // fixed costs; open all others

    // First, open all plants
    for (p = 0; p < nPlants; p++)
      open[p].set(GRB_DoubleAttr_Start, 1.0);

    // Now close the plant with the highest fixed cost
    cout << "Initial guess:" << endl;
    for (p = 0; p < nPlants; p++) {
      if (FixedCosts[p] == maxFixed) {
        open[p].set(GRB_DoubleAttr_Start, 0.0);
        cout << "Closing plant " << p << endl << endl;
        break;
      }
    }

    // Use barrier to solve root relaxation
    model.set(GRB_IntParam_Method, GRB_METHOD_BARRIER);

    // Solve multi-scenario model
    model.optimize();

    int nScenarios = model.get(GRB_IntAttr_NumScenarios);

    // Print solution for each */
    for (int s = 0; s < nScenarios; s++) {
      int modelSense = GRB_MINIMIZE;

      // Set the scenario number to query the information for this scenario
      model.set(GRB_IntParam_ScenarioNumber, s);

      // collect result for the scenario
      double scenNObjBound = model.get(GRB_DoubleAttr_ScenNObjBound);
      double scenNObjVal = model.get(GRB_DoubleAttr_ScenNObjVal);

      cout << endl << endl << "------ Scenario " << s
           << " (" << model.get(GRB_StringAttr_ScenNName) << ")" << endl;

      // Check if we found a feasible solution for this scenario
      if (modelSense * scenNObjVal >= GRB_INFINITY)
        if (modelSense * scenNObjBound >= GRB_INFINITY)
          // Scenario was proven to be infeasible
          cout << endl << "INFEASIBLE" << endl;
        else
          // We did not find any feasible solution - should not happen in
          // this case, because we did not set any limit (like a time
          // limit) on the optimization process
          cout << endl << "NO SOLUTION" << endl;
      else {
        cout << endl << "TOTAL COSTS: " << scenNObjVal << endl;
        cout << "SOLUTION:" << endl;
        for (p = 0; p < nPlants; p++) {
          double scenNX = open[p].get(GRB_DoubleAttr_ScenNX);

          if (scenNX > 0.5) {
            cout << "Plant " << p << " open" << endl;
            for (w = 0; w < nWarehouses; w++) {
              scenNX = transport[w][p].get(GRB_DoubleAttr_ScenNX);

              if (scenNX > 0.0001)
                cout << "  Transport " << scenNX
                     << " units to warehouse " << w << endl;
            }
          } else
            cout << "Plant " << p << " closed!" << endl;
        }
      }
    }

    // Print a summary table: for each scenario we add a single summary
    // line
    cout << endl << endl << "Summary: Closed plants depending on scenario" << endl << endl;
    cout << setw(8) << " " << " | " << setw(17) << "Plant" << setw(14) << "|" << endl;

    cout << setw(8) << "Scenario" << " |";
    for (p = 0; p < nPlants; p++)
      cout << " " << setw(5) << p;
    cout << " | " << setw(6) << "Costs" << "  Name" << endl;

    for (int s = 0; s < nScenarios; s++) {
      int modelSense = GRB_MINIMIZE;

      // Set the scenario number to query the information for this scenario
      model.set(GRB_IntParam_ScenarioNumber, s);

      // Collect result for the scenario
      double scenNObjBound = model.get(GRB_DoubleAttr_ScenNObjBound);
      double scenNObjVal = model.get(GRB_DoubleAttr_ScenNObjVal);

      cout << left << setw(8) << s << right << " |";

      // Check if we found a feasible solution for this scenario
      if (modelSense * scenNObjVal >= GRB_INFINITY) {
        if (modelSense * scenNObjBound >= GRB_INFINITY)
          // Scenario was proven to be infeasible
          cout << " " << left << setw(30) << "infeasible" << right;
        else
          // We did not find any feasible solution - should not happen in
          // this case, because we did not set any limit (like a time
          // limit) on the optimization process
          cout << " " << left << setw(30) << "no solution found" << right;

        cout << "| " << setw(6) << "-"
             << "  " << model.get(GRB_StringAttr_ScenNName)
             << endl;
      } else {
        for (p = 0; p < nPlants; p++) {
          double scenNX = open[p].get(GRB_DoubleAttr_ScenNX);
          if (scenNX  > 0.5)
            cout << setw(6) << " ";
          else
            cout << " " << setw(5) << "x";
        }

        cout << " | " << setw(6) << scenNObjVal
             << "  " << model.get(GRB_StringAttr_ScenNName)
             << endl;
      }
    }
  }
  catch (GRBException e) {
    cout << "Error code = " << e.getErrorCode() << endl;
    cout << e.getMessage() << endl;
  }
  catch (...) {
    cout << "Exception during optimization" << endl;
  }

  delete[] open;
  for (int i = 0; i < transportCt; ++i) {
    delete[] transport[i];
  }
  delete[] transport;
  delete[] demandConstr;
  delete env;
  return 0;
}