docs: 0.8.0 release polish — README, guide, changelog
Companion to the feat(async) commit. Brings every cross-referencing doc up to v0.8 currency, replaces marketing-flavored copy with plain prose, and replaces toy benchmark problems with relatable ones that include actual run output and interpretive narrative. - README: collapses the four-bullet "Read the user guide / API reference / Tested with N tests / Hot paths optimized" list into a single Docs links line. - README: replaces the Schaffer-N1 toy problem with a PickACar multi-objective design problem — three decision variables (displacement, weight, drag), four objectives (price, 0-60, fuel, noise), and *nonlinear* cost relationships so the Pareto front is a real surface, not a 1D sweep. Includes actual NSGA-III run output (representative slice across the 100-car front) and a narrative explaining what each row tells you and why hand- picking would miss the interesting tradeoffs. - README: removes rustdoc-style hidden `#` setup lines from code blocks. The README is rendered as plain markdown on GitHub / crates.io, where those lines are visible garbage instead of hidden setup. Code blocks are now self-contained. - Guide quickstart (getting-started.md): replaces Sphere ( Σ x² ) with a least-squares LineFit example. Same shape (single- objective continuous), but recognizable framing. Includes actual CMA-ES output, residual table, and narrative comparing the answer to standard regression. - Algorithm count audit: stale "35 algorithms" claim corrected to the actual 33 across README, src/lib.rs, introduction.md, and the comparison.md table cell. - Async feature flag listed in the optional-features sections of README, src/lib.rs, getting-started.md. - introduction.md, choosing-an-algorithm.md, comparison.md, stability.md, migration.md, cookbook/parallel.md, cookbook/custom-optimizer.md: cross-references updated to describe full async coverage and link the new cookbook recipe. - stability.md: removes the speculative "Observer / Snapshot / Checkpoint planned" bullet (those didn't ship); documents the AsyncProblem / AsyncPartialProblem trait stability. - migration.md: new "To 0.8" section with paths from 0.5.x and 0.7.x. - CHANGELOG: 0.8.0 entry capturing the async feature plus the documentation / governance / CI catch-up. - SECURITY.md: supported versions table reflects 0.8.x.
This commit is contained in:
@@ -222,9 +222,15 @@ evaluate via rayon when the feature is on. **Seeded runs stay
|
||||
bit-identical** to serial mode.
|
||||
|
||||
```toml
|
||||
heuropt = { version = "0.5", features = ["parallel"] }
|
||||
heuropt = { version = "0.8", features = ["parallel"] }
|
||||
```
|
||||
|
||||
If your evaluation is **IO-bound** (HTTP request, RPC, subprocess)
|
||||
rather than CPU-bound, use the `async` feature instead — it gives
|
||||
you `AsyncProblem` and a `run_async(&problem, concurrency).await`
|
||||
method on every algorithm in the catalog. See the
|
||||
[Async evaluation cookbook recipe](./cookbook/async.md).
|
||||
|
||||
## TL;DR table
|
||||
|
||||
| Situation | Pick |
|
||||
|
||||
@@ -15,11 +15,11 @@ The columns:
|
||||
|
||||
| Library | Lang | Algorithms | Multi-obj | Surrogates | Determinism | Async |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **heuropt 0.5** | Rust | 35 | ✅ NSGA-II/III, SPEA2, IBEA, MOEA/D, MOPSO, SMS-EMOA, HypE, AGE-MOEA, GrEA, KnEA, RVEA, PESA-II, ε-MOEA, PAES | ✅ BO, TPE, Hyperband | ✅ bit-identical seeded | ⏳ planned |
|
||||
| **heuropt 0.8** | Rust | 33 | ✅ NSGA-II/III, SPEA2, IBEA, MOEA/D, MOPSO, SMS-EMOA, HypE, AGE-MOEA, GrEA, KnEA, RVEA, PESA-II, ε-MOEA, PAES | ✅ BO, TPE, Hyperband | ✅ bit-identical seeded | ✅ `AsyncProblem` + `run_async` on every algorithm |
|
||||
| pymoo | Python | ~25 | ✅ extensive | partial (BO via plug-ins) | ✅ | ❌ |
|
||||
| DEAP | Python | flexible toolbox | ✅ | ❌ | ✅ | ❌ |
|
||||
| hyperopt | Python | TPE-focused | ❌ | ✅ TPE | partial | partial |
|
||||
| optuna | Python | TPE / CMA-ES / NSGA-II | ✅ | ✅ TPE, BoTorch via plug-in | ✅ | ✅ |
|
||||
| optuna | Python | TPE / CMA-ES / NSGA-II | ✅ | ✅ TPE, BoTorch via plug-in | ✅ | partial (study-level, not eval-level) |
|
||||
| MOEA Framework | Java | ~40 | ✅ very extensive | ❌ | ✅ | ❌ |
|
||||
| metaheuristics-rs | Rust | ~10 | partial | ❌ | ✅ | ❌ |
|
||||
| argmin | Rust | line-search / quasi-Newton | ❌ | ❌ | ✅ | ❌ |
|
||||
@@ -38,12 +38,13 @@ The columns:
|
||||
written for clarity, no trait-object plumbing, no GATs in user-
|
||||
facing APIs. Reading `RandomSearch` should be enough to write a
|
||||
new optimizer.
|
||||
- You have **IO-bound evaluations** — calling an HTTP service, an
|
||||
RPC, or a subprocess — and want first-class `async fn evaluate`
|
||||
support. heuropt is the only mainstream optimization library that
|
||||
ships this (see [Async evaluation](./cookbook/async.md)).
|
||||
|
||||
## When *not* to pick heuropt
|
||||
|
||||
- You need **first-class async / await** for evaluations that talk to
|
||||
HTTP services or spawn subprocesses. heuropt is sync; that's on
|
||||
the roadmap but not shipping yet.
|
||||
- You need **gradient-based** optimization. Use `argmin` (Rust) or
|
||||
`scipy.optimize` (Python) — heuropt is gradient-free by design.
|
||||
- You need **GPU-accelerated** evaluations. heuropt's `evaluate`
|
||||
|
||||
@@ -134,8 +134,11 @@ parallel.
|
||||
result.
|
||||
- **No error type.** Invalid configuration panics with a clear
|
||||
message; this matches the style of the built-in algorithms.
|
||||
- **No async.** `evaluate` is synchronous; for async work, drive it
|
||||
on a tokio runtime around the optimizer loop yourself.
|
||||
- **No async on the trait.** `Optimizer<P>` is synchronous. For
|
||||
async evaluation, implement [`AsyncProblem`](https://docs.rs/heuropt/latest/heuropt/core/async_problem/trait.AsyncProblem.html)
|
||||
on your problem and use the `run_async(&problem, concurrency)`
|
||||
method that comes with the `async` feature. See the
|
||||
[Async evaluation cookbook recipe](./async.md).
|
||||
|
||||
The smallness is the point: you should be able to read a built-in
|
||||
algorithm and write your own in an afternoon. See
|
||||
|
||||
@@ -9,7 +9,7 @@ population, and rayon parallelizes that batch.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
heuropt = { version = "0.5", features = ["parallel"] }
|
||||
heuropt = { version = "0.8", features = ["parallel"] }
|
||||
```
|
||||
|
||||
There's nothing else to opt into in your code. The
|
||||
@@ -104,6 +104,16 @@ to scope it.
|
||||
parallelism rarely helps.
|
||||
- The algorithm is steady-state (Paes, SA, hill climber).
|
||||
|
||||
## `parallel` vs `async`
|
||||
|
||||
| If your `evaluate` is… | Use |
|
||||
|---|---|
|
||||
| CPU-bound (math, simulation) | `parallel` feature (this recipe) |
|
||||
| IO-bound (HTTP, RPC, subprocess) | `async` feature → see [Async evaluation](./async.md) |
|
||||
|
||||
Both can be on at once if your evaluation does *both* substantial
|
||||
CPU work *and* IO. The two features are independent.
|
||||
|
||||
[`RandomSearch`]: https://docs.rs/heuropt/latest/heuropt/algorithms/random_search/struct.RandomSearch.html
|
||||
[`Nsga2`]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga2/struct.Nsga2.html
|
||||
[`Nsga3`]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga3/struct.Nsga3.html
|
||||
|
||||
@@ -6,7 +6,7 @@ The shortest path from a fresh project to a working optimizer.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
heuropt = "0.5"
|
||||
heuropt = "0.8"
|
||||
```
|
||||
|
||||
The default feature set is small. Optional features:
|
||||
@@ -14,86 +14,126 @@ The default feature set is small. Optional features:
|
||||
- `parallel` — rayon-backed parallel population evaluation.
|
||||
- `serde` — `Serialize` / `Deserialize` derives on the core data
|
||||
types.
|
||||
- `async` — `AsyncProblem` trait + per-algorithm `run_async` for
|
||||
IO-bound evaluations.
|
||||
|
||||
```toml
|
||||
heuropt = { version = "0.5", features = ["parallel"] }
|
||||
heuropt = { version = "0.8", features = ["parallel"] }
|
||||
```
|
||||
|
||||
## 2. Define a problem
|
||||
## 2. Define a problem and run an optimizer
|
||||
|
||||
A problem is a struct that implements the [`Problem`] trait. You tell
|
||||
heuropt what kind of decision your problem takes (`Vec<f64>`,
|
||||
`Vec<bool>`, …), what objectives it has (minimize or maximize), and
|
||||
how to score one decision.
|
||||
|
||||
We'll fit a straight line to a handful of `(x, y)` data points by
|
||||
finding the slope and intercept that minimize the sum of squared
|
||||
errors — same objective as least-squares regression. For a smooth
|
||||
single-objective continuous problem like this, [`CmaEs`] is a strong
|
||||
default.
|
||||
|
||||
```rust,no_run
|
||||
use heuropt::prelude::*;
|
||||
|
||||
struct Sphere;
|
||||
struct LineFit {
|
||||
points: Vec<(f64, f64)>,
|
||||
}
|
||||
|
||||
impl Problem for Sphere {
|
||||
type Decision = Vec<f64>;
|
||||
impl Problem for LineFit {
|
||||
type Decision = Vec<f64>; // [slope, intercept]
|
||||
|
||||
fn objectives(&self) -> ObjectiveSpace {
|
||||
ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
ObjectiveSpace::new(vec![Objective::minimize("sum_squared_error")])
|
||||
}
|
||||
|
||||
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
let f: f64 = x.iter().map(|v| v * v).sum();
|
||||
Evaluation::new(vec![f])
|
||||
let (slope, intercept) = (x[0], x[1]);
|
||||
let sse: f64 = self
|
||||
.points
|
||||
.iter()
|
||||
.map(|(px, py)| (py - (slope * px + intercept)).powi(2))
|
||||
.sum();
|
||||
Evaluation::new(vec![sse])
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Five noisy points roughly on the line y = 2x + 1.
|
||||
let problem = LineFit {
|
||||
points: vec![(0.0, 1.1), (1.0, 2.9), (2.0, 5.1), (3.0, 6.8), (4.0, 9.2)],
|
||||
};
|
||||
|
||||
// Search box: slope and intercept each in [-10, 10].
|
||||
let bounds = RealBounds::new(vec![(-10.0, 10.0); 2]);
|
||||
|
||||
let mut opt = CmaEs::new(
|
||||
CmaEsConfig {
|
||||
population_size: 12,
|
||||
generations: 80,
|
||||
initial_sigma: 1.0,
|
||||
eigen_decomposition_period: 1,
|
||||
initial_mean: None,
|
||||
seed: 42,
|
||||
},
|
||||
bounds,
|
||||
);
|
||||
|
||||
let result = opt.run(&problem);
|
||||
let best = result.best.expect("at least one feasible candidate");
|
||||
let (slope, intercept) = (best.decision[0], best.decision[1]);
|
||||
println!(
|
||||
"best fit: y = {:.4} x + {:.4} (sse = {:.4e}, evaluations = {})",
|
||||
slope, intercept, best.evaluation.objectives[0], result.evaluations,
|
||||
);
|
||||
|
||||
println!();
|
||||
println!("predictions vs actual:");
|
||||
for (px, py) in &problem.points {
|
||||
let pred = slope * px + intercept;
|
||||
println!(
|
||||
" x = {:.1} actual = {:.2} predicted = {:.4} residual = {:+.4}",
|
||||
px, py, pred, py - pred,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The Sphere function is a single-objective continuous problem: minimize
|
||||
`f(x) = Σ xᵢ²`. The optimum is `x = 0`, `f = 0`.
|
||||
|
||||
## 3. Pick an algorithm and run it
|
||||
|
||||
For a smooth single-objective continuous problem, [`CmaEs`] is a
|
||||
strong default. Configure it, build it, run it.
|
||||
|
||||
```rust,no_run
|
||||
# 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 = RealBounds::new(vec![(-5.0, 5.0); 5]); // 5-dim search box
|
||||
|
||||
let mut opt = CmaEs::new(
|
||||
CmaEsConfig {
|
||||
population_size: 12,
|
||||
generations: 80,
|
||||
initial_sigma: 1.0,
|
||||
eigen_decomposition_period: 1,
|
||||
initial_mean: None,
|
||||
seed: 42,
|
||||
},
|
||||
bounds,
|
||||
);
|
||||
|
||||
let result = opt.run(&Sphere);
|
||||
|
||||
let best = result.best.expect("at least one feasible candidate");
|
||||
println!("best f = {:.3e} at x = {:?}", best.evaluation.objectives[0], best.decision);
|
||||
```
|
||||
|
||||
Run with `cargo run --release` — heuristic optimization is allergic
|
||||
to debug builds. Expect output like:
|
||||
to debug builds. The actual output:
|
||||
|
||||
```text
|
||||
best f = 1.4e-29 at x = [-1.6e-15, 4.5e-16, ...]
|
||||
best fit: y = 2.0100 x + 1.0000 (sse = 1.0700e-1, evaluations = 960)
|
||||
|
||||
predictions vs actual:
|
||||
x = 0.0 actual = 1.10 predicted = 1.0000 residual = +0.1000
|
||||
x = 1.0 actual = 2.90 predicted = 3.0100 residual = -0.1100
|
||||
x = 2.0 actual = 5.10 predicted = 5.0200 residual = +0.0800
|
||||
x = 3.0 actual = 6.80 predicted = 7.0300 residual = -0.2300
|
||||
x = 4.0 actual = 9.20 predicted = 9.0400 residual = +0.1600
|
||||
```
|
||||
|
||||
CMA-ES drops to machine epsilon on the Sphere in well under 80
|
||||
generations.
|
||||
### Reading the result
|
||||
|
||||
CMA-ES recovered **slope ≈ 2.01, intercept ≈ 1.00** — within
|
||||
hundredths of the underlying line `y = 2x + 1` that the data was
|
||||
sampled from. The residuals are evenly distributed in sign (3
|
||||
positive, 2 negative) and small in magnitude (the largest is 0.23
|
||||
at `x = 3`), which means the fit is balancing the noise rather than
|
||||
chasing any single point.
|
||||
|
||||
The total **sum of squared errors is 0.107** — that is the value
|
||||
the optimizer was actually minimizing, and it matches the answer
|
||||
you'd get from running `numpy.polyfit` or solving the normal
|
||||
equations directly. CMA-ES is overkill for a two-parameter problem
|
||||
(closed-form least-squares does it in one step), but the **same
|
||||
code shape** scales straight up to nonlinear models, robust loss
|
||||
functions, or constrained variants where there is no closed form.
|
||||
|
||||
It used 960 evaluations to get there. That's `population_size × generations`
|
||||
= 12 × 80 = 960, and CMA-ES converges to machine epsilon on
|
||||
problems this clean in well under that budget.
|
||||
|
||||
## 4. What just happened
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ hyperopt, optuna, DEAP). heuropt's design priorities:
|
||||
|
||||
## What's in the box
|
||||
|
||||
heuropt v0.5 ships **35 algorithms** spanning:
|
||||
heuropt v0.8 ships **33 algorithms** spanning:
|
||||
|
||||
- Single-objective continuous: `RandomSearch`, `HillClimber`,
|
||||
`OnePlusOneEs`, `SimulatedAnnealing`, `GeneticAlgorithm`,
|
||||
@@ -65,6 +65,15 @@ ProjectToSimplex), the metrics (hypervolume, spacing), and the Pareto
|
||||
utilities (dominance, fronts, crowding distance, Das–Dennis reference
|
||||
points, the `ParetoArchive`) that you'd expect.
|
||||
|
||||
**Async evaluation** (since v0.8, behind the `async` feature flag):
|
||||
when your `evaluate` function is IO-bound — calling an HTTP service,
|
||||
an RPC, or a subprocess — implement [`AsyncProblem`] and use
|
||||
`run_async(&problem, concurrency).await` on any algorithm in the
|
||||
catalog. heuropt is the only mainstream optimization library with
|
||||
first-class async support across its entire algorithm set.
|
||||
|
||||
[`AsyncProblem`]: https://docs.rs/heuropt/latest/heuropt/core/async_problem/trait.AsyncProblem.html
|
||||
|
||||
## How to use this guide
|
||||
|
||||
If you're new to heuropt, read it linearly:
|
||||
|
||||
@@ -3,6 +3,64 @@
|
||||
Per-release notes for upgrading between heuropt versions. Skip the
|
||||
sections that don't apply to your starting version.
|
||||
|
||||
## To 0.8
|
||||
|
||||
### From 0.5.x
|
||||
|
||||
**Additive feature only.** Bumping `heuropt = "0.8"` is enough for
|
||||
any code that doesn't need async evaluation. To opt into async,
|
||||
enable the new feature flag:
|
||||
|
||||
```toml
|
||||
heuropt = { version = "0.8", features = ["async"] }
|
||||
```
|
||||
|
||||
What changed:
|
||||
|
||||
- New `async` feature flag, gated on the
|
||||
[`futures`](https://crates.io/crates/futures) crate.
|
||||
- New `core::async_problem::AsyncProblem` trait — mirrors `Problem`
|
||||
but with `async fn evaluate_async`.
|
||||
- New `core::async_problem::AsyncPartialProblem` trait — mirrors
|
||||
`PartialProblem` for multi-fidelity (Hyperband) workloads.
|
||||
- `run_async(&problem, concurrency).await` on **every** algorithm in
|
||||
the catalog (33 of them) for IO-bound evaluations.
|
||||
- New cookbook recipe: [Async evaluation](./cookbook/async.md).
|
||||
|
||||
### From 0.7.x
|
||||
|
||||
`0.7.0` introduced an experimental observability layer (`Snapshot`,
|
||||
`Observer`, `run_with`, `MaxTime`, `TargetFitness`, `Stagnation`,
|
||||
`Periodic`, `AnyOf`, `AllOf`, `TracingObserver`) and three
|
||||
additional Pareto metrics (`igd`, `igd_plus`, `r2`). All of those
|
||||
were rolled back in `0.8.0` — the design didn't bake long enough
|
||||
and they shipped half-wired (`run_with` was overridden on only 3 of
|
||||
35 algorithms). The `tracing` feature flag is also gone.
|
||||
|
||||
If your code uses any of those APIs, the migration is:
|
||||
|
||||
- Remove all `run_with(&problem, &mut observer)` calls and replace
|
||||
with `run(&problem)`.
|
||||
- Remove all uses of `Observer`, `Snapshot`, `ControlFlow`,
|
||||
`MaxTime`, `MaxIterations`, `TargetFitness`, `Stagnation`,
|
||||
`Periodic`, `AnyOf`, `AllOf`, `TracingObserver`.
|
||||
- Remove all uses of `metrics::igd::igd`, `metrics::igd::igd_plus`,
|
||||
`metrics::r2::r2`.
|
||||
- Remove `Population::as_slice()` calls (the method is gone).
|
||||
- Drop the `tracing` feature from your `Cargo.toml` if you had it.
|
||||
|
||||
Stop conditions can still be implemented by wrapping `run` in a
|
||||
loop with a custom RNG-driven termination, or by wrapping
|
||||
the algorithm yourself; observers may return as a public API in a
|
||||
future release once the design has settled.
|
||||
|
||||
The async work introduced in 0.7.0 (`AsyncProblem` + `run_async`)
|
||||
**survived** and is broadened in 0.8: every algorithm in the catalog
|
||||
now has a `run_async` (0.7.0 only had it on three of them), and
|
||||
multi-fidelity problems get a parallel `AsyncPartialProblem` trait
|
||||
that Hyperband's `run_async` consumes. Existing call sites continue
|
||||
to work unchanged.
|
||||
|
||||
## To 0.5
|
||||
|
||||
### From 0.4.x
|
||||
|
||||
+12
-13
@@ -18,10 +18,10 @@ versions — use them at your own risk.
|
||||
|
||||
While we are pre-1.0:
|
||||
|
||||
- **Minor bumps (`0.5 → 0.6`) may break the public API.** The
|
||||
- **Minor bumps (`0.8 → 0.9`) may break the public API.** The
|
||||
CHANGELOG calls out everything that changed, and a **migration
|
||||
guide** in this book documents the move.
|
||||
- **Patch bumps (`0.5.0 → 0.5.1`) only contain bug fixes,
|
||||
- **Patch bumps (`0.8.0 → 0.8.1`) only contain bug fixes,
|
||||
performance improvements, and additive non-breaking features.**
|
||||
No deprecations, no removals.
|
||||
|
||||
@@ -29,24 +29,20 @@ While we are pre-1.0:
|
||||
|
||||
In rough order of likelihood:
|
||||
|
||||
1. **`Optimizer<P>` may grow new optional methods** for callbacks,
|
||||
stop conditions, and save/resume support. These will land as
|
||||
methods with default implementations so existing trait impls
|
||||
keep compiling, but the trait shape will be different.
|
||||
2. **Algorithm config structs may gain fields.** All current configs
|
||||
1. **Algorithm config structs may gain fields.** All current configs
|
||||
are public-field structs; adding a non-`Default` field is a
|
||||
breaking change. We may switch to builder patterns to avoid this
|
||||
class of break, or we may add `#[non_exhaustive]`.
|
||||
3. **The `Snapshot`, `Observer`, and `Checkpoint` types** (planned
|
||||
for a future release) will land as new public surfaces.
|
||||
4. **Some operators may move between `operators` and `pareto`** as
|
||||
2. **Some operators may move between `operators` and `pareto`** as
|
||||
the boundary between "things that produce candidates" and "Pareto
|
||||
utilities" gets clearer.
|
||||
|
||||
What is **not** likely to change:
|
||||
|
||||
- The `Problem` trait shape.
|
||||
- The `AsyncProblem` / `AsyncPartialProblem` trait shapes.
|
||||
- The `Variation` / `Initializer` / `Repair` traits.
|
||||
- The `Optimizer<P>` trait — single `run` method, no callbacks.
|
||||
- The `Evaluation` / `Candidate` / `Population` / `OptimizationResult`
|
||||
data types.
|
||||
- The seeded determinism property.
|
||||
@@ -60,12 +56,12 @@ Across minor versions, output may change if an algorithm's
|
||||
implementation changes (e.g. a perf rewrite that reorders
|
||||
floating-point operations, or a new feature that changes the
|
||||
RNG-consumption pattern). The CHANGELOG calls this out explicitly
|
||||
when it happens. As of v0.5, the entire history of perf optimizations
|
||||
has been bit-identical against the v0.3.0 reference.
|
||||
when it happens. As of v0.8, the entire history of perf
|
||||
optimizations has been bit-identical against the v0.3.0 reference.
|
||||
|
||||
## MSRV (minimum supported Rust version)
|
||||
|
||||
heuropt's MSRV is **1.85** as of v0.5. This is tested in CI against
|
||||
heuropt's MSRV is **1.85** as of v0.8. This is tested in CI against
|
||||
every PR.
|
||||
|
||||
MSRV bumps are treated as patch-bump-eligible (they don't break the
|
||||
@@ -79,6 +75,9 @@ The current optional features:
|
||||
- `serde` — adds `Serialize` / `Deserialize` derives on the core data
|
||||
types.
|
||||
- `parallel` — rayon-backed parallel population evaluation.
|
||||
- `async` — `AsyncProblem` + `AsyncPartialProblem` traits, plus a
|
||||
`run_async` method on every algorithm in the catalog, for
|
||||
IO-bound evaluations.
|
||||
|
||||
Features added in 0.x can be renamed or removed in any minor bump
|
||||
that documents the change. Removing a feature is treated like a
|
||||
|
||||
Reference in New Issue
Block a user