Theme: documentation and project polish. No public-API changes; this is the v0.5 release that elevates heuropt's docs/onboarding/governance to bar-setting status. Adds: - mdbook user guide at docs/book/ with intro, getting-started, defining-problems, choosing-an-algorithm, cookbook (7 recipes), comparison vs other libraries, stability/SemVer, migration guides. Deploys to https://swaits.github.io/heuropt/ via .github/workflows/ docs.yml. - Runnable rustdoc examples on every algorithm (35 of them), all exercised by cargo test --doc. - Three real-world examples: portfolio.rs (multi-obj with budget constraint), hyperparam_tuning.rs (BO + TPE), scheduling.rs (permutation via SA + SwapMutation against Smith's-rule oracle). - Governance: CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md (adopting builderscode.org's Builder's Code of Conduct), GitHub issue templates, PR template. Polishes: - README hero with badges + user-guide link. - lib.rs crate-level docs. - CHANGELOG entry for 0.5.0. Bumps Cargo.toml to 0.5.0.
5.5 KiB
Parallelize evaluation with rayon
If a single call to your evaluate takes more than ~50 µs, enabling
the parallel feature usually pays for itself immediately on
population-based algorithms. Each generation evaluates an entire
population, and rayon parallelizes that batch.
Enable the feature
[dependencies]
heuropt = { version = "0.5", features = ["parallel"] }
There's nothing else to opt into in your code. The
population-evaluation helper is feature-gated; with parallel on it
uses rayon::into_par_iter internally, with parallel off it falls
back to plain into_iter.
Determinism still holds
Seeded runs are bit-identical between the serial and parallel modes. The trick is that population members are evaluated in parallel but assembled back into the same order. Variation, selection, and the RNG are all driven by the main thread, so seed-stability tests still pass.
Which algorithms benefit
Algorithms with a per-generation evaluate_batch:
RandomSearch,Nsga2,Nsga3,Spea2,Moead,Mopso,Ibea,SmsEmoa,HypE,PesaII,EpsilonMoea,AgeMoea,Knea,Grea,Rvea.DifferentialEvolutionandGeneticAlgorithmbenefit on the initial population and offspring batches.
Steady-state algorithms (Paes, SimulatedAnnealing,
HillClimber, OnePlusOneEs) only evaluate one or a few
candidates per iteration, so the parallel feature gives them
nothing — leave it off if those are your primary optimizers.
Worked example
The Sphere problem is too cheap to actually benefit from parallelism
— this example just shows the shape. In real workloads evaluate is
the expensive bit (a simulation, a model fit, an HTTP call).
use heuropt::prelude::*;
struct ExpensiveSphere;
impl Problem for ExpensiveSphere {
type Decision = Vec<f64>;
fn objectives(&self) -> ObjectiveSpace {
ObjectiveSpace::new(vec![Objective::minimize("f")])
}
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
// Pretend this is a 5 ms simulation.
std::thread::sleep(std::time::Duration::from_millis(5));
Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
}
}
fn main() {
let bounds = vec![(-1.0_f64, 1.0_f64); 5];
let mut opt = DifferentialEvolution::new(
DifferentialEvolutionConfig {
population_size: 16,
generations: 50,
differential_weight: 0.5,
crossover_probability: 0.9,
seed: 42,
},
RealBounds::new(bounds),
);
let r = opt.run(&ExpensiveSphere);
println!("best f = {}", r.best.unwrap().evaluation.objectives[0]);
}
With the parallel feature on, each generation's 16 evaluations run
across rayon's worker threads. On a 16-core machine the wall-clock
cost per generation drops from 16 × 5 ms = 80 ms to roughly
5 ms + scheduling overhead.
Sizing your thread pool
heuropt uses rayon's global thread pool. Override the size with:
rayon::ThreadPoolBuilder::new().num_threads(8).build_global().unwrap();
Run this before any heuropt call, or use rayon's install API
to scope it.
When parallelism doesn't help
- Your
evaluateis sub-microsecond (Sphere, Rastrigin, Ackley unweighted) — the rayon scheduling overhead exceeds the work. - You're already running multiple seeds in parallel at the harness level (see Compare two algorithms). Stacking parallelism rarely helps.
- The algorithm is steady-state (Paes, SA, hill climber).