Object-oriented interface reference

What is the object-oriented interface?

The object-oriented interface provides the functionality of the callable library with an easy-to-use set of classes. The interface is available in C++, C#, and Java. The problem definition is contained in a class definition and is simpler: variable and constraint properties can be defined more compactly; memory for the problem characteristics does not need to be allocated; and Knitro API functions are simplified with some of the arguments handled internally within the classes.

This guide focuses on the C++ version of the interface.

Simple examples of the object-oriented interface can be found in User guide. More complex examples can be found in the examples folder.

Getting started with the Java object-oriented interface

Java interfaces are distributed as a JAR with additional packages containing Javadoc, sources and dependencies.

Java interfaces require using Java 8 or higher and BridJ, a Java native interoperability library, which is also provided.

Import JAR files and dependencies within your project in order to enable using Knitro with Java interfaces.

Examples can be compiled and run from your favorite IDE or using the provided makefile.

Getting started with the C# object-oriented interface

C# interface requires .net 4.0 or higher.

A project is distributed as a Microsoft Visual Studio solution with all the sources and examples.

In order to run, the project only needs to have KNITRODIR environment variable set to the Knitro directory.

To modify the example run by Visual Studio, right click on “KNCS” project then select “Properties” and modify “Startup object” (in Application tab).

Getting started with the C++ object-oriented interface

The C++ object oriented interface is distributed as a library which is straightforward to include within your projects.

Examples are provided within a CMake project. Please refer to the README file in examples/C++ directory for more details.

The following sections and subsequent references to object-oriented interfaces within the documentation, use C++ interface methods and classes.

Defining a problem

This section describes how to define a problem in the object-oriented interface by implementing the KNProblem class.

Sparse structures

The C++ interface uses the concept of sparse structures to provide Knitro information concerning only subparts of the problem. Sparse structures are used to affect values to a subset of indexes (variables or constraints). For example, to specify upper bounds for some variables:

setVarUpBnds({ {1, 5, 8}, {10.0, 7.5, 8.9} });

Variables x1, x5 and x8 will have respectively 10.0, 7.5 and 8.9 as upper bound. Other variables upper bounds would have default values set internally by Knitro. This behaviour is ensured by the sparse structure KNSparseVector.

/**
 * A SparseVector is simply a mapping between a set of indexes and a set of values.
 * The number of indexes shall never exceed the number of values.
 *
 * A sparse vector represent the mapping indexes[i] -> values[i], where i is a coordinate of the vector.
 * A SparseVector is empty if size(index) == size(values) == 0
 * A SparseVector is plain if size(index) == 0 && size(values) > 0
 * A SparseVector is sparse if size(index) == size(values) > 0
 * Examples :
 *    T = ({0,1,3,5}, {1.2,36,3.9,4.8})
 *    T is a sparse vector and T[3] = 4.8, mapping the index 5 to 4.8
 *
 *    T = ({}, {1.2,36,3.9,4.8})
 *    T is a plain vector and T[3] = 4.8, mapping the index 3 to 4.8
 *
 *    T = ({}, {})
 *    T is an empty vector
 *
 *    T = ({{0,1,3,5}}, {})
 *    T is an invalid vector
 */
template<class IndexType, class ValueType>
class KNSparseVector {

    ...

  /**
   * List of indexes.
   */
  std::vector<IndexType> _indexes;

  /**
   * List of values.
   */
  std::vector<ValueType> _values;

};

The same syntax can be used to represent linear expressions as a set of variable indexes and a set of constant coefficients.

/**
 * A LinearStructure is a SparseVector with types :
 * int for indexes
 * double for values
 *
 * Typically, a LinearStructure is a matrix form of the linear part of a given function.
 * Where indexes are the variables indexes of the problem which appear linearly in the function
 * and values are the corresponding coefficients.
 *
 * Example:
 *
 * ({2,3,7}, {5.0, -5.8, 3.6})
 * Represents the linear part:
 * 5.0x2 - 5.8x3 + 3.6x7
 */
class KNLinearStructure : public KNSparseVector<int,double>

Or to provide coordinates in a matrix (Jacobian for instance).

/**
 * A SparseMatrixStructure is a SparseVector with types :
 * int for indexes
 * int for values
 *
 * Typically, a SparseMatrixStructure represents a list of coordinates in a matrix.
* Example:
 *
 * ({2,3,7}, {6,8,9})
 *
 * Are the coordinates :
 * [(2,6),(3,8),(7,9)]
 */
class KNSparseMatrixStructure : public KNSparseVector<int,int>

The concept is also extended to a tripet of sets representing quadratic expressions.

