Simulation Optimization Methods - The Cross-Entropy Method
Advanced / supplementary — beyond the textbook treatment
Manuel D. Rossetti, PhD P.E.
Learning Objectives
To be able to describe the simulation optimization setting in which the Cross-Entropy (CE) method applies and explain why it is an attractive approach.
To be able to explain the “associated stochastic problem” (ASP) reformulation that connects optimization to probability estimation.
To be able to trace through the two-phase generate/update loop of Algorithm 3.3 and identify the role of each step.
To be able to derive and interpret the analytic update equations for the Normal sampler, including the smoothed updating rule.
To be able to identify every key tuning parameter of the CE algorithm, state its role, and give practical guidance on setting it.
To be able to map the KSL classes CESampler, CENormalSampler, and CrossEntropySolver to their corresponding algorithmic steps.
Part 1: Motivation and Problem Setting
The Simulation Optimization Problem
General continuous SO problem:
\[\min_{\mathbf{x} \in X \subseteq \mathbb{R}^d} \; E[G(\mathbf{x})]\]
where:
\(G : \mathbf{x} \mapsto \mathbb{R}\) is a black-box objective, observable only through a stochastic simulation oracle.
At any \(\mathbf{x}\), only a noisy estimate \(\hat{G}(\mathbf{x})\) is available.
The feasible region \(X\) may be continuous, integer-ordered, or mixed.
Practical examples: system configuration parameters, staffing levels, inventory policies, service rates, resource allocation vectors.
Where CE Fits in the SO Landscape
Local Search
Global Search
Finite/Discrete
COMPASS, R-SPLINE
Ranking & Selection, Nested Partition
Continuous
Gradient-based SA
CE, Genetic Algorithms, Simulated Annealing
CE targets global search on continuous (or mixed) domains.
Key properties that make CE attractive for simulation optimization:
Derivative-free — uses only function evaluations, no gradient information needed.
Noise-tolerant — the population-based update naturally averages out stochastic noise.
Self-tuning — the sampling distribution automatically narrows around good regions.
Flexible — the parametric family \(\{f(\cdot;\mathbf{v})\}\) can be chosen to match the problem structure.
CE Origins and Scope
The CE method was originally developed for rare event probability estimation (Rubinstein, 1997).
It was quickly recognized that the same adaptive sampling framework could be redirected toward combinatorial and continuous optimization (Rubinstein, 1999).
In this presentation:
Rare event simulation is mentioned only as motivation for the underlying mathematics.
The focus is entirely on CE as a simulation optimization algorithm.
All KSL implementation details refer to the optimization use case.
Part 2: The CE Philosophy
The Associated Stochastic Problem (ASP)
Key idea: transform the optimization problem into a probability estimation problem.
For the maximization problem \(\gamma^* = \min_{\mathbf{x} \in X} G(\mathbf{x})\), define for any threshold \(\gamma\):
where \(\mathbf{X}\) is drawn from a parametric family of densities \(f(\cdot; \mathbf{u})\) on \(X\).
The connection to optimization:
When \(\gamma = \gamma^*\) and \(f(\cdot;\mathbf{u})\) is uniform, \(\ell(\gamma^*)\) is very small — a “rare event.”
The CE method finds a reference parameter \(\mathbf{v}^*\) such that \(f(\cdot;\mathbf{v}^*)\) concentrates mass near \(\mathbf{x}^*\).
As \(\mathbf{v}_t \to \mathbf{v}^*\), the sampling distribution degenerates to a point mass at the optimum.
The Parametric Family and Reference Parameter
Choose a parametric family\(\{f(\cdot;\mathbf{v}), \, \mathbf{v} \in V\}\) on \(X\).
For continuous problems: product of independent Normal distributions, one per dimension.
For discrete problems: product of independent Bernoulli distributions.
The parameter vector \(\mathbf{v}\) (called the reference parameter) fully describes the current sampling distribution.
Goal of the CE algorithm: construct a sequence \(\hat{\mathbf{v}}_0, \hat{\mathbf{v}}_1, \hat{\mathbf{v}}_2, \ldots\) such that:
The threshold \(\hat{\gamma}_t\) rises toward \(\gamma^*\) at each iteration.
\(f(\cdot;\hat{\mathbf{v}}_t)\) concentrates progressively more mass near \(\mathbf{x}^*\).
The sequence terminates when the distribution has collapsed — i.e., it has converged to a near-degenerate distribution.
The Two-Phase Loop
Every CE iteration consists of exactly two phases:
repeat until convergence:
Phase 1 — GENERATE
Draw N candidates X_1, ..., X_N ~ f(·; v_{t-1})
Evaluate G(X_i) for each i via the simulation oracle
Phase 2 — UPDATE
Identify the elite set: top ρ·N performers
Re-fit v_t by maximizing the log-likelihood over the elite set
(Equivalent to computing the MLE of v restricted to the elites)
This is the entire algorithm. All complexity lies in:
choosing the family \(f(\cdot;\mathbf{v})\),
deriving the analytic update formula, and
specifying when to stop.
The Kullback-Leibler Divergence — One Slide
The update formula is derived by minimizing the Kullback-Leibler (KL) divergence between the ideal sampling distribution \(g^*\) and \(f(\cdot;\mathbf{v})\):
Remark (Remark 3.5 in de Boer et al.): the update formula is exactly the maximum likelihood estimator of \(\mathbf{v}\) computed from only the elite samples.
Range/4 approximates \(\sigma\) for a uniform distribution over \([\text{lb}_j, \text{ub}_j]\) — giving broad initial coverage.
initialVariabilityFactor\(> 1\) increases spread (more exploration); \(< 1\) decreases it (exploitation near the midpoint).
Guard condition: the algorithm rejects an initialization where \(\sigma_{0,j}\) is already below the CV-based convergence threshold. This prevents inadvertently starting in a “converged” state.
Elite Sample Selection and Update
Elite selection: sort the \(N\) evaluated solutions by \(S(\mathbf{X}_i)\) in descending order; retain the top \(N_\text{elite} = \lfloor \rho \cdot N \rfloor\) as the elite set \(E_t\).
Analytic update (pre-smoothing):
For each dimension \(j\), compute statistics over \(E_t\):
where \(\epsilon\) is the desired half-width on the proportion estimate. Defaults: \(\rho=0.1\), 95% confidence, \(\epsilon=0.1\).
Effect of \(N\): - Too small \(\Rightarrow\) noisy elite set, unstable parameter updates, premature convergence. - Too large \(\Rightarrow\) excessive oracle calls per iteration, slow progress.
Elite Fraction \(\rho\) — elitePct
Role: determines the performance threshold \(\hat{\gamma}_t\) and the number of elites used to update \(\mathbf{v}_t\).
\[N_\text{elite} = \max\!\left(\lfloor \rho \cdot N \rfloor, \; N_\text{elite}^{\min}\right)\]
Effect of \(\rho\):
\(\rho\)
\(N_\text{elite}\)
Threshold \(\hat{\gamma}_t\)
Character
Large (0.2–0.3)
Many elites
Low (easy to exceed)
More exploration, slower rise of \(\gamma_t\)
Small (0.01–0.05)
Few elites
High (only the very best)
Faster convergence, risk of noisy update
Recommended
0.05–0.10
Balanced
Reliable in most problems
Interaction with \(N\):\(N_\text{elite} = \rho N\) must remain large enough to estimate mean and standard deviation. A minimum of 5 elites is enforced by defaultMinEliteSize.
Role: dampen oscillations in the parameter update and prevent premature distribution collapse.
Effect of \(\alpha\) (applies to both mean and SD smoothers):
\(\alpha\)
Memory
Behavior
1.0
No memory
Direct MLE update — fastest adaptation, highest risk of collapse
0.85 (default)
Strong memory
Smooth gradual convergence — recommended starting point
0.4–0.6
Moderate memory
Faster adaptation; suitable for well-behaved unimodal problems
Near 0
Very strong memory
Almost no adaptation — distribution barely changes
Practical tip: if the solver converges too quickly to a poor solution, increase \(\alpha\) (more smoothing). If convergence is very slow, decrease \(\alpha\) slightly.
Key design benefit:CrossEntropySolver is distribution-agnostic. Any subclass of CESampler (e.g., a Bernoulli sampler for discrete problems) can be plugged in without modifying the solver.
When a sampler is handed to the solver it is attached: adopted onto the solver’s own stream provider on a stream distinct from the solver’s base stream, keeping random number management consistent throughout the hierarchy.
Part 7: CESampler — The Sampling Contract
Abstract Class Overview
abstractclass CESampler(valproblemDefinition:ProblemDefinition,streamNum:Int=0,streamProvider:RNStreamProviderIfc= RNStreamProvider(),):MVSampleIfc,RNStreamControlIfc{varstreamProvider: RNStreamProviderIfc = streamProviderprivateset// replaced when attached to a solvervarrnStream: RNStreamIfc = streamProvider.rnStream(streamNum)privatesetvalstreamNumber:Intget()= streamProvider.streamNumber(rnStream)// Distribution-specific contract (implemented by CENormalSampler):abstractfunupdateParameters(elites:List<DoubleArray>)// update v_t from elitesabstractfunparameters():DoubleArray// current v_tabstractfuninitializeParameters(values:DoubleArray)// set v_0abstractfunhasConverged():Boolean// CV-based stop check// sample(array)/sample(n): inherited from MVSampleIfc — draw X ~ f(·; v_{t-1})}
Inherits from:
MVSampleIfc — provides sample(n: Int): List<DoubleArray> for drawing a population.
RNStreamControlIfc — provides resetStartStream(), advanceToNextSubStream(), antithetic option, etc.
Interface-to-Algorithm Mapping
Method
Algorithm 3.3 Step
Description
initializeParameters(values)
Initialization
Sets \(\hat{\mathbf{v}}_0\) from a starting point
sample(array)
Step 1 — Generate
Draws one \(\mathbf{X}_i \sim f(\cdot;\hat{\mathbf{v}}_{t-1})\); called \(N\) times
updateParameters(elites)
Steps 3–5 — Update
Receives the elite set; computes and stores \(\hat{\mathbf{v}}_t\) with smoothing
hasConverged()
Step 6 — Stop check
Returns true when CV criterion is met for all dimensions
parameters()
Inspection
Returns a copy of the current \(\hat{\mathbf{v}}_t\) for logging or diagnostics
Note: the base class makes no assumption about which parametric family is used. It only specifies what operations any CE sampler must support.
overridefuninitializeParameters(values:DoubleArray){// Assign initial means from the supplied starting point (e.g. midpoints)for(i in values.indices){ require(values[i].isFinite()) myMeans[i]= values[i] myEliteStats[i].reset()}// Assign initial standard deviations from input rangesvalranges= problemDefinition.inputRangesfor(i in values.indices){ myStdDevs[i]=(ranges[i]/4.0)* initialVariabilityFactorvalstdDevThreshold= abs(myMeans[i])* cvThreshold require(myStdDevs[i]> stdDevThreshold){"Initial σ (${myStdDevs[i]}) for ${problemDefinition.inputNames[i]}"+" already below stopping threshold ($stdDevThreshold)."}}}
Algorithm mapping:
values\(\leftarrow\) starting point supplied by CrossEntropySolver.initializeIterations().
Algorithm mapping: implements Step 1 of Algorithm 3.3 — one component of a single candidate \(\mathbf{X}_i\) is drawn per call. CrossEntropySolver.mainIteration() calls this \(N\) times via the inherited MVSampleIfc.sample(n).
Important:rNormal(mean, variance) takes the variance\(\sigma^2\) as its second argument, not the standard deviation. The squaring myStdDevs[i] * myStdDevs[i] is intentional.
overridefunupdateParameters(elites:List<DoubleArray>){ require(elites.isNotEmpty())// Reset and collect statistics over the elite set myEliteStats.forEach { it.reset()}for(array in elites){for(i in array.indices){ myEliteStats[i].collect(array[i])// accumulate X_ij values}}// Apply smoothed update for means and standard deviationsfor(i in myMeans.indices){// μ_t = α_μ · μ_{t-1} + (1-α_μ) · mean_elite myMeans[i]= meanSmoother * myMeans[i]+(1.0- meanSmoother)* myEliteStats[i].averageif(myEliteStats[i].count >=2){// σ_t = α_σ · σ_{t-1} + (1-α_σ) · sd_elite myStdDevs[i]= sdSmoother * myStdDevs[i]+(1.0- sdSmoother)* myEliteStats[i].standardDeviation}// If count < 2: skip σ update (cannot estimate variance from one point)}}
Algorithm mapping: implements Steps 3–5 of Algorithm 3.3. The elites list is the output of CrossEntropySolver.findEliteSolutions().
hasConverged() — CV-Based Stopping
overridefunhasConverged():Boolean{for(i in myStdDevs.indices){// If any dimension's σ exceeds its threshold, NOT convergedif(myStdDevs[i]> abs(myMeans[i])* cvThreshold)returnfalse}returntrue// All dimensions satisfied: distribution has collapsed}// Inspection property: shows threshold for each dimensionvalstdDeviationThresholds: DoubleArrayget()= DoubleArray(dimension){ abs(myMeans[it])* cvThreshold }
Algorithm mapping: implements the distribution-convergence branch of Step 6 of Algorithm 3.3.
This is equivalent to requiring the coefficient of variation \(\text{CV}_j = \hat{\sigma}_{t,j} / |\hat{\mu}_{t,j}|\) to be below \(\delta_\text{CV}\) for every dimension simultaneously.
Printing the sampler mid-run shows current \(\boldsymbol{\mu}\), \(\boldsymbol{\sigma}\), and their convergence thresholds — useful for diagnosing premature or stalled convergence.
ceSampler defaults to a CENormalSampler — can be replaced with any CESampler subclass.
The sampler is attached onto the solver’s own stream provider, on a stream distinct from the solver’s base stream.
replicationsPerEvaluation controls the noise level at each oracle call — a key trade-off parameter.
initializeIterations() — Algorithm Initialization
overridefuninitializeIterations(){// Obtain the starting point (user-supplied or randomly generated)valinitialPoint= startingPoint ?: startingPoint()// Initialize the CE sampler's parameters from the starting point// This sets μ_0 = starting input values, σ_0 from input ranges ceSampler.initializeParameters(initialPoint.inputValues) myEliteSolutions.clear() solutionChecker.clear()}
Algorithm mapping: implements the initialization step of Algorithm 3.3 — sets \(\hat{\mathbf{v}}_0 = \mathbf{u}\).
The starting point is either: - User-supplied via startingPoint property, or - Generated by startingPoint() which uses StochasticSolver’s feasibility-sampling mechanism.
mainIteration() — The CE Loop Body (Part 1)
overridefunmainIteration(){// ── Step 1: Generate the CE population ──────────────────────────────// Draws N candidates X_1,...,X_N ~ f(·; v_{t-1})valpoints= ceSampler.sample(sampleSize())// N × d matrix// Convert raw Double arrays to InputMap objects (respects feasibility)valinputs= problemDefinition.convertPointsToInputs(points)// ── Step 2: Evaluate each candidate via the simulation oracle ───────// Each evaluation uses `replicationsPerEvaluation` replicationsvalevaluations= requestEvaluations(inputs)valresults= evaluations.values.toList()if(results.isEmpty())return// Safety: no evaluations returned
Algorithm mapping: Steps 1–2 of Algorithm 3.3. The oracle evaluations are the dominant computational cost.
mainIteration() — The CE Loop Body (Part 2)
// ── Step 3: Identify elite set ───────────────────────────────────────// Sort by performance; retain top eliteSize() solutions// eliteSize() = max(⌈elitePct · ceSampleSize⌉, defaultMinEliteSize) myEliteSolutions = findEliteSolutions(results)// sorted, top-ρN// ── Steps 4–5: Update sampler parameters (with smoothing) ───────────// Extract the input coordinate arrays from elite Solution objectsvalelitePoints= extractSolutionInputPoints(myEliteSolutions)// Delegates to CENormalSampler.updateParameters():// - computes mean and SD of elitePoints per dimension// - applies exponential smoothing ceSampler.updateParameters(elitePoints)// ── Track best solution ────────────────────────────────────────────── currentSolution = myEliteSolutions.first()// best among elites solutionChecker.captureSolution(currentSolution)}
Algorithm mapping: Steps 3–5 and best-solution tracking of Algorithm 3.3.
isStoppingCriteriaSatisfied() — Step 6
overridefunisStoppingCriteriaSatisfied():Boolean{return solutionQualityEvaluator?.isStoppingCriteriaReached(this)?: checkForConvergence()}privatefuncheckForConvergence():Boolean{// Criterion 1: stagnation — no improvement in best solution// for `noImproveThreshold` consecutive iterations// Criterion 2: distribution convergence — CENormalSampler.hasConverged()// all σ_j ≤ |μ_j| · δ_CVreturn solutionChecker.checkSolutions()|| ceSampler.hasConverged()}
Two independent stopping criteria — either triggers termination:
Check
Implemented by
Algorithm meaning
solutionChecker.checkSolutions()
SolutionChecker
Best solution stagnated for \(d\) iterations
ceSampler.hasConverged()
CENormalSampler
CV criterion met in all dimensions
Elite Size Determination — eliteSize()
funeliteSize():Int{// If a custom function is provided, use it (allows dynamic elite sizing)return eliteSizeFn?.eliteSize(this)// Otherwise: max(⌈ρ·N⌉, defaultMinEliteSize)?: maxOf(ceil(elitePct * ceSampleSize).toInt(), defaultMinEliteSize)}
The EliteSizeIfc and SampleSizeFnIfc functional interfaces allow users to supply dynamic sizing strategies:
This supports adaptive variants analogous to the FACE algorithm described in de Boer et al.
Part 10: Parameter Configuration and Practical Usage
Basic Constructor Call
// Create a problem and evaluator (problem-specific)valproblem= ProblemDefinition(...)valevaluator= MySimulationEvaluator(...)// Build a CENormalSampler — tune algorithmic parameters herevalsampler= CENormalSampler( problemDefinition = problem, meanSmoother =0.85,// α_μ: smoothing on means sdSmoother =0.85,// α_σ: smoothing on SDs coefficientOfVariationThreshold =0.03// δ_CV: convergence threshold)sampler.initialVariabilityFactor =1.0// starting σ multiplier// Build the solvervalsolver= CrossEntropySolver( problemDefinition = problem, evaluator = evaluator, ceSampler = sampler, maxIterations =100,// hard iteration limit replicationsPerEvaluation =10// oracle noise level)// Tune population parameterssolver.ceSampleSize =50// Nsolver.elitePct =0.10// ρ = 10%solver.runAllIterations()println(solver.bestSolution)
Global Defaults via Companion Objects
Both CENormalSampler and CrossEntropySolver expose companion object properties that act as global defaults for all subsequently created instances:
// Change defaults before creating any solversCENormalSampler.defaultMeanSmoother =0.80CENormalSampler.defaultStdDevSmoother =0.80CENormalSampler.defaultCoefficientOfVariationThreshold =0.02CENormalSampler.defaultInitialVariabilityFactor =1.2CrossEntropySolver.defaultElitePct =0.05CrossEntropySolver.defaultMinEliteSize =10CrossEntropySolver.defaultNoImproveThresholdForCE =7CrossEntropySolver.defaultMaxCESampleSize =200CrossEntropySolver.ceDefaultMaxIterations =150
Use case: when running a batch of experiments with the same parameter profile, set the companion object defaults once rather than configuring each solver instance individually.
Diagnostic Guidance
Symptom
Likely Cause
Remedy
Converges too quickly to a poor solution
\(\alpha\) too small (distribution collapses fast)
Increase meanSmoother / sdSmoother toward 0.9
Very slow convergence, many iterations
\(\alpha\) too large or \(N_\text{elite}\) too small
Decrease smoothers slightly; increase elitePct
Elite set always size 5 (minimum)
ceSampleSize too small for elitePct
Increase ceSampleSize or increase elitePct
Standard deviation hits zero in one dimension
cvThreshold too loose
Decrease cvThreshold; check for unbounded input range
Algorithm stops on stagnation, not CV
Solution found is good but distribution still wide
CE reformulates optimization as adaptive importance sampling. The sampling distribution \(f(\cdot;\hat{\mathbf{v}}_t)\) is steered toward the optimum by repeatedly fitting it to the elite performers.
The update step is analytic for the Normal family. Means and standard deviations of the elite set are computed as sample statistics — no numerical optimization required inside the loop.
Smoothed updating is essential. Without it, the distribution collapses prematurely. The default smoothers (\(\alpha = 0.85\)) represent a good starting point for most problems.
Two stopping criteria work together. CV-based convergence confirms the distribution has degenerated to a solution; stagnation-based stopping provides a safety net when convergence is slow.
CrossEntropySolver is distribution-agnostic. Swapping CENormalSampler for any other CESampler subclass immediately generalizes CE to discrete, integer, or custom domains.
Diagnosis starts with toString(). Printing the sampler state reveals current means, standard deviations, and thresholds — enabling rapid identification of parameter tuning needs.
When to Choose CE
Scenario
Recommendation
Continuous decision variables, no gradient available
CE with Normal sampler is ideal
Multi-extremal or unknown landscape
CE’s global search character is an advantage
Integer or discrete variables
Use CE with an appropriate sampler (e.g., Bernoulli)
Low-dimensional, unimodal, fast oracle
Simpler local methods may suffice
Hard budget constraint on oracle calls
Tune \(N\) carefully; use recommendCESampleSize()
Integer-ordered local optimum required with convergence guarantee
Prefer R-SPLINE
References
de Boer, P.-T., Kroese, D. P., Mannor, S., and Rubinstein, R. Y. (2005). A Tutorial on the Cross-Entropy Method. Annals of Operations Research, 134(1), 19–67.
Rubinstein, R. Y. (1999). The simulated entropy method for combinatorial and continuous optimization. Methodology and Computing in Applied Probability, 2, 127–190.
Rubinstein, R. Y. and Kroese, D. P. (2004). The Cross-Entropy Method: A Unified Approach to Combinatorial Optimization, Monte-Carlo Simulation and Machine Learning. Springer.
Chen, H. and Kelton, W. D. (1999). Quantile and tolerance-interval estimation in simulation. Proceedings of the 1999 Winter Simulation Conference.
Rossetti, M. D. KSL — Kotlin Simulation Library. ksl.simopt.solvers.algorithms.