Examples
This page shows practical C++ usage patterns for Minion.
Reference files in this repository:
examples/main_minimizer.cppexamples/main_cec.cpptests/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 atx.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
DefaultSettingsas 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.