Monocurl Essays
Series Download

Quantifiers: for all, there exists, and negation

Everyone loves someone" has two meanings. Quantifiers separate them, show how to negate each, and turn the epsilon-delta definition of a limit into a game.

21 min·High school and early college·

AssumesTruth tables: and, or, not as calculationIf-then statements: converse and contrapositive

Two readings of “everyone loves someone” among four people. On the left each person loves somebody, a different somebody for different people. On the right there is one person whom all four love.
Monocurl source
let ROWY = [1.14, 0.38, -0.38, -1.14]
let CL = -2.05
let CR = 2.05
let People = |cx| [
    map(range(0, 4), |i| Person([cx - 1.2, ROWY[i], 0], i, AZURE)),
    map(range(0, 4), |i| Person([cx + 1.2, ROWY[i], 0], i, EMBER))
]
let Arrows = |cx, targets, k| block {
    for (i in range(0, 4)) {
        let f = clamp(0, k - i, 1)
        if (f > 0.2) {
            . Link([cx - 1.2, ROWY[i], 0], [cx + 1.2, ROWY[targets[i]], 0], INK, 3.5, f)
        }
    }
}
mesh people = [
    People(CL),
    People(CR),
    center{[0, 1.92, 0]} Cap("%"everyone loves someone%"", 0.85, INK),
    Seg([0, 1.45, 0], [0, -1.45, 0], MIST, 2)
]
mesh left = Arrows(CL, [1, 0, 3, 2], k: 0)
mesh right = Arrows(CR, [2, 2, 2, 2], k: 0)
mesh notes = []

slide "two"
    play Wait(0.5)
    left.k = 4
    play Lerp(2.4, [&left], linear)
    notes = [center{[CL, -1.88, 0]} Cap("each has a someone", 0.82, PLUM)]
    play Fade(0.4, [&notes])
    right.k = 4
    play Lerp(2.4, [&right], linear)
    notes = [
        center{[CL, -1.88, 0]} Cap("each has a someone", 0.82, PLUM),
        center{[CR, -1.88, 0]} Cap("one someone for all", 0.82, PLUM)
    ]
    play Fade(0.4, [&notes])
    play Wait(2.2)

Uses the shared definitions for this lesson's figures.

“Everyone loves someone.” Does that say each person has somebody they love, possibly a different somebody for each? Or does it say there is one particular person who is loved by all? English allows both, and the two claims are far apart: the first is true in most families, and the second is true of almost no group of people.

In conversation the ambiguity is harmless. In mathematics the same pair of readings sits inside the definition of a limit, and choosing the wrong one produces a definition that almost no function satisfies. So we need a notation that forces a choice, and rules for working with it.

This lesson leans on two earlier ones. From truth tables: a statement is a sentence that is true or false; $\land$, $\lor$, $\lnot$ mean “and”, “or”, “not”; and De Morgan’s laws say $\lnot(P \land Q) \equiv \lnot P \lor \lnot Q$ and $\lnot(P \lor Q) \equiv \lnot P \land \lnot Q$. From implication: $P \to Q$ is false only when $P$ is true and $Q$ is false, so it is true whenever $P$ is false, and its negation is $P \land \lnot Q$.

Predicates and the two quantifiers

The sentence “$x^2 < 30$” is not a statement, because it is neither true nor false until we know $x$. It becomes a statement when a value is put in: “$3^2 < 30$” is true and “$6^2 < 30$” is false. Such a sentence is called a predicate, and the values allowed for $x$ form its domain.

There is a second way to turn a predicate into a statement, which is to say how many values of the domain satisfy it. Take the domain $\{1, 2, 3, 4, 5, 6\}$ and check them one at a time.

The predicate $x^2 < 30$ tested on each element of the domain 1 to 6. It is true for five of them and false for 6. So “for all $x$” is false, with 6 as a counterexample, and “there exists $x$” is true, with 1 as a witness.
Monocurl source
let colx = |i| -2.05 + i * 1.02
let Column = |i| [
    Box([colx(i), 1.5, 0], [0.6, 0.6], SLATE, 0.12, 2.5),
    center{[colx(i), 1.5, 0]} Math(to_string(DOM[i]), 0.9, INK),
    center{[colx(i), 0.72, 0]} Math(to_string(DOM[i] * DOM[i]), 0.85, INK),
    TV([colx(i), -0.05, 0], P(DOM[i]), AZURE, 0.66, 0.5)
]
let bad = filter(DOM, |x| not P(x))
let good = filter(DOM, |x| P(x))
mesh labels = [
    center{[-3.3, 1.5, 0]} Math("x", 0.9, INK),
    center{[-3.3, 0.72, 0]} Math("x^2", 0.85, INK),
    center{[-3.3, -0.05, 0]} Math("x^2 < 30", 0.76, AZURE)
]
mesh cols = []
mesh verdict = []

slide "scan"
    play Wait(0.4)
    for (k in range(1, 7)) {
        cols = map(range(0, k), |i| Column(i))
        play Fade(0.4, [&cols])
        play Wait(0.35)
    }
    verdict = [
        FrameBox(colx(5) - 0.45, colx(5) + 0.45, -0.42, 1.92, ROSE),
        center{[0, -1.05, 0]} Math(str_join(["\forall x\; (x^2 < 30) \text{ is false: } x = ", to_string(bad[0]), "\text{ is a counterexample}"]), 0.76, ROSE),
        center{[0, -1.7, 0]} Math(str_join(["\exists x\; (x^2 < 30) \text{ is true: } x = ", to_string(good[0]), "\text{ is a witness}"]), 0.76, LEAF)
    ]
    play Fade(0.6, [&verdict])
    play Wait(2.2)

Uses the shared definitions for this lesson's figures.

Each clause matches the scan. One counterexample, $x = 6$, was enough to make the universal statement false, and five agreeing values could not save it. One witness was enough to make the existential statement true. On a finite domain a quantifier is a long connective: $\forall x\, P(x)$ is $P(1) \land P(2) \land \dots \land P(6)$, and $\exists x\, P(x)$ is $P(1) \lor P(2) \lor \dots \lor P(6)$.

The domain is part of the statement. “$\exists x\; (x^2 = 2)$” is false if $x$ ranges over the integers and true if it ranges over the real numbers. “$\forall x\; (x^2 \ge x)$” is true over the integers and false over the reals, where $x = \frac12$ is a counterexample. When the domain is not written, it must be clear from context.

Often we quantify over part of a domain, as in “every $x > 0$”. This is shorthand with a fixed meaning, and the two quantifiers unpack differently:

