Examples

This page shows practical C++ usage patterns for Minion.

Reference files in this repository:

  • examples/main_minimizer.cpp

  • examples/main_cec.cpp

  • tests/test_minion.cpp

Basic Pattern

Minion expects a vectorized objective:

std::vector<double> objective(const std::vector<std::vector<double>>& X, void* data) {
    std::vector<double> out(X.size(), 0.0);
    for (size_t i = 0; i < X.size(); ++i) {
        const auto& x = X[i];
        // compute f(x)
        out[i] = /* ... */;
    }
    return out;
}

Then call minion::Minimizer:

std::vector<std::pair<double, double>> bounds(dim, {-5.0, 5.0});
std::vector<double> x0(dim, 0.0);
std::string algo = "LSHADE";
auto settings = minion::DefaultSettings().getDefaultSettings(algo);

minion::MinionResult res = minion::Minimizer(
    objective, bounds, x0, nullptr, nullptr, algo, 0.0, 100000, 42, settings
).optimize();

Understanding MinionResult

optimize() returns minion::MinionResult with these fields:

  • x: best decision vector found.

  • fun: objective value at x.

  • nit: number of iterations/generations completed.

  • nfev: number of objective evaluations.

  • success: solver status flag.

  • message: termination message (if provided by the algorithm).

Example:

minion::MinionResult res = minion::Minimizer(
    objective, bounds, x0, nullptr, nullptr, "ARRDE", 0.0, 100000, 42
).optimize();

std::cout << "success: " << std::boolalpha << res.success << "\n";
std::cout << "fun: " << res.fun << "\n";
std::cout << "nit: " << res.nit << ", nfev: " << res.nfev << "\n";
std::cout << "x_best[0]: " << (res.x.empty() ? 0.0 : res.x[0]) << "\n";
if (!res.message.empty()) {
    std::cout << "message: " << res.message << "\n";
}

Using Callback

You can pass a callback to monitor progress after each iteration:

void progress_callback(minion::MinionResult* state) {
    std::cout << "iter=" << state->nit
              << " nfev=" << state->nfev
              << " best=" << state->fun << "\n";
}

minion::MinionResult res = minion::Minimizer(
    objective, bounds, x0, nullptr, progress_callback, "LSHADE", 0.0, 100000, 42
).optimize();

If you need custom early stopping, a practical pattern is to throw from the callback when your condition is met, then catch outside:

struct StopNow : public std::exception {
    const char* what() const noexcept override { return "user stop"; }
};

void early_stop_callback(minion::MinionResult* state) {
    if (state->nfev >= 20000 || state->fun < 1e-8) {
        throw StopNow();
    }
}

try {
    auto res = minion::Minimizer(
        objective, bounds, x0, nullptr, early_stop_callback, "ARRDE", 0.0, 100000, 42
    ).optimize();
    (void)res;
} catch (const StopNow&) {
    std::cout << "Optimization stopped by user callback.\n";
}

Override Default Options

Start from defaults, then override only what you need.

std::string algo = "DE";
auto settings = minion::DefaultSettings().getDefaultSettings(algo);

// Common overrides
settings["population_size"] = 80;
settings["bound_strategy"] = std::string("reflect-random");

// Algorithm-specific overrides
if (algo == "DE") {
    settings["mutation_rate"] = 0.7;
    settings["crossover_rate"] = 0.9;
    settings["mutation_strategy"] = std::string("current_to_pbest1bin");
}

minion::MinionResult res = minion::Minimizer(
    objective, bounds, x0, nullptr, nullptr, algo, 0.0, 100000, 42, settings
).optimize();

Notes:

  • Option keys are algorithm-specific. Use DefaultSettings as the source of valid keys.

  • Keep value types consistent with the expected type (int, double, std::string, bool).

Multithreading Objective Evaluation

Minion calls your objective in batches (X). Parallelization for C++ workflows is typically implemented inside your objective function.

1) Standalone Function (Thread-Safe)

If each evaluation is independent, parallelize the loop over X.

#include <omp.h>

double sphere(const std::vector<double>& x) {
    double s = 0.0;
    for (double v : x) s += v * v;
    return s;
}

std::vector<double> sphere_batch(const std::vector<std::vector<double>>& X, void*) {
    std::vector<double> out(X.size(), 0.0);
    #pragma omp parallel for
    for (int i = 0; i < static_cast<int>(X.size()); ++i) {
        out[i] = sphere(X[i]);
    }
    return out;
}

2) Non-Thread-Safe Class Method

If the class has mutable shared state, protect access.

#include <mutex>

class Model {
public:
    double eval(const std::vector<double>& x) {
        // touches mutable shared state internally
        return /* ... */;
    }
};

struct ModelData {
    Model* model;
    std::mutex* mtx;
};

std::vector<double> model_batch(const std::vector<std::vector<double>>& X, void* data) {
    auto* md = static_cast<ModelData*>(data);
    std::vector<double> out(X.size(), 0.0);
    #pragma omp parallel for
    for (int i = 0; i < static_cast<int>(X.size()); ++i) {
        std::lock_guard<std::mutex> lock(*md->mtx);
        out[i] = md->model->eval(X[i]);
    }
    return out;
}

If possible, prefer a re-entrant/stateless evaluator per thread to avoid lock contention.

3) Lambda Function

Use a lambda and assign it to minion::MinionFunction.

minion::MinionFunction objective = [](const std::vector<std::vector<double>>& X, void*) {
    std::vector<double> out(X.size(), 0.0);
    #pragma omp parallel for
    for (int i = 0; i < static_cast<int>(X.size()); ++i) {
        const auto& x = X[i];
        double f = 0.0;
        for (size_t j = 0; j + 1 < x.size(); ++j) {
            const double a = x[j + 1] - x[j] * x[j];
            const double b = 1.0 - x[j];
            f += 100.0 * a * a + b * b;
        }
        out[i] = f;
    }
    return out;
};

CEC Functions

Include both headers:

#include <minion.h>
#include <minion_cec.h>

Wrap CEC evaluator into Minion objective signature:

std::vector<double> cec2017_batch(const std::vector<std::vector<double>>& X, void* data) {
    auto* cec = static_cast<minion::CECBase*>(data);
    return (*cec)(X);
}

Example (CEC2017, F1, dimension 30):

const int dim = 30;
minion::CEC2017Functions cec_f1(1, dim);
std::vector<std::pair<double, double>> bounds(dim, {-100.0, 100.0});
std::vector<double> x0(dim, 0.0);

auto settings = minion::DefaultSettings().getDefaultSettings("ARRDE");
minion::MinionResult res = minion::Minimizer(
    cec2017_batch, bounds, x0, &cec_f1, nullptr, "ARRDE", 0.0, 30000, 20250306, settings
).optimize();

For a larger CEC sweep (multiple functions and algorithms), see tests/test_minion.cpp. For broader CEC benchmark utilities and bounds handling, see examples/main_cec.cpp.