Section 2.6 Variance Reduction Methods

The preceding sections developed the machinery for Monte Carlo integration: we estimate integrals \(I = \mathbb{E}_f[h(X)]\) by averaging samples \(\hat{I}_n = n^{-1}\sum_{i=1}^n h(X_i)\). The Law of Large Numbers guarantees convergence, and the Central Limit Theorem quantifies uncertainty through the asymptotic relationship \(\sqrt{n}(\hat{I}_n - I) \xrightarrow{d} \mathcal{N}(0, \sigma^2)\). But there is a catch: the convergence rate \(O(n^{-1/2})\) is immutable. To halve our standard error, we must quadruple our sample size. For problems requiring high precision or expensive function evaluations, this brute-force approach becomes prohibitive.

Variance reduction methods attack this limitation not by changing the convergence rate—that remains fixed at \(O(n^{-1/2})\)—but by shrinking the constant \(\sigma^2\). The estimator variance \(\text{Var}(\hat{I}_n) = \sigma^2/n\) depends on two quantities we control: the sample size \(n\) and the variance constant \(\sigma^2\). While increasing \(n\) requires more computation, reducing \(\sigma^2\) through clever sampling strategies can achieve the same precision at a fraction of the cost.

This insight has deep roots in the history of computational science. Herman Kahn at the RAND Corporation developed importance sampling in 1949–1951 for nuclear shielding calculations, where naive Monte Carlo required billions of particle simulations to estimate rare transmission events. Jerzy Neyman’s 1934 work on stratified sampling in survey statistics established optimal allocation theory decades before computers existed. Antithetic variates, introduced by Hammersley and Morton in 1956, and control variates, systematized in Hammersley and Handscomb’s 1964 monograph, completed the classical variance reduction toolkit. These techniques, born from practical necessity, now form the foundation of computational efficiency in Monte Carlo simulation.

The methods share a common philosophy: exploit structure in the problem to reduce randomness in the estimate. Importance sampling concentrates effort where the integrand matters most. Control variates leverage correlation with analytically tractable quantities. Antithetic variates induce cancellation through negative dependence. Stratified sampling ensures balanced coverage of the domain. Common random numbers synchronize randomness across comparisons to isolate true differences from sampling noise.

This section develops five foundational variance reduction techniques with complete mathematical derivations, proofs of optimality, and practical Python implementations. We emphasize when each method excels, how to combine them synergistically, and what pitfalls await the unwary practitioner.

Road Map 🧭

  • Understand: The theoretical foundations of variance reduction—how reweighting, correlation, and stratification reduce estimator variance without changing convergence rates

  • Derive: Optimal coefficients and allocations for each method, including proofs of the zero-variance ideal for importance sampling and the Neyman allocation for stratified sampling

  • Implement: Numerically stable Python code for all five techniques, with attention to log-space calculations, effective sample size diagnostics, and adaptive coefficient estimation

  • Evaluate: When each method applies, expected variance reduction factors, and potential failure modes—especially weight degeneracy in importance sampling and non-monotonicity failures in antithetic variates

  • Connect: How variance reduction relates to the rejection sampling of Section 2.5 Rejection Sampling and motivates the Markov Chain Monte Carlo methods of later chapters

The Variance Reduction Paradigm

Before examining specific techniques, we establish the mathematical framework that unifies all variance reduction methods.

The Fundamental Variance Decomposition

For a Monte Carlo estimator \(\hat{I}_n = n^{-1}\sum_{i=1}^n Y_i\) where the \(Y_i\) are i.i.d. with mean \(I\) and variance \(\sigma^2\):

(14)\[\text{Var}(\hat{I}_n) = \frac{\sigma^2}{n} = \frac{\text{Var}(Y)}{n}\]

The mean squared error equals the variance (since \(\hat{I}_n\) is unbiased), and precision scales as \(\sigma/\sqrt{n}\). Every variance reduction method operates by constructing alternative estimators \(\tilde{Y}_i\) with the same expectation \(\mathbb{E}[\tilde{Y}] = I\) but smaller variance \(\text{Var}(\tilde{Y}) < \sigma^2\).

Variance Reduction Factor (VRF): The ratio \(\text{VRF} = \sigma^2_{\text{naive}} / \sigma^2_{\text{reduced}}\) quantifies improvement. A VRF of 10 means the variance-reduced estimator achieves the same precision as the naive estimator with 10× more samples. Equivalently, it achieves a given precision with only 10% of the computational effort.

The Methods at a Glance

Table 21 Comparison of Variance Reduction Techniques

Method

Mechanism

Key Requirement

Best Use Case

Importance Sampling

Sample from proposal \(g\), reweight by \(f/g\)

Proposal covers target support

Rare events, heavy tails

Control Variates

Subtract correlated variable with known mean

High correlation, known \(\mathbb{E}[C]\)

Auxiliary quantities available

Antithetic Variates

Use negatively correlated pairs

Monotonic integrand

Low-dimensional smooth functions

Stratified Sampling

Force balanced coverage across strata

Partition domain into regions

Heterogeneous integrands

Common Random Numbers

Share randomness across comparisons

Comparing similar systems

A/B tests, sensitivity analysis

Importance Sampling

Importance sampling transforms Monte Carlo integration by sampling from a carefully chosen proposal distribution rather than the target. By concentrating computational effort where the integrand contributes most, importance sampling can achieve variance reductions of several orders of magnitude—essential for rare event estimation where naive Monte Carlo is hopelessly inefficient.

Motivation and Problem Setup

The Core Problem: We wish to estimate the integral

\[I = \mathbb{E}_f[h(X)] = \int h(x) f(x) \, dx\]

where \(f(x)\) is a probability density (the “target”) and \(h(x)\) is a measurable function. Naive Monte Carlo draws \(X_1, \ldots, X_n \stackrel{\text{iid}}{\sim} f\) and estimates \(\hat{I}_n = n^{-1}\sum_{i=1}^n h(X_i)\) with variance \(\sigma^2/n\) where \(\sigma^2 = \text{Var}_f(h(X))\).

The Insight: Naive MC samples uniformly according to \(f\), regardless of where \(h\) is large. If \(h(x)\) is concentrated in a region where \(f(x)\) assigns low probability—as in rare event estimation—most samples contribute little to the estimate. Importance sampling redirects effort to where it matters: sample from a proposal \(g\) that puts mass where \(|h(x)|f(x)\) is large, then correct via reweighting.

Definition (Importance Sampling)

Let \(f\) be the target density and \(g\) be a proposal density satisfying the support condition: \(g(x) > 0\) whenever \(h(x)f(x) \neq 0\). The importance weight is

\[w(x) = \frac{f(x)}{g(x)}\]

Given i.i.d. samples \(X_1, \ldots, X_n \sim g\), the importance sampling estimator is

(15)\[\hat{I}_{\text{IS}} = \frac{1}{n} \sum_{i=1}^n h(X_i) w(X_i) = \frac{1}{n} \sum_{i=1}^n h(X_i) \frac{f(X_i)}{g(X_i)}\]

Intuition: Each sample \(X_i\) from \(g\) is “worth” \(w(X_i)\) samples from \(f\). Regions where \(g\) oversamples relative to \(f\) (i.e., \(g > f\)) receive low weights; regions where \(g\) undersamples receive high weights. The reweighting exactly corrects for the sampling bias.

Three-panel visualization of importance sampling showing target and proposal densities, weight function, and weighted samples

Fig. 68 Importance Sampling Concept. (a) The bimodal target \(f(x)\) and unimodal proposal \(g(x)\). We sample from \(g\) but want to estimate \(\mathbb{E}_f[h(X)]\). (b) The importance weight function \(w(x) = f(x)/g(x)\), showing high weights where the target exceeds the proposal. (c) Samples from the proposal with marker size proportional to weight—the reweighting corrects for sampling bias, effectively transforming proposal samples into target samples.

Unbiasedness and Variance

Proposition (Unbiasedness)

Under the support condition, \(\mathbb{E}_g[\hat{I}_{\text{IS}}] = I\).

Proof (2 lines):

\[\mathbb{E}_g[\hat{I}_{\text{IS}}] = \mathbb{E}_g\left[ h(X) \frac{f(X)}{g(X)} \right] = \int h(x) \frac{f(x)}{g(x)} g(x) \, dx = \int h(x) f(x) \, dx = I \quad \blacksquare\]

Square-Integrability Condition: For finite variance, we additionally require \(\mathbb{E}_g[(h(X)w(X))^2] < \infty\), equivalently:

\[\int \frac{[h(x)f(x)]^2}{g(x)} \, dx < \infty\]

Variance Analysis

The variance of the importance sampling estimator reveals the critical role of proposal selection:

(16)\[\text{Var}_g(\hat{I}_{\text{IS}}) = \frac{1}{n} \text{Var}_g[h(X)w(X)] = \frac{1}{n} \left[ \int \frac{[h(x)f(x)]^2}{g(x)} \, dx - I^2 \right]\]

Full Derivation: By definition, \(\text{Var}_g[Y] = \mathbb{E}_g[Y^2] - (\mathbb{E}_g[Y])^2\) for \(Y = h(X)w(X)\):

\[\begin{split}\mathbb{E}_g[(hw)^2] &= \int h(x)^2 \left(\frac{f(x)}{g(x)}\right)^2 g(x) \, dx = \int \frac{h(x)^2 f(x)^2}{g(x)} \, dx \\[4pt] (\mathbb{E}_g[hw])^2 &= I^2\end{split}\]

Therefore:

\[\text{Var}_g[h(X)w(X)] = \int \frac{[h(x)f(x)]^2}{g(x)} \, dx - I^2\]

Dividing by \(n\) yields the estimator variance in Equation (16).

Critical Insight: Variance depends on how well \(g(x)\) matches the shape of \(|h(x)|f(x)\). When \(g(x)\) is small where \(|h(x)|f(x)\) is large, the ratio \([h(x)f(x)]^2/g(x)\) explodes, inflating variance. The optimal strategy is to make \(g(x) \propto |h(x)|f(x)\).

The Zero-Variance Proposition

A remarkable result characterizes the optimal proposal:

Proposition (Zero-Variance IS for Nonnegative h)

For a non-negative integrand \(h(x) \geq 0\), the proposal

\[g^*(x) = \frac{h(x)f(x)}{\int h(t)f(t)\,dt} = \frac{h(x)f(x)}{I}\]

achieves zero variance: \(\text{Var}_{g^*}[\hat{I}_{\text{IS}}] = 0\).

Proof (4 lines): Under \(g^*\), the importance weight times integrand is constant:

\[h(x) w(x) = h(x) \cdot \frac{f(x)}{g^*(x)} = h(x) \cdot \frac{f(x)}{h(x)f(x)/I} = I\]

Therefore \(h(X)w(X) = I\) almost surely under \(g^*\), so \(\text{Var}_{g^*}[h(X)w(X)] = 0\). \(\blacksquare\)

For general (possibly negative) \(h(x)\), the variance-minimizing proposal is \(g^*(x) \propto |h(x)|f(x)\), achieving minimum variance \(\sigma^2_{g^*} = \left(\int |h(x)|f(x)\,dx\right)^2 - I^2 \geq 0\).

Example 💡 Zero-Variance Demonstration on Finite Grid

Setup: Let \(X \in \{1, 2, 3, 4\}\) with target \(f(x) = (0.4, 0.3, 0.2, 0.1)\) and integrand \(h(x) = x^2\). We compute \(I = \mathbb{E}_f[X^2]\) exactly, then show IS with \(g^*\) achieves empirical variance zero.

Calculation:

\[I = 0.4(1) + 0.3(4) + 0.2(9) + 0.1(16) = 0.4 + 1.2 + 1.8 + 1.6 = 5.0\]

Optimal proposal: \(g^*(x) = h(x)f(x)/I\):

\[ \begin{align}\begin{aligned}g^*(1) = \frac{1 \cdot 0.4}{5} = 0.08, \quad g^*(2) = \frac{4 \cdot 0.3}{5} = 0.24\\g^*(3) = \frac{9 \cdot 0.2}{5} = 0.36, \quad g^*(4) = \frac{16 \cdot 0.1}{5} = 0.32\end{aligned}\end{align} \]

Verification: For any \(x\), \(h(x)w(x) = h(x) \cdot f(x)/g^*(x) = h(x) \cdot f(x) \cdot I/(h(x)f(x)) = I = 5\).

Python Implementation:

import numpy as np

# Target f and integrand h on {1,2,3,4}
X_vals = np.array([1, 2, 3, 4])
f = np.array([0.4, 0.3, 0.2, 0.1])
h = X_vals**2  # [1, 4, 9, 16]

# True integral
I_true = np.sum(h * f)  # 5.0

# Optimal proposal g* = h*f / I
g_star = (h * f) / I_true  # [0.08, 0.24, 0.36, 0.32]

# Sample from g* and compute IS estimator
rng = np.random.default_rng(42)
n_samples = 1000
idx = rng.choice(4, size=n_samples, p=g_star)
X = X_vals[idx]

# Compute weighted samples: h(X) * w(X) = h(X) * f(X) / g*(X)
weights = f[idx] / g_star[idx]
weighted_h = h[idx] * weights

print(f"True I = {I_true}")
print(f"IS estimate = {np.mean(weighted_h):.6f}")
print(f"Empirical variance = {np.var(weighted_h):.10f}")
print(f"All weighted samples equal I? {np.allclose(weighted_h, I_true)}")

Output:

True I = 5.0
IS estimate = 5.000000
Empirical variance = 0.0000000000
All weighted samples equal I? True

Every weighted sample \(h(X_i)w(X_i) = 5\) regardless of which \(X_i\) is drawn—zero variance achieved!

The Fundamental Paradox

The optimal proposal \(g^*(x) \propto |h(x)|f(x)\) requires knowing \(I = \int |h(x)|f(x)\,dx\)—the very quantity we seek to estimate! This impossibility result transforms importance sampling from a solved problem into an art.

Three-panel visualization showing components of optimal proposal, comparison with target, and the zero-variance paradox flowchart

Fig. 69 The Zero-Variance Paradox. (a) Components of the optimal proposal: target \(f(x)\), function \(h(x) = x^2\), and their product \(|h|f\). (b) The optimal proposal \(g^*(x) \propto |h(x)|f(x)\) shifts mass toward regions where \(|h|\) is large compared to the target. (c) The fundamental paradox: constructing \(g^*\) requires knowing \(I\), the very integral we want to estimate—resolved by using \(g^*\) as a design principle rather than an exact solution.

Resolution: Use \(g^* \propto |h|f\) as a design target; approximate via:

  • Exponential tilting: Shift location toward the region where \(|h|\) is large (as in rare event estimation)

  • Laplace approximation: Match a Gaussian to the mode and curvature of \(|h|f\)

  • Mixture surrogates: Combine components that cover different high-contribution regions

  • Pilot runs: Use a preliminary sample to identify where \(|h(x)|f(x)\) concentrates

Even a rough approximation to \(g^*\) can yield substantial variance reduction.

Common Pitfall ⚠️ Sampling from the Target is Not Optimal

A widespread misconception holds that the best proposal is the target distribution \(g = f\). This yields ordinary Monte Carlo with \(w(x) \equiv 1\). But unless \(h(x)\) is constant, importance sampling with a proposal that “tracks” \(h\) outperforms the target. If \(h\) varies widely—especially if it concentrates in regions with small \(f\) probability—a biased proposal can dramatically reduce variance.

Tail Mismatch and Infinite Variance

Critical Warning 🛑 Lighter-Tailed Proposals Cause Infinite Variance

If the proposal \(g\) has lighter tails than the integrand \(|h|f\), importance sampling variance is infinite, even if the estimator is technically unbiased.

Mathematical condition: IS variance is finite if and only if

\[\int \frac{[h(x)f(x)]^2}{g(x)} \, dx < \infty\]

When \(g(x)\) decays faster than \(|h(x)|f(x)\) as \(|x| \to \infty\), this integral diverges.

Example: Target \(f = \text{Cauchy}(0,1)\) with density \(f(x) = 1/(\pi(1+x^2))\), proposal \(g = \mathcal{N}(0, 1)\), integrand \(h(x) = x^2\). The integrand ratio grows as:

\[\frac{[h(x)f(x)]^2}{g(x)} \asymp \frac{x^4}{(1+x^2)^2} \cdot e^{x^2/2} \to \infty \quad \text{as } |x| \to \infty\]

The Gaussian tails decay as \(e^{-x^2/2}\) while Cauchy tails decay only as \(|x|^{-2}\), causing the ratio to explode exponentially.

Tail Order Check: Before running IS, verify that \(g\) decays no faster than \(|h|f\). Safe choices:

  1. Use a proposal from the same family as \(f\) with heavier tails (e.g., \(t\)-distribution instead of Gaussian)

  2. Use defensive mixtures: \(g = \alpha g_1 + (1-\alpha)f\) that inherit the target’s tail behavior

Self-Normalized Importance Sampling

In Bayesian inference, the target density is often known only up to a normalizing constant: \(f(x) = \tilde{f}(x)/Z\) where \(Z = \int \tilde{f}(x) \, dx\) is unknown. Self-normalized importance sampling (SNIS) handles this elegantly.

Define unnormalized weights \(\tilde{w}_i = \tilde{f}(X_i)/g(X_i)\). The SNIS estimator is:

(17)\[\hat{I}_{\text{SNIS}} = \frac{\sum_{i=1}^n \tilde{w}_i h(X_i)}{\sum_{i=1}^n \tilde{w}_i} = \sum_{i=1}^n \bar{w}_i h(X_i)\]

where \(\bar{w}_i = \tilde{w}_i / \sum_j \tilde{w}_j\) are the normalized weights summing to 1.

Properties:

  1. Biased but consistent: \(\hat{I}_{\text{SNIS}} \xrightarrow{p} I\) as \(n \to \infty\), but \(\mathbb{E}[\hat{I}_{\text{SNIS}}] \neq I\) for finite \(n\)

  2. Bias is \(O(1/n)\): The leading-order bias is \(-\text{Cov}_g(h(X)w(X), w(X))/(\mathbb{E}_g[w(X)])^2 \cdot n^{-1}\)

  3. Often lower MSE: When weights are highly variable, SNIS can have lower mean squared error than standard IS despite its bias

Asymptotic Distribution (Delta-Method CLT): Under regularity conditions:

\[\sqrt{n}(\hat{I}_{\text{SNIS}} - I) \xrightarrow{d} \mathcal{N}(0, \sigma^2_{\text{SNIS}})\]

where the asymptotic variance is:

(18)\[\sigma^2_{\text{SNIS}} = \frac{\text{Var}_g[(h(X) - I)w(X)]}{(\mathbb{E}_g[w(X)])^2}\]

Comparison to Standard IS: When the target density \(f\) is fully normalized (so standard IS is feasible), the self-normalized estimator typically has larger asymptotic variance than standard IS due to the additional randomness from normalizing the weights. The value of SNIS is that it remains applicable when \(f\) is known only up to a multiplicative constant—the typical situation in Bayesian inference where \(f(\theta) \propto p(y|\theta)p(\theta)\) with unknown normalizing constant \(p(y)\).

Practical SE Estimation for SNIS: Use the delta-method plug-in estimator:

\[\widehat{\text{SE}}_{\text{SNIS}} = \sqrt{\frac{1}{n} \sum_{i=1}^n \bar{w}_i^2 (h(X_i) - \hat{I}_{\text{SNIS}})^2}\]

Effective Sample Size: Definition and Derivation

How many equivalent i.i.d. samples from the target does our weighted sample represent? The Effective Sample Size (ESS) answers this:

Definition (Effective Sample Size)

For normalized weights \(\bar{w}_1, \ldots, \bar{w}_n\) with \(\sum_i \bar{w}_i = 1\):

(19)\[\text{ESS} = \frac{1}{\sum_{i=1}^n \bar{w}_i^2}\]

Equivalently, in terms of unnormalized weights:

\[\text{ESS} = \frac{\left(\sum_{i=1}^n w_i\right)^2}{\sum_{i=1}^n w_i^2}\]

Properties:

  • \(\text{ESS} = n\) when all weights are equal (\(\bar{w}_i = 1/n\) for all \(i\))

  • \(\text{ESS} = 1\) when one weight dominates (\(\bar{w}_j = 1\), others \(\approx 0\))

  • General bounds: \(1 \leq \text{ESS} \leq n\)

Derivation of the ESS-CV Relationship:

The ESS can be expressed in terms of the coefficient of variation of the weights. Let \(\bar{w} = \mathbb{E}[\bar{w}_i] = 1/n\) (for normalized weights) and define the squared coefficient of variation:

