optora.core.dro_base¶
Shared contract for ambiguity sets used by DRO formulations.
AmbiguitySet
¶
Bases: Module, ABC
Set of distributions within a bounded divergence of a nominal distribution.
An ambiguity set pairs a Divergence with a radius: every distribution
q inside the set satisfies divergence(q, nominal) <= radius. Modules
in optora.dro subclass AmbiguitySet to implement the inner
maximization of the DRO minimax problem for a specific divergence
geometry (for example KL, a general phi-divergence, or Wasserstein).
Inherits from torch.nn.Module (rather than a plain ABC) so nominal
is registered as a buffer and divergence as a submodule: a single
.to(device)/.cuda() call then moves the nominal distribution and any
tensor state the divergence holds (for example SinkhornDivergence's
ground-cost matrix) together, and both surface through state_dict().
Attributes:
| Name | Type | Description |
|---|---|---|
nominal |
Tensor
|
Reference distribution the ambiguity set is centered on, a nonnegative tensor that sums to one along its last dimension. |
divergence |
Divergence used to measure distance from |
|
radius |
float | Tensor
|
Nonnegative bound on the divergence of any distribution
inside the ambiguity set from |
__init__(nominal, divergence, radius, validate=False)
¶
Initialize the ambiguity set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nominal
|
Tensor
|
Reference distribution the ambiguity set is centered on. |
required |
divergence
|
Divergence
|
Divergence used to measure distance from |
required |
radius
|
float | Tensor
|
Nonnegative bound on the divergence of any distribution
inside the ambiguity set from |
required |
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)
abstractmethod
¶
Compute the worst-case expected loss over the 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
|
worst-case expected loss attainable by any distribution inside |
Tensor
|
the ambiguity set. An unbatched |
DualAmbiguitySet
¶
Bases: AmbiguitySet
Ambiguity set whose worst-case expectation is a low-dimensional dual solve.
Every divergence-based ambiguity set that reformulates its inner
supremum as a convex dual minimization over a handful of dual variables
(optora.dro.KLAmbiguitySet over \(\log(\eta)\),
optora.dro.PhiAmbiguitySet over \((\log(\eta), \lambda)\)) runs the
same machinery around a formulation-specific dual objective, so that
machinery lives here: hold an injected dual_solver, start it from a
dual point, and re-evaluate the dual objective at the returned optimum
so the result stays differentiable with respect to loss by the
envelope theorem.
Repeated calls are warm-started. The dual optimum moves only slightly
between consecutive worst_case_expectation calls on a slowly changing
loss — exactly what an optora.dro.MinimaxSolver outer iteration
produces — so _solve_dual caches the detached optimum of each solve
and starts the next solve from it instead of from initial_dual_point.
That makes the second and later solves of such a sequence converge in
far fewer inner iterations at the same optimum, since each dual is
convex and its solution does not depend on where the iteration started.
Call reset_warm_start() to discard the cache and go back to
initial_dual_point, for example before evaluating an unrelated loss or
after a diverged solve.
A batched loss of shape (..., n) is solved with one set of dual
variables per batch element, since the duals decouple across batch
elements. _solve_dual therefore hands dual_solver the sum of the
per-element dual objectives: the sum's gradient with respect to any one
element's dual variables is that element's own gradient, so a single
joint solve reproduces independent per-element solves exactly.
Convergence is judged on the joint gradient norm over the whole batch,
so every element keeps iterating until the batch as a whole is
stationary (see progress/decisions.md).
Attributes:
| Name | Type | Description |
|---|---|---|
nominal |
Tensor
|
Reference distribution the ambiguity set is centered on. |
divergence |
Divergence used to measure distance from |
|
radius |
float | Tensor
|
Nonnegative bound on the divergence of any distribution
inside the ambiguity set from |
dual_solver |
Solver minimizing the dual objective. Required to evaluate a positive-radius set. |
|
initial_dual_point |
Tensor
|
Dual point of a single batch element that the
first solve starts from, registered as a buffer so it is built
once and follows |
__init__(nominal, divergence, radius, dual_solver, initial_dual_point, validate=False)
¶
Initialize the dual-solved ambiguity set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nominal
|
Tensor
|
Reference distribution the ambiguity set is centered on. |
required |
divergence
|
Divergence
|
Divergence used to measure distance from |
required |
radius
|
float | Tensor
|
Nonnegative bound on the divergence of any distribution
inside the ambiguity set from |
required |
dual_solver
|
Solver[MinimizationProblem, MinimizationResult] | None
|
Solver minimizing the dual objective. Required to evaluate a positive-radius set. |
required |
initial_dual_point
|
Tensor
|
Dual point of a single batch element that the first solve starts from. |
required |
validate
|
bool
|
Whether to check that |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
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.
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)
abstractmethod
¶
Compute the worst-case expected loss over the 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
|
worst-case expected loss attainable by any distribution inside |
Tensor
|
the ambiguity set. An unbatched |