optora.dro.wasserstein_dro¶
Wasserstein-distance-constrained ambiguity set (Wasserstein-DRO).
WassersteinAmbiguitySet
¶
Bases: AmbiguitySet
Wasserstein-distance-constrained ambiguity set for Wasserstein-DRO.
Bounds every candidate distribution q, sharing nominal's finite
support with pairwise ground cost cost, by the type-1 Wasserstein
distance \(W_c(q, \mathrm{nominal}) \le \mathrm{radius}\), where \(W_c\) is
the optimal-transport cost of moving nominal to q under cost. The
worst-case expected loss over this set admits an exact strong-duality
reformulation (Mohajerin Esfahani and Kuhn 2018; Blanchet and Murthy
2019; Gao and Kleywegt 2022), specialized to a finite shared support:
This follows from LP duality on the transportation polytope: the primal is
(the column marginal, which defines the candidate q, is left free).
Dualizing the budget constraint with multiplier \(\gamma \ge 0\) and the
row constraints with free multipliers, and maximizing out \(\pi\)
pointwise, yields exactly the one-dimensional convex dual above.
Unlike optora.dro.KLAmbiguitySet and optora.dro.PhiAmbiguitySet,
this dual is not handed to an injected first-order solver. It is a
nonnegative combination of maxima of affine functions of the scalar
\(\gamma\), hence convex piecewise linear: its derivative is
piecewise constant and jumps across the minimizer, so a fixed-step
gradient method oscillates around the kink and never satisfies a
gradient-norm stopping test. What the derivative does do is increase
monotonically,
where \(j^\star(i, \gamma)\) is the maximizing column of row \(i\), so the minimizer is located by bisecting that derivative's sign change. The search starts from the exact bracket \([0, \Gamma]\), where
with \(j_0(i)\) the highest-loss column attaining \(m_i\): beyond \(\Gamma\)
every row is maximized at a cheapest column, the derivative is frozen
at \(\mathrm{radius} - \sum_i \mathrm{nominal}_i m_i \ge 0\), and the
minimizer cannot lie further right. Bisection therefore reaches the
dual optimum to working precision in a fixed, dtype-determined number
of steps, with no step size, iteration budget, or tolerance to tune --
the same treatment optora.dro.TotalVariationAmbiguitySet gets for its
own exactly solvable inner problem. It also removes the need to
reparameterize \(\gamma\): the constraint \(\gamma \ge 0\) is the left end
of the bracket, and the optimum is genuinely attained at \(\gamma = 0\)
once radius is large enough to move all nominal mass onto the single
highest-loss support point.
The bracket assumes the ambiguity set is nonempty, that is \(\mathrm{radius} \ge \sum_i \mathrm{nominal}_i m_i\), the smallest transport cost at which any candidate distribution can be reached. That holds automatically for a ground cost with a zero diagonal, the standard choice, for which \(m_i = 0\).
Because \(\gamma^\star\) is a kink, the worst-case expectation is then
read off the primal solution rather than the dual value. At a kink
several columns tie for a row's inner maximum, and re-evaluating the
dual objective there would differentiate through that tie with
arbitrary weights, giving a transport plan that does not spend the
radius exactly and therefore a wrong gradient with respect to loss.
The two ends of the final bracket instead name the two extreme
tie-breaks explicitly: the plan that is still worth paying for just
below \(\gamma^\star\) overspends the radius, the one that takes over
just above it underspends, and the unique convex combination of the two
that spends exactly radius is the worst-case distribution
\(q^\star\). Complementary slackness then makes
\(\mathbb{E}_{q^\star}[\mathrm{loss}]\) equal to the dual minimum, and
since \(q^\star\) carries no autograd graph, differentiating that
expectation gives exactly the envelope-theorem gradient with respect to
loss. A minimizer at \(\gamma^\star = 0\) is the degenerate case where
both ends agree and \(q^\star\) simply moves all mass to the highest-loss
support point.
divergence is fixed to a SinkhornDivergence over cost (the only
Wasserstein-type divergence implemented in optora.divergences), so
contains(...) checks the entropy-regularized Sinkhorn divergence as a
fast, differentiable approximation of exact Wasserstein-ball
membership. worst_case_expectation does not depend on this
approximation: it solves the exact (non-regularized) dual above
directly from cost, since that dual has a clean closed form and does
not need entropic relaxation for tractability.
Attributes:
| Name | Type | Description |
|---|---|---|
nominal |
Tensor
|
Reference distribution the ambiguity set is centered on. |
divergence |
|
|
radius |
float | Tensor
|
Nonnegative bound on the Wasserstein distance of any
distribution inside the ambiguity set from |
cost |
Tensor
|
Square, nonnegative pairwise ground cost matrix between the
shared support points of |
cost
property
¶
Ground cost matrix, aliasing divergence.cost (no separate buffer).
__init__(nominal, cost, radius, epsilon=0.1, sinkhorn_max_iter=100, sinkhorn_tol=1e-06, eps=1e-12, validate=False)
¶
Initialize the Wasserstein-DRO ambiguity set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nominal
|
Tensor
|
Reference distribution the ambiguity set is centered on, a nonnegative tensor that sums to one along its last dimension. |
required |
cost
|
Tensor
|
Square, nonnegative pairwise ground cost matrix between
the shared support points of |
required |
radius
|
float | Tensor
|
Nonnegative bound on the Wasserstein distance of any
distribution inside the ambiguity set from |
required |
epsilon
|
float
|
Positive entropic regularization strength for the
|
0.1
|
sinkhorn_max_iter
|
int
|
Maximum number of Sinkhorn scaling
iterations, passed through to |
100
|
sinkhorn_tol
|
float
|
Sinkhorn scaling convergence tolerance, passed
through to |
1e-06
|
eps
|
float
|
Small positive constant used to clamp Sinkhorn scaling
denominators away from zero, passed through to
|
1e-12
|
validate
|
bool
|
Whether to check that |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
worst_case_expectation(loss)
¶
Compute the worst-case expected loss over the Wasserstein ambiguity set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
loss
|
Tensor
|
Per-scenario loss values of shape |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A tensor of shape |
Tensor
|
the exact |
Tensor
|
ambiguity set then contains only |
Tensor
|
expectation of |
Tensor
|
recovered from the dual optimum. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
contains(candidate)
¶
Check whether a candidate distribution lies inside the ambiguity set.
The answer is returned as a boolean tensor on candidate's device
rather than as a Python bool, so membership can be used as a mask
or composed with further tensor work without forcing a
device-to-host synchronization. Call bool(...) on the result only
where a host-side branch is genuinely needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
candidate
|
Tensor
|
Candidate distribution with the same shape as
|
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A boolean tensor that is |
Tensor
|
|