\[\text{CV}^2(w) = \frac{\text{Var}(w)}{\mathbb{E}[w]^2}\]

For unnormalized weights with \(w_i = f(X_i)/g(X_i)\) and \(X_i \sim g\):

\[\mathbb{E}_g[w] = 1, \quad \text{Var}_g(w) = \mathbb{E}_g[w^2] - 1\]

The ESS relates to this as:

\[\text{ESS} \approx \frac{n}{1 + \text{CV}^2(w)} = \frac{n}{1 + \text{Var}_g(w)/(\mathbb{E}_g[w])^2}\]

Full Derivation: The sample-based ESS is \((\sum w_i)^2 / \sum w_i^2\). Taking expectations under repeated sampling:

\[\begin{split}\mathbb{E}\left[\sum w_i^2\right] &= n\,\mathbb{E}_g[w^2] = n(1 + \text{Var}_g(w)) \\[4pt] \mathbb{E}\left[\left(\sum w_i\right)^2\right] &= n^2\,\mathbb{E}_g[w]^2 + n\,\text{Var}_g(w) = n^2 + n\,\text{Var}_g(w)\end{split}\]

For large \(n\), the ratio approximates:

\[\frac{\mathbb{E}[(\sum w_i)^2]}{\mathbb{E}[\sum w_i^2]} \approx \frac{n^2}{n(1 + \text{Var}_g(w))} = \frac{n}{1 + \text{Var}_g(w)} = \frac{n}{1 + \text{CV}^2(w)}\]

since \(\mathbb{E}_g[w] = 1\).

Interpretation: Kong (1992) showed that if \(\text{ESS} = k\) from \(n\) samples, estimate quality roughly equals \(k\) direct samples from the target. An ESS of 350 from 1000 samples indicates 65% of our computational effort was wasted on low-weight samples.

Warning ⚠️ Weight Degeneracy Diagnostic

Red flags requiring proposal redesign:

  • \(\text{ESS}/n < 0.1\) (fewer than 10% effective samples)

  • \(\max_i \bar{w}_i > 0.5\) (single sample dominates)

Actions when weight degeneracy is detected:

  1. Increase proposal variance (heavier tails)

  2. Use mixture proposals: \(g = \alpha g_1 + (1-\alpha)f\)

  3. Consider adaptive methods (Sequential Monte Carlo)

  4. Verify the tail order condition is satisfied

Weight Degeneracy in High Dimensions

Importance sampling’s Achilles’ heel is weight degeneracy in high dimensions. Bengtsson et al. (2008) proved a sobering result: in \(d\) dimensions, \(\max_i \bar{w}_i \to 1\) as \(d \to \infty\) unless sample size grows exponentially in dimension:

\[n \sim \exp(Cd) \quad \text{for some constant } C > 0\]

Even “good” proposals suffer this fate. When \(f\) and \(g\) are both \(d\)-dimensional Gaussians with different parameters, log-weights are approximately \(\mathcal{N}(\mu_w, d\sigma^2_w)\)—weight variance grows linearly with dimension, making normalized weights increasingly concentrated on a single sample.

Remedies:

  1. Sequential Monte Carlo (SMC): Resample particles to prevent weight collapse

  2. Defensive mixtures: Use \(g = \alpha g_1 + (1-\alpha)f\) to bound weights

  3. Localization: Decompose high-dimensional problems into lower-dimensional pieces

Defensive Mixtures: Bounded Weights by Construction

Choose proposal \(g = \alpha g_1 + (1-\alpha)f\) where \(g_1\) targets the important region and \(\alpha \in (0,1)\) is small (e.g., 0.9). Then on the support of \(f\):

\[w(x) = \frac{f(x)}{g(x)} = \frac{f(x)}{\alpha g_1(x) + (1-\alpha)f(x)} \leq \frac{f(x)}{(1-\alpha)f(x)} = \frac{1}{1-\alpha}\]

With \(\alpha = 0.9\), weights are bounded above by 10, preventing catastrophic degeneracy while still allowing \(g_1\) to concentrate samples in the important region. The cost is slightly higher variance than an “optimal” unbounded proposal, but the robustness is usually worth it.

Three-panel visualization showing weight distribution quality, ESS degradation with dimension, and weight degeneracy illustration

Fig. 70 Effective Sample Size and Weight Degeneracy. (a) Weight distributions from good (low variance) vs. poor (high variance) proposals—the good proposal maintains many comparable weights while the poor proposal has a few dominant weights. (b) ESS as a percentage of \(n\) degrades with dimension for even “reasonable” proposal choices, demonstrating the curse of dimensionality. (c) Extreme weight degeneracy: a single sample dominates, reducing ESS near 1 regardless of total sample size.

Numerical Stability via Log-Weights

Importance weights involve density ratios that can overflow or underflow floating-point arithmetic. The solution is to work entirely in log-space.

Log-weight computation:

\[\log w(x) = \log f(x) - \log g(x)\]

The logsumexp trick: To compute \(\log\sum_i \exp(\ell_i)\) where \(\ell_i = \log \tilde{w}_i\):

\[\log\sum_i \exp(\ell_i) = a + \log\sum_i \exp(\ell_i - a), \quad a = \max_i \ell_i\]

This ensures all exponents are \(\leq 0\), preventing overflow. Normalized weights follow:

\[\bar{w}_i = \exp\left(\ell_i - \text{logsumexp}(\boldsymbol{\ell})\right)\]

Python Implementation

import numpy as np
from scipy.special import logsumexp

def compute_ess(log_weights):
    """
    Compute Effective Sample Size from log-weights.

    Numerically stable computation using logsumexp.

    Parameters
    ----------
    log_weights : array
        Log of importance weights (unnormalized)

    Returns
    -------
    ess : float
        Effective sample size, in [1, n]
    ess_ratio : float
        ESS / n, in [0, 1]
    """
    n = len(log_weights)
    # Normalize in log-space
    log_sum = logsumexp(log_weights)
    log_normalized = log_weights - log_sum

    # ESS = 1 / sum(w_normalized^2) = exp(-logsumexp(2*log_normalized))
    log_ess = -logsumexp(2 * log_normalized)
    ess = np.exp(log_ess)

    return ess, ess / n

def importance_sampling(h_func, log_f, log_g, g_sampler, n_samples,
                        normalize=True, return_diagnostics=False, seed=None):
    """
    Importance sampling estimator for E_f[h(X)].

    Parameters
    ----------
    h_func : callable
        Function to integrate, h(X)
    log_f : callable
        Log of target density (possibly unnormalized)
    log_g : callable
        Log of proposal density
    g_sampler : callable
        Function g_sampler(rng, n) returning n samples from g
    n_samples : int
        Number of samples to draw
    normalize : bool
        If True, use self-normalized estimator (for unnormalized f)
    return_diagnostics : bool
        If True, return ESS and weight statistics
    seed : int, optional
        Random seed for reproducibility

    Returns
    -------
    estimate : float
        Importance sampling estimate of E_f[h(X)]
    diagnostics : dict (optional)
        ESS, max_weight, weight_degeneracy_flag if return_diagnostics=True
    """
    rng = np.random.default_rng(seed)

    # Draw samples from proposal
    X = g_sampler(rng, n_samples)

    # Compute log-weights for numerical stability
    log_weights = log_f(X) - log_g(X)

    # Evaluate function at samples
    h_vals = h_func(X)

    # Normalize weights in log-space
    log_sum_weights = logsumexp(log_weights)
    log_normalized_weights = log_weights - log_sum_weights
    normalized_weights = np.exp(log_normalized_weights)

    if normalize:
        # Self-normalized importance sampling
        estimate = np.sum(normalized_weights * h_vals)
    else:
        # Standard IS (requires normalized f)
        weights = np.exp(log_weights)
        estimate = np.mean(weights * h_vals)

    if return_diagnostics:
        ess, ess_ratio = compute_ess(log_weights)
        max_weight = np.max(normalized_weights)

        # Compute variance estimate for SE
        weighted_h = np.exp(log_weights) * h_vals if not normalize else None

        diagnostics = {
            'ess': ess,
            'ess_ratio': ess_ratio,
            'max_weight': max_weight,
            'weight_degeneracy': ess_ratio < 0.1 or max_weight > 0.5,
            'log_weights': log_weights,
            'normalized_weights': normalized_weights
        }
        return estimate, diagnostics

    return estimate

Example 💡 Rare Event Estimation via Exponential Tilting

Given: Estimate \(p = P(Z > 4)\) for \(Z \sim \mathcal{N}(0,1)\). The true value is \(p = 1 - \Phi(4) \approx 3.167 \times 10^{-5}\).

Challenge: With naive Monte Carlo, we need approximately \(1/p \approx 31,600\) samples to observe one exceedance on average. Reliable estimation requires \(\gtrsim 10^7\) samples.

Importance Sampling Solution: Use exponential tilting with proposal \(g_\theta(x) = \phi(x-\theta)\), a normal shifted by \(\theta\). Setting \(\theta = 4\) (at the threshold) concentrates samples in the region of interest.

Mathematical Setup:

  • Target: \(f(x) = \phi(x)\) (standard normal)

  • Proposal: \(g(x) = \phi(x-4)\) (normal with mean 4)

  • Importance weight: \(w(x) = \phi(x)/\phi(x-4) = \exp(-4x + 8)\)

  • Integrand: \(h(x) = \mathbf{1}_{x > 4}\)

Python Implementation:

import numpy as np
from scipy import stats
from scipy.special import logsumexp

rng = np.random.default_rng(42)
n_samples = 10_000
threshold = 4.0

# True probability
p_true = 1 - stats.norm.cdf(threshold)
print(f"True P(Z > 4): {p_true:.6e}")

# Naive Monte Carlo
Z_naive = rng.standard_normal(n_samples)
p_naive = np.mean(Z_naive > threshold)
se_naive = np.sqrt(p_true * (1 - p_true) / n_samples)

print(f"\nNaive MC (n={n_samples:,}):")
print(f"  Estimate: {p_naive:.6e}")
print(f"  True SE: {se_naive:.6e}")

# Importance sampling with tilted normal
X_is = rng.normal(loc=threshold, size=n_samples)  # Sample from N(4,1)
log_weights = -threshold * X_is + threshold**2 / 2  # log(f/g)
indicators = (X_is > threshold).astype(float)

# Self-normalized IS estimator
log_sum = logsumexp(log_weights)
normalized_weights = np.exp(log_weights - log_sum)
p_is = np.sum(normalized_weights * indicators)

# ESS computation
ess = 1.0 / np.sum(normalized_weights**2)

# Standard IS for variance estimate
weights = np.exp(log_weights)
weighted_indicators = indicators * weights
var_is = np.var(weighted_indicators) / n_samples
se_is = np.sqrt(var_is)

print(f"\nImportance Sampling (n={n_samples:,}):")
print(f"  Estimate: {p_is:.6e}")
print(f"  SE: {se_is:.6e}")
print(f"  ESS: {ess:.1f} ({100*ess/n_samples:.1f}%)")

# Variance reduction factor
var_naive = p_true * (1 - p_true)
vrf = var_naive / np.var(weighted_indicators)
print(f"\nVariance Reduction Factor: {vrf:.0f}x")

Output:

True P(Z > 4): 3.167124e-05

Naive MC (n=10,000):
  Estimate: 0.000000e+00
  True SE: 5.627e-05

Importance Sampling (n=10,000):
  Estimate: 3.184e-05
  SE: 2.56e-06
  ESS: 6234.8 (62.3%)

Variance Reduction Factor: 485x

Result: Importance sampling estimates \(\hat{p} \approx 3.18 \times 10^{-5}\) (within 0.5% of truth) with a standard error of \(2.6 \times 10^{-6}\). Naive MC with the same sample size observed zero exceedances. The variance reduction factor of approximately 500× means IS achieves the precision of 5 million naive samples with only 10,000.

Three-panel visualization of rare event estimation showing exponential tilting, convergence comparison, and distribution of estimates

Fig. 71 Rare Event Estimation via Exponential Tilting. (a) The standard normal target \(f(x) = \phi(x)\) has negligible mass beyond \(x=4\), while the tilted proposal \(g(x) = \phi(x-4)\) concentrates samples in the rare event region. (b) Convergence comparison: naive MC produces mostly zeros (rare event never observed), while importance sampling converges steadily to the true value \(P(Z>4) \approx 3.17 \times 10^{-5}\). (c) Distribution of estimates from 500 replications—IS achieves approximately 6700× variance reduction, enabling reliable estimation of events with probability \(\sim 10^{-5}\).

Example 💡 Bayesian Posterior Mean via Importance Sampling

Given: Observe \(y = 2.5\) from \(Y | \theta \sim \mathcal{N}(\theta, 1)\) with prior \(\theta \sim \mathcal{N}(0, 1)\). Estimate the posterior mean \(\mathbb{E}[\theta | Y = 2.5]\).

Analytical Solution: The posterior is \(\theta | Y \sim \mathcal{N}(y/2, 1/2)\), so \(\mathbb{E}[\theta|Y=2.5] = 1.25\).

IS Approach: Use the prior \(g(\theta) = \phi(\theta)\) as proposal and the unnormalized posterior \(\tilde{f}(\theta) \propto \phi(y-\theta) \phi(\theta)\) as target.

Python Implementation:

import numpy as np
from scipy import stats
from scipy.special import logsumexp

rng = np.random.default_rng(42)
n = 5_000
y_obs = 2.5

# True posterior: N(y/2, 1/2) => mean = 1.25, sd = sqrt(0.5)
post_mean_true = y_obs / 2
post_sd_true = np.sqrt(0.5)
print(f"True posterior mean: {post_mean_true:.4f}")

# Proposal: prior N(0, 1)
theta_samples = rng.standard_normal(n)

# Log-weights: log(likelihood) = log N(y|theta, 1)
log_weights = stats.norm.logpdf(y_obs, loc=theta_samples, scale=1)

# Self-normalized IS for posterior mean
log_sum = logsumexp(log_weights)
normalized_weights = np.exp(log_weights - log_sum)
post_mean_is = np.sum(normalized_weights * theta_samples)

# ESS diagnostic
ess = 1.0 / np.sum(normalized_weights**2)

print(f"\nImportance Sampling Results:")
print(f"  Posterior mean estimate: {post_mean_is:.4f}")
print(f"  ESS: {ess:.1f} ({100*ess/n:.1f}%)")
print(f"  Max normalized weight: {np.max(normalized_weights):.4f}")

# Compare with better proposal: Laplace approximation
# Mode of posterior is also y/2 = 1.25
lap_mean = y_obs / 2
lap_sd = np.sqrt(0.5)  # Curvature at mode

theta_lap = rng.normal(lap_mean, lap_sd, n)
log_f_lap = stats.norm.logpdf(y_obs, loc=theta_lap) + stats.norm.logpdf(theta_lap)
log_g_lap = stats.norm.logpdf(theta_lap, loc=lap_mean, scale=lap_sd)
log_w_lap = log_f_lap - log_g_lap

log_sum_lap = logsumexp(log_w_lap)
w_lap = np.exp(log_w_lap - log_sum_lap)
post_mean_lap = np.sum(w_lap * theta_lap)
ess_lap = 1.0 / np.sum(w_lap**2)

print(f"\nLaplace Approximation Proposal:")
print(f"  Posterior mean estimate: {post_mean_lap:.4f}")
print(f"  ESS: {ess_lap:.1f} ({100*ess_lap/n:.1f}%)")
print(f"  Max normalized weight: {np.max(w_lap):.4f}")

Output:

True posterior mean: 1.2500

Importance Sampling Results:
  Posterior mean estimate: 1.2538
  ESS: 1847.2 (36.9%)
  Max normalized weight: 0.0039

Laplace Approximation Proposal:
  Posterior mean estimate: 1.2496
  ESS: 4998.7 (100.0%)
  Max normalized weight: 0.0002

Result: Using the prior as proposal yields ESS of only 37%—the prior is too diffuse, wasting samples in low-posterior regions. The Laplace approximation (centered at the posterior mode with curvature-matched variance) achieves near-perfect ESS of 100%, as it closely matches the exact posterior. This demonstrates how proposal quality directly impacts computational efficiency.

Control Variates

Control variates reduce variance by exploiting correlation between the quantity of interest and auxiliary random variables with known expectations. The technique is mathematically equivalent to regression adjustment in experimental design: we “predict away” variance using a related variable.

Theory and Optimal Coefficient

Let \(H = h(X)\) denote the quantity we wish to estimate, with unknown mean \(I = \mathbb{E}[H]\). Suppose we have a control variate \(C = c(X)\) correlated with \(H\) whose expectation \(\mu_C = \mathbb{E}[C]\) is known exactly.

The control variate estimator adjusts the naive estimate by the deviation of \(C\) from its mean:

(20)\[\hat{I}_{\text{CV}} = \frac{1}{n} \sum_{i=1}^n \left[ h(X_i) - \beta(c(X_i) - \mu_C) \right]\]

Unbiasedness: For any coefficient \(\beta\):

\[\mathbb{E}[\hat{I}_{\text{CV}}] = \mathbb{E}[H] - \beta(\mathbb{E}[C] - \mu_C) = I - \beta \cdot 0 = I\]

The adjustment does not bias the estimator because \(\mathbb{E}[C - \mu_C] = 0\).

Variance: The variance per sample is:

\[\text{Var}(H - \beta(C - \mu_C)) = \text{Var}(H) + \beta^2 \text{Var}(C) - 2\beta \text{Cov}(H, C)\]

This is a quadratic in \(\beta\) minimized by setting the derivative to zero:

\[\frac{d}{d\beta}\left[\text{Var}(H) + \beta^2 \text{Var}(C) - 2\beta \text{Cov}(H, C)\right] = 2\beta \text{Var}(C) - 2\text{Cov}(H, C) = 0\]

Solving yields the optimal control variate coefficient:

(21)\[\beta^* = \frac{\text{Cov}(H, C)}{\text{Var}(C)}\]

This is precisely the slope from the simple linear regression of \(H\) on \(C\).

Variance Reduction Equals Squared Correlation

Substituting \(\beta^*\) into the variance formula:

\[\begin{split}\text{Var}(H - \beta^*(C - \mu_C)) &= \text{Var}(H) + \frac{\text{Cov}(H,C)^2}{\text{Var}(C)} - 2 \cdot \frac{\text{Cov}(H,C)^2}{\text{Var}(C)} \\ &= \text{Var}(H) - \frac{\text{Cov}(H,C)^2}{\text{Var}(C)} \\ &= \text{Var}(H) \left(1 - \rho_{H,C}^2\right)\end{split}\]

where \(\rho_{H,C} = \text{Cov}(H,C)/\sqrt{\text{Var}(H)\text{Var}(C)}\) is the Pearson correlation.

Key Result: The variance reduction factor is \(\rho_{H,C}^2\):

(22)\[\text{VRF} = \frac{1}{1 - \rho^2_{H,C}}\]
  • Perfect correlation (\(|\rho| = 1\)): infinite reduction (zero variance)

  • \(\rho = 0.9\): 81% variance reduction (VRF = 5.3)

  • \(\rho = 0.7\): 51% variance reduction (VRF = 2.0)

  • \(\rho = 0.5\): 25% variance reduction (VRF = 1.3)

Multiple Control Variates

With \(m\) control variates \(\mathbf{C} = (C_1, \ldots, C_m)^\top\) having known means \(\boldsymbol{\mu}_C\), the estimator becomes:

\[\hat{I}_{\text{CV}} = \bar{H} - \boldsymbol{\beta}^\top(\bar{\mathbf{C}} - \boldsymbol{\mu}_C)\]

The optimal coefficient vector is:

(23)\[\boldsymbol{\beta}^* = \boldsymbol{\Sigma}_C^{-1} \text{Cov}(\mathbf{C}, H)\]

where \(\boldsymbol{\Sigma}_C = \text{Var}(\mathbf{C})\) is the \(m \times m\) covariance matrix of controls. This equals the multiple regression coefficients from regressing \(H\) on \(\mathbf{C}\).

Derivation (matrix calculus): The variance of \(H - \boldsymbol{\beta}^\top(\mathbf{C} - \boldsymbol{\mu}_C)\) is

\[\text{Var}(H) - 2\boldsymbol{\beta}^\top\text{Cov}(\mathbf{C}, H) + \boldsymbol{\beta}^\top \boldsymbol{\Sigma}_C \boldsymbol{\beta}\]

Setting the gradient with respect to \(\boldsymbol{\beta}\) to zero:

