Skip to content

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 nominal.

radius float | Tensor

Nonnegative bound on the divergence of any distribution inside the ambiguity set from nominal, either a Python float or a tensor of radii broadcastable against the batch shape of worst_case_expectation's loss.

__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 nominal.

required
radius float | Tensor

Nonnegative bound on the divergence of any distribution inside the ambiguity set from nominal. A tensor radius is registered as a buffer and broadcast against the batch shape of worst_case_expectation's loss, which evaluates a whole sweep of radii in one call; its entries are not checked for nonnegativity, since reading them on the host would block on the device (the same opt-in policy the divergences apply to their tensor arguments), and a tensor radius never takes the zero-radius shortcut.

required
validate bool

Whether to check that nominal is nonnegative and sums to one along its last dimension. The check reads two reductions over nominal on the host, which blocks until the device has produced them, so it is opt-in and off by default to keep construction asynchronous. The shape and hyperparameter checks are metadata-only and always run.

False

Raises:

Type Description
ValueError

If nominal is a scalar tensor, if radius is a negative float, or if validate is set and nominal is not a valid probability distribution.

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 nominal.

required

Returns:

Type Description
Tensor

A boolean tensor that is True where the divergence of

Tensor

candidate from nominal does not exceed radius.

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 (..., n), one trailing entry per element of nominal's support. Leading dimensions are a batch of independent loss vectors, solved in one call.

required

Returns:

Type Description
Tensor

A tensor of shape (...) holding, for each batch element, the

Tensor

worst-case expected loss attainable by any distribution inside

Tensor

the ambiguity set. An unbatched (n,) loss gives a scalar.

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 nominal.

radius float | Tensor

Nonnegative bound on the divergence of any distribution inside the ambiguity set from nominal.

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 .to(device) with the rest of the module. It is expanded over the batch shape of the loss being evaluated.

__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 nominal.

required
radius float | Tensor

Nonnegative bound on the divergence of any distribution inside the ambiguity set from nominal.

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 nominal is a valid probability distribution, off by default because the check synchronizes with the device.

False

Raises:

Type Description
ValueError

If nominal is a scalar tensor, if radius is a negative float, or if validate is set and nominal is not a valid probability distribution.

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 nominal.

required

Returns:

Type Description
Tensor

A boolean tensor that is True where the divergence of

Tensor

candidate from nominal does not exceed radius.

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 (..., n), one trailing entry per element of nominal's support. Leading dimensions are a batch of independent loss vectors, solved in one call.

required

Returns:

Type Description
Tensor

A tensor of shape (...) holding, for each batch element, the

Tensor

worst-case expected loss attainable by any distribution inside

Tensor

the ambiguity set. An unbatched (n,) loss gives a scalar.