Skip to content

docs: add doctests to every function and a Documenter site - #25

Merged
wardjm merged 3 commits into
mainfrom
docs-doctests
Jul 12, 2026
Merged

wardjm merged 3 commits into
mainfrom
docs-doctests

Conversation

@wardjm

@wardjm wardjm commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Adds runnable # Examples to every documented function — 223 jldoctest blocks — plus docstrings for the 28 exports that had none (degree, cdc_components, wrap, the accessors, the dg_* factories), and a Documenter site to render and check them.

The examples assert behaviour rather than illustrate it: the circle's Laplacian spectrum (0, 1, 1, 3.78, …), adjointness of δ against d, antisymmetry of the wedge, ♯∘♭ = id, and each weak-form builder reproducing exactly the matrix its operator carries. They run under Pkg.test() (test/doctests.jl) and in the docs build, sharing the preamble in docs/doctest_setup.jl (dg = 60-point circle, dg3 = 200-point sphere, f = cos θ).

What writing them turned up

Three things that only surfaced because the examples were executed — twice, in different environments:

  • A function's individual coefficients are not reproducible when it straddles a degenerate eigenspace. λ=1 has multiplicity two on the circle, so the eigensolver returns an arbitrary orthonormal pair spanning that plane: c[2], c[3] came out 0.6144, 0.35 in one environment and 0.5898, 0.3901 in another. Only the Parseval norm is fixed. Noted on _from_pointwise_basis — any assertion on a single such coefficient is unsound.
  • betti_spectrum's harmonic eigenvalue magnitude is machine-dependent. The w = 1e10 penalty multiplies the float noise along with the energy, so values[1] roams 1e-5–1e-3 between BLAS versions. The gap that gives the Betti number does not move. BettiSpectrum now warns: count the gap, never a threshold.
  • laplacian(dg, 0) and codifferential ∘ d are not the same matrix. Composing passes through the Gram pseudo-inverse; the dedicated weak builder does not. They agree to ~2.5e-6 on resolved modes and differ by ~17% on the truncation tail (same for div ∘ grad vs -Δ). Documented in Conventions and on LinearOperator.

Also

  • DiffusionGeometry, MarkovTriple, ImmersedMarkovTriple and GammaCache gain compact show methods — printing a geometry previously dumped 123 KB of eigenbasis to the terminal.
  • docs/plotting.md and docs/upstream-bugs.md move under docs/src/ so they are part of the built site; the three references to them are updated.
  • CI gains a docs job that builds the site (and so runs the doctests) with checkdocs=:exports, which fails the build on any undocumented export.

Verification

Full suite green: 2473 passed, 0 failed (Pkg.test(), doctests included). julia --project=docs docs/make.jl builds clean with no doctest failures and no missing-docs errors.

wardjm added 3 commits July 11, 2026 22:14
Every documented function now carries runnable `# Examples` (223 jldoctest
blocks), and the 28 exports that had no docstring at all (degree,
cdc_components, wrap, the accessors, the dg_* factories) have one.

The examples assert behaviour rather than illustrate it: the circle's Laplacian
spectrum, adjointness of δ against d, antisymmetry of the wedge, ♯∘♭ = id, and
each weak-form builder reproducing exactly the matrix its operator carries. They
run under `Pkg.test()` (test/doctests.jl) and in the docs build, sharing the
preamble in docs/doctest_setup.jl.

Writing them turned up three things worth recording:

- A function's individual coefficients are not reproducible when it straddles a
  degenerate eigenspace: λ=1 has multiplicity two on the circle, so the
  eigensolver returns an arbitrary orthonormal pair spanning that plane. Only
  the Parseval norm is fixed. Noted on _from_pointwise_basis.
- betti_spectrum's harmonic eigenvalue *magnitude* is machine-dependent — the
  w=1e10 penalty multiplies the float noise with it — while the gap that gives
  the Betti number is not. BettiSpectrum now warns to count the gap, never a
  threshold.