/**
 * A QuadraticStructure works as a LinearStructure with types :
 * int for first variable indexes
 * int for second variable indexes
 * double for coefficients
 *
 * Typically, a QuadraticStructure represents a pair of variable with an associated coefficient.
* Example:
 *
 * ({2,3,7}, {2,8,9}, {5.0, -5.8, 3.6})
 *
 * Is the quadratic expression :
 * 5.0x2^2 - 5.8x3x8 + 3.6x7x9
 */
class KNQuadraticStructure : public KNLinearStructure {

    ...

  /**
   * List of indexes, indexes of second variable of a quadratic term in a quadratic part.
   */
  std::vector<int> _varIndexes2;
};

Sparse structures In Java and C#

These structures are implemented as well in Java and C#. However, the methods that use these structures are overloaded by more user-friendly methods.

Here is an example with setVarUpBnds:

In Java:

/**
 * To set variables upper bounds, variable indexes and upper bound values are given as arguments.
 */
setVarUpBnds(Arrays.asList(1, 5, 8), Arrays.asList(10.0, 7.5, 8.9));

/**
 * This method overloads the setVarUpBnds methods with KNSparseVector.
 */
public void setVarUpBnds(final List<Integer> varIndexes, final List<Double> varUpBnds) throws KNException
{
    setVarUpBnds(new KNSparseVector<>(varUpBndsIndexes, varUpBndsValues));
}

In C#:

/**
 * To set variables upper bounds, variable indexes and upper bound values are given as arguments.
 */
SetVarUpBnds(new List<int> {1, 5, 8}, new List<double> {10.0, 7.5, 8.9});

/**
 * This method overloads the setVarUpBnds methods with KNSparseVector.
 */
public void SetVarUpBnds(List<int> varIndexes, List<double> varUpBnds)
{
    SetVarUpBnds(new KNSparseVector<int, double>(varIndexes, varUpBnds));
}

Minimal required implementation

In order to define an optimization problem, a problem class that inherits from KNProblem must be defined by the user. Such a class should at least:

  • pass the number of variables to the KNProblem constructor.

  • pass the number of constraints to the KNProblem constructor.

The user can also :

  • set variable upper and lower bounds with KNProblem::setVarLoBnds() and KNProblem::setVarUpBnds()

  • set constraint upper and lower bounds with KNProblem::setConLoBnds() and KNProblem::setConUpBnds()

  • set constraint types with KNProblem::setConTypes()

  • set the objective type with KNProblem::setObjType()

  • set the objective goal with KNProblem::setObjGoal()

  • set the initial primal variable values with KNProblem::setXInitial()

  • set the initial dual variable values with KNProblem::setLambdaInitial()

If these values are not set, Knitro will automatically determine initial values.

Implementing a MIP problem

If the problem has integer or binary variables, the following must also be used:

  • set variable types with KNProblem::setVarTypes()

Implementing derivatives

Like the callable library, the object-oriented interface does not compute derivatives automatically.

To evaluate non linear parts of its problem, the user should define:

  • A callback to evaluate the non linear parts

  • If possible callbacks to evaluate first and second derivatives

  • Sparsity patterns for the Jacobian and Hessian matrices

Note

Exact derivative evaluation is a key element of the implementation. It will make Knitro more efficient and robust. If these callbacks are not available, Knitro will perform approximations.

Knitro uses the sparsity pattern to speed up linear algebra computations.

See parts Derivatives and Callbacks for more information.

The class KNNonLinearStructure holds all the necessary information to pass a nonlinear function to Knitro.

/**
 * NonLinearStructure represents the non-linear part of a given function
 * It stores all the information to be passed down to Knitro for its callback routines.
 *
 * NonLinearStructure holds an EvalCallback which stores 3 function pointers to evaluate :
 *  This function non-linear part through a callback
 *  This function non-linear part's jacobian (gradient) through a callback
 *  This function non-linear part's hessian through a callback
 *
 * NonLinearStructure also holds derivatives structural informations :
 *  KNSparseMatrixStructure for the jacobian non-zero pattern
 *  KNSparseMatrixStructure for the hessian non-zero pattern
 */
class KNNonLinearStructure {...}

/**
 * NonLinearBloc is a NonLinearStructure that is used for a set of constraints
 * The same structure of callbacks and non-zero patterns is used to represent
 * non-linear parts of several constraints.
 */
class KNNonLinearBloc : public KNNonLinearStructure {
  ...
    std::vector<int> _indexes;
}

