nlconstr_c++.cpp#

/* Copyright 2026, Gurobi Optimization, LLC */

/* This example formulates and solves the following simple nonlinear model
   using the nonlinear constraint API:

     minimize    x - y
     subject to  sin(x) * x + x^2 * y  <= 1
                 y = cos(x) (<=> cos(x) - y = 0)
                -1 <= x <= 2
                 0 <= y <= 2

   The nonlinear constraint is set via addNLConstr() using an expression tree.
   Then we retrieve the tree with getNLConstrLHS() and verify it, delete it
   with remove(), replace it with a simple linear constraint, and re-optimize.
*/

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

int
main(int   argc,
     char *argv[])
{
  try {

    /* Create environment and model */
    GRBEnv env = GRBEnv("nlconstr.log");
    GRBModel model = GRBModel(env);

    /* Create variables: x in [-1, 2], y in [0, 2] */
    GRBVar x = model.addVar(-1.0, 2.0, 1.0, GRB_CONTINUOUS, "x");
    GRBVar y = model.addVar(0.0, 2.0, -1.0, GRB_CONTINUOUS, "y");

    /* Expression tree for sin(x) * x + x^2 * y (10 nodes)
     *
     *   node 0: PLUS       (root)
     *   node 1: MULTIPLY   (child of 0)  ->  sin(x) * x
     *   node 2: SIN        (child of 1)
     *   node 3: VARIABLE x (child of 2)
     *   node 4: VARIABLE x (child of 1)
     *   node 5: MULTIPLY   (child of 0)  ->  x^2 * y
     *   node 6: POW        (child of 5)
     *   node 7: VARIABLE x (child of 6)
     *   node 8: CONSTANT 2 (child of 6)
     *   node 9: VARIABLE y (child of 5)
     */
    int    nnodes_in1 = 10;
    int    opcode1[10] = {GRB_OPCODE_PLUS,
                         GRB_OPCODE_MULTIPLY,
                         GRB_OPCODE_SIN,
                         GRB_OPCODE_VARIABLE,
                         GRB_OPCODE_VARIABLE,
                         GRB_OPCODE_MULTIPLY,
                         GRB_OPCODE_POW,
                         GRB_OPCODE_VARIABLE,
                         GRB_OPCODE_CONSTANT,
                         GRB_OPCODE_VARIABLE};
    double data1[10]   = {-1.0, -1.0, -1.0,
                         (double) x.index(),
                         (double) x.index(),
                         -1.0, -1.0,
                         (double) x.index(),
                         2.0,
                         (double) y.index()};
    int    parent1[10] = {-1, 0, 1, 2, 1, 0, 5, 6, 6, 5};

    /* Expression tree for cos(x) - y = 0 (4 nodes)
     *
     *   node 0: MINUS      (root)
     *   node 1: COS        (child of 0)
     *   node 2: VARIABLE x (child of 1)
     *   node 3: VARIABLE y (child of 0)
     */
    int    nnodes_in2 = 4;
    int    opcode2[4] = {GRB_OPCODE_MINUS,      /* 0: root         */
                        GRB_OPCODE_COS,        /* 1: cos(...)     */
                        GRB_OPCODE_VARIABLE,   /* 2: x            */
                        GRB_OPCODE_VARIABLE};  /* 3: y            */
    double data2[4]   = {-1.0, -1.0,
                         (double) x.index(),
                         (double) y.index()};
    int    parent2[4] = {-1, 0, 1, 0};

    /* Add nonlinear constraint: sin(x)*x + x^2*y <= 1 */
    GRBNLConstr nlc1 = model.addNLConstr(nnodes_in1, opcode1, data1, parent1,
                                         GRB_LESS_EQUAL, 1.0,
                                         "NonlinearConstraint1");

    /* Add nonlinear constraint: cos(x) - y = 0 */
    GRBNLConstr nlc2 = model.addNLConstr(nnodes_in2, opcode2, data2, parent2,
                                         GRB_EQUAL, 0.0,
                                         "NonlinearConstraint2");

    /* Optimize model */
    model.optimize();

    int status = model.get(GRB_IntAttr_Status);
    cout << "\nFirst solve (nonlinear constraints)" << endl;
    if (status == GRB_OPTIMAL) {
      cout << "Optimal objective: " << model.get(GRB_DoubleAttr_ObjVal) << endl;
      cout << "  x = " << x.get(GRB_DoubleAttr_X)
           << ", y = " << y.get(GRB_DoubleAttr_X) << endl;
    } else if (status == GRB_INF_OR_UNBD) {
      cout << "Model is infeasible or unbounded" << endl;
    } else {
      cout << "Optimization was stopped early" << endl;
    }

    /* Retrieve number of nodes (first pass with NULLs) */
    int nnodes_out = -1;
    model.getNLConstrLHS(nlc1, &nnodes_out, NULL, NULL, NULL);
    cout << "\nRetrieved NL constraint with " << nnodes_out << " nodes" << endl;

    /* Retrieve full tree (second pass) */
    int*    opcode_out = new int[nnodes_out];
    double* data_out   = new double[nnodes_out];
    int*    parent_out = new int[nnodes_out];

    model.getNLConstrLHS(nlc1, &nnodes_out, opcode_out, data_out, parent_out);

    /* Verify tree matches what we added */
    for (int i = 0; i < nnodes_out; i++) {
      if (opcode_out[i] != opcode1[i] || parent_out[i] != parent1[i] || data_out[i] != data1[i]) {
        cout << "ERROR: tree mismatch at node " << i << endl;
      }
    }

    delete[] opcode_out;
    delete[] data_out;
    delete[] parent_out;

    /* Delete nonlinear constraints */
    model.remove(nlc1);
    model.remove(nlc2);
    model.update();
    cout << "Deleted nonlinear constraints" << endl;

    /* Add a simple linear constraint instead: x + y >= 1 */
    model.addConstr(x + y >= 1.0, "LinearConstraint");

    /* Re-optimize */
    model.optimize();

    status = model.get(GRB_IntAttr_Status);
    cout << "\nSecond solve (linear constraint)" << endl;
    if (status == GRB_OPTIMAL) {
      cout << "Optimal objective: " << model.get(GRB_DoubleAttr_ObjVal) << endl;
      cout << "  x = " << x.get(GRB_DoubleAttr_X)
           << ", y = " << y.get(GRB_DoubleAttr_X) << endl;
    } else if (status == GRB_INF_OR_UNBD) {
      cout << "Model is infeasible or unbounded" << endl;
    } else {
      cout << "Optimization was stopped early" << endl;
    }

  } catch(GRBException e) {
    cout << "Error code = " << e.getErrorCode() << endl;
    cout << e.getMessage() << endl;
  } catch(...) {
    cout << "Exception during optimization" << endl;
  }

  return 0;
}