Monocurl Essays
Series Download

Divergence and curl: reading a vector field

Two numbers at each point of a flow. Divergence is the net outflow from a tiny square per unit area, and curl is the circulation around it per unit area.

22 min·Early college·

AssumesThe gradient: which way is uphill?What is an integral? Adding up to get areaWhat is a vector? Arrows, lists of numbers, and adding them

A wind field: the arrow at each point is the velocity of the air there, and the blue particles are carried along by it. The wind is $\mathbf{W}(x, y) = (x^2 - 1,\ xy)$. Air streams away from the point $(1, 0)$ and toward the point $(-1, 0)$.
Monocurl source
let SD = seeds(WFRAME, 15, 9, 0)
let PATHS = paths_of(W, SD, 0.4, 18)
mesh arrows = Arrows(WFRAME, W, sample(-2, 2, 17), sample(-1, 1, 9))
mesh box = FieldBox(WFRAME, [-2, -1, 0, 1, 2], [-1, 0, 1])
mesh flow = Flow(WFRAME, PATHS, SD, u: 0, 0.4)

slide "blow"
    flow.u = 4.8
    play Lerp(9.6, [&flow], linear)

Uses the shared definitions for this lesson's figures.

A weather map covered in wind arrows shows where the air is going. A forecaster wants more from it: where is air piling up, and where is it spinning? Piled-up air has to rise, which makes clouds, and spinning air is a storm in the making.

Neither question can be answered by looking at one arrow. A single arrow says how fast the air moves at one point and in what direction, and that is all. Piling up and spinning are facts about how the arrows differ between nearby points, so we should expect derivatives. The plan is to invent two measurements that could be carried out with instruments, run them on a specific wind, and then find what they compute.

A field of arrows

Recall that the partial derivative $P_x$ is the derivative of $P$ in $x$ with $y$ held constant. Our running example is the wind in the animation above,

$$ \mathbf{W}(x, y) = (x^2 - 1,\ xy), $$

so $P = x^2 - 1$ is the eastward part of the velocity and $Q = xy$ the northward part. At $(2, 1)$ the wind is $(3, 2)$: strong, toward the east-northeast. At the origin it is $(-1, 0)$, due west. At $(1, 0)$ and $(-1, 0)$ it is $(0, 0)$, dead calm, and the particles show that the two calm points are quite different: air streams out of one and into the other.

Net flow out of a small square

To test for piling up at a place, fence off a small square there and keep accounts: how much air leaves through the fence, minus how much comes in. If more leaves than enters, the air inside is thinning out.

How much air crosses one side? Wind blowing along a fence does not cross it. Only the component of the wind perpendicular to the fence counts, and in one second the air crossing a side of length $\ell$ is that perpendicular component times $\ell$. This is the flux across the side. Where the wind varies along the side, we add up over short pieces, which is an integral.

Take the square with lower left corner $(1, 0.5)$ and side $0.5$.

  1. A square of side 0.5 with its lower left corner at (1, 0.5), fenced off in the wind.
  2. On the right side the wind blows outward at 1.25. Flux out: 1.25 × 0.5 = 0.625.
  3. On the left side x = 1, where the wind has no eastward part. Nothing crosses.
  4. Air leaves through the top, flux 0.625, and enters through the bottom, flux -0.3125.
  5. Net flow out of the square: 0.9375. Its area is 0.25, so the outflow per unit area is 3.75.
Bookkeeping for the flow out of a square of side $0.5$ with corner $(1, 0.5)$ in the wind $\mathbf{W}$. On each side only the component of the wind across the side is drawn, orange where it points out of the square and blue where it points in. The net outflow is $0.9375$.
Monocurl source
let fl = fluxes(1, 0.5, 0.5)
let corners = [[1.5, 0.5], [1, 0.5], [1, 1], [1, 0.5]]
let along = [[0, 1], [0, 1], [1, 0], [1, 0]]
let normal = [[1, 0], [-1, 0], [0, 1], [0, -1]]
let names = ["right side", "left side", "top", "bottom"]
let places = [3, 0, 3, 4]
let Row = |k| {
    var txt = to_string(fl[k], places[k])
    if (abs(fl[k]) < 0.00005) { txt = "0" }
    return center{[3.2, 1.55 - 0.5 * k, 0]} Math(txt, 0.8, INK)
}
let Heads = |title| block {
    . center{[2.75, 1.95, 0]} Cap(title, 0.72, INK)
    for (k in range(0, 4)) {
        . center{[2.3, 1.55 - 0.5 * k, 0]} Note(names[k], 0.72)
    }
}
let Side = |k| block {
    for (j in range(0, 5)) {
        let p = [corners[k][0] + along[k][0] * (0.05 + 0.1 * j), corners[k][1] + along[k][1] * (0.05 + 0.1 * j)]
        let v = W(p)
        let c = v[0] * normal[k][0] + v[1] * normal[k][1]
        if (abs(c) > 0.01) {
            var col = EMBER
            if (c < 0) { col = AZURE }
            . Tip(at(ZFRAME, p[0], p[1]), at(ZFRAME, p[0] + 0.11 * c * normal[k][0], p[1] + 0.11 * c * normal[k][1]), col, 3.5, 0.15)
        }
    }
}
mesh arrows = Arrows(ZFRAME, W, sample(0.6, 1.9, 14), sample(0.35, 1.15, 9), 0.3)
mesh box = FieldBox(ZFRAME, [1, 1.5], [0.5, 1])
mesh sq = Square(ZFRAME, 1, 0.5, 0.5)
mesh heads = Heads("flux out through")
mesh sides = []
mesh total = []

