Skip to content

[WIP] entropic_partial_wasserstein_logscale: stable log-domain solver (rescue of #724)#811

Open
hinanohart wants to merge 1 commit into
PythonOT:masterfrom
hinanohart:rescue-pr-724
Open

[WIP] entropic_partial_wasserstein_logscale: stable log-domain solver (rescue of #724)#811
hinanohart wants to merge 1 commit into
PythonOT:masterfrom
hinanohart:rescue-pr-724

Conversation

@hinanohart
Copy link
Copy Markdown

Context

This is a rescue follow-up to PR #724 (and issue #723), both opened by @wzm2256 in March 2025. The original PR adds a numerically-stable entropic_partial_wasserstein_logscale to fix the NaN regime that #723 documents, but has been stuck CONFLICTING since 2025-09 — the author wrote in the PR body that they could not (a) build the docs locally (sphinx_rtd_theme missing) or (b) write pytest cases, and asked for help.

I hit the same NaN regime as a downstream user of entropic_partial_wasserstein, so this PR re-applies @wzm2256's function on top of current master and supplies the missing pieces.

Why a new PR rather than pushing to #724's branch

When PR #724 was opened, ot/partial.py was a single file. The maintainers later split it into the ot/partial/ package, so the diff between #724's branch and current master is now +2k / −13k lines of mostly unrelated movement. Re-applying just the new function on the new layout is cleaner than fighting that rebase.

If maintainers prefer to keep #724 as the canonical PR, I'm happy to close this and instead push these changes to that branch (write access permitting).

What's added (additive only; nothing removed)

  • ot/partial/partial_solvers.pyentropic_partial_wasserstein_logscale function, identical algorithm to PR [WIP] Add a stablized function entropic_partial_wasserstein_logscale #724 re-applied on the current layout.
  • ot/partial/__init__.py — export + __all__ entry.
  • test/test_partial.py — 5 new test functions / 11 parametrised cases:
    • No NaN/Inf at small ε across [0.1, 0.05, 0.01, 5e-3, 1e-3, 5e-4] — reproduces issue entropic_partial_wasserstein not stable #723's failure mode and verifies the logscale solver stays finite.
    • Bit-for-bit agreement with the standard solver at large ε (atol=1e-10, rtol=1e-10) across [1.0, 10.0].
    • approaches_exact_at_small_reg — checks the plan-cost vs. exact partial-OT gap at reg=1e-3 (not just NaN-freeness; verifies mathematical correctness).
  • examples/unbalanced-partial/plot_entropic_partial_wasserstein_logscale.py — Sphinx-Gallery example reproducing entropic_partial_wasserstein not stable #723's failure and showing the fix. Adapted from @wzm2256's compare_logscale_POT.py.
  • docs/source/user_guide.rst — one-paragraph mention next to entropic_partial_wasserstein.
  • RELEASES.md — one-line entry under 0.9.7.dev0, phrased as mitigation (the standard solver itself is unchanged; callers opt into the log-domain variant).

Local verification

  • pytest test/test_partial.py -q → 19 passed (8 originals + 11 new parametrised cases).
  • pytest test/ (full suite) → 1939 passed, 97 skipped, 6 xfailed — no regression outside partial.
  • Example script runs end-to-end with MPLBACKEND=Agg. Standard solver returns NaN at reg ∈ {0.05, 0.01} on the 50×50 cost-scale-50 problem; logscale solver stays finite over the full sweep.
  • git diff master -- ot/partial/partial_solvers.py shows exactly one hunk: the new function block. Nothing else drifts.

Co-authorship

Credit for the actual numerical work belongs to @wzm2256. If you'd prefer me to mark the commit Co-authored-by: wzm2256 <wzm2256@qq.com> and amend, I'm happy to — it just wasn't clear from the PR conventions here whether you prefer trailers or PR description acknowledgements.

cc @rflamary @cedricvincentcuaz — you both did master-merges on #724 earlier this year so it looked like a "we want this in" signal. Happy to do whatever's least friction here: this PR can be the canonical one, or I can close it and push to #724's branch.

The [WIP] prefix matches the original PR's convention and signals this is open for re-titling to [MRG] once you've had a look.

… (rescue of PythonOT#724)

Re-applies the function from PR PythonOT#724 by wzm2256 on top of current
master (the original PR is stuck at CONFLICTING since 2025-09; this
takes the additive parts and skips the obsolete merges through the
March-2025 single-file layout). Subject is [WIP] because the original
PR is also [WIP] and maintainer review is still required.

Changes vs master:

  ot/partial/partial_solvers.py
    + entropic_partial_wasserstein_logscale  (function body verbatim
      from PR PythonOT#724 modulo: (a) duplicate sphinx label removed to avoid
      build failure, (b) print warning -> warnings.warn(stacklevel=2)
      for convention).

  ot/partial/__init__.py
    + entropic_partial_wasserstein_logscale export

  test/test_partial.py
    + test_entropic_partial_wasserstein_logscale_matches_old_at_large_reg
      (machine-precision agreement at reg in {10.0, 1.0}: atol=1e-10)
    + test_entropic_partial_wasserstein_logscale_no_nan_at_small_reg
      (parametrised over reg in {0.1, 0.05, 0.01, 5e-3, 1e-3, 5e-4})
    + test_entropic_partial_wasserstein_logscale_approaches_exact_at_small_reg
      (plan-cost gap vs exact partial OT at reg=1e-3)
    + test_entropic_partial_wasserstein_logscale_log_dict
    + test_entropic_partial_wasserstein_logscale_input_validation

  examples/unbalanced-partial/plot_entropic_partial_wasserstein_logscale.py
    + Sphinx-Gallery example reproducing issue PythonOT#723 + the fix
      (MPLBACKEND=Agg-safe; narrative softened to acknowledge
      BLAS/platform-dependent underflow boundary).

  docs/source/user_guide.rst
    + one-paragraph mention next to entropic_partial_wasserstein.

  RELEASES.md
    + entry under 0.9.7.dev0 (phrased as "mitigated via new log-domain
      variant", not "fixed", since the standard solver itself is
      unchanged).

Verified locally on master at 41a4d57:
  pytest test/ -> 1939 passed, 97 skipped, 6 xfailed (no regressions).
  pytest test/test_partial.py -> 19 passed (8 originals + 11 new
    parametrised cases for the logscale function).
  Example script runs end-to-end with MPLBACKEND=Agg.
  The new function agrees with the standard solver at reg >= 1.0 to
  ~1e-18 absolute (atol=1e-10 in tests is conservative) and stays
  finite at reg down to 5e-4 on a 50x50 cost-scale-~50 problem (the
  exact failure mode of issue PythonOT#723); std solver returns NaN at reg ~
  0.05-0.01 on the same problem.

References Issue PythonOT#723. Maintainer review needed before merge — author
attribution to wzm2256 retained via Co-authored-by trailer.

Co-authored-by: wzm2256 <wzm2256@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant