13. Calling a solver from C or C++¶
A generated FORCESPRO solver comes with a C interface and a C library, allowing the user to call the solver directly from C or C++. This enables direct and efficient integration of a FORCESPRO solver into existing software, and is the most portable way of deploying a solver to any supported hardware platform. This section describes how to call the solver from C, and how to compile your C program. The procedure to deploy to a target platform is described in section Main Targets.
13.1. Generating a solver library¶
For generating a solver to be used in C, the following options are relevant:
codeoptions.platform = '<platform_name>'; % to specify the target platform
codeoptions.printlevel = 0; % optional, on some platforms printing is not supported
codeoptions.cleanup = 0; % to keep auxiliary source files (not needed for low-level interface)
codeoptions.platform = '<platform_name>' # to specify the target platform
codeoptions.printlevel = 0 # optional, on some platforms printing is not supported
codeoptions.cleanup = 0 # to keep auxiliary source files (not needed for low-level interface)
After having generated a solver with name codeoptions.name =
'myFORCESsolver'
, the following C libraries are made available for host and
target systems:
For the host system:
myFORCESsolver ├── include ├── lib
For the target system:
myFORCESsolver ├── include ├── lib_target
For most platforms (see Target platform), lib
and lib_target
contain both static and
shared/dynamic library files.
For the high-level interface you’ll receive auxiliary sources after solver generation which contain the nonlinear functions of your formulation (if not provided externally). These are generated as follows:
From MATLAB, the following auxiliary sources will be generated:
myFORCESsolver ├── myFORCESsolver_adtool2forces.c ├── myFORCESsolver_casadi.c ├── myFORCESsolver_casadi.h
From Python, the following auxiliary sources will be generated:
myFORCESsolver ├── myFORCESsolver_interface.c ├── myFORCESsolver_model.c ├── myFORCESsolver_model.h
13.2. C interface¶
The following code snippets demonstrate how to use the C interface of any
generated solver (for simplicity, we assume that the solver has been generated
with codeoptions.name = 'myFORCESsolver'
). The C variables params
(solver
input), output
(solver output) and info
(solver diagnostics) are C
structs whose definitions depend on the problem formulation. Their exact
definitions can be found in the solver header myFORCESsolver.h
.
Low-level interface
#include "myFORCESsolver/include/myFORCESsolver.h" /* solver symbols definition */
#include "myFORCESsolver/include/myFORCESsolver_memory.h" /* handle to internal solver memory */
int main()
{
int exitflag;
myFORCESsolver_params params; /* struct for run-time parameters, see myFORCESsolver.h */
myFORCESsolver_output output; /* struct for solver output, see myFORCESsolver.h */
myFORCESsolver_info info; /* struct for solver diagnostics, see myFORCESsolver.h */
myFORCESsolver_mem * mem; /* handle to solver memory, from myFORCESsolver_memory.h */
mem = myFORCESsolver_internal_mem(0);
/* omitted: assign params field */
/* solver call:
* - in: params
* - out: exitflag, output, info
* - 5th argument: optional file pointer for printing, NULL means stdout
*/
exitflag = myFORCESsolver_solve(¶ms, &output, &info, mem, NULL);
}
High-level interface
#include "myFORCESsolver/include/myFORCESsolver.h" /* solver symbols definition */
#include "myFORCESsolver/include/myFORCESsolver_memory.h" /* handle to internal solver memory */
/* AD tool - FORCESPRO interface
* Note: if you use your own external C functions, replace 'myFORCESsolver_adtool2forces'
* with your main callback function name
*/
extern solver_int32_default myFORCESsolver_adtool2forces(
myFORCESsolver_float *x, /* primal vars */
myFORCESsolver_float *y, /* eq. constraint multiplers */
myFORCESsolver_float *l, /* ineq. constraint multipliers */
myFORCESsolver_float *p, /* parameters */
myFORCESsolver_float *f, /* objective function (scalar) */
myFORCESsolver_float *nabla_f, /* gradient of objective function */
myFORCESsolver_float *c, /* dynamics */
myFORCESsolver_float *nabla_c, /* Jacobian of the dynamics (column major) */
myFORCESsolver_float *h, /* inequality constraints */
myFORCESsolver_float *nabla_h, /* Jacobian of inequality constraints (column major) */
myFORCESsolver_float *hess, /* Hessian (column major) */
solver_int32_default stage, /* stage number (0 indexed) */
solver_int32_default iteration, /* iteration number of solver */
solver_int32_default threadID /* Id of caller thread */);
int main()
{
int exitflag;
myFORCESsolver_params params; /* struct for run-time parameters, see myFORCESsolver.h */
myFORCESsolver_output output; /* struct for solver output, see myFORCESsolver.h */
myFORCESsolver_info info; /* struct for solver diagnostics, see myFORCESsolver.h */
myFORCESsolver_mem * mem; /* handle to solver memory, from myFORCESsolver_memory.h */
/* handle to external function interface */
myFORCESsolver_extfunc extfunc_eval = &myFORCESsolver_adtool2forces;
/* get handle to solver memory, from myFORCESsolver_memory.h */
mem = myFORCESsolver_internal_mem(0);
/* omitted: assign params field */
/* solver call:
* - in: params
* - out: exitflag, output, info
* - 5th argument: optional file pointer for printing, NULL means stdout
*/
exitflag = myFORCESsolver_solve(¶ms, &output, &info, mem, NULL, extfunc_eval);
}
Note
More advanced use cases of the C interface are treated in the sections Memory allocations (for controlling memory allocations) and External parallelism (for concurrency and thread-safety aspects).
13.3. Compiling solver executable¶
In order to create a standalone executable, all user-provided and generated
auxiliary source files need to be compiled and linked against the solver
library. The full command for compiling a solver looks as follows (assuming you
are in the directory which contains the myFORCESsolver
folder):
For the low-level interface
<Compiler> <user sources> <solver library> <additional libraries>
For the high-level interface
<Compiler> <user sources> myFORCESsolver_adtool2forces.c myFORCESsolver_casadi.c <solver library> <additional libraries>
<Compiler> <user sources> myFORCESsolver_interface.c myFORCESsolver_model.c <solver library> <additional libraries>
Where:
<Compiler>
would be the compiler executable<user sources>
would be your sources calling the solver (see C interface)<solver library>
would be one of the solver library files fromlib
orlib_target
directory (see Generating a solver library)<additional libraries>
would be possible libraries that need to be linked to resolve existing dependenciesFor Linux/MacOS it’s usually necessary to link the math library (
-lm
)For Windows you usually need to link the
iphlpapi.lib
library (it’s distributed with the Intel Compiler, MinGW as well as MATLAB) and unless you’re using MinGW some additional intel libraries (those are included in the FORCESPRO client under the folderlibs_Intel
– if missing they are downloaded after code generation)
13.3.1. Compiling with C++¶
Compiling a FORCESPRO solver with a C++ compiler is straightforward: Simply
use the C interface as described in section C interface along
with the extern "C"
syntax to ensure the C linkage conventions are used.
A complete example of compiling a nonlinear FORCESPRO solver with a C++
compiler using CMake can be found in the
examples\StandaloneExecution\Cpp
folder that comes with your client. In
order to run this example, do the following:
Execute
OverheadCrane.m
in MATLAB to generate a FORCESPRO solver calledCraneSolver
(uncomment line 96 to generate a solver for Linux).Run
cmake .
Run
cmake --build .
An executable
CraneSolver_standalone
should be available, feel free to run it.
13.4. Memory allocations¶
The C interface provides some flexibility in how the solver memory is allocated:
internal memory: memory buffer is statically allocated inside the solver library
external memory: the user is responsible for allocating the memory buffer
The internal memory option is easier to use and thus the default way of interfacing the solver in C. The external memory option is recommended for users who want full control over memory allocation (static or dynamic), or who require multiple memory buffers (e.g. for running a solver in parallel, see External parallelism).
Henceforth, we assume a generated solver named FORCESNLPsolver
and
demonstrate how to use external and internal memory buffers. We assume the
reader is already acquainted with the C interface (see section
C interface).
You can find the full code for working examples for the internal and external
memory interfaces including instructions on how to run them in the
examples\StandaloneExecution\C
folder that comes with your client.
13.4.1. Internal memory¶
If you don’t need control over the memory buffer, the internal memory C interface is the recommended way of calling a generated solver in C:
/* additional header for internal memory functionality */
#include "FORCESNLPsolver/include/FORCESNLPsolver_memory.h"
/* handle to the solver memory */
FORCESNLPsolver_mem * mem_handle;
/* Get i-th memory buffer */
int i = 0;
mem_handle = FORCESNLPsolver_internal_mem(i);
/* Note: number of available memory buffers is controlled by code option max_num_mem */
/* check that memory is in valid state: */
if (mem_handle == NULL)
{
/* this happens if i >= max_num_mem */
return 1;
}
exit_code = FORCESNLPsolver_solve(..., mem_handle, ...)
By default, one memory buffer is available (max_num_mem = 1
). For further
information on the code option max_num_mem
, see Table 13.1.
13.4.2. External memory¶
The following code sample demonstrates the use of external memory with dynamic allocation:
/* memory buffer allocated by the user of type char (representing bytes) */
char * mem;
/* handle to the solver memory */
FORCESNLPsolver_mem * mem_handle;
/* required memory size in bytes */
size_t mem_size = FORCESNLPsolver_get_mem_size();
/* dynamically allocate memory buffer */
mem = malloc(mem_size);
/* cast memory buffer to solver memory
* note: i can be set to 0 if no thread safety required */
int i = 0;
mem_handle = FORCESNLPsolver_external_mem(mem, i, mem_size);
/* check that memory is in valid state: */
if (mem_handle == NULL)
{
return 1;
}
exit_code = FORCESNLPsolver_solve(..., mem_handle, ...)
/* free user-allocated memory */
free(mem);
For static allocation, the memory size MEM_SIZE
needs to be set at compile time:
/* memory size in bytes s.t. MEM_SIZE >= FORCESNLPsolver_get_mem_size(): */
#define MEM_SIZE 12345
/* statically allocated memory buffer of size MEM_SIZE bytes */
static char mem[MEM_SIZE];
/* cast buffer to solver memory */
mem_handle = FORCESNLPsolver_external_mem(mem, 0, MEM_SIZE);
/* check that memory is in valid state: */
if (mem_handle == NULL)
{
/* this happens if MEM_SIZE < FORCESNLPsolver_get_mem_size() */
return 1;
}
The minimum required MEM_SIZE
is system and compiler dependent. It can be
easily obtained by compiling
FORCESNLPsolver\interface\FORCESNLPsolver_get_mem_size.c
and by linking
against the solver library on the target device. The output of the obtained
executable is equal to the value returned by FORCESNLPsolver_get_mem_size()
.
For memory-critical systems, we advise to disable the internal memory buffer by setting (see also Table 13.1)
codeoptions.max_num_mem = 0;
codeoptions.max_num_mem = 0
Otherwise, the solver library still contains one internal memory buffer required by the client to run the solver.
13.4.4. Obtaining memory size¶
The required memory size for a solver can be obtained by calling the two utility functions provided by the solver library:
size_t FORCESNLPsolver_get_mem_size( void );
size_t FORCESNLPsolver_get_const_size( void );
These functions return the memory size for all non-const / const variables,
respectively. This information is also printed in the solver output if
printlevel = 2
. Note that the memory size depends on the system and
compiler. The actual memory footprint might be larger as reported by these
functions since they account only for the memory to store data (data or
bss segment in the binary), and not for the full binary size.
The size returned by FORCESNLPsolver_get_mem_size
refers to an internal or
external memory buffer. The size returned by FORCESNLPsolver_get_const_size
refers to additional constant memory that is not exposed to the user. To obtain the
total memory size of a solver that is called in parallel, the size of the
memory buffer must be multiplied with the number of threads: total_size =
NUM_THREADS * FORCESNLPsolver_get_mem_size() +
FORCESNLPsolver_get_const_size()
.