- laplacian(dg, 0) and codifferential ∘ d are not the same matrix: composing
  passes through the Gram pseudo-inverse, so the two agree on the resolved modes
  and part company on the truncation tail. Documented in Conventions.

DiffusionGeometry, MarkovTriple, ImmersedMarkovTriple and GammaCache also gain
compact `show` methods — printing a geometry previously dumped 123 KB of
eigenbasis to the terminal.

docs/plotting.md and docs/upstream-bugs.md move under docs/src so they are part
of the built site; the three references to them are updated. CI gains a docs job
that builds the site (and so runs the doctests) with checkdocs=:exports, which
fails the build on any undocumented export.
Follow-up to c3bbd84, from a review of it. Four of the guarantees that commit
advertised were not actually enforced, and the examples quietly leaned on names a
reader cannot call.

- `warnonly=[:missing_docs]` in docs/make.jl downgraded `checkdocs=:exports` to a
  warning, so the claim that the build fails on an undocumented export was
  vacuous. Dropped it; the strict build passes, so all 179 exports were already
  documented and nothing was hiding behind the warning.
- The CI docs job passed GITHUB_TOKEN but granted no `contents: write` and had no
  tag trigger, so `deploydocs` could never push to gh-pages and versioned docs
  would never build. Added the permissions and a `v*` tag trigger.
- test/doctests.jl ran with `manual=false`, so the examples in index.md,
  conventions.md and the API pages were exercised only by the docs job, not by
  `Pkg.test()` — while README claimed they "run as part of the test suite, so they
  cannot drift". Now `manual=true`, which makes the README true as written.
- The doctest preamble imported twenty unexported names so the examples could call
  them bare. Copying `_is_orthonormal(function_space(dg))` out of the rendered docs
  got you an UndefVarError.

That last one split in two. The five accessors — `coeffs`, `space`, `geometry`,
`batch_shape`, `space_degree` — are the read side of every tensor a user holds and
were unexported by oversight (the c3bbd84 message already called them exports), so
they are exported now. The genuinely internal helpers (`np_reshape`, the batch
utilities, `_from_pointwise_basis`) stay private and their examples spell them
`DiffusionGeometryJ.np_reshape(...)`, as a reader would have to. The preamble now
imports nothing unexported, so an example that reaches for a private name fails the
doctests rather than misleading.

Also: `_metric_apply`'s example did not call `_metric_apply` — it re-ran the public
`metric_apply` example verbatim. Replaced with a cross-reference, since the kernel
takes raw arrays rather than a space.
Audited every API name and code block in the README against the package by
executing them, rather than reading. The prose was accurate but had fallen behind
three merged features and carried one broken link.

- The install URL pointed at `wardjm/DiffusionGeometryJ.jl`; the repository is
  `wardjm/DiffusionGeometryJ` (no `.jl`), so `Pkg.add` as documented would have
  failed.
- Batching went unmentioned entirely — no `batch_shape`, and no note that batch axes
  broadcast numpy-style from the right rather than by Julia's rule. That is a real
  trap when porting, and it was only recorded in the Conventions page.
- `.*` and `./` (#24) and the accessors were undocumented. A numeric array scaling a
  tensor means batch-wise scalars, which is what makes `exp.(-evals) .* evecs` a
  spectral filter; that now appears where a reader will look for it.
- The `show` methods added in c3bbd84 are shown, and the coefficients-not-values
  distinction that motivates `to_pointwise_basis` is stated in both the tensor
  section and Conventions.

The Documentation section now says the guide pages' examples run in the suite too
(true as of the previous commit) and links the Conventions and upstream-bugs pages.

Everything else — the constructor table, the operator and Betti sections, the
degree conventions, `hodge_decomposition`'s two return shapes — was checked against
a live geometry and is correct as written.
@wardjm
wardjm merged commit 6ae223d into main Jul 12, 2026
3 checks passed
@wardjm
wardjm deleted the docs-doctests branch July 15, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant