Other programmatic interfaces
This chapter discusses interfaces to C++, C#, Java, Fortran and Python offered by the Knitro callable library.
Knitro in a C++ application
The C++ interface is built on top of the Knitro C API
Calling Knitro from a C++ application follows a similar outline
as a C application, but uses an object-oriented framework.
The Knitro distribution provides many examples of formulating and solving
problems using the object-oriented C++ interface in the directory
examples/C++.
For more details regarding calling Knitro from C++, see
Object-oriented interface reference.
Knitro in a Java application
Calling Knitro from a Java application is similar to using the object-oriented interface in C++. The primary difference between the C++ and Java version of the object-oriented interface is in the syntax. The Java function names are capitalized, and the functions use List<> (implemented as ArrayList<>) for function arguments and return values.
The Java object-oriented interface requires Java 1.8 and up.
The interface uses JNA, a Java native interoperability library, to call the C Knitro callable
library and convert data and function signatures between Java and C,
and uses knitro.h and the knitro.dll dynamic library
(libknitro.so on Linux; libknitro.dylib on Mac OS X).
Examples of problem definitions and Knitro callbacks in Java can be found in the example folders distributed with Knitro, and the source code for the interface is provided for informational purposes.
Knitro in a C# application
Calling Knitro from a C# application is similar to using the
object-oriented interface in C++. The primary difference between the C++
and C# version of the object-oriented interface is in the syntax and function
name capitalization. The C# function names are capitalized, and the functions
use List<> for function arguments and return values.
In the problem evaluation callbacks (KNEvalFCCallback, KNEvalGACallback,
KNEvalHCallback, KNEvalHVCallback, KNEvalRsdCallback, KNEvalRsdJacCallback),
in KNMSInitPtCallback and in KNUserCallback, the functions use IList<>
(ReadOnlyCollection<> for read-only data) for function arguments.
The C# object-oriented interface requires .NET Version 4.0 and up.
The interface uses P/Invoke to call the C Knitro callable library and
convert data and function signatures between C# and C,
and uses knitro.h and the knitro.dll dynamic library.
Examples of problem definitions and Knitro callbacks in C# can be found in the example folders distributed with Knitro, and the source code for the interface is provided for informational purposes.
Knitro in a Fortran application
Calling Knitro from a Fortran application follows the same outline as a C application. The optimization problem must be defined in terms of arrays and constants that follow the C callable library API, and then the Fortran version of the C API functions should be called. Fortran integer and double precision types map directly to C int and double types.
Fortran applications require wrapper functions written in C to (1) isolate the KN_context and CB_context structures, which have no analog in unstructured Fortran, (2) convert C function names into names recognized by the Fortran linker, and (3) possibly renumber array indices to start from zero (the C convention used by Knitro) for applications that follow the Fortran convention of starting from one. Generally the wrapper functions can be called from Fortran with exactly the same arguments as their C language counterparts, except for the omission of any KN_context and CB_context arguments.
Several example Fortran programs and a set of C wrappers are provided in
examples/Fortran.
The C wrappers provided allow the user to specify either 0-based or 1-based
array indexing. When using 1-based indexing, the C wrappers will automatically
make the conversion to 0-based indexing as required by the Knitro API.
The provided C wrappers cover most of the important API functions available
in Knitro, but do not currently implement all the functionality of the Knitro
callable library. Users are free to write their own C wrapper routines, or
extend the example wrappers as needed.
Knitro in a Python application
See Getting started with Python for a quick introduction to Knitro Python
and the recommended Problem class interface.
In addition to the Problem class, Knitro provides two alternative
Python interfaces for advanced use cases.
The knitro.optimize() single call interface
The knitro.optimize() method provides a single call interface that can be
used for large-scale models where API performance is critical.
from knitro import *
# Define the variables information
variables = Variables(nV=4, xLoBnds=[0,0,0,0])
# Define the objective information
# Default objGoal is set to 'minimize'
objective = Objective(objLinear=[[0, 1], [-4, -2]])
# Define the constraints information
constraints = Constraints(nC=2,
cLinear=[[0, 0, 0, 1, 1, 1],
[0, 1, 2, 0, 1, 3],
[1., 1., 1., 2., 0.5, 1.]],
cEqBnds=[5., 8.])
# Solve the problem
solution = optimize(variables=variables,
objective=objective,
constraints=constraints)
The Python callable library
The Python callable library provides direct access to the Knitro C API functions
defined in knitro.h. It should be reserved for advanced use cases requiring
fine-grained control over the solver or access to low-level features such as custom
callbacks.
The Python API loads directly the Knitro library (knitro.dll on Windows;
libknitro.so on Unix; libknitro.dylib on Mac OS X). With this interface, Python
applications can create a Knitro solver instance and call Python methods that execute the
corresponding Knitro functions. The Python form of Knitro is thread-safe, which means that a
Python application can create multiple instances of a Knitro solver in different threads, each
instance solving a different problem. However, please note that Python interpreters are usually not
thread safe so callbacks cannot be evaluated in parallel. Thus concurrent_evals is
always initialized to 0/no in the Python interface, but may still be set to 1/yes by the user.
Calling Knitro from a Python application follows the same outline as a C application, with the same methods. C int and double types are automatically mapped into their Python counterparts (int and float). C arrays are automatically mapped into Python list types. C pointers to native C types are automatically mapped into the corresponding Python native type. Methods that accept NULL values in C also accept None values in Python.
A typical sequence of function calls is as follows:
KN_new(): create a new Knitro solver context pointer, allocating resources.KN_add_vars()/KN_add_cons()/KN_set_*bnds(): add basic problem information to Knitro.KN_add_*_linear_struct()/KN_add_*_quadratic_struct(): add specific problem structures.KN_add_eval_callback(): add callback for nonlinear evaluations if needed.KN_set_cb_*(): set properties for nonlinear evaluation callbacks.KN_set_*_param(): set user options/parameters.KN_solve(): solve the problem.KN_free(): delete the Knitro context pointer, releasing allocated resources.
A major difference between the C and Python APIs is the handling of function return codes.
In C, Knitro functions always return an integer code, which is 0 in case of success and non-zero
in case of error. C users have to check the return code to make sure that each function was executed
properly.
In Python, Knitro functions raise Python errors in case of failure to execute the function. Some
functions do not return anything (such as KN_set_*_param()) and some return the expected
output (such as KN_get_*_param()).
Python functions can be provided as callbacks for Knitro as long as they follow
the corresponding callback function prototypes (defined in knitro.h).
Although the Python language makes it unnecessary, Python objects may be passed to the
callback function through the userParams argument.
The following example solves a simple linear problem using the callable library:
from knitro import *
# Create a new Knitro solver instance.
try:
kc = KN_new ()
except:
print ("Failed to find a valid license.")
quit ()
# Illustrate how to override default options by reading from
# the knitro.opt file.
KN_load_param_file (kc, "knitro.opt")
# Initialize Knitro with the problem definition.
# Add the variables and set their bounds.
# Note: unset bounds assumed to be infinite.
xIndices = KN_add_vars (kc, 4)
for x in xIndices:
KN_set_var_lobnds (kc, x, 0.0)
# Add the constraints and set the rhs and coefficients.
KN_add_cons(kc, 2)
KN_set_con_eqbnds (kc, cEqBnds = [5, 8])
# Add Jacobian structure and coefficients.
# First constraint
jacIndexCons = [0, 0, 0]
jacIndexVars = [0, 1, 2]
jacCoefs = [1.0, 1.0, 1.0]
# Second constraint
jacIndexCons += [1, 1, 1]
jacIndexVars += [0, 1, 3]
jacCoefs += [2.0, 0.5, 1.0]
KN_add_con_linear_struct (kc, jacIndexCons, jacIndexVars, jacCoefs)
# Set minimize or maximize (if not set, assumed minimize).
KN_set_obj_goal (kc, KN_OBJGOAL_MINIMIZE)
# Set the coefficients for the objective.
objIndices = [0, 1]
objCoefs = [-4.0, -2.0]
KN_add_obj_linear_struct (kc, objIndices, objCoefs)
# Solve the problem.
# Return status codes are defined in "knitro.py" and described in the Knitro manual.
nStatus = KN_solve (kc)
print ("Knitro converged with final status = %d" % nStatus)
# An example of obtaining solution information.
nStatus, objSol, x, lambda_ = KN_get_solution (kc)
print (" optimal objective value = %e" % objSol)
print (" optimal primal values x = (%e, %e, %e, %e)" % (x[0], x[1], x[2], x[3]))
print (" feasibility violation = %e" % KN_get_abs_feas_error (kc))
print (" KKT optimality violation = %e" % KN_get_abs_opt_error (kc))
# Delete the Knitro solver instance.
KN_free (kc)