slide "A square of side 0.5 with its lower left corner at (1, 0.5), fenced off in the wind."
    play Fade(0.6)

slide "On the right side the wind blows outward at 1.25. Flux out: 1.25 × 0.5 = 0.625."
    sides = [Side(0)]
    total = [Row(0)]
    play Fade(0.6, [&sides, &total])

slide "On the left side x = 1, where the wind has no eastward part. Nothing crosses."
    sides .= Side(1)
    total .= Row(1)
    play Fade(0.6, [&sides, &total])

slide "Air leaves through the top, flux 0.625, and enters through the bottom, flux -0.3125."
    sides .= Side(2)
    sides .= Side(3)
    total .= Row(2)
    total .= Row(3)
    play Fade(0.6, [&sides, &total])

slide "Net flow out of the square: 0.9375. Its area is 0.25, so the outflow per unit area is 3.75."
    total .= [
        Seg([1.85, -0.25, 0], [3.6, -0.25, 0], SLATE, 2),
        center{[2.3, -0.6, 0]} Note("net", 0.72),
        center{[3.2, -0.6, 0]} Math(to_string(fl[0] + fl[1] + fl[2] + fl[3], 4), 0.8, EMBER),
        center{[2.25, -1.25, 0]} Note("per unit area", 0.72),
        center{[3.3, -1.25, 0]} Math(to_string((fl[0] + fl[1] + fl[2] + fl[3]) / 0.25, 2), 0.9, EMBER)
    ]
    play Fade(0.5, [&total])

Uses the shared definitions for this lesson's figures.

The accounts, side by side. On the right side, $x = 1.5$, the outward direction is east, and the eastward part of the wind is $P = 1.5^2 - 1 = 1.25$ all along it, so the flux out is $1.25 \times 0.5 = 0.625$. On the left side, $x = 1$, we have $P = 0$: nothing crosses. On the top, $y = 1$, outward is north and the northward part is $Q = x$, which varies, so the flux out is $\int_1^{1.5} x\,dx = 0.625$. On the bottom, $y = 0.5$, the wind’s northward part $Q = 0.5x$ carries air into the square, and the flux out is $-\int_1^{1.5} 0.5x\,dx = -0.3125$.

The total is $0.625 + 0 + 0.625 - 0.3125 = 0.9375$. More air leaves than enters. A bigger square would lose more, so to compare places fairly we divide by the area, $0.25$, and get an outflow of $3.75$ per unit area.

That number still depends on the size of our square. To get a property of the point $(1, 0.5)$ we do what the derivative does with an interval: shrink the square and look for a limit.

Pause and try

Repeat the bookkeeping for the square with the same corner $(1, 0.5)$ and side $0.1$. What is the net flux out per unit area? Guess the limit as the side shrinks to 0.

Show answerHide answer

Right side: $P = 1.1^2 - 1 = 0.21$, flux $0.021$. Left side: $0$. Top, at $y = 0.6$: $\int_1^{1.1} 0.6x\,dx = 0.6 \times 0.105 = 0.063$. Bottom, at $y = 0.5$: $-\int_1^{1.1} 0.5x\,dx = -0.0525$. Net: $0.0315$. The area is $0.01$, so the outflow per unit area is $3.15$. From $3.75$ at side $0.5$ to $3.15$ at side $0.1$, the natural guess for the limit is $3$.

Circulation around the same square

For spinning, picture a small wheel with paddles, held at one spot. Wind along the rim pushes the paddles round; wind across the rim does not. So the second measurement walks the same fence, counterclockwise, and adds up the component of the wind along the direction of travel, times the length traveled. This is the circulation around the square. Wind that helps the walker counts as positive and wind in the walker’s face as negative.

  1. Walk around the square counterclockwise, adding up the part of the wind that blows along the direction of travel.
  2. Bottom side, heading east with the wind: +0.2917. Right side, heading north with the wind: +0.5625.
  3. Top side, heading west against the wind: -0.2917. Left side, heading south against the wind: -0.375.
  4. Circulation: 0.1875. Per unit area: 0.75.
Bookkeeping for the circulation around the same square, walked counterclockwise. On each side only the component of the wind along the side is drawn, orange where it agrees with the direction of travel and blue where it opposes it. The total is $0.1875$.
Monocurl source
let cs = circs(1, 0.5, 0.5)
let corners = [[1, 0.5], [1.5, 0.5], [1.5, 1], [1, 1]]
let travel = [[1, 0], [0, 1], [-1, 0], [0, -1]]
let out = [[0, -1], [1, 0], [0, 1], [-1, 0]]
let names = ["bottom", "right side", "top", "left side"]
let places = [4, 4, 4, 3]
let Row = |k| center{[3.2, 1.55 - 0.5 * k, 0]} Math(to_string(cs[k], places[k]), 0.8, INK)
let Heads = |title| block {
    . center{[2.75, 1.95, 0]} Cap(title, 0.72, INK)
    for (k in range(0, 4)) {
        . center{[2.3, 1.55 - 0.5 * k, 0]} Note(names[k], 0.72)
    }
}
let Side = |k| block {
    for (j in range(0, 3)) {
        let p = [corners[k][0] + travel[k][0] * (0.09 + 0.16 * j), corners[k][1] + travel[k][1] * (0.09 + 0.16 * j)]
        let v = W([p[0] + 0.08 * travel[k][0], p[1] + 0.08 * travel[k][1]])
        let c = v[0] * travel[k][0] + v[1] * travel[k][1]
        var col = EMBER
        if (c < 0) { col = AZURE }
        let s = [p[0] + 0.045 * out[k][0] + 0.08 * travel[k][0], p[1] + 0.045 * out[k][1] + 0.08 * travel[k][1]]
        . Tip(at(ZFRAME, s[0] - 0.07 * c * travel[k][0], s[1] - 0.07 * c * travel[k][1]), at(ZFRAME, s[0] + 0.07 * c * travel[k][0], s[1] + 0.07 * c * travel[k][1]), col, 3.5, 0.15)
    }
}
let turn = block {
    let c = at(ZFRAME, 1.25, 0.75)
    . Path(map(sample(0.5, 5.2, 40), |t| [c[0] + 0.42 * cos(t), c[1] + 0.42 * sin(t), 0]), SLATE, 2.5)
    . Tip([c[0] + 0.42 * cos(5.2), c[1] + 0.42 * sin(5.2), 0], [c[0] + 0.42 * cos(5.2) - 0.15 * sin(5.2), c[1] + 0.42 * sin(5.2) + 0.15 * cos(5.2), 0], SLATE, 2.5, 0.14)
}
mesh arrows = Arrows(ZFRAME, W, sample(0.6, 1.9, 14), sample(0.35, 1.15, 9), 0.3)
mesh box = FieldBox(ZFRAME, [1, 1.5], [0.5, 1])
mesh sq = [Square(ZFRAME, 1, 0.5, 0.5), turn]
mesh heads = Heads("wind along the path")
mesh sides = []
mesh total = []

slide "Walk around the square counterclockwise, adding up the part of the wind that blows along the direction of travel."
    play Fade(0.6)

slide "Bottom side, heading east with the wind: +0.2917. Right side, heading north with the wind: +0.5625."
    sides = [Side(0), Side(1)]
    total = [Row(0), Row(1)]
    play Fade(0.6, [&sides, &total])

slide "Top side, heading west against the wind: -0.2917. Left side, heading south against the wind: -0.375."
    sides .= Side(2)
    sides .= Side(3)
    total .= Row(2)
    total .= Row(3)
    play Fade(0.6, [&sides, &total])

slide "Circulation: 0.1875. Per unit area: 0.75."
    total .= [
        Seg([1.85, -0.25, 0], [3.6, -0.25, 0], SLATE, 2),
        center{[2.3, -0.6, 0]} Note("total", 0.72),
        center{[3.2, -0.6, 0]} Math(to_string(cs[0] + cs[1] + cs[2] + cs[3], 4), 0.8, EMBER),
        center{[2.25, -1.25, 0]} Note("per unit area", 0.72),
        center{[3.3, -1.25, 0]} Math(to_string((cs[0] + cs[1] + cs[2] + cs[3]) / 0.25, 2), 0.9, EMBER)
    ]
    play Fade(0.5, [&total])

Uses the shared definitions for this lesson's figures.

Along the bottom we head east, and the wind’s eastward part is $P = x^2 - 1$: the contribution is $\int_1^{1.5} (x^2 - 1)\,dx = \frac{7}{24} \approx 0.2917$. Up the right side we head north, with $Q = 1.5y$ behind us: $\int_{0.5}^{1} 1.5y\,dy = 0.5625$. Along the top we head west, into the same eastward wind as on the bottom, which gives $-0.2917$. Down the left side we head south against $Q = y$: $-\int_{0.5}^{1} y\,dy = -0.375$.

The top and bottom cancel, and the right side beats the left: the circulation is $0.5625 - 0.375 = 0.1875$. It is positive, so a wheel here would be pushed counterclockwise, because the northward wind is stronger on its east side than on its west. Per unit area the circulation is $0.75$. With side $0.1$ the same bookkeeping gives $0.55$ per unit area.

The square shrinks toward its corner $(1, 0.5)$. The net flux out and the circulation both shrink to nothing, but each divided by the area of the square approaches a limit: $3$ for the flux and $0.5$ for the circulation.
Monocurl source
let Shrink = |h| {
    let fl = fluxes(1, 0.5, h)
    let cs = circs(1, 0.5, h)
    return [
        Square(ZFRAME, 1, 0.5, h),
        center{[2.75, 1.1, 0]} scale{1.15} color{INK} Number(h, 2),
        center{[2.75, 0.0, 0]} scale{1.15} color{EMBER} Number((fl[0] + fl[1] + fl[2] + fl[3]) / (h * h), 2),
        center{[2.75, -1.1, 0]} scale{1.15} color{PLUM} Number((cs[0] + cs[1] + cs[2] + cs[3]) / (h * h), 2)
    ]
}
mesh arrows = Arrows(ZFRAME, W, sample(0.6, 1.9, 14), sample(0.35, 1.15, 9), 0.3)
mesh box = FieldBox(ZFRAME, [1, 1.5], [0.5, 1])
mesh labels = [
    Pt(at(ZFRAME, 1, 0.5), INK, 0.07),
    center{[2.75, 1.55, 0]} Note("side of the square", 0.72),
    center{[2.75, 0.45, 0]} Note("flux ÷ area", 0.72),
    center{[2.75, -0.65, 0]} Note("circulation ÷ area", 0.72)
]
mesh shrink = Shrink(h: 0.5)

slide "shrink"
    play Wait(1)
    shrink.h = 0.01
    play Lerp(6, [&shrink], |u| (1 - 0.02 ^ u) / 0.98)
    play Wait(1.8)

Uses the shared definitions for this lesson's figures.

Formulas for the two limits

Shrinking squares by hand at every point is no way to read a map. We want formulas, and the bookkeeping shows where they come from. Take any continuously differentiable field $\mathbf{F} = (P, Q)$ and the square with corner $(a, b)$ and side $h$.