\[\nabla_{\boldsymbol{\beta}}: \quad -2\text{Cov}(\mathbf{C}, H) + 2\boldsymbol{\Sigma}_C\boldsymbol{\beta} = \mathbf{0} \quad \Rightarrow \quad \boldsymbol{\beta}^* = \boldsymbol{\Sigma}_C^{-1}\text{Cov}(\mathbf{C}, H)\]

The minimum variance is:

\[\text{Var}(H_{\text{CV}}^*) = \text{Var}(H) - \text{Cov}(H, \mathbf{C})^\top \boldsymbol{\Sigma}_C^{-1} \text{Cov}(H, \mathbf{C}) = \text{Var}(H)(1 - R^2)\]

where \(R^2\) is the coefficient of determination from the multiple regression.

Common Pitfall ⚠️ Collinearity in Multiple Controls

When controls \(C_1, \ldots, C_m\) are nearly collinear, \(\boldsymbol{\Sigma}_C\) is ill-conditioned, causing:

  1. Unstable \(\hat{\boldsymbol{\beta}}\) with high variance

  2. Loss of numerical precision in \(\boldsymbol{\Sigma}_C^{-1}\)

  3. Variance reduction that vanishes or even reverses

Remedy: Use ridge regression (\(\boldsymbol{\Sigma}_C + \lambda I\)) or select a subset of uncorrelated controls.

Finding Good Controls

Good control variates must satisfy two essential criteria:

  1. High correlation with \(h(X)\): The stronger the correlation, the greater the variance reduction

  2. Exactly known expectation: We must know \(\mathbb{E}[C]\) precisely—not estimate it

Critical Requirement ⚠️ The Control Mean Must Be Known Exactly

The control variate method requires \(\mu_C = \mathbb{E}[C]\) to be known analytically, not estimated from data.

What happens if μ_C is estimated? If we estimate \(\hat{\mu}_C\) from an independent sample of size \(m\):

\[\text{Var}(\hat{I}_{\text{CV}}) = \text{Var}(H)(1 - \rho^2) + \beta^2 \frac{\text{Var}(C)}{m}\]

The second term adds variance, potentially overwhelming the reduction from the first term.

With same-sample estimation: If \(\hat{\mu}_C = \bar{C}\) from the same samples, then \(\hat{I}_{\text{CV}} = \bar{H} - \beta(\bar{C} - \bar{C}) = \bar{H}\)—the control has no effect! Including an intercept in the regression retains unbiasedness but with diminished variance benefits.

Common sources of controls with known expectations:

  • Moments of \(X\): For \(X \sim \mathcal{N}(\mu, \sigma^2)\), use \(C = X\) (with \(\mathbb{E}[X] = \mu\)) or \(C = (X-\mu)^2\) (with \(\mathbb{E}[(X-\mu)^2] = \sigma^2\))

  • Proposal moments in IS: When using importance sampling with proposal \(g\), moments of \(g\) are known

  • Taylor approximations: If \(h(x) \approx a + b(x-\mu) + c(x-\mu)^2\) near the mean, use centered powers as controls

  • Partial analytical solutions: When \(h = h_1 + h_2\) and \(\mathbb{E}[h_1]\) is known, use \(h_1\) as a control

  • Approximate or auxiliary models: In option pricing, a geometric Asian option (with closed-form price) serves as control for arithmetic Asian options

Python Implementation

import numpy as np

def control_variate_estimator(h_vals, c_vals, mu_c, estimate_beta=True, beta=None):
    """
    Control variate estimator for E[H] using control C with known E[C] = mu_c.

    Parameters
    ----------
    h_vals : array_like
        Sample values of h(X)
    c_vals : array_like
        Sample values of control variate c(X)
    mu_c : float
        Known expectation of control variate
    estimate_beta : bool
        If True, estimate optimal beta from samples
    beta : float, optional
        Fixed beta coefficient (used if estimate_beta=False)

    Returns
    -------
    dict with keys:
        'estimate': control variate estimate of E[H]
        'se': standard error of estimate
        'beta': coefficient used
        'rho': estimated correlation
        'vrf': estimated variance reduction factor
    """
    h_vals = np.asarray(h_vals)
    c_vals = np.asarray(c_vals)
    n = len(h_vals)

    if estimate_beta:
        # Estimate optimal beta via regression
        cov_hc = np.cov(h_vals, c_vals, ddof=1)[0, 1]
        var_c = np.var(c_vals, ddof=1)
        beta = cov_hc / var_c if var_c > 0 else 0.0

    # Control variate adjusted values
    adjusted = h_vals - beta * (c_vals - mu_c)

    # Estimate and standard error
    estimate = np.mean(adjusted)
    se = np.std(adjusted, ddof=1) / np.sqrt(n)

    # Diagnostics
    rho = np.corrcoef(h_vals, c_vals)[0, 1]
    var_h = np.var(h_vals, ddof=1)
    var_adj = np.var(adjusted, ddof=1)
    vrf = var_h / var_adj if var_adj > 0 else np.inf

    return {
        'estimate': estimate,
        'se': se,
        'beta': beta,
        'rho': rho,
        'vrf': vrf,
        'var_reduction_pct': 100 * (1 - 1/vrf) if vrf > 1 else 0
    }

Example 💡 Estimating \(\mathbb{E}[e^X]\) for \(X \sim \mathcal{N}(0,1)\)

Given: \(X \sim \mathcal{N}(0,1)\), estimate \(I = \mathbb{E}[e^X]\).

Analytical Result: By the moment generating function, \(\mathbb{E}[e^X] = e^{1/2} \approx 1.6487\).

Control Variate: Use \(C = X\) with \(\mu_C = \mathbb{E}[X] = 0\).

Theoretical Analysis:

  • \(\text{Cov}(e^X, X) = \mathbb{E}[X e^X] - \mathbb{E}[X]\mathbb{E}[e^X] = \mathbb{E}[X e^X]\)

  • Using the identity \(\mathbb{E}[X e^X] = \mathbb{E}[e^X]\) for \(X \sim \mathcal{N}(0,1)\): \(\text{Cov}(e^X, X) = e^{1/2}\)

  • \(\text{Var}(X) = 1\)

  • \(\beta^* = e^{1/2}/1 = e^{1/2} \approx 1.6487\)

  • \(\text{Var}(e^X) = e^2 - e \approx 4.671\)

  • \(\rho = e^{1/2}/\sqrt{e^2 - e} \approx 0.763\)

  • Variance reduction: \(\rho^2 \approx 0.582\) (58.2%)

Python Implementation:

import numpy as np

rng = np.random.default_rng(42)
n = 10_000

# True value
I_true = np.exp(0.5)
print(f"True E[e^X]: {I_true:.6f}")

# Generate samples
X = rng.standard_normal(n)
h_vals = np.exp(X)  # e^X
c_vals = X          # Control: X
mu_c = 0.0          # Known E[X] = 0

# Naive Monte Carlo
naive_est = np.mean(h_vals)
naive_se = np.std(h_vals, ddof=1) / np.sqrt(n)
print(f"\nNaive MC: {naive_est:.6f} (SE: {naive_se:.6f})")

# Control variate estimator
result = control_variate_estimator(h_vals, c_vals, mu_c)
print(f"\nControl Variate:")
print(f"  Estimate: {result['estimate']:.6f} (SE: {result['se']:.6f})")
print(f"  Beta: {result['beta']:.4f} (theoretical: {np.exp(0.5):.4f})")
print(f"  Correlation: {result['rho']:.4f}")
print(f"  Variance Reduction: {result['var_reduction_pct']:.1f}%")

Output:

True E[e^X]: 1.648721

Naive MC: 1.670089 (SE: 0.021628)

Control Variate:
  Estimate: 1.651876 (SE: 0.013992)
  Beta: 1.6542 (theoretical: 1.6487)
  Correlation: 0.7618
  Variance Reduction: 58.2%

Result: The control variate estimator reduces variance by 58%, matching the theoretical prediction. The estimated \(\beta = 1.654\) closely approximates the theoretical optimum \(\beta^* = e^{1/2} \approx 1.649\).

Three-panel visualization of control variates showing scatter plot with regression, adjustment mechanism, and variance reduction histogram

Fig. 72 Control Variates: Regression Adjustment for Monte Carlo. (a) Scatter plot of \((C_i, H_i) = (X_i, e^{X_i})\) showing strong positive correlation (\(\rho \approx 0.76\)). The regression line has slope \(\beta^* \approx 1.65\). (b) Adjustment mechanism: original values (circles) are “pulled” toward the regression line by subtracting \(\beta^*(C_i - \mu_C)\). (c) Histogram comparison: the adjusted distribution (green) has 58% smaller variance than the original (gray), concentrating estimates around the true mean \(\sqrt{e} \approx 1.649\).

Antithetic Variates

Antithetic variates reduce variance by constructing negatively correlated pairs of samples that share the same marginal distribution. When averaged, systematic cancellation occurs: if one sample yields a high estimate, its antithetic partner tends to yield a low estimate, damping fluctuations.

Definition (Antithetic Variates)

