Samplers
A sampler decides which hyperparameter values to try next, given the history of the study. It is
the search strategy: random, grid, Bayesian (TPE / Gaussian process), evolutionary (CMA-ES, NSGA),
quasi-Monte-Carlo, or exhaustive. OptunaSharp ports Optuna's entire sampler surface into
OptunaSharp.Samplers, reproducing each algorithm's output bit-for-bit under a fixed seed wherever
the numeric path is deterministic.
Choosing a sampler
using OptunaSharp;
using OptunaSharp.Samplers;
var study = Optuna.CreateStudy(sampler: new TPESampler(seed: 42));
The default is TPESampler for single-objective studies and NSGAIISampler for multi-objective
ones — the same defaults as Optuna.
| Sampler | Best for | Constructor (key parameters) |
|---|---|---|
RandomSampler |
Baselines, cheap high-dimensional search | (int? seed = null) |
GridSampler |
Small, fully-enumerable grids | (IReadOnlyDictionary<string, IReadOnlyList<CategoricalChoice>> searchSpace, int? seed = null) |
TPESampler |
The general-purpose default; mixed spaces | (nStartupTrials: 10, nEiCandidates: 24, multivariate: false, group: false, constantLiar: true, seed: null, …) |
CmaEsSampler |
Continuous, moderately high-dimensional | (nStartupTrials: 1, useSeparableCma: false, withMargin: false, lrAdapt: false, restartStrategy: null, seed: null, …) |
GPSampler |
Expensive objectives, small trial budgets | (nStartupTrials: 10, deterministicObjective: false, seed: null, …) |
QMCSampler |
Low-discrepancy space filling | (qmcType: "sobol", scramble: false, seed: null, …) |
NSGAIISampler |
Multi-objective (2–3 objectives) | (populationSize: 50, crossover: null, mutation: null, seed: null, …) |
NSGAIIISampler |
Multi-objective (many objectives) | (populationSize: 50, referencePoints: null, dividingParameter: 3, seed: null, …) |
BruteForceSampler |
Exhaustive define-by-run search | (int? seed = null, bool avoidPrematureStop = false) |
PartialFixedSampler |
Fix some params, search the rest | (IReadOnlyDictionary<string, object?> fixedParams, BaseSampler baseSampler) |
Pass seed: for reproducibility. All RNG flows through a LazyRandomState built on NumSharp's
MT19937, never System.Random — this is what makes runs bit-identical to Python.
Reproducibility under parallelism. A seeded sampler reproduces a run bit-for-bit only with
nJobs: 1. Whenstudy.Optimize(..., nJobs: n)withn != 1spawns more than one worker, each worker reseeds its sampler — so parallel workers don't draw the same parameters — exactly as Optuna does, which makes a multi-worker run not reproducible. For distributed optimization across processes, for the same reason use eitherseed: nullor a different seed value per process.
Capabilities, cost, and budget
The nine samplers OptunaSharp ports 1-to-1 from Optuna support the following, reproducing Optuna's own capability matrix. Read the marks as ✓ supported · ▲ works, but inefficiently · ✗ errors or has no interface.
Parameter types and search space
| Sampler | Float | Int | Categorical | Conditional | Multivariate |
|---|---|---|---|---|---|
RandomSampler |
✓ | ✓ | ✓ | ✓ | ▲ |
TPESampler |
✓ | ✓ | ✓ | ✓ | ✓ |
GPSampler |
✓ | ✓ | ✓ | ▲ | ✓ |
CmaEsSampler |
✓ | ✓ | ▲ | ▲ | ✓ |
NSGAIISampler |
▲ | ▲ | ✓ | ▲ | ▲ |
NSGAIIISampler |
▲ | ▲ | ✓ | ▲ | ▲ |
GridSampler |
✓ † | ✓ | ✓ | ▲ | ▲ |
QMCSampler |
✓ | ✓ | ▲ | ▲ | ▲ |
BruteForceSampler |
✓ † | ✓ | ✓ | ✓ | ▲ |
† ✗ for an infinite (unbounded) float domain.
Optimization modes
| Sampler | Pruning | Multi-objective | Constrained | Batch | Distributed |
|---|---|---|---|---|---|
RandomSampler |
✓ | ✓ | ▲ | ✓ | ✓ |
TPESampler |
✓ | ✓ | ✓ | ✓ | ✓ |
GPSampler |
▲ | ✓ | ✓ | ✓ | ✓ |
CmaEsSampler |
▲ | ✗ | ▲ | ✓ | ✓ |
NSGAIISampler |
✗ ‡ | ✓ ‡ | ✓ | ✓ | ✓ |
NSGAIIISampler |
✗ ‡ | ✓ ‡ | ✓ | ✓ | ✓ |
GridSampler |
✓ | ✓ | ▲ | ✓ | ✓ |
QMCSampler |
✓ | ✓ | ▲ | ✓ | ✓ |
BruteForceSampler |
✓ | ✓ | ▲ | ✓ | ✓ |
‡ The NSGA samplers are multi-objective by design: ✗/✓ shown is for multi-objective use, but each
degrades to ▲ on a single-objective study.
Cost and recommended budget
| Sampler | Time complexity per trial | Recommended budget (#trials) |
|---|---|---|
RandomSampler |
O(d) | as many as you like |
TPESampler |
O(dn log n) | 100–1000 |
GPSampler |
O(n³) | up to ~500 |
CmaEsSampler |
O(d³) | 1000–10000 |
NSGAIISampler |
O(mp²) | 100–10000 |
NSGAIIISampler |
O(mp²) | 100–10000 |
GridSampler |
O(dn) | number of combinations |
QMCSampler |
O(dn) | as many as you like |
BruteForceSampler |
O(dn) | number of combinations |
where d is the search-space dimension, n the number of finished trials, m the number of
objectives, and p the population size. The complexities are for the sampling step in
single-objective optimization (the NSGA rows are the multi-objective exception); OptunaSharp also spends
O(d) to call the suggest methods and O(n) to collect finished trials, which the table omits.
The recommended budget includes any nStartupTrials a sampler takes, and depends on the number of
parameters and objectives.
Beyond these nine, OptunaSharp ports many more samplers — CMA-ES variants (separable, with-margin, LM-CMA, VkD-CMA), the natural-evolution-strategy family (OpenAI-ES, SNES, xNES), trust-region and random-embedding Bayesian optimization (TuRBO, REMBO, SAASBO), the JANUS infill family, and additional MOEAs (SMS-EMOA, AGE-MOEA-2, MOEA/D, RVEA, PSO, Differential Evolution) — each documented on its own Methods page. Optuna's table also lists two samplers that live outside Core: AutoSampler (not ported; reachable through the OptunaHub bridge) and BoTorchSampler (O(n³) per trial, 10–100 trials; available through the BoTorch bridge).
The workhorses
TPESampler
Tree-structured Parzen Estimator — Optuna's default. It models good and bad trials with kernel density estimates and samples where the ratio favors improvement. Notable options:
new TPESampler(
nStartupTrials: 10, // random trials before TPE kicks in
nEiCandidates: 24, // candidates scored per suggestion
multivariate: false, // model parameters jointly instead of independently
group: false, // with multivariate, model co-occurring params as groups
constantLiar: true, // penalize params of still-running trials (distributed runs)
constraintsFunc: null, // constrained optimization
seed: 42);
multivariate: true (optionally with group: true) captures parameter interactions;
constraintsFunc enables constrained optimization; the sampler also drives MOTPE for
multi-objective studies.
CmaEsSampler
Covariance Matrix Adaptation Evolution Strategy, for continuous spaces. One class covers all four
backends: full-covariance CMA (default), separable CMA (useSeparableCma: true), CMA-with-margin for
discrete/mixed spaces (withMargin: true), and warm-starting from prior trials
(sourceTrials:). Restart strategies and learning-rate adaptation (lrAdapt: true) are supported.
GPSampler
Gaussian-process Bayesian optimization for expensive objectives with small budgets. The full stack —
kernel fitting, log-expected-improvement and its multi-objective / constrained / batched variants, and
the mixed continuous-discrete acquisition optimizer — is implemented natively (no TorchSharp). Set
deterministicObjective: true for noise-free objectives; constraintsFunc: for constraints.
NSGA-II and NSGA-III
Genetic algorithms for multi-objective optimization. NSGAIISampler uses non-dominated sorting with
crowding distance; NSGAIIISampler uses reference points and suits many-objective problems. Both let
you plug in a crossover and mutation operator from OptunaSharp.Samplers.Nsgaii:
using OptunaSharp.Samplers;
using OptunaSharp.Samplers.Nsgaii;
var sampler = new NSGAIISampler(
populationSize: 64,
crossover: new SBXCrossover(),
mutation: new PolynomialMutation(eta: 20.0),
seed: 42);
Available crossovers (all in OptunaSharp.Samplers.Nsgaii): UniformCrossover, BLXAlphaCrossover,
SBXCrossover, VSBXCrossover, SPXCrossover, UNDXCrossover; mutation: PolynomialMutation.
NSGAIIISampler additionally accepts referencePoints and dividingParameter.
GridSampler and BruteForceSampler
GridSampler enumerates an explicit grid you provide and stops when it is exhausted
(IsExhausted(study) reports this). BruteForceSampler enumerates a define-by-run space discovered
from the objective itself — no grid needed — and is ideal for exhaustively covering a small
conditional space.
QMCSampler and RandomSampler
RandomSampler samples independently and uniformly — a strong baseline. QMCSampler fills the space
with a low-discrepancy sequence (qmcType: "sobol" (default) or "halton"); scramble: true is
supported for both engines (scrambled Sobol' via the linear-matrix-scramble, scrambled Halton via
Owen's digit permutations), each bit-identical to Optuna/scipy.
PartialFixedSampler
Wraps another sampler and pins a subset of parameters to fixed values, letting the base sampler search the remainder — useful for freezing a known-good parameter while tuning others. The base sampler's RNG continues uninterrupted across the fixed parameters.
Writing a custom sampler
Derive from the abstract BaseSampler and implement the three-method contract Optuna uses:
public abstract OrderedDict<string, BaseDistribution> InferRelativeSearchSpace(Study study, FrozenTrial trial);
public abstract OrderedDict<string, object?> SampleRelative(Study study, FrozenTrial trial, OrderedDict<string, BaseDistribution> searchSpace);
public abstract object? SampleIndependent(Study study, FrozenTrial trial, string paramName, BaseDistribution paramDistribution);
// optional lifecycle hooks: BeforeTrial, AfterTrial, ReseedRng
SampleRelative handles parameters modeled jointly (from the inferred search space);
SampleIndependent handles the rest one at a time. Genetic samplers derive from BaseGASampler.
Relationship to Optuna
| Optuna (Python) | OptunaSharp |
|---|---|
TPESampler(multivariate=True, seed=42) |
new TPESampler(multivariate: true, seed: 42) |
CmaEsSampler(with_margin=True) |
new CmaEsSampler(withMargin: true) |
GPSampler(deterministic_objective=True) |
new GPSampler(deterministicObjective: true) |
NSGAIISampler(population_size=64) |
new NSGAIISampler(populationSize: 64) |
QMCSampler(qmc_type="sobol", scramble=True) |
new QMCSampler(qmcType: "sobol", scramble: true) |
QMCSampler(qmc_type="halton", scramble=True) |
new QMCSampler(qmcType: "halton", scramble: true) |
Divergences
These are the documented, tested differences from Optuna:
- TPESampler rides the error-function path, so its float outputs match Python only to a few ULP (bit-exact off that path); certain argsort ties and repeated discrete observations in the multivariate estimator are order-sensitive.
- CmaEsSampler's with-margin variant is tolerance-bounded rather than bit-exact. Its optimizer state is Optuna's own pickle, so C# and Python workers can share one CMA-ES study in any order; see mixed fleets.
- GridSampler may order not-yet-visited grid points differently under concurrency (cosmetic).
- NSGAIIISampler can differ on near-tie reference-point selection; UNDXCrossover uses a managed QR and diverges for 1-D search spaces.
All are marked in the source and pinned by tests.
See also
Python sampler integrations: BoTorch, PyCma, pymoo, and OptunaHub.
Study and Trials · Distributions · Search Spaces · Pruners · Multi-objective Optimization · the API reference.