The right and left sides together contribute $\int_b^{b+h} \bigl(P(a + h, y) - P(a, y)\bigr)\,dy$ to the flux: what leaves on the right minus what enters on the left. The two sides are a distance $h$ apart, so to first order

$$ P(a + h, y) \approx P(a, y) + P_x(a, b)\,h, $$

and the integrand is about $P_x(a, b)\,h$. Integrating over a side of length $h$ gives $P_x(a, b)\,h^2$. In the same way the top and bottom give about $Q_y(a, b)\,h^2$. The factor $h^2$ is the area of the square, so the outflow per unit area should approach $P_x + Q_y$.

For the circulation, the right and left sides contribute $\int_b^{b+h} \bigl(Q(a + h, y) - Q(a, y)\bigr)\,dy \approx Q_x(a, b)\,h^2$, and the bottom and top contribute $\int_a^{a+h} \bigl(P(x, b) - P(x, b + h)\bigr)\,dx \approx -P_y(a, b)\,h^2$, with a minus sign because the top is walked backwards.

The four terms of $\Phi$ are the right, left, top and bottom sides, each with the component of $\mathbf{F}$ along the outward direction. The four terms of $\Gamma$ are the bottom, right, top and left sides in the order walked, each with the component along the direction of travel.

The proof is the first-order expansion made honest: the mean value theorem replaces “$\approx$” by an exact equation at a nearby point, and continuity of the partial derivatives says the nearby point may as well be $(a, b)$.

Pause and try

Compute $\operatorname{div} \mathbf{W}$ and $\operatorname{curl} \mathbf{W}$ for the wind $\mathbf{W} = (x^2 - 1, xy)$. Check them against the limits $3$ and $0.5$ found at $(1, 0.5)$. Then say where on the map air is thinning out, where it is piling up, and where a wheel would turn clockwise.

Show answerHide answer

$P_x = 2x$ and $Q_y = x$, so $\operatorname{div} \mathbf{W} = 3x$. $Q_x = y$ and $P_y = 0$, so $\operatorname{curl} \mathbf{W} = y$. At $(1, 0.5)$ these are $3$ and $0.5$, as measured. The divergence is positive in the whole eastern half of the map, $x > 0$, where air is thinning out, and negative in the western half, where it is piling up. The curl is negative in the southern half, $y < 0$, and there a wheel turns clockwise.

Notice what the formulas do not say. The air thins out everywhere east of the line $x = 0$, far from the calm point $(1, 0)$ that the particles seem to burst from, and a wheel is pushed round everywhere off the line $y = 0$, although no particle in the animation travels in a loop.

The paddle wheel

The curl has a mechanical reading. A small paddle wheel pinned at a point turns with an angular speed of half the curl there.

Three paddle wheels pinned in the wind $\mathbf{W}$, whose curl at $(x, y)$ is $y$. The wheel at $(0, 0.9)$ turns counterclockwise and the wheel at $(0, -0.9)$ clockwise, each at half the curl. The wheel at $(1.6, 0)$, in the fastest air of the three, does not turn.
Monocurl source
let SD = seeds(WFRAME, 15, 9, 400)
let PATHS = paths_of(W, SD, 0.4, 18)
let spots = [[0, 0.9], [0, -0.9], [1.6, 0]]
let Wheels = |u| map(spots, |s| Wheel(at(WFRAME, s[0], s[1]), 0.5 * s[1] * u, 0.3))
mesh arrows = Arrows(WFRAME, W, sample(-2, 2, 17), sample(-1, 1, 9))
mesh box = FieldBox(WFRAME, [-2, -1, 0, 1, 2], [-1, 0, 1])
mesh flow = Flow(WFRAME, PATHS, SD, u: 0, 0.4)
mesh wheels = Wheels(u: 0)
mesh tags = [
    Plate(at(WFRAME, 0.66, 0.9), [1.0, 0.36]),
    center{at(WFRAME, 0.66, 0.9)} Math("\text{curl } 0.9", 0.72, INK),
    Plate(at(WFRAME, 0.7, -0.9), [1.15, 0.36]),
    center{at(WFRAME, 0.7, -0.9)} Math("\text{curl } {-0.9}", 0.72, INK),
    Plate(at(WFRAME, 1.6, -0.45), [0.8, 0.36]),
    center{at(WFRAME, 1.6, -0.45)} Math("\text{curl } 0", 0.72, INK)
]

slide "spin"
    flow.u = 6
    wheels.u = 6
    play Lerp(10, [&flow, &wheels], linear)

Uses the shared definitions for this lesson's figures.

Here is why, as a sketch. For the field $\mathbf{F} = \omega(-y, x)$, in which the whole plane turns rigidly about the origin at angular speed $\omega$, the curl is $\omega + \omega = 2\omega$, so the claim holds exactly. In general, a wheel of small radius $\rho$ responds to the average of the wind’s component along its rim. That average is the circulation around the rim divided by the rim’s length $2\pi\rho$. The circulation is about the curl times the enclosed area $\pi\rho^2$, so the rim speed is about $\operatorname{curl} \cdot \rho/2$, and dividing by the radius gives an angular speed of half the curl. Two things are missing from this argument: we proved the circulation limit for squares, not circles (the next lesson covers every shape at once), and “responds to the average” is a physical model of a wheel, not a theorem.

Four fields classified by computing

Four simple fields are worth knowing by heart, because they show each effect on its own.

Pause and try

In the shear field $(y, 0)$ every particle moves in a straight horizontal line, faster the farther it is from the $x$-axis. A square of dye and a paddle wheel are placed at the origin. From the two numbers just computed, predict what happens to the area of the dye and whether the wheel turns. Then explain the wheel’s behavior directly from the arrows.

Show answerHide answer

The divergence is $0$, so there is no net flow out of any small square and the dye keeps its area, although its shape is sheared into a parallelogram. The curl is $-1$, so the wheel turns clockwise, at angular speed $\frac12$. Directly: the wind above the wheel blows east and the wind below it blows west, and both push the wheel clockwise.

A square of dye and a paddle wheel in each of four fields: the source $(x, y)$, the sink $(-x, -y)$, the shear $(y, 0)$ and the rotation $(-y, x)$. In the source the dye spreads and in the sink it shrinks; the wheel stays still in both. In the shear the dye keeps its area but the wheel turns clockwise. In the rotation the dye and the wheel turn together.
Monocurl source
let fields = [|p| [p[0], p[1]], |p| [-p[0], -p[1]], |p| [p[1], 0], |p| [-p[1], p[0]]]
let centers = [[-1.85, 0.9, 0], [1.85, 0.9, 0], [-1.85, -1.2, 0], [1.85, -1.2, 0]]
let names = ["source", "sink", "shear", "rotation"]
let nums = ["\text{div } 2,\ \text{curl } 0", "\text{div } {-2},\ \text{curl } 0", "\text{div } 0,\ \text{curl } {-1}", "\text{div } 0,\ \text{curl } 2"]
let spin = [0, 0, -0.5, 1]
let frames = map(centers, |c| Frame(-1.9, 1.9, -0.9, 0.9, c, 3.3, 1.563))
let SD = seeds(frames[0], 9, 5, 900)
let PATHS = map(fields, |F| paths_of(F, SD, 0.5, 10))
# where the point p of the dye is at time t
let carried = |k, p, t| {
    if (k == 0) { return [p[0] * exp(t), p[1] * exp(t)] }
    if (k == 1) { return [p[0] * exp(-t), p[1] * exp(-t)] }
    if (k == 2) { return [p[0] + p[1] * t, p[1]] }
    return [p[0] * cos(t) - p[1] * sin(t), p[0] * sin(t) + p[1] * cos(t)]
}
let DYE = [[-0.3, -0.3], [0.3, -0.3], [0.3, 0.3], [-0.3, 0.3]]
let Panels = |t| block {
    for (k in range(0, 4)) {
        . Flow(frames[k], PATHS[k], SD, t, 0.5)
        . Shape(map(DYE, |p| {
            let q = carried(k, p, t)
            return at(frames[k], q[0], q[1])
        }), EMBER, 0.35, 2.5)
        . Wheel(at(frames[k], 0, 0), spin[k] * t, 0.19)
    }
}
mesh back = block {
    for (k in range(0, 4)) {
        . Arrows(frames[k], fields[k], sample(-1.6, 1.6, 9), sample(-0.7, 0.7, 5), 0.3)
        . Outline([at(frames[k], -1.9, -0.9), at(frames[k], 1.9, -0.9), at(frames[k], 1.9, 0.9), at(frames[k], -1.9, 0.9)], SLATE, 2)
        . center{[centers[k][0] - 1.0, centers[k][1] + 0.99, 0]} Cap(names[k], 0.82, INK)
        . center{[centers[k][0] + 0.65, centers[k][1] + 0.99, 0]} Math(nums[k], 0.8, SLATE)
    }
}
mesh panels = Panels(t: 0)

slide "carry"
    play Wait(0.8)
    panels.t = 1
    play Lerp(7, [&panels], linear)
    play Wait(1.5)

Uses the shared definitions for this lesson's figures.

The dye makes the meaning of divergence visible: where the divergence is positive, a blob carried by the flow grows, and where it is negative it shrinks. (The exact statement is that the area of a blob changes at a rate equal to the integral of the divergence over it, which for a small blob is the divergence times its area. We do not prove it here; a problem below checks a case.)

What the eye gets wrong

Both quantities are easy to misjudge from a picture.

Two flows in which every particle travels in a circle. In rigid rotation $(-y, x)$, with curl 2, the paddle wheel turns as it is carried round, keeping the same paddle toward the center. In the whirlpool $\mathbf{V}$, with curl 0, the wheel is carried round without turning.
Monocurl source
let fa = Frame(-1.6, 1.6, -1.6, 1.6, [-1.9, -0.2, 0], 3.3, 3.3)
let fb = Frame(-1.6, 1.6, -1.6, 1.6, [1.9, -0.2, 0], 3.3, 3.3)
let rigid = |p| [-p[1], p[0]]
let whirl = |p| [-p[1] / (p[0] * p[0] + p[1] * p[1]), p[0] / (p[0] * p[0] + p[1] * p[1])]
let radii = [0.45, 0.72, 1.0, 1.28, 1.52]
let Orbit = |fr, t, rate| block {
    for (i in range(0, 5)) {
        for (j in range(0, 3 + 2 * i)) {
            let r = radii[i]
            let w = rate(r)
            let ang = TAU * (j + rnd(7 * i + j, 5)) / (3 + 2 * i) + w * t
            . Path(map(sample(ang - min(0.22 * w, 1.1), ang, 12), |a| at(fr, r * cos(a), r * sin(a))), AZURE, 2.5)
            . Pt(at(fr, r * cos(ang), r * sin(ang)), AZURE, 0.04)
        }
    }
}
let Both = |t| [
    Orbit(fa, t, |r| 1),
    Orbit(fb, t, |r| 1 / (r * r)),
    Wheel(at(fa, cos(t), sin(t)), t + PI, 0.27),
    Wheel(at(fb, cos(t), sin(t)), PI, 0.27)
]
let grid = [-1.4, -1.05, -0.7, -0.35, 0, 0.35, 0.7, 1.05, 1.4]
mesh back = [
    Arrows(fa, rigid, grid, grid, 0.3),
    Arrows(fb, |p| {
        if (p[0] * p[0] + p[1] * p[1] < 0.1) { return [0, 0] }
        return whirl(p)
    }, grid, grid, 0.3),
    Outline([at(fa, -1.6, -1.6), at(fa, 1.6, -1.6), at(fa, 1.6, 1.6), at(fa, -1.6, 1.6)], SLATE, 2),
    Outline([at(fb, -1.6, -1.6), at(fb, 1.6, -1.6), at(fb, 1.6, 1.6), at(fb, -1.6, 1.6)], SLATE, 2),
    center{[-1.9, 1.75, 0]} Cap("rigid rotation: curl 2", 0.72, INK),
    center{[1.9, 1.75, 0]} Cap("whirlpool: curl 0", 0.72, INK)
]
mesh both = Both(t: 0)

