Skip to main content

Tpe

Struct Tpe 

pub struct Tpe { /* private fields */ }
Expand description

A Tree-structured Parzen Estimator sampler.

Construct one with Tpe::new (the defaults it documents) and hand it to a study through StudyBuilder::sampler. The knobs below are the only configuration; everything else is derived from the study’s history on each decision. See the module-level documentation for the algorithm.

use atune_core::sampler::Tpe;
use atune_core::space::{Distribution, ParamSpec, SpaceSchema};
use atune_core::study::{Budget, Study, StudyConfig};
use std::sync::Arc;

let space = SpaceSchema::try_new([
    ParamSpec::try_new("x", Distribution::try_new_with_float(-5.0, 5.0).unwrap()).unwrap(),
    ParamSpec::try_new("y", Distribution::try_new_with_float(-5.0, 5.0).unwrap()).unwrap(),
])
.unwrap();

let study = Study::builder()
    .sampler(Arc::new(Tpe::new()))
    .budget(Budget::new_with_trials(60))
    .create(StudyConfig::new("sphere").with_seed(0).with_space(space))
    .unwrap();

study
    .optimize(|ctx| {
        let x = ctx.suggest_f64("x", -5.0..=5.0, atune_core::space::Scale::Linear)?;
        let y = ctx.suggest_f64("y", -5.0..=5.0, atune_core::space::Scale::Linear)?;
        Ok((x * x + y * y).into())
    })
    .unwrap();

// TPE concentrates near the origin, so the best is well below a random draw.
let best = study.best_trial().unwrap().unwrap();
assert!(best.single_objective_value().unwrap() < 1.0);

Implementations§

§

impl Tpe

pub const fn new() -> Self

A TPE sampler with the default settings.

n_startup_trials = 10, n_ei_candidates = 24, gamma = 0.25 (read only by the sub-linear split), prior_weight = 1.0, constant liar on, the multivariate kernel, discrete kernels, log scoring, the linear split, Optuna bandwidths and sharpening on, and constraints and MOTPE/HSSP off.

The six modelling settings became defaults together; before that each was off. Pass false to their builders (with_multivariate, with_discrete_kernels, with_log_scoring, with_linear_split, with_optuna_bandwidth, with_sharpening) to reproduce the earlier default’s seeded output bit for bit.

pub const fn with_startup_trials(self, n: usize) -> Self

Sets how many initial trials are sampled uniformly before TPE engages.

Too few and the first density models are fit on noise; the default of 10 matches Optuna.

pub const fn with_ei_candidates(self, n: usize) -> Self

Sets how many candidate points are scored per decision.

More candidates sharpen the argmax at linear cost; the default is 24.

pub const fn with_gamma(self, gamma: f64) -> Self

Sets the split quantile factor gamma in n_good = ceil(gamma·√n).

Larger gamma widens the “good” set. The raw value is retained by this infallible builder; it must be finite and in (0, 1] when sampling. Read only with with_linear_split(false): the default linear split ignores it.

pub const fn with_prior_weight(self, weight: f64) -> Self

Sets the prior weight used in every Parzen estimator.

pub const fn with_constant_liar(self, on: bool) -> Self

Turns the pending-aware constant liar on or off.

On by default. With it off, running trials are ignored and parallel workers no longer repel from one another — useful only for isolating the effect in a test.

pub const fn with_constraints(self, on: bool) -> Self

Turns the feasibility-first split over recorded constraints on or off.

Off by default, which keeps the objective-only split and a raw ask-time view. On, trials whose recorded constraints all hold (≤ 0 satisfied) are ordered by objective ahead of every violating trial, and violating trials use the order of compare_violations, so the good set l(x) comes from feasible trials while any exist. Representable f64 totals keep their existing order; finite constraint sums that overflow are ordered by their exact positive sums, and NaN or positive infinity inputs rank above finite violations. The scalar total_violation can still return +∞ for an overflowing finite sum; TPE keeps the richer rank internally. A trial that recorded no constraints counts as feasible. The study then attaches constraint values to the ask-time view (reads_constraints), at the cost of one state read per newly synced finished trial.

pub const fn constraints(&self) -> bool

Whether the split is feasibility-first over recorded constraints.

pub const fn with_multivariate(self, on: bool) -> Self

Turns multivariate TPE — one mixture of product kernels per group — on or off.

On by default. Off keeps the joint split with a product of per-parameter estimators, l(x) = Πᵈ lᵈ(xᵈ), the earlier default. On, each co-occurrence group is modelled by one mixture with a component per weighted trial (plus the prior), each component a product of per-parameter kernels: l(x) = Σₖ wₖ Πᵈ Kₖ,ᵈ(xᵈ), and candidates are drawn jointly from it. This is Optuna’s multivariate=True (_MixtureOfProductDistribution in optuna/samplers/_tpe/probability_distributions.py; Falkner et al., 2018). It captures correlation between parameters, at a cost linear in trials times parameters per candidate. Categorical parameters join the mixture with indicator kernels, or with Aitchison–Aitken kernels under with_discrete_kernels.