Here is an example on how to use nonlinear bloc to register several nonlinear constraints and their derivatives.

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *  This example demonstrates how to use Knitro to solve the following
 *  simple nonlinear optimization problem.  This model is test problem
 *  HS40 from the Hock & Schittkowski collection.
 *
 *  max   x0*x1*x2*x3         (obj)
 *  s.t.  x0^3 + x1^2 = 1     (c0)
 *        x0^2*x3 - x2 = 0    (c1)
 *        x3^2 - x1 = 0       (c2)
 *
 *  This is the same as exampleNLP2.c, but it demonstrates using
 *  multiple callbacks for the nonlinear evaluations and computing
 *  some gradients using finite-differencs, while others are provided
 *  in callback routines.
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/


#include "KNSolver.h"
#include "KNProblem.h"

using namespace knitro;

class ProblemNLP2 : public knitro::KNProblem {
public:

  /*------------------------------------------------------------------*/
  /*     FUNCTION EVALUATION CALLBACKS                                */
  /*------------------------------------------------------------------*/

  /** The signature of this function matches KN_eval_callback in knitro.h.
   *  Only "obj" is set in the KN_eval_result structure.
   */
  static int callbackEvalObj (KN_context_ptr             kc,
                       CB_context_ptr             cb,
                       KN_eval_request_ptr const  evalRequest,
                       KN_eval_result_ptr  const  evalResult,
                       void              * const  userParams)
  {
      const double *x;
      double *obj;

      if (evalRequest->type != KN_RC_EVALFC)
      {
          printf ("*** callbackEvalObj incorrectly called with eval type %d\n",
                  evalRequest->type);
          return( -1 );
      }
      x = evalRequest->x;
      obj = evalResult->obj;

      /** Evaluate nonlinear term in objective */
      *obj = x[0]*x[1]*x[2]*x[3];

      return( 0 );
  }

  /** The signature of this function matches KN_eval_callback in knitro.h.
   *  Only "c0" is set in the KN_eval_result structure.
   */
  static int callbackEvalC0 (KN_context_ptr             kc,
                      CB_context_ptr             cb,
                      KN_eval_request_ptr const  evalRequest,
                      KN_eval_result_ptr  const  evalResult,
                      void              * const  userParams)
  {
      const double *x;
      double *c;

      if (evalRequest->type != KN_RC_EVALFC)
      {
          printf ("*** callbackEvalC0 incorrectly called with eval type %d\n",
                  evalRequest->type);
          return( -1 );
      }
      x = evalRequest->x;
      c = evalResult->c;

      /** Evaluate nonlinear terms in constraint, c0 */
      *c = x[0]*x[0]*x[0];

      return( 0 );
  }

  /** The signature of this function matches KN_eval_callback in knitro.h.
   *  Only "c1" is set in the KN_eval_result structure.
   */
  static int callbackEvalC1 (KN_context_ptr             kc,
                      CB_context_ptr             cb,
                      KN_eval_request_ptr const  evalRequest,
                      KN_eval_result_ptr  const  evalResult,
                      void              * const  userParams)
  {
      const double *x;
      double *c;

      if (evalRequest->type != KN_RC_EVALFC)
      {
          printf ("*** callbackEvalC1 incorrectly called with eval type %d\n",
                  evalRequest->type);
          return( -1 );
      }
      x = evalRequest->x;
      c = evalResult->c;

      /** Evaluate nonlinear terms in constraint, c1 */
      *c = x[0]*x[0]*x[3];

      return( 0 );
  }


  /*------------------------------------------------------------------*/
  /*     GRADIENT EVALUATION CALLBACKS                                */
  /*------------------------------------------------------------------*/

  /** The signature of this function matches KN_eval_callback in knitro.h.
   *  Only "objGrad" is set in the KN_eval_result structure.
   */
  static int callbackEvalObjGrad (KN_context_ptr             kc,
                           CB_context_ptr             cb,
                           KN_eval_request_ptr const  evalRequest,
                           KN_eval_result_ptr  const  evalResult,
                           void              * const  userParams)
  {
      const double *x;
      double *objGrad;

      if (evalRequest->type != KN_RC_EVALGA)
      {
          printf ("*** callbackEvalObjGrad incorrectly called with eval type %d\n",
                  evalRequest->type);
          return( -1 );
      }
      x = evalRequest->x;
      objGrad = evalResult->objGrad;

      /** Evaluate nonlinear terms in objective gradient */
      objGrad[0] = x[1]*x[2]*x[3];
      objGrad[1] = x[0]*x[2]*x[3];
      objGrad[2] = x[0]*x[1]*x[3];
      objGrad[3] = x[0]*x[1]*x[2];

      return( 0 );
  }

