/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <minizinc/hash.hh>
#include <minizinc/solvers/nl/nl_file.hh>

/**
 *  A NL File reprensentation.
 *  The purpose of this file is mainly to be a writer.
 *  Most of the information come from 'nlwrite.pdf'
 *  Note:
 *      * a character '#' starts a comment until the end of line.
 *      * A new line is '\n'
 */

using namespace std;

namespace MiniZinc {

/** *** *** *** Helpers *** *** *** **/

/** Create a string representing the name (and unique identifier) from an identifier. */
string NLFile::getVarName(const Id* id) {
  stringstream os;
  if (id->idn() != -1) {
    os << "X_INTRODUCED_" << id->idn() << "_";
  } else if (!id->v().empty()) {
    os << id->v();
  }
  string name = os.str();
  return name;
}

/** Create a string representing the name (and unique identifier) of a variable from a variable
 * declaration. */
string NLFile::getVarName(const VarDecl* vd) { return getVarName(vd->id()); }

/** Create a string representing the name (and unique identifier) of a constraint from a specific
 * call expression. */
string NLFile::getConstraintName(const Call* c) {
  stringstream os;
  os << c->id() << "_" << static_cast<const void*>(c);  // use the memory address as unique ID.
  string name = os.str();
  return name;
}

/** Obtain the vector of an array, either from an identifier or an array litteral */
const ArrayLit* NLFile::getArrayLit(const Expression* e) {
  switch (Expression::eid(e)) {
    case Expression::E_ID: {
      return getArrayLit(Expression::cast<Id>(e)
                             ->decl()
                             ->e());  // Follow the pointer to the expression of the declaration
    }

    case Expression::E_ARRAYLIT: {
      return Expression::cast<ArrayLit>(e);
    }

    default:
      should_not_happen("Could not read array from expression.");
  }
}

/** Create a vector of double from a vector containing Expression being IntLit. */
vector<double> NLFile::fromVecInt(const ArrayLit* v_int) {
  vector<double> v = {};
  for (unsigned int i = 0; i < v_int->size(); ++i) {
    double d = static_cast<double>(IntLit::v(Expression::cast<IntLit>((*v_int)[i])).toInt());
    v.push_back(d);
  }
  return v;
}

/** Create a vector of double from a vector containing Expression being FloatLit. */
vector<double> NLFile::fromVecFloat(const ArrayLit* v_fp) {
  vector<double> v = {};
  for (unsigned int i = 0; i < v_fp->size(); ++i) {
    double d = FloatLit::v(Expression::cast<FloatLit>((*v_fp)[i])).toDouble();
    v.push_back(d);
  }
  return v;
}

/** Create a vector of variable names from a vector containing Expression being identifier Id. */
vector<string> NLFile::fromVecId(const ArrayLit* v_id) {
  vector<string> v = {};
  for (unsigned int i = 0; i < v_id->size(); ++i) {
    string s = getVarName(Expression::cast<Id>((*v_id)[i])->decl());
    v.push_back(s);
  }
  return v;
}

/** *** *** *** Phase 1: collecting data from MZN *** *** *** **/

/** Add a variable declaration in the NL File.
 *  This function pre-analyse the declaration VarDecl, then delegate to addVarDeclInteger or
 * addVarDeclFloat. In flatzinc, arrays always have a rhs: the can always be replaced by their
 * definition (following the pointer starting at the ID) Hence, we do not reproduce arrays in the NL
 * file.
 */
void NLFile::addVarDecl(const VarDecl* vd, const TypeInst* ti, const Expression* rhs) {
  // Get the name
  string name = getVarName(vd);
  // Discriminate according to the type:
  if (ti->isEnum()) {
    should_not_happen("Enum type in the flatzinc");
  } else if (ti->isarray()) {
    DEBUG_MSG("     Definition of array " << name << " is not reproduced in nl.");

    // Look for the annotation "output_array"
    for (ExpressionSetIter it = Expression::ann(vd).begin(); it != Expression::ann(vd).end();
         ++it) {
      Call* c = Expression::dynamicCast<Call>(*it);
      if (c != nullptr && c->id() == (Constants::constants().ann.output_array)) {
        NLArray array;
        array.name = name;
        array.isInteger = ti->type().bt() == Type::BT_INT;

        // Search the 'annotation' array
        const ArrayLit* aa = getArrayLit(c->arg(0));
        for (int i = 0; i < aa->size(); ++i) {
          IntSetVal* r = Expression::cast<SetLit>((*aa)[i])->isv();
          stringstream ss;
          if (r->empty()) {
            ss << "1..0";
          } else {
            ss << r->min().toInt() << ".." << r->max().toInt();
          }
          array.dimensions.push_back(ss.str());
        }

        // Search the 'real' array. Items can be an identifier or a litteral.
        const ArrayLit* ra = getArrayLit(rhs);
        for (int i = 0; i < ra->size(); ++i) {
          NLArray::Item item;

          if (Expression::isa<Id>((*ra)[i])) {
            item.variable = getVarName(Expression::cast<Id>((*ra)[i]));
          } else if (Expression::isa<IntLit>((*ra)[i])) {
            assert(array.isInteger);
            item.value = static_cast<double>(IntLit::v(Expression::cast<IntLit>((*ra)[i])).toInt());
          } else {
            assert(!array.isInteger);  // Floating point
            item.value = FloatLit::v(Expression::cast<FloatLit>((*ra)[i])).toDouble();
          }

          array.items.push_back(item);
        }

        outputArrays.push_back(array);

        break;
      }
    }
  } else {
    // Check if the variable needs to be reported
    bool toReport = Expression::ann(vd).contains(Constants::constants().ann.output_var);
    DEBUG_MSG("     '" << name << "' to be reported? " << toReport);

    // variable declaration
    const Type& type = ti->type();
    const Expression* domain = ti->domain();

    // Check the type: integer or floatin point
    assert(type.isvarint() || type.isvarfloat());
    bool isvarint = type.isvarint();

    // Call the declaration function according to the type
    // Check the domain and convert if not null.
    // Note: we directly jump to the specialized Int/Float set, going through the set literal
    if (isvarint) {
      // Integer
      IntSetVal* isv = nullptr;
      if (domain != nullptr) {
        isv = Expression::cast<SetLit>(domain)->isv();
      }
      addVarDeclInteger(name, isv, toReport);
    } else {
      // Floating point
      FloatSetVal* fsv = nullptr;
      if (domain != nullptr) {
        fsv = Expression::cast<SetLit>(domain)->fsv();
      }
      addVarDeclFloat(name, fsv, toReport);
    }
  }
}

/** Add an integer variable declaration to the NL File. */
void NLFile::addVarDeclInteger(const string& name, const IntSetVal* isv, bool toReport) {
  // Check that we do not have naming conflict
  assert(variables.find(name) == variables.end());

  // Check the domain.
  NLBound bound;
  if (isv == nullptr) {
    bound = NLBound::makeNoBound();
  } else if (isv->size() == 1) {
    double lb = static_cast<double>(isv->min(0).toInt());
    double ub = static_cast<double>(isv->max(0).toInt());
    bound = NLBound::makeBounded(lb, ub);
  } else {
    should_not_happen("Range: switch on mzn_opt_only_range_domains" << endl);
  }
  // Create the variable and update the NLFile
  NLVar v = NLVar(name, true, toReport, bound);
  variables[name] = v;
}

/** Add a floating point variable declaration to the NL File. */
void NLFile::addVarDeclFloat(const string& name, const FloatSetVal* fsv, bool toReport) {
  // Check that we do not have naming conflict
  assert(variables.find(name) == variables.end());
  // Check the domain.
  NLBound bound;
  if (fsv == nullptr) {
    bound = NLBound::makeNoBound();
  } else if (fsv->size() == 1) {
    double lb = fsv->min(0).toDouble();
    double ub = fsv->max(0).toDouble();
    bound = NLBound::makeBounded(lb, ub);
  } else {
    should_not_happen("Range: switch on mzn_opt_only_range_domains" << std::endl);
  }
  // Create the variable and update the NLFile
  NLVar v = NLVar(name, false, toReport, bound);
  variables[name] = v;
}

// --- --- --- Constraints analysis

/** Dispatcher for constraint analysis. */
void NLFile::analyseConstraint(const Call* c) {
  // ID of the call
  auto id = c->id();
  // Constants for integer builtins
  auto consint = Constants::constants().ids.int_;
  // Constants for floating point builtins
  auto consfp = Constants::constants().ids.float_;

  // Integer linear predicates
  if (id == consint.lin_eq) {
    consint_lin_eq(c);
  } else if (id == consint.lin_le) {
    consint_lin_le(c);
  } else if (id == consint.lin_ne) {
    consint_lin_ne(c);
  }

  // Integer predicates
  else if (id == consint.le) {
    consint_le(c);
  } else if (id == consint.eq) {
    consint_eq(c);
  } else if (id == consint.ne) {
    consint_ne(c);
  }

  // Integer binary operators
  else if (id == consint.times) {
    consint_times(c);
  } else if (id == consint.div) {
    consint_div(c);
  } else if (id == consint.mod) {
    consint_mod(c);
  } else if (id == consint.plus) {
    consint_plus(c);
  } else if (id == "int_pow") {
    int_pow(c);
  } else if (id == "int_max") {
    int_max(c);
  } else if (id == "int_min") {
    int_min(c);
  }

  // Integer unary operators
  else if (id == "int_abs") {
    int_abs(c);
  }

  // Floating point linear predicates
  else if (id == consfp.lin_eq) {
    consfp_lin_eq(c);
  } else if (id == consfp.lin_le) {
    consfp_lin_le(c);
  } else if (id == consfp.lin_lt) {
    consfp_lin_lt(c);
  } else if (id == consfp.lin_ne) {
    consfp_lin_ne(c);
  }

  // Floating point predicates
  else if (id == consfp.lt) {
    consfp_lt(c);
  } else if (id == consfp.le) {
    consfp_le(c);
  } else if (id == consfp.eq) {
    consfp_eq(c);
  } else if (id == consfp.ne) {
    consfp_ne(c);
  }

  // Floating point binary operators
  else if (id == consfp.plus) {
    consfp_plus(c);
  } else if (id == consfp.minus) {
    consfp_minus(c);
  } else if (id == consfp.div) {
    consfp_div(c);
  } else if (id == consfp.times) {
    consfp_times(c);
  } else if (id == "float_pow") {
    float_pow(c);
  } else if (id == "float_max") {
    float_max(c);
  } else if (id == "float_min") {
    float_min(c);
  }

  // Floating point unary operators
  else if (id == "float_abs") {
    float_abs(c);
  } else if (id == "float_acos") {
    float_acos(c);
  } else if (id == "float_acosh") {
    float_acosh(c);
  } else if (id == "float_asin") {
    float_asin(c);
  } else if (id == "float_asinh") {
    float_asinh(c);
  } else if (id == "float_atan") {
    float_atan(c);
  } else if (id == "float_atanh") {
    float_atanh(c);
  } else if (id == "float_cos") {
    float_cos(c);
  } else if (id == "float_cosh") {
    float_cosh(c);
  } else if (id == "float_exp") {
    float_exp(c);
  } else if (id == "float_ln") {
    float_ln(c);
  } else if (id == "float_log10") {
    float_log10(c);
  } else if (id == "float_log2") {
    float_log2(c);
  } else if (id == "float_sqrt") {
    float_sqrt(c);
  } else if (id == "float_sin") {
    float_sin(c);
  } else if (id == "float_sinh") {
    float_sinh(c);
  } else if (id == "float_tan") {
    float_tan(c);
  } else if (id == "float_tanh") {
    float_tanh(c);
  }

  // Other
  else if (id == "int2float") {
    int2float(c);
  }

  // Domain
  else if (id == consfp.in) {
    should_not_happen("Ignore for now: constraint 'float in    ' not implemented");
  } else if (id == consfp.dom) {
    should_not_happen("Ignore for now: constraint 'float dom   ' not implemented");
  }

  // Grey area
  else if (id == consint.lt) {
    should_not_happen("'int lt'");
  } else if (id == consint.gt) {
    should_not_happen("'int gt'");
  } else if (id == consint.ge) {
    should_not_happen("'int ge'");
  } else if (id == consint.minus) {
    should_not_happen("'int minus'");
  } else if (id == consfp.gt) {
    should_not_happen("float gt'");
  } else if (id == consfp.ge) {
    should_not_happen("float ge'");
  }

  // Not implemented
  else {
    should_not_happen("Builtins " << c->id() << " not implemented or not recognized.");
  }
}

// --- --- --- Helpers

/** Create a token from an expression representing a variable */
NLToken NLFile::getTokenFromVarOrInt(const Expression* e) {
  if (Expression::type(e).isPar()) {
    // Constant
    double value = static_cast<double>(IntLit::v(Expression::cast<IntLit>(e)).toInt());
    return NLToken::n(value);
  }  // Variable
  VarDecl* vd = Expression::cast<Id>(e)->decl();
  string n = getVarName(vd);
  return NLToken::v(n);
}

/** Create a token from an expression representing either a variable or a floating point numeric
 * value. */
NLToken NLFile::getTokenFromVarOrFloat(const Expression* e) {
  if (Expression::type(e).isPar()) {
    // Constant
    double value = FloatLit::v(Expression::cast<FloatLit>(e)).toDouble();
    return NLToken::n(value);
  }  // Variable
  VarDecl* vd = Expression::cast<Id>(e)->decl();
  string n = getVarName(vd);
  return NLToken::v(n);
}

/** Create a token from an expression representing either a variable. */
NLToken NLFile::getTokenFromVar(const Expression* e) {
  assert(!Expression::type(e).isPar());
  // Variable
  VarDecl* vd = Expression::cast<Id>(e)->decl();
  string n = getVarName(vd);
  return NLToken::v(n);
}

/** Update an expression graph (only by appending token) with a linear combination
 *  of coefficients and variables. Count as "non linear" for the variables occuring here.
 */
void NLFile::makeSigmaMult(vector<NLToken>& expressionGraph, const vector<double>& coeffs,
                           const vector<string>& vars) {
  assert(coeffs.size() == vars.size());
  assert(coeffs.size() >= 2);

  // Create a sum of products.
  // WARNING: OPSUMLIST needs at least 3 operands! We need a special case for the sum of 2.
  if (coeffs.size() == 2) {
    expressionGraph.push_back(NLToken::o(NLToken::OpCode::OPPLUS));
  } else {
    expressionGraph.push_back(
        NLToken::mo(NLToken::MOpCode::OPSUMLIST, static_cast<int>(coeffs.size())));
  }

  // Component of the sum. Simplify multiplication by one.
  for (unsigned int i = 0; i < coeffs.size(); ++i) {
    // Product if coeff !=1
    if (coeffs[i] != 1) {
      expressionGraph.push_back(NLToken::o(NLToken::OpCode::OPMULT));
      expressionGraph.push_back(NLToken::n(coeffs[i]));
    }
    // Set the variable as "non linear"
    expressionGraph.push_back(NLToken::v(vars[i]));
  }
}

// --- --- --- Linear Builders

/** Create a linear constraint [coeffs] *+ [vars] = value. */
void NLFile::linconsEq(const Call* c, const vector<double>& coeffs, const vector<string>& vars,
                       const NLToken& value) {
  // Create the Algebraic Constraint and set the data
  NLAlgCons cons;

  // Get the name of the constraint
  string cname = getConstraintName(c);
  cons.name = cname;

  if (value.isConstant()) {
    // Create the bound of the constraint
    NLBound bound = NLBound::makeEqual(value.numericValue);
    cons.range = bound;
    // No non linear part: leave the expression graph empty.
    // Linear part: set the jacobian
    cons.setJacobian(vars, coeffs, this);
  } else {
    // Create the bound of the constraint = 0 and change the Jacobian to incorporate a -1 on the
    // variable in 'value'
    NLBound bound = NLBound::makeEqual(0);
    cons.range = bound;
    // No non linear part: leave the expression graph empty.
    // Linear part: set the jacobian
    vector<double> coeffs_(coeffs);
    coeffs_.push_back(-1);
    vector<string> vars_(vars);
    vars_.push_back(value.str);
    cons.setJacobian(vars_, coeffs_, this);
  }

  // Add the constraint in our mapping
  constraints[cname] = cons;
}

/** Create a linear constraint [coeffs] *+ [vars] <= value. */
void NLFile::linconsLe(const Call* c, const vector<double>& coeffs, const vector<string>& vars,
                       const NLToken& value) {
  // Create the Algebraic Constraint and set the data
  NLAlgCons cons;

  // Get the name of the constraint
  string cname = getConstraintName(c);
  cons.name = cname;

  if (value.isConstant()) {
    // Create the bound of the constraint
    NLBound bound = NLBound::makeUBBounded(value.numericValue);
    cons.range = bound;
    // No non linear part: leave the expression graph empty.
    // Linear part: set the jacobian
    cons.setJacobian(vars, coeffs, this);
  } else {
    // Create the bound of the constraint = 0 and change the Jacobian to incorporate a -1 on the
    // variable in 'value'
    NLBound bound = NLBound::makeUBBounded(0);
    cons.range = bound;
    // No non linear part: leave the expression graph empty.
    // Linear part: set the jacobian
    vector<double> coeffs_(coeffs);
    coeffs_.push_back(-1);
    vector<string> vars_(vars);
    vars_.push_back(value.str);
    cons.setJacobian(vars_, coeffs_, this);
  }

  // Add the constraint in our mapping
  constraints[cname] = cons;
}

/** Create a linear logical constraint [coeffs] *+ [vars] PREDICATE value.
 *  Use a generic comparison operator.
 *  Warnings:   - Creates a logical constraint
 *              - Only use for conmparisons that cannot be expressed with '=' xor '<='.
 */
void NLFile::linconsPredicate(const Call* c, NLToken::OpCode oc, const vector<double>& coeffs,
                              const vector<string>& vars, const NLToken& value) {
  // Create the Logical Constraint and set the data
  NLLogicalCons cons(static_cast<int>(logicalConstraints.size()));

  // Get the name of the constraint
  string cname = getConstraintName(c);
  cons.name = cname;

  // Create the expression graph (Note: no Jacobian associated with a logical constraint).
  // 1) Push the comparison operator, e.g. "!= operand1 operand2"
  cons.expressionGraph.push_back(NLToken::o(oc));

  // 2) Operand1 := sum of product
  makeSigmaMult(cons.expressionGraph, coeffs, vars);

  // 3) Operand 2 := value.
  cons.expressionGraph.push_back(value);

  // Store the constraint
  logicalConstraints.push_back(cons);
}

// --- --- --- Non Linear Builders
// For predicates, uses 2 variables or literals: x := c->arg(0), y := c->arg(1)
// x PREDICATE y

// For operations, uses 3 variables or literals: x := c->arg(0), y := c->arg(1), and z := c->arg(2).
// x OPERATOR y = z

/** Create a non linear constraint x = y
 *  Use the jacobian and the bound on constraint to translate into x - y = 0
 *  Simply update the bound if one is a constant.
 */
void NLFile::nlconsEq(const Call* c, const NLToken& x, const NLToken& y) {
  if (x.kind != y.kind) {
    if (x.isConstant()) {
      // Update bound on y
      double value = x.numericValue;
      NLVar& v = variables.at(y.str);
      v.bound.updateEq(value);
    } else {
      // Update bound on x
      double value = y.numericValue;
      NLVar& v = variables.at(x.str);
      v.bound.updateEq(value);
    }
  } else if (x.str != y.str) {  // both must be variables anyway.
    assert(x.isVariable() && y.isVariable());
    // Create the Algebraic Constraint and set the data
    NLAlgCons cons;

    // Get the name of the constraint
    string cname = getConstraintName(c);
    cons.name = cname;

    // Create the bound of the constraint: equal 0
    NLBound bound = NLBound::makeEqual(0);
    cons.range = bound;

    // Create the jacobian
    vector<double> coeffs = {1, -1};
    vector<string> vars = {x.str, y.str};
    cons.setJacobian(vars, coeffs, this);

    // Store the constraint
    constraints[cname] = cons;
  }
}

/** Create a non linear constraint x <= y
 *  Use the jacobian and the bound on constraint to translate into x - y <= 0
 *  Simply update the bound if one is a constant.
 */
void NLFile::nlconsLe(const Call* c, const NLToken& x, const NLToken& y) {
  if (x.kind != y.kind) {
    if (x.isConstant()) {
      // Update lower bound on y
      double value = x.numericValue;
      NLVar& v = variables.at(y.str);
      v.bound.updateLB(value);
    } else {
      // Update upper bound on x
      double value = y.numericValue;
      NLVar& v = variables.at(x.str);
      v.bound.updateUB(value);
    }
  } else if (x.str != y.str) {  // both must be variables anyway.
    assert(x.isVariable() && y.isVariable());

    // Create the Algebraic Constraint and set the data
    NLAlgCons cons;

    // Get the name of the constraint
    string cname = getConstraintName(c);
    cons.name = cname;

    // Create the bound of the constraint: <= 0
    NLBound bound = NLBound::makeUBBounded(0);
    cons.range = bound;

    // Create the jacobian
    vector<double> coeffs = {1, -1};
    vector<string> vars = {x.str, y.str};
    cons.setJacobian(vars, coeffs, this);

    // Store the constraint
    constraints[cname] = cons;
  }
}

/** Create a non linear constraint with a predicate: x PREDICATE y
 *  Use a generic comparison operator.
 *  Warnings:   - Creates a logical constraint
 *              - Only use for conmparisons that cannot be expressed with '=' xor '<='.
 */
void NLFile::nlconsPredicate(const Call* c, NLToken::OpCode oc, const NLToken& x,
                             const NLToken& y) {
  // Create the Logical Constraint and set the data
  NLLogicalCons cons(static_cast<int>(logicalConstraints.size()));

  // Get the name of the constraint
  string cname = getConstraintName(c);
  cons.name = cname;

  // Create the expression graph
  cons.expressionGraph.push_back(NLToken::o(oc));
  cons.expressionGraph.push_back(x);
  cons.expressionGraph.push_back(y);

  // Store the constraint
  logicalConstraints.push_back(cons);
}

/** Create a non linear constraint with a binary operator: x OPERATOR y = z */
void NLFile::nlconsOperatorBinary(const Call* c, NLToken::OpCode oc, const NLToken& x,
                                  const NLToken& y, const NLToken& z) {
  // Create the Algebraic Constraint and set the data
  NLAlgCons cons;

  // Get the name of the constraint
  string cname = getConstraintName(c);
  cons.name = cname;

  // If z is a constant, use the bound, else use the jacobian
  if (z.isConstant()) {
    // Create the bound of the constraint with the numeric value of z
    NLBound bound = NLBound::makeEqual(z.numericValue);
    cons.range = bound;
  } else {
    // Else, use a constraint bound = 0 and use the jacobian to substract z from the result.
    // Create the bound of the constraint to 0
    NLBound bound = NLBound::makeEqual(0);
    cons.range = bound;

    vector<double> coeffs = {};
    vector<string> vars = {};

    // If x is a variable different from y (and must be different from z), give it 0 for the linear
    // part
    if (x.isVariable() && x.str != y.str) {
      assert(x.str != z.str);
      coeffs.push_back(0);
      vars.push_back(x.str);
    }
    // Same as above for y.
    if (y.isVariable()) {
      assert(y.str != z.str);
      coeffs.push_back(0);
      vars.push_back(y.str);
    }
    // z is a variable whose value is substracted from the result
    coeffs.push_back(-1);
    vars.push_back(z.str);

    // Finish jacobian
    cons.setJacobian(vars, coeffs, this);
  }

  // Create the expression graph using the operand code 'oc'
  cons.expressionGraph.push_back(NLToken::o(oc));
  cons.expressionGraph.push_back(x);
  cons.expressionGraph.push_back(y);

  // Store the constraint
  constraints[cname] = cons;
}

/** Create a non linear constraint with a binary operator: x OPERATOR y = z.
 *  OPERATOR is now a Multiop, with a count of 2 (so the choice of the method to use depends on the
 * LN implementation) */
void NLFile::nlconsOperatorBinary(const Call* c, NLToken::MOpCode moc, const NLToken& x,
                                  const NLToken& y, const NLToken& z) {
  // Create the Algebraic Constraint and set the data
  NLAlgCons cons;

  // Get the name of the constraint
  string cname = getConstraintName(c);
  cons.name = cname;

  // If z is a constant, use the bound, else use the jacobian
  if (z.isConstant()) {
    // Create the bound of the constraint with the numeric value of z
    NLBound bound = NLBound::makeEqual(z.numericValue);
    cons.range = bound;
  } else {
    // Else, use a constraint bound = 0 and use the jacobian to substract vres from the result.
    // Create the bound of the constraint to 0
    NLBound bound = NLBound::makeEqual(0);
    cons.range = bound;

    vector<double> coeffs = {};
    vector<string> vars = {};

    // If x is a variable different from y (and must be different from z), give it 0 for the linear
    // part
    if (x.isVariable() && x.str != y.str) {
      assert(x.str != z.str);
      coeffs.push_back(0);
      vars.push_back(x.str);
    }
    // Same as above for y.
    if (y.isVariable()) {
      assert(y.str != z.str);
      coeffs.push_back(0);
      vars.push_back(y.str);
    }
    // z is a variable whose value is substracted from the result
    coeffs.push_back(-1);
    vars.push_back(z.str);

    // Finish jacobian
    cons.setJacobian(vars, coeffs, this);
  }

  // Create the expression graph using the operand code 'oc'
  cons.expressionGraph.push_back(NLToken::mo(moc, 2));
  cons.expressionGraph.push_back(x);
  cons.expressionGraph.push_back(y);

  // Store the constraint
  constraints[cname] = cons;
}

/** Create a non linear constraint with an unary operator: OPERATOR x = y */
void NLFile::nlconsOperatorUnary(const Call* c, NLToken::OpCode oc, const NLToken& x,
                                 const NLToken& y) {
  // Create the Algebraic Constraint and set the data
  NLAlgCons cons;

  // Get the name of the constraint
  string cname = getConstraintName(c);
  cons.name = cname;

  // If y is a constant, use the bound, else use the jacobian
  if (y.isConstant()) {
    // Create the bound of the constraint with the numeric value of y
    NLBound bound = NLBound::makeEqual(y.numericValue);
    cons.range = bound;
  } else {
    // Else, use a constraint bound = 0 and use the jacobian to substract y from the result.
    // Create the bound of the constraint to 0
    NLBound bound = NLBound::makeEqual(0);
    cons.range = bound;

    vector<double> coeffs = {};
    vector<string> vars = {};

    // If x is a variable (must be different from y), give it '0' for the linear part
    if (x.isVariable()) {
      assert(x.str != y.str);
      coeffs.push_back(0);
      vars.push_back(x.str);
    }

    // z is a variable whose value is substracted from the result
    coeffs.push_back(-1);
    vars.push_back(y.str);

    // Finish jacobian
    cons.setJacobian(vars, coeffs, this);
  }

  // Create the expression graph using the operand code 'oc'
  cons.expressionGraph.push_back(NLToken::o(oc));
  cons.expressionGraph.push_back(x);

  // Store the constraint
  constraints[cname] = cons;
}

/** Create a non linear constraint, specialized for log2 unary operator: Log2(x) = y */
void NLFile::nlconsOperatorUnaryLog2(const Call* c, const NLToken& x, const NLToken& y) {
  // Create the Algebraic Constraint and set the data
  NLAlgCons cons;

  // Get the name of the constraint
  string cname = getConstraintName(c);
  cons.name = cname;

  // If y is a constant, use the bound, else use the jacobian
  if (y.isConstant()) {
    // Create the bound of the constraint with the numeric value of y
    NLBound bound = NLBound::makeEqual(y.numericValue);
    cons.range = bound;
  } else {
    // Else, use a constraint bound = 0 and use the jacobian to substract vres from the result.
    // Create the bound of the constraint to 0
    NLBound bound = NLBound::makeEqual(0);
    cons.range = bound;

    vector<double> coeffs = {};
    vector<string> vars = {};

    // If x is a variable (must be different from y), give it '0' for the linear part
    if (x.isVariable()) {
      assert(x.str != y.str);
      coeffs.push_back(0);
      vars.push_back(x.str);
    }
    // z is a variable whose value is substracted from the result
    coeffs.push_back(-1);
    vars.push_back(y.str);

    // Finish jacobian
    cons.setJacobian(vars, coeffs, this);
  }

  // Create the expression graph with log2(x) = ln(x)/ln(2)
  cons.expressionGraph.push_back(NLToken::o(NLToken::OpCode::OPDIV));
  cons.expressionGraph.push_back(NLToken::o(NLToken::OpCode::OP_log));
  cons.expressionGraph.push_back(x);
  cons.expressionGraph.push_back(NLToken::o(NLToken::OpCode::OP_log));
  cons.expressionGraph.push_back(NLToken::n(2));

  // Store the constraint
  constraints[cname] = cons;
}

// --- --- --- Integer Linear Constraints

/** Linar constraint: [coeffs] *+ [vars] = value */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_lin_eq(const Call* c) {
  // Get the arguments arg0 (array0 = coeffs), arg1 (array = variables) and arg2 (value)
  vector<double> coeffs = fromVecInt(getArrayLit(c->arg(0)));
  vector<string> vars = fromVecId(getArrayLit(c->arg(1)));
  NLToken value = getTokenFromVarOrInt(c->arg(2));
  // Create the constraint
  linconsEq(c, coeffs, vars, value);
}

/** Linar constraint: [coeffs] *+ [vars] =< value */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_lin_le(const Call* c) {
  // Get the arguments arg0 (array0 = coeffs), arg1 (array = variables) and arg2 (value)
  vector<double> coeffs = fromVecInt(getArrayLit(c->arg(0)));
  vector<string> vars = fromVecId(getArrayLit(c->arg(1)));
  NLToken value = getTokenFromVarOrInt(c->arg(2));
  // Create the constraint
  linconsLe(c, coeffs, vars, value);
}

/** Linar constraint: [coeffs] *+ [vars] != value */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_lin_ne(const Call* c) {
  // Get the arguments arg0 (array0 = coeffs), arg1 (array = variables) and arg2 (value)
  vector<double> coeffs = fromVecInt(getArrayLit(c->arg(0)));
  vector<string> vars = fromVecId(getArrayLit(c->arg(1)));
  NLToken value = getTokenFromVarOrInt(c->arg(2));
  // Create the constraint
  linconsPredicate(c, NLToken::OpCode::NE, coeffs, vars, value);
}

// --- --- --- Integer Non Linear Predicate Constraints

/** Non linear constraint x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_eq(const Call* c) {
  nlconsEq(c, getTokenFromVarOrInt(c->arg(0)), getTokenFromVarOrInt(c->arg(1)));
}

/** Non linear constraint x <= y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_le(const Call* c) {
  nlconsLe(c, getTokenFromVarOrInt(c->arg(0)), getTokenFromVarOrInt(c->arg(1)));
}

/** Non linear constraint x != y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_ne(const Call* c) {
  nlconsPredicate(c, NLToken::OpCode::NE, getTokenFromVarOrInt(c->arg(0)),
                  getTokenFromVarOrInt(c->arg(1)));
}

// --- --- --- Integer Non Linear Binary Operator Constraints

/** Non linear constraint x + y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_plus(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPPLUS, getTokenFromVarOrInt(c->arg(0)),
                       getTokenFromVarOrInt(c->arg(1)), getTokenFromVarOrInt(c->arg(2)));
}

/** Non linear constraint x * y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_times(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPMULT, getTokenFromVarOrInt(c->arg(0)),
                       getTokenFromVarOrInt(c->arg(1)), getTokenFromVarOrInt(c->arg(2)));
}

/** Non linear constraint x / y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_div(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPDIV, getTokenFromVarOrInt(c->arg(0)),
                       getTokenFromVarOrInt(c->arg(1)), getTokenFromVarOrInt(c->arg(2)));
}

/** Non linear constraint x mod y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consint_mod(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPREM, getTokenFromVarOrInt(c->arg(0)),
                       getTokenFromVarOrInt(c->arg(1)), getTokenFromVarOrInt(c->arg(2)));
}

/** Non linear constraint x pow y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::int_pow(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPPOW, getTokenFromVarOrInt(c->arg(0)),
                       getTokenFromVarOrInt(c->arg(1)), getTokenFromVarOrInt(c->arg(2)));
}

/** Non linear constraint max(x, y) = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::int_max(const Call* c) {
  nlconsOperatorBinary(c, NLToken::MOpCode::MAXLIST, getTokenFromVarOrInt(c->arg(0)),
                       getTokenFromVarOrInt(c->arg(1)), getTokenFromVarOrInt(c->arg(2)));
}

/** Non linear constraint min(x, y) = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::int_min(const Call* c) {
  nlconsOperatorBinary(c, NLToken::MOpCode::MINLIST, getTokenFromVarOrInt(c->arg(0)),
                       getTokenFromVarOrInt(c->arg(1)), getTokenFromVarOrInt(c->arg(2)));
}

// --- --- --- Integer Non Linear Unary Operator Constraints

/** Non linear constraint abs x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::int_abs(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::ABS, getTokenFromVarOrInt(c->arg(0)),
                      getTokenFromVarOrInt(c->arg(1)));
}

// --- --- --- Floating Point Linear Constraints

/** Linar constraint: [coeffs] *+ [vars] = value */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_lin_eq(const Call* c) {
  // Get the arguments arg0 (array0 = coeffs), arg1 (array = variables) and arg2 (value)
  vector<double> coeffs = fromVecFloat(getArrayLit(c->arg(0)));
  vector<string> vars = fromVecId(getArrayLit(c->arg(1)));
  NLToken value = getTokenFromVarOrFloat(c->arg(2));
  // Create the constraint
  linconsEq(c, coeffs, vars, value);
}

/** Linar constraint: [coeffs] *+ [vars] = value */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_lin_le(const Call* c) {
  // Get the arguments arg0 (array0 = coeffs), arg1 (array = variables) and arg2 (value)
  vector<double> coeffs = fromVecFloat(getArrayLit(c->arg(0)));
  vector<string> vars = fromVecId(getArrayLit(c->arg(1)));
  NLToken value = getTokenFromVarOrFloat(c->arg(2));
  // Create the constraint
  linconsLe(c, coeffs, vars, value);
}

/** Linar constraint: [coeffs] *+ [vars] != value */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_lin_ne(const Call* c) {
  // Get the arguments arg0 (array0 = coeffs), arg1 (array = variables) and arg2 (value)
  vector<double> coeffs = fromVecFloat(getArrayLit(c->arg(0)));
  vector<string> vars = fromVecId(getArrayLit(c->arg(1)));
  NLToken value = getTokenFromVarOrFloat(c->arg(2));
  // Create the constraint
  linconsPredicate(c, NLToken::OpCode::NE, coeffs, vars, value);
}

/** Linar constraint: [coeffs] *+ [vars] < value */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_lin_lt(const Call* c) {
  // Get the arguments arg0 (array0 = coeffs), arg1 (array = variables) and arg2 (value)
  vector<double> coeffs = fromVecFloat(getArrayLit(c->arg(0)));
  vector<string> vars = fromVecId(getArrayLit(c->arg(1)));
  NLToken value = getTokenFromVarOrFloat(c->arg(2));
  // Create the constraint
  linconsPredicate(c, NLToken::OpCode::LT, coeffs, vars, value);
}

// --- --- --- Floating Point Non Linear Predicate Constraints

/** Non linear constraint x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_eq(const Call* c) {
  nlconsEq(c, getTokenFromVarOrFloat(c->arg(0)), getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint x <= y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_le(const Call* c) {
  nlconsLe(c, getTokenFromVarOrFloat(c->arg(0)), getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint x != y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_ne(const Call* c) {
  nlconsPredicate(c, NLToken::OpCode::NE, getTokenFromVarOrFloat(c->arg(0)),
                  getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint x < y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_lt(const Call* c) {
  nlconsPredicate(c, NLToken::OpCode::LT, getTokenFromVarOrFloat(c->arg(0)),
                  getTokenFromVarOrFloat(c->arg(1)));
}

// --- --- --- Floating Point Non Linear Binary Operator Constraints

/** Non linear constraint x + y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_plus(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPPLUS, getTokenFromVarOrFloat(c->arg(0)),
                       getTokenFromVarOrFloat(c->arg(1)), getTokenFromVarOrFloat(c->arg(2)));
}

/** Non linear constraint x - y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_minus(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPMINUS, getTokenFromVarOrFloat(c->arg(0)),
                       getTokenFromVarOrFloat(c->arg(1)), getTokenFromVarOrFloat(c->arg(2)));
}

/** Non linear constraint x * y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_times(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPMULT, getTokenFromVarOrFloat(c->arg(0)),
                       getTokenFromVarOrFloat(c->arg(1)), getTokenFromVarOrFloat(c->arg(2)));
}

/** Non linear constraint x / y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_div(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPDIV, getTokenFromVarOrFloat(c->arg(0)),
                       getTokenFromVarOrFloat(c->arg(1)), getTokenFromVarOrFloat(c->arg(2)));
}

/** Non linear constraint x mod y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::consfp_mod(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPREM, getTokenFromVarOrFloat(c->arg(0)),
                       getTokenFromVarOrFloat(c->arg(1)), getTokenFromVarOrFloat(c->arg(2)));
}

/** Non linear constraint x pow y = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_pow(const Call* c) {
  nlconsOperatorBinary(c, NLToken::OpCode::OPPOW, getTokenFromVarOrFloat(c->arg(0)),
                       getTokenFromVarOrFloat(c->arg(1)), getTokenFromVarOrFloat(c->arg(2)));
}

/** Non linear constraint max(x, y) = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_max(const Call* c) {
  nlconsOperatorBinary(c, NLToken::MOpCode::MAXLIST, getTokenFromVarOrFloat(c->arg(0)),
                       getTokenFromVarOrFloat(c->arg(1)), getTokenFromVarOrFloat(c->arg(2)));
}

/** Non linear constraint min(x, y) = z */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_min(const Call* c) {
  nlconsOperatorBinary(c, NLToken::MOpCode::MINLIST, getTokenFromVarOrFloat(c->arg(0)),
                       getTokenFromVarOrFloat(c->arg(1)), getTokenFromVarOrFloat(c->arg(2)));
}

// --- --- --- Floating Point Non Linear Unary operator Constraints

/** Non linear constraint abs x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_abs(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::ABS, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint acos x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_acos(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_acos, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint acosh x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_acosh(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_acosh, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint asin x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_asin(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_asin, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint asinh x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_asinh(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_asinh, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint atan x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_atan(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_atan, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint atanh x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_atanh(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_atanh, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint cos x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_cos(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_cos, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint cosh x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_cosh(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_cosh, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint exp x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_exp(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_exp, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint ln x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_ln(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_log, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint log10 x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_log10(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_log10, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint log2 x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_log2(const Call* c) {
  nlconsOperatorUnaryLog2(c, getTokenFromVarOrFloat(c->arg(0)), getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint sqrt x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_sqrt(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_sqrt, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint sin x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_sin(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_sin, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint sinh x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_sinh(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_sinh, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint tan x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_tan(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_tan, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

/** Non linear constraint tanh x = y */
// NOLINTNEXTLINE(readability-identifier-naming)
void NLFile::float_tanh(const Call* c) {
  nlconsOperatorUnary(c, NLToken::OpCode::OP_tanh, getTokenFromVarOrFloat(c->arg(0)),
                      getTokenFromVarOrFloat(c->arg(1)));
}

// --- --- --- Other

/** Integer x to floating point y. Constraint x = y translated into x - y = 0.
 *  Simulate a linear constraint [1, -1]+*[x, y] = 0
 */
void NLFile::int2float(const Call* c) {
  vector<double> coeffs = {1, -1};
  vector<string> vars = {};
  vars.push_back(getTokenFromVar(c->arg(0)).str);
  vars.push_back(getTokenFromVar(c->arg(1)).str);
  // Create the constraint
  linconsEq(c, coeffs, vars, NLToken::n(0));
}

// --- --- --- Objective

/** Add a solve goal in the NL File. In our case, we can only have one and only one solve goal. */
void NLFile::addSolve(SolveI::SolveType st, const Expression* e) {
  // We can only have one objective. Prevent override.
  assert(!objective.isDefined());

  switch (st) {
    case SolveI::SolveType::ST_SAT: {
      // Satisfy: implemented by minimizing 0 (print n0 for an empty expression graph)
      objective.minmax = NLObjective::SATISFY;
      break;
    }
    case SolveI::SolveType::ST_MIN: {
      // Maximize an objective represented by a variable
      objective.minmax = NLObjective::MINIMIZE;
      string v = getTokenFromVar(e).str;
      // Use the gradient
      vector<double> coeffs = {1};
      vector<string> vars = {v};
      objective.setGradient(vars, coeffs);
      break;
    }
    case SolveI::SolveType::ST_MAX: {
      // Maximize an objective represented by a variable
      objective.minmax = NLObjective::MAXIMIZE;
      string v = getTokenFromVar(e).str;
      // Use the gradient
      vector<double> coeffs = {1};
      vector<string> vars = {v};
      objective.setGradient(vars, coeffs);
      break;
    }
  }

  // Ensure that the obejctive is now defined.
  assert(objective.isDefined());
}

/* *** *** *** Phase 2: processing *** *** *** */

void NLFile::phase2() {
  // --- --- --- Go over all constraint (algebraic AND logical) and mark non linear variables
  for (auto const& n_c : constraints) {
    for (auto const& tok : n_c.second.expressionGraph) {
      if (tok.isVariable()) {
        variables.at(tok.str).isInNLConstraint = true;
      }
    }
  }

  for (auto const& c : logicalConstraints) {
    for (auto const& tok : c.expressionGraph) {
      if (tok.isVariable()) {
        variables.at(tok.str).isInNLConstraint = true;
      }
    }
  }

  // --- --- --- Go over the objective and mark non linear variables
  for (auto const& tok : objective.expressionGraph) {
    if (tok.isVariable()) {
      variables.at(tok.str).isInNLObjective = true;
    }
  }

  // --- --- --- Variables ordering and indexing
  for (auto const& name_var : variables) {
    const NLVar& v = name_var.second;

    // Accumulate jacobian count
    _jacobianCount += v.jacobianCount;

    // First check non linear variables in BOTH objective and constraint.
    if (v.isInNLObjective && v.isInNLConstraint) {
      if (v.isInteger) {
        vname_nliv_both.push_back(v.name);
      } else {
        vname_nlcv_both.push_back(v.name);
      }
    }
    // Variables in non linear constraint ONLY
    else if (!v.isInNLObjective && v.isInNLConstraint) {
      if (v.isInteger) {
        vname_nliv_cons.push_back(v.name);
      } else {
        vname_nlcv_cons.push_back(v.name);
      }
    }
    // Variables in non linear objective ONLY
    else if (v.isInNLObjective && !v.isInNLConstraint) {
      if (v.isInteger) {
        vname_nliv_obj.push_back(v.name);
      } else {
        vname_nlcv_obj.push_back(v.name);
      }
    }
    // Variables not appearing nonlinearly
    else if (!v.isInNLObjective && !v.isInNLConstraint) {
      if (v.isInteger) {
        vname_liv_all.push_back(v.name);
      } else {
        vname_lcv_all.push_back(v.name);
      }
    }
    // Should not happen
    else {
      should_not_happen("Dispatching variables in phase2");
    }
  }

  // Note:  In the above, we dealt with all 'vname_*' vectors BUT 'vname_larc_all' and
  // 'vname_bv_all'
  //        networks and boolean are not implemented. Nevertheless, we keep the vectors and deal
  //        with them below to ease further implementations.

  vnames.reserve(variables.size());

  vnames.insert(vnames.end(), vname_nlcv_both.begin(), vname_nlcv_both.end());
  vnames.insert(vnames.end(), vname_nliv_both.begin(), vname_nliv_both.end());
  vnames.insert(vnames.end(), vname_nlcv_cons.begin(), vname_nlcv_cons.end());
  vnames.insert(vnames.end(), vname_nliv_cons.begin(), vname_nliv_cons.end());
  vnames.insert(vnames.end(), vname_nlcv_obj.begin(), vname_nlcv_obj.end());
  vnames.insert(vnames.end(), vname_nliv_obj.begin(), vname_nliv_obj.end());
  vnames.insert(vnames.end(), vname_larc_all.begin(), vname_larc_all.end());
  vnames.insert(vnames.end(), vname_lcv_all.begin(), vname_lcv_all.end());
  vnames.insert(vnames.end(), vname_bv_all.begin(), vname_bv_all.end());
  vnames.insert(vnames.end(), vname_liv_all.begin(), vname_liv_all.end());

  // Create the mapping name->index
  for (int i = 0; i < vnames.size(); ++i) {
    variableIndexes[vnames[i]] = i;
  }

  // --- --- --- Constraint ordering, couting, and indexing
  for (auto const& name_cons : constraints) {
    const NLAlgCons& c = name_cons.second;

    // Sort by linearity. We do not have network constraint.
    if (c.isLinear()) {
      cnames_lin_general.push_back(c.name);
    } else {
      cnames_nl_general.push_back(c.name);
    }

    // Count the number of ranges and eqns constraints
    switch (c.range.tag) {
      case NLBound::LB_UB: {
        ++algConsRangeCount;
      }
      case NLBound::EQ: {
        ++algConsEqCount;
      }
      default: {
        break;
      }
    }
  }

  cnames.reserve(constraints.size());
  cnames.insert(cnames.end(), cnames_nl_general.begin(), cnames_nl_general.end());
  cnames.insert(cnames.end(), cnames_nl_network.begin(), cnames_nl_network.end());
  cnames.insert(cnames.end(), cnames_lin_network.begin(), cnames_lin_network.end());
  cnames.insert(cnames.end(), cnames_lin_general.begin(), cnames_lin_general.end());

  // Create the mapping name->index
  for (int i = 0; i < cnames.size(); ++i) {
    constraintIndexes[cnames[i]] = i;
  }
}

// --- --- --- Simple tests

bool NLFile::hasIntegerVars() const {
  return (lvbiCount() + lvciCount() + lvoiCount() + ivCount()) > 0;
}

bool NLFile::hasContinousVars() const { return !hasIntegerVars(); }

// --- --- --- Counts

/** Jacobian count. */
unsigned int NLFile::jacobianCount() const { return _jacobianCount; }

/** Total number of variables. */
unsigned int NLFile::varCount() const { return variables.size(); }

/** Number of variables appearing nonlinearly in constraints. */
unsigned int NLFile::lvcCount() const {
  // Variables in both + variables in constraint only (integer+continuous)
  return lvbCount() + vname_nliv_cons.size() + vname_nlcv_cons.size();
}

/** Number of variables appearing nonlinearly in objectives. */
unsigned int NLFile::lvoCount() const {
  // Variables in both + variables in objective only (integer+continuous)
  return lvbCount() + vname_nliv_obj.size() + vname_nlcv_obj.size();
}

/** Number of variables appearing nonlinearly in both constraints and objectives.*/
unsigned int NLFile::lvbCount() const { return vname_nlcv_both.size() + vname_nliv_both.size(); }

/** Number of integer variables appearing nonlinearly in both constraints and objectives.*/
unsigned int NLFile::lvbiCount() const { return vname_nliv_both.size(); }

/** Number of integer variables appearing nonlinearly in constraints **only**.*/
unsigned int NLFile::lvciCount() const { return vname_nliv_cons.size(); }

/** Number of integer variables appearing nonlinearly in objectives **only**.*/
unsigned int NLFile::lvoiCount() const { return vname_nliv_obj.size(); }

/** Number of linear arcs. Network nor implemented, so always 0.*/
unsigned int NLFile::wvCount() const { return vname_larc_all.size(); }

/** Number of "other" integer variables.*/
unsigned int NLFile::ivCount() const { return vname_liv_all.size(); }

/** Number of binary variables.*/
unsigned int NLFile::bvCount() const { return vname_bv_all.size(); }

/** *** *** *** Printable *** *** *** **/
// Note:  * empty line not allowed
//        * comment only not allowed
ostream& NLFile::printToStream(ostream& os) const {
  // Print the header
  {
    NLHeader header;
    NLHeader::printToStream(os, *this);
  }
  os << endl;

  // Print the unique segments about the variables
  if (varCount() > 1) {
    // Print the 'k' segment Maybe to adjust with the presence of 'J' segments
    os << "k" << (varCount() - 1)
       << "   # Cumulative Sum of non-zero in the jacobian matrix's (nbvar-1) columns." << endl;
    unsigned int acc = 0;
    // Note stop before the last var. Total jacobian count is in the header.
    for (int i = 0; i < varCount() - 1; ++i) {
      string name = vnames[i];
      acc += variables.at(name).jacobianCount;
      os << acc << "   # " << name << endl;
    }

    // Print the 'b' segment
    os << "b   # Bounds on variables (" << varCount() << ")" << endl;
    for (const auto& n : vnames) {
      NLVar v = variables.at(n);
      v.bound.printToStream(os, n);
      os << endl;
    }
  }

  // Print the unique segments about the constraints
  if (!cnames.empty()) {
    // Create the 'r' range segment per constraint
    // For now, it is NOT clear if the network constraint should appear in the range constraint or
    // not. To be determine if later implemented.
    os << "r   # Bounds on algebraic constraint bodies (" << cnames.size() << ")" << endl;
    for (const auto& n : cnames) {
      NLAlgCons c = constraints.at(n);
      c.range.printToStream(os, n);
      os << endl;
    }
  }

  // Print the Algebraic Constraints
  for (const auto& n : cnames) {
    NLAlgCons c = constraints.at(n);
    c.printToStream(os, *this);
  }

  // Print the Logical constraint
  for (const auto& lc : logicalConstraints) {
    lc.printToStream(os, *this);
  }

  // Print the objective
  objective.printToStream(os, *this);
  return os;
}

}  // namespace MiniZinc