use atune_core::sampler::Tpe;

assert!(Tpe::new().multivariate());
assert!(!Tpe::new().with_multivariate(false).multivariate());

pub const fn multivariate(&self) -> bool

Whether each group is one mixture of product kernels.

pub const fn with_discrete_kernels(self, on: bool) -> Self

Turns Optuna’s discrete and categorical kernels on or off.

On by default. Off (the earlier default), an integer or stepped parameter is modelled by the continuous unit-interval density and snapped to its grid afterwards, and a categorical one by recency-weighted counts plus uniform prior mass. On:

  • an integer or stepped parameter’s kernel is the Gaussian’s mass on each grid point’s cell, [v − step/2, v + step/2] (for a log integer [ln(v − ½), ln(v + ½)]), truncated to the union of the cells — Optuna’s _BatchedDiscreteTruncNormDistributions and its log variant. Probability lives on the grid, so a candidate is scored by the mass of the value it will become;
  • a categorical or boolean parameter’s kernel is Aitchison–Aitken, (ε + [c = cᵢ]) / (1 + kε) with ε = prior_weight / n_kernels, Optuna’s categorical kernel (Aitchison and Aitken, 1976), which spreads every observation’s mass a little over all choices.
use atune_core::sampler::Tpe;

assert!(Tpe::new().discrete_kernels());
assert!(!Tpe::new().with_discrete_kernels(false).discrete_kernels());

pub const fn discrete_kernels(&self) -> bool

Whether discrete and categorical parameters use their own kernels.

pub const fn with_log_scoring(self, on: bool) -> Self

Turns log-space scoring on or off.

On by default. Off (the earlier default), a mixture’s density is the linear sum of its components, floored at 1e-12 before the logarithm. On, it is logsumexp over the components’ log terms with no floor, as Optuna computes it, so log l(x) − log g(x) stays finite and keeps its order where every component underflows — far from all observations, or over many dimensions of a multivariate mixture — instead of collapsing to a tie at the floor. Where nothing underflows the two agree to rounding.

use atune_core::sampler::Tpe;

assert!(Tpe::new().log_scoring());
assert!(!Tpe::new().with_log_scoring(false).log_scoring());

pub const fn log_scoring(&self) -> bool

Whether mixtures are scored in log space.

pub const fn with_hssp(self, on: bool) -> Self

Turns Optuna 5-style multi-objective TPE splitting on or off.

Off by default: a multi-objective study uses uniform random sampling, preserving the legacy behavior. On, complete and pruned observations are ranked by nondomination fronts; HSSP chooses the required subset of the last partial front. The good-set Parzen components receive uniform weights, as in Optuna 5.0.0, while the bad set keeps the usual recency weights. The split and all weights are deterministic. Single-objective studies keep their existing split and seeded output.

Exact hypervolume calculations within HSSP use the existing WFG path for objective counts above three.

use atune_core::sampler::Tpe;

assert!(Tpe::new().with_hssp(true).hssp());
assert!(!Tpe::new().hssp());

pub const fn hssp(&self) -> bool

Whether multi-objective studies use nondomination ranks and HSSP.

pub const fn with_linear_split(self, on: bool) -> Self

Turns Optuna’s linear-capped split on or off.

On by default: the good set holds min(ceil(0.1·n), 25) of the n ranked observations, the default_gamma that Optuna’s TPESampler uses unless told otherwise, and with_gamma is not read (it is still validated). Off (the earlier default), it holds ceil(gamma·√n): Optuna’s hyperopt_default_gamma without its cap at 25, so the two agree up to n = (25 / gamma)². Either rule is clamped so both sets stay non-empty, and the multi-objective HSSP split takes its size from the same rule.

use atune_core::sampler::Tpe;

assert!(Tpe::new().linear_split());
assert!(!Tpe::new().with_linear_split(false).linear_split());

pub const fn linear_split(&self) -> bool

Whether the split is min(ceil(0.1·n), 25) instead of ceil(gamma·√n).

pub const fn with_optuna_bandwidth(self, on: bool) -> Self

Turns Optuna’s numeric bandwidth rule on or off.

On by default. Off (the earlier default), every numeric kernel’s sigma is the larger gap to its neighbours, the prior’s mean among them, clamped below by one floor, 1/n of the unit width for n split observations (within [0.005, 1]), shared by l and g. On, each estimator follows Optuna’s _ParzenEstimator with its default consider_endpoints=False and consider_magic_clip=True: gaps among its own observations only, the outermost taking their inner gap, clamped below by width / min(100, n + 2) for its own n observations, where an integer or stepped axis under the discrete kernels has its cells’ extended width.

use atune_core::sampler::Tpe;