$$ \forall x > 0\; Q(x) \text{ means } \forall x\,(x > 0 \to Q(x)), \qquad \exists x > 0\; Q(x) \text{ means } \exists x\,(x > 0 \land Q(x)). $$

Pause and try

True or false? (a) $\forall x \in \mathbb{R}\; (x^2 > 0)$. (b) $\exists n \in \mathbb{Z}\; (n^2 = n)$. (c) “Every unicorn in this room is purple”, that is, $\forall x \in U\; \text{Purple}(x)$ where $U$ is the set of unicorns in the room, which is empty.

Show answerHide answer

(a) False: $x = 0$ is a counterexample. (b) True: $n = 1$ is a witness, and so is $n = 0$. (c) True. A universal statement is false only if there is a counterexample, and an empty domain contains none. This is the vacuous truth of the conditional again, since the claim is “for every $x$, if $x$ is a unicorn in this room then $x$ is purple” and the hypothesis is always false. Over an empty domain every $\forall$ statement is true and every $\exists$ statement is false.

How to say “no”

Someone claims that all swans are white. What must be the case for the claim to be false? A common first answer is “no swans are white”, and it overshoots badly. The claim is refuted by one black swan in a lake of white ones.

On the finite example this is De Morgan’s law at work on a long conjunction.

  1. For all x, P(x) is six statements joined by AND. One false piece makes the whole thing false.
  2. Negate it. By De Morgan’s law the NOT lands on every piece, flipping each value, and each AND becomes OR.
  3. The counterexample that made the universal statement false, x = 6, is the witness that makes the new statement true.
Negating “for all $x$, $P(x)$” on the domain 1 to 6. The universal statement is six statements joined by “and”. Negating it turns every “and” into “or” and negates each piece, which is the statement “there exists $x$ such that not $P(x)$”.
Monocurl source
let tx = |i| -2.9 + i * 1.0
let vals = map(DOM, |x| P(x))
let YA = 0.85
let YB = -0.95
let RowA = [
    center{[-0.4, 1.95, 0]} Math("\forall x\; P(x)", 0.9, AZURE),
    map(range(0, 6), |i| center{[tx(i), 1.42, 0]} Math(str_join(["P(", to_string(DOM[i]), ")"]), 0.72, SLATE)),
    map(range(0, 6), |i| TV([tx(i), YA, 0], vals[i], AZURE, 0.6)),
    map(range(0, 5), |i| center{[tx(i) + 0.5, YA, 0]} Math("\land", 0.8, INK)),
    center{[tx(5) + 0.62, YA, 0]} Math("=", 0.85, INK),
    TV([tx(5) + 1.22, YA, 0], all(vals), AZURE, 0.6)
]
let Moved = |t| map(range(0, 6), |i| TV([tx(i), lerp(YA, YB, t), 0], [vals[i], 1 - vals[i]][to_int(t > 0.5)], [AZURE, EMBER][to_int(t > 0.5)], 0.6))
let RowB = [
    center{[-0.4, 0.15, 0]} Math("\exists x\; \lnot P(x)", 0.9, EMBER),
    map(range(0, 6), |i| center{[tx(i), -0.38, 0]} Math(str_join(["\lnot P(", to_string(DOM[i]), ")"]), 0.72, SLATE)),
    map(range(0, 5), |i| center{[tx(i) + 0.5, YB, 0]} Math("\lor", 0.8, INK)),
    center{[tx(5) + 0.62, YB, 0]} Math("=", 0.85, INK),
    TV([tx(5) + 1.22, YB, 0], any(map(vals, |v| 1 - v)), EMBER, 0.6)
]
mesh moved = []
mesh row_a = []
mesh row_b = []
mesh marks = []

slide "For all x, P(x) is six statements joined by AND. One false piece makes the whole thing false."
    row_a = RowA
    play Fade(0.6)

slide "Negate it. By De Morgan's law the NOT lands on every piece, flipping each value, and each AND becomes OR."
    moved = Moved(t: 0)
    play Set([&moved])
    moved.t = 1
    play Lerp(1.4, [&moved])
    row_b = RowB
    play Fade(0.5, [&row_b])

slide "The counterexample that made the universal statement false, x = 6, is the witness that makes the new statement true."
    marks = [
        FrameBox(tx(5) - 0.37, tx(5) + 0.37, YA - 0.33, YA + 0.33, ROSE),
        FrameBox(tx(5) - 0.37, tx(5) + 0.37, YB - 0.33, YB + 0.33, LEAF)
    ]
    play Fade(0.5, [&marks])

Uses the shared definitions for this lesson's figures.

When $D$ is finite, with $n$ elements, these rules are De Morgan’s laws for $n$ statements, which follow from the two-statement laws by induction on $n$: peel off the last piece, apply the two-statement law, and use the case of $n - 1$ on the rest. When $D$ is infinite there is no table to check, and the argument from the definitions above is the whole proof. The rules are sometimes called the generalized De Morgan laws.

A restricted domain stays as it is under negation. For example $\lnot\,\forall x > 0\; Q(x)$ unpacks to $\lnot \forall x\,(x > 0 \to Q(x))$, which is $\exists x\, \lnot(x > 0 \to Q(x))$, which is $\exists x\,(x > 0 \land \lnot Q(x))$: there is a positive $x$ for which $Q$ fails. Only the quantifier and the predicate changed.

Pause and try

Negate each statement, and say which of the pair is true. (a) “Some prime number is even.” (b) $\forall x \in \mathbb{R}\; (x > 0 \to x^2 > x)$.

Show answerHide answer

(a) “Every prime number is odd.” The original is true, with witness 2, so the negation is false. (b) $\exists x \in \mathbb{R}\; (x > 0 \land x^2 \le x)$. Here the negation is true, with witness $x = \frac12$, since $\frac14 \le \frac12$. So the original is false.

Two quantifiers: the order matters

Now we can separate the two readings of the opening sentence. Let the domain be four people, A, B, C and D, and let $L(x, y)$ mean “$x$ loves $y$”. This predicate has two variables, and each needs a quantifier:

  • $\forall x\, \exists y\; L(x, y)$: for every person $x$ there is a person $y$ whom $x$ loves.
  • $\exists y\, \forall x\; L(x, y)$: there is a person $y$ whom every person $x$ loves.

