Skip to content

Rust

givp is also available as a native Rust crate, providing the same GRASP-ILS-VND with Path Relinking algorithm with an idiomatic, zero-overhead Rust API.

Installation

From a local clone of the repository:

cd rust
cargo build

Requires Rust ≥ 1.85 (stable channel).

Add to your own project's Cargo.toml:

[dependencies]
givp = { path = "../rust" }

Optional JSON serialisation (via serde):

[dependencies]
givp = { path = "../rust", features = ["serde"] }

Quick start

use givp::{givp, GivpConfig};

fn sphere(x: &[f64]) -> f64 {
    x.iter().map(|v| v * v).sum()
}

fn main() {
    let bounds = vec![(-5.12, 5.12); 10];
    let cfg = GivpConfig {
        seed: Some(42),
        integer_split: Some(10), // all continuous
        ..Default::default()
    };
    let result = givp(sphere, &bounds, cfg).unwrap();
    println!("{:?}", result.x);   // best vector found
    println!("{}", result.fun);   // best objective value
    println!("{}", result.nfev);  // number of evaluations
}

Maximize

use givp::{givp, Direction, GivpConfig};

let cfg = GivpConfig {
    direction: Direction::Maximize,
    seed: Some(42),
    integer_split: Some(5),
    ..Default::default()
};
let result = givp(my_score, &bounds, cfg).unwrap();

Configuration

All hyper-parameters are exposed via the GivpConfig struct:

use givp::GivpConfig;

let cfg = GivpConfig {
    max_iterations: 50,
    vnd_iterations: 100,
    ils_iterations: 10,
    elite_size: 7,
    adaptive_alpha: true,
    time_limit: 30.0,
    seed: Some(42),
    integer_split: Some(10), // number of continuous variables
    ..Default::default()
};

Warm start

let cfg = GivpConfig {
    initial_guess: Some(vec![1.0; 5]),
    integer_split: Some(5),
    ..Default::default()
};
let result = givp(rosenbrock, &bounds, cfg).unwrap();

Mixed continuous/integer problems

let n_cont = 12usize;
let n_int  = 8usize;
let bounds: Vec<(f64, f64)> = (0..n_cont)
    .map(|_| (-5.0_f64, 5.0_f64))
    .chain((0..n_int).map(|_| (0.0_f64, 4.0_f64)))
    .collect();

let cfg = GivpConfig {
    integer_split: Some(n_cont),
    ..Default::default()
};
let result = givp(my_objective, &bounds, cfg).unwrap();

Parallel candidate evaluation (rayon)

let cfg = GivpConfig {
    n_workers: 4,          // rayon thread pool size
    use_cache: false,      // cache is not thread-safe; must be disabled
    integer_split: Some(10),
    ..Default::default()
};
let result = givp(sphere, &bounds, cfg).unwrap();

Result struct

givp returns Result<OptimizeResult, GivpError> with the following fields:

Field Type Meaning
x Vec<f64> Best solution vector.
fun f64 Objective value at x (user's original sign).
nit usize GRASP outer iterations executed.
nfev usize Number of objective evaluations.
success bool true when at least one feasible solution found.
message String Human-readable termination reason.
direction Direction Minimize or Maximize.
termination TerminationReason Typed termination reason enum.
meta HashMap<String, String> Algorithm-specific extras.

Error handling

use givp::GivpError;

match givp(sphere, &bounds, cfg) {
    Ok(r) => println!("best = {}", r.fun),
    Err(GivpError::InvalidBounds(msg))      => eprintln!("bounds: {msg}"),
    Err(GivpError::InvalidConfig(msg))      => eprintln!("config: {msg}"),
    Err(GivpError::InvalidInitialGuess(msg))=> eprintln!("guess: {msg}"),
    Err(e) => eprintln!("error: {e}"),
}

Running tests

cd rust
cargo test

With property-based tests (proptest, 64 cases per property):

cargo test          # proptest runs automatically as part of the suite

Running benchmarks

Benchmarks use Criterion.rs and cover sphere 5D, Rosenbrock 5D, and Rastrigin 10D:

cd rust
cargo bench

HTML reports are written to rust/target/criterion/.

Literature comparison experiment

A reproducible multi-run experiment is provided as a benchmark runner binary at rust/benchmarks/run_literature_comparison.rs.

Canonical command:

# Default: 30 seeds × 10-D × 6 functions → rust/benchmarks/literature_comparison.json
cargo run --bin run_literature_comparison

# Custom parameters
cargo run --bin run_literature_comparison -- \
    --n-runs 30 --dims 10 --output results.json --verbose

Output JSON is compatible with the Python generate_report.py tool.

Notebook experiment (Rust, reproducible)

The notebook Notebooks/Rust/benchmark_literature_comparison_rust.ipynb runs a reproducible 30-seed comparison between GIVP-full and GRASP-only for 10-D Sphere, Rosenbrock, Rastrigin, and Ackley.

Latest exported payload:

Summary from that run (30 seeds, lower is better):

Function GIVP-full mean ± std GRASP-only mean ± std Wilcoxon (one-sided, GIVP < GRASP)
Sphere 1.278e-04 ± 4.694e-05 1.175e+00 ± 4.622e-01 p < 1e-4
Rosenbrock 4.590e-01 ± 1.438e-01 5.539e+03 ± 2.964e+03 p < 1e-4
Rastrigin 8.347e-02 ± 4.601e-02 1.934e+01 ± 4.239e+00 p < 1e-4
Ackley 9.956e-02 ± 2.599e-02 8.520e+00 ± 9.990e-01 p < 1e-4

This notebook profile intentionally reflects a strong local-search setting, so GIVP-full is much slower but significantly better in objective quality than GRASP-only.

CLI

The Rust port also provides a small CLI binary:

cd rust
cargo run --bin givp -- --function sphere --dims 10 --seed 42

Supported options include --function, --dims, --seed, and --direction.

Coverage

The CI enforces a minimum of 90 % line coverage on rust/src/.

To check locally:

cd rust
cargo install cargo-llvm-cov  # once
cargo llvm-cov --all-features --lcov --output-path lcov.info
# Parse coverage percentage from lcov.info
grep '^LH:\|^LF:' lcov.info | awk -F: '
  /^LH:/ { hit  += $2 }
  /^LF:/ { total+= $2 }
  END    { printf "Coverage: %.1f%% (%d / %d lines)\n",
           hit/total*100, hit, total }
'

API parity with Python

Feature Python Julia Rust
GRASP construction
VND local search
ILS perturbation
Path Relinking
Elite pool
Convergence monitor
LRU evaluation cache
Adaptive α
Time budget
Mixed integer/continuous
Warm start
Reproducible (seed=)
Parallel candidates (rayon)
Literature comparison
JSON serialisation ✓ (feature)
Property-based tests
CLI entry point
Fuzzing driver