6 Commits

Author SHA1 Message Date
dependabot[bot] 47f63b2461 chore(deps): bump the all-actions group across 1 directory with 2 updates
Bumps the all-actions group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

Updates `codecov/codecov-action` from 6 to 7
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-27 01:53:11 +02:00
Manuel f60b2e9a9e Update deps 2026-06-24 13:51:45 +02:00
dependabot[bot] 2c41412c77 chore(deps): bump the all-actions group across 1 directory with 2 updates
Bumps the all-actions group with 2 updates in the / directory: [codecov/codecov-action](https://github.com/codecov/codecov-action) and [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete).


Updates `codecov/codecov-action` from 5 to 6
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

Updates `bnjbvr/cargo-machete` from 0.9.1 to 0.9.2
- [Release notes](https://github.com/bnjbvr/cargo-machete/releases)
- [Changelog](https://github.com/bnjbvr/cargo-machete/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bnjbvr/cargo-machete/compare/7959c845782fed02ee69303126d4a12d64f1db18...ac30a525c0a8d163a92d727b3ff079ee3f6ecb08)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 0.9.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-actions
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-01 12:15:27 +02:00
Manuel Raimann da2d367fb7 chore: release v1.0.1 2026-05-01 12:06:45 +02:00
Manuel Raimann ab74468603 fix(deps): set tracing minimum to 0.1.26 for Span::entered() 2026-05-01 12:06:45 +02:00
Manuel Raimann 4225d61027 fix(sampler): deterministic parameter matching in TPE
When two parameters share the same Distribution (e.g. two
FloatParam::new(-5.0, 5.0) in one study), TpeSampler iterated
trial.distributions (a HashMap) with find_map to extract a value
matching the target distribution. HashMap iteration order varies
across runs, so x's history could be conflated with y's history
non-deterministically — making seeded TPE runs flaky in parallel
test execution.

Pick the smallest-ParamId match instead, so the choice is stable
across runs.

Also:
- src/sampler/genetic.rs: collapse if into match guard (clippy)
- src/lib.rs: drop std_instead_of_core lint (the only remaining
  hits are std::io::Error/ErrorKind, which have no stable core
  equivalent yet)
- Cargo.toml: relax minimum versions for fastrand/tokio/tracing/
  optimizer-derive; bump dev-dep tokio to 1.50 to match in-tree usage
2026-04-30 09:27:06 +02:00
7 changed files with 71 additions and 104 deletions
+21 -21
View File
@@ -17,7 +17,7 @@ jobs:
name: Format name: Format
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -29,7 +29,7 @@ jobs:
name: Clippy name: Clippy
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -42,7 +42,7 @@ jobs:
name: Test name: Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -57,7 +57,7 @@ jobs:
name: Examples name: Examples
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -86,7 +86,7 @@ jobs:
name: Docs name: Docs
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -104,7 +104,7 @@ jobs:
matrix: matrix:
partition: [1, 2, 3, 4] partition: [1, 2, 3, 4]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -119,7 +119,7 @@ jobs:
name: Coverage name: Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -131,7 +131,7 @@ jobs:
- name: Generate coverage report - name: Generate coverage report
run: cargo llvm-cov --all-features --lcov --output-path lcov.info run: cargo llvm-cov --all-features --lcov --output-path lcov.info
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v7
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: lcov.info files: lcov.info
@@ -141,7 +141,7 @@ jobs:
name: MSRV (1.89) name: MSRV (1.89)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust 1.89 - name: Install Rust 1.89
run: | run: |
rustup override set 1.89 rustup override set 1.89
@@ -154,27 +154,27 @@ jobs:
name: Cargo Deny name: Cargo Deny
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: EmbarkStudios/cargo-deny-action@v2 - uses: EmbarkStudios/cargo-deny-action@v2
machete: machete:
name: Unused Dependencies name: Unused Dependencies
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
rustup update stable rustup update stable
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Machete - name: Machete
uses: bnjbvr/cargo-machete@7959c845782fed02ee69303126d4a12d64f1db18 uses: bnjbvr/cargo-machete@ac30a525c0a8d163a92d727b3ff079ee3f6ecb08
semver: semver:
name: Semver Check name: Semver Check
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -189,7 +189,7 @@ jobs:
name: Minimal Versions name: Minimal Versions
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust nightly - name: Install Rust nightly
run: | run: |
rustup toolchain install nightly rustup toolchain install nightly
@@ -212,7 +212,7 @@ jobs:
matrix: matrix:
os: [windows-latest, macos-latest] os: [windows-latest, macos-latest]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -235,7 +235,7 @@ jobs:
- powerpc64le-unknown-linux-gnu - powerpc64le-unknown-linux-gnu
- s390x-unknown-linux-gnu - s390x-unknown-linux-gnu
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -249,7 +249,7 @@ jobs:
name: Typos name: Typos
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -265,7 +265,7 @@ jobs:
if: github.event_name == 'push' if: github.event_name == 'push'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
@@ -288,7 +288,7 @@ jobs:
run: cargo install critcmp run: cargo install critcmp
# --- Run benchmarks on the BASE (target) branch --- # --- Run benchmarks on the BASE (target) branch ---
- uses: actions/checkout@v6 - uses: actions/checkout@v7
with: with:
ref: ${{ github.event.pull_request.base.sha }} ref: ${{ github.event.pull_request.base.sha }}
clean: false clean: false
@@ -301,7 +301,7 @@ jobs:
run: cargo bench --all-features --bench samplers --bench optimization -- --save-baseline base run: cargo bench --all-features --bench samplers --bench optimization -- --save-baseline base
# --- Run benchmarks on the HEAD (PR) branch --- # --- Run benchmarks on the HEAD (PR) branch ---
- uses: actions/checkout@v6 - uses: actions/checkout@v7
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
clean: false clean: false
@@ -375,7 +375,7 @@ jobs:
- typos - typos
- bench-check - bench-check
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
run: | run: |
rustup override set stable rustup override set stable
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
matrix: matrix:
rust: [beta, nightly] rust: [beta, nightly]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust ${{ matrix.rust }} - name: Install Rust ${{ matrix.rust }}
run: | run: |
rustup override set ${{ matrix.rust }} rustup override set ${{ matrix.rust }}
@@ -41,7 +41,7 @@ jobs:
matrix: matrix:
rust: [stable, beta, nightly] rust: [stable, beta, nightly]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Install Rust ${{ matrix.rust }} - name: Install Rust ${{ matrix.rust }}
run: | run: |
rustup override set ${{ matrix.rust }} rustup override set ${{ matrix.rust }}
+7 -7
View File
@@ -3,7 +3,7 @@ members = ["optimizer-derive"]
[package] [package]
name = "optimizer" name = "optimizer"
version = "1.0.0" version = "1.0.1"
edition = "2024" edition = "2024"
rust-version = "1.89" rust-version = "1.89"
license = "MIT" license = "MIT"
@@ -16,16 +16,16 @@ categories = ["algorithms", "science", "mathematics"]
readme = "README.md" readme = "README.md"
[dependencies] [dependencies]
fastrand = "2.3" fastrand = "2"
thiserror = "2" thiserror = "2"
parking_lot = "0.12" parking_lot = "0.12"
tokio = { version = "1.30", features = ["sync", "rt-multi-thread"], optional = true } tokio = { version = "1", features = ["sync", "rt-multi-thread"], optional = true }
optimizer-derive = { version = "0.1.0", path = "optimizer-derive", optional = true } optimizer-derive = { version = "0.1", path = "optimizer-derive", optional = true }
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }
serde_json = { version = "1", optional = true } serde_json = { version = "1", optional = true }
tracing = { version = "0.1.29", optional = true } tracing = { version = "0.1.44", optional = true }
sobol_burley = { version = "0.5", optional = true } sobol_burley = { version = "0.5", optional = true }
nalgebra = { version = "0.34", optional = true } nalgebra = { version = "0.35", optional = true }
fs2 = { version = "0.4", optional = true } fs2 = { version = "0.4", optional = true }
[features] [features]
@@ -40,7 +40,7 @@ cma-es = ["dep:nalgebra"]
gp = ["dep:nalgebra"] gp = ["dep:nalgebra"]
[dev-dependencies] [dev-dependencies]
tokio = { version = "1.30", features = ["rt-multi-thread", "macros", "time"] } tokio = { version = "1.52", features = ["rt-multi-thread", "macros", "time"] }
optimizer-derive = { version = "0.1.0", path = "optimizer-derive" } optimizer-derive = { version = "0.1.0", path = "optimizer-derive" }
serde_json = "1" serde_json = "1"
criterion = { version = "0.8", features = ["html_reports"] } criterion = { version = "0.8", features = ["html_reports"] }
-1
View File
@@ -7,7 +7,6 @@
#![deny(clippy::complexity)] #![deny(clippy::complexity)]
#![deny(clippy::perf)] #![deny(clippy::perf)]
#![deny(clippy::pedantic)] #![deny(clippy::pedantic)]
#![deny(clippy::std_instead_of_core)]
//! Bayesian and population-based optimization library with an Optuna-like API //! Bayesian and population-based optimization library with an Optuna-like API
//! for hyperparameter tuning and black-box optimization. It ships 12 samplers //! for hyperparameter tuning and black-box optimization. It ships 12 samplers
+6 -6
View File
@@ -295,10 +295,10 @@ pub(crate) fn crossover(
child2[i] = ParamValue::Int((c2.round() as i64).clamp(d.low, d.high)); child2[i] = ParamValue::Int((c2.round() as i64).clamp(d.low, d.high));
} }
} }
(ParamValue::Categorical(_), ParamValue::Categorical(_), _) => { (ParamValue::Categorical(_), ParamValue::Categorical(_), _)
if rng_util::f64_range(rng, 0.0, 1.0) < 0.5 { if rng_util::f64_range(rng, 0.0, 1.0) < 0.5 =>
core::mem::swap(&mut child1[i], &mut child2[i]); {
} core::mem::swap(&mut child1[i], &mut child2[i]);
} }
_ => {} _ => {}
} }
@@ -324,8 +324,8 @@ pub(crate) fn sbx_crossover_f64(
(1.0 / (2.0 * (1.0 - u))).powf(1.0 / (eta + 1.0)) (1.0 / (2.0 * (1.0 - u))).powf(1.0 / (eta + 1.0))
}; };
let c1 = 0.5 * ((1.0 + beta) * p1 + (1.0 - beta) * p2); let c1 = f64::midpoint((1.0 + beta) * p1, (1.0 - beta) * p2);
let c2 = 0.5 * ((1.0 - beta) * p1 + (1.0 + beta) * p2); let c2 = f64::midpoint((1.0 - beta) * p1, (1.0 + beta) * p2);
(c1.clamp(low, high), c2.clamp(low, high)) (c1.clamp(low, high), c2.clamp(low, high))
} }
+1 -1
View File
@@ -256,7 +256,7 @@ mod tests {
if let ParamValue::Int(v) = value { if let ParamValue::Int(v) = value {
assert!((0..=10).contains(&v)); assert!((0..=10).contains(&v));
// Check it's on the step grid: 0, 2, 4, 6, 8, 10 // Check it's on the step grid: 0, 2, 4, 6, 8, 10
assert!(v % 2 == 0); assert_eq!(v % 2, 0);
} else { } else {
panic!("Expected Int value"); panic!("Expected Int value");
} }
+34 -66
View File
@@ -642,6 +642,22 @@ impl Default for TpeSamplerBuilder {
} }
} }
/// Deterministically pick a parameter value matching `target_dist` from a
/// trial. When multiple parameters share the same distribution (e.g. two
/// `FloatParam::new(-5.0, 5.0)` in the same study), the one with the
/// smallest [`ParamId`] is chosen so behavior does not depend on
/// `HashMap` iteration order.
fn find_matching_value<'t>(
t: &'t CompletedTrial,
target_dist: &Distribution,
) -> Option<&'t ParamValue> {
t.distributions
.iter()
.filter(|(_, dist)| *dist == target_dist)
.min_by_key(|(id, _)| *id)
.and_then(|(id, _)| t.params.get(id))
}
impl TpeSampler { impl TpeSampler {
fn sample_float( fn sample_float(
&self, &self,
@@ -653,33 +669,17 @@ impl TpeSampler {
let target_dist = Distribution::Float(d.clone()); let target_dist = Distribution::Float(d.clone());
let good_values: Vec<f64> = good_trials let good_values: Vec<f64> = good_trials
.iter() .iter()
.filter_map(|t| { .filter_map(|t| match find_matching_value(t, &target_dist)? {
t.distributions.iter().find_map(|(id, dist)| { ParamValue::Float(f) => Some(*f),
if *dist == target_dist { _ => None,
t.params.get(id).and_then(|v| match v {
ParamValue::Float(f) => Some(*f),
_ => None,
})
} else {
None
}
})
}) })
.collect(); .collect();
let bad_values: Vec<f64> = bad_trials let bad_values: Vec<f64> = bad_trials
.iter() .iter()
.filter_map(|t| { .filter_map(|t| match find_matching_value(t, &target_dist)? {
t.distributions.iter().find_map(|(id, dist)| { ParamValue::Float(f) => Some(*f),
if *dist == target_dist { _ => None,
t.params.get(id).and_then(|v| match v {
ParamValue::Float(f) => Some(*f),
_ => None,
})
} else {
None
}
})
}) })
.collect(); .collect();
@@ -708,33 +708,17 @@ impl TpeSampler {
let target_dist = Distribution::Int(d.clone()); let target_dist = Distribution::Int(d.clone());
let good_values: Vec<i64> = good_trials let good_values: Vec<i64> = good_trials
.iter() .iter()
.filter_map(|t| { .filter_map(|t| match find_matching_value(t, &target_dist)? {
t.distributions.iter().find_map(|(id, dist)| { ParamValue::Int(i) => Some(*i),
if *dist == target_dist { _ => None,
t.params.get(id).and_then(|v| match v {
ParamValue::Int(i) => Some(*i),
_ => None,
})
} else {
None
}
})
}) })
.collect(); .collect();
let bad_values: Vec<i64> = bad_trials let bad_values: Vec<i64> = bad_trials
.iter() .iter()
.filter_map(|t| { .filter_map(|t| match find_matching_value(t, &target_dist)? {
t.distributions.iter().find_map(|(id, dist)| { ParamValue::Int(i) => Some(*i),
if *dist == target_dist { _ => None,
t.params.get(id).and_then(|v| match v {
ParamValue::Int(i) => Some(*i),
_ => None,
})
} else {
None
}
})
}) })
.collect(); .collect();
@@ -764,33 +748,17 @@ impl TpeSampler {
let target_dist = Distribution::Categorical(d.clone()); let target_dist = Distribution::Categorical(d.clone());
let good_indices: Vec<usize> = good_trials let good_indices: Vec<usize> = good_trials
.iter() .iter()
.filter_map(|t| { .filter_map(|t| match find_matching_value(t, &target_dist)? {
t.distributions.iter().find_map(|(id, dist)| { ParamValue::Categorical(i) => Some(*i),
if *dist == target_dist { _ => None,
t.params.get(id).and_then(|v| match v {
ParamValue::Categorical(i) => Some(*i),
_ => None,
})
} else {
None
}
})
}) })
.collect(); .collect();
let bad_indices: Vec<usize> = bad_trials let bad_indices: Vec<usize> = bad_trials
.iter() .iter()
.filter_map(|t| { .filter_map(|t| match find_matching_value(t, &target_dist)? {
t.distributions.iter().find_map(|(id, dist)| { ParamValue::Categorical(i) => Some(*i),
if *dist == target_dist { _ => None,
t.params.get(id).and_then(|v| match v {
ParamValue::Categorical(i) => Some(*i),
_ => None,
})
} else {
None
}
})
}) })
.collect(); .collect();