2026-10-08
In 1988 V. I. Arnold asked in how many ways a river can cross a straight road times. No closed formula for these meander numbers is known, and the values are known only by computer enumeration. We formalize the problem in the Lean 4 proof assistant and give two counting algorithms that are proved, for every , to return exactly the number defined by the specification. The first is a depth-first search over crossing orders with a pruning rule based on a parity argument. The second is a transfer matrix that scans the road from west to east, tracks how the open strands of the river connect, merges equal states, and discards states that can no longer be completed. Its correctness proof is a bijection between the action sequences it accepts and meanders, established through a decorated version of the machine that carries the actual pieces of river. Along the way we prove that closed meanders of order are equinumerous with open meanders with crossings, and that there are at most closed meanders, where is the -th Catalan number. The verified transfer matrix computes the meander numbers up to in about half a minute; values up to are checked by the Lean kernel alone. Unverified parallel implementations of the same machine in Rust and OxCaml, with each state packed into one machine word, reproduce the published values up to , and companion notebooks implement the algorithms in Lean, Python and OCaml. The development is about 5,000 lines of Lean and is available at https://github.com/querygraph/meander.
A meander is a non-self-intersecting curve in the plane that crosses a fixed straight line a prescribed number of times. In a short note on the topology of real algebraic curves, Arnold [2] posed the enumeration question in its most vivid form: in how many ways can a river cross a straight road times? Two rivers are considered the same if one can be deformed into the other without changing the order of the crossings along the road and along the river. The resulting numbers, form sequence A005316 of the OEIS [11]. The closely related count of closed curves crossing the line times is A005315 [12]. Meanders appeared earlier in work on folding a strip of stamps [14], were studied systematically by Lando and Zvonkin [8], and became a test bed for exact enumeration and statistical physics [4, 5, 6].
Despite this attention there is no closed formula. Di Francesco, Golinelli and Guitter conjectured from a conformal field theory argument that the number of closed meanders of order grows like with and [5]. Rigorous bounds on the growth rate are far apart: Albert and Paterson proved that the exponential growth constant lies between about and [1]. Every known value of the sequence comes from a computer program, and the best of these are intricate transfer-matrix codes [6].
This paper asks a narrower question: can we compute meander numbers with programs whose correctness is machine-checked against the plain definition? We answer it in the Lean 4 proof assistant [9] with the mathlib library [10]. Our contributions are:
A formal specification of open meanders as permutations of the bridges satisfying a non-crossing condition, and of closed meanders (Section 2).
Structural theorems: open meanders exist for every ; there are at most ; closed meanders of order correspond bijectively to open meanders with crossings; and there are at most closed meanders (Section 3).
A depth-first search with a pruning rule derived from a parity argument, proved equal to the specification (Section 4).
A transfer matrix with state merging and a capacity-based pruning rule, proved equal to the specification by a bijection between accepted action sequences and meanders (Section 5).
Certified values: kernel-checked for , and computed by the verified transfer matrix up to , matching the OEIS (Section 6).
Parallel implementations of the same machine in Rust and OxCaml, unverified but checked against the certified values and the OEIS, which reach ; teaching implementations in Python and OCaml; and a comparison of all of them (Section 6.1).
We do not claim new values of the sequence; larger values are known [6]. What is new is that the values we report come from programs proved correct for all , and that the proofs expose the combinatorial facts that make the fast algorithms work.
Label the bridges from west to east. Following Arnold, the river enters from the south and leaves to the east. Between consecutive crossings it runs along an arc in one of the two half-planes, alternately: the first arc, from the south, lies below the road, the next above, and so on. A river is therefore determined up to deformation by its crossing order, the permutation with the bridge crossed -th.
Not every permutation is realized. To state the condition, compactify each half-plane to a disk whose boundary consists of the road and one point at infinity, and record the two ends of the river as two boundary points east of every bridge: the east end and the south end . (Going around the lower disk counterclockwise, one meets the bridges, then east, then south.) The river’s path is the sequence of boundary points and arc joins and in the lower half-plane if is even and the upper one if is odd. By the Jordan curve theorem the river is simple exactly when no two arcs on the same side cross, and two chords of a disk cross exactly when their endpoints alternate around the boundary. Because all endpoints are now points of a line, alternation is an elementary condition.
Definition 2.1. Chords and interleave if exactly one of lies strictly between and . A point sequence has no crossing among its first arcs if for all with , the arcs and do not interleave. A permutation of is an open meander if its path has no crossing among its arcs. We write for the number of open meanders.
Figure 1 gives the specification in Lean. Figure 2 shows the three open meanders with four crossings, and Figure 3 shows all eight with five.
def Interleave (a b c d : Nat) : Prop :=
¬ ((min a b < c ∧ c < max a b) ↔ (min a b < d ∧ d < max a b))
def NoCross (P : Nat → Nat) (L : Nat) : Prop :=
∀ k < L, ∀ j < k, j % 2 = k % 2 →
¬ Interleave (P j) (P (j + 1)) (P k) (P (k + 1))
def IsMeander {n : ℕ} (σ : Equiv.Perm (Fin n)) : Prop :=
NoCross (pathPt σ) (n + 1)
def openMeanderCount (n : ℕ) : ℕ :=
Fintype.card {σ : Equiv.Perm (Fin n) // IsMeander σ}
pathPt σ is the point sequence P of the river with crossing
order σ.
A closed meander of order is a simple closed curve crossing the road times. To count each curve once we fix where it starts and in which direction it runs: it starts at the westmost bridge and leaves it into the upper half-plane. It is then a permutation of with whose cyclic path has no crossing among its arcs. We write for their number.
|
|
|
| 01234 | 01432 |
|
|
|
| 03214 | 21034 |
|
|
|
| 23410 | 41230 |
|
|
|
| 43012 | 43210 |
Theorem 3.1. For every , .
Proof sketch. The upper bound is the cardinality of a subtype of the permutations. For the lower bound, the identity permutation is a meander: all its arcs except the first join neighbouring bridges, so nothing lies strictly between their ends, and the first arc, from to bridge , encloses every other lower arc. ◻
Theorem 3.2. For every , .
Proof sketch. Delete the westmost bridge of a closed meander
and rotate the picture by
.
The two arcs at the deleted bridge now run off to infinity: one in the
lower half-plane, from which the river enters, and one to the east,
where it leaves. The result is an open meander with
crossings, and the construction can be reversed. In coordinates the map
sends an open crossing order
on
bridges to the closed order
,
which in Lean is
.
Reflection
reverses the order on the bridges, so it preserves interleaving. The
only subtlety is the south end
,
which truncated subtraction sends to the new bridge
;
it is an endpoint of a single lower arc, and every other lower arc has
both endpoints among the bridges, so interleaving is unaffected. All of
this reduces to omega once the values of the path are
pinned down. ◻
Theorem 3.3. For every , , where .
Proof sketch. By Theorem 3.2 it suffices to inject open meanders with crossings into pairs of Dyck words of semilength ; mathlib already proves that there are such words. Treat the two ends of the river as extra points east of every bridge. Each half-plane then carries a non-crossing perfect matching of points, and the river is recovered by walking from the south end and taking, at each point, the partner on the side where the river currently is. A non-crossing matching is determined by its pattern, the word recording whether each point opens () or closes () its arc. To see this, let according to the pattern and let . For an opener with partner , every arc starting strictly inside also ends inside it, so and for . These two facts locate from the pattern alone: if a second matching with the same pattern paired with , then , a contradiction. Closers are handled symmetrically. The pattern is a Dyck word because every closer in a prefix is matched to an opener in the same prefix. ◻
The specification counts permutations, so evaluating it directly costs steps. The first algorithm builds the crossing order one bridge at a time. It abandons a branch as soon as the newest arc crosses an earlier arc on the same side, or as soon as the following test fails.
Lemma 4.1 (Parity). Let be a prefix of a meander, with arcs already drawn, and let be one of them, on side , with ends . Count the arc ends still to come on side that lie strictly between and : every unused bridge in ; the current point, if the next arc is on side ; and the east end, if the last arc is on side . This number is even.
Proof. Each later arc on side has both ends inside or neither, since it may not cross arc . Each point listed is the end of exactly one later arc on side , and no other point is. Formally, an induction along the remainder of the river shows that the running count of such ends inside changes by or with each later arc on side . ◻
The correctness theorem openMeanderCount_eq_meanderCount
has two parts. First, the search equals the number of complete
orderings, generated by the same recursion, that satisfy the
specification; this holds because a pruned branch contains no meanders
(for crossings, by monotonicity of NoCross; for parity, by
Lemma 4.1). Second, permutations of
correspond bijectively to members of that list of orderings.
Measured in a prototype, parity pruning reduces the number of search nodes at from to . A further rule, that every remaining bridge be reachable from the current point through the regions cut out by the drawn arcs, reduces this to . For the reduced search visits exactly the prefixes of actual meanders, so no further pruning rule of this kind can help (Table 1). In the compiled search the reachability test cost more than it saved, so we did not adopt it; the transfer matrix of the next section enforces connectivity by construction.
| crossing test | parity | reachability | completable prefixes | ||
|---|---|---|---|---|---|
| 8 | 81 | 6,077 | 522 | 404 | 404 |
| 9 | 262 | 22,452 | 1,746 | 1,336 | 1,336 |
| 10 | 538 | 84,419 | 4,115 | 2,854 | 2,854 |
| 11 | 1,828 | 322,080 | 14,471 | 9,835 | — |
Scan the points of the road from west to east: the bridges, then (one arc, on side ), then (one arc, below). Each point opens or closes its arc on each of its sides. Arcs on one side never cross, so the arcs still open at a cut form a stack on each side, and a closing point always closes the top of its stack.
To the west of the cut the river is broken into pieces. Each piece joins two open arcs, or an open arc and the east end. The state records, for every open arc in stack order, a reference to where its piece leads:
inductive Ref where
| E -- the east end
| s (σ : Bool) (i : Nat) -- position i in the stack of side σ
structure St where
up : List Ref
dn : List Ref
There are four transitions at a bridge. Opening both sides creates a new piece whose two ends are the two new arcs. Opening one side and closing the other continues the closed arc’s piece along the new arc. Closing both sides joins two pieces, unless the two arcs being closed are the two ends of the same piece; that would close a loop, so the machine rejects it. At the top arc on its side is closed and its piece now ends at , or a new arc is opened towards . A sequence is accepted when, after , exactly one arc is open, below the road, leading to , so that can close it.
The machine is run on all action sequences at once, layer by layer.
Each layer is a list of states with multiplicities, and equal states are
merged by sorting and adding adjacent counts. Any merge procedure is
admissible if it preserves weighted sums
for every weight function
;
the counting theorem tmCountWith_eq_countP is proved for an
arbitrary such procedure. We instantiate it twice: with a sort-based
merge for compiled execution, and with a structurally recursive
insertion merge that the Lean kernel can evaluate.
Each layer also drops states that cannot be finished. Every remaining point closes at most one arc on each of its sides, and the accepting state has no open arc above the road and one below, so a state with more open arcs than the remaining points can close has no accepted continuation. In a prototype this viability test reduces the total number of states over all layers at from to . Its soundness is an induction showing that a single step lowers each stack by at most one, and only on the sides the point has.
The counting theorem reduces correctness of the transfer matrix to the following statement.
Theorem 5.1. The action sequences accepted by the machine
are in bijection with open meanders. Consequently
tmCount m = openMeanderCount m for every
.
The bijection is given explicitly. A meander is encoded by letting point open its arc on side exactly when the other end of that arc lies east of . An accepted sequence is decoded by running a decorated machine, described next, and reading off the river it has built. We prove that decoding an accepted sequence yields a meander whose encoding is the sequence, and that encoding a meander yields an accepted sequence that decodes back to the meander.
The decorated machine stores, for every open arc, the point where it
opened and its actual piece of river: the list of points from its
origin, through scanned points, to the origin of its partner or to
.
It also records the closed arcs. Forgetting the decorations gives back
the transfer matrix exactly (lemma step_abs), so decorated
runs and plain runs accept the same sequences. The heart of the proof is
an invariant with twenty conjuncts, preserved by each of the five kinds
of step (open–open, open–close for either side, close–close, and the two
moves at
).
It states, among other things, that partners point to each other; that a
piece is duplicate-free, begins at its arc’s origin, and is the reverse
of its partner’s piece; that consecutive points of a piece are joined by
recorded arcs; that pieces of different components are disjoint and
together cover every scanned point; that recorded arcs on one side do
not interleave and do not straddle the origin of an open arc on that
side; that each point is an arc end at most once per side; and that the
recorded arcs agree with the actions read. At acceptance only one piece
remains. Prefixing the south end gives a duplicate-free list of all
points in which consecutive points are joined by arcs. Because each
point is an arc end at most once per side, the sides of consecutive arcs
must alternate, and the non-interleaving of recorded arcs then yields
the meander condition.
Along the run of a meander’s own sequence we maintain a second, simpler invariant: every recorded arc is an arc of the meander, and the stack on each side consists exactly of the scanned points whose arc on that side crosses the cut, in increasing order. The non-crossing property implies that a closing point’s partner is the largest such point, hence the top of the stack. The only remaining danger is the loop check. Suppose a bridge closes both sides and the two arcs being closed are the ends of one piece. That piece is a duplicate-free chain of meander arcs from one path-neighbour of the bridge to the other, and every step changes the position along the river by exactly one. A discrete intermediate-value argument shows the chain passes through the position of the bridge itself, which is impossible because the bridge has not been scanned yet. Finally, after the stacks are forced: nothing is open above, and below only the first bridge on the path is open, joined to . Its piece is the whole river, so decoding returns the original meander.
The transfer-matrix development is about 3,650 lines of Lean. The
project as a whole, including the specification, the structural theorems
and the search, is about 4,960 lines and 171 theorems. All main theorems
depend only on Lean’s standard axioms (propext,
Classical.choice and Quot.sound).
Table 2 compares the algorithms. Times are wall-clock on an Apple-silicon laptop for computing all values up to the given . The specification itself is evaluable only for very small .
| Algorithm | largest | time |
|---|---|---|
| search, crossing test only (compiled) | 15 | 72 s |
| search with parity pruning (compiled) | 15 | 5 s |
| 17 | 62 s | |
| transfer matrix (compiled) | 24 | 1.3 s |
| 28 | 11 s | |
| 30 | 32 s | |
| 32 | 93 s | |
| search, kernel evaluation | 9 | 30 s for alone |
| transfer matrix, kernel evaluation | 13 | 68 s for alone |
The repository certifies values at two levels of trust. The statement
is proved by evaluation in the Lean kernel, through Theorem 5.1 applied
to the insertion-merge variant, and the corresponding closed values
follow by Theorem 3.2. Values up to
are proved with native_decide, which additionally trusts
the Lean compiler. The compiled transfer matrix produces
and all values for
agree with the OEIS b-file. The executable is the same function that the
correctness theorem is about, so these values carry the guarantee of
Theorem 5.1 up to the correctness of compilation.
The repository contains the algorithms in five languages. Only the Lean programs are verified; the others run the same machine and are checked against the certified values and the OEIS.
Lean. The specification (evaluable by brute force for
),
the search meanderCount with its array implementation
substituted by csimp, and the transfer matrix in two
variants: tmCount, compiled into the executable
meanders, and tmCountK, evaluated by the
kernel.
Rust (rust/). The transfer matrix with a
packed state. Read along the cut from top to bottom, the open arcs pair
up like brackets, because the pieces of river west of the cut never
cross. So a state is a bracket word with at most one marker for the arc
that leads to the east end, plus the number of arcs above the road, and
fits in 64 bits for
.
Each layer is split into 4096 hash shards, each an open-addressing table
behind its own lock; worker threads claim source shards, batch
successors per target shard, and free each source shard once read, so
memory holds about one layer. Tables grow on demand, or, with an option,
are allocated once at a size predicted from the previous layers’ growth.
Counts are kept modulo
and, from
,
also modulo
,
and recovered by the Chinese remainder theorem.
OxCaml (oxcaml/). The same algorithm in
OxCaml, Jane Street’s extension of OCaml. Workers run under OxCaml’s
Parallel scheduler, shards are capsules guarded by mutexes,
and the compiler’s mode system rules out data races. The state is an
immediate 63-bit integer, the hot loop allocates nothing on the heap,
and tables are flat arrays of unboxed values that the garbage collector
never scans.
Python and OCaml. Companion notebooks teach each language by building the specification, the parity search and the transfer matrix from scratch, with the state as a string or a list of constructors and layers in hash tables, run on all cores with process pools (Python) and domains (OCaml 5). A third notebook teaches Lean with the library’s own declarations.
Table 3 times the parallel programs on a workstation with an 18-core
Intel Xeon W-2191B (36 threads) and 128 GB of memory, one value per run
on all threads; on the same machine the verified meanders
computes all values up to
in 160 s. Every count equals the OEIS term; in particular
Memory is the limit: the largest layer for
holds
states, and
is the largest value that fits in 128 GB, at about 35 bytes per state.
Table 4 shows that the programs scale to the physical cores and no
further, as expected for work dominated by random memory access.
The OxCaml program is faster than the Rust program in its default mode, and the difference is one design choice. By default Rust grows each shard’s table by half when it is 80% full, so every state is moved several times as its table grows; OxCaml predicts each layer’s size from the growth of the previous layers and allocates every table once. With the same prediction (Rust’s pre-sizing option), Rust is 20–28% faster at every thread count, including one, so the saving is the rehashing work itself rather than time spent waiting on locks. Pre-sized Rust is then the fastest program for and on a single thread; OxCaml keeps an edge for small on many threads, plausibly because it keeps one pool of workers and their buffers for the whole run, while Rust starts its threads and allocates their batch buffers for every point. Pre-sizing costs memory, since each table is allocated at its final size while the previous layer is still draining (17.0 against 12.6 GiB at ); OxCaml needs more still (20.5 GiB), because its freed tables are returned only after a major garbage collection. Rust grows on demand by default, so that the largest fits in memory. The teaching implementations are far slower: interpreted Python computes all values up to in about twelve seconds on the laptop, and the OCaml notebook’s transfer matrix, run as bytecode on ten domains, takes about half a second for .
| largest layer | Rust | pre-sized | OxCaml | ||
|---|---|---|---|---|---|
| 40 | 3.6 s | 2.9 s | 2.2 s | ||
| 42 | 6.1 s | 5.3 s | 5.3 s | ||
| 44 | 30 s | 22 s | 25 s | ||
| 46 | 80 s | 57 s | 65 s | ||
| 49 | 516 s | ||||
| 50 | 1052 s |
| threads | 1 | 4 | 9 | 18 | 36 |
|---|---|---|---|---|---|
| Rust | 21.3 s | 6.1 s | 3.5 s | 3.0 s | 3.3 s |
| Rust, pre-sized | 17.0 s | 4.8 s | 2.8 s | 2.4 s | 2.9 s |
| OxCaml | 20.4 s | 5.6 s | 2.8 s | 2.2 s | 2.3 s |
The two pruning rules are the computational shadows of the Jordan curve theorem. Parity (Lemma 4.1) says that arcs pair up inside every region; viability says that a point can close at most one arc per side. Neither rule needs any topology beyond the interleaving of chords, which is why both proofs reduce to linear arithmetic.
The transfer-matrix proof is longer, mostly because the machine’s
state records connectivity abstractly, by reference, while correctness
needs to know what is connected to what. The decorated machine bridges
the gap: it carries explicit pieces of river, so the connectivity
argument becomes reasoning about lists. Making the abstract machine
literally the decorated one with decorations forgotten
(step_abs) means no separate refinement proof is
needed.
The specification is a statement about permutations, and the same
permutations appear in recent probability. Borga, Gwynne and Sun [3]
attach to a closed meander its meandric permutation: number the
crossings from west to east, and again in the order in which the river
meets them; the permutation relates the two orders. Up to inversion and
the choice of where and in which direction the river is started, this is
the permutation
of IsClosedMeander, and Rosenstiehl [13] had studied the
same objects as planar permutations. The formalization therefore counts
meandric permutations exactly, and Theorem 3.2 is a bijection between
meandric permutations of size
and open meanders with
crossings.
Borga, Gwynne and Sun conjecture that a uniform random meander, viewed as a planar map with two Hamiltonian paths (the road and the river), converges to a Liouville quantum gravity surface with matter central charge , that is , decorated by two independent space-filling SLE curves, and that the plots of uniform meandric permutations converge to the corresponding meandric permuton. They prove, among other things, that the longest increasing subsequence of permutations sampled from the permuton is sublinear. The conjecture is the probabilistic counterpart of the asymptotic formula of Di Francesco, Golinelli and Guitter [5], for the number of closed meanders of order , with estimated by Jensen and Guttmann [6, 7].
Exact counts test the exponent directly. With fixed at that estimate, estimates , with an error of order ; extrapolating linearly in from and removes most of it. Table 5 uses (Theorem 3.2) with open counts up to crossings, computed by the parallel programs of Section 6.1. The extrapolated values increase steadily towards . This is numerical evidence for the conjectured exponent, not a proof, and it assumes the estimate of .
| extrapolated | |||
|---|---|---|---|
| 12 | 9.4415 | 3.2665 | 3.4053 |
| 14 | 9.7747 | 3.2870 | 3.4100 |
| 16 | 10.0377 | 3.3027 | 3.4129 |
| 18 | 10.2506 | 3.3152 | 3.4149 |
| 20 | 10.4263 | 3.3253 | 3.4163 |
| 22 | 10.5739 | 3.3337 | 3.4172 |
| 24 | 10.6996 | 3.3407 | 3.4180 |
| conjecture [5] | 3.4201 | ||
Our verified transfer matrix is far from the state of the art. Jensen’s algorithm [6] encodes states more compactly and enumerates well beyond . The unverified Rust and OxCaml programs of Section 6.1 pack each state into one machine word and reach . Verifying such an encoding is a natural next step, and the generic counting theorem already accepts any merge procedure that preserves weighted sums. A second direction is to formalize the rigorous growth-rate bounds of Albert and Paterson [1], which would make our Catalan bound one instance of a more general framework. Finally, the question that motivated the work, a closed formula for the meander numbers, remains open; the conjectured critical exponent suggests that if a formula exists it is not of an elementary kind.
The formalization and this paper were developed with the assistance of Claude Code (Anthropic).
M. H. Albert and M. S. Paterson, Bounds for the growth rate of meander numbers, J. Combin. Theory Ser. A 112 (2005), 250–262.
V. I. Arnold, The branched covering , hyperbolicity and projective topology, Siberian Math. J. 29 (1988), 717–726.
J. Borga, E. Gwynne and X. Sun, Permutons, meanders, and SLE-decorated Liouville quantum gravity, J. Eur. Math. Soc. 28 (2026), 4893–4949; arXiv:2207.02319.
P. Di Francesco, O. Golinelli and E. Guitter, Meander, folding, and arch statistics, Math. Comput. Modelling 26 (1997), 97–147.
P. Di Francesco, O. Golinelli and E. Guitter, Meanders: exact asymptotics, Nuclear Phys. B 570 (2000), 699–712.
I. Jensen, A transfer matrix approach to the enumeration of plane meanders, J. Phys. A 33 (2000), 5953–5963.
I. Jensen and A. J. Guttmann, Critical exponents of plane meanders, J. Phys. A 33 (2000), L187–L192.
S. K. Lando and A. K. Zvonkin, Plane and projective meanders, Theoret. Comput. Sci. 117 (1993), 227–241.
L. de Moura and S. Ullrich, The Lean 4 theorem prover and programming language, in Automated Deduction – CADE 28, Lecture Notes in Comput. Sci. 12699, Springer, 2021, 625–635.
The mathlib Community, The Lean mathematical library, in Proceedings of the 9th ACM SIGPLAN International Conference on Certified Programs and Proofs (CPP 2020), 367–381.
OEIS Foundation, Sequence A005316: Meandric numbers, The On-Line Encyclopedia of Integer Sequences, https://oeis.org/A005316.
OEIS Foundation, Sequence A005315: Closed meandric numbers, The On-Line Encyclopedia of Integer Sequences, https://oeis.org/A005315.
P. Rosenstiehl, Planar permutations defined by two intersecting Jordan curves, in Graph Theory and Combinatorics (Cambridge, 1983), Academic Press, London, 1984, 259–271.
J. Touchard, Contributions à l’étude du problème des timbres-poste, Canad. J. Math. 2 (1950), 385–398.