Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ jobs:
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-buildpkg@v1
- name: Native C ABI smoke test
if: runner.os == 'Linux'
run: bash test/native_c_api_smoke.sh
- uses: julia-actions/julia-runtest@v1
# - uses: julia-actions/julia-processcoverage@v1
# - uses: codecov/codecov-action@v2
# with:
# file: lcov.info
# file: lcov.info
65 changes: 65 additions & 0 deletions c_api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# MQLib C ABI

This directory defines the native C ABI for solving QUBO instances through
MQLib without invoking the command-line executable.

The files here are intended to be compiled with the upstream MQLib C++ sources
and packaged as a shared library by `MQLib_jll`. The Julia wrapper does not call
this ABI yet; that integration belongs to the follow-up shared-library issue.

## API

Include `c_api/include/mqlib_c_api.h` from C, C++, or Julia `ccall` bindings.
The public ABI exposes only C scalars, pointers, and structs:

- `MQLibCQUBOInput` passes the problem dimension, linear coefficients, sparse
off-diagonal quadratic coordinate arrays, heuristic selection, runtime limit,
and random seed.
- `MQLibCQUBOResult` passes caller-owned buffers for the objective value,
binary solution vector, runtime, selected heuristic, and optional incumbent
history.
- `mqlib_solve_qubo` returns a stable `MQLibCStatus` code. It does not transfer
ownership of any caller buffer.

The wrapper validates ABI inputs before constructing MQLib C++ objects and
reports those failures through status codes. Unrecoverable exits inside
upstream heuristic internals are still upstream behavior until those internals
are made status-code aware.

Quadratic coordinates may be zero-based or one-based, selected by
`index_base`. Diagonal terms belong in `linear`; quadratic arrays must contain
only off-diagonal entries. MQLib interprets off-diagonal entries as the upper
triangle of the symmetric QUBO matrix, matching the existing MQLib `.qubo` file
format.

Set `heuristic` to a QUBO heuristic code such as `ALKHAMIS1998` to run that
heuristic. Set it to `NULL` or an empty string to run the hyperheuristic path.
For hyperheuristic runs, set `hyperheuristic_data_dir` to the directory
containing MQLib's random-forest model files, usually the upstream `hhdata`
directory. If no model files are found, `mqlib_solve_qubo` returns
`MQLIB_STATUS_HYPERHEURISTIC_DATA_NOT_FOUND`.

## Build Sketch

The exact build recipe belongs in `MQLib_jll`, but a local syntax check against
an upstream MQLib checkout looks like:

```sh
c++ -std=c++11 \
-Ic_api/include \
-I/path/to/MQLib/include \
-fsyntax-only \
c_api/src/mqlib_c_api.cpp
```

When building the shared library, compile `c_api/src/mqlib_c_api.cpp` together
with the upstream MQLib implementation sources needed by the heuristics. The
upstream executable entry point (`src/main.cpp`) should not be part of the
shared-library target.

The small native example in `c_api/examples/solve_qubo.c` demonstrates both an
explicit QUBO heuristic and the hyperheuristic call shape:

```sh
./solve_qubo /path/to/MQLib/hhdata
```
75 changes: 75 additions & 0 deletions c_api/examples/solve_qubo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include <stdio.h>

#include "mqlib_c_api.h"

static int solve_with_heuristic(const char *heuristic, const char *hhdata_dir) {
const double linear[3] = {5.0, 3.0, 1.0};
const int32_t quadratic_i[2] = {0, 1};
const int32_t quadratic_j[2] = {1, 2};
const double quadratic_value[2] = {-6.0, -1.0};

int32_t solution[3] = {0, 0, 0};
char selected_heuristic[64];
double history_values[32];
double history_times[32];

MQLibCQUBOInput input = {
MQLIB_C_ABI_VERSION,
3,
linear,
2,
quadratic_i,
quadratic_j,
quadratic_value,
MQLIB_C_INDEX_BASE_ZERO,
heuristic,
0.01,
1234,
hhdata_dir
};

MQLibCQUBOResult result = {
MQLIB_C_ABI_VERSION,
0.0,
0.0,
solution,
3,
selected_heuristic,
(int32_t)sizeof(selected_heuristic),
history_values,
history_times,
32,
0
};

const int status = mqlib_solve_qubo(&input, &result);
if (status != MQLIB_STATUS_OK) {
fprintf(stderr, "MQLib failed: %s\n", mqlib_c_status_message(status));
return status;
}

printf(
"%s objective %.15g solution %d %d %d runtime %.6f seconds\n",
result.selected_heuristic,
result.objective_value,
result.solution[0],
result.solution[1],
result.solution[2],
result.runtime_seconds
);
return MQLIB_STATUS_OK;
}

