Repository navigation
[MRG] Honor the documented unbalanced=float("inf") contract without changing the objective - #895
Open
xihaian251 wants to merge 1 commit into
Open
xihaian251 wants to merge 1 commit into
xihaian251 wants to merge 1 commit into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #895 +/- ##
==========================================
- Coverage 97.00% 97.00% -0.01%
==========================================
Files 130 131 +1
Lines 26842 27012 +170
==========================================
+ Hits 26039 26203 +164
- Misses 803 809 +6 🚀 New features to boost your workflow:
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
ot.solvedocuments thatunbalanced=float("inf")recovers balanced OT andthat
(inf, rho)/(rho, inf)request the semi-relaxed problem. Only theSinkhorn KL path implemented that sentinel. The exact (MM) and L-BFGSB paths
treated
infas a number and failed silently:ot.solve(M, a, b, reg=0, unbalanced=float("inf"))returnedvalue=nan,plan=nan,status=None, with no warning.ot.solve(M, a, b, reg=0.5, reg_type="l2", unbalanced=(inf, 1.0))returnedvalue=nantogether with a plan satisfying both marginals to 2.8e-17,i.e. it silently solved the balanced problem instead of the semi-relaxed one.
Root cause:
mm_unbalancedbuildsr1 = reg_m1 / (reg + reg_m1 + reg_m2)(
inf / inf = nan), andlbfgsb_unbalancedevaluatesreg_m1 * kl_div(...)(
inf * 0 = nan).Why a plain "treat inf as balanced" is not enough
A two-sided infinite relaxation is the hard-constraint limit of the same
objective. It equals the problem solved by
unbalanced=Noneonly when thebalanced branch minimizes the same objective. That is not the case for every
regularizer. On the 2x2 fixture
a = b = (0.5, 0.5),M = [[0, 1], [1, 0]]:unbalanced=Nonevaluekl, defaultc = a b^Tkl, customc(entropic path)x*=0.4804)cis ignored)l2l2entropyreg)The custom reference measure used in that row is
c = [[0.45, 0.05], [0.05, 0.45]], which is feasible for these marginals;the hard-constraint limit is therefore not
conceM != 0(
x* = 0.4804, whileccorresponds tox = 0.45).Two independent reasons behind those differences:
l2: the balanced branch solvesreg/2 * ||P||^2with no referencemeasure and reports
<P,M> + reg * ||P||^2, while the UOT limit is<P,M> + reg/2 * ||P - c||^2.entropy: the balanced value omits-sum(P), so it differs by exactlyreg.The plans are not guaranteed to match either. With the default
c = a b^T,<P,c>is constant on the feasible set only whenaorbis uniform. Fora = (0.7, 0.3),b = (0.6, 0.4),M = 0,reg = 1the UOT limit isP00 = 0.42while the balanced L2 solver returnsP00 = 0.40.Dispatching those combinations would therefore return the solution of a
different optimization problem, so this PR rejects them instead.
What this changes
reg == 0with both relaxations infinite → the exact balanced solver(
unbalanced=None), for anyc(the regularizer vanishes).reg > 0withreg_type="KL"and the default reference measurec = a b^T→ the balanced KL solver; its objective is exactly the limit.
(f, df)pair → the balanced CG solver, which minimizes thesame
f.honours a custom
cwhenunbalancedis infinite, and is the only path thatsupports
potentials_initandmethod. Note that the plain balanced KL path(
unbalanced=None) ignorescand always usesa b^T.ones for
l2/entropy/KLwith a customcoutside the entropic KLpath, raise an explicit
NotImplementedErrorbefore any optimization.No silent NaN, no silently-wrong plan.
mm_unbalanced/lbfgsb_unbalanceddocstrings state that the wrapperresolves or rejects the sentinel while the low-level solvers still require a
finite
reg_m.RELEASES.mdentry added.Tests
test/unbalanced/test_inf_marginal.py, 99 cases over NumPy / PyTorch / JAX.The 2x2 fixtures make the hard-constraint feasible set one dimensional
(
P = [[x, .5-x], [.5-x, x]]), so every accepted combination is checkedagainst an independent analytic oracle (bounded scalar minimization of the
original objective), including the value - a value-only guard would miss the
entropyandl2cases. The rejected combinations are covered byexception-only tests; their numeric divergence is documented above, not
asserted in CI.
test/unbalanced+test/test_solvers.py: 1593 passed, 131 skipped5cb82c9:test/unbalanced681 passed, 33 skippedAll numbers above are local runs (Windows, Python 3.13, NumPy 2.4.6,
SciPy 1.16.3, PyTorch 2.10 CPU, JAX 0.12); GitHub Actions results will
appear separately and are not claimed here.
Compatibility
Inputs that previously returned NaN or a wrong plan now either return the
correct limit or raise. Already-correct calls are unchanged, in particular the
entropic KL path with
potentials_init/method. The only behaviour changefor a previously "successful" call is that
l2/entropy/KL+ customcwith
unbalanced=infnow raise instead of returning the balanced solution.Not verified: CUDA / CuPy / TensorFlow (no environment available).
Open questions for maintainers
entropybe supported instead of rejected? The plan coincides withthe balanced one; only the reported value differs, by exactly
reg. Thiscould be handled by compensating the value, if you prefer that convention.
l2branchreports
reg * ||P||^2while its solver minimizesreg/2 * ||P||^2.