optora.dro.phi_dro¶
Phi-divergence-constrained ambiguity sets (phi-DRO).
PhiAmbiguitySet
¶
Bases: DualAmbiguitySet
Phi-divergence-constrained ambiguity set solved via its convex dual.
Bounds every candidate distribution q by
\(D_\phi(q \,\|\, \mathrm{nominal}) \le \mathrm{radius}\) for a general
convex generator \(\phi\) (see
optora.divergences.f_divergence.PhiDivergence). The worst-case expected
loss over this set admits a convex dual (Ben-Tal et al. 2013; Duchi,
Glynn, and Namkoong 2021; Duchi and Namkoong 2021):
where \(\phi^*\) is the convex (Legendre-Fenchel) conjugate of \(\phi\)
restricted to its effective domain \(t \ge 0\). This generalizes the
KLAmbiguitySet dual to an arbitrary phi-divergence at the cost of a
second dual variable \(\lambda\); setting \(\phi(t) = t \log t - t + 1\)
(whose conjugate is \(\phi^*(s) = \exp(s) - 1\)) recovers the KL-DRO dual
exactly. dual_solver minimizes this joint objective over
\((\log(\eta), \lambda)\) rather than \((\eta, \lambda)\) directly, so the
unconstrained GradientDescent solver keeps \(\eta\) strictly positive
throughout the iteration.
This base class assumes phi_conjugate is finite everywhere on the real
line (true for, for example, the chi-square generator's conjugate used
by ChiSquareAmbiguitySet). Phi-divergences whose conjugate has a hard
finite feasibility boundary (for example total variation, whose
conjugate is +inf past a threshold) are not solved robustly by this
unconstrained joint dual, since gradient descent can step past the
boundary into a region of infinite objective value; TotalVariationAmbiguitySet
instead computes its worst-case expectation from a dedicated closed
form.
Attributes:
| Name | Type | Description |
|---|---|---|
nominal |
Tensor
|
Reference distribution the ambiguity set is centered on. |
divergence |
|
|
radius |
float | Tensor
|
Nonnegative bound on the phi-divergence of any distribution
inside the ambiguity set from |
phi_conjugate |
Convex (Legendre-Fenchel) conjugate of
|
|
dual_solver |
Solver minimizing the dual objective over
|
|
initial_dual_point |
Tensor
|
Values of |
__init__(nominal, divergence, radius, phi_conjugate, dual_solver=None, initial_log_eta=0.0, initial_lam=0.0, validate=False)
¶
Initialize the phi-divergence 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 |
divergence
|
PhiDivergence
|
|
required |
radius
|
float | Tensor
|
Nonnegative bound on the phi-divergence of any
distribution inside the ambiguity set from |
required |
phi_conjugate
|
Callable[[Tensor], Tensor]
|
Convex conjugate of |
required |
dual_solver
|
Solver[MinimizationProblem, MinimizationResult] | None
|
Solver minimizing the dual objective over
|
None
|
initial_log_eta
|
float
|
Value of |
0.0
|
initial_lam
|
float
|
Value of |
0.0
|
validate
|
bool
|
Whether to check that |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
worst_case_expectation(loss)
¶
Compute the worst-case expected loss over the phi-divergence 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
|
convex dual objective evaluated at the |
Tensor
|
by |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
RuntimeError
|
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
|
|
reset_warm_start()
¶
Discard the cached dual optimum so the next solve starts cold.
After this call the next worst_case_expectation starts from
initial_dual_point again, as the first one did.
ChiSquareAmbiguitySet
¶
Bases: PhiAmbiguitySet
Chi-square-divergence-constrained ambiguity set for chi-square-DRO.
Fixes divergence to a ChiSquareDivergence and phi_conjugate to the
closed-form conjugate of \(\phi(t) = (t-1)^2\), which is finite and
continuously differentiable everywhere on the real line (see
_chi_square_conjugate), making the joint PhiAmbiguitySet dual solve
over \((\log(\eta), \lambda)\) numerically well-behaved.
In the interior regime where no candidate distribution is pushed to the boundary \(q_i = 0\), the dual optimum over \(\lambda\) reduces to \(\lambda = \mathbb{E}_{\mathrm{nominal}}[\mathrm{loss}]\), and the dual optimum over \(\eta\) reduces to \(\eta = \sqrt{\mathrm{Var}_{\mathrm{nominal}}(\mathrm{loss}) / (4\,\mathrm{radius})}\), giving the well-known closed form (Duchi and Namkoong 2021):
worst_case_expectation still solves the general dual rather than this
closed form directly, since the closed form only holds away from the
boundary regime.
__init__(nominal, radius, eps=1e-12, dual_solver=None, initial_log_eta=0.0, initial_lam=0.0, validate=False)
¶
Initialize the chi-square 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 |
radius
|
float | Tensor
|
Nonnegative bound on the chi-square divergence of any
distribution inside the ambiguity set from |
required |
eps
|
float
|
Small positive constant used to clamp |
1e-12
|
dual_solver
|
Solver[MinimizationProblem, MinimizationResult] | None
|
Solver minimizing the dual objective over
|
None
|
initial_log_eta
|
float
|
Value of |
0.0
|
initial_lam
|
float
|
Value of |
0.0
|
validate
|
bool
|
Whether to check that |
False
|
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
|
|
worst_case_expectation(loss)
¶
Compute the worst-case expected loss over the phi-divergence 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
|
convex dual objective evaluated at the |
Tensor
|
by |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
RuntimeError
|
If |
reset_warm_start()
¶
Discard the cached dual optimum so the next solve starts cold.
After this call the next worst_case_expectation starts from
initial_dual_point again, as the first one did.
TotalVariationAmbiguitySet
¶
Bases: AmbiguitySet
Total-variation-constrained ambiguity set for total-variation-DRO.
Bounds every candidate distribution q by
Unlike
PhiAmbiguitySet, worst_case_expectation is computed from a direct
closed form rather than the general convex dual, because total
variation's conjugate has a hard finite feasibility boundary that is
not well suited to unconstrained gradient-based dual optimization (see
PhiAmbiguitySet).
The worst-case expectation is instead the value of a linear program over
the simplex intersected with the total-variation ball, whose optimal
solution has a simple combinatorial structure (Ben-Tal et al. 2013):
starting from nominal, reallocate mass, in ascending order of loss,
from the lowest-loss scenarios to the single highest-loss scenario,
until the reallocated mass reaches radius (or every scenario but the
highest-loss one has been fully drained, whichever happens first). This
is implemented as a sort followed by a cumulative-sum sweep rather than
an iterative solve, so it is both exact and free of solver tuning.
Attributes:
| Name | Type | Description |
|---|---|---|
nominal |
Tensor
|
Reference distribution the ambiguity set is centered on. |
divergence |
|
|
radius |
float | Tensor
|
Nonnegative bound on the total variation distance of any
distribution inside the ambiguity set from |
__init__(nominal, radius, eps=1e-12, validate=False)
¶
Initialize the total variation 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 |
radius
|
float | Tensor
|
Nonnegative bound on the total variation distance of any
distribution inside the ambiguity set from |
required |
eps
|
float
|
Small positive constant used to clamp the reference
distribution away from zero before dividing, passed through
to the underlying |
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 total variation 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
|
closed-form value of the mass-reallocation linear program |
Tensor
|
described in the class docstring. |
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
|
|