To test them we need to know who loves whom. Here is one possibility.

  1. Who loves whom: an arrow from x on the left to y on the right means x loves y. There are six arrows.
  2. For all x there exists y: check that each person on the left has at least one arrow going out. All four do.
  3. There exists y for all x: look for one person on the right who receives an arrow from all four. D comes closest, with three, but D does not love D.
  4. Add one arrow, from D to D. Now D is loved by all four and the second statement is true as well.
A relation among four people, drawn as arrows from each person (left) to the people they love (right). Every person has an outgoing arrow, so the first statement is true. No person on the right receives arrows from all four, so the second is false, until one more arrow is added.
Monocurl source
let LX = -3.2
let RX = -0.2
let ROWY = [1.2, 0.3, -0.6, -1.5]
let EDGES = [[0, 1], [0, 3], [1, 0], [1, 3], [2, 3], [3, 2]]
# each arrow ends at its own point on the rim of y, higher on the rim when x is higher up
let Edge = |e, col, w = 3.5| {
    let a = [LX, ROWY[e[0]], 0]
    let th = PI - 0.42 * (e[1] - e[0])
    let t = [RX + 0.39 * cos(th), ROWY[e[1]] + 0.39 * sin(th), 0]
    let d = normalize(t - a)
    return Tip(a + d * 0.41, t, col, w, 0.15)
}
let outs = |x| filter(EDGES, |e| e[0] == x)
let ins = |y| filter(EDGES, |e| e[1] == y)
let first_out = map(range(0, 4), |x| outs(x)[0])
let isin = |e, es| len(filter(es, |g| g[0] == e[0] and g[1] == e[1])) > 0
let best = 3
let Words = |tex, verdict, col, note| [
    center{[2.3, 0.95, 0]} Math(tex, 0.85, INK),
    center{[2.3, 0.2, 0]} Cap(verdict, 0.9, col),
    center{[2.3, -0.5, 0]} Note(note, 0.76)
]
mesh edges = []
mesh people = [
    map(range(0, 4), |i| Person([LX, ROWY[i], 0], i, AZURE)),
    map(range(0, 4), |i| Person([RX, ROWY[i], 0], i, EMBER)),
    center{[LX, 1.9, 0]} Math("x", 0.9, AZURE),
    center{[RX, 1.9, 0]} Math("y", 0.9, EMBER),
    center{[(LX + RX) / 2, 1.9, 0]} Note("loves", 0.78)
]
mesh words = []

slide "Who loves whom: an arrow from x on the left to y on the right means x loves y. There are six arrows."
    edges = map(EDGES, |e| Edge(e, SLATE, 3))
    words = [
        center{[2.3, 0.6, 0]} Math("L(x, y)", 0.95, INK),
        center{[2.3, -0.1, 0]} Note("x loves y", 0.8)
    ]
    play Fade(0.6)

slide "For all x there exists y: check that each person on the left has at least one arrow going out. All four do."
    edges = []
    words = []
    play Fade(0.3)
    edges = [
        map(filter(EDGES, |e| not isin(e, first_out)), |e| Edge(e, MIST, 3)),
        map(first_out, |e| Edge(e, AZURE, 4.5))
    ]
    words = Words("\forall x\,\exists y\; L(x, y)", "true", LEAF, "an arrow out of every x")
    play Fade(0.5)

slide "There exists y for all x: look for one person on the right who receives an arrow from all four. D comes closest, with three, but D does not love D."
    edges = []
    words = []
    play Fade(0.3)
    edges = [
        map(filter(EDGES, |e| e[1] != best), |e| Edge(e, MIST, 3)),
        map(ins(best), |e| Edge(e, EMBER, 4.5)),
        stroke{ROSE, 4} dashed{[0.14, 0.1]} Line([LX + 0.44, ROWY[best], 0], [RX - 0.44, ROWY[best], 0])
    ]
    words = Words("\exists y\,\forall x\; L(x, y)", "false", ROSE, str_join([to_string(len(ins(best))), " of 4 arrows into D"]))
    play Fade(0.5)

slide "Add one arrow, from D to D. Now D is loved by all four and the second statement is true as well."
    edges = []
    words = []
    play Fade(0.3)
    edges = [
        map(filter(EDGES, |e| e[1] != best), |e| Edge(e, MIST, 3)),
        map(ins(best), |e| Edge(e, EMBER, 4.5)),
        Edge([best, best], EMBER, 4.5)
    ]
    words = Words("\exists y\,\forall x\; L(x, y)", "true", LEAF, "4 of 4 arrows into D")
    play Fade(0.5)

Uses the shared definitions for this lesson's figures.

With the six arrows of the first slide, $\forall x\, \exists y\; L(x, y)$ is true and $\exists y\, \forall x\; L(x, y)$ is false. The same relation, the same two quantifiers, and opposite truth values. So the order in which quantifiers are written is part of the meaning.

A grid makes the difference easy to see. Put $x$ down the side and $y$ across the top, and mark the cell $(x, y)$ when $L(x, y)$ is true.

The same relation as a grid, before and after the arrow from D to D is added. "For all x there exists y" asks for a mark in every row. "There exists y for all x" asks for a column that is completely marked, which is a stronger demand.
The same relation as a grid, before and after the arrow from D to D is added. “For all $x$ there exists $y$” asks for a mark in every row. “There exists $y$ for all $x$” asks for a column that is completely marked, which is a stronger demand.
Monocurl source
let EDGES = [[0, 1], [0, 3], [1, 0], [1, 3], [2, 3], [3, 2]]
let S = 0.62
let cell = |cx, x, y| [cx + (y - 1.5) * S, 0.3 - (x - 1.5) * S, 0]
let Grid4 = |cx, es, extra| [
    map(range(0, 4), |x| map(range(0, 4), |y| center{cell(cx, x, y)} fill{PAPER} stroke{SLATE, 2} Rect([S, S]))),
    map(es, |e| center{cell(cx, e[0], e[1])} fill{tint(AZURE, 0.75)} stroke{AZURE, 2} Rect([S - 0.14, S - 0.14])),
    map(extra, |e| center{cell(cx, e[0], e[1])} fill{tint(GOLD, 0.9)} stroke{GOLD, 2} Rect([S - 0.14, S - 0.14])),
    map(range(0, 4), |i| center{cell(cx, i, -1) + [0.1, 0, 0]} Cap(NAMES[i], 0.8, AZURE)),
    map(range(0, 4), |i| center{cell(cx, -1, i) + [0, -0.1, 0]} Cap(NAMES[i], 0.8, EMBER)),
    center{cell(cx, 1.5, -1) + [-0.38, 0, 0]} Math("x", 0.85, AZURE),
    center{cell(cx, -1, 4) + [-0.12, -0.1, 0]} Math("y", 0.85, EMBER)
]
let full = |es, y| len(filter(es, |e| e[1] == y)) == 4
let more = [[0, 1], [0, 3], [1, 0], [1, 3], [2, 3], [3, 2], [3, 3]]
mesh left = [
    Grid4(-1.75, EDGES, []),
    center{[-1.9, -1.4, 0]} Cap("a mark in every row", 0.78, LEAF),
    center{[-1.9, -1.85, 0]} Cap(["no full column", "a full column"][full(EDGES, 3)], 0.78, [ROSE, LEAF][full(EDGES, 3)])
]
mesh right = [
    Grid4(1.95, EDGES, [[3, 3]]),
    FrameBox(cell(1.95, 0, 3)[0] - S / 2, cell(1.95, 0, 3)[0] + S / 2, cell(1.95, 3, 3)[1] - S / 2, cell(1.95, 0, 3)[1] + S / 2, LEAF, 5.5),
    center{[1.8, -1.4, 0]} Cap("a mark in every row", 0.78, LEAF),
    center{[1.8, -1.85, 0]} Cap(["no full column", "column D is full"][full(more, 3)], 0.78, [ROSE, LEAF][full(more, 3)])
]

Uses the shared definitions for this lesson's figures.

A full column puts a mark in every row, but marks in every row need not line up in one column. That is the content of the next theorem.

The same thing with real numbers

Over the real numbers, compare $\forall x\, \exists y\; (y > x)$ with $\exists y\, \forall x\; (y > x)$. It helps to read them as a game between you and me, with the moves made in the order the quantifiers are written. A $\forall$ is your move: you pick the value, trying to make me fail. An $\exists$ is my move. Whoever moves later can see the earlier moves.

Two games on the number line. Top: you pick $x$, then I pick $y = x + 1$, which is always larger, so “for all $x$ there exists $y > x$” is true. Bottom: I must fix $y$ first, and then you move $x$ past it, so “there exists $y$ larger than all $x$” is false.
Monocurl source
let ux = |v| -3.3 + (v + 4) * 0.73
let NumLine = |y| [
    Seg([ux(-4.4), y, 0], [ux(5.4), y, 0], SLATE, 2.5),
    map(range(-4, 6), |v| [
        Seg([ux(v), y - 0.08, 0], [ux(v), y + 0.08, 0], SLATE, 2),
        center{[ux(v), y - 0.34, 0]} Math(to_string(v), 0.7, SLATE)
    ])
]
let Mark = |v, y, name, col| [
    Pt([ux(v), y, 0], col, 0.13),
    center{[ux(v), y + 0.4, 0]} Math(name, 0.9, col)
]
let YT = 0.7
let YB = -1.35
let FIX = 1
let Play = |x| [
    Mark(x, YT, "x", EMBER),
    Mark(x + 1, YT, "y", AZURE),
    Mark(x, YB, "x", EMBER),
    center{[1.75, -0.38, 0]} Cap(["x has passed y: I lose", "y > x so far"][to_int(x < FIX - 0.05)], 0.82, [ROSE, SLATE][to_int(x < FIX - 0.05)])
]
mesh lines = [
    NumLine(YT),
    NumLine(YB),
    center{[-2.05, 1.72, 0]} Math("\forall x\,\exists y\; (y > x)", 0.85, INK),
    center{[1.75, 1.72, 0]} Cap("y = x + 1: I always win", 0.82, LEAF),
    center{[-2.05, -0.38, 0]} Math("\exists y\,\forall x\; (y > x)", 0.85, INK),
    Mark(FIX, YB, "y", AZURE)
]
mesh game = Play(x: -3.2)

slide "play"
    play Wait(0.6)
    game.x = 3.4
    play Lerp(6.5, [&game])
    play Wait(1.8)

Uses the shared definitions for this lesson's figures.

In the first game I move second and answer your $x$ with $y = x + 1$. That is a winning strategy, so $\forall x\, \exists y\; (y > x)$ is true. In the second I must name one $y$ before you move, and you reply with $x = y$. To prove it false properly, negate it with the theorem, one quantifier at a time:

$$ \lnot\, \exists y\, \forall x\; (y > x) \;\equiv\; \forall y\, \lnot \forall x\; (y > x) \;\equiv\; \forall y\, \exists x\; (y \le x), $$

and this is true, with $x = y$ as the witness for each $y$.

The game is a way of reading the definitions and adds nothing to them: “for every $x$ there is a $y$” says exactly that whatever $x$ is chosen, a suitable $y$ can be found afterwards. One caution. Two quantifiers of the same kind may be swapped freely, since $\forall x\, \forall y$ and $\forall y\, \forall x$ both say “for all pairs”, and likewise for $\exists$. Only a $\forall$ next to an $\exists$ is sensitive to order.

The limit definition as a game

The best known sentence with three alternating quantifiers is the definition of a limit. Let $a$ be a real number and let $f$ be a function defined at every point of some open interval containing $a$, except possibly at $a$ itself. Then $\lim_{x \to a} f(x) = L$ means

$$ \forall \varepsilon > 0\;\; \exists \delta > 0\;\; \forall x\;\; \bigl(0 < |x - a| < \delta \;\to\; |f(x) - L| < \varepsilon\bigr). $$

The calculus lesson writes the last part as “$0 < |x - a| < \delta$ implies $|f(x) - L| < \varepsilon$”. The word “implies” there carries a silent “for every $x$”, which is the third quantifier, and $x$ ranges over the points where $f$ is defined.

As a game: you choose a tolerance $\varepsilon$. I answer with a distance $\delta$, which may depend on your $\varepsilon$. You then choose any $x$. I win if the conditional holds, which means that either your $x$ is not within $\delta$ of $a$ (or equals $a$), and the conditional is vacuously true, or else $f(x)$ is within $\varepsilon$ of $L$. The limit is $L$ exactly when I have a strategy that wins against every $\varepsilon$.

Take $f(x) = 2x + 1$, $a = 1$ and $L = 3$. Then $|f(x) - 3| = |2x - 2| = 2|x - 1|$, so my strategy is $\delta = \varepsilon / 2$: if $|x - 1| < \delta$ then $|f(x) - 3| < 2\delta = \varepsilon$.

