This site is version 0.0.1. Most of the code on this site is drafted by an agent and needs review. I apologize for the AI-speak, we are working on rewriting it!
viv-lang.org

The Faddeeva and plasma dispersion functions on the complex plane

Map the Faddeeva function and the plasma dispersion function over a rectangle of the complex plane, and measure their accuracy against SciPy and against mpmath at 30 digits. vp.special.faddeeva_w and vp.special.plasma_z evaluate one point per call; for a whole grid this page loads a ten-line Viv file, gallery/science/zgrid.viv, that loops the same library function (lib/zfunc.viv, Weideman's rational expansion) over the grid in one call.

A grid in one call

vp.load compiles the file once and caches the library. w_grid(x, y) returns the real and imaginary parts of w at every x + iy, row by row.

import numpy as np
import vipyr as vp
from vipyr.figure import figure

zg = vp.load("gallery/science/zgrid.viv")
x = np.linspace(-6.0, 6.0, 121)
y = np.linspace(-1.0, 4.0, 51)
re_w, im_w = zg.w_grid(x, y)
W = (re_w + 1j * im_w).reshape(len(y), len(x))
print("grid: %d x %d points" % W.shape)
print(
    "w(0) = %s   w(i) = %.15f (exp(1) erfc(1) = %.15f)"
    % (W[10, 60], W[20, 60].real, np.exp(1) * 0.15729920705028513)
)
print(
    "one point, vp.special.faddeeva_w(z=2+1j):",
    vp.special.faddeeva_w(z=2.0 + 1.0j).w,
)
grid: 51 x 121 points
w(0) = (1.0000000000000002+0j)   w(i) = 0.427583576155807 (exp(1) erfc(1) = 0.427583576155807)
one point, vp.special.faddeeva_w(z=2+1j): (0.14023958136627798+0.22221344017989914j)

and the phase of

In the upper half plane is at most 1 and decays like . Below the real axis takes over and grows fast, which is why the plot stops at . On the real axis the plasma dispersion function's imaginary part is , and its real part is odd, with extremes of about 1.08 at and at (on the grid, at the nearest point, ). The Landau damping example solves the Langmuir dispersion relation with this function.

fig = figure(
    "$\\log_{10} |w(x + iy)|$", "$\\mathrm{Re}\\,z$", "$\\mathrm{Im}\\,z$", window=(-6.0, 6.0, -1.0, 4.0)
)
fig.heatmap(np.log10(np.abs(W)), "log10 |w|")
fig.origin("lower")
fig.clim(-1.5, 1.5)
fig.colormap("diverging")

re_z, im_z = zg.z_grid(x, y)
Z = (re_z + 1j * im_z).reshape(len(y), len(x))
fig = figure(
    "$\\mathrm{Re}\\,Z(x + iy)$, the real part of the plasma dispersion function",
    "$\\mathrm{Re}\\,\\zeta$",
    "$\\mathrm{Im}\\,\\zeta$",
    window=(-6.0, 6.0, -1.0, 4.0),
)
fig.heatmap(np.clip(Z.real, -2.0, 2.0), "$\\mathrm{Re}\\,Z$")
fig.origin("lower")
fig.colormap("diverging")

fig = figure("$Z$ on the real axis", "$x$", "$Z(x)$")
fig.plot(x, Z[10].real, "$\\mathrm{Re}\\,Z$")
fig.plot(x, Z[10].imag, "$\\mathrm{Im}\\,Z = \\sqrt{\\pi}\\,e^{-x^2}$")
fig.style("$\\mathrm{Re}\\,Z$", "s1 nomarker")
fig.style("$\\mathrm{Im}\\,Z = \\sqrt{\\pi}\\,e^{-x^2}$", "s2 dash nomarker")
print(
    "Re Z(x) is largest at x = %.2f: %.4f"
    % (x[10 + np.argmax(Z[10, 10:-10].real)], Z[10, 10:-10].real.max())
)
Re Z(x) is largest at x = -0.90: 1.0814
log10|w(x+iy)|−505024RezImz−101
ReZ(x+iy), the real part of the plasma dispersion function−505024ReζImζ−2−1012
Z on the real axisReZImZ=πe−x2−101−6−4−20246xZ(x)

Accuracy

Against SciPy's wofz on the whole grid, and against mpmath at 30 digits on every fifth row and fourth column of it, as relative errors |w - w_ref| / |w_ref|. The library's stated tolerance on this rectangle is 2 × 10−14 (lib/zfunc_ref.py measured it).

from scipy.special import wofz
import mpmath

rel_scipy = np.abs(W - wofz(x[None, :] + 1j * y[:, None])) / np.abs(W)
mpmath.mp.dps = 30
sub = [(k, j) for k in range(0, len(y), 5) for j in range(0, len(x), 4)]
rel_mp = np.zeros((len(range(0, len(y), 5)), len(range(0, len(x), 4))))
for k, j in sub:
    z = mpmath.mpc(x[j], y[k])
    exact = mpmath.exp(-z * z) * mpmath.erfc(-1j * z)
    rel_mp[k // 5, j // 4] = float(
        abs(mpmath.mpc(W[k, j].real, W[k, j].imag) - exact) / abs(exact)
    )
print("worst relative error against SciPy wofz: %.1e" % rel_scipy.max())
print(
    "worst relative error against mpmath (30 digits), %d points: %.1e"
    % (len(sub), rel_mp.max())
)

fig = figure(
    "Relative error against mpmath, $\\log_{10}$",
    "$\\mathrm{Re}\\,z$",
    "$\\mathrm{Im}\\,z$",
    window=(-6.0, 6.0, -1.0, 4.0),
)
fig.heatmap(np.log10(np.maximum(rel_mp, 1e-17)), "log10 error")
fig.origin("lower")
fig.clim(-17.0, -13.0)
worst relative error against SciPy wofz: 8.5e-15
worst relative error against mpmath (30 digits), 341 points: 6.7e-16
Relative error against mpmath, log10−505024RezImz−17−16−15−14−13

How it's checked

The faddeeva_w and plasma_z functions agree with SciPy to 2 × 10−14 relative on this rectangle in their recorded cases, and check() reruns them. The grid file carries one test of its own, w(0) = 1.

print(
    "check():",
    vp.special.faddeeva_w(z=2.0 + 1.0j).check().passed,
    vp.special.plasma_z(zeta=1.0 + 0.5j).check().passed,
)
check(): True True

Source

gallery/science/plot_plasma_dispersion_function.py, run as written when these pages were built. The output and the figures above are from that run.

The Viv program it calls is posts/2026-09-25-landau-damping-from-the-dispersion-relation.viv: its atlas, the defs and the calls between them, or open it in the workbench.