Skip to content

Examples

Reading a dual formula and believing it are two different things. Every script in this section exists because, at some point while building Optora, I wanted to see a formulation behave before trusting it: does the worst-case expectation really saturate at \(\max_i \mathrm{loss}_i\), does the adversary really move mass the way the derivation claims, does the inner dual solve really land where a brute-force grid search says it should?

So the scripts under examples/ are less "look how easy the API is" and more small numerical experiments. Each one runs end to end, prints the numbers it is checking, and draws a figure.

Running them

The scripts plot, and matplotlib is deliberately not a runtime dependency of optora, so it lives in its own extra:

pip install -e ".[examples]"

Then run whichever one you want from the repository root:

python examples/01_kl_dro_radius_sweep.py

Where the figures go

Every figure is written to examples/outputs/, which is git-ignored. Nothing binary — no .png, no .gif — is ever committed to this repository; a plot that is worth publishing gets hosted externally and linked by URL. The reasoning is in examples/README.md.

The experiments

Example Question it answers
KL-DRO radius sweep How does the worst-case expectation grow with the ambiguity radius, and does it respect both closed-form limits?
Worst-case distribution shift Where does the adversary actually put the probability mass?
Robust decisions across geometries How much does the choice of ambiguity set change the decision you end up making?
Convergence diagnostics Are the outer and the inner solve both genuinely converged, or only plausibly so?
Wasserstein-DRO and Lipschitz regularization In what sense, exactly, is Wasserstein-DRO "the same as" a Lipschitz penalty?
Calibrating the ambiguity radius What radius should you actually use, and does the finite-sample theory that answers it hold up at the sample size you have?

They are ordered roughly by how much machinery they involve: the first one touches a single AmbiguitySet, the fourth pulls a solver apart to inspect its trajectory, the fifth turns the dual formula itself into the object under test, and the last one stops treating the radius as a free parameter at all.

These scripts are tuned down on purpose

Examples 3 and 4 nest an outer gradient descent around an ambiguity set's own inner dual solve, and in eager PyTorch that composition multiplies iteration counts unpleasantly fast. The budgets in those scripts are much smaller than each solver's defaults. They are chosen to show qualitative behaviour — monotonicity, limits, ordering — not to hit tight tolerances. Example 6 is the slowest of the set for a different reason: estimating a coverage probability needs thousands of independent dual solves, not deeper ones.

The shared plotting helper

There is one non-example file in the folder. It only exists so that no script has to think about where its figure goes:

examples/_plotting.py
import os

from matplotlib.figure import Figure


def save_figure(fig: Figure, name: str) -> str:
    """Save `fig` as a PNG under `examples/outputs/` and return its path."""
    output_dir = os.path.join(os.path.dirname(__file__), "outputs")
    os.makedirs(output_dir, exist_ok=True)
    path = os.path.join(output_dir, f"{name}.png")
    fig.savefig(path, dpi=150, bbox_inches="tight")
    print(f"saved figure -> {path}")
    return path