assert!(Tpe::new().optuna_bandwidth());
assert!(!Tpe::new().with_optuna_bandwidth(false).optuna_bandwidth());

pub const fn optuna_bandwidth(&self) -> bool

Whether numeric bandwidths follow Optuna’s per-estimator rule.

pub const fn with_sharpening(self, on: bool) -> Self

Turns sharpening of the multivariate kernel on or off.

On by default (atune’s own; off is the earlier default). On, and with with_multivariate, each decision scores twice n_ei_candidates, drawn alternately from l and from a sharpened l′, and keeps the argmax of log l′ − log g′. Once a group has 30 observations (three default startup phases), l′ and g′ are its mixtures rebuilt with Optuna’s neighbour gaps and a lower floor, the smaller of Optuna’s magic clip and 0.08 · width · (n + 2)^(−1/d) for n observations over d parameters; before that they are l and g. The floor scales as the observations’ nearest-neighbour spacing, so the kernels sharpen most in few dimensions, and the unsharpened proposals keep exploring. Without the multivariate kernel the setting has no effect, and it stands aside for a group once any observation drew a parameter from a different distribution than the one modelled — an open range that grew, whose optimum lies outside the region sharpening would refine.

use atune_core::sampler::Tpe;

assert!(Tpe::new().sharpening());
assert!(!Tpe::new().with_sharpening(false).sharpening());

pub const fn sharpening(&self) -> bool

Whether the multivariate kernel adds sharpened candidates and scoring.

Trait Implementations§

§

impl Clone for Tpe

§

fn clone(&self) -> Tpe

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Copy for Tpe

§

impl Debug for Tpe

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
§

impl Default for Tpe

§

fn default() -> Self

Returns the “default value” for a type. Read more
§

impl PartialEq for Tpe

§

fn eq(&self, other: &Tpe) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
§

impl Sampler for Tpe

§

fn reads_constraints(&self) -> bool

with_constraints: only a feasibility-first split needs the recorded constraint values on its view.

§

fn infer_relative_space(&self, study: &StudyView) -> Result<SpaceSchema>

The study’s declared space, or the intersection of the visible trials’ parameters for a define-by-run study.

Identical in shape to Random: a declared space is returned verbatim, and an inferred one keeps only the parameters every visible trial recorded compatibly, name-ordered. The group decomposition then runs inside sample_relative; for a co-occurring space it is a single group.

§Errors

Error::InvalidSpace if a recorded parameter name is empty, which no writer of this crate produces, or if one of the sampler’s configuration knobs is invalid.

§

fn sample_relative( &self, study: &StudyView, trial: &TrialMeta, space: &SpaceSchema, ) -> Result<Assignment>

Samples the joint space with multivariate, group-decomposed TPE.

Each co-occurrence group is modelled jointly and the constant liar folds in the pending trials; a group short of n_startup_trials completed observations (including the whole study during startup) is drawn uniformly. Every returned value lies in its declared support, because it is produced through Distribution::from_unit or a valid choice index.

§Errors

Error::InvalidSpace if a declared distribution is malformed or one of the sampler’s configuration knobs is invalid.

§

fn sample_independent( &self, study: &StudyView, trial: &TrialMeta, name: &str, dist: &Distribution, ) -> Result<ParamValue>

Samples one parameter with univariate TPE — the fallback for a parameter the relative space did not cover, or one outside any joint group.

Uses the same joint split (by trial objective) and the same constant liar as the relative path, over the trials that recorded this parameter. Below the startup threshold it is the uniform Random draw.

§Errors

Error::InvalidSpace if dist is malformed or one of the sampler’s configuration knobs is invalid.

§

fn state(&self) -> Result<Option<StateBlob>>

A no-op, and deliberately so: TPE holds no persistable state.

Everything it knows is recomputed from the StudyView each decision, so there is nothing to write. See the module-level documentation — this is what makes a journal reopen continue identically.

§Errors

Never.

§

fn reseed(&self, _seed: u64)

A no-op.

TPE derives its candidate RNG from TrialMeta::sampler_seed, so there is no auxiliary randomness for a reseed to move — the same argument Random::reseed documents. Change the study seed to change what a study samples.

§

fn after_trial(&self, study: &StudyView, trial: &FrozenTrial) -> Result<()>

Called once a trial reaches a terminal state. Read more
§

fn snapshots_space(&self) -> bool

Whether this sampler enumerates a snapshot of the space it was constructed with, rather than the space each ask presents. Read more
§

fn restore_state(&self, blob: &SamplerState) -> Result<()>

Restores a sampler from a previously persisted blob. Read more
§

impl StructuralPartialEq for Tpe

Auto Trait Implementations§

§

impl Freeze for Tpe

§

impl RefUnwindSafe for Tpe

§

impl Send for Tpe

§

impl Sync for Tpe

§

impl Unpin for Tpe

§

impl UnsafeUnpin for Tpe

§

impl UnwindSafe for Tpe

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more