slide "orbit"
    both.t = TAU
    play Lerp(10, [&both], linear)

Uses the shared definitions for this lesson's figures.

The whirlpool also marks a limit of the theorem. Walk once around the circle of radius $R$ centered at the origin: the wind is behind you the whole way at speed $\frac1R$, over a distance $2\pi R$, so the circulation is $2\pi$, however small $R$ is. Divided by the area $\pi R^2$ it does not tend to the curl, $0$; it blows up. There is no contradiction, because the theorem requires $\mathbf{F}$ to be continuously differentiable on an open set containing the point, and $\mathbf{V}$ is not even defined at the origin. Divergence and curl describe a field near points where it is smooth, and say nothing about what happens at a point where it is not.

Check yourself

For the field $\mathbf{F}(x, y) = (y^2, x)$, what is the curl at the point $(1, 3)$?

Step back. Both questions about the wind map became measurements on a small square: add up the wind across its sides, or along them. Each measurement shrinks to nothing with the square, but its ratio to the area has a limit, and the mean value theorem turns that limit into a combination of partial derivatives. The hard part was the bookkeeping of signs on the four sides, and the reusable idea is that a quantity defined by a boundary sum has a density, per unit area, at each point. The next lesson runs this in reverse: adding up the density over a region recovers the boundary sum.

Problems

Problems 1 and 2 are warm-ups, 3 to 5 combine ideas, and 6 and 7 go further.

Problem 1

Find the divergence and curl of $\mathbf{F}(x, y) = (x^2 y,\ x - y^3)$ at the point $(2, 1)$.

Show solutionHide solution

$P = x^2 y$ gives $P_x = 2xy$ and $P_y = x^2$. $Q = x - y^3$ gives $Q_x = 1$ and $Q_y = -3y^2$. So $\operatorname{div} \mathbf{F} = 2xy - 3y^2$ and $\operatorname{curl} \mathbf{F} = 1 - x^2$. At $(2, 1)$: divergence $4 - 3 = 1$, curl $1 - 4 = -3$. Air is thinning out slightly there, and a wheel turns clockwise at angular speed $1.5$.

Problem 2

For the “traffic” field $\mathbf{F}(x, y) = (x^2, 0)$, compute the flux out of the square with corner $(a, b)$ and side $h$ directly from the definition, divide by the area, and take the limit. Compare with the formula for the divergence.

Show solutionHide solution

Since $Q = 0$, the top and bottom contribute nothing. On the right side $P = (a + h)^2$ and on the left $P = a^2$, both constant along the side, so $\Phi(h) = (a + h)^2 h - a^2 h = (2ah + h^2)\,h$. Dividing by $h^2$ gives $2a + h$, with limit $2a$. The formula gives $\operatorname{div} \mathbf{F} = P_x = 2x$, which is $2a$ at $(a, b)$. For $a > 0$ all the traffic moves east and still thins out, because it leaves the square faster than it enters.

Problem 3

Let $\mathbf{F}(x, y) = (\alpha x + \beta y,\ \gamma x + \delta y)$, where $\alpha, \beta, \gamma, \delta$ are constants. (a) Find its divergence and curl. (b) Find all such fields with divergence 0 and curl 0, and describe the one with $\alpha = 1$, $\beta = 0$.

Show solutionHide solution

(a) $P_x = \alpha$, $Q_y = \delta$, $Q_x = \gamma$, $P_y = \beta$. So $\operatorname{div} \mathbf{F} = \alpha + \delta$ and $\operatorname{curl} \mathbf{F} = \gamma - \beta$, both constant. (b) We need $\delta = -\alpha$ and $\gamma = \beta$, so $\mathbf{F} = (\alpha x + \beta y,\ \beta x - \alpha y)$. With $\alpha = 1$, $\beta = 0$ this is $(x, -y)$: air moves away from the $y$-axis and toward the $x$-axis. A blob of dye is stretched east-west and squeezed north-south by the same factor, so its area stays fixed (divergence 0), and the stretching is symmetric, with nothing to turn a wheel (curl 0). So there is a fifth basic field besides source, sink, shear and rotation: pure strain.

Problem 4

The wind $\mathbf{W} = (x^2 - 1, xy)$ has divergence $3x$. Compute the flux of $\mathbf{W}$ out of the square with corner $(a, b)$ and side $h$ exactly, and confirm that the flux per unit area is $3a + \frac32 h$. Which values in the lesson does this reproduce?

Show solutionHide solution

