Skip to content

Commit 95cfd37

Browse files
committed
fix: plot
1 parent 7e2b0b9 commit 95cfd37

2 files changed

Lines changed: 118 additions & 93 deletions

File tree

personal-website/optimal-tennis-match/index.html

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,9 @@ <h2 class="font-serif text-2xl">Example of a 2-point tiebreaker</h2>
6262
$$\mathbf{P}(p, T_2) = p^2 + p(1-p)p + (1-p)p^2 = 3p^2 - 2p^3.$$
6363
</p>
6464
<p>
65-
where we define $\mathbf{P}(p, T_2)$ to be the probability of Alex to win $T_2$. Plugging in $p=0.54$ like
66-
in Federer's case, for a 2-point tiebreaker, we get $\mathbf{P}(0.54, T_2) = 0.559872$. Being the match very
67-
short, the amplification is rather small: circa $2\%$. But for longer matches, we can expect to amplify it
68-
much more.
65+
Here, $\mathbf{P}(p, T_2)$ denotes the probability that Alex wins $T_2$. Plugging Federer&apos;s $p = 0.54$ into
66+
the formula gives $\mathbf{P}(0.54, T_2) = 0.559872$. In such a short match the amplification is only about
67+
$2\%$, but longer matches can magnify even a small advantage much more.
6968
</p>
7069
</section>
7170

@@ -119,18 +118,22 @@ <h2 class="font-serif text-2xl">N-point tiebreaker</h2>
119118
/>
120119
</div>
121120
<p class="text-center font-serif text-[15px] mt-2">
122-
<strong>Figure 1.</strong> Probability $\mathbf{P}(p, T)$ on the left and expected length $\mathbf{E}(p, T)$ on the right for N-point tiebreakers.
121+
<strong>Figure 1.</strong> Probability $\mathbf{P}(p, T)$ for N-point tiebreakers on the left and $\mathbf{P}(p, M)$ for the official tennis match on the right.
123122
</p>
124123
</div>
125124
</div>
126-
<p>In the plot in Figure 1 we can see how $\mathbf{P}(T; p)$ follows a "S" shape. On the right part of the plot, where $p &gt; 0.5$, </p> we observe that $\mathbf{P}(T; p) > p$, meaning that the advantage is amplified.
125+
<p>
126+
In Figure 1, the curve for $\mathbf{P}(T; p)$ has an "S" shape. On the right side where $p &gt; 0.5$, we observe that
127+
$\mathbf{P}(T; p) > p$, so the stronger player&apos;s advantage is amplified.
128+
</p>
127129
</section>
128130
<section class="space-y-3">
129131
<h2 class="font-serif text-2xl">Fairness</h2>
130132
<p>
131-
While all tiebreakers aplify the advantage of the stronger player, it is clear that $T_7$ will be more &quot;fair&quot; than $T_2$. To quantify this &quot;fairness&quot;, we look at the steepness of the line tangent to
132-
$\mathbf{P}(p, T)$ at $p = 0.5$, this is the dashed line in the left plot of Figure 1. When this line is very steep, it means that a small edge for Alex results
133-
in a large advantage. We call this steepness $\mathbf{F}(T)$.
133+
While all tiebreakers amplify the advantage of the stronger player, $T_7$ is clearly more &quot;fair&quot; than $T_2$.
134+
To quantify this &quot;fairness&quot; we look at the steepness of the tangent to $\mathbf{P}(p, T)$ at $p = 0.5$ (the dashed
135+
line in the left plot of Figure 1). A steeper line means that a small edge for Alex turns into a much larger advantage.
136+
We call that steepness $\mathbf{F}(T)$.
134137
</p>
135138
<p>
136139
Formally, $\mathbf{F}(T)$ is the derivative of $\mathbf{P}(p, T)$ evaluated at $p=0.5$. Intuitively, let
@@ -159,10 +162,15 @@ <h2 class="font-serif text-2xl">Match length</h2>
159162
<section class="space-y-3">
160163
<h2 class="font-serif text-2xl">The Tradeoff</h2>
161164
<p>
162-
While we can all agree that there is a tradeoff between match length and fairness, we would like to quantify it better. In Figure 2, we plot $\mathbf{E}(0.5, T)$ against $\mathbf{F}(T)$. All the dots are tiebreaker of different length, we can see that as the tiebreakers become longer (more up), they also become more fair (more right).
165+
While we can agree that match length trades off with fairness, we would like to measure this relationship more precisely.
166+
In Figure 2 we plot $\mathbf{E}(0.5, T)$ against $\mathbf{F}(T)$. Each dot corresponds to a tiebreaker of a different
167+
length: as the tiebreakers grow longer (higher on the axis) they also become fairer (further to the right).
163168
</p>
164169
<p>
165-
The question that now rises naturally is: how does the real tennis game structure compare, with bet of 3 sets, each set being 6 games, tiebreakers and so on? You can see it as the star in Figure 2. Surprisingly, it lies under the tiebreaker curve. This means, that a tiebreaker of 91 points, will be just as fair as a normal tennis match, but on average it would take a bit more to play, and a tiebreaker of 87 points will be just as long as a tennis match on average, but it will be less fair.
170+
The natural question is how the official tennis format compares: best-of-three sets with first-to-six games and
171+
tiebreakers. This format is shown as the star in Figure 2. Surprisingly, it falls below the tiebreaker curve,
172+
meaning a 91-point tiebreaker would be as fair as a normal tennis match but would take slightly longer, while an
173+
87-point tiebreaker would last about the same as a tennis match but be less fair.
166174
</p>
167175