The limit game for $f(x) = 2x + 1$ at $a = 1$ with $L = 3$. You shrink the horizontal band of half-height $\varepsilon$ around 3. The piece of the graph over my window sticks out of the band until I answer by shrinking the window to half-width $\delta = \varepsilon / 2$.
Monocurl source
let f = |x| 2 * x + 1
let fr = Frame(0, 2, 1, 5, [-1.15, -0.05, 0], 4.4, 3.4)
let Zones = |e, d| [
    Region([at(fr, 0, 3 - e), at(fr, 2, 3 - e), at(fr, 2, 3 + e), at(fr, 0, 3 + e)], tint(EMBER, 0.22)),
    Region([at(fr, 1 - d, 1), at(fr, 1 + d, 1), at(fr, 1 + d, 5), at(fr, 1 - d, 5)], tint(AZURE, 0.22)),
    Region([at(fr, 1 - d, 3 - e), at(fr, 1 + d, 3 - e), at(fr, 1 + d, 3 + e), at(fr, 1 - d, 3 + e)], tint(PLUM, 0.3))
]
let Piece = |e, d| {
    let ok = to_int(d <= e / 2 + 0.001)
    return [
        Seg(at(fr, 1 - d, f(1 - d)), at(fr, 1 + d, f(1 + d)), [ROSE, LEAF][ok], 6),
        Ring(at(fr, 1, 3), INK, 0.09, 3),
        center{[2.75, 0.85, 0]} scale{1.3} color{EMBER} Number(e, 2),
        center{[2.75, -0.45, 0]} scale{1.3} color{AZURE} Number(d, 2),
        center{[2.75, -1.35, 0]} Cap(["sticks out", "inside the band"][ok], 0.8, [ROSE, LEAF][ok])
    ]
}
mesh zones = Zones(e: 1, d: 0.5)
mesh plot = [
    GridLines(fr, [0.5, 1, 1.5], [2, 3, 4]),
    Axes(fr),
    XTicks(fr, [0.5, 1, 1.5], SLATE, 1),
    YTicks(fr, [2, 3, 4]),
    center{[fx(fr, 2) + 0.42, fy(fr, 1), 0]} Math("x", 0.8, SLATE),
    center{[fx(fr, 0) + 0.55, fy(fr, 5) + 0.12, 0]} Math("f(x)", 0.8, SLATE),
    Seg(at(fr, 0, 1), at(fr, 2, 5), SLATE, 3),
    center{[2.75, 1.35, 0]} Math("\text{you: } \varepsilon", 0.8, EMBER),
    center{[2.75, 0.05, 0]} Math("\text{me: } \delta", 0.8, AZURE)
]
mesh piece = Piece(e: 1, d: 0.5)

slide "game"
    play Wait(0.8)
    for (step in [0.5, 0.2]) {
        zones.e = step
        piece.e = step
        play Lerp(0.9, [&zones, &piece])
        play Wait(0.7)
        zones.d = step / 2
        piece.d = step / 2
        play Lerp(0.9, [&zones, &piece])
        play Wait(0.9)
    }
    play Wait(1.2)

Uses the shared definitions for this lesson's figures.

The order $\forall \varepsilon\, \exists \delta$ is the first reading of “everyone loves someone”: each $\varepsilon$ gets its own $\delta$. Swap the two and you get $\exists \delta > 0\; \forall \varepsilon > 0\; \forall x\,(\dots)$, which demands one window that works for every tolerance at once. That forces $|f(x) - L|$ to be smaller than every positive number, so $f(x) = L$ exactly throughout the window. Even $2x + 1$ fails this. The definition of a limit depends on the order of its quantifiers.

Pause and try

Write out the negation of the limit definition: what does it mean that $L$ is not the limit of $f(x)$ as $x \to a$? Remember that $\forall \varepsilon > 0$ and $\exists \delta > 0$ keep their restrictions, and that the negation of $A \to B$ is $A \land \lnot B$.

Show answerHide answer
$$ \exists \varepsilon > 0\;\; \forall \delta > 0\;\; \exists x\;\; \bigl(0 < |x - a| < \delta \;\land\; |f(x) - L| \ge \varepsilon\bigr).$$ There is a tolerance that cannot be met: however small a window is proposed, it contains a point $x \ne a$ at which $f(x)$ is at least $\varepsilon$ away from $L$. $$

In the negated game the roles are exchanged. Now I move first, with a single $\varepsilon$, and I must beat every $\delta$ you offer. Let $f(x) = 1$ for $x > 0$ and $f(x) = -1$ for $x < 0$, and suppose you claim that $\lim_{x \to 0} f(x) = 1$.

  1. The function is -1 to the left of 0 and 1 to the right. The claim is that its limit at 0 is 1.
  2. To refute the claim I move first, with tolerance 1. The band runs from 0 to 2, and the left half of the graph is outside it.
  3. You offer the window of half-width 1. I pick x = -0.5, inside the window, where the function is -1.
  4. You try a narrower window, 0.4. I pick x = -0.2. No window can work, because every window reaches a little to the left of 0.
Refuting the claim that a step function has limit 1 at 0. I choose the tolerance 1. Whatever window you offer, I pick the point halfway to its left edge, where the function is $-1$, which is a distance 2 from the claimed limit.
Monocurl source
let fr = Frame(-1.5, 1.5, -2, 2.5, [-1.15, -0.05, 0], 4.4, 3.5)
let EPS = 1
let Strip = |d| [
    Region([at(fr, 0 - d, -2), at(fr, d, -2), at(fr, d, 2.5), at(fr, 0 - d, 2.5)], tint(AZURE, 0.22)),
    Region([at(fr, 0 - d, 1 - EPS), at(fr, d, 1 - EPS), at(fr, d, 1 + EPS), at(fr, 0 - d, 1 + EPS)], tint(PLUM, 0.3))
]
let Bad = |d| [
    Seg(at(fr, 0 - d / 2, 0), at(fr, 0 - d / 2, -1), ROSE, 2.5),
    Pt(at(fr, 0 - d / 2, -1), ROSE, 0.12)
]
let Words = |d| [
    center{[2.8, 1.5, 0]} Math(str_join(["\varepsilon = ", to_string(EPS)]), 0.85, EMBER),
    center{[2.8, 0.8, 0]} Math(str_join(["\delta = ", to_string(d, 2)]), 0.85, AZURE),
    center{[2.8, 0.1, 0]} Math(str_join(["x = ", to_string(0 - d / 2, 3)]), 0.85, ROSE),
    center{[2.8, -0.75, 0]} Math("|f(x) - 1| = 2", 0.8, ROSE),
    center{[2.8, -1.3, 0]} Math(str_join(["2 \ge ", to_string(EPS)]), 0.8, ROSE)
]
mesh band = []
mesh strip = []
mesh plot = [
    GridLines(fr, [-1, 1], [-1, 1, 2]),
    Axes(fr, "x", ""),
    center{[fx(fr, 0) + 0.5, fy(fr, 2.5) + 0.12, 0]} Math("f(x)", 0.8, SLATE),
    XTicks(fr, [-1, 1]),
    YTicks(fr, [1, 2]),
    center{[fx(fr, 0) + 0.36, fy(fr, -1) - 0.32, 0]} Math("-1", 0.72, SLATE),
    Seg(at(fr, -1.5, -1), at(fr, 0, -1), INK, 4.5),
    Seg(at(fr, 0, 1), at(fr, 1.5, 1), INK, 4.5),
    Ring(at(fr, 0, -1), INK, 0.09, 3),
    Ring(at(fr, 0, 1), INK, 0.09, 3)
]
mesh bad = []
mesh words = []