Right minus left: $P$ is constant along each of these sides, so the contribution is $\bigl((a + h)^2 - 1\bigr)h - (a^2 - 1)h = (2ah + h^2)h$. Top minus bottom: $\int_a^{a+h} x(b + h)\,dx - \int_a^{a+h} xb\,dx = h \int_a^{a+h} x\,dx = h \cdot \frac{(a + h)^2 - a^2}{2} = h\left(ah + \frac{h^2}{2}\right)$. The sum is $h^2\left(3a + \frac32 h\right)$, and dividing by $h^2$ gives $3a + \frac32 h$, with limit $3a$ as $h \to 0$. With $a = 1$: $h = 0.5$ gives $3.75$ and $h = 0.1$ gives $3.15$, the two values found by bookkeeping. The answer does not involve $b$.

Problem 5

Let $f$ be a function of two variables whose second partial derivatives exist and are continuous. Show that $\operatorname{curl} \nabla f = 0$. Then decide whether the rotation field $(-y, x)$ is the gradient of some function. (You may use Clairaut’s theorem: for such $f$, the mixed partial derivatives are equal, $f_{xy} = f_{yx}$.)

Show solutionHide solution

The gradient is $\nabla f = (f_x, f_y)$, so $P = f_x$ and $Q = f_y$. Then $Q_x = f_{yx}$ and $P_y = f_{xy}$, and $\operatorname{curl} \nabla f = f_{yx} - f_{xy} = 0$ by Clairaut’s theorem. The rotation field has curl $2 \ne 0$. If it were $\nabla f$, then $f_x = -y$ and $f_y = x$ would have continuous partial derivatives, so $f$ would satisfy the hypotheses and the curl would have to be 0. So $(-y, x)$ is not a gradient: no hill has uphill arrows that circle round.

Problem 6

Let $\mathbf{E}(x, y) = \left(\dfrac{x}{x^2 + y^2},\ \dfrac{y}{x^2 + y^2}\right)$ for $(x, y) \ne (0, 0)$, which points straight away from the origin with speed $\frac1r$. (a) Show that $\operatorname{div} \mathbf{E} = 0$ away from the origin. (b) Find the flux of $\mathbf{E}$ out of the circle of radius $R$ centered at the origin, using the fact that $\mathbf{E}$ is perpendicular to the circle. (c) Reconcile (a) and (b).

Show solutionHide solution

(a) $P_x = \dfrac{(x^2 + y^2) - 2x^2}{(x^2 + y^2)^2} = \dfrac{y^2 - x^2}{(x^2 + y^2)^2}$, and by the same computation with the letters exchanged, $Q_y = \dfrac{x^2 - y^2}{(x^2 + y^2)^2}$. Their sum is 0. (b) On the circle the field points along the outward direction with length $\frac1R$, so the flux is $\frac1R$ times the length of the circle: $\frac1R \cdot 2\pi R = 2\pi$, the same for every $R$. (c) Zero divergence says that any small square not containing the origin has no net outflow. The circle encloses the origin, where $\mathbf{E}$ is not defined and the theorem does not apply. All of the outflow $2\pi$ is produced at that single point: $\mathbf{E}$ is the field of a point source, and the same amount of air crosses every circle around it because none is created or destroyed in between.

Problem 7

Let $\mathbf{F}(x, y) = (\alpha x, \delta y)$ with constants $\alpha$ and $\delta$. A particle carried by this flow from $(x_0, y_0)$ is at $(x_0 e^{\alpha t}, y_0 e^{\delta t})$ at time $t$. (a) Verify this by differentiating. (b) A rectangle of dye with sides parallel to the axes has area $A_0$ at time 0. Find its area $A(t)$, and show that $\dfrac{A'(t)}{A(t)} = \operatorname{div} \mathbf{F}$.

Show solutionHide solution

(a) The velocity of the particle is $\left(\alpha x_0 e^{\alpha t},\ \delta y_0 e^{\delta t}\right) = (\alpha x, \delta y)$ at its current position $(x, y)$, which is $\mathbf{F}$ there; and at $t = 0$ it is at $(x_0, y_0)$. (b) Every $x$-coordinate is multiplied by $e^{\alpha t}$ and every $y$-coordinate by $e^{\delta t}$, so the rectangle remains a rectangle with its width multiplied by $e^{\alpha t}$ and its height by $e^{\delta t}$. Hence $A(t) = A_0\,e^{(\alpha + \delta)t}$, and $A'(t) = (\alpha + \delta)A(t)$. So $\dfrac{A'(t)}{A(t)} = \alpha + \delta = \operatorname{div} \mathbf{F}$: the area of the dye grows at a rate, per unit area, equal to the divergence. With $\alpha = \delta = 1$ this is the source, whose dye grows like $e^{2t}$; with $\delta = -\alpha$ it is the strain field of Problem 3, whose dye keeps its area.

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)
}