168176
<div class="flex justify-center">
@@ -188,17 +196,22 @@ <h2 class="font-serif text-2xl">The Tradeoff</h2>
188196
</div>
189197
</div>
190198
<p>
191-
Before being faced with this result, I thought tiebreakers would have been more efficient than the official tennis format, maybe less exiting to watch, but for sure simpler. This motivated me to try to undestand what is that makes tennis matches better, and is there a tennis match which is even more efficient?
199+
Before seeing this result, I assumed tiebreakers would be more efficient than the official format—maybe less exciting
200+
to watch. That motivated me to understand what makes tennis matches better and whether there
201+
is an even more efficient structure.
192202
</p>
193203
</section>
194204
<section>
195205
<h2 class="font-serif text-2xl">The Optimal Tennis Match</h2>
196206
<p>
197-
After some thinkering, I came up with a match structure that is considerably more efficient. The match would work as follows. It starts with a score of $0$. If the first player wins the point, the score goes up by $1$, if the second one wins instead, it goes down by $1$. When the score reaches $N$, the first player won, when it reaches $-N$, the second one does.
207+
After some tinkering I devised a match structure that is significantly more efficient. It starts at a score of
208+
$0$. Whenever the first player wins a point the score increases by $1$, and if the second player wins it decreases
209+
by $1$. When the score reaches $N$, the first player wins; if it reaches $-N$, the second player wins.
198210
</p>
199211
<p>
200-
We will call this match structure $O_N$, where $N$ and $-N$ are the scores to be reached respectivly from the two players.
201-
Using our <a class="underline hover:no-underline" href="../probability-calculator/">Probability Calculator</a>, we observe a very satisfing fact:
212+
We call this match structure $O_N$, where $N$ and $-N$ are the target scores for the two players. Using our
213+
<a class="underline hover:no-underline" href="../probability-calculator/">Probability Calculator</a>, we observe a very
214+
satisfying fact:
202215
$$
203216
\begin{align*}
204217
\mathbf{F}(O_N) &= N, \\
@@ -213,10 +226,11 @@ <h2 class="font-serif text-2xl">The Optimal Tennis Match</h2>
213226
<section>
214227
<h2 class="font-serif text-2xl">The Conjecture</h2>
215228
<p>
216-
Even though the name $O_N$ is of course inspired by &quot;optimal&quot;, I never managed to prove this. I have a strong intuiton that there is a bound that cannot be beaten, and that such bound is achieved by $O_N$. Such bound is
217-
$$
218-
\mathbf{E}(T; 0.5) \geq \mathbf{F}(T)^2.
219-
$$
229+
Even though the name $O_N$ is inspired by &quot;optimal&quot;, I have not been able to prove it. I have a strong
230+
intuition that there is a lower bound that cannot be beaten and that $O_N$ achieves it:
231+
$$
232+
\mathbf{E}(T; 0.5) \geq \mathbf{F}(T)^2.
233+
$$
220234
</p>
221235
<p>
222236
Of course, I would love to either prove this conjecture, or to find a counterexample. If you have any cool ideas, you can use my <a class="underline hover:no-underline" href="../probability-calculator/">Probability Calculator</a> to test your ideas. If you find a counterexample, or a proof, please let me know! Contact me at <a class="underline hover:no-underline" href="mailto:marco.milanta@gmail.com">marco.milanta@gmail.com</a>.