  /** The signature of this function matches KN_eval_callback in knitro.h.
   *  Only gradient of c0 is set in the KN_eval_result structure.
   */
  static int callbackEvalC0Grad (KN_context_ptr             kc,
                          CB_context_ptr             cb,
                          KN_eval_request_ptr const  evalRequest,
                          KN_eval_result_ptr  const  evalResult,
                          void              * const  userParams)
  {
      const double *x;
      double *jac;

      if (evalRequest->type != KN_RC_EVALGA)
      {
          printf ("*** callbackEvalC0Grad incorrectly called with eval type %d\n",
                  evalRequest->type);
          return( -1 );
      }
      x = evalRequest->x;
      jac = evalResult->jac;

      /** Evaluate nonlinear terms in c0 constraint gradients */
      jac[0] = 3.0*x[0]*x[0]; /* derivative of x0^3 term  wrt x0    */

      return( 0 );
  }

  ProblemNLP2() : KNProblem(4,3) {
    // Initial primal values
    this->setXInitial({ {0.8, 0.8, 0.8, 0.8} });

    // Constraints rhs
    this->setConEqBnds({ {1.0, 0.0, 0.0} });

    /** Add linear structure and coefficients. */
    this->setConstraintsLinearParts({ {1, 2}, // c1 and c2 have linear parts
      {
        { {2}, {-1.0} }, // linear pattern for c1 (-x2)
        { {1}, {-1.0} }  // linear pattern for c2 (-x1)
      }
    });

    /** Add quadratic structure and coefficients. */
    this->setConstraintsQuadraticParts({ {0, 2}, // c0 and c2 have quadratic parts
      {
        { {1}, {1}, {1.0} }, // quadratic pattern for c0 (x1^2)
        { {3}, {3}, {1.0} }  // quadratic pattern for c2 (x3^2)
      }
    });

    this->setObjEvalCallback(&ProblemNLP2::callbackEvalObj);
    this->setObjGradNnzPattern({ {0, 1, 2, 3} });
    this->setGradEvalCallback(&ProblemNLP2::callbackEvalObjGrad);

    /** Set minimize or maximize (if not set, assumed minimize) */
    this->setObjGoal(KN_OBJGOAL_MAXIMIZE);

    this->setNonLinearC0();
    this->setNonLinearC1();
  }

  void setNonLinearC0() {
    KNEvalCallback callback(&ProblemNLP2::callbackEvalC0, NULL, &ProblemNLP2::callbackEvalC0Grad, NULL);
    KNSparseMatrixStructure jacNnzPattern = { {0}, {0} };
    KNSparseMatrixStructure hessNnzPattern;
    std::vector<int> cst_indexes = {0};
    KNNonLinearBloc c0bloc(cst_indexes, callback, jacNnzPattern, hessNnzPattern);
    this->getConstraintsNonLinearParts().push_back(c0bloc);
  }

  void setNonLinearC1() {
    KNEvalCallback callback(&ProblemNLP2::callbackEvalC1, NULL, NULL, NULL);
    KNSparseMatrixStructure jacNnzPattern = { {1,1}, {0,3} };
    KNSparseMatrixStructure hessNnzPattern;
    std::vector<int> cst_indexes = {1};
    KNNonLinearBloc c1bloc(cst_indexes, callback, jacNnzPattern, hessNnzPattern);
    this->getConstraintsNonLinearParts().push_back(c1bloc);
  }

};

int main() {
  // Create a problem instance.
  ProblemNLP2 instance = ProblemNLP2();

  // Create a solver
  knitro::KNSolver solver(&instance);

  /** Set option to print output after every iteration. */
  solver.setParam("hessopt", KN_HESSOPT_BFGS);

  solver.initProblem();
  int solveStatus = solver.solve();

  std::vector<double> x;
  std::vector<double> lambda;
  int nStatus = solver.getSolution(x, lambda);

  printf ("Knitro converged with final status = %d\n", nStatus);
  printf ("  optimal objective value  = %e\n", solver.getObjValue());
  printf ("  optimal primal values x  = (%e, %e, %e, %e)\n", x[0], x[1], x[2], x[3]);
  printf ("  feasibility violation    = %e\n", solver.getAbsFeasError());
  printf ("  KKT optimality violation = %e\n", solver.getAbsOptError());
  return 0;
}

Non linear structures and function callbacks in Java and C#

KNNonLinearStructure and KNNonLinearBloc are available as well in Java and C#. The example above is available in Java and C# (ExampleMultipleCB) in the Knitro examples folder. Note that function callbacks are defined differently in Java and C#. The function callbacks are abstract classes with an evaluate function to override. Below are examples in Java and C# on how to define the objective callback callbackEvalObj from the example above.