# the running wind field
let W = |p| [p[0] * p[0] - 1, p[0] * p[1]]
let rnd = |i, salt| mod(seeded_random(i, salt) + 1, 1)
let WFRAME = Frame(-2.2, 2.2, -1.2, 1.2, [0.1, 0.15, 0], 6.6, 3.6)
# arrows of the field F at the grid points; length grows with speed and levels off
let Arrows = |fr, F, xs, ys, len = 0.34, col = [0.62, 0.65, 0.70, 1], w = 2| block {
    for (x in xs) {
        for (y in ys) {
            let v = F([x, y])
            let m = sqrt(v[0] * v[0] + v[1] * v[1])
            if (m > 0.04) {
                let l = len * m / (m + 0.7)
                let a = at(fr, x, y)
                let d = [l * v[0] / m / 2, l * v[1] / m / 2]
                . Tip([a[0] - d[0], a[1] - d[1], 0], [a[0] + d[0], a[1] + d[1], 0], col, w, min(0.1, 0.55 * l))
            }
        }
    }
}
# border and tick numbers along the bottom and left of a field frame
let FieldBox = |fr, xs, ys| block {
    . Outline([at(fr, fr[0], fr[2]), at(fr, fr[1], fr[2]), at(fr, fr[1], fr[3]), at(fr, fr[0], fr[3])], SLATE, 2.5)
    for (x in xs) {
        . Seg(at(fr, x, fr[2]), [fx(fr, x), fy(fr, fr[2]) - 0.08, 0], SLATE, 2)
        . center{[fx(fr, x), fy(fr, fr[2]) - 0.29, 0]} Math(to_string(x), 0.7, SLATE)
    }
    for (y in ys) {
        . Seg(at(fr, fr[0], y), [fx(fr, fr[0]) - 0.08, fy(fr, y), 0], SLATE, 2)
        . center{[fx(fr, fr[0]) - 0.33, fy(fr, y), 0]} Math(to_string(y), 0.7, SLATE)
    }
}
# the path of a particle released at p0 and carried by F for time span, as steps + 1 points
let trace = |F, p0, span, steps| {
    var p = p0
    var pts = [p0]
    let dt = span / steps
    for (i in range(0, steps)) {
        let k1 = F(p)
        let k2 = F([p[0] + 0.5 * dt * k1[0], p[1] + 0.5 * dt * k1[1]])
        p = [p[0] + dt * k2[0], p[1] + dt * k2[1]]
        pts .= p
    }
    return pts
}
# nx by ny release points, jittered, each with a phase: [x, y, phase]
let seeds = |fr, nx, ny, salt| block {
    for (i in range(0, nx)) {
        for (j in range(0, ny)) {
            let n = salt + i * ny + j
            . [
                fr[0] + (fr[1] - fr[0]) * (i + rnd(n, 1)) / nx,
                fr[2] + (fr[3] - fr[2]) * (j + rnd(n, 2)) / ny,
                rnd(n, 3)
            ]
        }
    }
}
let paths_of = |F, sd, span, steps| map(sd, |s| trace(F, [s[0], s[1]], span, steps))
let path_at = |pts, k| {
    let i = to_int(clamp(floor(k), 0, len(pts) - 2))
    let f = clamp(k - i, 0, 1)
    return [pts[i][0] + f * (pts[i + 1][0] - pts[i][0]), pts[i][1] + f * (pts[i + 1][1] - pts[i][1])]
}
# particles riding their paths at time u; each lives for `span`, then is released again
let Flow = |fr, paths, sd, u, span, col = AZURE| block {
    for (n in range(0, len(paths))) {
        let steps = len(paths[n]) - 1
        let age = mod(u / span + sd[n][2], 1)
        let p = path_at(paths[n], age * steps)
        let q = path_at(paths[n], max(age * steps - 0.45 * steps, 0))
        if (p[0] > fr[0] and p[0] < fr[1] and p[1] > fr[2] and p[1] < fr[3] and q[0] > fr[0] and q[0] < fr[1] and q[1] > fr[2] and q[1] < fr[3]) {
            let c = [col[0], col[1], col[2], clamp(1.6 * sin(PI * age), 0, 1)]
            . Seg(at(fr, q[0], q[1]), at(fr, p[0], p[1]), c, 2.5)
            . Pt(at(fr, p[0], p[1]), c, 0.04)
        }
    }
}
# a paddle wheel at the scene point pos, turned by ang; one paddle is orange
let Wheel = |pos, ang, r = 0.27| block {
    for (i in range(0, 4)) {
        let d = [cos(ang + i * PI / 2), sin(ang + i * PI / 2)]
        var col = INK
        if (i == 0) { col = EMBER }
        . Seg(pos, [pos[0] + r * d[0], pos[1] + r * d[1], 0], col, 3.5)
        . Seg([pos[0] + r * d[0] + 0.08 * d[1], pos[1] + r * d[1] - 0.08 * d[0], 0], [pos[0] + r * d[0] - 0.08 * d[1], pos[1] + r * d[1] + 0.08 * d[0], 0], col, 5.5)
    }
    . Pt(pos, INK, 0.055)
}
# the integral of g over [a, b] by the midpoint rule
let integ = |g, a, b| {
    var total = 0
    for (k in range(0, 80)) {
        total = total + g(a + (k + 0.5) * (b - a) / 80) * (b - a) / 80
    }
    return total
}
# the four edge fluxes [right, left, top, bottom] and the four edge circulations [bottom, right, top, left]
# of W for the square with corner (a, b) and side h
let fluxes = |a, b, h| [
    integ(|y| W([a + h, y])[0], b, b + h),
    -integ(|y| W([a, y])[0], b, b + h),
    integ(|x| W([x, b + h])[1], a, a + h),
    -integ(|x| W([x, b])[1], a, a + h)
]
let circs = |a, b, h| [
    integ(|x| W([x, b])[0], a, a + h),
    integ(|y| W([a + h, y])[1], b, b + h),
    -integ(|x| W([x, b + h])[0], a, a + h),
    -integ(|y| W([a, y])[1], b, b + h)
]
# the close-up used for the bookkeeping figures
let ZFRAME = Frame(0.55, 1.95, 0.3, 1.2, [-1.0, 0, 0], 5.0, 3.214)
let Square = |fr, a, b, h, col = INK, w = 3.5| Outline([at(fr, a, b), at(fr, a + h, b), at(fr, a + h, b + h), at(fr, a, b + h)], col, w)