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 6 or higher and Java Native Access 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 “KTRC” 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 header 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 abstract KTRProblem class. The KTRProblem class inherits from the KTRIProblem class and defines several functions that make implementing the problem easier. Users should consult KTRIProblem.h for more information on how to implement the KTRIProblem class if the KTRProblem is not used.

Minimal required implementation

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

  • pass the number of variables to the KTRProblem constructor.
  • pass the number of constraints to the KTRProblem constructor.
  • set variable upper and lower bounds with KTRProblem::setVarLoBnds() and KTRProblem::setVarUpBnds()
  • set constraint upper and lower bounds with KTRProblem::setConLoBnds() and KTRProblem::setConUpBnds()
  • set constraint types with KTRProblem::setConTypes()
  • set the objective type with KTRProblem::setObjType()
  • set the objective goal with KTRProblem::setObjGoal()

It should also define evaluation functions; at minimum

  • define the function double KTRProblem::evaluateFC(), evaluating the objective function and constraints, setting the constraint values in the function parameter std::vector<double> c and returning the objective function value.

If possible, the user should also

  • pass the number of non-zero elements of the Jacobian to the KTRProblem constructor
  • set the Jacobian sparsity pattern with KTRProblem::setJacIndexCons() and KTRProblem::setJacIndexVars().

If these are not known, a dense pattern will automatically be set, but a sparsity pattern can help solver performance significantly regardless of whether exact first derivatives are implemented or not.

The functions KTRProblem::setXInitial() and KTRProblem::setLambdaInitial() can respectively be used to set values for the initial primal and dual variable values. 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 defined:

  • set variable types with KTRProblem::setVarTypes()
  • set constraint function types with KTRProblem::setConFnTypes()
  • set the objective function type with KTRProblem::setObjFnType()

Implementing derivatives

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

To evaluate first derivatives exactly, the problem class should define double KTRProblem::evaluateGA(), evaluating the gradient and Jacobian and setting their values in the function parameters std::vector<double> objGrad and std::vector<double> jac. The function should return 0 to indicate that no error occurred, or KTR_RC_CALLBACK_ERR can be returned; this return code will stop the Knitro solver.

To evaluate second derivatives exactly, the problem class should:

  • pass the number of non-zero elements of the Hessian to the KTRProblem constructor
  • define the Hessian sparsity pattern with KTRProblem::setHessIndexCols() and KTRProblem::setHessIndexRows().

The problem class should define the function int KTRProblem::evaluateHess() to set the value of the Hessian in the parameter hess. The function should return 0 to indicate that no error or occurred. KTR_RC_CALLBACK_ERR can be returned; this return code will stop the Knitro solver.

To evaluate the Hessian-vector product, the problem class should define the function int KTRProblem::evaluateHessianVector() to set the value of the Hessian-vector product in the parameter vector. The function should return 0 to indicate that no error occurred, or KTR_RC_CALLBACK_ERR can be returned; this return code will stop the Knitro solver.

Complementarity Constraints

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

KTRIProblem::setComplementarity(const std::vector<int>& indexList1,
                          const std::vector<int>& indexList2)

Using the KTRSolver class to solve a problem

Once a problem is defined by inheriting from KTRIProblem or KTRProblem, the KTRSolver 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 KTRSolver class, one of four constructors can be used. Each of the constructors takes at least a pointer to a KTRIProblem object (each KTRSolver object is associated with one problem definition. If different problems are to be solved, multiple KTRSolver objects are needed).

explicit KTRSolver(KTRIProblem * problem);

This constructor should be used when using exact gradient and Hessian evaluation, which must be defined in the KTRIProblem object.

