-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdemos.html
More file actions
358 lines (348 loc) · 27 KB
/
Copy pathdemos.html
File metadata and controls
358 lines (348 loc) · 27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
<!doctype html>
<html lang="en">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-133810388-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-133810388-1');
</script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Interactive demos · François-Pierre Paty</title>
<meta name="description" content="Interactive in-browser demos of François-Pierre Paty's research on optimal transport, regularity and weak optimal transport in economics.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/academicons/1.9.4/css/academicons.min.css">
<link rel="stylesheet" href="stylesheets/main.css">
<script>
window.MathJax = { tex: { inlineMath: [['\\(', '\\)']], displayMath: [['$$', '$$']] }, svg: { fontCache: 'global' } };
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" async></script>
</head>
<body data-page="demos">
<div class="wrap">
<div class="layout">
<aside class="sidebar"><!-- injected by javascripts/sidebar.js --></aside>
<main>
<section id="demos-intro">
<h2>Interactive demos</h2>
<p>
Four of my papers, running live in your browser on small toy problems. Pick a demo below, drag the clouds and move the sliders.
</p>
<div class="demo-tabs" role="tablist">
<button class="demo-tab-btn" data-tab="srw" role="tab">Subspace Robust Wasserstein</button>
<button class="demo-tab-btn" data-tab="reg1d" role="tab">Regularity as Regularization</button>
<button class="demo-tab-btn" data-tab="adversarial" role="tab">Ground Cost Adversarial OT</button>
<button class="demo-tab-btn" data-tab="econ" role="tab">Weak OT in Economics</button>
</div>
</section>
<!-- ===================== SRW ===================== -->
<section class="demo-panel" data-panel="srw" role="tabpanel">
<h3 class="demo-heading">Subspace Robust Wasserstein</h3>
<p>
This demo brings to life <a href="http://proceedings.mlr.press/v97/paty19a.html">Subspace Robust Wasserstein Distances</a> (Paty and Cuturi, ICML 2019). Drag either point cloud to move it around.
</p>
<p>
Optimal transport measures the effort needed to morph one cloud of points, the measure \(\mu\), into the other one, the measure \(\nu\). In high dimension this effort is often dominated by a handful of directions that carry little real information. The paper asks a sharper question: along which low dimensional view are the two clouds the <em>hardest</em> to align? Writing \(P_E\) for the projection onto a subspace \(E\) of dimension \(k\), the subspace robust distance is
</p>
$$ \mathcal{S}_k(\mu,\nu)^2 \;=\; \max_{\dim E = k}\ \min_{\pi \in \Pi(\mu,\nu)}\ \int \lVert P_E(x-y) \rVert^2 \, \mathrm{d}\pi(x,y). $$
<p>
The data here is two dimensional and we take \(k = 1\), so the worst view is a single line, drawn in yellow. The algorithm goes back and forth until its two steps agree: solve the transport problem along the current line, then turn the line towards the direction that transport stretches the most. The dashed line is ordinary PCA, the direction of largest spread, which usually points somewhere else.
</p>
<div class="demo-card">
<div class="demo-canvas-wrap">
<canvas id="srw-canvas" width="900" height="520"></canvas>
</div>
<div class="demo-controls">
<button class="btn" id="srw-resample">Resample clouds</button>
<button class="btn ghost" id="srw-toggle-plan">Show transport plan</button>
<label>Spread of Y
<input type="range" id="srw-spread" min="0.04" max="0.34" step="0.01" value="0.18">
</label>
<label>Regularization ε
<input type="range" id="srw-eps" min="0.0005" max="0.015" step="0.0005" value="0.003">
</label>
</div>
<div class="demo-legend">
<span><span class="swatch" style="background:#2a6f7a"></span>first cloud (μ)</span>
<span><span class="swatch" style="background:#a4452f"></span>second cloud (ν)</span>
<span><span class="line-swatch" style="border-color:#caa84a"></span>worst-case line (k = 1)</span>
<span><span class="line-swatch dashed" style="border-color:#6a6258"></span>PCA</span>
</div>
<div class="demo-readout">
<span>robust distance S₁: <b id="srw-val">…</b></span>
<span>ordinary Wasserstein W₂: <b id="srw-w2">…</b></span>
<span>angle of the worst-case line: <b id="srw-dir">…</b></span>
</div>
</div>
<p class="demo-note">
Everything runs live on a few dozen points. The transport step uses entropic optimal transport (Sinkhorn), and the new line is the leading eigenvector of the transport weighted spread of the displacements. The projection \(P\) ranges over \(\{P : 0 \preceq P \preceq I,\ \operatorname{tr} P = 1\}\), exactly as in the paper. <a href="https://github.com/francoispierrepaty/SubspaceRobustWasserstein">Original Python code</a>.
</p>
</section>
<!-- ===================== REGULARITY 1D ===================== -->
<section class="demo-panel" data-panel="reg1d" role="tabpanel" hidden>
<h3 class="demo-heading">Regularity as Regularization</h3>
<p>
This demo brings to life <a href="http://proceedings.mlr.press/v108/paty20a.html">Regularity as Regularization: Smooth and Strongly Convex Brenier Potentials in Optimal Transport</a> (Paty, d'Aspremont and Cuturi, AISTATS 2020).
</p>
<p>
We want the optimal transport map \(T\) that carries a source distribution \(P\) onto a target distribution \(Q\), and we only see samples of each. Brenier's theorem says such a map is the gradient of a convex potential, \(T = \nabla f\). The paper estimates the map by looking for a potential whose gradient pushes \(P\) as close to \(Q\) as possible, while staying regular:
</p>
$$ \min_{f \,\in\, \mathcal{F}_{\mu,L}}\ W_2^2\big( \nabla f \,\#\, P,\ Q \big), $$
<p>
where \(\mathcal{F}_{\mu,L}\) is the set of potentials that are \(\mu\)-strongly convex and \(L\)-smooth, \(\nabla f \,\#\, P\) is the distribution obtained by sending every point \(x\) to \(\nabla f(x)\), and \(W_2\) is the Wasserstein distance. Imposing this regularity is the same as asking the map to be <strong>bi-Lipschitz</strong>: for all points,
</p>
$$ \mu\,\lVert x - x' \rVert \;\le\; \lVert T(x) - T(x') \rVert \;\le\; L\,\lVert x - x' \rVert. $$
<p>
In plain terms, this bounds how much the map is allowed to stretch or squeeze distances: it never pulls two points apart by more than a factor \(L\), and never crushes them together by more than a factor \(\mu\). Close points stay close, and far points stay far.
</p>
<p>
In one dimension this becomes especially transparent. A transport map is just an increasing curve, so regularity becomes a bound on the slope between any two observed source points:
</p>
$$ \mu \;\le\; \frac{T(x') - T(x)}{x' - x} \;\le\; L . $$
<p>
The lower bound \(\mu\) prevents the fitted map from flattening out and collapsing distant source points together. The upper bound \(L\) prevents it from making sudden steep jumps to chase noisy observations. Together they say: fit the data, but only with a curve whose local stretching stays plausible.
</p>
<p>
In the simulation, the smooth curves show the underlying source and target distributions \(P\) and \(Q\). What the estimator actually receives is much poorer: source samples \(x_i\) from \(P\), and noisy observations of where the true map sends them, \(y_i = T^*(x_i) + \varepsilon_i\), with \(\varepsilon_i \sim \mathcal{N}(0,\sigma^2)\) iid.
</p>
<p>
The red curve is the least-squares fit to those observations under the slope constraints,
</p>
$$ \min_T \sum_i \bigl(T(x_i) - y_i\bigr)^2
\quad\text{subject to}\quad
\mu \le \frac{T(x') - T(x)}{x' - x} \le L . $$
<p>
This is isotonic regression with two extra regularity rules. Plain isotonic regression only asks the map to keep increasing, which corresponds to \(\mu = 0\) and \(L = \infty\). Here the bounds also stop the map from becoming too flat or too steep, which is how the regularized fit filters out the noise.
</p>
<div class="demo-card">
<div class="demo-canvas-wrap">
<canvas id="reg-canvas" width="900" height="560"></canvas>
</div>
<div class="demo-controls">
<button class="btn" id="reg-resample">Resample</button>
<button class="btn" id="reg-bestfit">Best fit (μ, L)</button>
<label>noise σ
<input type="range" id="reg-sigma" min="0" max="0.4" step="0.01" value="0.14">
</label>
</div>
<div class="demo-controls">
<div class="rangefield">
<span class="rangelabel">fit couple (μ, L) = <b id="reg-fit-val">(0.15, 2.20)</b></span>
<div class="dualrange" id="reg-fit-dr"></div>
</div>
<div class="rangefield">
<span class="rangelabel">true couple (μ*, L*) = <b id="reg-true-val">(0.20, 3.00)</b></span>
<div class="dualrange" id="reg-true-dr"></div>
</div>
</div>
<div class="demo-legend">
<span><span class="swatch" style="background:#3a6ea5"></span>source P</span>
<span><span class="swatch" style="background:#c47d33"></span>target Q</span>
<span><span class="line-swatch" style="border-color:#4e9b5b"></span>true map</span>
<span><span class="line-swatch" style="border-color:#a4452f"></span>regularized map (μ ≤ T' ≤ L)</span>
<span><span class="line-swatch" style="border-color:#7d6fa3"></span>plain isotonic (μ=0, L=∞)</span>
<span><span class="line-swatch dashed" style="border-color:#8a857b"></span>unconstrained fit</span>
</div>
<div class="demo-readout">
<span>distance to the true map. Regularized fit: <b id="reg-rmse">…</b></span>
<span>isotonic: <b id="reg-rmse-iso">…</b></span>
<span>unconstrained: <b id="reg-rmse-raw">…</b></span>
</div>
</div>
<p class="demo-note">
About the two couples. The <em>true couple</em> \((\mu^*, L^*)\) is the regularity of the hidden true map: the source \(P\) is uniform, and pushing it through that map gives the target \(Q\), so this couple is what reshapes \(Q\). The amber curve is the clean \(Q\); the ticks underneath it are the samples \(y_i\), scattered around \(Q\) by the noise σ. The <em>fit couple</em> \((\mu, L)\) is instead the regularity you assume when reconstructing the map, and Best fit looks for the couple that recovers it best. The fit is computed exactly in the browser by coordinate descent on the slopes; in higher dimension the same estimator becomes the convex QCQP of the paper.
</p>
</section>
<!-- ===================== GROUND COST ADVERSARIAL OT ===================== -->
<section class="demo-panel" data-panel="adversarial" role="tabpanel" hidden>
<h3 class="demo-heading">Regularized OT is Ground Cost Adversarial</h3>
<p>
This demo illustrates <a href="http://proceedings.mlr.press/v119/paty20a.html">Regularized Optimal Transport is Ground Cost Adversarial</a> (Paty and Cuturi, ICML 2020). We work in the discrete setting, with a source distribution \(\mu\) supported on points \(i=1,\dots,n\) and a target distribution \(\nu\) supported on points \(j=1,\dots,m\). A <em>transport plan</em> \(\pi\) is a matrix whose entry \(\pi_{ij}\ge 0\) says how much mass is moved from \(i\) to \(j\); moving it there incurs a cost \(c_0(i,j)\,\pi_{ij}\). A plan is admissible if it ships out exactly the mass each source has and delivers exactly the mass each target wants, that is \(\sum_j \pi_{ij}=\mu_i\) and \(\sum_i \pi_{ij}=\nu_j\). We write \(\Pi(\mu,\nu)\) for this set of admissible plans.
</p>
<p>
Classical optimal transport picks the admissible plan of least total cost, and we write \(\mathcal{T}_c(\mu,\nu)\) for that minimum value when the ground cost is \(c\):
</p>
$$ \mathcal{T}_c(\mu,\nu)
\;=\;
\min_{\pi \in \Pi(\mu,\nu)} \langle c,\pi\rangle,
\qquad
\langle c,\pi\rangle = \sum_{ij} c(i,j)\,\pi_{ij}. $$
<p>
In practice one rarely solves this raw problem: a small convex regularizer \(\varepsilon R(\pi)\) is added to the plan \(\pi\) so that the solution is smoother and faster to compute. The message of the paper is that this regularizer is not just a numerical convenience. Adding \(\varepsilon R(\pi)\) to the cost of a fixed prior ground cost \(c_0\) is <em>exactly equivalent</em> to letting an adversary perturb the ground cost itself, away from \(c_0\), and then solving ordinary (unregularized) transport \(\mathcal{T}_c\) against that worst-case cost. In the discrete setting this equivalence reads
</p>
$$ \min_{\pi \in \Pi(\mu,\nu)} \langle c_0,\pi\rangle + \varepsilon R(\pi)
\;=\;
\sup_c \mathcal{T}_c(\mu,\nu)
- \varepsilon R^*\!\left({c-c_0\over\varepsilon}\right). $$
<p>
On the left is the usual regularized transport. On the right, an adversary searches over ground costs \(c\): it is rewarded for raising the transport cost \(\mathcal{T}_c(\mu,\nu)\), but pays the penalty \(\varepsilon R^*((c-c_0)/\varepsilon)\) for straying from the prior \(c_0\). The shape of that penalty, and so the kind of perturbation the adversary can afford, is set entirely by \(R^*\), the convex conjugate of the regularizer. Different regularizers therefore produce qualitatively different adversaries; switch between them with the buttons to see the specialized form of the identity and the cost it learns.
</p>
<div class="demo-card">
<div class="econ-models adv-modes" role="group" aria-label="Regularizer">
<button class="econ-model adv-mode active" data-regularizer="entropy">Entropy</button>
<button class="econ-model adv-mode" data-regularizer="quadratic">Quadratic</button>
<button class="econ-model adv-mode" data-regularizer="tsallis">Tsallis</button>
<button class="econ-model adv-mode" data-regularizer="capacity">Capacity</button>
</div>
<div class="adv-equation" id="adv-equation">
$$\min_{\pi\in\Pi(\mu,\nu)}\langle c_0,\pi\rangle+\varepsilon\sum_{ij}\pi_{ij}(\log\pi_{ij}-1)
=
\max_c \mathcal{T}_c(\mu,\nu)-\varepsilon\sum_{ij}\exp\!\left({c_{ij}-{c_0}_{ij}\over\varepsilon}\right).$$
</div>
<div class="adv-explainer" id="adv-explainer">
Entropic regularization uses \(R(\pi)=\sum_{ij}\pi_{ij}(\log\pi_{ij}-1)\), with conjugate \(R^*(s)=\sum_{ij}\exp(s_{ij})\). The penalty grows exponentially as a cost rises above \(c_0\), so the adversary nudges every cost a little but pays steeply for large moves: a soft perturbation spread around \(c_0\).
</div>
<div class="demo-canvas-wrap">
<canvas id="adv-canvas" width="900" height="620"></canvas>
</div>
<div class="demo-controls">
<label>Regularization ε
<input type="range" id="adv-eps" min="0.01" max="0.20" step="0.002" value="0.060">
</label>
</div>
<div class="demo-legend">
<span><span class="swatch" style="background:#2a6f7a"></span>source μ</span>
<span><span class="swatch" style="background:#a4452f"></span>target ν</span>
<span><span class="line-swatch" style="border-color:#6a6258"></span>transport plan</span>
<span><span class="swatch square" style="background:#123f4a"></span>large cost</span>
<span><span class="swatch square" style="background:#efe4cb"></span>small cost</span>
<span><span class="swatch square" style="background:#2a6f7a"></span>cost reduced</span>
<span><span class="swatch square" style="background:#a4452f"></span>cost increased</span>
</div>
<div class="demo-readout">
<span>regularizer: <b id="adv-reg-name">Entropy</b></span>
<span>plan entropy: <b id="adv-plan-entropy">…</b></span>
<span>active links: <b id="adv-active-links">…</b></span>
<span>adversarial cost spread: <b id="adv-cost-spread">…</b></span>
</div>
</div>
</section>
<!-- ===================== ECONOMICS WOT ===================== -->
<section class="demo-panel" data-panel="econ" role="tabpanel" hidden>
<h3 class="demo-heading">Weak Optimal Transport in Economics</h3>
<p>
This demo illustrates <a href="https://arxiv.org/abs/2205.09825">Algorithms for Weak Optimal Transport with an Application to Economics</a> (Paty, Choné and Kramarz, 2022). The economic question is: who works for whom, and what does the matching reveal about workers' skills and firms' technologies?
</p>
<p>
The model looks for the matching that maximizes total production. Firms have technology \(x\), workers have skill \(y\), and a matching says how much worker mass of each type is employed by each firm type. The figure shows that matching together with its two margins. In the central heatmap each row is a firm type and each column is a worker type, and darker cells mean the production-maximizing matching sends more of that worker type to that firm type. Aligned underneath the columns is the worker distribution \(\nu\); aligned beside the rows, on the right, are the firm sizes, the total mass each firm type hires. In OT, entropic OT and WOT these row totals are fixed and equal; only WOTUK lets them move.
</p>
<p>
Production uses a constant-elasticity-of-substitution (CES) function. A worker of type \(y=(y_1,y_2)\) carries a mix of two skills, and a firm of technology \(\alpha=(\alpha_1,\alpha_2)\) weights them by how much it relies on each:
</p>
$$ F(\alpha,y)
=
\left(\alpha_1 y_1^\rho + \alpha_2 y_2^\rho\right)^{1/\rho}. $$
<p>
In the demo the firm rows run from skill-1-intensive to skill-2-intensive, \(\alpha_i=(1-s_i,s_i)\), and the worker columns are a fixed grid \(y_j=(1-t_j,t_j)\) from one specialist extreme to the other. Maximizing production is the same problem as the optimal transport with cost \(c=-F\).
</p>
<p>
The two sliders reshape these inputs. <em>Firm specialization</em> spreads the firm technologies \(\alpha_i\) out from a tight mixed cluster toward the two specialist extremes. <em>Worker population</em> reshapes \(\nu\): the skill positions stay fixed, but their weights shift from specialists piled at the extremes to generalists massed in the middle.
</p>
<p>
Classical optimal transport treats production as pairwise: a firm of type \(x\) hiring a worker of type \(y\) produces \(F(x,y)\). Weak optimal transport lets the production of a firm depend on the whole group of workers it hires, written as a distribution \(\pi_x\). In barycentric WOT, only the average skill of that group matters. In WOTUK, the kernel is unnormalized, so a firm's size is also chosen by the model instead of being fixed in advance.
</p>
<p>
Let \(\mu\) be the distribution of firm types, \(\nu\) the distribution of worker types, and \(\Pi(\mu,\nu)\) the set of matchings whose two marginals are fixed. Classical optimal transport chooses the matching \(\pi\) that maximizes total production:
</p>
$$ \operatorname{OT}(\mu,\nu)
= \sup_{\pi \in \Pi(\mu,\nu)}
\int_{X \times Y} F(x,y)\,\mathrm{d}\pi(x,y). $$
<p>
Entropic OT adds a small entropy reward. It is easier and smoother to compute, but the economic meaning is still pairwise: firms mainly hire nearby worker types, with some blur around the best matches.
</p>
$$ \operatorname{EOT}_{\varepsilon}(\mu,\nu)
= \sup_{\pi \in \Pi(\mu,\nu)}
\left\{
\int F(x,y)\,\mathrm{d}\pi(x,y)
- \varepsilon \int \log\!\left(\frac{\mathrm{d}\pi}{\mathrm{d}\mu\,\mathrm{d}\nu}\right)\,\mathrm{d}\pi
\right\}. $$
<p>
Weak OT changes the object that enters production. Disintegrate the matching as \(\mathrm{d}\pi(x,y)=\mathrm{d}\mu(x)\,\mathrm{d}\pi_x(y)\). The measure \(\pi_x\) is the distribution of workers hired by firms of type \(x\). Production may now depend on the whole workforce:
</p>
$$ \operatorname{WOT}(\mu,\nu)
= \sup_{\pi \in \Pi(\mu,\nu)}
\int_X F(x,\pi_x)\,\mathrm{d}\mu(x). $$
<p>
In the barycentric case, \(F(x,\pi_x)\) only sees the average skill \( \int y\,\mathrm{d}\pi_x(y) \). In the demo this is
</p>
$$ F(x,\pi_x)
= \widetilde F\!\left(x,\int_Y y\,\mathrm{d}\pi_x(y)\right),
\qquad
\widetilde F(\alpha,z)
=
\left(\alpha_1 z_1^\rho + \alpha_2 z_2^\rho\right)^{1/\rho}. $$
<p>
This is what lets a generalist firm combine specialists in two skills and obtain the same aggregate skill as from a generalist worker.
</p>
<p>
WOTUK goes one step further. The kernel \(q_x\) is no longer normalized: its total mass \(q_x(Y)\) is the size of firms of type \(x\). The model still exhausts the worker population, but it no longer forces every firm type to have the same size.
</p>
$$ \operatorname{WOTUK}(\mu,\nu)
= \sup_{\substack{q_x \in \mathcal{M}_+(Y)\\
\int_X q_x\,\mathrm{d}\mu(x)=\nu}}
\int_X F(x,q_x)\,\mathrm{d}\mu(x),
\qquad
q_x(Y)=\text{firm size}. $$
<p>
The demo uses the corresponding conical WOTUK production: \(z=\int y\,\mathrm{d}q_x(y)\) is total skill rather than average skill, and the same CES formula is evaluated on that total skill vector.
</p>
$$ F(\alpha,q_x)
=
\left(
\alpha_1 \left(\int_Y y_1\,\mathrm{d}q_x(y)\right)^\rho
+
\alpha_2 \left(\int_Y y_2\,\mathrm{d}q_x(y)\right)^\rho
\right)^{1/\rho}. $$
<div class="demo-card">
<div class="econ-models" role="group" aria-label="Transport model">
<button class="econ-model active" data-model="ot">OT</button>
<button class="econ-model" data-model="eot">Entropic OT</button>
<button class="econ-model" data-model="wot">WOT</button>
<button class="econ-model" data-model="wotuk">WOTUK</button>
</div>
<div class="demo-canvas-wrap">
<canvas id="econ-canvas" width="900" height="620"></canvas>
</div>
<div class="demo-controls">
<label>worker population: specialists to generalists
<input type="range" id="econ-workers" min="0" max="1" step="0.01" value="0.35">
</label>
<label>firm specialization: mixed to specialized
<input type="range" id="econ-firms" min="0" max="1" step="0.01" value="0.70">
</label>
</div>
<div class="demo-legend">
<span><span class="swatch square" style="background:#123f4a"></span>strong match</span>
<span><span class="swatch square" style="background:#d7b46a"></span>worker distribution</span>
<span><span class="swatch square" style="background:#8f3b2d"></span>firm technology / size</span>
</div>
<div class="demo-readout">
<span>model: <b id="econ-model-name">OT</b></span>
<span>matching concentration: <b id="econ-concentration">…</b></span>
<span>firm-size spread: <b id="econ-size-spread">…</b></span>
</div>
</div>
<p class="demo-note" id="econ-explainer">
Each firm hires essentially one kind of worker: skill-1 firms take skill-1 specialists, skill-2 firms take skill-2 specialists. The matching is sharp and runs along the diagonal.
</p>
<p class="demo-note">
The heatmap is computed by the same primal mirror-ascent pattern as Algorithm 1 in the paper. OT and WOT use the multiplicative update \(P \leftarrow P \odot \exp(\gamma \nabla f(P))\), followed by a Sinkhorn KL projection onto \(\Pi(\mu,\nu)\). Entropic OT is a direct Sinkhorn solve with kernel \(\exp(F/\varepsilon)\). WOTUK uses the same multiplicative update, then the closed-form projection that rescales each worker column to the marginal \(\nu\), leaving firm sizes free.
</p>
</section>
</main>
</div>
</div>
<script src="javascripts/sidebar.js"></script>
<script src="javascripts/srw.js"></script>
<script src="javascripts/regularity.js"></script>
<script src="javascripts/adversarial.js"></script>
<script src="javascripts/economics.js"></script>
<script src="javascripts/demos-tabs.js"></script>
</body>
</html>