int main(int argc, char **argv) {
int status = solve_with_heuristic("ALKHAMIS1998", NULL);
if (status != MQLIB_STATUS_OK) {
return status;
}

if (argc < 2) {
fprintf(stderr, "Pass the path to MQLib's hhdata directory to run the hyperheuristic example.\n");
return 2;
}

return solve_with_heuristic(NULL, argv[1]);
}
107 changes: 107 additions & 0 deletions c_api/include/mqlib_c_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#ifndef MQLIB_C_API_H
#define MQLIB_C_API_H

#include <stdint.h>

#define MQLIB_C_ABI_VERSION 1u

#define MQLIB_C_INDEX_BASE_ZERO 0
#define MQLIB_C_INDEX_BASE_ONE 1

#if defined(_WIN32) && defined(MQLIB_C_BUILD_SHARED)
#define MQLIB_C_API __declspec(dllexport)
#elif defined(_WIN32) && defined(MQLIB_C_USE_SHARED)
#define MQLIB_C_API __declspec(dllimport)
#else
#define MQLIB_C_API
#endif

#ifdef __cplusplus
extern "C" {
#endif

typedef enum MQLibCStatus {
MQLIB_STATUS_OK = 0,
MQLIB_STATUS_ABI_VERSION_MISMATCH = 1,
MQLIB_STATUS_INVALID_ARGUMENT = 2,
MQLIB_STATUS_INVALID_HEURISTIC = 3,
MQLIB_STATUS_BUFFER_TOO_SMALL = 4,
MQLIB_STATUS_ALLOCATION_FAILED = 5,
MQLIB_STATUS_INTERNAL_ERROR = 6,
MQLIB_STATUS_HYPERHEURISTIC_DATA_NOT_FOUND = 7
} MQLibCStatus;

typedef struct MQLibCQUBOInput {
uint32_t abi_version;

int32_t dimension;
const double *linear;

int64_t quadratic_count;
const int32_t *quadratic_i;
const int32_t *quadratic_j;
const double *quadratic_value;
int32_t index_base;

/*
* NULL or an empty string runs the MQLib hyperheuristic. Otherwise this
* must be a valid MQLib QUBO or Max-Cut heuristic code.
*/
const char *heuristic;

double runtime_limit_seconds;
int32_t random_seed;

/*
* Optional path to the directory containing the hyperheuristic .rf model
* files. Used only when heuristic is NULL or empty. If this is NULL or
* empty, mqlib_solve_qubo looks for ./hhdata relative to the caller's
* current working directory.
*/
const char *hyperheuristic_data_dir;
} MQLibCQUBOInput;

typedef struct MQLibCQUBOResult {
uint32_t abi_version;

double objective_value;
double runtime_seconds;

/*
* Caller-owned buffer with capacity in solution_length on input. The
* required length is written back on output.
*/
int32_t *solution;
int32_t solution_length;

/*
* Optional caller-owned buffer. selected_heuristic_length is the byte
* capacity on input and the required byte count, including NUL, on output.
*/
char *selected_heuristic;
int32_t selected_heuristic_length;

/*
* Optional caller-owned buffers for objective history. history_capacity is
* the input capacity of both arrays. history_length is the required entry
* count on output. If the capacity is too small, the prefix that fits is
* copied and MQLIB_STATUS_BUFFER_TOO_SMALL is returned.
*/
double *history_objective_values;
double *history_times_seconds;
int32_t history_capacity;
int32_t history_length;
} MQLibCQUBOResult;

MQLIB_C_API int mqlib_c_abi_version(void);
MQLIB_C_API const char *mqlib_c_status_message(int status);
MQLIB_C_API int mqlib_solve_qubo(
const MQLibCQUBOInput *input,
MQLibCQUBOResult *result
);

#ifdef __cplusplus
}
#endif

#endif
Loading
Loading