sensitivity_c++.cpp#

// Copyright 2024, Gurobi Optimization, LLC

// A simple sensitivity analysis example which reads a MIP model from a
// file and solves it. Then uses the scenario feature to analyze the impact
// w.r.t. the objective function of each binary variable if it is set to
// 1-X, where X is its value in the optimal solution.
//
// Usage:
//     sensitivity_c++ <model filename>

#include "gurobi_c++.h"
using namespace std;

// Maximum number of scenarios to be considered
#define MAXSCENARIOS 100

int
main(int   argc,
     char *argv[])
{
  if (argc < 2) {
    cout << "Usage: sensitivity_c++ filename" << endl;
    return 1;
  }

  GRBVar *vars  = NULL;
  double *origX = NULL;

  try {

    // Create environment
    GRBEnv env = GRBEnv();

    // Read model
    GRBModel model = GRBModel(env, argv[1]);

    int scenarios;

    if (model.get(GRB_IntAttr_IsMIP) == 0) {
      cout << "Model is not a MIP" << endl;
      return 1;
    }

    // Solve model
    model.optimize();

    if (model.get(GRB_IntAttr_Status) != GRB_OPTIMAL) {
      cout << "Optimization ended with status "
           << model.get(GRB_IntAttr_Status) << endl;
      return 1;
    }

    // Store the optimal solution
    double origObjVal = model.get(GRB_DoubleAttr_ObjVal);
    vars = model.getVars();
    int numVars = model.get(GRB_IntAttr_NumVars);
    origX = model.get(GRB_DoubleAttr_X, vars, numVars);

    scenarios = 0;

    // Count number of unfixed, binary variables in model. For each we
    // create a scenario.
    for (int i = 0; i < numVars; i++) {
      GRBVar v     = vars[i];
      char   vType = v.get(GRB_CharAttr_VType);

      if (v.get(GRB_DoubleAttr_LB) == 0.0               &&
          v.get(GRB_DoubleAttr_UB) == 1.0               &&
          (vType == GRB_BINARY || vType == GRB_INTEGER)   ) {
        scenarios++;

        if (scenarios >= MAXSCENARIOS)
          break;
      }
    }

    cout << "###  construct multi-scenario model with "
         << scenarios << " scenarios" << endl;

    // Set the number of scenarios in the model */
    model.set(GRB_IntAttr_NumScenarios, scenarios);

    scenarios = 0;

    // Create a (single) scenario model by iterating through unfixed binary
    // variables in the model and create for each of these variables a
    // scenario by fixing the variable to 1-X, where X is its value in the
    // computed optimal solution
    for (int i = 0; i < numVars; i++) {
      GRBVar v     = vars[i];
      char   vType = v.get(GRB_CharAttr_VType);

      if (v.get(GRB_DoubleAttr_LB) == 0.0               &&
          v.get(GRB_DoubleAttr_UB) == 1-0               &&
          (vType == GRB_BINARY || vType == GRB_INTEGER) &&
          scenarios < MAXSCENARIOS                        ) {

        // Set ScenarioNumber parameter to select the corresponding
        // scenario for adjustments
        model.set(GRB_IntParam_ScenarioNumber, scenarios);

        // Set variable to 1-X, where X is its value in the optimal solution */
        if (origX[i] < 0.5)
          v.set(GRB_DoubleAttr_ScenNLB, 1.0);
        else
          v.set(GRB_DoubleAttr_ScenNUB, 0.0);

        scenarios++;
      } else {
        // Add MIP start for all other variables using the optimal solution
        // of the base model
        v.set(GRB_DoubleAttr_Start, origX[i]);
      }
    }

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

    // In case we solved the scenario model to optimality capture the
    // sensitivity information
    if (model.get(GRB_IntAttr_Status) == GRB_OPTIMAL) {

      // get the model sense (minimization or maximization)
      int modelSense = model.get(GRB_IntAttr_ModelSense);

      scenarios = 0;

      for (int i = 0; i < numVars; i++) {
        GRBVar v     = vars[i];
        char   vType = v.get(GRB_CharAttr_VType);

        if (v.get(GRB_DoubleAttr_LB) == 0.0               &&
            v.get(GRB_DoubleAttr_UB) == 1-0               &&
            (vType == GRB_BINARY || vType == GRB_INTEGER)   ) {

          // Set scenario parameter to collect the objective value of the
          // corresponding scenario
          model.set(GRB_IntParam_ScenarioNumber, scenarios);

          // Collect objective value and bound for the scenario
          double scenarioObjVal = model.get(GRB_DoubleAttr_ScenNObjVal);
          double scenarioObjBound = model.get(GRB_DoubleAttr_ScenNObjBound);

          cout << "Objective sensitivity for variable "
               << v.get(GRB_StringAttr_VarName)
               << " is ";

          // Check if we found a feasible solution for this scenario
          if (modelSense * scenarioObjVal >= GRB_INFINITY) {
            // Check if the scenario is infeasible
            if (modelSense * scenarioObjBound >= GRB_INFINITY)
              cout << "infeasible"  << endl;
            else
              cout << "unknown (no solution available)"  << endl;
          } else {
            // Scenario is feasible and a solution is available
            cout << modelSense * (scenarioObjVal - origObjVal) << endl;
          }

          scenarios++;

          if (scenarios >= MAXSCENARIOS)
            break;
        }
      }
    }
  } catch (GRBException e) {
    cout << "Error code = " << e.getErrorCode() << endl;
    cout << e.getMessage() << endl;
  } catch (...) {
    cout << "Error during optimization" << endl;
  }

  delete[] vars;
  delete[] origX;

  return 0;
}