Skip to content

The Two-Argument Zeta

The Riemann zeta has a two-argument cousin, the Hurwitz zeta,

convergent for and continued analytically everywhere else. It recovers the ordinary case at () and shifts the sum otherwise, so , and in general for a positive integer .

@enumeratio/analytic adds HurwitzZeta (and a two-argument Zeta) to compute-engine by Euler–Maclaurin summation, aligned with Wolfram. Because the head is declared on the playground engine, you can evaluate it in any cell:

ζ(s, a) at a few values

source
<notatio-cell value="HurwitzZeta(2, 1)" />
<notatio-cell value="HurwitzZeta(2, 2)" />
<notatio-cell value="HurwitzZeta(0, a)" />

The first two reduce to closed forms — and . At nonpositive integer the values are Bernoulli polynomials in , so (and ). Write the head as \operatorname{HurwitzZeta} — the bare \zeta(s, a) LaTeX still parses as the one-argument Riemann zeta.

The surface

As a bivariate function it is a smooth sheet for , , climbing steeply as toward the pole at . Drag to rotate; the surface is sampled by compiling the expression to a native function (no per-point symbolic evaluation), so it stays responsive.

ζ(s, a) over s ∈ [2,5], a ∈ (0,3]

source
<notatio-plot-3d value="HurwitzZeta(x, y)" x-domain="2,5" y-domain="0.35,3" label="ζ(s, a)" color-legend />

Here is and is . Push toward and the sheet lifts off — the term blows up. The generalized Zeta drops exactly that term, so Zeta(s, 0) is finite and equals — the one place the two functions genuinely disagree.

On the GPU

The same surface with gpu set evaluates its grid in a WebGPU compute shader instead of on the CPU — HurwitzZeta compiles to the zetaWGSL kernel via compute-engine's WGSL target. It falls back to the CPU path automatically where WebGPU is unavailable, so the two look identical; the difference is a denser grid stays interactive. (Look for the GPU badge under the figure.)

The third argument, as a dial

A surface fixes one slice; a Manipulate hands you the rest. LerchPhi(z, s, a) has three arguments, and a surface can only show two — so sweep the one it cannot, and the sheet deforms as walks across its disk. The same works for the order of the polylogarithm, which is the exponent every term is raised to.

At only the term survives and the sheet is just ; push toward and the higher terms pile on until it becomes the Hurwitz zeta itself. Push it the other way and the alternating series pulls the sheet down instead.

Turn the same surface ninety degrees — plot it over and , and put the order on the dial:

Raising flattens it: every term carries , so a larger exponent crushes the tail and leaves the term standing alone.

ζ(s, a) evaluated on the GPU (denser grid)

source
<notatio-plot-3d value="HurwitzZeta(x, y)" x-domain="2,5" y-domain="0.35,3" gpu="120" label="ζ(s, a)" color-legend />

The parent: LerchPhi

Both of these sit inside the Lerch transcendent

which is at and the polylogarithm along . @enumeratio/analytic provides LerchPhi; the factor gives geometric convergence for , so the series is summed directly.

LerchPhi reductions

source
<notatio-cell value="LerchPhi(1, 2, 1)" />
<notatio-cell value="LerchPhi(z, 0, a)" />

The first is ; the second is .

Its surface Φ(z, 2, a) — sweeping (as ) and (as ) — is a heavier per-point cost than zeta (a full series each), which makes it a good stress test for the GPU: a 256×256 grid takes ~4 ms on the GPU versus ~270 ms on the CPU here (~65×), because every pixel runs its own series in parallel.

Φ(z, 2, a) on the GPU

source
<notatio-plot-3d value="LerchPhi(x, 2, y)" x-domain="-0.9,0.9" y-domain="0.5,3" gpu="120" label="Φ(z, 2, a)" color-legend />

Where it gets beautiful

Fix instead and let range over the whole complex plane, and becomes a phase portrait full of pinwheels, zeros, and poles.

ζ on the GPU: a phase portrait — the whole Euler–Maclaurin kernel running per pixel, in real time.