2222 :math:`\langle g, b\rangle + \mathbb{E}_X[\varphi_g(X)]` estimated by
2323 Monte Carlo, where the c-transform
2424 :math:`\varphi_g(x) = \min_j\big(c(x, y_j) - g_j\big)` is computed by
25- :func:`ot.semidiscrete.c_transform `. The solver **maximises** this
25+ :func:`ot.semidiscrete.semidiscrete_c_transform `. The solver **maximises** this
2626 objective.
2727
2828.. [90] Genans, F., Godichon-Baggioni, A., Vialard, F.-X., Wintenberger, O.
4141
4242from ot .semidiscrete import (
4343 solve_semidiscrete ,
44- atom_weights ,
45- c_transform ,
44+ semidiscrete_atom_weights ,
45+ semidiscrete_c_transform ,
46+ semidiscrete_ot_map ,
4647)
4748
4849##############################################################################
@@ -60,20 +61,20 @@ def source_sampler(batch_size):
6061target_positions = 0.1 + 0.8 * np .random .default_rng (0 ).random ((n_atoms , 2 ))
6162
6263
63- def plot_laguerre_cells (target , g , ax , title , resolution = 300 ):
64+ def plot_laguerre_cells (target , g , ax , title , resolution = 300 , alpha = 0.55 ):
6465 xs = np .linspace (0 , 1 , resolution )
6566 ys = np .linspace (0 , 1 , resolution )
6667 XX , YY = np .meshgrid (xs , ys )
6768 grid = np .stack ([XX .ravel (), YY .ravel ()], axis = 1 )
68- labels = atom_weights (target , grid , g , reg = 0.0 ).argmax (axis = 1 )
69+ labels = semidiscrete_atom_weights (target , grid , g , reg = 0.0 ).argmax (axis = 1 )
6970 image = labels .reshape (resolution , resolution )
7071 cmap = plt .get_cmap ("tab20" , target .shape [0 ])
7172 ax .imshow (
7273 image ,
7374 origin = "lower" ,
7475 extent = (0 , 1 , 0 , 1 ),
7576 cmap = cmap ,
76- alpha = 0.55 ,
77+ alpha = alpha ,
7778 vmin = - 0.5 ,
7879 vmax = target .shape [0 ] - 0.5 ,
7980 interpolation = "nearest" ,
@@ -101,21 +102,61 @@ def plot_laguerre_cells(target, g, ax, title, resolution=300):
101102# A single call to :func:`solve_semidiscrete` runs DRAG with the default
102103# arguments (``decreasing_reg=True``). We show the initial Voronoi cells
103104# (:math:`g = 0`) next to the Laguerre cells at the optimum.
104- # In this problem, the maximum cost between samples is 1.0, so we pass it as
105- # ``max_cost=1.0``. Knowing this bound, the potential values are clipped to
106- # [-max_cost, max_cost], where it is known that an optimal potential lies ([90]_, Lemma 1),
107- # which speeds up convergence.
105+ # With the squared-Euclidean cost (default ``metric='sqeuclidean'``), the cost
106+ # between a source point in :math:`[0, 1]^2` and an atom is
107+ # :math:`\|x - y\|^2 \le 2`. We clip the potential to
108+ # ``[-max_cost, max_cost] = [-2, 2]``, the localizing set where an optimal
109+ # potential lies ([90]_, Lemma 1), which speeds up convergence.
108110g_drag = solve_semidiscrete (
109111 target_positions ,
110112 source_sampler ,
111- n_iter = 20_000 ,
112- batch_size = 16 ,
113- max_cost = 1 .0 ,
113+ max_iter = 20_000 ,
114+ batch_size = 32 ,
115+ max_cost = 2 .0 ,
114116)
115117
116118fig , axes = plt .subplots (1 , 2 , figsize = (11 , 5.5 ))
117119plot_laguerre_cells (target_positions , np .zeros (n_atoms ), axes [0 ], "Voronoi (g = 0)" )
118- plot_laguerre_cells (target_positions , g_drag , axes [1 ], "DRAG" )
120+ plot_laguerre_cells (target_positions , g_drag , axes [1 ], "Approximated OT Laguerre cells" )
121+ plt .tight_layout ()
122+ plt .show ()
123+
124+
125+ ##############################################################################
126+ # Transport map over the Laguerre cells
127+ # -------------------------------------
128+ #
129+ # :func:`semidiscrete_ot_map` with ``reg=0`` is the hard Monge map: every
130+ # source point is sent to the atom of its Laguerre cell. Overlaying the map
131+ # (arrows on a source grid) on the *faded* cells shows each cell's mass
132+ # collapsing onto its atom -- a direct illustration of the mapping function.
133+
134+ gx = np .linspace (0.04 , 0.96 , 14 )
135+ grid = np .stack ([a .ravel () for a in np .meshgrid (gx , gx )], axis = 1 )
136+ mapped = semidiscrete_ot_map (target_positions , grid , g_drag , reg = 0.0 )
137+ labels = semidiscrete_atom_weights (target_positions , grid , g_drag , reg = 0.0 ).argmax (
138+ axis = 1
139+ )
140+
141+ cmap = plt .get_cmap ("tab20" , n_atoms )
142+ fig , ax = plt .subplots (figsize = (6.5 , 6.5 ))
143+ plot_laguerre_cells (
144+ target_positions , g_drag , ax , "Approximated OT map over Laguerre cells" , alpha = 0.22
145+ )
146+ ax .quiver (
147+ grid [:, 0 ],
148+ grid [:, 1 ],
149+ mapped [:, 0 ] - grid [:, 0 ],
150+ mapped [:, 1 ] - grid [:, 1 ],
151+ angles = "xy" ,
152+ scale_units = "xy" ,
153+ scale = 1 ,
154+ width = 0.005 ,
155+ headwidth = 4 ,
156+ headlength = 5 ,
157+ color = [cmap (i ) for i in labels ],
158+ zorder = 2 ,
159+ )
119160plt .tight_layout ()
120161plt .show ()
121162
@@ -134,15 +175,17 @@ def plot_laguerre_cells(target, g, ax, title, resolution=300):
134175
135176
136177def cell_masses (target , g , sampler , n_samples = 100_000 ):
137- labels = atom_weights (target , sampler (n_samples ), g , reg = 0.0 ).argmax (axis = 1 )
178+ labels = semidiscrete_atom_weights (target , sampler (n_samples ), g , reg = 0.0 ).argmax (
179+ axis = 1
180+ )
138181 counts = np .bincount (labels , minlength = target .shape [0 ])
139182 return counts / n_samples
140183
141184
142185def mc_cost (target , g , sampler , n_samples = 100_000 ):
143186 b = np .full (target .shape [0 ], 1.0 / target .shape [0 ])
144187 samples = sampler (n_samples )
145- return float (g @ b + c_transform (target , samples , g , reg = 0.0 ).mean ())
188+ return float (g @ b + semidiscrete_c_transform (target , samples , g , reg = 0.0 ).mean ())
146189
147190
148191target_mass = 1.0 / n_atoms
@@ -156,3 +199,37 @@ def mc_cost(target, g, sampler, n_samples=100_000):
156199 f" max: { np .max (np .abs (m_drag - target_mass )):.4f} "
157200 f" semi-dual cost (MC): { cost_drag :.5f} "
158201)
202+
203+
204+ ##############################################################################
205+ # Laguerre-cell masses
206+ # --------------------
207+ #
208+ # At the optimum every cell carries the same mass :math:`1/15`. The bar plot
209+ # shows the empirical mass per cell against this ground truth (dashed line):
210+ # every cell sits close to the theoretical value.
211+
212+ cmap = plt .get_cmap ("tab20" , n_atoms )
213+ fig , ax = plt .subplots (figsize = (7.5 , 4 ))
214+ ax .bar (
215+ np .arange (n_atoms ),
216+ m_drag ,
217+ color = [cmap (i ) for i in range (n_atoms )],
218+ edgecolor = "black" ,
219+ linewidth = 0.6 ,
220+ )
221+ ax .axhline (
222+ target_mass ,
223+ ls = "--" ,
224+ color = "black" ,
225+ lw = 1.5 ,
226+ label = "theoretical mass per cell at the optimum" ,
227+ )
228+ ax .set_ylim (0 , 1.6 * target_mass )
229+ ax .set_xticks (np .arange (n_atoms ))
230+ ax .set_xlabel ("atom index" )
231+ ax .set_ylabel ("Laguerre-cell mass" )
232+ ax .set_title ("Approximated OT: Laguerre-cell masses" )
233+ ax .legend ()
234+ plt .tight_layout ()
235+ plt .show ()
0 commit comments