Skip to content

Commit 43c19d0

Browse files
authored
Merge branch 'master' into release0.9.7
2 parents 82741d8 + c836b97 commit 43c19d0

11 files changed

Lines changed: 1020 additions & 85 deletions

File tree

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -459,18 +459,20 @@ Artificial Intelligence.
459459

460460
\[82] Bonet, C., Nadjahi, K., Séjourné, T., Fatras, K., & Courty, N. (2024). [Slicing Unbalanced Optimal Transport](https://openreview.net/forum?id=AjJTg5M0r8). Transactions on Machine Learning Research.
461461

462-
[83] Germain, T., Flamary, R., Kostic, V. R., & Lounici, K. (2025). [A Spectral-Grassmann Wasserstein Metric for Operator Representations of Dynamical Systems](https://arxiv.org/abs/2509.24920).
462+
\[83] Germain, T., Flamary, R., Kostic, V. R., & Lounici, K. (2025). [A Spectral-Grassmann Wasserstein Metric for Operator Representations of Dynamical Systems](https://arxiv.org/abs/2509.24920).
463463

464-
[84] Genest, B., Bonneel, N., Nivoliers, V., & Coeurjolly, D. (2025). [BSP-OT: Sparse transport plans between discrete measures in loglinear time.](https://dl.acm.org/doi/10.1145/3763281) ACM Transactions on Graphics (TOG), 44(6), 1-15.
464+
\[84] Genest, B., Bonneel, N., Nivoliers, V., & Coeurjolly, D. (2025). [BSP-OT: Sparse transport plans between discrete measures in loglinear time.](https://dl.acm.org/doi/10.1145/3763281) ACM Transactions on Graphics (TOG), 44(6), 1-15.
465465

466-
[85] Mahey, G., Chapel, L., Gasso, G., Bonet, C., & Courty, N. (2023). [Fast Optimal Transport through Sliced Generalized Wasserstein Geodesics](https://proceedings.neurips.cc/paper_files/paper/2023/hash/6f1346bac8b02f76a631400e2799b24b-Abstract-Conference.html). Advances in Neural Information Processing Systems, 36, 35350–35385.
466+
\[85] Mahey, G., Chapel, L., Gasso, G., Bonet, C., & Courty, N. (2023). [Fast Optimal Transport through Sliced Generalized Wasserstein Geodesics](https://proceedings.neurips.cc/paper_files/paper/2023/hash/6f1346bac8b02f76a631400e2799b24b-Abstract-Conference.html). Advances in Neural Information Processing Systems, 36, 35350–35385.
467467

468-
[86] Tanguy, E., Chapel, L., Delon, J. (2025). [Sliced Transport Plans](https://arxiv.org/abs/2508.01243) arXiv preprint 2506.03661.
468+
\[86] Tanguy, E., Chapel, L., Delon, J. (2025). [Sliced Transport Plans](https://arxiv.org/abs/2508.01243) arXiv preprint 2506.03661.
469469

470-
[87] Liu, X., Diaz Martin, R., Bai Y., Shahbazi A., Thorpe M., Aldroubi A., Kolouri, S. (2024). [Expected Sliced Transport Plans](https://openreview.net/forum?id=P7O1Vt1BdU). International Conference on Learning Representations.
470+
\[87] Liu, X., Diaz Martin, R., Bai Y., Shahbazi A., Thorpe M., Aldroubi A., Kolouri, S. (2024). [Expected Sliced Transport Plans](https://openreview.net/forum?id=P7O1Vt1BdU). International Conference on Learning Representations.
471471

472-
[88] Bouveyron, C. & Corneli, M. (2026). [Scaling optimal transport to high-dimensional Gaussian distributions with application to domain adaptation](https://hal.science/hal-04930868v4/file/Article-OT-HDGauss-v4.pdf). Statistics and Computing 36.2 (2026): 88.
472+
\[88] Bouveyron, C. & Corneli, M. (2026). [Scaling optimal transport to high-dimensional Gaussian distributions with application to domain adaptation](https://hal.science/hal-04930868v4/file/Article-OT-HDGauss-v4.pdf). Statistics and Computing 36.2 (2026): 88.
473473

474-
[89] Tipping, M.E. & Bishop, C.M. (1999). [Probabilistic principal component analysis](https://www.cs.columbia.edu/~blei/seminar/2020-representation/readings/TippingBishop1999.pdf). Journal of the Royal Statistical Society Series B: Statistical Methodology 61.3 (1999): 611-622.
474+
\[89] Tipping, M.E. & Bishop, C.M. (1999). [Probabilistic principal component analysis](https://www.cs.columbia.edu/~blei/seminar/2020-representation/readings/TippingBishop1999.pdf). Journal of the Royal Statistical Society Series B: Statistical Methodology 61.3 (1999): 611-622.
475475

476-
[90] Genans, F., Godichon-Baggioni, A., Vialard, F. X., & Wintenberger, O. (2025). [Decreasing Entropic Regularization Averaged Gradient for Semi-Discrete Optimal Transport](https://proceedings.neurips.cc/paper_files/paper/2025/file/d7efa12e98f5e0dd8b4f48cd60b4e3aa-Paper-Conference.pdf). Advances in Neural Information Processing Systems, 38, 146913-146949.
476+
\[90] Genans, F., Godichon-Baggioni, A., Vialard, F. X., & Wintenberger, O. (2025). [Decreasing Entropic Regularization Averaged Gradient for Semi-Discrete Optimal Transport](https://proceedings.neurips.cc/paper_files/paper/2025/file/d7efa12e98f5e0dd8b4f48cd60b4e3aa-Paper-Conference.pdf). Advances in Neural Information Processing Systems, 38, 146913-146949.
477+
478+
\[91] Fatras, K., Zine, Y., Majewski, S., Flamary, R., Gribonval, R., & Courty, N. (2021). [Minibatch optimal transport distances; analysis and applications](https://arxiv.org/pdf/2101.01792). arXiv preprint arXiv:2101.01792.

RELEASES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ The release finally includes several bug fixes and performance improvements, as
4646
- Update the geomloss wrapper to the new version and API (PR #826)
4747
- Fix docstrings for `lowrank_gromov_wasserstein_samples` and `lowrank_sinkhorn` (PR #823)
4848
- Reorganize all tests per backend (PR #828)
49-
- Update sgot cost function and example (PR #830)
49+
- Implement debiased OT solvers in `ot.solve_sample`.
5050

5151

5252
#### Closed issues
@@ -70,7 +70,7 @@ The release finally includes several bug fixes and performance improvements, as
7070
- Fix failing test for unbalanced solver with generic regularization (PR #824)
7171
- Fix reference number error introduced in PR #767 (PR #819)
7272
- Fix docstrings for `lowrank_gromov_wasserstein_samples` and `lowrank_sinkhorn` (PR #823)
73-
73+
- Update sgot cost function and example (PR #830)
7474

7575
## 0.9.6.post1
7676

examples/plot_debias_sink_div.py

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
======================================
4+
Sinkhorn Divergence and Debiased OT solvers
5+
======================================
6+
7+
This example shows how to use the debiased OT solvers in `ot.solve_sample` to
8+
compute Sinkhorn divergences and debiased Minibatch solutions. The debiased OT solvers
9+
can be used with balanced and unbalanced OT problems, and with different
10+
regularization types (entropic, L2, group lasso).
11+
"""
12+
13+
# Author: Remi Flamary <remi.flamary@polytechnique.edu>
14+
#
15+
# License: MIT License
16+
# sphinx_gallery_thumbnail_number = 3
17+
18+
# %%
19+
20+
import numpy as np
21+
import matplotlib.pylab as pl
22+
import ot
23+
import ot.plot
24+
from ot.datasets import make_1D_gauss as gauss
25+
26+
##############################################################################
27+
# Generate data
28+
# -------------
29+
30+
31+
# %%
32+
def sample_ball(n, radius=1.0, center=(0.0, 0.0)):
33+
np.random.seed(0)
34+
theta = 2 * np.pi * np.random.rand(n)
35+
r = radius * np.sqrt(np.random.rand(n))
36+
37+
x = r * np.cos(theta) + center[0]
38+
y = r * np.sin(theta) + center[1]
39+
40+
return np.stack((x, y), axis=1)
41+
42+
43+
def sample_two_balls(n, radius=1.0, sep=1):
44+
assert n % 2 == 0, "n must be even"
45+
centers = ((-sep, -sep), (sep, sep))
46+
n_half = n // 2
47+
X1 = sample_ball(n_half, radius, centers[0])
48+
X2 = sample_ball(n_half, radius, centers[1])
49+
50+
perm = np.random.permutation(n_half * 2)
51+
X = np.vstack((X1, X2))
52+
X = X[perm]
53+
return X
54+
55+
56+
n = 50
57+
58+
x1 = sample_ball(n, radius=1.0, center=(0, 0))
59+
x2 = sample_two_balls(n, radius=1.0, sep=1.5)
60+
61+
pl.figure(1, figsize=(5, 5))
62+
pl.scatter(x1[:, 0], x1[:, 1], label="Source distribution", alpha=0.7)
63+
pl.scatter(x2[:, 0], x2[:, 1], label="Target distribution", alpha=0.7)
64+
pl.legend()
65+
pl.title("Two distributions")
66+
ax = pl.axis()
67+
68+
69+
##############################################################################
70+
# Compute Sinkhorn divergence and visualize plans
71+
# -----------------------------------------------
72+
# The Sinkhorn divergence is computed by setting the `debias` parameter to
73+
# `True` in the `ot.solve_sample` function. The resulting value is the Sinkhorn
74+
# divergence. The Sinkhorn divergences is computed as:
75+
#
76+
# .. math::
77+
# S_\epsilon(\mu, \nu) = OT_\epsilon(\mu, \nu) - \frac{1}{2} OT_\epsilon(\mu, \mu) - \frac{1}{2} OT_\epsilon(\nu, \nu)
78+
#
79+
# The entropic OT plans for each of those terms can be accessed in the `log`
80+
# attribute of the result, and can be visualized using the
81+
# `ot.plot.plot2D_samples_mat` function.
82+
83+
res = ot.solve_sample(x1, x2, reg=0.1, debias=True)
84+
85+
print("Sinkhorn divergence: ", res.value)
86+
87+
plan_11 = res.log["res_aa"].plan
88+
plan_12 = res.log["res_ab"].plan
89+
plan_22 = res.log["res_bb"].plan
90+
91+
#
92+
pl.figure(2, figsize=(15, 5))
93+
94+
pl.subplot(1, 3, 1)
95+
ot.plot.plot2D_samples_mat(x1, x1, plan_11, thr=0.05)
96+
pl.scatter(x1[:, 0], x1[:, 1], label="Source distribution", zorder=2)
97+
pl.axis(ax)
98+
pl.title("Plan between source and source")
99+
pl.subplot(1, 3, 2)
100+
ot.plot.plot2D_samples_mat(x1, x2, plan_12, thr=0.05)
101+
pl.scatter(x1[:, 0], x1[:, 1], label="Source distribution", zorder=2)
102+
pl.scatter(x2[:, 0], x2[:, 1], label="Target distribution", zorder=2)
103+
pl.axis(ax)
104+
pl.title("Plan between source and target")
105+
pl.subplot(1, 3, 3)
106+
ot.plot.plot2D_samples_mat(x2, x2, plan_22, thr=0.05)
107+
pl.scatter(x2[:, 0], x2[:, 1], label="Target distribution", color="C1", zorder=2)
108+
pl.axis(ax)
109+
pl.title("Plan between target and target")
110+
111+
112+
##############################################################################
113+
# Debiased Minibatch OT
114+
# ---------------------------------
115+
#
116+
# Doing OT on minibatches leads to a similar bias than using entropic
117+
# regularization since the average OT plan is densified due to the stochasticity
118+
# of the minibatch sampling. On a given minibatch, the debiased loss can be
119+
# computed by setting the `debias` parameter to `'split'`that split the data
120+
# points in each distributions in two and computes the debias OT loss as:
121+
#
122+
# .. math::
123+
# \tilde{OT}_m(\mu, \nu) = \frac{1}{2}(\hat{OT}_m(\mu_1, \nu_1) + \hat{OT}_m(\mu_2, \nu_2) - \hat{OT}_m(\nu_1, \nu_2) - \hat{OT}_m(\mu_1, \nu_2))
124+
#
125+
126+
# %% solve OT minibtach and visualize the plans
127+
128+
res = ot.solve_sample(x1, x2, debias="split")
129+
130+
print("Debiased minibatch OT loss: ", res.value)
131+
132+
# recover the plans for each of the four terms in the debiased loss
133+
plan_11 = res.log["res_aa"].plan
134+
plan_12 = res.log["res_ab1"].plan
135+
plan_21 = res.log["res_ab2"].plan
136+
plan_22 = res.log["res_bb"].plan
137+
sel_a1 = res.log["sel_a1"]
138+
sel_a2 = res.log["sel_a2"]
139+
sel_b1 = res.log["sel_b1"]
140+
sel_b2 = res.log["sel_b2"]
141+
142+
nb1 = plan_11.shape[0]
143+
nb2 = plan_22.shape[0]
144+
145+
pl.figure(4, figsize=(15, 3))
146+
147+
pl.subplot(1, 4, 1)
148+
pl.scatter(x1[sel_a1, 0], x1[sel_a1, 1], label="$\mu_1$", zorder=2)
149+
pl.scatter(
150+
x1[sel_a2, 0], x1[sel_a2, 1], label=r"$\mu_2$", zorder=2, color="C0", alpha=0.5
151+
)
152+
pl.scatter(x2[sel_b1, 0], x2[sel_b1, 1], label=r"$\nu_1$", zorder=2, color="C1")
153+
pl.scatter(
154+
x2[sel_b2, 0], x2[sel_b2, 1], label=r"$\nu_2$", zorder=2, color="C1", alpha=0.5
155+
)
156+
pl.title("Minibatch split")
157+
pl.axis(ax)
158+
pl.legend()
159+
160+
161+
pl.subplot(1, 4, 2)
162+
ot.plot.plot2D_samples_mat(x1[sel_a1], x1[sel_a2], plan_11, thr=0.05)
163+
pl.scatter(x1[sel_a1, 0], x1[sel_a1, 1], zorder=2)
164+
pl.scatter(
165+
x1[sel_a2, 0],
166+
x1[sel_a2, 1],
167+
zorder=2,
168+
color="C0",
169+
alpha=0.5,
170+
)
171+
pl.axis(ax)
172+
pl.title("Plan between source and source")
173+
pl.subplot(1, 4, 3)
174+
ot.plot.plot2D_samples_mat(x1[sel_a1], x2[sel_b1], plan_12, thr=0.05)
175+
ot.plot.plot2D_samples_mat(x1[sel_a2], x2[sel_b2], plan_21, thr=0.05, alpha=0.5)
176+
177+
pl.scatter(x1[sel_a1, 0], x1[sel_a1, 1], label="Source distribution", zorder=2)
178+
pl.scatter(
179+
x2[sel_b1, 0], x2[sel_b1, 1], label="Target distribution", zorder=2, color="C1"
180+
)
181+
pl.scatter(
182+
x1[sel_a2, 0],
183+
x1[sel_a2, 1],
184+
label="Source distribution",
185+
zorder=2,
186+
color="C0",
187+
alpha=0.5,
188+
)
189+
pl.scatter(
190+
x2[sel_b2, 0],
191+
x2[sel_b2, 1],
192+
label="Target distribution",
193+
zorder=2,
194+
color="C1",
195+
alpha=0.5,
196+
)
197+
pl.axis(ax)
198+
pl.title("Plan between source and target")
199+
200+
pl.subplot(1, 4, 4)
201+
ot.plot.plot2D_samples_mat(x2[sel_b1], x2[sel_b2], plan_22, thr=0.05)
202+
pl.scatter(
203+
x2[sel_b1, 0], x2[sel_b1, 1], label="Target distribution", zorder=2, color="C1"
204+
)
205+
pl.scatter(
206+
x2[sel_b2, 0],
207+
x2[sel_b2, 1],
208+
label="Target distribution",
209+
zorder=2,
210+
color="C1",
211+
alpha=0.5,
212+
)
213+
pl.axis(ax)
214+
pl.title("Plan between target and target")
215+
216+
217+
##############################################################################
218+
# Comparison of the divergences
219+
# -------------------------------------------------
220+
221+
# %% move a distribution and compute Sinkhorn divergence and Sinkhorn distance
222+
reg = 0.1
223+
224+
sep_list = np.linspace(0, 1.0, 10)
225+
sink_list = []
226+
sink_div_list = []
227+
ot_mb_list = []
228+
ot_mb_sink_list = []
229+
for sep in sep_list:
230+
x2sep = sample_two_balls(n, radius=1.0, sep=sep)
231+
sink_list.append(
232+
ot.solve_sample(
233+
x1,
234+
x2sep,
235+
reg=reg,
236+
).value
237+
)
238+
sink_div_list.append(ot.solve_sample(x1, x2sep, reg=reg, debias=True).value)
239+
ot_mb_list.append(ot.solve_sample(x1, x2sep, debias="split").value)
240+
241+
ot_mb_sink_list.append(ot.solve_sample(x1, x2sep, reg=1, debias="split").value)
242+
243+
pl.figure(3)
244+
pl.plot(sep_list, sink_list, label="Sinkhorn loss", color="C0")
245+
pl.plot(sep_list, sink_div_list, label="Sinkhorn divergence", color="C1")
246+
pl.plot(sep_list, ot_mb_list, label="Debiased MB OT", color="C2")
247+
pl.plot(sep_list, ot_mb_sink_list, label="Debiased MB Sinkhorn", color="C3")
248+
pl.xlabel("Separation between distributions")
249+
pl.ylabel("Loss/Divergence")
250+
pl.title("Comparison of biased VS debiased OT losses")
251+
pl.grid()
252+
pl.legend()
253+
254+
# %%

examples/plot_quickstart_guide.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,8 @@ def df(G):
467467
# loss_fgw = ot.gromov.fused_gromov_wasserstein2(C1, C2, M, a, b, alpha=0.1)
468468
# loss_efgw = ot.gromov.entropic_fused_gromov_wasserstein2(C1, C2, M, a, b, alpha=0.1, epsilon=reg)
469469
#
470-
# Large scale OT
471-
# --------------
470+
# Large scale OT and approximations
471+
# ---------------------------------
472472
#
473473
# We discuss here strategies to solve large scale OT problems using approximations
474474
# of the exact OT problem.
@@ -583,11 +583,67 @@ def df(G):
583583
print(f"Exact OT loss = {loss:1.3f}")
584584
print(f"Bures-Wasserstein distance = {bw_value:1.3f}")
585585

586+
# %%
587+
# One can also use the HD gaussian assumption (low rank covariance + diagonal)
588+
# that has better properties in high dimension. The rank of the covariance
589+
# matrices can be controlled with the :code:`rank` parameter.
590+
591+
hdbw_value = ot.solve_sample(x1, x2, a, b, method="gaussian_hd", rank=1).value
592+
593+
print(f"Bures-Wasserstein distance = {bw_value:1.3f}")
594+
print(f"High Dimensional Bures-Wasserstein distance = {hdbw_value}")
595+
586596
# %%
587597
# .. note::
588598
# The Gaussian Wasserstein problem can be solved with the classic API using the
589599
# :func:`ot.gaussian.empirical_bures_wasserstein_distance` function.
590600
#
601+
# Sliced Wasserstein
602+
# ~~~~~~~~~~~~~~~~~~
603+
#
604+
# The Sliced Wasserstein distance is a Wasserstein distance between
605+
# empirical distributions that is computed by projecting the samples on random
606+
# directions and averaging the Wasserstein distances between the projected
607+
# distributions. It can be used as an approximation of the Wasserstein distance
608+
# between empirical distributions.
609+
610+
sw_value = ot.solve_sample(x1, x2, a, b, method="sliced", n_projections=10).value
611+
max_sw_value = ot.solve_sample(
612+
x1, x2, a, b, method="max_sliced", n_projections=10
613+
).value
614+
615+
print(f"Exact OT loss = {loss:1.3f}")
616+
print(f"Sliced Wasserstein distance = {sw_value:1.3f}")
617+
print(f"Max Sliced Wasserstein distance = {max_sw_value:1.3f}")
618+
619+
# %%
620+
# Binary Space Partitioning (BSP) OT
621+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
622+
#
623+
# One can also use the BSP OT approximation that is based on a recursive
624+
# partitioning of the space and computes the Wasserstein distance between the
625+
# empirical distributions by solving small OT problems between the samples in each
626+
# partition. The number of partitions can be controlled with the
627+
# `n_projections` parameter.
628+
629+
# BSP can only find bijections so require same number of points
630+
x1_bsp = np.concatenate([x1, x1], axis=0)
631+
632+
sol_bsp = ot.solve_sample(x1_bsp, x2, method="bsp", n_projections=10)
633+
bsp_value = sol_bsp.value
634+
bsp_sparse_plan = sol_bsp.sparse_plan
635+
636+
# sphinx_gallery_start_ignore
637+
638+
pl.figure(1, (3, 3))
639+
plot2D_samples_mat(x1_bsp, x2, bsp_sparse_plan)
640+
pl.plot(x1_bsp[:, 0], x1_bsp[:, 1], "ob", label="Source samples", **style)
641+
pl.plot(x2[:, 0], x2[:, 1], "or", label="Target samples", **style)
642+
pl.title("BSP OT plan loss={:.3f}".format(bsp_value))
643+
pl.show()
644+
645+
# sphinx_gallery_end_ignore
646+
# %%
591647
# Comparing all OT plans
592648
# ----------------------
593649
#

0 commit comments

Comments
 (0)