KTRSolver(KTRIProblem * 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 ZLM 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 KTRSolver::setParam(), or by loading a parameters file with KTRSolver::loadParamFile(). Finally, the function KTRSolver::solve() will call Knitro to solve the problem and will return a solution status code.

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

  • KTRSolver::setParam() can be used to change problem parameters, except for the gradient and Hessian evaluation types.
  • Variable bounds can be changed by calling KTRIProblem::setVarLoBnds() and KTRIProblem::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. Note that in the C# interface, function names are capitalized keeping with C# convention.

The majority of the functions are accessed directly through KTRSolver methods, or in the case of the callback setting functions, KTRIProblem methods. There are a few major differences between the callable library functions and the object-oriented interface methods:

  • The callable library methods take a KTR_context_ptr argument (created from a call to KTR_new()), which holds problem information. The object-oriented interface methods do not take this argument, storing the necessary information in the KTRSolver object.
  • The callable library methods return status codes, with a non-zero status code usually indicating an error. The object-oriented interface methods (with the exception of KTRSolver::solve()) do not return status codes. If the methods encounter an error, usually related to an invalid Knitro license or invalid function arguments, a KTRException is thrown.
  • Several callable library methods, such as KTR_get_constraint_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
KTR_new() Not necessary - problem information stored in KTRSolver object.
KTR_new_puts() Not necessary - redirect output by inheriting from KTRPuts class.
KTR_free() Not necessary - problem information stored in KTRSolver object.
KTR_reset_params_to_defaults() KTRSolver::resetParamsToDefaults()
KTR_load_param_file() KTRSolver::loadParamFile()
KTR_save_param_file() KTRSolver::saveParamFile()
KTR_set_int_param_by_name() KTRSolver::setParam()
KTR_set_char_param_by_name() KTRSolver::setParam()
KTR_set_double_param_by_name() KTRSolver::setParam()
KTR_set_int_param() KTRSolver::setParam()
KTR_set_char_param() KTRSolver::setParam()
KTR_set_double_param() KTRSolver::setParam()
KTR_get_int_param_by_name() KTRSolver::getIntParam()
KTR_get_double_param_by_name() KTRSolver::getDoubleParam()
KTR_get_int_param() KTRSolver::getIntParam()
KTR_get_double_param() KTRSolver::getDoubleParam()
KTR_get_param_name() KTRSolver::getParamName()
KTR_get_param_doc() KTRSolver::getParamDoc()
KTR_get_param_type() KTRSolver::getParamType()
KTR_get_num_param_values() KTRSolver::getNumParamValues()
KTR_get_param_value_doc() KTRSolver::getParamValueDoc()
KTR_get_param_id() KTRSolver::getParamID()
KTR_get_release() KTRSolver::getRelease()
KTR_load_tuner_file() KTRSolver::loadTunerFile()
KTR_set_feastols() KTRSolver::setFeastols()
KTR_set_names() KTRSolver::setNames()
KTR_set_compcons() Not necessary - define complementarity constraints in the KTRIProblem class constructor.
KTR_chgvarbnds() Not necessary - change variable bounds in a KTRIProblem object.
KTR_init_problem() Not necessary - problem initialized in KTRSolver constructor.
KTR_solve() KTRSolver::solve()
KTR_restart() KTRSolver::restart()
KTR_mip_init_problem() Not necessary - problem initialized in KTRSolver constructor.
KTR_mip_set_branching_priorities() KTRSolver::mipSetBranchingPriorities()
KTR_mip_solve() KTRSolver::solve()
KTR_set_findiff_relstepsizes() KTRSolver::setFindiffRelstepsizes()
KTR_set_func_callback() Not necessary - define function evaluation in class that inherits from KTRIProblem()
KTR_set_grad_callback() Not necessary - define gradient evaluation in class that inherits from KTRIProblem()
KTR_set_hess_callback() Not necessary - define Hessian evaluation in class that inherits from KTRIProblem()
KTR_set_newpt_callback() KTRIProblem::setNewPointCallback()
KTR_set_ms_process_callback() KTRIProblem::setMSProcessCallback()
KTR_set_mip_node_callback() KTRIProblem::setMipNodeCallback()
KTR_set_ms_initpt_callback() KTRIProblem::setMSInitptCallback()
KTR_set_puts_callback() KTRIProblem::setPutsCallback()
KTR_get_number_FC_evals() KTRSolver::getNumberFCEvals()
KTR_get_number_GA_evals() KTRSolver::getNumberGAEvals()
KTR_get_number_H_evals() KTRSolver::getNumberHEvals()
KTR_get_number_HV_evals() KTRSolver::getNumberHVEvals()
KTR_get_number_iters() KTRSolver::getNumberIters()
KTR_get_number_cg_iters() KTRSolver::getNumberCGIters()
KTR_get_abs_feas_error() KTRSolver::getAbsFeasError()
KTR_get_rel_feas_error() KTRSolver::getRelFeasError()
KTR_get_abs_opt_error() KTRSolver::getAbsOptError()
KTR_get_rel_opt_error() KTRSolver::getRelOptError()
KTR_get_solution() KTRSolver::getSolution()
KTR_get_constraint_values() KTRSolver::getConstraintValues()
KTR_get_objgrad_values() KTRSolver::getObjgradValues()
KTR_get_jacobian_values() KTRSolver::getJacobianValues()
KTR_get_hessian_values() KTRSolver::getHessianValues()
KTR_get_mip_num_nodes() KTRSolver::getMipNumNodes()
KTR_get_mip_num_solves() KTRSolver::getMipNumSolves()
KTR_get_mip_abs_gap() KTRSolver::getMipAbsGap()
KTR_get_mip_rel_gap() KTRSolver::getMipRelGap()
KTR_get_mip_incumbent_obj() KTRSolver::getMipIncumbentObj()
KTR_get_mip_relaxation_bnd() KTRSolver::getMipRelaxationBnd()
KTR_get_mip_lastnode_obj() KTRSolver::getMipLastnodeObj()
KTR_get_mip_incumbent_x() KTRSolver::getMipIncumbentX()
KTR_set_var_scaling() KTRSolver::setVarScaling()
KTR_set_con_scaling() KTRSolver::setConScaling()
KTR_set_obj_scaling() KTRSolver::setObjScaling()
KTR_set_int_var_strategy() KTRSolver::setIntVarStrategy()
No callable library equivalent KTRSolver::setIntegralityRelaxed()
double * obj set by KTR_solve() KTRSolver::getObj()
double * x set by KTR_solve() KTRSolver::getXValues()
double * lambda set by KTR_solve() KTRSolver::getLambdaValues()
KTR_check_first_ders() Deprecated - set derivative check options with KTRSolver::setParam().

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 implemented by extending the appropriate callback class, and passing the callback object to a KTRProblem object via the function KTRProblem::set{Callbacktype} (for some callback type).

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

Below, we show an example of implementing KTRNewptCallback. 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.

The following defines the callback, inheriting from KTRNewptCallback.

#include <iostream>
#include "KTRNewptCallback.h"
#include "KTRSolver.h"

class ExampleNewPointCallback : public knitro::KTRNewptCallback {
 public:

  int CallbackFunction(const std::vector<double>& x, const std::vector<double>& lambda,
                       double obj,
                       const std::vector<double>& c, const std::vector<double>& objGrad,
                       const std::vector<double>& jac,
                       knitro::KTRSolver * solver)
                       {
      int n = x.size();
      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 << "Number FC evals= " << solver->getNumberFCEvals() << std::endl;
      std::cout << "Current feasError= " << solver->getAbsFeasError() << std::endl;

      return 0;
  }
};

To use this callback, it should be passed to the KTRProblem object, before passing it to the KTRSolver constructor. This is shown below. The problem solved is the same example problem solved in previous sections, but the callback defined above is independent of the problem solved.

#include "KTRSolver.h"
#include "ProblemQCQP.h"
#include "ExampleNewPointCallback.h"
#include "ExampleHelpers.h"

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

    ExampleNewPointCallback callback;

    instance.setNewPointCallback(&callback);

    // Create a solver
    knitro::KTRSolver solver(&instance, KTR_GRADOPT_FORWARD, KTR_HESSOPT_BFGS);
    solver.useNewptCallback();

    int solveStatus = solver.solve();

    printSolutionResults(solver, solveStatus);

    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 10.1.0
=======================================

Knitro performing finite-difference gradient computation with 1 thread.
Knitro presolve eliminated 0 variables and 0 constraints.

gradopt:              2
hessopt:              2
newpoint:             3
The problem is identified as a QCQP.
Knitro changing algorithm from AUTO to 1.
Knitro changing bar_initpt from AUTO to 3.
Knitro changing bar_murule from AUTO to 4.
Knitro changing bar_penaltycons from AUTO to 1.
Knitro changing bar_penaltyrule from AUTO to 2.
Knitro changing bar_switchrule from AUTO to 2.
Knitro changing linsolver from AUTO to 2.
Knitro performing finite-difference gradient computation with 1 thread.

Problem Characteristics                    ( Presolved)
-----------------------
Objective goal:  Minimize
Number of variables:                     3 (         3)
        bounded below:                   3 (         3)
        bounded above:                   0 (         0)
        bounded below and above:         0 (         0)
        fixed:                           0 (         0)
        free:                            0 (         0)
Number of constraints:                   2 (         2)
        linear equalities:               1 (         1)
        nonlinear equalities:            0 (         0)
        linear inequalities:             0 (         0)
        nonlinear inequalities:          1 (         1)
        range:                           0 (         0)
Number of nonzeros in Jacobian:          6 (         6)
Number of nonzeros in Hessian:           6 (         6)

  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= 12
Current feasError= 1.01993
>> New point computed by Knitro: (3.86709, 5e-05, 3.71871
Number FC evals= 16
Current feasError= 0.968364
>> New point computed by Knitro: (3.21473, 2.5e-07, 4.34569
Number FC evals= 20
Current feasError= 0.137681
>> New point computed by Knitro: (0.0160737, 7.84537e-08, 7.99343
Number FC evals= 24
Current feasError= 0.0826197
>> New point computed by Knitro: (8.03683e-05, 7.8344e-08, 8.01171
Number FC evals= 28
Current feasError= 0.08261
>> New point computed by Knitro: (4.01842e-07, 7.25596e-08, 8.01118
Number FC evals= 32
Current feasError= 0.078234
>> New point computed by Knitro: (2.00921e-09, 4.9857e-10, 8.00003
Number FC evals= 36
Current feasError= 0.000202022
>> New point computed by Knitro: (9.71398e-10, 3.11247e-10, 8
Number FC evals= 40
Current feasError= 6.53699e-13
>> New point computed by Knitro: (1.65208e-11, 5.31022e-12, 8
Number FC evals= 44
Current feasError= 7.10543e-15
       9    9.360000e+02   7.105e-15   1.374e-07   1.976e-09        0

EXIT: Locally optimal solution found.

Final Statistics
----------------
Final objective value               =   9.36000000000340e+02
Final feasibility error (abs / rel) =   7.11e-15 / 5.47e-16
Final optimality error  (abs / rel) =   1.37e-07 / 8.59e-09
# of iterations                     =          9
# of CG iterations                  =          0
# of function evaluations           =         44
# of gradient evaluations           =          0
Total program time (secs)           =       0.010 (     0.016 CPU time)
Time spent in evaluations (secs)    =       0.010

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

Knitro successful, feasibility violation = 7.10543e-15
KKT optimality violation = 1.37375e-07

Below we give an example of how the KTRSolver::setIntVarStrategy() callback can be used to reformulate an MINLP.

    #include "KTRSolver.h"
    #include "ProblemMINLP.h"
    #include "ExampleHelpers.h"

/**
* An example of loading and solving a MINLP problem.
* Sets MIP parameters using parameter string names to choose the solution algorithm.
*/
int main() {
    // Create a problem instance.
    ProblemMINLP instance;

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

    solver.setParam("mip_method", KTR_MIP_METHOD_BB);
    solver.setParam("algorithm", KTR_ALG_ACT_CG);
    solver.setIntVarStrategy(4, KTR_MIP_INTVAR_STRATEGY_RELAX );
    solver.setIntVarStrategy(5, KTR_MIP_INTVAR_STRATEGY_RELAX );

    int solveStatus = solver.solve();

    printSolutionResults(solver, solveStatus);

    return 0;
    }

These examples and examples of other callbacks can be found in the examples directory.

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, but the only problem characteristics that can be changed between solves are the variable bounds. The object-oriented interface differs from the callable library in how variable bounds are changed. In the object-oriented interface, variable bounds are set in a KTRIProblem object. When KTRSolver::solve() is called to solve the problem, the variable bounds in the KTRIProblem object are passed to the solver. the following example shows changing variable bounds between calls to KTRSolver::solve.

// Create a problem instance.
ProblemQCQP instance;

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

solver.solve();

// changing upper bounds makes previous optimal solution infeasible
instance.setVarUpBnds(7.0);

solver.solve();

In this example, the problem characteristics (including variable bounds) are initialized in the constructor of ProblemQCQP. After the first call of KTRSolver::solve(), the variable bounds are changed in the problem instance with the KTRIProblem::setVarUpBnds() function. This function sets all variable bounds to 7. When solver.solve() is called for a second time, the solve function calls KTRIProblem::getVarLoBnds() and KTRIProblem::getVarUpBnds() and updates them. Note that although other KTRIProblem functions can be called after the KTRIProblem object is passed to the KTRSolver constructor, all changes except variable bounds are ignored. In addition to variable bounds, Knitro parameters except for the gradient and Hessian evaluation type can be changed between calls to solve.

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 ZLM object needs to be created and passed to a KTRSolver object constructor. When the ZLM object is created, a network license will be checked out for use and unavailable for other users until the ZLM 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 ZLM object and the KTRSolver constructor that takes a pointer to the ZLM object.

knitro::ZLM zlm;

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

// Create a solver
knitro::KTRSolver solver(&zlm, &instance);

int solveStatus = solver.solve();

printSolutionResults(solver, solveStatus);