In Java:

private class callbackEvalObj extends KNEvalFCCallback
{
    @Override
    public void evaluateFC(final List<Double> x, final List<Double> obj, final List<Double> c)
    {
        obj.set(0, x.get(0)*x.get(1)*x.get(2)*x.get(3));
    }
}
/**
 * Then register the callback in the problem.
 */
ProbmlemNLP2() throws KNException
{
    super(4, 3);
    //...
    setObjEvalCallback(new callbackEvalObj());
    //...
}

In C#:

private class CallbackEvalObj : KNEvalFCCallback
{
    public override void EvaluateFC(ReadOnlyCollection<double> x, IList<double> obj, IList<double> c)
    {
        obj[0] = x[0] * x[1] * x[2] * x[3];
    }
}
/**
 * Then register the callback in the problem.
 */
public ProblemNLP2() : base(4, 3)
{
    //...
    SetObjEvalCallback(new CallbackEvalObj());
    //...
}

Complementarity Constraints

Complementarity constraints can be specified in the object-oriented interface by passing the lists of complementary variables to the function:

KNProblem::setCompConstraintsParts(const KNSparseMatrixStructure& compConsPart);

Example:

// x2 x3 and x4 complements respectively x5, x6 and x7
setCompConstraintsParts({{2, 3, 4}, {5, 6, 7}});

Using the KNSolver class to solve a problem

Once a problem is defined by inheriting from KNProblem or KNProblemLSQ, the KNSolver class is used to call Knitro to solve the problem. This class is also used to set most Knitro parameters, and access solution information after Knitro has completed solving the problem.

To use the KNSolver class, one of four constructors can be used. Each of the constructors takes at least a pointer to a KNBaseProblem object (each KNSolver object is associated with one problem definition. If different problems are to be solved, multiple KNSolver objects are needed).

KNSolver(KNBaseProblem * problem);

This constructor should be used when using exact gradient and Hessian evaluation.

KNSolver(KNBaseProblem * problem, int gradopt, int hessopt);

This constructor should be used when specifying a gradient and Hessian evaluation other than the default exact gradient and Hessian evaluations.

For both of these constructors, a pointer to a ALM object can also be passed as an additional argument, when using a network license of Knitro with the Artelys License Manager. Otherwise, a local Knitro license is used.

Once the solver object is created, Knitro options can be set with KNSolver::setParam(), or by loading a parameters file with KNSolver::loadParamFile(). Finally, the function KNSolver::solve() will call Knitro to solve the problem and will return a solution status code.

KNSolver::solve() can be called multiple times. Between each call to solve(), two types of changes can be made:

  • KNSolver::setParam() can be used to change problem parameters, except for the gradient and Hessian evaluation types.

  • Variable bounds can be changed by calling KNSolver::setVarLoBnds() and KNSolver::setVarUpBnds() in the problem object that the solver object points to.

Accessing callable library functions from the object-oriented interface

The object-oriented interface provides access to the Knitro callable library functions. The table below shows the correspondence between callable library functions and object-oriented interface functions.