slide "The function is -1 to the left of 0 and 1 to the right. The claim is that its limit at 0 is 1."
    words = [center{[2.8, 1.5, 0]} Math("\text{claim: } L = 1", 0.85, INK)]
    play Fade(0.6)

slide "To refute the claim I move first, with tolerance 1. The band runs from 0 to 2, and the left half of the graph is outside it."
    words = []
    play Fade(0.3, [&words])
    band = Region([at(fr, -1.5, 1 - EPS), at(fr, 1.5, 1 - EPS), at(fr, 1.5, 1 + EPS), at(fr, -1.5, 1 + EPS)], tint(EMBER, 0.22))
    words = [center{[2.8, 1.5, 0]} Math(str_join(["\varepsilon = ", to_string(EPS)]), 0.85, EMBER)]
    play Fade(0.6)

slide "You offer the window of half-width 1. I pick x = -0.5, inside the window, where the function is -1."
    words = []
    play Fade(0.3, [&words])
    strip = Strip(d: 1)
    bad = Bad(d: 1)
    words = Words(1)
    play Fade(0.6)

slide "You try a narrower window, 0.4. I pick x = -0.2. No window can work, because every window reaches a little to the left of 0."
    words = []
    play Fade(0.3, [&words])
    strip.d = 0.4
    bad.d = 0.4
    play Lerp(1.2, [&strip, &bad])
    words = Words(0.4)
    play Fade(0.4, [&words])

Uses the shared definitions for this lesson's figures.

Written as a proof: take $\varepsilon = 1$. Let $\delta > 0$ be arbitrary and put $x = -\delta/2$. Then $0 < |x - 0| = \delta/2 < \delta$, and $|f(x) - 1| = |-1 - 1| = 2 \ge 1$. This establishes $\exists \varepsilon\, \forall \delta\, \exists x\,(\dots)$, the negation of the claim. Notice the shape of the proof: one $\varepsilon$ exhibited, an arbitrary $\delta$ handled, and an $x$ produced that depends on $\delta$.

Worked examples

Common mistakes and edge cases

Two edge cases. Over an empty domain $\forall x\, P(x)$ is true and $\exists x\, P(x)$ is false, so “for all” does not imply “there exists” unless the domain has at least one element. And “there exists” means at least one: if you want exactly one, say “there exists a unique $x$”, which is the conjunction of $\exists x\, P(x)$ with $\forall x\, \forall y\,\bigl((P(x) \land P(y)) \to x = y\bigr)$.

Stepping back

The hard part was noticing that English hides two things a mathematical sentence must show: which variables are “all” and which are “some”, and in what order they are chosen. Once those are written down, truth is a matter of who can win a game, and negation is a mechanical exchange of the two players. Nearly every definition in analysis, including continuity, convergence and boundedness, is a short string of quantifiers, and reading one comes down to asking who moves when.

Check yourself

What is the negation of “every student has read some book on the list”?

Problems

Problems 1 to 3 repeat the moves of the lesson, 4 to 6 combine them, and 7 and 8 go further.

Problem 1

Decide whether each statement is true, giving a witness, a counterexample or a short reason. (a) $\forall n \in \mathbb{Z}\; (n^2 \ge n)$. (b) $\exists x \in \mathbb{R}\; (x^2 < x)$. (c) $\forall x \in \mathbb{R}\; \exists y \in \mathbb{R}\; (xy = 1)$. (d) $\exists y \in \mathbb{R}\; \forall x \in \mathbb{R}\; (xy = 0)$.

Show solutionHide solution

(a) True. If $n \le 0$ then $n^2 \ge 0 \ge n$. If $n \ge 1$ then $n^2 = n \cdot n \ge n \cdot 1 = n$. Every integer is in one of the two cases. (b) True, with witness $x = \frac12$: $\frac14 < \frac12$. (c) False: $x = 0$ is a counterexample, since $0 \cdot y = 0 \ne 1$ for every $y$. (d) True, with witness $y = 0$: $x \cdot 0 = 0$ for every $x$.

Problem 2

Negate each statement, moving the negation all the way in. (a) $\forall x \in \mathbb{R}\; (x^2 \ne 2)$. (b) $\exists n \in \mathbb{N}\; \forall m \in \mathbb{N}\; (m \le n)$. (c) $\forall x \in \mathbb{R}\; (x > 2 \to x^2 > 4)$. For (b), say which of the statement and its negation is true.

Show solutionHide solution

(a) $\exists x \in \mathbb{R}\; (x^2 = 2)$. (b) $\forall n \in \mathbb{N}\; \exists m \in \mathbb{N}\; (m > n)$. The original says there is a largest natural number. The negation is true, with witness $m = n + 1$ for each $n$, so the original is false. (c) $\exists x \in \mathbb{R}\; (x > 2 \land x^2 \le 4)$, using the fact that the negation of $A \to B$ is $A \land \lnot B$.

Problem 3

On the domain $\{A, B, C\}$ the relation $L$ consists of exactly the pairs $(A, B)$, $(B, B)$ and $(C, A)$. Decide the truth of $\forall x\, \exists y\; L(x, y)$, $\exists y\, \forall x\; L(x, y)$, $\exists x\, \forall y\; L(x, y)$ and $\forall y\, \exists x\; L(x, y)$.

Show solutionHide solution

Draw the grid with rows $x$ and columns $y$: row A has a mark in column B, row B in column B, row C in column A. (1) Every row has a mark, so $\forall x\, \exists y\; L$ is true. (2) A full column is needed: column B has marks from A and B but not C, and columns A and C have fewer. False. (3) A full row is needed, and every row has only one mark. False. (4) Every column needs a mark, and column C has none. False.

