Skip to content

[MRG] Warn when the convolutional barycenter kernel underflows (closes #458) - #873

Open
deeb01 wants to merge 3 commits into
PythonOT:masterfrom
deeb01:fix-458-convol-kernel-underflow
Open

deeb01 wants to merge 3 commits into
PythonOT:masterfrom
deeb01:fix-458-convol-kernel-underflow

Conversation

@deeb01

@deeb01 deeb01 commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Types of changes

Bug fix (non-breaking change which fixes an issue).

Motivation and context / Related issue

Closes #458.

The 2D convolutional barycenters build K = exp(-(x-y)**2 / reg) on a unit grid. At small reg the distant entries underflow, mass can no longer cross the image, and the barycenter collapses towards the arithmetic mean of the inputs — which reads as over-diffuse rather than as a failure.

Two things it is not, both raised in the thread: not the debiasing (the barycenter of {mu, mu} comes back as mu to L1 4e-4 at every reg), and not slow convergence (200000 iterations at stopThr=1e-12 give the same wrong answer as 10000 at 1e-3).

Recovered width for two Gaussians of width 0.06 separated by 0.15:

1/reg default sinkhorn_log
2000 236% off 0.0%
1000 182% 0.0%
500 20% 0.0%
400 2.8% 0.0%
<=333 <=0.3% 0.0%

At reg=1e-3 the width is 0.169 ~= sqrt(0.15^2 + 0.06^2): the inputs never merged.

Sinkhorn forms products and ratios of kernel entries, so only about half the exponent range is usable. The warning triggers when the smallest kernel exponent falls below log(tiny)/2, which is where the error stops being negligible, and points at method='sinkhorn_log'.

test_convolutional_barycenter_non_square used reg=1e-3, which underflows on any grid; its uniform input needs no transport so the assertion held. Now 1e-2, same code path. Happy to revert if you prefer the old value.

How has this been tested (if it applies)

New test asserts the warning fires for both solvers and stays silent for a usable kernel and for sinkhorn_log; verified to fail on master. A second pins the equal-width property from Janati et al. 2020. Full suite 2692 passed, 63 skipped, warning fires nowhere else. pre-commit clean.

PR checklist

  • I have read the CONTRIBUTING document.
  • The documentation is up-to-date with the changes I made (check build artifacts).
  • All tests passed, and additional code has been covered with new tests.
  • I have added the PR and Issue fix to the RELEASES.md file.

…onOT#458)

The 2D convolutional barycenters build K = exp(-(x-y)**2 / reg) on a unit
grid. For small reg the distant entries underflow, so mass can no longer be
moved across the image and the barycenter collapses towards the arithmetic
mean of the inputs. That reads as an over-diffuse result rather than a
failure, which is what PythonOT#458 reports.

Measured on two Gaussians of equal width separated by 0.15, comparing the
recovered width against the true one: the default solver is 236% off at
reg=5e-4, 182% at 1e-3, 20% at 2e-3 and correct from 4e-3 up, while
method='sinkhorn_log' is exact throughout. It is not a convergence problem,
200000 iterations at stopThr=1e-12 return the same wrong answer, and it is
not the debiasing, which reproduces the barycenter of {mu, mu} to 4e-4.

Sinkhorn forms products and ratios of kernel entries, so only about half the
exponent range is usable. The warning triggers when the smallest kernel
exponent falls below log(tiny)/2, which lands where the measured error stops
being negligible.

test_convolutional_barycenter_non_square used reg=1e-3, which underflows on
any grid. Its uniform input needs no transport so the assertion held, but the
kernel was degenerate; it now uses 1e-2 and exercises the same code path.
Review of the previous commit:

- it converted the whole width x width exponent matrix with nx.to_numpy
  purely to read a dtype, which copies the array and forces a device sync
  on GPU backends, once per barycenter call;
- it compared two backend scalars with the builtin min, relying on __lt__
  returning a Python bool across all five backends;
- the reduction was unnecessary. The grid is always linspace(0, 1, n), so
  the most negative exponent is -1 / reg whatever the image size.

The check now derives the exponent from reg alone and reads the dtype from
a single-element array.

stacklevel was 3, which attributed the warning to ot/bregman/_convolutional.py
rather than to the caller. Measured against the real call stack: 4 lands on
the user's call site.
@codecov

codecov Bot commented Sep 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.87%. Comparing base (98d09a1) to head (752c789).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #873   +/-   ##
=======================================
  Coverage   96.86%   96.87%           
=======================================
  Files         128      128           
  Lines       26304    26345   +41     
=======================================
+ Hits        25480    25521   +41     
  Misses        824      824           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
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.

Sinkhorn_debiaised_barycenter seems too diffuse

1 participant