The majority of the functions are accessed directly through KNSolver methods. There are a few major differences between the callable library functions and the object-oriented interface methods:

  • The callable library methods take a KN_context_ptr argument (created from a call to KN_new()), which holds problem information. The object-oriented interface methods do not take this argument, storing the necessary information in the KNSolver object.

  • The callable library methods return status codes, with a non-zero status code usually indicating an error. The object-oriented interface methods do not return status codes. If the methods encounter an error, usually related to an invalid Knitro license or invalid function arguments, a KNException is thrown.

  • Several callable library methods, such as KN_get_con_values(), modify input parameters. Instead, the object-oriented interface methods return these values as output parameters (rather than returning status codes).

  • Function arguments use std::vector<> (C++), IList<> (Java), or List<> (C#) instead of C-style (pointer) arrays. Instead of character arrays, functions use std::string (C++), or String (Java and C#).

Callable Library Function

Object-Oriented Interface Methods

KN_new()

Not necessary - problem information stored in KNSolver object.

KN_free()

Not necessary - problem information stored in KNSolver object.

KN_reset_params_to_defaults()

KNSolver::resetParamsToDefault()

KN_load_param_file()

KNSolver::loadParamFile()

KN_save_param_file()

KNSolver::saveParamFile()

KN_set_int_param_by_name()

KNSolver::setParam()

KN_set_char_param_by_name()

KNSolver::setParam()

KN_set_double_param_by_name()

KNSolver::setParam()

KN_set_int_param()

KNSolver::setParam()

KN_set_char_param()

KNSolver::setParam()

KN_set_double_param()

KNSolver::setParam()

KN_get_int_param_by_name()

KNSolver::getIntParam()

KN_get_double_param_by_name()

KNSolver::getDoubleParam()

KN_get_int_param()

KNSolver::getIntParam()

KN_get_double_param()

KNSolver::getDoubleParam()

KN_get_param_name()

KNSolver::getParamName()

KN_get_param_doc()

KNSolver::getParamDoc()

KN_get_param_type()

KNSolver::getParamType()

KN_get_num_param_values()

KNSolver::getNumParamValues()

KN_get_param_value_doc()

KNSolver::getParamValueDoc()

KN_get_param_id()

KNSolver::getParamID()

KN_get_release()

KNSolver::getRelease()

KN_load_tuner_file()

KNSolver::loadTunerFile()

KN_set_compcons()

Not necessary - KNSolver::setCompCons()

KN_solve()

KNSolver::solve()

KN_set_mip_branching_priorities()

KNBaseProblem::setVarMipBranchingPriorities()

KN_set_newpt_callback()

KNBaseProblem::setNewPointCallback()

KN_set_ms_process_callback()

KNBaseProblem::setMSProcessCallback()

KN_set_mip_node_callback()

KNBaseProblem::setMipNodeCallback()

KN_set_ms_initpt_callback()

KNBaseProblem::setMSInitptCallback()

KN_get_number_FC_evals()

KNSolver::getNumberFCEvals()

KN_get_number_GA_evals()

KNSolver::getNumberGAEvals()

KN_get_number_H_evals()

KNSolver::getNumberHEvals()

KN_get_number_HV_evals()

KNSolver::getNumberHVEvals()

KN_get_number_iters()

KNSolver::getNumberIters()

KN_get_number_cg_iters()

KNSolver::getNumberCGIters()

KN_get_abs_feas_error()

KNSolver::getAbsFeasError()

KN_get_rel_feas_error()

KNSolver::getRelFeasError()

KN_get_abs_opt_error()

KNSolver::getAbsOptError()

KN_get_rel_opt_error()

KNSolver::getRelOptError()

KN_get_solution()

KNSolver::getSolution()

KN_get_con_values_all()

KNSolver::getConstraintValues()

KN_get_objgrad_values()

KNSolver::getObjGradValues()

KN_get_jacobian_values()

KNSolver::getJacobianValues()

KN_get_hessian_values()

KNSolver::getHessianValues()

KN_get_mip_number_nodes()

KNSolver::getMipNumNodes()

KN_get_mip_number_solves()

KNSolver::getMipNumSolves()

KN_get_mip_abs_gap()

KNSolver::getMipAbsGap()

KN_get_mip_rel_gap()

KNSolver::getMipRelGap()

KN_get_mip_incumbent_obj()

KNSolver::getMipIncumbentObj()

KN_get_mip_relaxation_bnd()

KNSolver::getMipRelaxationBnd()

KN_get_mip_lastnode_obj()

KNSolver::getMipLastnodeObj()

KN_get_mip_incumbent_x()

KNSolver::getMipIncumbentX()

KN_set_var_scalings()

KNSolver::setVarScalings()

KN_set_con_scalings()

KNSolver::setConScaleFactors()

KN_set_obj_scaling()

KNSolver::setObjScaleFactor()

KN_get_obj_value

KNSolver::getObjValue()

KN_get_obj_type

KNSolver::getObjType()

Callbacks in the object-oriented interface

The object-oriented interface supports all of the callbacks that are supported by the callable library: MIP node callbacks; multi-start initial point callbacks; multi-start process callbacks; new point callbacks, and Knitro output redirection callbacks.

Each of these callbacks can be used through the KNUserCallback class, and passing the callback object to a KNBaseProblem object via the function KNBaseProblem::set{Callbacktype} (for some callback type):

KNUserCallback(KN_user_callback * fct_ptr, void * userParams)

The callback functionality is the same as described in the callable library reference section.

Below, we show an example of use of a NewPointCallback. This type of callback is called by Knitro during the problem iteration whenever Knitro finds a new estimate of the solution point (i.e., after each major iteration). This callback cannot modify any of its arguments, but can provide information about the solve before it is completed. In this example, the callback prints the number of objective function and constraint evaluations.

This callback should be passed to the KNProblem object, before passing it to the KNSolver object. This is shown below. The problem solved is the same example problem solved in previous sections (QCQP), but this callback is independent of the problem solved.

int newPointCBFct(KN_context_ptr kcSub, const double * const  x, const double * const  lambda, void *userParams) {

  /** The Knitro solver pointer was passed in */
  KNSolver* solver = static_cast<KNSolver*>(userParams);
  int n = solver->getNumVars();
  std::cout << ' ' << ">> New point computed by Knitro: (";
  for (int i = 0; i < n - 1; i++) {
    std::cout << x[i] << ", ";
  }

  std::cout << x[n - 1] << ")" << std::endl;
  std::cout << '\t' << "Number FC evals = " << solver->getNumberFCEvals() << std::endl;
  std::cout << '\t' << "Current feasError = " << solver->getAbsFeasError() << std::endl;
  return 0;
}

int main() {
  // Create a problem instance.
  ProblemQCQP1 instance = ProblemQCQP1();

  // Create a solver
  knitro::KNSolver solver(&instance, KN_GRADOPT_FORWARD, KN_HESSOPT_BFGS);

  // Create a callback for new points
  KNUserCallback newPointCB(&newPointCBFct, &solver);
  instance.setNewPointCallback(newPointCB);

  solver.initProblem();
  int solveStatus = solver.solve();

  std::vector<double> x;
  std::vector<double> lambda;
  int nStatus = solver.getSolution(x, lambda);
  printf ("Knitro converged with final status = %d\n", nStatus);
  printf ("  optimal objective value  = %e\n", solver.getObjValue());
  printf ("  optimal primal values x  = (%e, %e, %e)\n", x[0], x[1], x[2]);
  printf ("  feasibility violation    = %e\n", solver.getAbsFeasError());
  printf ("  KKT optimality violation = %e\n", solver.getAbsOptError());

  return 0;
}

Calling this function gives the following output, showing additional information each time Knitro finds a new estimate of the solution value:

=======================================
          Commercial License
         Artelys Knitro 12.4.0
=======================================

Knitro presolve eliminated 0 variables and 0 constraints.

gradopt:                 2
hessopt:                 2
The problem is identified as a QCQP.

Problem Characteristics                                 (   Presolved)
-----------------------
Objective goal:  Minimize
Objective type:  quadratic
Number of variables:                                  3 (           3)
    bounded below only:                               3 (           3)
    bounded above only:                               0 (           0)
    bounded below and above:                          0 (           0)
    fixed:                                            0 (           0)
    free:                                             0 (           0)
Number of constraints:                                2 (           2)
    linear equalities:                                1 (           1)
    quadratic equalities:                             0 (           0)
    gen. nonlinear equalities:                        0 (           0)
    linear one-sided inequalities:                    0 (           0)
    quadratic one-sided inequalities:                 1 (           1)
    gen. nonlinear one-sided inequalities:            0 (           0)
    linear two-sided inequalities:                    0 (           0)
    quadratic two-sided inequalities:                 0 (           0)
    gen. nonlinear two-sided inequalities:            0 (           0)
Number of nonzeros in Jacobian:                       6 (           6)
Number of nonzeros in Hessian:                        0 (           6)

Knitro using the Interior-Point/Barrier Direct algorithm.

  Iter      Objective      FeasError   OptError    ||Step||    CGits
--------  --------------  ----------  ----------  ----------  -------
       0    9.760000e+02   1.300e+01
 >> New point computed by Knitro: (3.8794, 0.01, 3.69211)
    Number FC evals = 0
    Current feasError = 1.01993
 >> New point computed by Knitro: (3.86709, 5e-05, 3.71871)
    Number FC evals = 0
    Current feasError = 0.968364
 >> New point computed by Knitro: (3.21477, 2.5e-07, 4.34564)
    Number FC evals = 0
    Current feasError = 0.137647
 >> New point computed by Knitro: (0.0160738, 8.08315e-08, 7.99343)
    Number FC evals = 0
    Current feasError = 0.0825911
 >> New point computed by Knitro: (8.03692e-05, 8.07197e-08, 8.01171)
    Number FC evals = 0
    Current feasError = 0.0825814
 >> New point computed by Knitro: (4.01846e-07, 7.48165e-08, 8.01117)
    Number FC evals = 0
    Current feasError = 0.0782078
 >> New point computed by Knitro: (2.00923e-09, 5.04472e-10, 8.00003)
    Number FC evals = 0
    Current feasError = 0.000201966
 >> New point computed by Knitro: (9.71394e-10, 3.11246e-10, 8)
    Number FC evals = 0
    Current feasError = 1.42109e-14
 >> New point computed by Knitro: (1.6533e-11, 5.31416e-12, 8)
    Number FC evals = 0
    Current feasError = 7.10543e-15
       9    9.360000e+02   7.105e-15   3.591e-09   1.976e-09        0

EXIT: Locally optimal solution found.

HINT: Knitro spent 0.000620 seconds checking model convexity.
      To skip the automatic convexity checker for QPs and QCQPs,
      explicity set the user option convex=0 or convex=1.

Final Statistics
----------------
Final objective value               =   9.36000000000340e+02
Final feasibility error (abs / rel) =   7.11e-15 / 5.47e-16
Final optimality error  (abs / rel) =   3.59e-09 / 2.24e-10
# of iterations                     =          9
# of CG iterations                  =          0
# of function evaluations           =          0
# of gradient evaluations           =          0
Total program time (secs)           =       0.00610 (     0.005 CPU time)
Time spent in evaluations (secs)    =       0.00000

===============================================================================

Knitro converged with final status = 0
  optimal objective value  = 9.360000e+02
  optimal primal values x  = (1.653297e-11, 5.314156e-12, 8.000000e+00)
  feasibility violation    = 7.105427e-15
  KKT optimality violation = 3.590869e-09

Callbacks in Java and C#

Java and C# handle these callbacks with different classes; MIP node callbacks, multi-start process callbacks and new point callbacks are handle by KNUserCallback; multi-start initial point callbacks is handle by KNMSInitPtCallback; Knitro output redirection callbacks is handle by KNPutsCallback. Some examples provided in the Knitro examples folder show how to use them.

  • MIP node callbacks : ExampleMINLP1

  • Multi-start process callbacks : ExampleMultiStart

  • New point callbacks : ExampleNewPointCallback

  • Multi-start initial point callbacks : ExampleMSInitPtCallback

  • Knitro output redirection callbacks : ExamplePuts

Changing variable bounds in the object-oriented interface

In both the object-oriented interface and the callable library, a problem can be solved multiple times. The object-oriented interface differs from the callable library in how variable bounds are changed between two solves. In the object-oriented interface, variable bounds are set in a KNProblem object. When KNSolver::solve() is called to solve the problem, the variable bounds in the KNProblem object are passed to the solver. the following example shows changing variable bounds between calls to KNSolver::solve().

int main() {
  // Create a problem instance.
  ProblemQCQP1 instance = ProblemQCQP1();

  // Create a solver
  knitro::KNSolver solver(&instance);

  solver.initProblem();
  int solveStatus = solver.solve();

  solver.setVarUpBnds({{0.7, 0.7, 0.7}}, 3);
  solveStatus = solver.solve();

  return 0;
}

In this example, the problem characteristics (including variable bounds) are initialized in the constructor of ProblemQCQP1. After the first call of KNSolver::solve(), the variable bounds are changed directly into the solver with the KNSolver::setVarUpBnds() function. This function sets all variable bounds to 0.7. In addition to variable bounds, Knitro parameters except for the gradient and Hessian evaluation type can be changed between calls to solve.

Equivalently, the user can change variable bounds through the KNProblem, and call KNSolver::setVarUpBnds() to trigger the update of variable upper bounds and nothing else.

int main() {
  // Create a problem instance.
  ProblemQCQP1 instance = ProblemQCQP1();

  // Create a solver
  knitro::KNSolver solver(&instance);

  solver.initProblem();
  int solveStatus = solver.solve();

  instance.setVarUpBnds({{0.7, 0.7, 0.7}});
  solver.setVarUpBnds();
  solveStatus = solver.solve();

  return 0;
}

Using the Artelys License Manager with the object-oriented interface

The object-oriented interface can be used with either a standalone Knitro license or a network Knitro license using the Artelys License Manager (ALM) and a license server.

In order to use the ALM with the object-oriented interface, the license server needs to be installed and the user machine (from which Knitro is run) should be configured to find the license server. For information on installing and configuring the ALM, see the Artelys License Manager User’s Manual.

To use the ALM with the object-oriented interface, a ALM object needs to be created and passed to a KNSolver object constructor. When the ALM object is created, a network license will be checked out for use and unavailable for other users until the ALM object is destroyed.

An example usage is shown below. This example is identical to (and produces the same output as) the other examples of the object-oriented interface, except for the instantiation of the ALM object and the KNSolver constructor that takes a pointer to the ALM object.

int main() {
  knitro::ALM alm;

  // Create a problem instance.
  ProblemQCQP instance = ProblemQCQP();

  // Create a solver
  KNSolver solver(&alm, &instance);

  int solveStatus = solver.solve();

  return 0;
}