Problem 4

Consider “for every positive real number there is a smaller positive real number”. Write it with quantifiers and decide whether it is true. Then swap the two quantifiers, say in words what the new statement claims, and decide whether it is true.

Show solutionHide solution

$\forall x > 0\; \exists y > 0\; (y < x)$. True: given $x$, take $y = x/2$, which is positive and less than $x$. Swapped: $\exists y > 0\; \forall x > 0\; (y < x)$, “there is a positive real number smaller than every positive real number”. This is false. Its negation is $\forall y > 0\; \exists x > 0\; (y \ge x)$, which is true with $x = y$.

Problem 5

Play the limit game for $f(x) = 3x - 1$ at $a = 2$ with $L = 5$: give a strategy for choosing $\delta$ from $\varepsilon$ and prove that it wins.

Show solutionHide solution

We have $|f(x) - 5| = |3x - 6| = 3|x - 2|$. Strategy: given $\varepsilon > 0$, choose $\delta = \varepsilon / 3$. Now let $x$ be any number with $0 < |x - 2| < \delta$. Then $|f(x) - 5| = 3|x - 2| < 3\delta = \varepsilon$. The conditional holds for every $x$, so $\forall \varepsilon > 0\, \exists \delta > 0\, \forall x\,(\dots)$ is true and $\lim_{x \to 2}(3x - 1) = 5$. For instance, against $\varepsilon = 0.03$ the reply is $\delta = 0.01$.

Problem 6

“There are infinitely many primes” can be written $\forall n \in \mathbb{N}\; \exists p \in \mathbb{N}\; (p > n \land p \text{ is prime})$. Write its negation with the negation moved all the way in, and say in words what the negation claims.

Show solutionHide solution

Flip both quantifiers and negate the predicate with De Morgan’s law: $\exists n \in \mathbb{N}\; \forall p \in \mathbb{N}\; (p \le n \lor p \text{ is not prime})$. By the equivalence $\lnot A \lor B \equiv A \to B$, the bracket can also be written “$p$ is prime $\to p \le n$”. In words: there is a number $n$ such that every prime is at most $n$, that is, the primes are bounded above. (Euclid proved that this negation is false.)

Problem 7

Use the negated definition to prove that $\lim_{x \to 1} (2x + 1)$ is not $3.5$.

Show solutionHide solution

We need $\exists \varepsilon > 0\; \forall \delta > 0\; \exists x\; (0 < |x - 1| < \delta \land |2x + 1 - 3.5| \ge \varepsilon)$. Choose $\varepsilon = 0.25$. Let $\delta > 0$ be arbitrary, and pick any $x$ with $0 < |x - 1| < \delta$ and also $|x - 1| < 0.1$ (for instance $x = 1 + \min(\delta, 0.1)/2$). Then $|(2x + 1) - 3| = 2|x - 1| < 0.2$, so $2x + 1$ lies between $2.8$ and $3.2$, and its distance from $3.5$ is more than $0.3$, which is at least $0.25$. Both parts of the conjunction hold.

Problem 8

Let $f(x) = 1$ for $x > 0$ and $f(x) = -1$ for $x < 0$. The lesson showed that 1 is not the limit of $f$ at 0. Prove that no real number $L$ is: write the statement “$f$ has no limit at 0” with quantifiers, and prove it.

Show solutionHide solution

“$f$ has a limit at 0” is $\exists L\, \forall \varepsilon > 0\, \exists \delta > 0\, \forall x\,(0 < |x| < \delta \to |f(x) - L| < \varepsilon)$. Its negation is

$$ \forall L\;\; \exists \varepsilon > 0\;\; \forall \delta > 0\;\; \exists x\;\; \bigl(0 < |x| < \delta \land |f(x) - L| \ge \varepsilon\bigr). $$

Let $L$ be any real number and choose $\varepsilon = 1$. Let $\delta > 0$ be arbitrary. Consider the two points $x_1 = \delta/2$ and $x_2 = -\delta/2$; both satisfy $0 < |x| < \delta$, and $f(x_1) = 1$, $f(x_2) = -1$. If both $|1 - L| < 1$ and $|-1 - L| < 1$ held, the triangle inequality would give $2 = |1 - (-1)| \le |1 - L| + |L - (-1)| < 2$, which is impossible. So at least one of $|f(x_1) - L| \ge 1$, $|f(x_2) - L| \ge 1$ holds, and we choose $x$ to be that point. Notice the order of dependence: $\varepsilon$ did not depend on $L$ here, but $x$ depended on both $\delta$ and $L$, as the quantifiers allow.

Shared definitions used by this lesson's figures
# house kit: prepended to every essay figure after the std imports

# palette. AZURE and EMBER are the two actors, LEAF marks an answer, ROSE a problem
let PAPER = [1, 1, 1, 1]
let INK = [0.08, 0.09, 0.11, 1]
let SLATE = [0.40, 0.44, 0.50, 1]
let MIST = [0.85, 0.86, 0.88, 1]
let AZURE = [0.13, 0.42, 0.68, 1]
let EMBER = [0.89, 0.42, 0.12, 1]
let LEAF = [0.13, 0.55, 0.36, 1]
let ROSE = [0.78, 0.22, 0.24, 1]
let PLUM = [0.47, 0.30, 0.66, 1]
let GOLD = [0.85, 0.62, 0.08, 1]

background = PAPER

# an opaque pastel of col; use for fills so overlaps do not darken
let tint = |col, amount = 0.18| [
    1 - (1 - col[0]) * amount,
    1 - (1 - col[1]) * amount,
    1 - (1 - col[2]) * amount,
    1
]

# text: Cap for labels, Note for quiet labels, Math for typeset maths
let Cap = |txt, size = 0.75, col = INK| color{col} Text(txt, size, "Charter")
let Note = |txt, size = 0.65| color{SLATE} Text(txt, size, "Charter")
let Math = |tex, size = 0.9, col = INK| color{col} Tex(tex, size)

# a paper rectangle to sit behind text that crosses lines
let Plate = |pos, size| center{pos} fill{PAPER} stroke{alpha{0} PAPER, 0} Rect(size)

