feat: v0.5.0 — comprehensive documentation release
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.
This commit is contained in:
@@ -40,6 +40,35 @@ impl Default for AgeMoeaConfig {
|
||||
/// score survivors by a combination of proximity (distance to the
|
||||
/// translated origin in the L_p frame) and diversity (distance to the
|
||||
/// nearest survivor in the same frame).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = AgeMoea::new(
|
||||
/// AgeMoeaConfig { population_size: 30, generations: 20, seed: 42 },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AgeMoea<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -57,6 +57,53 @@ impl Default for AntColonyTspConfig {
|
||||
/// Each ant builds a tour by repeatedly choosing the next node with
|
||||
/// probability `∝ τ_ij^α · η_ij^β` over the unvisited cities, where
|
||||
/// `η_ij = 1 / distance_ij` is the heuristic desirability.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Tsp { distances: Vec<Vec<f64>> }
|
||||
/// impl Problem for Tsp {
|
||||
/// type Decision = Vec<usize>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("length")])
|
||||
/// }
|
||||
/// fn evaluate(&self, tour: &Vec<usize>) -> Evaluation {
|
||||
/// let mut len = 0.0;
|
||||
/// for w in tour.windows(2) { len += self.distances[w[0]][w[1]]; }
|
||||
/// len += self.distances[*tour.last().unwrap()][tour[0]];
|
||||
/// Evaluation::new(vec![len])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // 5 cities laid out in a small square + center. The optimal tour
|
||||
/// // is the perimeter; the diagonal is suboptimal.
|
||||
/// let cities = [(0.0_f64, 0.0), (3.0, 0.0), (3.0, 3.0), (0.0, 3.0), (1.5, 1.5)];
|
||||
/// let n = cities.len();
|
||||
/// let mut d = vec![vec![0.0; n]; n];
|
||||
/// for i in 0..n {
|
||||
/// for j in 0..n {
|
||||
/// let dx = cities[i].0 - cities[j].0;
|
||||
/// let dy = cities[i].1 - cities[j].1;
|
||||
/// d[i][j] = (dx * dx + dy * dy).sqrt();
|
||||
/// }
|
||||
/// }
|
||||
/// let problem = Tsp { distances: d.clone() };
|
||||
///
|
||||
/// let mut opt = AntColonyTsp::new(AntColonyTspConfig {
|
||||
/// ants: 10,
|
||||
/// generations: 50,
|
||||
/// alpha: 1.0,
|
||||
/// beta: 5.0,
|
||||
/// evaporation: 0.5,
|
||||
/// deposit: 1.0,
|
||||
/// initial_pheromone: 0.1,
|
||||
/// seed: 42,
|
||||
/// }, d);
|
||||
/// let r = opt.run(&problem);
|
||||
/// assert!(r.best.is_some());
|
||||
/// ```
|
||||
pub struct AntColonyTsp {
|
||||
/// Algorithm configuration.
|
||||
pub config: AntColonyTspConfig,
|
||||
|
||||
@@ -61,6 +61,40 @@ impl Default for BayesianOptConfig {
|
||||
/// evaluation budgets (50–500). The GP kernel is anisotropic RBF; the
|
||||
/// acquisition function is EI; both are optimized by best-of-N random
|
||||
/// sampling each step (simple, predictable cost).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = BayesianOpt::new(
|
||||
/// BayesianOptConfig {
|
||||
/// initial_samples: 10,
|
||||
/// iterations: 30,
|
||||
/// length_scales: None, // default per-axis length scales
|
||||
/// signal_variance: 1.0,
|
||||
/// noise_variance: 1e-6,
|
||||
/// acquisition_samples: 200,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-3.0, 3.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// // 10 random + 30 BO steps = 40 total evaluations.
|
||||
/// assert_eq!(r.evaluations, 40);
|
||||
/// assert!(r.best.is_some());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BayesianOpt {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -59,6 +59,38 @@ impl Default for CmaEsConfig {
|
||||
/// `Vec<f64>` decisions only. Bounds come from the embedded `RealBounds`
|
||||
/// field; both the initial mean and every offspring are clamped per
|
||||
/// dimension.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = CmaEs::new(
|
||||
/// CmaEsConfig {
|
||||
/// population_size: 12,
|
||||
/// generations: 100,
|
||||
/// initial_sigma: 1.0,
|
||||
/// eigen_decomposition_period: 1,
|
||||
/// initial_mean: None,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 5]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// // CMA-ES converges aggressively on Sphere.
|
||||
/// assert!(r.best.unwrap().evaluation.objectives[0] < 1e-3);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CmaEs {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -44,6 +44,37 @@ impl Default for DifferentialEvolutionConfig {
|
||||
///
|
||||
/// `Vec<f64>` decisions only; single-objective problems only. Bounds come from
|
||||
/// the embedded `RealBounds`, and mutant vectors are clamped to those bounds.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = DifferentialEvolution::new(
|
||||
/// DifferentialEvolutionConfig {
|
||||
/// population_size: 20,
|
||||
/// generations: 50,
|
||||
/// differential_weight: 0.5,
|
||||
/// crossover_probability: 0.9,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 5]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// // DE crushes Sphere; expect very small objective.
|
||||
/// assert!(r.best.unwrap().evaluation.objectives[0] < 1e-3);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DifferentialEvolution {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -39,6 +39,45 @@ impl Default for EpsilonMoeaConfig {
|
||||
}
|
||||
|
||||
/// ε-dominance MOEA.
|
||||
///
|
||||
/// Steady-state EA with an ε-grid archive: every member that lands in
|
||||
/// the same ε-box as an existing one is replaced by the closer point
|
||||
/// to the box's grid corner. Auto-bounds the front size by the choice
|
||||
/// of `epsilon`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = EpsilonMoea::new(
|
||||
/// EpsilonMoeaConfig {
|
||||
/// population_size: 20,
|
||||
/// evaluations: 1_000,
|
||||
/// epsilon: vec![0.1, 0.1],
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EpsilonMoea<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -46,6 +46,41 @@ impl Default for GeneticAlgorithmConfig {
|
||||
/// produces offspring, those are evaluated, and the next population is
|
||||
/// the top `elitism` from the previous generation plus the best
|
||||
/// `population_size - elitism` offspring (by fitness).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64); 3];
|
||||
/// let mut opt = GeneticAlgorithm::new(
|
||||
/// GeneticAlgorithmConfig {
|
||||
/// population_size: 30,
|
||||
/// generations: 50,
|
||||
/// tournament_size: 2,
|
||||
/// elitism: 2,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert!(r.best.is_some());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GeneticAlgorithm<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -38,6 +38,40 @@ impl Default for GreaConfig {
|
||||
}
|
||||
|
||||
/// Grid-based Evolutionary Algorithm (GrEA).
|
||||
///
|
||||
/// Many-objective EA that uses three grid-based metrics — grid rank,
|
||||
/// grid crowding distance, and grid coordinate point distance — to
|
||||
/// select survivors. Particularly strong on linear / simplex-shaped
|
||||
/// fronts (e.g. DTLZ1).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Grea::new(
|
||||
/// GreaConfig { population_size: 30, generations: 20, grid_divisions: 8, seed: 42 },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Grea<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -34,6 +34,31 @@ impl Default for HillClimberConfig {
|
||||
/// feasible beats infeasible, smaller violation wins among infeasibles.
|
||||
///
|
||||
/// Single-objective only.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = HillClimber::new(
|
||||
/// HillClimberConfig { iterations: 500, seed: 42 },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// GaussianMutation { sigma: 0.3 },
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert!(r.best.is_some());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HillClimber<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -48,6 +48,41 @@ impl Default for HypeConfig {
|
||||
|
||||
/// Hypervolume Estimation Algorithm: many-objective MOEA that selects via
|
||||
/// Monte Carlo–estimated hypervolume contributions.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Hype::new(
|
||||
/// HypeConfig {
|
||||
/// population_size: 20,
|
||||
/// generations: 20,
|
||||
/// reference_point: vec![30.0, 30.0],
|
||||
/// mc_samples: 100,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Hype<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -52,6 +52,38 @@ impl Default for HyperbandConfig {
|
||||
/// low budget), later brackets favor exploitation (fewer configs run
|
||||
/// near the max budget). The single best result across all brackets
|
||||
/// is returned.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
/// use heuropt::core::partial_problem::PartialProblem;
|
||||
///
|
||||
/// struct Tuning;
|
||||
/// impl PartialProblem for Tuning {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("loss")])
|
||||
/// }
|
||||
/// fn evaluate_at_budget(&self, x: &Vec<f64>, budget: f64) -> Evaluation {
|
||||
/// // Pretend a model where more budget = lower loss.
|
||||
/// let loss = x[0].powi(2) + x[1].powi(2) + 1.0 / (budget + 1.0);
|
||||
/// Evaluation::new(vec![loss])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = Hyperband::new(
|
||||
/// HyperbandConfig {
|
||||
/// max_budget: 27.0,
|
||||
/// eta: 3.0,
|
||||
/// max_brackets: 4,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-1.0, 1.0); 2]),
|
||||
/// );
|
||||
/// let r = opt.run(&Tuning);
|
||||
/// assert!(r.best.is_some());
|
||||
/// ```
|
||||
pub struct Hyperband<I, D>
|
||||
where
|
||||
D: Clone,
|
||||
|
||||
@@ -37,6 +37,40 @@ impl Default for IbeaConfig {
|
||||
}
|
||||
|
||||
/// IBEA (Indicator-Based EA) using the additive ε-indicator.
|
||||
///
|
||||
/// Selects survivors by their contribution to a quality indicator
|
||||
/// (additive ε) rather than by dominance + crowding. On the comparison
|
||||
/// harness it consistently produces the best convergence of the dominance-
|
||||
/// alternative methods on smooth and disconnected fronts alike.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Ibea::new(
|
||||
/// IbeaConfig { population_size: 30, generations: 20, kappa: 0.05, seed: 42 },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ibea<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -53,6 +53,42 @@ impl Default for IpopCmaEsConfig {
|
||||
}
|
||||
|
||||
/// IPOP-CMA-ES: CMA-ES with population-doubling restarts.
|
||||
///
|
||||
/// Specifically designed to fix vanilla CMA-ES's weakness on multimodal
|
||||
/// landscapes — each restart doubles the population and randomizes the
|
||||
/// initial mean to escape from local basins. On the comparison harness
|
||||
/// it drops vanilla CMA-ES's Rastrigin score from f = 2.35 to f = 0.13.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = IpopCmaEs::new(
|
||||
/// IpopCmaEsConfig {
|
||||
/// initial_population_size: 8,
|
||||
/// total_generations: 100,
|
||||
/// initial_sigma: 1.0,
|
||||
/// eigen_decomposition_period: 1,
|
||||
/// stall_generations: Some(20),
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert!(r.best.unwrap().evaluation.objectives[0] < 1.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IpopCmaEs {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -39,6 +39,35 @@ impl Default for KneaConfig {
|
||||
/// Survival selection ranks splitting-front members by perpendicular
|
||||
/// distance from the hyperplane connecting the front's extreme points.
|
||||
/// Larger distance ≈ stronger knee = preferred survivor.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Knea::new(
|
||||
/// KneaConfig { population_size: 30, generations: 20, seed: 42 },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Knea<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -39,6 +39,45 @@ impl Default for MoeadConfig {
|
||||
}
|
||||
|
||||
/// MOEA/D optimizer using the Tchebycheff scalarizing function.
|
||||
///
|
||||
/// Decomposes the multi-objective problem into many single-objective
|
||||
/// scalarizations along Das–Dennis weight vectors and solves them
|
||||
/// in parallel with neighborhood-based mating. Very fast per generation;
|
||||
/// scales naturally to many objectives.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Moead::new(
|
||||
/// MoeadConfig {
|
||||
/// generations: 30,
|
||||
/// reference_divisions: 19, // 20 weights for 2 objectives
|
||||
/// neighborhood_size: 5,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Moead<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -52,6 +52,38 @@ impl Default for MopsoConfig {
|
||||
/// `Vec<f64>` decisions only. Each particle maintains a personal best (the
|
||||
/// last position that was Pareto-non-dominated by any later position). The
|
||||
/// social leader is sampled uniformly from the external archive each step.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = Mopso::new(
|
||||
/// MopsoConfig {
|
||||
/// swarm_size: 30,
|
||||
/// generations: 50,
|
||||
/// archive_size: 30,
|
||||
/// inertia: 0.4,
|
||||
/// cognitive: 1.5,
|
||||
/// social: 1.5,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0)]),
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mopso {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -48,6 +48,38 @@ impl Default for NelderMeadConfig {
|
||||
/// `Vec<f64>` decisions only. Single-objective only. Initial simplex is
|
||||
/// built around the midpoint of the configured bounds; every new vertex
|
||||
/// is clamped to those bounds.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = NelderMead::new(
|
||||
/// NelderMeadConfig {
|
||||
/// iterations: 200,
|
||||
/// reflection: 1.0,
|
||||
/// expansion: 2.0,
|
||||
/// contraction: 0.5,
|
||||
/// shrinkage: 0.5,
|
||||
/// initial_step: 1.0,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// // Nelder-Mead reaches machine precision on Sphere.
|
||||
/// assert!(r.best.unwrap().evaluation.objectives[0] < 1e-10);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NelderMead {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -35,6 +35,43 @@ impl Default for Nsga2Config {
|
||||
}
|
||||
|
||||
/// NSGA-II optimizer (spec §12.3).
|
||||
///
|
||||
/// The canonical Pareto-based EA: combines non-dominated sorting with
|
||||
/// crowding-distance secondary ranking. A strong default for 2- or
|
||||
/// 3-objective problems.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![
|
||||
/// Objective::minimize("f1"),
|
||||
/// Objective::minimize("f2"),
|
||||
/// ])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Nsga2::new(
|
||||
/// Nsga2Config { population_size: 30, generations: 20, seed: 42 },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert_eq!(r.population.len(), 30);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Nsga2<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -43,6 +43,44 @@ impl Default for Nsga3Config {
|
||||
}
|
||||
|
||||
/// NSGA-III optimizer.
|
||||
///
|
||||
/// NSGA-II's many-objective successor: replaces crowding distance with
|
||||
/// reference-point niching over Das–Dennis points in the normalized
|
||||
/// objective space. The canonical default for 4+ objectives.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Nsga3::new(
|
||||
/// Nsga3Config {
|
||||
/// population_size: 30,
|
||||
/// generations: 20,
|
||||
/// reference_divisions: 12,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Nsga3<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -47,6 +47,36 @@ impl Default for OnePlusOneEsConfig {
|
||||
|
||||
/// (1+1)-ES with the one-fifth rule: tiny, parameter-light continuous
|
||||
/// optimizer. `Vec<f64>` decisions only; single-objective only.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = OnePlusOneEs::new(
|
||||
/// OnePlusOneEsConfig {
|
||||
/// iterations: 1_000,
|
||||
/// initial_sigma: 0.5,
|
||||
/// adaptation_period: 50,
|
||||
/// step_increase: 1.22,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert!(r.best.unwrap().evaluation.objectives[0] < 1e-3);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OnePlusOneEs {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -36,6 +36,31 @@ impl Default for PaesConfig {
|
||||
/// One current candidate, one mutation per iteration, one bounded archive.
|
||||
/// Intentionally a readable baseline rather than a research-perfect PAES
|
||||
/// (spec §12.2).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = Paes::new(
|
||||
/// PaesConfig { iterations: 200, archive_size: 30, seed: 42 },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0)]),
|
||||
/// GaussianMutation { sigma: 0.3 },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Paes<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -55,6 +55,37 @@ impl Default for ParticleSwarmConfig {
|
||||
/// Velocities are clamped to `±(hi - lo)` per dimension to prevent
|
||||
/// "swarm explosion." Pair with `RealBounds` for both the search bounds
|
||||
/// and the initial particle positions.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = ParticleSwarm::new(
|
||||
/// ParticleSwarmConfig {
|
||||
/// swarm_size: 20,
|
||||
/// generations: 50,
|
||||
/// inertia: 0.7,
|
||||
/// cognitive: 1.5,
|
||||
/// social: 1.5,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert!(r.best.is_some());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParticleSwarm {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -47,6 +47,41 @@ impl Default for PesaIIConfig {
|
||||
/// Maintains an internal population (used to drive variation) and an
|
||||
/// external non-dominated archive. Selection biases toward members in
|
||||
/// sparsely-populated grid boxes so the front spreads out.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = PesaII::new(
|
||||
/// PesaIIConfig {
|
||||
/// population_size: 20,
|
||||
/// archive_size: 30,
|
||||
/// generations: 20,
|
||||
/// grid_divisions: 8,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PesaII<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -38,6 +38,31 @@ impl Default for RandomSearchConfig {
|
||||
/// Each iteration the configured `Initializer` produces `batch_size` decisions
|
||||
/// which are evaluated and pushed into the population. Cheap, parallelism-free,
|
||||
/// and useful as a sanity-check baseline.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = RandomSearch::new(
|
||||
/// RandomSearchConfig { iterations: 200, batch_size: 10, seed: 42 },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert_eq!(r.evaluations, 200 * 10);
|
||||
/// assert!(r.best.is_some());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RandomSearch<I> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -41,6 +41,45 @@ impl Default for RveaConfig {
|
||||
}
|
||||
|
||||
/// Reference Vector-guided Evolutionary Algorithm.
|
||||
///
|
||||
/// Many-objective EA that uses Das–Dennis reference vectors with an
|
||||
/// adaptive penalty term to balance convergence and diversity as
|
||||
/// generations progress.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Rvea::new(
|
||||
/// RveaConfig {
|
||||
/// population_size: 30,
|
||||
/// generations: 20,
|
||||
/// reference_divisions: 19,
|
||||
/// alpha: 2.0,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rvea<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -41,6 +41,36 @@ impl Default for SimulatedAnnealingConfig {
|
||||
/// and `T` anneals geometrically from `initial_temperature` to
|
||||
/// `final_temperature` over the iteration count. Generic over decision
|
||||
/// type — pair with any `Variation` impl that returns one child per call.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = SimulatedAnnealing::new(
|
||||
/// SimulatedAnnealingConfig {
|
||||
/// iterations: 2_000,
|
||||
/// initial_temperature: 1.0,
|
||||
/// final_temperature: 1e-3,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// GaussianMutation { sigma: 0.3 },
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert!(r.best.is_some());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimulatedAnnealing<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -49,6 +49,40 @@ impl Default for SmsEmoaConfig {
|
||||
/// non-dominated front. Excellent convergence quality at the price of
|
||||
/// quadratic-in-N hypervolume evaluations per generation, so practical
|
||||
/// up to ~4 objectives at population sizes ≤ 200.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = SmsEmoa::new(
|
||||
/// SmsEmoaConfig {
|
||||
/// population_size: 20,
|
||||
/// generations: 100,
|
||||
/// reference_point: vec![30.0, 30.0],
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SmsEmoa<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -51,6 +51,37 @@ impl Default for SeparableNesConfig {
|
||||
/// following the natural gradient of expected fitness, with rank-shaped
|
||||
/// fitness utilities for invariance to monotone transforms of the
|
||||
/// objective.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = SeparableNes::new(
|
||||
/// SeparableNesConfig {
|
||||
/// population_size: 16,
|
||||
/// generations: 80,
|
||||
/// initial_sigma: 1.0,
|
||||
/// mean_learning_rate: 1.0,
|
||||
/// sigma_learning_rate: None, // use NES default
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert!(r.best.unwrap().evaluation.objectives[0] < 1e-3);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SeparableNes {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -37,6 +37,40 @@ impl Default for Spea2Config {
|
||||
}
|
||||
|
||||
/// SPEA2 optimizer.
|
||||
///
|
||||
/// Strength Pareto Evolutionary Algorithm 2: combines a strength-based
|
||||
/// dominance score with a k-th nearest-neighbor density estimate. Maintains
|
||||
/// an external archive separate from the working population.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Schaffer;
|
||||
/// impl Problem for Schaffer {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let bounds = vec![(-5.0_f64, 5.0_f64)];
|
||||
/// let mut opt = Spea2::new(
|
||||
/// Spea2Config { population_size: 30, archive_size: 30, generations: 20, seed: 42 },
|
||||
/// RealBounds::new(bounds.clone()),
|
||||
/// CompositeVariation {
|
||||
/// crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
/// mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||
/// },
|
||||
/// );
|
||||
/// let r = opt.run(&Schaffer);
|
||||
/// assert_eq!(r.population.len(), 30);
|
||||
/// assert!(!r.pareto_front.is_empty());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Spea2<I, V> {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -41,6 +41,30 @@ impl Default for TlboConfig {
|
||||
/// population_size and generations. Compared with the rest of heuropt's
|
||||
/// SO toolkit (DE has F+CR, PSO has w+c1+c2, CMA-ES has σ, GA needs
|
||||
/// crossover+mutation operators), TLBO works out of the box.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = Tlbo::new(
|
||||
/// TlboConfig { population_size: 20, generations: 50, seed: 42 },
|
||||
/// RealBounds::new(vec![(-5.0, 5.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert!(r.best.unwrap().evaluation.objectives[0] < 1e-3);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tlbo {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -52,6 +52,37 @@ impl Default for TpeConfig {
|
||||
/// `BayesianOpt`, no GP — TPE models `p(x | y < y*)` and `p(x | y >= y*)`
|
||||
/// as per-axis Gaussian KDEs and picks the next candidate by maximizing
|
||||
/// the ratio of the two densities.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct Sphere;
|
||||
/// impl Problem for Sphere {
|
||||
/// type Decision = Vec<f64>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().map(|v| v * v).sum::<f64>()])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = Tpe::new(
|
||||
/// TpeConfig {
|
||||
/// initial_samples: 10,
|
||||
/// iterations: 50,
|
||||
/// good_fraction: 0.25,
|
||||
/// candidate_samples: 24,
|
||||
/// bandwidth_factor: 1.0,
|
||||
/// seed: 42,
|
||||
/// },
|
||||
/// RealBounds::new(vec![(-3.0, 3.0); 3]),
|
||||
/// );
|
||||
/// let r = opt.run(&Sphere);
|
||||
/// assert_eq!(r.evaluations, 60);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tpe {
|
||||
/// Algorithm configuration.
|
||||
|
||||
@@ -49,6 +49,34 @@ impl Default for UmdaConfig {
|
||||
/// `[1 / (2·selected_size), 1 - 1 / (2·selected_size)]` (Laplace-style
|
||||
/// smoothing) so the population never collapses to a single deterministic
|
||||
/// string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use heuropt::prelude::*;
|
||||
///
|
||||
/// struct OneMax;
|
||||
/// impl Problem for OneMax {
|
||||
/// type Decision = Vec<bool>;
|
||||
/// fn objectives(&self) -> ObjectiveSpace {
|
||||
/// ObjectiveSpace::new(vec![Objective::maximize("ones")])
|
||||
/// }
|
||||
/// fn evaluate(&self, x: &Vec<bool>) -> Evaluation {
|
||||
/// Evaluation::new(vec![x.iter().filter(|b| **b).count() as f64])
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut opt = Umda::new(UmdaConfig {
|
||||
/// population_size: 50,
|
||||
/// selected_size: 20,
|
||||
/// generations: 30,
|
||||
/// bits: 16,
|
||||
/// seed: 42,
|
||||
/// });
|
||||
/// let r = opt.run(&OneMax);
|
||||
/// // OneMax with 16 bits: optimum is 16. UMDA should be very close.
|
||||
/// assert!(r.best.unwrap().evaluation.objectives[0] >= 14.0);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Umda {
|
||||
/// Algorithm configuration.
|
||||
|
||||
+30
-9
@@ -1,16 +1,37 @@
|
||||
//! `heuropt` — a practical Rust toolkit for implementing heuristic
|
||||
//! single-objective, multi-objective, and many-objective optimization
|
||||
//! algorithms.
|
||||
//! `heuropt` — a practical Rust toolkit for heuristic single-,
|
||||
//! multi-, and many-objective optimization.
|
||||
//!
|
||||
//! The crate aims to make three things obvious:
|
||||
//!
|
||||
//! 1. Define an optimization problem by implementing [`Problem`](crate::core::Problem).
|
||||
//! 2. Run a built-in optimizer such as [`Nsga2`](crate::algorithms::Nsga2) or
|
||||
//! [`RandomSearch`](crate::algorithms::RandomSearch).
|
||||
//! 3. Implement a new optimizer by implementing
|
||||
//! [`Optimizer`](crate::traits::Optimizer).
|
||||
//! 1. **Define a problem** by implementing [`Problem`](crate::core::Problem).
|
||||
//! 2. **Run a built-in optimizer** — pick from 35 algorithms in
|
||||
//! [`algorithms`] covering single-objective continuous (CMA-ES,
|
||||
//! Differential Evolution, Nelder-Mead, …), multi-objective
|
||||
//! (NSGA-II, MOPSO, IBEA, MOEA/D, …), many-objective (NSGA-III,
|
||||
//! GrEA, RVEA, …), and sample-efficient regimes (Bayesian
|
||||
//! Optimization, TPE, Hyperband).
|
||||
//! 3. **Or implement your own** by implementing
|
||||
//! [`Optimizer`](crate::traits::Optimizer). The trait is one
|
||||
//! method long.
|
||||
//!
|
||||
//! See `docs/heuropt_tech_design_spec.md` for the full design rationale.
|
||||
//! ## Where to read more
|
||||
//!
|
||||
//! - **User guide / cookbook / comparison vs pymoo & friends:**
|
||||
//! <https://swaits.github.io/heuropt/>.
|
||||
//! - **Algorithm selection:** the README's decision tree, or the
|
||||
//! "Choosing an algorithm" book chapter.
|
||||
//! - **Design rationale:** `docs/heuropt_tech_design_spec.md` in the
|
||||
//! repository.
|
||||
//!
|
||||
//! ## Optional features
|
||||
//!
|
||||
//! - `serde` — derives `Serialize` / `Deserialize` on the core data
|
||||
//! types ([`Candidate`](crate::core::Candidate),
|
||||
//! [`Population`](crate::core::Population),
|
||||
//! [`Evaluation`](crate::core::Evaluation), …).
|
||||
//! - `parallel` — rayon-backed parallel population evaluation in
|
||||
//! every population-based algorithm. Seeded runs stay bit-
|
||||
//! identical to serial mode.
|
||||
//!
|
||||
//! # Quick example
|
||||
//!
|
||||
|
||||
Reference in New Issue
Block a user