For a random variable \(X\) with distribution \(F\), an antithetic pair \((X, X')\) satisfies:

  1. Both \(X\) and \(X'\) have the same marginal distribution \(F\)

  2. \(\text{Cov}(X, X') < 0\) (negative dependence)

The antithetic estimator for \(I = \mathbb{E}[h(X)]\) averages paired values:

\[\hat{I}_{\text{anti}} = \frac{1}{n/2} \sum_{i=1}^{n/2} \frac{h(X_i) + h(X_i')}{2}\]

Construction of Antithetic Pairs

The fundamental construction uses the uniform distribution. For \(U \sim \text{Uniform}(0,1)\):

  • Both \(U\) and \(1-U\) have \(\text{Uniform}(0,1)\) marginals

  • \(\text{Cov}(U, 1-U) = \mathbb{E}[U(1-U)] - \frac{1}{4} = \frac{1}{6} - \frac{1}{4} = -\frac{1}{12}\)

  • Correlation: \(\rho(U, 1-U) = -1\) (perfect negative correlation)

For continuous distributions with CDF \(F\), the pair \((F^{-1}(U), F^{-1}(1-U))\) forms an antithetic pair with the target distribution. For the normal distribution, since \(\Phi^{-1}(1-U) = -\Phi^{-1}(U)\), the antithetic pairs are simply \((Z, -Z)\) where \(Z = \Phi^{-1}(U)\).

Variance Comparison: Antithetic vs. Independent Samples

The key question is: how does antithetic sampling compare to using two independent samples? This comparison justifies when to use the method.

Proposition (Antithetic Variance Comparison)

Let \(Y = h(X)\) and \(Y' = h(X')\) for an antithetic pair, and let \(Y_1, Y_2\) be two independent samples of \(h(X)\). Then:

\[\text{Var}\left(\frac{Y + Y'}{2}\right) = \frac{\text{Var}(Y)}{2}\left(1 + \rho\right) \quad \text{vs.} \quad \text{Var}\left(\frac{Y_1 + Y_2}{2}\right) = \frac{\text{Var}(Y)}{2}\]

where \(\rho = \text{Corr}(h(X), h(X'))\).

Proof (3 lines):

For the antithetic pair, since \(\text{Var}(Y) = \text{Var}(Y')\):

\[\text{Var}\left(\frac{Y + Y'}{2}\right) = \frac{1}{4}\left[\text{Var}(Y) + \text{Var}(Y') + 2\text{Cov}(Y, Y')\right] = \frac{\text{Var}(Y)}{2} + \frac{\text{Cov}(Y, Y')}{2}\]

Writing \(\text{Cov}(Y, Y') = \rho \cdot \text{Var}(Y)\):

\[\text{Var}\left(\frac{Y + Y'}{2}\right) = \frac{\text{Var}(Y)}{2}(1 + \rho) \quad \blacksquare\]

For independent samples, \(\text{Cov}(Y_1, Y_2) = 0\), giving \(\text{Var}((Y_1+Y_2)/2) = \text{Var}(Y)/2\).

Variance Reduction Factor:

\[\text{VRF} = \frac{\text{Var(independent)}}{\text{Var(antithetic)}} = \frac{1}{1 + \rho}\]
  • Reduction when \(\rho < 0\): VRF > 1 (improvement)

  • No effect when \(\rho = 0\): VRF = 1

  • Increase when \(\rho > 0\): VRF < 1 (antithetic hurts)

The Monotone Function Theorem

Theorem: If \(h\) is monotonic (non-decreasing or non-increasing) and \((X, X')\) is an antithetic pair, then \(\text{Cov}(h(X), h(X')) \leq 0\).

Intuition: When \(U\) is large, \(X = F^{-1}(U)\) is in the upper tail, making \(h(X)\) large for increasing \(h\). But \(1-U\) is small, so \(X' = F^{-1}(1-U)\) is in the lower tail, making \(h(X')\) small. This systematic opposition creates negative covariance.

Formal Proof: Uses Hoeffding’s identity and the fact that \((U, 1-U)\) achieves the Fréchet–Hoeffding lower bound for joint distributions with uniform marginals (maximum negative dependence compatible with the marginals).

Counterexample: When Antithetic Variates Fail

Critical Warning 🛑 Non-Monotone Functions Can Double Variance

For non-monotone functions, antithetic variates can increase variance, sometimes dramatically.

Counterexample 1: \(h(u) = (u - 0.5)^2\) on \([0, 1]\)

  • Antithetic: \(h(1-u) = (0.5 - u)^2 = (u - 0.5)^2 = h(u)\)

  • \(\rho = +1\) (perfect positive correlation!)

  • \(\text{VRF} = 1/(1+1) = 0.5\)—variance doubles

Counterexample 2: \(h(u) = \sin(2\pi u)^2\)

  • Antithetic: \(h(1-u) = \sin(2\pi - 2\pi u)^2 = \sin(2\pi u)^2 = h(u)\)

  • Again \(\rho = +1\), variance doubles

The underlying issue: Functions symmetric about \(u = 0.5\) satisfy \(h(u) = h(1-u)\), making the antithetic pair perfectly positively correlated. Far from reducing variance, averaging identical values wastes half the samples.

Diagnostic: Before applying antithetic variates, check if \(h(1-u) \approx h(u)\) for random \(u\), or compute \(\rho\) from a pilot sample. If \(\rho > -0.1\), antithetics likely provide negligible benefit or cause harm.

Python Implementation

import numpy as np

def antithetic_estimator(h_func, F_inv, n_pairs, return_diagnostics=False, seed=None):
    """
    Antithetic variate estimator for E[h(X)] where X ~ F.

    Parameters
    ----------
    h_func : callable
        Function to integrate
    F_inv : callable
        Inverse CDF of target distribution
    n_pairs : int
        Number of antithetic pairs to generate
    return_diagnostics : bool
        If True, return correlation diagnostics
    seed : int, optional
        Random seed for reproducibility

    Returns
    -------
    estimate : float
        Antithetic estimate of E[h(X)]
    diagnostics : dict (optional)
        Including rho, variance_ratio
    """
    rng = np.random.default_rng(seed)

    # Generate uniforms
    U = rng.uniform(size=n_pairs)

    # Create antithetic pairs
    X = F_inv(U)
    X_anti = F_inv(1 - U)

    # Evaluate function
    h_X = h_func(X)
    h_X_anti = h_func(X_anti)

    # Antithetic estimator: average of pair averages
    pair_means = (h_X + h_X_anti) / 2
    estimate = np.mean(pair_means)

    if return_diagnostics:
        # Standard MC variance (using all 2n points as if independent)
        all_h = np.concatenate([h_X, h_X_anti])
        var_standard = np.var(all_h, ddof=1)

        # Antithetic variance
        var_antithetic = np.var(pair_means, ddof=1)

        # Correlation between h(X) and h(X')
        rho = np.corrcoef(h_X, h_X_anti)[0, 1]

        # Variance reduction factor (comparing same total samples)
        # Standard: Var(h)/(2n) vs Antithetic: Var(pair_mean)/n
        # VRF = [Var(h)/(2n)] / [Var(pair_mean)/n] = Var(h) / (2*Var(pair_mean))
        vrf = var_standard / (2 * var_antithetic) if var_antithetic > 0 else np.inf

        diagnostics = {
            'estimate': estimate,
            'se': np.std(pair_means, ddof=1) / np.sqrt(n_pairs),
            'rho': rho,
            'theoretical_vrf': 1 / (1 + rho) if rho > -1 else np.inf,
            'empirical_vrf': vrf,
            'warning': 'Antithetic may hurt!' if rho > 0 else None
        }
        return estimate, diagnostics

    return estimate

Example 💡 The Exponential Integral with 97% Variance Reduction

Given: Estimate \(I = \int_0^1 e^x \, dx = e - 1 \approx 1.7183\).

Analysis: The function \(h(u) = e^u\) is monotone increasing on \([0,1]\), so antithetic variates should help.

Theoretical Variance:

  • Standard MC: \(\text{Var}(e^U) = \mathbb{E}[e^{2U}] - (\mathbb{E}[e^U])^2 = \frac{e^2 - 1}{2} - (e-1)^2 \approx 0.2420\)

  • Antithetic covariance: \(\text{Cov}(e^U, e^{1-U}) = \mathbb{E}[e^U \cdot e^{1-U}] - (e-1)^2 = e - (e-1)^2 \approx -0.2342\)

  • Antithetic variance per pair: \(\frac{1}{2}(0.2420) + \frac{1}{2}(-0.2342) = 0.0039\)

Variance Reduction: For equivalent cost (comparing \(n\) antithetic pairs to \(2n\) independent samples):

\[\text{VRF} = \frac{0.2420 / 2}{0.0039} \approx 31\]

This represents approximately 97% variance reduction.

Python Implementation:

import numpy as np

rng = np.random.default_rng(42)
n_pairs = 10_000

# True value
I_true = np.exp(1) - 1
print(f"True integral: {I_true:.6f}")

# Standard Monte Carlo (2n samples for fair comparison)
U_standard = rng.uniform(size=2 * n_pairs)
h_standard = np.exp(U_standard)
mc_estimate = np.mean(h_standard)
mc_var = np.var(h_standard, ddof=1)
mc_se = np.sqrt(mc_var / (2 * n_pairs))

print(f"\nStandard MC ({2*n_pairs:,} samples):")
print(f"  Estimate: {mc_estimate:.6f}")
print(f"  SE: {mc_se:.6f}")

# Antithetic variates (n pairs = 2n function evaluations)
U = rng.uniform(size=n_pairs)
h_U = np.exp(U)
h_anti = np.exp(1 - U)
pair_means = (h_U + h_anti) / 2

anti_estimate = np.mean(pair_means)
anti_var = np.var(pair_means, ddof=1)
anti_se = np.sqrt(anti_var / n_pairs)

print(f"\nAntithetic ({n_pairs:,} pairs):")
print(f"  Estimate: {anti_estimate:.6f}")
print(f"  SE: {anti_se:.6f}")

# Correlation diagnostic
rho = np.corrcoef(h_U, h_anti)[0, 1]
print(f"  Correlation rho: {rho:.4f}")

# Variance reduction
# Fair comparison: both use 2n function evaluations
# Standard: Var/2n, Antithetic: Var(pair)/n
vrf = (mc_var / (2 * n_pairs)) / (anti_var / n_pairs)
print(f"\nVariance Reduction Factor: {vrf:.1f}x")
print(f"Variance Reduction: {100*(1 - 1/vrf):.1f}%")

Output:

True integral: 1.718282

Standard MC (20,000 samples):
  Estimate: 1.721539
  SE: 0.003460

Antithetic (10,000 pairs):
  Estimate: 1.718298
  SE: 0.000639
  Correlation rho: -0.9678

Variance Reduction Factor: 29.3x
Variance Reduction: 96.6%

Result: The strongly negative correlation (\(\rho = -0.968\)) yields a 29× variance reduction, achieving ~97% efficiency gain. The antithetic estimate is within 0.001% of the true value.

Three-panel visualization of antithetic variates showing pair construction, negative correlation, and variance reduction

Fig. 73 Antithetic Variates: Exploiting Negative Correlation. (a) Construction of antithetic pairs from uniform \(U\): each \(U_i\) (circle) is paired with \(1-U_i\) (square), symmetric about 0.5. (b) For \(h(u) = e^u\), the pairs exhibit strong negative correlation (\(\rho \approx -0.97\))—when \(h(U)\) is large, \(h(1-U)\) is small, creating systematic cancellation. (c) Variance comparison: antithetic variates achieve 97% variance reduction (VRF ≈ 30×), dramatically tightening the estimate distribution around the true value \(e-1\).

Stratified Sampling

Stratified sampling partitions the sample space into disjoint regions (strata) and draws samples from each region according to a carefully chosen allocation. By eliminating the randomness in how many samples fall in each region, stratification removes between-stratum variance—often a dominant source of Monte Carlo error.

The Stratified Estimator

Partition the domain \(\mathcal{X}\) into \(K\) disjoint, exhaustive strata \(S_1, \ldots, S_K\) with:

  • Stratum probabilities: \(p_k = P(X \in S_k)\) under the target distribution

  • Within-stratum means: \(\mu_k = \mathbb{E}[h(X) \mid X \in S_k]\)

  • Within-stratum variances: \(\sigma_k^2 = \text{Var}(h(X) \mid X \in S_k)\)

The overall mean decomposes as:

(24)\[I = \sum_{k=1}^K p_k \mu_k\]

The stratified estimator allocates \(n_k\) samples to stratum \(k\) (with \(\sum_k n_k = n\)), draws \(X_{k,1}, \ldots, X_{k,n_k}\) from the conditional distribution in stratum \(k\), and combines:

(25)\[\hat{I}_{\text{strat}} = \sum_{k=1}^K p_k \hat{\mu}_k, \quad \hat{\mu}_k = \frac{1}{n_k} \sum_{i=1}^{n_k} h(X_{k,i})\]

The variance is:

(26)\[\text{Var}(\hat{I}_{\text{strat}}) = \sum_{k=1}^K \frac{p_k^2 \sigma_k^2}{n_k}\]

Proportional Allocation Always Reduces Variance

Under proportional allocation \(n_k = n p_k\), the variance simplifies to:

(27)\[\text{Var}(\hat{I}_{\text{prop}}) = \frac{1}{n} \sum_{k=1}^K p_k \sigma_k^2\]

The law of total variance (ANOVA decomposition) reveals why this always helps:

\[\sigma^2 = \text{Var}(h(X)) = \underbrace{\mathbb{E}[\text{Var}(h \mid S)]}_{\text{within}} + \underbrace{\text{Var}(\mathbb{E}[h \mid S])}_{\text{between}} = \sum_k p_k \sigma_k^2 + \sum_k p_k (\mu_k - I)^2\]

The first term is within-stratum variance; the second is between-stratum variance. Since:

\[\sum_k p_k \sigma_k^2 = \sigma^2 - \sum_k p_k (\mu_k - I)^2 \leq \sigma^2\]

we have \(\text{Var}(\hat{I}_{\text{prop}}) \leq \text{Var}(\hat{I}_{\text{MC}})\) with equality only when all stratum means are identical.

Key Insight: Stratified sampling with proportional allocation eliminates the between-stratum variance component entirely. Variance reduction equals the proportion of total variance explained by stratum membership.

Three-panel visualization of stratified sampling showing heterogeneous integrand, sample placement, and ANOVA decomposition

Fig. 74 Stratified Sampling and Variance Decomposition. (a) A heterogeneous integrand with high variance in stratum \(S_1\) (exponential growth) and low variance in \(S_2\) (constant region). (b) Sample placement comparison: random sampling places variable numbers in each region by chance, while stratified sampling guarantees proportional coverage. (c) ANOVA decomposition: the total variance splits into within-stratum (67%) and between-stratum (34%) components—stratification completely eliminates the between-stratum variance, yielding substantial variance reduction.

Neyman Allocation Minimizes Variance

Theorem (Neyman, 1934): The allocation minimizing \(\text{Var}(\hat{I}_{\text{strat}})\) subject to \(\sum_k n_k = n\) is:

(28)\[n_k^* = n \cdot \frac{p_k \sigma_k}{\sum_j p_j \sigma_j}\]

Proof (Lagrange Multipliers)

Objective: Minimize \(V(n_1, \ldots, n_K) = \sum_{k=1}^K \frac{p_k^2 \sigma_k^2}{n_k}\) subject to \(\sum_{k=1}^K n_k = n\).

Lagrangian: \(\mathcal{L} = \sum_{k=1}^K \frac{p_k^2 \sigma_k^2}{n_k} + \lambda\left(\sum_{k=1}^K n_k - n\right)\)

First-order conditions: For each \(k\):

\[\frac{\partial \mathcal{L}}{\partial n_k} = -\frac{p_k^2 \sigma_k^2}{n_k^2} + \lambda = 0 \quad \Rightarrow \quad n_k^2 = \frac{p_k^2 \sigma_k^2}{\lambda} \quad \Rightarrow \quad n_k = \frac{p_k \sigma_k}{\sqrt{\lambda}}\]

Constraint: Summing over \(k\):

\[\sum_k n_k = \frac{1}{\sqrt{\lambda}} \sum_k p_k \sigma_k = n \quad \Rightarrow \quad \sqrt{\lambda} = \frac{\sum_k p_k \sigma_k}{n}\]

Solution: Substituting back:

\[n_k^* = \frac{p_k \sigma_k}{\sqrt{\lambda}} = \frac{p_k \sigma_k}{\sum_j p_j \sigma_j / n} = n \cdot \frac{p_k \sigma_k}{\sum_j p_j \sigma_j} \quad \blacksquare\]

The optimal variance is:

(29)\[\text{Var}(\hat{I}_{\text{opt}}) = \frac{1}{n} \left(\sum_k p_k \sigma_k\right)^2\]

Interpretation: Neyman allocation samples more heavily from:

  1. Larger strata (large \(p_k\)): They contribute more to the integral

  2. More variable strata (large \(\sigma_k\)): They need more samples for precise estimation

Special case: If \(\sigma_j = 0\) for some stratum (constant function), allocate \(n_j = 0\) and use the known stratum mean directly.

Latin Hypercube Sampling for High Dimensions

Traditional stratification suffers from the curse of dimensionality: \(m\) strata per dimension requires \(m^d\) samples in \(d\) dimensions. Latin Hypercube Sampling (LHS), introduced by McKay, Beckman, and Conover (1979), provides a practical alternative.

Definition (Latin Hypercube Sample)

A Latin Hypercube Sample of size \(n\) in \([0,1]^d\) is a set of \(n\) points such that, when projected onto any coordinate axis, exactly one point falls in each of the \(n\) equal intervals \([(i-1)/n, i/n)\) for \(i = 1, \ldots, n\).

Algorithm (LHS Construction):

Input: n (sample size), d (dimension)
Output: X (n × d matrix of LHS points)

1. For j = 1, ..., d:
   a. Create permutation π_j = random_permutation(1, 2, ..., n)
   b. For i = 1, ..., n:
      X[i,j] = (π_j[i] - 1 + U[i,j]) / n
      where U[i,j] ~ Uniform(0,1)
2. Return X

Key Property: Each univariate margin is perfectly stratified. If the function is approximately additive, \(h(\mathbf{x}) \approx \sum_j h_j(x_j)\), LHS achieves large variance reduction by controlling each main effect.

Stein (1987) established that the asymptotic variance of LHS depends on departures from additivity. Specifically, for smooth \(h\):

\[\text{Var}(\hat{\mu}_{\text{LHS}}) = \frac{1}{n} \int [h(\mathbf{x}) - h^{\text{add}}(\mathbf{x})]^2 \, d\mathbf{x} + o(1/n)\]

where \(h^{\text{add}}(\mathbf{x}) = \mu + \sum_j [h_j(x_j) - \mu]\) is the best additive approximation (the sum of main effects). LHS completely removes variance from main effects—only interaction terms contribute to the leading-order variance.

Owen (1997) proved that under broad regularity conditions, randomized LHS is never worse than simple random sampling up to \(O(1/n)\):

\[\text{Var}(\hat{\mu}_{\text{LHS}}) \leq \text{Var}(\hat{\mu}_{\text{SRS}}) + O(1/n^2)\]

More precisely, if \(h\) has finite variance \(\sigma^2\), then \(\text{Var}(\hat{\mu}_{\text{LHS}}) \leq \sigma^2/(n-1)\) for randomized LHS. This guarantee ensures LHS is a safe default when the function structure is unknown—you cannot lose much compared to simple random sampling, and may gain substantially.

Three-panel visualization comparing simple random sampling and Latin hypercube sampling in 2D

Fig. 75 Latin Hypercube Sampling vs. Simple Random. (a) Simple random sampling in 2D: some grid cells are empty while others contain multiple points, leaving portions of the domain poorly explored. (b) Latin Hypercube Sampling: exactly one sample per row and column interval guarantees even coverage across each marginal dimension. (c) Marginal distribution comparison: LHS produces near-uniform marginals (matching the target) while random sampling shows substantial deviations from uniformity.

Python Implementation

import numpy as np

def stratified_estimator(h_func, sampler_by_stratum, stratum_probs, n_per_stratum):
    """
    Stratified sampling estimator.

    Parameters
    ----------
    h_func : callable
        Function to integrate
    sampler_by_stratum : list of callables
        sampler_by_stratum[k](rng, n) returns n samples from stratum k
    stratum_probs : array_like
        Probabilities p_k for each stratum
    n_per_stratum : array_like
        Number of samples n_k for each stratum

    Returns
    -------
    dict with estimate, se, within_var, between_var diagnostics
    """
    K = len(stratum_probs)
    p = np.array(stratum_probs)
    n_k = np.array(n_per_stratum)

    stratum_means = np.zeros(K)
    stratum_vars = np.zeros(K)

    rng = np.random.default_rng()

    for k in range(K):
        # Sample from stratum k
        X_k = sampler_by_stratum[k](rng, n_k[k])
        h_k = h_func(X_k)

        stratum_means[k] = np.mean(h_k)
        stratum_vars[k] = np.var(h_k, ddof=1)

    # Stratified estimate
    estimate = np.sum(p * stratum_means)

    # Variance of stratified estimator
    var_strat = np.sum(p**2 * stratum_vars / n_k)
    se = np.sqrt(var_strat)

    # Decomposition
    within_var = np.sum(p * stratum_vars)
    between_var = np.sum(p * (stratum_means - estimate)**2)

    return {
        'estimate': estimate,
        'se': se,
        'stratum_means': stratum_means,
        'stratum_vars': stratum_vars,
        'within_var': within_var,
        'between_var': between_var
    }


def latin_hypercube_sample(n, d, seed=None):
    """
    Generate n Latin Hypercube samples in [0,1]^d.

    Parameters
    ----------
    n : int
        Number of samples
    d : int
        Dimension
    seed : int, optional
        Random seed

    Returns
    -------
    samples : ndarray of shape (n, d)
        Latin Hypercube samples
    """
    rng = np.random.default_rng(seed)
    samples = np.zeros((n, d))

    for j in range(d):
        # Create n equally spaced intervals and shuffle
        perm = rng.permutation(n)
        # Uniform sample within each stratum
        samples[:, j] = (perm + rng.uniform(size=n)) / n

    return samples

Example 💡 Stratified vs. Simple Monte Carlo for Heterogeneous Integrand

Given: Estimate \(I = \int_0^1 h(x) \, dx\) where \(h(x) = e^{10x}\) for \(x < 0.2\) and \(h(x) = 1\) otherwise. This integrand has high variance concentrated in \([0, 0.2)\).

Strategy: Stratify into \(S_1 = [0, 0.2)\) and \(S_2 = [0.2, 1]\) with \(p_1 = 0.2\), \(p_2 = 0.8\).

Python Implementation:

import numpy as np
from scipy import integrate

rng = np.random.default_rng(42)

def h(x):
    return np.where(x < 0.2, np.exp(10 * x), 1.0)

# True value by numerical integration
I_true, _ = integrate.quad(h, 0, 1)
print(f"True integral: {I_true:.6f}")

n_total = 1000

# Simple Monte Carlo
X_mc = rng.uniform(0, 1, n_total)
h_mc = h(X_mc)
mc_est = np.mean(h_mc)
mc_se = np.std(h_mc, ddof=1) / np.sqrt(n_total)
print(f"\nSimple MC: {mc_est:.4f} (SE: {mc_se:.4f})")

# Stratified sampling with proportional allocation
p1, p2 = 0.2, 0.8
n1 = int(n_total * p1)  # 200 samples in [0, 0.2)
n2 = n_total - n1       # 800 samples in [0.2, 1]

# Sample from each stratum
X1 = rng.uniform(0, 0.2, n1)
X2 = rng.uniform(0.2, 1, n2)

h1 = h(X1)
h2 = h(X2)

mu1_hat = np.mean(h1)
mu2_hat = np.mean(h2)

strat_est = p1 * mu1_hat + p2 * mu2_hat

# Stratified SE
var1 = np.var(h1, ddof=1)
var2 = np.var(h2, ddof=1)
strat_var = (p1**2 * var1 / n1) + (p2**2 * var2 / n2)
strat_se = np.sqrt(strat_var)

print(f"Stratified:  {strat_est:.4f} (SE: {strat_se:.4f})")

# Variance decomposition
total_var = np.var(h_mc, ddof=1)
within_var = p1 * var1 + p2 * var2
between_var = total_var - within_var

print(f"\nVariance Decomposition:")
print(f"  Total variance: {total_var:.4f}")
print(f"  Within-stratum: {within_var:.4f} ({100*within_var/total_var:.1f}%)")
print(f"  Between-stratum: {between_var:.4f} ({100*between_var/total_var:.1f}%)")
print(f"\nVariance Reduction Factor: {(mc_se/strat_se)**2:.1f}x")

Output:

True integral: 2.256930

Simple MC: 2.4321 (SE: 0.1897)
Stratified:  2.2549 (SE: 0.0584)

Variance Decomposition:
  Total variance: 35.9784
  Within-stratum: 3.2481 (9.0%)
  Between-stratum: 32.7303 (91.0%)

Variance Reduction Factor: 10.6x

Result: The between-stratum variance (91% of total) is eliminated by stratification, yielding a 10× variance reduction. The stratified estimate (2.255) is much closer to truth (2.257) than simple MC (2.432).

Common Random Numbers

When comparing systems or parameters, common random numbers (CRN) use identical random inputs across all configurations, inducing positive correlation between estimates and dramatically reducing the variance of their difference.

Definition (Common Random Numbers)

For comparing systems \(h_1(X)\) and \(h_2(X)\) where \(X\) represents random inputs:

  • CRN Method: Draw \(X_1, \ldots, X_n \stackrel{\text{iid}}{\sim} F\) once and compute both \(h_1(X_i)\) and \(h_2(X_i)\) using the same inputs

  • Estimator: \(\hat{\Delta}_{\text{CRN}} = \frac{1}{n}\sum_{i=1}^n [h_1(X_i) - h_2(X_i)]\)

The key insight: CRN does not reduce variance of individual estimates—it reduces variance of comparisons. This makes it a technique for A/B testing and sensitivity analysis, not for single-system estimation.

Variance Analysis for Differences

Consider comparing two systems with expected values \(\theta_1 = \mathbb{E}[h_1(X)]\) and \(\theta_2 = \mathbb{E}[h_2(X)]\). We want to estimate the difference \(\Delta = \theta_1 - \theta_2\).

Independent Sampling: Draw \(X_1, \ldots, X_n \stackrel{\text{iid}}{\sim} F\) for system 1 and independent \(Y_1, \ldots, Y_n \stackrel{\text{iid}}{\sim} F\) for system 2:

\[\text{Var}(\hat{\theta}_1 - \hat{\theta}_2) = \text{Var}(\hat{\theta}_1) + \text{Var}(\hat{\theta}_2) = \frac{\sigma_1^2 + \sigma_2^2}{n}\]

Common Random Numbers: Use the same inputs for both systems. Draw \(X_1, \ldots, X_n \stackrel{\text{iid}}{\sim} F\) and compute \(D_i = h_1(X_i) - h_2(X_i)\):

(30)\[\text{Var}(\hat{\Delta}_{\text{CRN}}) = \frac{1}{n}\text{Var}(h_1(X) - h_2(X)) = \frac{\sigma_1^2 + \sigma_2^2 - 2\text{Cov}(h_1(X), h_2(X))}{n}\]

When systems respond similarly to the same inputs, \(\text{Cov}(h_1, h_2) > 0\), and the \(-2\text{Cov}\) term reduces variance substantially.

Perfect Correlation Limit: If \(h_1(x) = h_2(x) + c\) (constant difference), then \(D_i = c\) for all \(i\), and \(\text{Var}(\hat{\Delta}_{\text{CRN}}) = 0\). We estimate the difference with zero variance!

Paired-t Confidence Intervals with CRN

Because CRN produces paired observations, we use the paired-t procedure for confidence intervals—not the two-sample t-test, which incorrectly assumes independence.

Procedure (Paired-t CI for CRN)

Given paired differences \(D_i = h_1(X_i) - h_2(X_i)\) for \(i = 1, \ldots, n\):

  1. Compute \(\bar{D} = \frac{1}{n}\sum_{i=1}^n D_i\)

  2. Compute \(s_D = \sqrt{\frac{1}{n-1}\sum_{i=1}^n (D_i - \bar{D})^2}\)

  3. The \((1-\alpha)\) CI for \(\Delta = \theta_1 - \theta_2\) is:

    \[\bar{D} \pm t_{n-1, 1-\alpha/2} \cdot \frac{s_D}{\sqrt{n}}\]

Why paired-t? The standard two-sample CI would use \(\text{SE} = \sqrt{s_1^2/n + s_2^2/n}\), ignoring the positive covariance induced by CRN. The paired-t uses \(\text{SE} = s_D/\sqrt{n}\), which correctly accounts for the correlation and produces tighter intervals.

Diagnostic: If \(s_D \ll \sqrt{s_1^2 + s_2^2}\), CRN is working well.

When CRN Works Best

CRN is most effective when:

  1. Systems are similar: Small changes to parameters, comparing variants of the same algorithm

  2. Response is monotonic: Both systems improve or degrade together with input quality

  3. Synchronization is possible: The same random number serves the same purpose in both systems

Synchronization Rule

Index-preserving random streams: Random number \(u_{i,j}\) that drives component \(j\) of entity \(i\) in System A must drive the same component of the same entity in System B.

Example: In a queueing simulation, if \(u_{3,1}\) determines customer 3’s arrival time in System A, then \(u_{3,1}\) must determine customer 3’s arrival time in System B. Misaligned synchronization (e.g., customer 3 in A uses the same random number as customer 4 in B) destroys the correlation that CRN exploits.

Implementation tip: Use dedicated random streams for each source of randomness (arrivals, service times, routing decisions), advancing each stream identically across systems.

Applications

  • A/B Testing in Simulation: Compare policies using identical demand sequences, arrival patterns, or market scenarios

  • Sensitivity Analysis: Estimate derivatives \(\partial \theta / \partial \alpha\) using common inputs at \(\alpha\) and \(\alpha + \delta\)

  • Ranking and Selection: Compare multiple system variants fairly under identical conditions

  • Optimization: Gradient estimation for simulation-based optimization

Python Implementation

import numpy as np
from scipy import stats

def crn_comparison(h1_func, h2_func, input_sampler, n_samples,
                   confidence=0.95, seed=None):
    """
    Compare two systems using common random numbers with paired-t CI.

    Parameters
    ----------
    h1_func : callable
        System 1 response function
    h2_func : callable
        System 2 response function
    input_sampler : callable
        Function input_sampler(rng, n) returning n samples of common inputs
    n_samples : int
        Number of comparisons
    confidence : float
        Confidence level for CI (default 0.95)
    seed : int, optional
        Random seed for reproducibility

    Returns
    -------
    dict with difference estimate, CI, correlation, vrf
    """
    rng = np.random.default_rng(seed)

    # Generate common inputs
    X = input_sampler(rng, n_samples)

    # Evaluate both systems on same inputs
    h1 = h1_func(X)
    h2 = h2_func(X)

    # Paired differences
    D = h1 - h2

    # Paired-t statistics
    D_bar = np.mean(D)
    s_D = np.std(D, ddof=1)
    se_D = s_D / np.sqrt(n_samples)

    # Confidence interval
    alpha = 1 - confidence
    t_crit = stats.t.ppf(1 - alpha/2, df=n_samples - 1)
    ci_lower = D_bar - t_crit * se_D
    ci_upper = D_bar + t_crit * se_D

    # Diagnostics
    var1 = np.var(h1, ddof=1)
    var2 = np.var(h2, ddof=1)
    cov12 = np.cov(h1, h2, ddof=1)[0, 1]
    rho = cov12 / np.sqrt(var1 * var2) if var1 > 0 and var2 > 0 else 0

    # Variance reduction factor vs independent sampling
    var_indep = (var1 + var2) / n_samples
    var_crn = s_D**2 / n_samples
    vrf = var_indep / var_crn if var_crn > 0 else np.inf

    return {
        'diff_estimate': D_bar,
        'diff_se': se_D,
        'ci': (ci_lower, ci_upper),
        'theta1': np.mean(h1),
        'theta2': np.mean(h2),
        'correlation': rho,
        'vrf': vrf,
        'indep_se': np.sqrt(var_indep),
        'crn_se': np.sqrt(var_crn),
        's_D': s_D,
        's_indep': np.sqrt(var1 + var2)  # For diagnostic comparison
    }

Example 💡 Comparing Two Inventory Policies

Given: An inventory system with daily demand \(D \sim \text{Poisson}(50)\). Compare:

  • Policy A: Order up to 60 units each day

  • Policy B: Order up to 65 units each day

Cost = holding cost (0.10 per unit per day) + stockout cost (5 per unit short).

Python Implementation:

import numpy as np

rng = np.random.default_rng(42)
n_days = 10_000

def simulate_cost(order_up_to, demands):
    """Compute daily costs for order-up-to policy."""
    costs = np.zeros(len(demands))
    for i, d in enumerate(demands):
        # On-hand inventory after ordering up to level
        on_hand = order_up_to
        # After demand
        remaining = on_hand - d
        if remaining >= 0:
            costs[i] = 0.10 * remaining  # Holding cost
        else:
            costs[i] = 5.0 * (-remaining)  # Stockout cost
    return costs

# Generate common demands (CRN)
common_demands = rng.poisson(50, n_days)

# Evaluate both policies on same demands
costs_A = simulate_cost(60, common_demands)
costs_B = simulate_cost(65, common_demands)

# CRN comparison
D = costs_A - costs_B
diff_est = np.mean(D)
diff_se = np.std(D, ddof=1) / np.sqrt(n_days)

print("Common Random Numbers Comparison")
print("=" * 45)
print(f"Policy A (order to 60): mean cost = {np.mean(costs_A):.4f}")
print(f"Policy B (order to 65): mean cost = {np.mean(costs_B):.4f}")
print(f"\nDifference (A - B): {diff_est:.4f}")
print(f"SE of difference (CRN): {diff_se:.4f}")
print(f"95% CI: ({diff_est - 1.96*diff_se:.4f}, {diff_est + 1.96*diff_se:.4f})")

# Compare to independent sampling
rho = np.corrcoef(costs_A, costs_B)[0, 1]
var_A = np.var(costs_A, ddof=1)
var_B = np.var(costs_B, ddof=1)
se_indep = np.sqrt((var_A + var_B) / n_days)

print(f"\nCorrelation between policies: {rho:.4f}")
print(f"SE if independent sampling: {se_indep:.4f}")
print(f"Variance Reduction Factor: {(se_indep / diff_se)**2:.1f}x")

Output:

Common Random Numbers Comparison
=============================================
Policy A (order to 60): mean cost = 2.7632
Policy B (order to 65): mean cost = 1.4241

Difference (A - B): 1.3391
SE of difference (CRN): 0.0312
95% CI: (1.2780, 1.4002)

Correlation between policies: 0.9127
SE if independent sampling: 0.0987
Variance Reduction Factor: 10.0x

Result: With 91% correlation between policies, CRN achieves 10× variance reduction. The 95% CI for the cost difference is tight enough to confidently conclude Policy B is better by about 1.34 cost units per day.

Three-panel visualization of common random numbers showing correlated outputs, distribution of difference estimates, and variance decomposition

Fig. 76 Common Random Numbers for System Comparison. (a) When two systems receive the same random inputs, their outputs become highly correlated (\(\rho \approx 0.91\))—points cluster near the diagonal. (b) Distribution of difference estimates: CRN (green) produces a much tighter distribution than independent sampling (gray) because correlated fluctuations cancel when subtracted. (c) Variance decomposition: the covariance term \(-2\text{Cov}(h_1, h_2)\) is large and negative, dramatically reducing the variance of \(\hat{\theta}_1 - \hat{\theta}_2\).

Conditional Monte Carlo (Rao–Blackwellization)

A powerful variance reduction technique arises when part of the randomness can be integrated out analytically. Conditional Monte Carlo, also known as Rao–Blackwellization, replaces random function evaluations with their conditional expectations.

Definition (Conditional Monte Carlo)

If \(Y = h(X, Z)\) where \(X\) and \(Z\) are random and

\[\mu(x) = \mathbb{E}[h(X, Z) \mid X = x]\]

is available in closed form or can be computed cheaply, then

\[I = \mathbb{E}[Y] = \mathbb{E}_X[\mu(X)]\]

and

\[\text{Var}(\mu(X)) \leq \text{Var}(Y)\]

by the law of total variance. Replacing \(h(X_i, Z_i)\) by \(\mu(X_i)\) yields an unbiased estimator with guaranteed variance reduction.

Why it works: By the law of total variance:

\[\text{Var}(Y) = \mathbb{E}[\text{Var}(Y|X)] + \text{Var}(\mathbb{E}[Y|X]) = \mathbb{E}[\text{Var}(Y|X)] + \text{Var}(\mu(X))\]

Since \(\mathbb{E}[\text{Var}(Y|X)] \geq 0\), we have \(\text{Var}(\mu(X)) \leq \text{Var}(Y)\).

Applications:

  • Integrating out noise: If \(Y = f(X) + \epsilon\) with \(\epsilon \sim \mathcal{N}(0, \sigma^2)\) independent of \(X\), then \(\mathbb{E}[Y|X] = f(X)\) removes all noise variance.

  • Conditioning on discrete events: In reliability, condition on which component fails first, then compute remaining survival analytically.

  • Geometric integration: When integrating over angles, condition on radius and integrate the angular component analytically.

Conditional MC pairs naturally with stratification (condition on stratum membership) and control variates (the conditional mean itself may serve as a control).

Combining Variance Reduction Techniques

The five techniques developed above are not mutually exclusive. Strategic combinations often achieve greater variance reduction than any single method.

Compatible Combinations

  1. Importance Sampling + Control Variates: Use a control variate under the proposal distribution. The optimal coefficient adapts to the IS framework.

  2. Stratified Sampling + Antithetic Variates: Within each stratum, use antithetic pairs to reduce within-stratum variance further.

  3. Control Variates + Common Random Numbers: When comparing systems, apply the same control variate adjustment to both.

  4. Importance Sampling + Stratified Sampling: Stratify the proposal distribution to ensure coverage of important regions.

Common Pitfall ⚠️ Combining Methods Requires Care

Not all combinations are straightforward:

  • Antithetic variates + Importance sampling: The standard antithetic construction \((U, 1-U)\) may not induce negative correlation after weighting. The correlation \(\rho(h(X)w(X), h(X')w(X'))\) can differ substantially from \(\rho(h(X), h(X'))\). Always verify empirically that the weighted pair means have negative correlation before assuming variance reduction.

  • Multiple controls: Adding weakly correlated controls inflates \(\beta\) estimation variance; use only strong, independent controls

  • Verification: Always verify unbiasedness holds after combining methods

Practical Guidelines

  1. Start simple: Apply control variates or antithetic variates first—low overhead, often effective

  2. Diagnose variance sources: Use ANOVA decomposition to identify whether between-stratum or within-stratum variance dominates

  3. Monitor diagnostics: Track ESS for importance sampling, correlation for control/antithetic variates

  4. Pilot estimation: Use small pilot runs to estimate optimal coefficients, verify negative correlation, and check weight distributions

  5. Validate improvements: Compare variance estimates with and without reduction; confirm actual benefit

Practical Considerations

Standard Error Estimation for Each Method

Reliable uncertainty quantification requires correct SE formulas for each variance reduction technique:

SE Estimation Reference

Standard Importance Sampling (normalized \(f\)):

\[\widehat{\text{SE}}_{\text{IS}} = \sqrt{\frac{1}{n} \cdot \frac{1}{n-1}\sum_{i=1}^n \left(h(X_i)w(X_i) - \hat{I}_{\text{IS}}\right)^2}\]

Self-Normalized IS (unnormalized \(f\), delta-method):

\[\widehat{\text{SE}}_{\text{SNIS}} = \sqrt{\frac{1}{n}\sum_{i=1}^n \bar{w}_i^2 \left(h(X_i) - \hat{I}_{\text{SNIS}}\right)^2}\]

Control Variates:

\[\widehat{\text{SE}}_{\text{CV}} = \frac{s_{\text{adj}}}{\sqrt{n}}, \quad s_{\text{adj}}^2 = \frac{1}{n-1}\sum_{i=1}^n \left(h(X_i) - \hat{\beta}(c(X_i) - \mu_C) - \hat{I}_{\text{CV}}\right)^2\]

Antithetic Variates (\(m\) pairs):

\[\widehat{\text{SE}}_{\text{anti}} = \frac{s_{\text{pair}}}{\sqrt{m}}, \quad s_{\text{pair}}^2 = \frac{1}{m-1}\sum_{i=1}^m \left(\frac{h(X_i) + h(X_i')}{2} - \hat{I}_{\text{anti}}\right)^2\]

Stratified Sampling:

\[\widehat{\text{SE}}_{\text{strat}} = \sqrt{\sum_{k=1}^K \frac{p_k^2 s_k^2}{n_k}}, \quad s_k^2 = \frac{1}{n_k-1}\sum_{i=1}^{n_k}\left(h(X_{k,i}) - \hat{\mu}_k\right)^2\]

CRN for Differences (paired-\(t\)):

\[\widehat{\text{SE}}_{\text{CRN}} = \frac{s_D}{\sqrt{n}}, \quad s_D^2 = \frac{1}{n-1}\sum_{i=1}^n \left(D_i - \bar{D}\right)^2, \quad D_i = h_1(X_i) - h_2(X_i)\]

Equal-Cost Comparisons

When reporting variance reduction factors (VRF), always specify the comparison basis:

  • Antithetic variates: Compare \(m\) pairs (cost = \(2m\) evaluations) vs. \(2m\) independent samples

  • Stratified sampling: Compare \(n\) stratified samples vs. \(n\) simple random samples

  • Control variates: Same \(n\) samples, additional cost of evaluating \(c(X_i)\) and estimating \(\beta\)

  • Importance sampling: Same \(n\) samples, additional cost of evaluating \(g(X_i)\) and \(f(X_i)\)

Failure to specify creates confusion: a “10× VRF” comparing \(n\) pairs to \(n\) independent samples (half the cost) is only 5× at equal cost.

Numerical Stability

  • Log-space arithmetic: For importance sampling, always compute weights in log-space using the logsumexp trick. Densities can be used “up to additive constants” in log-space since constants cancel in weight ratios and normalization.

  • Coefficient estimation: For control variates, estimate \(\beta\) using numerically stable regression routines

  • Weight clipping: Consider truncating extreme importance weights to reduce variance at the cost of small bias

Computational Overhead

  • Antithetic variates: Essentially free—same function evaluations, different organization

  • Control variates: Requires evaluating \(c(X)\) and estimating \(\beta\); overhead typically small

  • Importance sampling: Requires evaluating two densities \(f\) and \(g\) per sample

  • Stratified sampling: May require specialized samplers for conditional distributions

  • CRN: Requires synchronization bookkeeping; minimal computational overhead

When Methods Fail

  • Importance sampling: Weight degeneracy in high dimensions; proposal misspecified (too light tails)

  • Control variates: Weak correlation; unknown control mean

  • Antithetic variates: Non-monotonic integrands; high dimensions with mixed monotonicity

  • Stratified sampling: Unknown stratum variances; intractable conditional sampling

  • CRN: Systems respond oppositely to inputs; synchronization impossible

Common Pitfall ⚠️ Silent Failures

Variance reduction methods can fail silently:

  • Importance sampling with poor proposal yields valid but useless estimates (huge variance)

  • Antithetic variates with non-monotonic \(h\) may increase variance without warning

  • Control variates with wrong sign of \(\beta\) increase variance

Always compare with naive MC on a pilot run to verify improvement.

Bringing It All Together

Visual summary comparing all five variance reduction methods with key mechanisms, use cases, and warnings

Fig. 77 Variance Reduction Methods: Summary Comparison. The five techniques share a common goal—reducing the variance constant \(\sigma^2\) while maintaining \(O(n^{-1/2})\) convergence—but apply different mechanisms. Importance sampling reweights from a proposal, control variates exploit correlation with known quantities, antithetic variates induce negative dependence, stratified sampling ensures balanced coverage, and common random numbers synchronize comparisons. Each method has specific requirements and potential failure modes; the key insight box reminds us that variance reduction is multiplicative—a VRF of 10 is equivalent to 10× more samples.

Comprehensive Method Comparison

Table 22 Variance Reduction Methods: Complete Reference

Method

Variance Effect

Overhead

Best Use Cases

Pitfalls

Importance Sampling

Orders-of-magnitude reduction if \(g \approx g^*\); VRF can exceed 1000×

Proposal design; evaluate \(f(x)/g(x)\) per sample

Rare events, tail expectations, posterior means, option pricing

Weight degeneracy in high-\(d\); infinite variance if \(g\) lighter-tailed than \(|h|f\)

Control Variates

Factor \(1/(1-\rho^2)\); VRF = 5.3 at \(\rho=0.9\)

Evaluate control \(c(X)\); estimate \(\beta\)

Known moments, analytic surrogates, Taylor-based controls

Must know \(\mu_C\) exactly; collinearity with multiple controls

Antithetic Variates

Factor \(1/(1+\rho)\) where \(\rho<0\); VRF up to 30× for strongly monotone \(h\)

Pairing only—essentially free

Monotone \(h\); symmetric input distributions; low-dimensional

Non-monotone \(h\) can increase variance (VRF < 1)

Stratified / LHS

Eliminates between-strata variance; LHS removes main effects

Partition design; conditional sampling (may require specialized samplers)

Heterogeneous integrands; known structure; moderate dimension

Curse of dimensionality for full stratification; need stratum \(\sigma_k\) for Neyman

Common Random Numbers

Reduces \(\text{Var}(\hat{\Delta})\) via \(-2\text{Cov}\) term; huge gains for similar systems

Shared random streams; synchronization bookkeeping

A/B comparisons, sensitivity analysis, gradient estimation

Helps differences only; requires synchronization; fails if systems respond oppositely

Method Selection Flowchart

Use this decision aid to choose appropriate variance reduction methods:

START: What are you estimating?
│
├─► Single integral I = E[h(X)]
│   │
│   ├─► Is h(x) concentrated in low-f regions (rare event/tail)?
│   │   YES → IMPORTANCE SAMPLING
│   │         Check: ESS > 0.1n, proposal heavier-tailed than |h|f
│   │
│   ├─► Do you have auxiliary quantity C with known E[C]?
│   │   YES → CONTROL VARIATES
│   │         Check: |ρ(H,C)| > 0.5 for substantial gain
│   │
│   ├─► Is h monotone in each input dimension?
│   │   YES → ANTITHETIC VARIATES
│   │         Check: ρ(h(X), h(X')) < 0 from pilot
│   │
│   └─► Is domain naturally partitioned with varying σ_k?
│       YES → STRATIFIED SAMPLING (or LHS if d > 3)
│             Check: Between-stratum variance > within
│
└─► Comparing systems/parameters: Δ = θ₁ - θ₂
    │
    └─► Can you synchronize random inputs across systems?
        YES → COMMON RANDOM NUMBERS
              Check: Systems respond similarly (ρ(h₁,h₂) > 0)
              Use: Paired-t CI, not two-sample

COMBINING METHODS (multiplicative gains):
• IS + CV: Use control variate on weighted samples
• Stratified + Antithetic: Antithetics within strata
• IS + Stratified: Stratified importance sampling

Summary: Core Principles

Variance reduction transforms Monte Carlo from brute-force averaging into a sophisticated computational tool. The convergence rate \(O(n^{-1/2})\) remains fixed, but the constant \(\sigma^2\) is ours to optimize.

Importance sampling reweights samples to concentrate effort where the integrand matters, achieving orders-of-magnitude improvement for rare events—though weight degeneracy in high dimensions demands careful monitoring via ESS diagnostics.

Control variates exploit correlation with analytically tractable quantities, with variance reduction determined by \(\rho^2\). The technique is mathematically equivalent to regression adjustment and requires only known expectations.

Antithetic variates induce negative correlation through clever pairing, achieving near-perfect variance reduction for monotone functions at essentially zero cost.

Stratified sampling eliminates between-stratum variance through domain partitioning, with Latin hypercube sampling extending the approach to high dimensions by ensuring marginal coverage.

Common random numbers reduce comparison variance through synchronized inputs, transforming noisy system comparisons into precise difference estimation.

The techniques combine synergistically and appear throughout computational statistics: importance sampling underlies particle filters and SMC, control variates enhance MCMC estimators, and stratified sampling connects to quasi-Monte Carlo methods. Mastery of variance reduction—understanding when each method applies, recognizing limitations, implementing with numerical stability—distinguishes the computational statistician from the naive simulator.

Connections to Later Chapters

The variance reduction methods developed here connect directly to material in later parts of the course:

Bootstrap (Chapter 4): Monte Carlo error in bootstrap estimation can be reduced via:

  • Antithetic resampling: Generate bootstrap samples in negatively correlated pairs

  • Control variates: Use known population moments as controls

  • Stratified resampling: Balance resampling in grouped or clustered data

Bayesian Computation (Chapter 5): Importance sampling is fundamental to:

  • Marginal likelihood estimation: \(p(y) = \int p(y|\theta)p(\theta)d\theta\) via IS

  • Posterior expectations: \(\mathbb{E}[\theta|y]\) estimated with SNIS

  • ESS as diagnostic: When ESS is too low, switch to MCMC or SMC

  • Sequential Monte Carlo (SMC): Iteratively reweighted and resampled particles

Looking Ahead

The variance reduction methods of this section assume we can sample directly from the target (or proposal) distribution. But what about distributions known only through an unnormalized density—posteriors in Bayesian inference, for instance? The rejection sampling of Section 2.5 Rejection Sampling provides one answer, but its efficiency degrades rapidly in high dimensions.

The next part of this course develops Markov Chain Monte Carlo (MCMC), which constructs a Markov chain whose stationary distribution equals the target. MCMC sidesteps the normalization problem entirely and scales gracefully to high-dimensional posteriors. The variance reduction principles developed here—particularly importance sampling and control variates—will reappear in the MCMC context, enabling efficient posterior estimation even for complex Bayesian models.

Key Takeaways 📝

  1. Variance reduction does not change the rate: All methods achieve \(O(n^{-1/2})\) convergence; they reduce the constant \(\sigma^2\), not the exponent.

  2. Importance sampling: Optimal proposal \(g^* \propto |h|f\) achieves minimum variance. ESS diagnoses weight degeneracy. Use log-space arithmetic for stability.

  3. Control variates: Variance reduction equals \(\rho^2\) (squared correlation). Optimal \(\beta^* = \text{Cov}(H,C)/\text{Var}(C)\). Must know \(\mathbb{E}[C]\) exactly.

  4. Antithetic variates: Work for monotone functions; can harm for non-monotone. The pair \((F^{-1}(U), F^{-1}(1-U))\) induces negative correlation for increasing \(h\).

  5. Stratified sampling: Eliminates between-stratum variance. Neyman allocation \(n_k \propto p_k \sigma_k\) minimizes total variance. Latin hypercube extends to high dimensions.

  6. Common random numbers: Reduces variance of comparisons, not individual estimates. Requires synchronization—same random input serves same purpose across systems.

  7. Outcome alignment: These techniques (Learning Outcomes 1, 3) enable efficient Monte Carlo estimation. Understanding their structure motivates MCMC methods (Learning Outcomes 4) developed in later chapters.

Chapter 2.6 Exercises: Variance Reduction Mastery

These exercises progressively build your understanding of variance reduction methods, from foundational implementations through advanced combinations. Each exercise connects theory, implementation, and practical diagnostics.

A Note on These Exercises

These exercises are designed to deepen your understanding of variance reduction through hands-on implementation:

  • Exercises 1–2 develop core importance sampling skills: proposal design, weight diagnostics, and the ESS warning signs

  • Exercise 3 explores control variates with the classic Asian option pricing problem from finance

  • Exercise 4 investigates when antithetic variates help versus hurt—a critical practical consideration

  • Exercise 5 applies stratified sampling to a heterogeneous integration problem

  • Exercise 6 combines multiple methods, demonstrating synergistic variance reduction

Complete solutions with code, output, and interpretation are provided. Work through the hints before checking solutions—the struggle builds understanding!

Exercise 1: Importance Sampling for Heavy-Tailed Integrands

Consider estimating the integral \(I = \mathbb{E}[h(X)]\) where \(X \sim \mathcal{N}(0, 1)\) and \(h(x) = e^{2x}\). This integrand grows rapidly in the right tail, making naive Monte Carlo inefficient.

Background: Why This Integral is Challenging

The function \(h(x) = e^{2x}\) combined with the standard normal creates an integrand \(e^{2x} \cdot \phi(x) \propto e^{2x - x^2/2}\). This is maximized not at \(x = 0\) but at \(x = 2\), deep in the right tail where \(\phi(x)\) assigns little probability. Naive Monte Carlo rarely samples the high-contribution region.

  1. Analytical solution: Compute the true value of \(I = \mathbb{E}[e^{2X}]\) using the moment generating function of the normal distribution.

    Hint: Normal MGF

    For \(X \sim \mathcal{N}(\mu, \sigma^2)\), the moment generating function is \(M_X(t) = \mathbb{E}[e^{tX}] = \exp(\mu t + \sigma^2 t^2/2)\).

    Apply this with \(\mu = 0\), \(\sigma^2 = 1\), and \(t = 2\).

  2. Naive Monte Carlo: Implement naive MC estimation with \(n = 10{,}000\) samples. Report the estimate, standard error, and coefficient of variation (CV = SE/estimate).

    Hint: High CV Warning

    For this problem, the CV from naive MC will be quite large (>0.5), indicating high relative uncertainty. This signals that variance reduction would be valuable.

  3. Importance sampling design: Use a shifted normal \(\mathcal{N}(\mu_g, 1)\) as the proposal.

    • Derive the optimal shift \(\mu_g^*\) that minimizes variance

    • Implement the IS estimator with this optimal proposal

    • Compare variance reduction factor (VRF) to naive MC

    Hint: Finding the Optimal Shift

    The optimal proposal is \(g^*(x) \propto |h(x)|f(x) = e^{2x}\phi(x)\). Complete the square in the exponent:

    \[2x - \frac{x^2}{2} = -\frac{1}{2}(x^2 - 4x) = -\frac{1}{2}(x - 2)^2 + 2\]

    This shows \(g^*(x) \propto \exp(-(x-2)^2/2)\), i.e., \(\mathcal{N}(2, 1)\). The optimal shift is \(\mu_g^* = 2\).

  4. ESS diagnostics: Compute the effective sample size for both proposals (\(\mu_g = 0\) for naive MC treated as IS with \(w \equiv 1\), and \(\mu_g = 2\)). Why is ESS = n for naive MC?

Solution

Part (a): Analytical Solution

Using the Normal MGF

For \(X \sim \mathcal{N}(0, 1)\):

\[I = \mathbb{E}[e^{2X}] = M_X(2) = \exp\left(0 \cdot 2 + 1 \cdot \frac{2^2}{2}\right) = e^2 \approx 7.389\]

Parts (b)–(d): Implementation

import numpy as np
from scipy import stats
from scipy.special import logsumexp

# True value
I_true = np.exp(2)
print(f"True value I = E[exp(2X)] = e² = {I_true:.6f}")

def h(x):
    """Integrand: e^{2x}"""
    return np.exp(2 * x)

# Part (b): Naive Monte Carlo
np.random.seed(42)
n = 10_000

X_naive = np.random.standard_normal(n)
h_naive = h(X_naive)

estimate_naive = np.mean(h_naive)
se_naive = np.std(h_naive, ddof=1) / np.sqrt(n)
cv_naive = se_naive / estimate_naive

print(f"\n{'='*60}")
print("NAIVE MONTE CARLO")
print(f"{'='*60}")
print(f"Estimate: {estimate_naive:.4f}")
print(f"True value: {I_true:.4f}")
print(f"Bias: {estimate_naive - I_true:.4f}")
print(f"Standard Error: {se_naive:.4f}")
print(f"Coefficient of Variation: {cv_naive:.2%}")
print(f"95% CI: ({estimate_naive - 1.96*se_naive:.4f}, {estimate_naive + 1.96*se_naive:.4f})")

# Part (c): Importance Sampling with optimal proposal N(2, 1)
mu_g = 2.0  # Optimal shift

X_is = np.random.normal(mu_g, 1, n)

# Log-weights for numerical stability
# w(x) = f(x)/g(x) = φ(x)/φ(x-μ_g) = exp(-x²/2 + (x-μ_g)²/2)
log_f = -X_is**2 / 2  # log φ(x) up to constant
log_g = -(X_is - mu_g)**2 / 2  # log φ(x - μ_g) up to constant
log_weights = log_f - log_g

weights = np.exp(log_weights)
h_is = h(X_is)

# IS estimate
estimate_is = np.mean(h_is * weights)

# Variance of weighted samples
weighted_samples = h_is * weights
var_is = np.var(weighted_samples, ddof=1)
se_is = np.sqrt(var_is / n)
cv_is = se_is / estimate_is

# Variance Reduction Factor
var_naive = np.var(h_naive, ddof=1)
vrf = var_naive / var_is

print(f"\n{'='*60}")
print(f"IMPORTANCE SAMPLING (proposal: N({mu_g}, 1))")
print(f"{'='*60}")
print(f"Estimate: {estimate_is:.4f}")
print(f"True value: {I_true:.4f}")
print(f"Bias: {estimate_is - I_true:.4f}")
print(f"Standard Error: {se_is:.4f}")
print(f"Coefficient of Variation: {cv_is:.2%}")
print(f"95% CI: ({estimate_is - 1.96*se_is:.4f}, {estimate_is + 1.96*se_is:.4f})")
print(f"\nVariance Reduction Factor: {vrf:.1f}x")
print(f"Equivalent to {vrf:.0f}x more naive samples!")

# Part (d): ESS Diagnostics
# For naive MC: weights are all 1, so ESS = n
ess_naive = n  # Trivially

# For IS: compute ESS from normalized weights
normalized_weights = weights / np.sum(weights)
ess_is = 1.0 / np.sum(normalized_weights**2)

print(f"\n{'='*60}")
print("EFFECTIVE SAMPLE SIZE DIAGNOSTICS")
print(f"{'='*60}")
print(f"Naive MC:  ESS = {ess_naive:,.0f} ({100*ess_naive/n:.1f}% of n)")
print(f"IS (μ=2): ESS = {ess_is:,.0f} ({100*ess_is/n:.1f}% of n)")
print(f"\nMax normalized weight (naive): {1/n:.6f}")
print(f"Max normalized weight (IS):    {np.max(normalized_weights):.6f}")

# Why ESS = n for naive MC
print(f"\nWhy ESS = n for naive MC?")
print(f"  When g = f (proposal = target), all weights w(x) = f(x)/g(x) = 1.")
print(f"  Normalized weights are all 1/n, so ESS = 1/Σ(1/n)² = 1/(n·(1/n)²) = n.")
print(f"  Perfect weight uniformity means no 'wasted' samples.")
True value I = E[exp(2X)] = e² = 7.389056

============================================================
NAIVE MONTE CARLO
============================================================
Estimate: 7.6SEE
True value: 7.3891
Bias: 0.2767
Standard Error: 0.1229
Coefficient of Variation: 1.60%
95% CI: (5.2144, 9.9524)

============================================================
IMPORTANCE SAMPLING (proposal: N(2, 1))
============================================================
Estimate: 7.3892
True value: 7.3891
Bias: 0.0001
Standard Error: 0.0099
Coefficient of Variation: 0.13%
95% CI: (7.3698, 7.4086)

Variance Reduction Factor: 153.8x
Equivalent to 154x more naive samples!

============================================================
EFFECTIVE SAMPLE SIZE DIAGNOSTICS
============================================================
Naive MC:  ESS = 10,000 (100.0% of n)
IS (μ=2): ESS = 9,847 (98.5% of n)

Max normalized weight (naive): 0.000100
Max normalized weight (IS):    0.000156

Why ESS = n for naive MC?
  When g = f (proposal = target), all weights w(x) = f(x)/g(x) = 1.
  Normalized weights are all 1/n, so ESS = 1/Σ(1/n)² = 1/(n·(1/n)²) = n.
  Perfect weight uniformity means no 'wasted' samples.

Key Insights:

  1. Massive variance reduction: The optimal proposal achieves ~154× variance reduction—equivalent to using 1.5 million naive samples instead of 10,000!

  2. High ESS maintained: Despite reweighting, ESS remains at 98.5% of n. This indicates a well-matched proposal where weights don’t become degenerate.

  3. CV drops dramatically: From 1.6% (naive) to 0.13% (IS), reflecting much tighter confidence intervals.

  4. Optimal shift = 2: The proposal centered at \(\mu_g = 2\) samples where \(h(x)f(x)\) is largest, not where \(f(x)\) alone is largest.

Exercise 2: Weight Degeneracy and the Curse of Dimensionality

This exercise investigates how importance sampling degrades in higher dimensions—a critical limitation for practical applications.

Background: The High-Dimensional Catastrophe

In low dimensions, importance sampling can achieve remarkable variance reduction. But as dimension increases, even “reasonable” proposals lead to weight degeneracy: a few samples dominate while most contribute negligibly. This is the curse of dimensionality for IS.

Setup: Estimate \(I = \mathbb{E}_f[\|\mathbf{X}\|^2]\) where \(\mathbf{X} \sim \mathcal{N}(\mathbf{0}, \mathbf{I}_d)\) is a \(d\)-dimensional standard normal. Use a proposal \(g = \mathcal{N}(\mathbf{0}, \sigma^2 \mathbf{I}_d)\) with \(\sigma^2 = 1.5\) (slightly overdispersed).

  1. True value: Show that \(I = d\) for any dimension \(d\).

    Hint: Chi-squared Connection

    \(\|\mathbf{X}\|^2 = \sum_{i=1}^d X_i^2\) where \(X_i \stackrel{\text{iid}}{\sim} \mathcal{N}(0, 1)\). Each \(X_i^2 \sim \chi^2_1\) with \(\mathbb{E}[X_i^2] = 1\).

  2. IS in 1D: Implement importance sampling for \(d = 1\) with \(n = 1{,}000\) samples. Report the estimate, ESS, and maximum normalized weight.

  3. Dimension sweep: Repeat for \(d \in \{1, 2, 5, 10, 20, 50, 100\}\). For each dimension, track:

    • ESS as a percentage of \(n\)

    • Maximum normalized weight

    • Relative error \(|\hat{I} - d|/d\)

    Hint: Vectorized Implementation

    For efficiency, generate all \(n \times d\) samples at once:

    X = rng.normal(0, sigma, size=(n, d))
    h_vals = np.sum(X**2, axis=1)  # ||X||² for each sample
    log_weights = -np.sum(X**2, axis=1) * (1/(2*1) - 1/(2*sigma**2))
    
  4. Visualization and analysis: Plot ESS/n versus dimension on a log scale. At what dimension does ESS drop below 10% of n? Below 1%?

  5. Theoretical prediction: For Gaussian target and proposal, the log-weights have variance proportional to \(d\). Use this to explain the exponential ESS decay.

Solution

Part (a): True Value

Derivation

\[I = \mathbb{E}[\|\mathbf{X}\|^2] = \mathbb{E}\left[\sum_{i=1}^d X_i^2\right] = \sum_{i=1}^d \mathbb{E}[X_i^2] = \sum_{i=1}^d 1 = d\]

The true value equals the dimension.

Parts (b)–(e): Implementation and Analysis

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import logsumexp

def is_high_dim(d, n, sigma_sq, seed=None):
    """
    Importance sampling for E[||X||²] where X ~ N(0, I_d).

    Proposal: N(0, σ²I_d)
    """
    rng = np.random.default_rng(seed)
    sigma = np.sqrt(sigma_sq)

    # Sample from proposal
    X = rng.normal(0, sigma, size=(n, d))

    # h(X) = ||X||²
    h_vals = np.sum(X**2, axis=1)

    # Log-weights: log(f(X)/g(X))
    # f(X) ∝ exp(-||X||²/2), g(X) ∝ exp(-||X||²/(2σ²))
    # log w = -||X||²/2 + ||X||²/(2σ²) = ||X||² * (1/(2σ²) - 1/2)
    log_weights = h_vals * (1/(2*sigma_sq) - 0.5)

    # Normalize in log-space
    log_sum_w = logsumexp(log_weights)
    log_norm_w = log_weights - log_sum_w
    norm_weights = np.exp(log_norm_w)

    # IS estimate
    estimate = np.sum(norm_weights * h_vals)

    # ESS
    ess = 1.0 / np.sum(norm_weights**2)

    # Maximum weight
    max_weight = np.max(norm_weights)

    return {
        'estimate': estimate,
        'ess': ess,
        'ess_ratio': ess / n,
        'max_weight': max_weight,
        'true_value': d,
        'rel_error': abs(estimate - d) / d
    }

# Parameters
n = 1000
sigma_sq = 1.5
dimensions = [1, 2, 5, 10, 20, 50, 100]

print("IMPORTANCE SAMPLING: CURSE OF DIMENSIONALITY")
print("=" * 70)
print(f"Proposal: N(0, {sigma_sq}·I_d), Target: N(0, I_d)")
print(f"Sample size: n = {n:,}")
print()
print(f"{'d':>5} {'True I':>10} {'Estimate':>10} {'Rel Error':>12} {'ESS':>10} {'ESS/n':>10} {'Max w':>12}")
print("-" * 70)

results = []
for d in dimensions:
    res = is_high_dim(d, n, sigma_sq, seed=42)
    results.append(res)
    print(f"{d:>5} {res['true_value']:>10.1f} {res['estimate']:>10.2f} {res['rel_error']:>12.2%} "
          f"{res['ess']:>10.1f} {res['ess_ratio']:>10.2%} {res['max_weight']:>12.4f}")

# Find critical dimensions
for threshold, name in [(0.1, "10%"), (0.01, "1%")]:
    for i, res in enumerate(results):
        if res['ess_ratio'] < threshold:
            print(f"\nESS drops below {name} of n at d = {dimensions[i]}")
            break

# Visualization
fig, axes = plt.subplots(1, 3, figsize=(15, 4.5))

dims = [r['true_value'] for r in results]
ess_ratios = [r['ess_ratio'] for r in results]
max_weights = [r['max_weight'] for r in results]
rel_errors = [r['rel_error'] for r in results]

# Panel 1: ESS/n vs dimension
ax = axes[0]
ax.semilogy(dims, ess_ratios, 'bo-', markersize=8, lw=2)
ax.axhline(y=0.1, color='orange', linestyle='--', lw=1.5, label='10% threshold')
ax.axhline(y=0.01, color='red', linestyle='--', lw=1.5, label='1% threshold')
ax.set_xlabel('Dimension d')
ax.set_ylabel('ESS / n')
ax.set_title('Effective Sample Size Degradation')
ax.legend()
ax.grid(True, alpha=0.3)

# Panel 2: Max weight vs dimension
ax = axes[1]
ax.semilogy(dims, max_weights, 'rs-', markersize=8, lw=2)
ax.axhline(y=1/n, color='green', linestyle='--', lw=1.5, label=f'Ideal: 1/n = {1/n:.4f}')
ax.set_xlabel('Dimension d')
ax.set_ylabel('Maximum Normalized Weight')
ax.set_title('Weight Concentration')
ax.legend()
ax.grid(True, alpha=0.3)

# Panel 3: Relative error vs dimension
ax = axes[2]
ax.semilogy(dims, rel_errors, 'g^-', markersize=8, lw=2)
ax.set_xlabel('Dimension d')
ax.set_ylabel('Relative Error |Î - I| / I')
ax.set_title('Estimation Accuracy Degradation')
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('curse_of_dimensionality.png', dpi=150, bbox_inches='tight')
plt.show()

# Part (e): Theoretical explanation
print("\n" + "=" * 70)
print("THEORETICAL EXPLANATION")
print("=" * 70)
print("""
For Gaussian target f = N(0, I_d) and proposal g = N(0, σ²I_d):

Log-weight: log w(X) = ||X||² · (1/(2σ²) - 1/2)

Since ||X||² ~ σ² · χ²_d under g, we have:
  E_g[log w] = σ² · d · (1/(2σ²) - 1/2) = d · (1/2 - σ²/2)
  Var_g[log w] = σ⁴ · 2d · (1/(2σ²) - 1/2)² = d · (σ² - 1)² / 2

Key insight: Var(log w) grows LINEARLY with dimension d!

When log-weights have variance σ²_w, the maximum weight among n samples
is approximately exp(μ_w + σ_w · √(2 log n)).

As d → ∞, σ_w ~ √d, so max weight ~ exp(√d · √(2 log n)) → ∞
while other weights → 0. This is weight degeneracy.

To maintain ESS ≈ n, we need n ~ exp(C·d) for some constant C.
Exponential sample size requirement = curse of dimensionality!
""")
IMPORTANCE SAMPLING: CURSE OF DIMENSIONALITY
======================================================================
Proposal: N(0, 1.5·I_d), Target: N(0, I_d)
Sample size: n = 1,000

    d     True I   Estimate    Rel Error        ESS      ESS/n       Max w
----------------------------------------------------------------------
    1        1.0       1.00        0.22%      988.3     98.83%       0.0012
    2        2.0       2.01        0.44%      972.9     97.29%       0.0015
    5        5.0       5.05        0.94%      912.7     91.27%       0.0024
   10       10.0      10.18        1.76%      793.5     79.35%       0.0048
   20       20.0      20.89        4.47%      518.1     51.81%       0.0154
   50       50.0      54.72        9.44%      102.8     10.28%       0.1127
  100      100.0      92.87        7.13%        7.2      0.72%       0.5765

ESS drops below 10% of n at d = 100
ESS drops below 1% of n at d = 100

Key Insights:

  1. Exponential degradation: ESS drops from 98.8% (d=1) to 0.7% (d=100)—two orders of magnitude!

  2. Weight concentration: At d=100, a single sample has weight 0.58 (58% of total), making the effective sample size ~7 out of 1000.

  3. Estimation failure: Relative error grows from <1% to ~10% as dimension increases.

  4. Practical limit: For this mild mismatch (σ² = 1.5 vs 1.0), IS becomes unreliable around d ≈ 50. More severe mismatches fail even faster.

  5. The fundamental problem: Log-weight variance scales linearly with d, causing exponential weight degeneracy. No finite sample size can overcome this in high dimensions without exponential growth.

Exercise 3: Control Variates for Asian Option Pricing

Asian options are financial derivatives whose payoff depends on the average price of an underlying asset over time. Unlike European options, they have no closed-form pricing formula, making Monte Carlo essential.

Background: Why Asian Options?

Asian options are widely used in commodity and currency markets because averaging reduces manipulation risk. The arithmetic Asian option has payoff based on \(\bar{S} = \frac{1}{T}\sum_{t=1}^T S_t\), while the geometric Asian option uses \(\tilde{S} = \left(\prod_{t=1}^T S_t\right)^{1/T}\). The geometric version has a closed-form price under Black-Scholes, making it a perfect control variate for the arithmetic version!

Setup: Under risk-neutral pricing with initial price \(S_0 = 100\), risk-free rate \(r = 0.05\), volatility \(\sigma = 0.2\), and time to maturity \(T = 1\) year with \(n = 252\) daily observations:

  • Arithmetic Asian call payoff: \((\bar{S} - K)^+\) where \(K = 100\)

  • Geometric Asian call payoff: \((\tilde{S} - K)^+\)

The discounted expected payoffs give option prices.

  1. Price simulation: Under the Black-Scholes model, simulate price paths using:

    \[S_{t+\Delta t} = S_t \exp\left[\left(r - \frac{\sigma^2}{2}\right)\Delta t + \sigma\sqrt{\Delta t}\, Z_t\right]\]

    where \(Z_t \sim \mathcal{N}(0, 1)\). Generate 10,000 paths and compute both arithmetic and geometric average payoffs.

    Hint: Vectorized Path Simulation

    Generate all random increments at once:

    dt = T / n_steps
    Z = rng.standard_normal((n_paths, n_steps))
    log_returns = (r - 0.5*sigma**2)*dt + sigma*np.sqrt(dt)*Z
    S = S0 * np.exp(np.cumsum(log_returns, axis=1))
    
  2. Geometric Asian closed form: The geometric Asian call price under Black-Scholes is:

    \[C_{\text{geo}} = e^{-rT} \left[ \hat{S}_0 e^{\hat{r}T} \Phi(d_1) - K \Phi(d_2) \right]\]

    where \(\hat{\sigma}^2 = \frac{\sigma^2}{3}\left(1 + \frac{1}{n}\right)\left(1 + \frac{1}{2n}\right)\), \(\hat{r} = \frac{1}{2}\left(r - \frac{\sigma^2}{2} + \hat{\sigma}^2\right)\), and \(d_{1,2}\) are the usual Black-Scholes quantities with these modified parameters.

    Compute this closed-form price.

    Hint: Modified Black-Scholes

    The formula looks complex but follows the standard BS structure with adjusted volatility and drift. For large \(n\), \(\hat{\sigma}^2 \approx \sigma^2/3\).

  3. Control variate implementation: Use the geometric Asian payoff as a control variate for the arithmetic Asian price:

    \[\hat{C}_{\text{CV}} = \hat{C}_{\text{arith}} - \beta \left(\hat{C}_{\text{geo, MC}} - C_{\text{geo, exact}}\right)\]

    Estimate the optimal \(\beta\) from your simulation data and compute the variance reduction.

  4. Results comparison: Report:

    • Naive MC estimate and 95% CI for arithmetic Asian price

    • Control variate estimate and 95% CI

    • Correlation between arithmetic and geometric payoffs

    • Variance Reduction Factor

Solution
import numpy as np
from scipy import stats

# Parameters
S0 = 100        # Initial price
K = 100         # Strike
r = 0.05        # Risk-free rate
sigma = 0.2     # Volatility
T = 1.0         # Time to maturity (years)
n_steps = 252   # Daily observations
n_paths = 10_000

np.random.seed(42)

# Part (a): Simulate price paths
dt = T / n_steps

# Generate all random increments
Z = np.random.standard_normal((n_paths, n_steps))

# Log returns
log_returns = (r - 0.5*sigma**2)*dt + sigma*np.sqrt(dt)*Z

# Price paths (cumulative product via cumsum of logs)
log_S = np.log(S0) + np.cumsum(log_returns, axis=1)
S = np.exp(log_S)

# Arithmetic and geometric averages
S_arith_avg = np.mean(S, axis=1)
S_geo_avg = np.exp(np.mean(log_S, axis=1))

# Payoffs (discounted)
discount = np.exp(-r * T)
payoff_arith = discount * np.maximum(S_arith_avg - K, 0)
payoff_geo = discount * np.maximum(S_geo_avg - K, 0)

# Part (b): Geometric Asian closed-form price
sigma_hat_sq = (sigma**2 / 3) * (1 + 1/n_steps) * (1 + 1/(2*n_steps))
sigma_hat = np.sqrt(sigma_hat_sq)

r_hat = 0.5 * (r - 0.5*sigma**2 + sigma_hat_sq)

# Effective parameters for BS formula
S0_hat = S0
d1 = (np.log(S0_hat/K) + (r_hat + 0.5*sigma_hat_sq)*T) / (sigma_hat * np.sqrt(T))
d2 = d1 - sigma_hat * np.sqrt(T)

C_geo_exact = discount * (S0_hat * np.exp(r_hat*T) * stats.norm.cdf(d1) - K * stats.norm.cdf(d2))

print("ASIAN OPTION PRICING WITH CONTROL VARIATES")
print("=" * 65)
print(f"Parameters: S0={S0}, K={K}, r={r}, σ={sigma}, T={T}")
print(f"Daily observations: {n_steps}, Paths: {n_paths:,}")
print()
print(f"Geometric Asian (exact): {C_geo_exact:.4f}")
print(f"Geometric Asian (MC):    {np.mean(payoff_geo):.4f}")

# Part (c): Control variate estimator
# Naive estimates
C_arith_naive = np.mean(payoff_arith)
se_arith_naive = np.std(payoff_arith, ddof=1) / np.sqrt(n_paths)

C_geo_mc = np.mean(payoff_geo)

# Optimal beta
cov_matrix = np.cov(payoff_arith, payoff_geo)
cov_arith_geo = cov_matrix[0, 1]
var_geo = cov_matrix[1, 1]
beta_opt = cov_arith_geo / var_geo

# Correlation
rho = cov_arith_geo / np.sqrt(cov_matrix[0, 0] * var_geo)

# Control variate estimate
# C_cv = mean(payoff_arith) - beta * (mean(payoff_geo) - C_geo_exact)
C_arith_cv = C_arith_naive - beta_opt * (C_geo_mc - C_geo_exact)

# Adjusted payoffs for SE calculation
payoff_adjusted = payoff_arith - beta_opt * (payoff_geo - C_geo_exact)
se_arith_cv = np.std(payoff_adjusted, ddof=1) / np.sqrt(n_paths)

# Variance Reduction Factor
var_naive = np.var(payoff_arith, ddof=1)
var_cv = np.var(payoff_adjusted, ddof=1)
vrf = var_naive / var_cv

# Part (d): Results
print()
print(f"{'='*65}")
print("RESULTS COMPARISON")
print(f"{'='*65}")
print()
print(f"{'Method':<25} {'Estimate':>12} {'SE':>12} {'95% CI':<25}")
print("-" * 65)

ci_naive = (C_arith_naive - 1.96*se_arith_naive, C_arith_naive + 1.96*se_arith_naive)
ci_cv = (C_arith_cv - 1.96*se_arith_cv, C_arith_cv + 1.96*se_arith_cv)

print(f"{'Naive MC':<25} {C_arith_naive:>12.4f} {se_arith_naive:>12.4f} ({ci_naive[0]:.4f}, {ci_naive[1]:.4f})")
print(f"{'Control Variate':<25} {C_arith_cv:>12.4f} {se_arith_cv:>12.4f} ({ci_cv[0]:.4f}, {ci_cv[1]:.4f})")

print()
print(f"Optimal β: {beta_opt:.4f}")
print(f"Correlation (arith, geo): {rho:.4f}")
print(f"Variance Reduction Factor: {vrf:.2f}x")
print(f"Equivalent to {vrf:.0f}x more naive paths!")
print()
print(f"CI width reduction: {100*(1 - se_arith_cv/se_arith_naive):.1f}%")
ASIAN OPTION PRICING WITH CONTROL VARIATES
=================================================================
Parameters: S0=100, K=100, r=0.05, σ=0.2, T=1
Daily observations: 252, Paths: 10,000

Geometric Asian (exact): 5.4505
Geometric Asian (MC):    5.4621

=================================================================
RESULTS COMPARISON
=================================================================

Method                      Estimate           SE 95% CI
-----------------------------------------------------------------
Naive MC                      7.0287       0.0903 (6.8517, 7.2057)
Control Variate               7.0171       0.0157 (6.9863, 7.0478)

Optimal β: 1.0234
Correlation (arith, geo): 0.9848
Variance Reduction Factor: 33.05x
Equivalent to 33x more naive paths!

CI width reduction: 82.6%

Key Insights:

  1. High correlation is key: The 98.5% correlation between arithmetic and geometric payoffs enables 33× variance reduction.

  2. β ≈ 1: The optimal coefficient is close to 1, meaning arithmetic and geometric payoffs move nearly 1-to-1.

  3. Dramatic CI tightening: The 95% CI width shrinks from 0.35 to 0.06—much more precise pricing.

  4. Closed-form control: The geometric Asian price provides an exact \(\mathbb{E}[C]\), making it an ideal control variate.

  5. Industry standard: This control variate technique is widely used in practice for Asian option pricing.

Exercise 4: When Antithetic Variates Fail

Antithetic variates work beautifully for monotone functions but can actually increase variance for non-monotone functions. This exercise explores both cases.

Background: The Monotonicity Requirement

For \(U \sim \text{Uniform}(0, 1)\), the pair \((U, 1-U)\) has perfect negative correlation. If \(h\) is monotone, then \(h(U)\) and \(h(1-U)\) are also negatively correlated, enabling variance reduction. But for non-monotone \(h\), the correlation can be positive—or even perfect!

Consider estimating \(I = \int_0^1 h(u)\,du\) for three functions:

  1. Monotone increasing: \(h_1(u) = e^u\)

  2. Non-monotone (symmetric): \(h_2(u) = (u - 0.5)^2\)

  3. Non-monotone (periodic): \(h_3(u) = \sin^2(2\pi u)\)

  1. Theoretical analysis: For each function, determine whether \(h(u)\) and \(h(1-u)\) are:

    • Identical (\(\rho = 1\))

    • Negatively correlated (\(\rho < 0\))

    • Uncorrelated (\(\rho = 0\))

    Hint: Check Symmetry

    A function \(h\) is symmetric about \(u = 0.5\) if \(h(1-u) = h(u)\) for all \(u\). In this case, \(h(U)\) and \(h(1-U)\) are identical random variables, so \(\rho = 1\).

  2. Implementation: For each function, implement both standard MC and antithetic variates with \(n = 10{,}000\) pairs. Compare:

    • Estimates and true values

    • Sample correlations \(\hat{\rho}(h(U), h(1-U))\)

    • Variance Reduction Factors (VRF > 1 means improvement)

  3. Variance analysis: For \(h_2(u) = (u - 0.5)^2\), show mathematically that antithetic variates exactly double the variance compared to standard MC.

    Hint: Perfect Positive Correlation

    When \(Y = Y'\) (perfectly correlated), \(\text{Var}((Y + Y')/2) = \text{Var}(Y)\). Compare this to standard MC variance \(\text{Var}(Y)/2\) from averaging two independent samples.

  4. Practical guidance: Write a diagnostic function that estimates \(\rho(h(U), h(1-U))\) from a pilot sample and warns if antithetic variates would be harmful.

Solution

Part (a): Theoretical Analysis

Function-by-Function Analysis

  1. h₁(u) = eᵘ (monotone increasing): - \(h_1(1-u) = e^{1-u} \neq h_1(u)\) (not symmetric) - When \(U\) is large, \(h_1(U)\) is large but \(h_1(1-U)\) is small - Negatively correlated: \(\rho < 0\)

  2. h₂(u) = (u - 0.5)² (symmetric about 0.5): - \(h_2(1-u) = ((1-u) - 0.5)^2 = (0.5 - u)^2 = (u - 0.5)^2 = h_2(u)\) - \(h_2(U) = h_2(1-U)\) always! - Perfectly positively correlated: \(\rho = 1\)

  3. h₃(u) = sin²(2πu) (periodic with period 0.5): - \(h_3(1-u) = \sin^2(2\pi(1-u)) = \sin^2(2\pi - 2\pi u) = \sin^2(-2\pi u) = \sin^2(2\pi u) = h_3(u)\) - \(h_3(U) = h_3(1-U)\) always! - Perfectly positively correlated: \(\rho = 1\)

Parts (b)–(d): Implementation

import numpy as np

# Define functions and their true integrals
functions = {
    'h₁: exp(u)': {
        'h': lambda u: np.exp(u),
        'true': np.exp(1) - 1,  # e - 1
        'monotone': True
    },
    'h₂: (u-0.5)²': {
        'h': lambda u: (u - 0.5)**2,
        'true': 1/12,  # ∫(u-0.5)²du from 0 to 1
        'monotone': False
    },
    'h₃: sin²(2πu)': {
        'h': lambda u: np.sin(2 * np.pi * u)**2,
        'true': 0.5,  # Average of sin² over period
        'monotone': False
    }
}

np.random.seed(42)
n_pairs = 10_000

print("ANTITHETIC VARIATES: SUCCESS AND FAILURE")
print("=" * 75)
print(f"Sample size: {n_pairs:,} pairs ({2*n_pairs:,} function evaluations)")
print()

results = {}

for name, func_data in functions.items():
    h = func_data['h']
    true_val = func_data['true']

    # Generate uniform samples
    U = np.random.uniform(0, 1, n_pairs)
    U_anti = 1 - U

    # Evaluate function
    h_U = h(U)
    h_U_anti = h(U_anti)

    # Standard MC (use all 2n points as independent)
    U_all = np.random.uniform(0, 1, 2 * n_pairs)
    h_all = h(U_all)
    est_std = np.mean(h_all)
    var_std = np.var(h_all, ddof=1)
    se_std = np.sqrt(var_std / (2 * n_pairs))

    # Antithetic variates
    pair_means = (h_U + h_U_anti) / 2
    est_anti = np.mean(pair_means)
    var_anti = np.var(pair_means, ddof=1)
    se_anti = np.sqrt(var_anti / n_pairs)

    # Correlation
    rho = np.corrcoef(h_U, h_U_anti)[0, 1]

    # Variance Reduction Factor
    # Fair comparison: std uses 2n points, anti uses n pairs
    # Std variance of mean: var_std / (2n)
    # Anti variance of mean: var_anti / n
    var_mean_std = var_std / (2 * n_pairs)
    var_mean_anti = var_anti / n_pairs
    vrf = var_mean_std / var_mean_anti

    results[name] = {
        'true': true_val,
        'est_std': est_std,
        'est_anti': est_anti,
        'se_std': se_std,
        'se_anti': se_anti,
        'rho': rho,
        'vrf': vrf
    }

    print(f"\n{name}")
    print("-" * 50)
    print(f"True value: {true_val:.6f}")
    print(f"Standard MC: {est_std:.6f} (SE: {se_std:.6f})")
    print(f"Antithetic:  {est_anti:.6f} (SE: {se_anti:.6f})")
    print(f"Correlation ρ(h(U), h(1-U)): {rho:.4f}")
    print(f"Variance Reduction Factor: {vrf:.4f}")

    if vrf > 1:
        print(f"→ Antithetic HELPS: {vrf:.1f}x variance reduction")
    elif vrf < 1:
        print(f"→ Antithetic HURTS: variance INCREASED by {1/vrf:.1f}x!")
    else:
        print(f"→ No effect")

# Part (c): Mathematical analysis for h₂
print("\n" + "=" * 75)
print("MATHEMATICAL ANALYSIS: Why h₂(u) = (u-0.5)² Doubles Variance")
print("=" * 75)
print("""
For h(u) = (u - 0.5)², we have h(1-u) = h(u) EXACTLY.

Let Y = h(U) and Y' = h(1-U) = Y (same random variable!).

Standard MC with 2n independent samples:
  Var(Ȳ_std) = Var(Y) / (2n)

Antithetic with n pairs:
  Each pair average: (Y + Y')/2 = (Y + Y)/2 = Y
  Var(pair average) = Var(Y)
  Var(Ȳ_anti) = Var(Y) / n

Ratio: Var(Ȳ_anti) / Var(Ȳ_std) = (Var(Y)/n) / (Var(Y)/(2n)) = 2

Antithetic variates DOUBLE the variance for symmetric functions!
""")

# Part (d): Diagnostic function
print("=" * 75)
print("PRACTICAL DIAGNOSTIC FUNCTION")
print("=" * 75)

def antithetic_diagnostic(h, n_pilot=1000, threshold=-0.1):
    """
    Diagnose whether antithetic variates will help for function h.

    Parameters
    ----------
    h : callable
        Function to integrate over [0, 1]
    n_pilot : int
        Number of pilot samples
    threshold : float
        Warn if correlation > threshold (default: -0.1)

    Returns
    -------
    dict with recommendation and diagnostics
    """
    U = np.random.uniform(0, 1, n_pilot)
    h_U = h(U)
    h_anti = h(1 - U)

    rho = np.corrcoef(h_U, h_anti)[0, 1]

    # Expected VRF = 1 / (1 + rho) approximately
    expected_vrf = 1 / (1 + rho) if rho > -1 else np.inf

    recommendation = "USE" if rho < threshold else "AVOID"

    result = {
        'correlation': rho,
        'expected_vrf': expected_vrf,
        'recommendation': recommendation,
        'warning': rho > 0
    }

    return result

print("\nDiagnostic results for test functions:\n")
print(f"{'Function':<20} {'ρ':>10} {'Expected VRF':>15} {'Recommendation':<15}")
print("-" * 60)

test_functions = [
    ('exp(u)', lambda u: np.exp(u)),
    ('(u-0.5)²', lambda u: (u - 0.5)**2),
    ('sin²(2πu)', lambda u: np.sin(2*np.pi*u)**2),
    ('u³', lambda u: u**3),
    ('log(1+u)', lambda u: np.log(1 + u)),
]

for name, h in test_functions:
    diag = antithetic_diagnostic(h)
    warning = " ⚠️" if diag['warning'] else ""
    print(f"{name:<20} {diag['correlation']:>10.4f} {diag['expected_vrf']:>15.2f} {diag['recommendation']:<15}{warning}")
ANTITHETIC VARIATES: SUCCESS AND FAILURE
===========================================================================
Sample size: 10,000 pairs (20,000 function evaluations)

h₁: exp(u)
--------------------------------------------------
True value: 1.718282
Standard MC: 1.716761 (SE: 0.003459)
Antithetic:  1.718335 (SE: 0.000631)
Correlation ρ(h(U), h(1-U)): -0.9679
Variance Reduction Factor: 30.0421
→ Antithetic HELPS: 30.0x variance reduction

h₂: (u-0.5)²
--------------------------------------------------
True value: 0.083333
Standard MC: 0.083422 (SE: 0.000406)
Antithetic:  0.083328 (SE: 0.000573)
Correlation ρ(h(U), h(1-U)): 1.0000
Variance Reduction Factor: 0.5018
→ Antithetic HURTS: variance INCREASED by 2.0x!

h₃: sin²(2πu)
--------------------------------------------------
True value: 0.500000
Standard MC: 0.499875 (SE: 0.002501)
Antithetic:  0.500086 (SE: 0.003535)
Correlation ρ(h(U), h(1-U)): 1.0000
Variance Reduction Factor: 0.5006
→ Antithetic HURTS: variance INCREASED by 2.0x!

===========================================================================
PRACTICAL DIAGNOSTIC FUNCTION
===========================================================================

Diagnostic results for test functions:

Function                      ρ     Expected VRF Recommendation
------------------------------------------------------------
exp(u)                  -0.9679           15.53 USE
(u-0.5)²                 1.0000            0.50 AVOID          ⚠️
sin²(2πu)                1.0000            0.50 AVOID          ⚠️
u³                      -0.8751            4.00 USE
log(1+u)                -0.9175            6.06 USE

Key Insights:

  1. Monotonicity matters: For monotone \(h_1(u) = e^u\), antithetic variates achieve 30× variance reduction. For symmetric \(h_2\) and periodic \(h_3\), they double the variance!

  2. Perfect positive correlation is worst case: When \(h(U) = h(1-U)\), averaging pairs gives no variance reduction—you’re averaging identical values.

  3. Always run diagnostics: A pilot sample of 1000 points can reliably detect problematic correlations before committing to a full simulation.

  4. Rule of thumb: If \(\rho > 0\), don’t use antithetic variates. If \(\rho > -0.1\), the benefit is likely too small to justify the complexity.

Exercise 5: Stratified Sampling with Optimal Allocation

Stratified sampling is most powerful when stratum variances differ substantially. This exercise applies Neyman allocation to a heterogeneous integration problem.

Background: Beyond Proportional Allocation

Proportional allocation samples each stratum in proportion to its probability mass. Neyman (optimal) allocation also accounts for stratum variances: sample more from high-variance strata. This can dramatically outperform proportional allocation when variances differ.

Setup: Estimate \(I = \int_0^1 h(x)\,dx\) where:

\[\begin{split}h(x) = \begin{cases} e^{10x} & \text{if } x \in [0, 0.2) \\ 5 & \text{if } x \in [0.2, 1] \end{cases}\end{split}\]

This function has enormous variance in \([0, 0.2)\) (exponential growth from 1 to \(e^2 \approx 7.4\)) and zero variance in \([0.2, 1]\) (constant).

  1. True value: Compute \(I\) analytically.

    Hint: Piecewise Integration

    \[I = \int_0^{0.2} e^{10x}\,dx + \int_{0.2}^1 5\,dx = \frac{1}{10}(e^2 - 1) + 5 \cdot 0.8\]
  2. Naive Monte Carlo: Estimate \(I\) using 1000 uniform samples. Report estimate and SE.

  3. Proportional allocation: Stratify into \(S_1 = [0, 0.2)\) and \(S_2 = [0.2, 1]\). Use proportional allocation: \(n_1 = 200\), \(n_2 = 800\). Compare to naive MC.

  4. Neyman allocation: Estimate stratum standard deviations \(\sigma_1, \sigma_2\) from pilot samples. Compute optimal allocation \(n_k \propto p_k \sigma_k\). With total \(n = 1000\), how should samples be distributed?

    Hint: High-Variance Stratum Gets More

    Stratum 2 has \(\sigma_2 = 0\) (constant function), so all its allocation should go to stratum 1! In practice, \(\sigma_2 \approx 0\) from numerical estimation, leading to \(n_1 \approx n\) and \(n_2 \approx 0\).

  5. Comparison table: Summarize naive MC, proportional, and Neyman allocation in terms of:

    • Estimate

    • Standard error

    • Variance Reduction Factor vs. naive

Solution

Part (a): True Value

Analytical Calculation

\[\begin{split}I &= \int_0^{0.2} e^{10x}\,dx + \int_{0.2}^1 5\,dx \\ &= \frac{1}{10}\left[e^{10x}\right]_0^{0.2} + 5 \cdot 0.8 \\ &= \frac{1}{10}(e^2 - 1) + 4 \\ &= \frac{e^2 - 1}{10} + 4 \\ &\approx 0.6389 + 4 = 4.6389\end{split}\]

Parts (b)–(e): Implementation

import numpy as np

def h(x):
    """Piecewise function: exp(10x) on [0, 0.2), 5 on [0.2, 1]."""
    return np.where(x < 0.2, np.exp(10 * x), 5.0)

# True value
I_true = (np.exp(2) - 1) / 10 + 4
print(f"True value: I = {I_true:.6f}")
print()

np.random.seed(42)
n_total = 1000

# Part (b): Naive Monte Carlo
X_naive = np.random.uniform(0, 1, n_total)
h_naive = h(X_naive)
est_naive = np.mean(h_naive)
se_naive = np.std(h_naive, ddof=1) / np.sqrt(n_total)

print("=" * 65)
print("NAIVE MONTE CARLO")
print("=" * 65)
print(f"Estimate: {est_naive:.6f}")
print(f"SE: {se_naive:.6f}")
print(f"95% CI: ({est_naive - 1.96*se_naive:.4f}, {est_naive + 1.96*se_naive:.4f})")

# Part (c): Proportional Allocation
p1, p2 = 0.2, 0.8
n1_prop = int(p1 * n_total)  # 200
n2_prop = n_total - n1_prop   # 800

X1_prop = np.random.uniform(0, 0.2, n1_prop)
X2_prop = np.random.uniform(0.2, 1, n2_prop)

h1_prop = h(X1_prop)
h2_prop = h(X2_prop)

mu1_prop = np.mean(h1_prop)
mu2_prop = np.mean(h2_prop)

est_prop = p1 * mu1_prop + p2 * mu2_prop

# Variance of stratified estimator
var1 = np.var(h1_prop, ddof=1)
var2 = np.var(h2_prop, ddof=1)
var_prop = (p1**2 * var1 / n1_prop) + (p2**2 * var2 / n2_prop)
se_prop = np.sqrt(var_prop)

print()
print("=" * 65)
print("PROPORTIONAL ALLOCATION (n₁=200, n₂=800)")
print("=" * 65)
print(f"Stratum 1 [0, 0.2): mean = {mu1_prop:.4f}, var = {var1:.4f}")
print(f"Stratum 2 [0.2, 1]: mean = {mu2_prop:.4f}, var = {var2:.4f}")
print(f"Estimate: {est_prop:.6f}")
print(f"SE: {se_prop:.6f}")
print(f"VRF vs naive: {(se_naive/se_prop)**2:.2f}x")

# Part (d): Neyman Allocation
# Estimate stratum std devs from pilot
sigma1_est = np.std(h1_prop, ddof=1)
sigma2_est = np.std(h2_prop, ddof=1)

print()
print("=" * 65)
print("NEYMAN (OPTIMAL) ALLOCATION")
print("=" * 65)
print(f"Estimated σ₁ = {sigma1_est:.4f}")
print(f"Estimated σ₂ = {sigma2_est:.4f}")

# Neyman: n_k ∝ p_k * σ_k
# If σ₂ = 0, all samples go to stratum 1
total_weight = p1 * sigma1_est + p2 * sigma2_est

if sigma2_est < 1e-10:
    n1_neyman = n_total
    n2_neyman = 0
else:
    n1_neyman = int(n_total * (p1 * sigma1_est) / total_weight)
    n2_neyman = n_total - n1_neyman

print(f"Neyman allocation: n₁ = {n1_neyman}, n₂ = {n2_neyman}")

# Resample with Neyman allocation
if n1_neyman > 0:
    X1_neyman = np.random.uniform(0, 0.2, n1_neyman)
    h1_neyman = h(X1_neyman)
    mu1_neyman = np.mean(h1_neyman)
    var1_neyman = np.var(h1_neyman, ddof=1)
else:
    mu1_neyman = (np.exp(2) - 1) / (10 * 0.2)  # Theoretical mean
    var1_neyman = 0

if n2_neyman > 0:
    X2_neyman = np.random.uniform(0.2, 1, n2_neyman)
    h2_neyman = h(X2_neyman)
    mu2_neyman = np.mean(h2_neyman)
    var2_neyman = np.var(h2_neyman, ddof=1)
else:
    mu2_neyman = 5.0  # Known exactly
    var2_neyman = 0

est_neyman = p1 * mu1_neyman + p2 * mu2_neyman

# SE with Neyman (handle n_k = 0 case)
var_neyman = 0
if n1_neyman > 0:
    var_neyman += p1**2 * var1_neyman / n1_neyman
if n2_neyman > 0:
    var_neyman += p2**2 * var2_neyman / n2_neyman

se_neyman = np.sqrt(var_neyman)

print(f"Estimate: {est_neyman:.6f}")
print(f"SE: {se_neyman:.6f}")

# Part (e): Comparison Table
print()
print("=" * 65)
print("COMPARISON SUMMARY")
print("=" * 65)
print()
print(f"{'Method':<25} {'Estimate':>12} {'SE':>12} {'VRF':>12}")
print("-" * 65)
print(f"{'Naive MC':<25} {est_naive:>12.4f} {se_naive:>12.6f} {'1.00':>12}")
print(f"{'Proportional (200/800)':<25} {est_prop:>12.4f} {se_prop:>12.6f} {(se_naive/se_prop)**2:>12.2f}x")
print(f"{'Neyman ({}/{})'.format(n1_neyman, n2_neyman):<25} {est_neyman:>12.4f} {se_neyman:>12.6f} {(se_naive/se_neyman)**2 if se_neyman > 0 else np.inf:>12.2f}x")
print()
print(f"True value: {I_true:.4f}")

# Analysis
print()
print("=" * 65)
print("KEY INSIGHT")
print("=" * 65)
print("""
With σ₂ ≈ 0 (constant function in stratum 2), Neyman allocation
sends ALL samples to stratum 1. Why waste samples on a region
with no uncertainty?

The stratum 2 contribution to the integral is known exactly:
  ∫₀.₂¹ 5 dx = 5 × 0.8 = 4.0

All estimation uncertainty comes from stratum 1, so optimal
allocation concentrates all sampling effort there.
""")
True value: I = 4.638943

=================================================================
NAIVE MONTE CARLO
=================================================================
Estimate: 4.660521
SE: 0.040573
95% CI: (4.5810, 4.7400)

=================================================================
PROPORTIONAL ALLOCATION (n₁=200, n₂=800)
=================================================================
Stratum 1 [0, 0.2): mean = 3.1698, var = 2.8821
Stratum 2 [0.2, 1]: mean = 5.0000, var = 0.0000
Estimate: 4.633957
SE: 0.024029
VRF vs naive: 2.85x

=================================================================
NEYMAN (OPTIMAL) ALLOCATION
=================================================================
Estimated σ₁ = 1.6977
Estimated σ₂ = 0.0000
Neyman allocation: n₁ = 1000, n₂ = 0
Estimate: 4.638684
SE: 0.010768
VRF vs naive: 14.20x

=================================================================
COMPARISON SUMMARY
=================================================================

Method                      Estimate           SE          VRF
-----------------------------------------------------------------
Naive MC                      4.6605     0.040573         1.00
Proportional (200/800)        4.6340     0.024029         2.85x
Neyman (1000/0)               4.6387     0.010768        14.20x

True value: 4.6389

Key Insights:

  1. Neyman crushes proportional: With 14× VRF vs. naive and 5× vs. proportional, optimal allocation makes a huge difference.

  2. Zero-variance stratum: When one stratum has known value (σ = 0), don’t sample there at all—use all samples in the uncertain region.

  3. Real-world application: This principle applies to adaptive sampling in simulations—concentrate effort where uncertainty is highest.

Exercise 6: Combining Methods for Maximum Efficiency

The variance reduction methods are not mutually exclusive. This exercise combines importance sampling with control variates for synergistic improvement.

Background: Why Combine?

Different methods attack different sources of variance. Importance sampling addresses poor coverage of important regions; control variates exploit auxiliary information. Used together, they can achieve variance reductions that neither achieves alone.

Setup: Estimate \(I = \mathbb{E}[e^{-X} \cdot \mathbf{1}_{X > 2}]\) where \(X \sim \mathcal{N}(0, 1)\). This is an expectation involving both a rare event (\(X > 2\) has probability ~2.3%) and a smooth transformation.

  1. Decomposition: Write \(I = P(X > 2) \cdot \mathbb{E}[e^{-X} | X > 2]\). Compute the first factor exactly.

  2. Naive MC: Estimate \(I\) with \(n = 10{,}000\) samples. What fraction of samples contribute (i.e., have \(X > 2\))?

  3. Importance sampling alone: Use proposal \(g = \mathcal{N}(2, 1)\) (shifted to the rare event region). Compute the IS estimate and VRF vs. naive.

    Hint: Weight Function

    For target \(f = \mathcal{N}(0, 1)\) and proposal \(g = \mathcal{N}(2, 1)\):

    \[w(x) = \frac{\phi(x)}{\phi(x-2)} = \exp\left(-\frac{x^2}{2} + \frac{(x-2)^2}{2}\right) = \exp(2 - 2x)\]
  4. Control variate within IS: Under the shifted proposal, the control variate \(C = X\) has known expectation \(\mathbb{E}_g[X] = 2\). Use \(C\) to reduce variance further:

    \[\hat{I}_{\text{IS+CV}} = \frac{1}{n} \sum_{i=1}^n w(X_i) h(X_i) - \beta \left(\frac{1}{n}\sum_{i=1}^n w(X_i) X_i - 2\right)\]

    Wait—this isn’t quite right. The control variate in IS context is trickier. Instead, use the weighted control variate approach: regress \(w(X)h(X)\) on \(w(X)X - \mathbb{E}_g[w(X)X]\) and adjust.

    Hint: Weighted Control Variate

    Under self-normalized IS, we can use control variates on the weighted samples. Define:

    • \(Y_i = w_i h(X_i)\) (weighted integrand values)

    • \(C_i = w_i X_i\) (weighted control)

    The control variate adjustment is \(\hat{I}_{\text{CV}} = \bar{Y} - \beta(\bar{C} - \mu_C)\) where \(\mu_C = \mathbb{E}_g[w(X)X] = \mathbb{E}_f[X] = 0\).

  5. Final comparison: Report estimates and SEs for:

    • Naive MC

    • IS alone

    • IS + CV

    What is the total VRF from combining methods?

Solution

Part (a): Decomposition

Analysis

\[I = \mathbb{E}[e^{-X} \cdot \mathbf{1}_{X > 2}] = P(X > 2) \cdot \mathbb{E}[e^{-X} | X > 2]\]

The first factor: \(P(X > 2) = 1 - \Phi(2) \approx 0.0228\) (2.28%).

Parts (b)–(e): Implementation

import numpy as np
from scipy import stats

# Setup
threshold = 2
n = 10_000
np.random.seed(42)

def h(x):
    """Integrand: exp(-x) * I(x > 2)"""
    return np.exp(-x) * (x > threshold)

# Part (a): Exact probability
p_rare = 1 - stats.norm.cdf(threshold)
print("COMBINING IMPORTANCE SAMPLING AND CONTROL VARIATES")
print("=" * 70)
print(f"Target: E[exp(-X) · I(X > 2)] where X ~ N(0, 1)")
print(f"P(X > 2) = {p_rare:.4f} = {100*p_rare:.2f}%")
print()

# Compute true value via numerical integration
from scipy.integrate import quad
integrand = lambda x: np.exp(-x) * stats.norm.pdf(x)
I_true, _ = quad(integrand, threshold, np.inf)
print(f"True value (numerical): I = {I_true:.6f}")
print()

# Part (b): Naive MC
X_naive = np.random.standard_normal(n)
h_naive = h(X_naive)
n_contribute = np.sum(X_naive > threshold)

est_naive = np.mean(h_naive)
se_naive = np.std(h_naive, ddof=1) / np.sqrt(n)

print("=" * 70)
print("NAIVE MONTE CARLO")
print("=" * 70)
print(f"Samples contributing (X > 2): {n_contribute} / {n} = {100*n_contribute/n:.1f}%")
print(f"Estimate: {est_naive:.6f}")
print(f"True value: {I_true:.6f}")
print(f"SE: {se_naive:.6f}")
print(f"CV (SE/estimate): {se_naive/est_naive:.2%}")

# Part (c): Importance Sampling with N(2, 1)
mu_g = 2
X_is = np.random.normal(mu_g, 1, n)

# Weights: w(x) = f(x)/g(x) = exp(2 - 2x)
log_weights = 2 - 2 * X_is
weights = np.exp(log_weights)

# Weighted samples
h_is = h(X_is)
weighted_h = weights * h_is

est_is = np.mean(weighted_h)
se_is = np.std(weighted_h, ddof=1) / np.sqrt(n)

vrf_is = (se_naive / se_is)**2

print()
print("=" * 70)
print("IMPORTANCE SAMPLING (proposal: N(2, 1))")
print("=" * 70)
print(f"Samples in rare region: {np.sum(X_is > threshold)} / {n} = {100*np.sum(X_is > threshold)/n:.1f}%")
print(f"Estimate: {est_is:.6f}")
print(f"SE: {se_is:.6f}")
print(f"VRF vs naive: {vrf_is:.2f}x")

# Part (d): IS + Control Variate
# Control: C = w(X) * X, with E_g[w(X) * X] = E_f[X] = 0
weighted_X = weights * X_is
mu_C = 0  # E_f[X] = 0

# Optimal beta via regression of weighted_h on weighted_X
cov_hC = np.cov(weighted_h, weighted_X)[0, 1]
var_C = np.var(weighted_X, ddof=1)
beta_opt = cov_hC / var_C

# Control variate adjustment
adjusted = weighted_h - beta_opt * (weighted_X - mu_C)
est_is_cv = np.mean(adjusted)
se_is_cv = np.std(adjusted, ddof=1) / np.sqrt(n)

# Correlation
rho = np.corrcoef(weighted_h, weighted_X)[0, 1]

vrf_is_cv_vs_naive = (se_naive / se_is_cv)**2
vrf_is_cv_vs_is = (se_is / se_is_cv)**2

print()
print("=" * 70)
print("IMPORTANCE SAMPLING + CONTROL VARIATE")
print("=" * 70)
print(f"Control: C = w(X)·X, E[C] = 0")
print(f"Correlation ρ(w·h, w·X): {rho:.4f}")
print(f"Optimal β: {beta_opt:.4f}")
print(f"Estimate: {est_is_cv:.6f}")
print(f"SE: {se_is_cv:.6f}")
print(f"VRF vs IS alone: {vrf_is_cv_vs_is:.2f}x")
print(f"VRF vs naive: {vrf_is_cv_vs_naive:.2f}x")

# Part (e): Final Comparison
print()
print("=" * 70)
print("FINAL COMPARISON")
print("=" * 70)
print()
print(f"{'Method':<25} {'Estimate':>12} {'SE':>12} {'VRF vs Naive':>15}")
print("-" * 70)
print(f"{'Naive MC':<25} {est_naive:>12.6f} {se_naive:>12.6f} {'1.00':>15}")
print(f"{'IS alone':<25} {est_is:>12.6f} {se_is:>12.6f} {vrf_is:>15.2f}x")
print(f"{'IS + CV':<25} {est_is_cv:>12.6f} {se_is_cv:>12.6f} {vrf_is_cv_vs_naive:>15.2f}x")
print()
print(f"True value: {I_true:.6f}")
print()
print("Synergy breakdown:")
print(f"  IS contribution: {vrf_is:.1f}x")
print(f"  CV additional: {vrf_is_cv_vs_is:.1f}x")
print(f"  Combined: {vrf_is:.1f} × {vrf_is_cv_vs_is:.1f}{vrf_is_cv_vs_naive:.1f}x")
COMBINING IMPORTANCE SAMPLING AND CONTROL VARIATES
======================================================================
Target: E[exp(-X) · I(X > 2)] where X ~ N(0, 1)
P(X > 2) = 0.0228 = 2.28%

True value (numerical): I = 0.003034

======================================================================
NAIVE MONTE CARLO
======================================================================
Samples contributing (X > 2): 226 / 10000 = 2.3%
Estimate: 0.002986
True value: 0.003034
SE: 0.000393
CV (SE/estimate): 13.17%

======================================================================
IMPORTANCE SAMPLING (proposal: N(2, 1))
======================================================================
Samples in rare region: 5028 / 10000 = 50.3%
Estimate: 0.003045
SE: 0.000046
VRF vs naive: 72.49x

======================================================================
IMPORTANCE SAMPLING + CONTROL VARIATE
======================================================================
Control: C = w(X)·X, E[C] = 0
Correlation ρ(w·h, w·X): 0.7821
Optimal β: 0.0012
Estimate: 0.003036
SE: 0.000029
VRF vs IS alone: 2.56x
VRF vs naive: 185.55x

======================================================================
FINAL COMPARISON
======================================================================

Method                      Estimate           SE     VRF vs Naive
----------------------------------------------------------------------
Naive MC                    0.002986     0.000393            1.00
IS alone                    0.003045     0.000046           72.49x
IS + CV                     0.003036     0.000029          185.55x

True value: 0.003034

Synergy breakdown:
  IS contribution: 72.5x
  CV additional: 2.6x
  Combined: 72.5 × 2.6 ≈ 185.6x

Key Insights:

  1. Multiplicative gains: IS gives 72× and CV adds another 2.6×, for a total of 186× variance reduction!

  2. Different targets: IS addresses the rare-event problem (sampling where the integrand matters), while CV exploits correlation structure.

  3. Synergy is real: The combined VRF (186×) exceeds either method alone. Methods attack different variance sources.

  4. Practical importance: For problems with multiple challenges (rare events + complex integrands), combining methods is often essential.

  5. Implementation subtlety: The control variate must be applied to the weighted samples, not the raw samples—this preserves unbiasedness within the IS framework.

References

[Kahn1951]

Kahn, H. and Harris, T. E. (1951). Estimation of particle transmission by random sampling. National Bureau of Standards Applied Mathematics Series, 12, 27–30. Foundational paper on importance sampling from the RAND Corporation.

[Neyman1934]

Neyman, J. (1934). On the two different aspects of the representative method: The method of stratified sampling and the method of purposive selection. Journal of the Royal Statistical Society, 97(4), 558–625. Establishes optimal allocation theory for stratified sampling.

[HammersleyMorton1956]

Hammersley, J. M. and Morton, K. W. (1956). A new Monte Carlo technique: antithetic variates. Mathematical Proceedings of the Cambridge Philosophical Society, 52(3), 449–475. Introduces antithetic variates.

[HammersleyHandscomb1964]

Hammersley, J. M. and Handscomb, D. C. (1964). Monte Carlo Methods. Methuen, London. Classic monograph systematizing variance reduction techniques including control variates.

[McKayEtAl1979]

McKay, M. D., Beckman, R. J., and Conover, W. J. (1979). A comparison of three methods for selecting values of input variables in the analysis of output from a computer code. Technometrics, 21(2), 239–245. Introduces Latin Hypercube Sampling.

[Kong1992]

Kong, A. (1992). A note on importance sampling using standardized weights. Technical Report 348, Department of Statistics, University of Chicago. Establishes ESS interpretation for importance sampling.

[Owen1997]

Owen, A. B. (1997). Monte Carlo variance of scrambled net quadrature. SIAM Journal on Numerical Analysis, 34(5), 1884–1910. Theoretical analysis of Latin Hypercube Sampling.

[BengtssonEtAl2008]

Bengtsson, T., Bickel, P., and Li, B. (2008). Curse-of-dimensionality revisited: Collapse of the particle filter in very large scale systems. In Probability and Statistics: Essays in Honor of David A. Freedman, 316–334. Institute of Mathematical Statistics. Proves exponential sample size requirements for importance sampling in high dimensions.

[RobertCasella2004]

Robert, C. P. and Casella, G. (2004). Monte Carlo Statistical Methods (2nd ed.). Springer-Verlag, New York. Comprehensive treatment of variance reduction in the Monte Carlo context.

[Glasserman2004]

Glasserman, P. (2004). Monte Carlo Methods in Financial Engineering. Springer-Verlag, New York. Variance reduction applications in financial computing.