# shapes with fill and stroke both set, so no stray black appears
let Pt = |pos, col = INK, r = 0.075| center{pos} fill{col} stroke{col, 1} Circle(r, 48)
let Ring = |pos, col = INK, r = 0.1, w = 3| center{pos} fill{PAPER} stroke{col, w} Circle(r, 48)
let Seg = |a, b, col = INK, w = 3| stroke{col, w} Line(a, b)
let Path = |pts, col = INK, w = 3| stroke{col, w} Polyline(pts)
let Region = |pts, col| fill{col} stroke{alpha{0} col, 0} Polygon(pts)
let Outline = |pts, col = INK, w = 3| fill{alpha{0} PAPER} stroke{col, w} Polygon(pts)
let Shape = |pts, col, amount = 0.18, w = 3| fill{tint(col, amount)} stroke{col, w} Polygon(pts)
let Disc = |pos, r, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Circle(r, 96)
let Box = |pos, size, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Rect(size)
# a line from a to b ending in a small solid arrowhead
let Tip = |a, b, col = INK, w = 3, head = 0.17| {
    let d = normalize([b[0] - a[0], b[1] - a[1], 0])
    let n = [-d[1], d[0], 0]
    let base = [b[0] - d[0] * head, b[1] - d[1] * head, 0]
    return [
        stroke{col, w} Line(a, base),
        fill{col} stroke{col, 1} Polygon([
            b,
            [base[0] + n[0] * head * 0.42, base[1] + n[1] * head * 0.42, 0],
            [base[0] - n[0] * head * 0.42, base[1] - n[1] * head * 0.42, 0]
        ])
    ]
}

# plots. a frame is [x0, x1, y0, y1, centre, width, height]: a data window and the scene box it fills
let Frame = |x0, x1, y0, y1, c = [0, 0, 0], w = 6.4, h = 3.6| [x0, x1, y0, y1, c, w, h]
let fx = |fr, x| fr[4][0] + (x - (fr[0] + fr[1]) / 2) * fr[5] / (fr[1] - fr[0])
let fy = |fr, y| fr[4][1] + (y - (fr[2] + fr[3]) / 2) * fr[6] / (fr[3] - fr[2])
let at = |fr, x, y| [fx(fr, x), fy(fr, y), 0]

# axes through the data origin (or the nearest edge of the window), with optional names
let Axes = |fr, x_name = "", y_name = "", col = SLATE| {
    let ox = clamp(fr[0], 0, fr[1])
    let oy = clamp(fr[2], 0, fr[3])
    var parts = [
        Tip(at(fr, fr[0], oy), [fx(fr, fr[1]) + 0.25, fy(fr, oy), 0], col, 2.5, 0.15),
        Tip(at(fr, ox, fr[2]), [fx(fr, ox), fy(fr, fr[3]) + 0.25, 0], col, 2.5, 0.15)
    ]
    if (x_name != "") {
        parts .= center{[fx(fr, fr[1]) + 0.5, fy(fr, oy), 0]} Math(x_name, 0.8, col)
    }
    if (y_name != "") {
        parts .= center{[fx(fr, ox), fy(fr, fr[3]) + 0.5, 0]} Math(y_name, 0.8, col)
    }
    return parts
}

# tick marks with numbers at the listed data values
let XTicks = |fr, xs, col = SLATE, places = 0| block {
    let oy = fy(fr, clamp(fr[2], 0, fr[3]))
    for (x in xs) {
        . Seg([fx(fr, x), oy - 0.07, 0], [fx(fr, x), oy + 0.07, 0], col, 2)
        . center{[fx(fr, x), oy - 0.33, 0]} Math(to_string(x, places), 0.72, col)
    }
}
let YTicks = |fr, ys, col = SLATE, places = 0| block {
    let ox = fx(fr, clamp(fr[0], 0, fr[1]))
    for (y in ys) {
        . Seg([ox - 0.07, fy(fr, y), 0], [ox + 0.07, fy(fr, y), 0], col, 2)
        . center{[ox - 0.36, fy(fr, y), 0]} Math(to_string(y, places), 0.72, col)
    }
}

# faint grid lines at the listed data values
let GridLines = |fr, xs, ys, col = MIST| block {
    for (x in xs) {
        . Seg(at(fr, x, fr[2]), at(fr, x, fr[3]), col, 1.5)
    }
    for (y in ys) {
        . Seg(at(fr, fr[0], y), at(fr, fr[1], y), col, 1.5)
    }
}

# the graph of f over [a, b], drawn in the frame. values are clamped to the window
let Graph = |fr, f, a, b, col = AZURE, w = 4, n = 160|
    stroke{col, w} Polyline(map(sample(a, b, n), |x| at(fr, x, clamp(fr[2], f(x), fr[3]))))

# the region between the graph of f and the x axis over [a, b]
let AreaUnder = |fr, f, a, b, col = AZURE, amount = 0.2, n = 80| {
    var pts = [at(fr, a, 0)]
    for (x in sample(a, b, n)) {
        pts .= at(fr, x, clamp(fr[2], f(x), fr[3]))
    }
    pts .= at(fr, b, 0)
    return fill{tint(col, amount)} stroke{alpha{0} col, 0} Polygon(pts)
}

# a truth value tile: T is solid, F is hollow
let TV = |pos, v, col = AZURE, w = 0.7, h = 0.48, size = 0.8| [
    center{pos} fill{[PAPER, col][to_int(v)]} stroke{[SLATE, col][to_int(v)], 2.5} Rect([w, h]),
    center{pos} Cap(["F", "T"][to_int(v)], size, [SLATE, PAPER][to_int(v)])
]
let FrameBox = |x0, x1, y0, y1, col, w = 4.5| Outline([[x0, y0, 0], [x1, y0, 0], [x1, y1, 0], [x0, y1, 0]], col, w)
let NAMES = ["A", "B", "C", "D"]
let Person = |pos, i, col, r = 0.34| [
    Disc(pos, r, col, 0.14, 3.5),
    center{pos} Cap(NAMES[i], 0.95, col)
]
# an arrow between two discs, stopping at their rims; f is the fraction drawn
let Link = |a, b, col = SLATE, w = 3.5, f = 1, r = 0.34, head = 0.2| {
    let d = normalize([b[0] - a[0], b[1] - a[1], 0])
    let s = [a[0] + d[0] * (r + 0.07), a[1] + d[1] * (r + 0.07), 0]
    let e = [b[0] - d[0] * (r + 0.09), b[1] - d[1] * (r + 0.09), 0]
    return Tip(s, lerp(s, e, f), col, w, head)
}
# the running finite example: x in 1..6, P(x) is x^2 < 30
let DOM = [1, 2, 3, 4, 5, 6]
let P = |x| x * x < 30