personal-website/src/optimal-tennis-match.ts

Lines changed: 85 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Chart, registerables, type ScatterDataPoint } from 'chart.js';
22
import { computeGraphOrError } from './compute';
3-
import { linspace, probability_parallel, fairness, expectedLength as expectedLengthGraph } from './loadAlgo';
3+
import { linspace, probability_parallel, fairness } from './loadAlgo';
44
import precomputedPoints from './precomputed-points.json';
55

66
Chart.register(...registerables);
@@ -27,6 +27,65 @@ def play_fn(state, next_point: bool):
2727
return GameEnd.LOSE
2828
return state
2929
`
30+
const tennis_match = `s0 = (0, 0, 0, 0, 0, 0)
31+
32+
def play_fn(state, next_point: bool):
33+
pts1, pts2, g1, g2, s1, s2 = state
34+
35+
is_tb = (g1 == 6 and g2 == 6)
36+
if next_point:
37+
pts1 += 1
38+
else:
39+
pts2 += 1
40+
41+
game_won_by = 0 # 0 = ongoing, 1 = Player 1, 2 = Player 2
42+
43+
if is_tb:
44+
if pts1 >= 7 and pts1 - pts2 >= 2:
45+
game_won_by = 1
46+
elif pts2 >= 7 and pts2 - pts1 >= 2:
47+
game_won_by = 2
48+
elif pts1 >= 6 and pts2 >= 6 and pts1 == pts2:
49+
pts1, pts2 = 6, 6
50+
else:
51+
if pts1 >= 4 and pts1 - pts2 >= 2:
52+
game_won_by = 1
53+
elif pts2 >= 4 and pts2 - pts1 >= 2:
54+
game_won_by = 2
55+
elif pts1 >= 3 and pts2 >= 3 and pts1 == pts2:
56+
pts1, pts2 = 3, 3
57+
58+
if game_won_by != 0:
59+
pts1, pts2 = 0, 0 # Reset points for the new game
60+
61+
if game_won_by == 1:
62+
g1 += 1
63+
else:
64+
g2 += 1
65+
66+
set_won_by = 0
67+
68+
if (g1 >= 6 and g1 - g2 >= 2) or g1 == 7:
69+
set_won_by = 1
70+
elif (g2 >= 6 and g2 - g1 >= 2) or g2 == 7:
71+
set_won_by = 2
72+
73+
if set_won_by != 0:
74+
g1, g2 = 0, 0 # Reset games for the new set
75+
76+
if set_won_by == 1:
77+
s1 += 1
78+
else:
79+
s2 += 1
80+
81+
if s1 == 2:
82+
return GameEnd.WIN
83+
if s2 == 2:
84+
return GameEnd.LOSE
85+
86+
return (pts1, pts2, g1, g2, s1, s2)
87+
`
88+
3089

3190
document.addEventListener('DOMContentLoaded', () => {
3291
const canvasLeft = document.getElementById('tiebreaker_plot') as HTMLCanvasElement | null;
@@ -123,68 +182,6 @@ document.addEventListener('DOMContentLoaded', () => {
123182
},
124183
});
125184
}
126-
function makeExpectedLengthChart(canvas: HTMLCanvasElement) {
127-
return new Chart(canvas, {
128-
type: 'line',
129-
data: {
130-
datasets: [
131-
{
132-
label: 'data',
133-
data: [] as ScatterDataPoint[],
134-
borderColor: 'black',
135-
borderWidth: 2,
136-
},
137-
],
138-
},
139-
options: {
140-
responsive: true,
141-
aspectRatio: 1,
142-
animation: false,
143-
scales: {
144-
x: {
145-
type: 'linear',
146-
position: 'bottom',
147-
min: 0,
148-
max: 1,
149-
ticks: {
150-
stepSize: 0.25,
151-
autoSkip: false,
152-
callback: tickLabel,
153-
},
154-
title: {
155-
display: false,
156-
text: '',
157-
},
158-
},
159-
y: {
160-
type: 'linear',
161-
position: 'bottom',
162-
min: 0,
163-
max: 19,
164-
ticks: {
165-
stepSize: 1,
166-
autoSkip: false,
167-
callback: tickLabel,
168-
},
169-
title: {
170-
display: false,
171-
text: '',
172-
},
173-
},
174-
},
175-
elements: {
176-
point: {
177-
radius: 0,
178-
},
179-
},
180-
plugins: {
181-
legend: {
182-
display: false,
183-
},
184-
},
185-
},
186-
});
187-
}
188185
function makeTradeoffChart(canvas: HTMLCanvasElement) {
189186
const xMax = 12;
190187
const yMax = 250;
@@ -274,7 +271,7 @@ document.addEventListener('DOMContentLoaded', () => {
274271
});
275272
}
276273
const chartLeft = makeProbabilityChart(canvasLeft);
277-
const chartRight = makeExpectedLengthChart(canvasRight);
274+
const chartRight = makeProbabilityChart(canvasRight);
278275
const tradeoffChart = makeTradeoffChart(canvasTradeoff);
279276

280277
const graphByN = new Map<number, number[]>();
@@ -289,22 +286,13 @@ document.addEventListener('DOMContentLoaded', () => {
289286
{ x: 0.5 - 1 / (2 * fr), y: 0 },
290287
{ x: 0.5 + 1 / (2 * fr), y: 1 },
291288
];
292-
const expectedLengthData: ScatterDataPoint[] = ps.map((p) => ({
293-
x: p,
294-
y: expectedLengthGraph(graph, p),
295-
}));
296289
chartLeft.data.datasets[0].data = probData;
297290
chartLeft.data.datasets[1].data = fairnessLine;
298291
chartLeft.update();
299292

300-
chartRight.data.datasets[0].data = expectedLengthData;
301-
chartRight.update();
302-
303293
if (xLabelLeft) xLabelLeft.innerHTML = '\\(p\\)';
304294
if (yLabelLeft) yLabelLeft.innerHTML = `\\(\\mathbf{P}(p, T_{${n}})\\)`;
305-
if (xLabelRight) xLabelRight.innerHTML = '\\(p\\)';
306-
if (yLabelRight) yLabelRight.innerHTML = `\\(\\mathbf{E}(p, T_{${n}})\\)`;
307-
const labels = [xLabelLeft, yLabelLeft, xLabelRight, yLabelRight].filter(Boolean) as Element[];
295+
const labels = [xLabelLeft, yLabelLeft].filter(Boolean) as Element[];
308296
if (labels.length) void typesetMath(...labels);
309297
}
310298

@@ -333,6 +321,29 @@ document.addEventListener('DOMContentLoaded', () => {
333321
}
334322
if (slider) slider.disabled = false;
335323

324+
{
325+
const { result, error } = await computeGraphOrError(tennis_match);
326+
if (error || !result) {
327+
console.error('Failed to compute tennis match graph:', error);
328+
} else {
329+
const matchGraph = result as number[];
330+
const probData = probability_parallel(matchGraph, ps) as ScatterDataPoint[];
331+
const fr = fairness(matchGraph);
332+
const fairnessLine: ScatterDataPoint[] = [
333+
{ x: 0.5 - 1 / (2 * fr), y: 0 },
334+
{ x: 0.5 + 1 / (2 * fr), y: 1 },
335+
];
336+
chartRight.data.datasets[0].data = probData;
337+
chartRight.data.datasets[1].data = fairnessLine;
338+
chartRight.update();
339+
340+
if (xLabelRight) xLabelRight.innerHTML = '\\(p\\)';
341+
if (yLabelRight) yLabelRight.innerHTML = '\\(\\mathbf{P}(p, M)\\)';
342+
const labels = [xLabelRight, yLabelRight].filter(Boolean) as Element[];
343+
if (labels.length) void typesetMath(...labels);
344+
}
345+
}
346+
336347
if (tradeoffChart) {
337348
// Use precomputed points instead of computing them
338349
// Filter out the last two points which are real match data

0 commit comments

Comments
 (0)