mathematicskit.complex_analysis#

The Cauchy-Riemann equations and complex derivatives; contours, contour integrals (scipy.integrate.quad with complex_func=True), winding numbers, and Cauchy’s integral formula; residues, the residue theorem, and the argument principle; conformal maps (Möbius transformations, the Joukowski map) and mapped coordinate grids; and domain coloring of complex functions.

mathematicskit.complex_analysis: functions of a complex variable.

The Cauchy-Riemann equations and complex derivatives (central differences from mathematicskit.calculus); contours, contour integrals, winding numbers, and Cauchy’s integral formula (via scipy.integrate.quad() with complex_func=True); Laurent series via numpy.fft.fft(); residues, the residue theorem, the argument principle, and Rouché’s theorem; conformal maps (Möbius transformations and their classification, the Joukowski map) and their action on coordinate grids; and domain coloring (phase portraits) of complex functions.

class mathematicskit.complex_analysis.CauchyRiemannResult(u_x, u_y, v_x, v_y)[source]#

Bases: object

Partial derivatives of \(u = \operatorname{Re} f\) and \(v = \operatorname{Im} f\) at one point.

Parameters:
property residual: float#

\(\max(|u_x - v_y|, |u_y + v_x|)\), zero exactly when the Cauchy-Riemann equations hold.

Type:

float

u_x: float#
u_y: float#
v_x: float#
v_y: float#
class mathematicskit.complex_analysis.Contour(gamma, dgamma, breakpoints)[source]#

Bases: object

A piecewise-smooth path \(\gamma(t)\) with derivative \(\gamma'(t)\).

gamma and dgamma must accept scalar or array t. breakpoints are the sorted parameter values where \(\gamma'\) may jump (a polygon’s corners); the path runs from breakpoints[0] to breakpoints[-1] and is smooth on each piece in between.

Parameters:
breakpoints: ndarray#
dgamma: Callable#
gamma: Callable#
points(n=400)[source]#

n points \(\gamma(t)\) at evenly spaced parameter values, endpoints included.

Return type:

ndarray

Parameters:

n (int)

class mathematicskit.complex_analysis.DomainColoringResult(z, w, rgb)[source]#

Bases: object

A sampled complex function \(w = f(z)\) and its domain-coloring image.

Parameters:
rgb: ndarray#

z.shape + (3,) RGB values in \([0, 1]\); hue encodes \(\arg w\), brightness \(\log_2|w|\).

Type:

ndarray

w: ndarray#
z: ndarray#
class mathematicskit.complex_analysis.LaurentSeriesResult(center, radius, orders, coefficients)[source]#

Bases: object

Laurent coefficients \(a_k\) of \(f(z) = \sum_k a_k (z - z_0)^k\) on the circle \(|z - z_0| = r\).

Parameters:
center: complex#
coefficient(k)[source]#

\(a_k\) for one power k.

Return type:

complex

Parameters:

k (int)

coefficients: ndarray#
orders: ndarray#

The powers \(k\), from -n_max to n_max.

Type:

ndarray of int

radius: float#
class mathematicskit.complex_analysis.MappedGrid(horizontal, vertical, horizontal_image, vertical_image)[source]#

Bases: object

Horizontal and vertical grid lines in the z-plane and their images under a map f.

Each array has one row per grid line.

Parameters:
horizontal: ndarray#
horizontal_image: ndarray#
vertical: ndarray#
vertical_image: ndarray#
class mathematicskit.complex_analysis.MobiusClassification(kind, trace_squared, fixed_points)[source]#

Bases: object

The conjugacy type and fixed points of a Möbius transformation.

Parameters:
fixed_points: ndarray#

Solutions of \(T(z) = z\); inf stands for the point at infinity.

Type:

ndarray of complex

kind: str#

"identity", "elliptic", "parabolic", "hyperbolic", or "loxodromic".

Type:

str

trace_squared: complex#

\((a + d)^2/(ad - bc)\), invariant under conjugation and rescaling.

Type:

complex

class mathematicskit.complex_analysis.ResidueTheoremResult(integral, poles, residues, winding_numbers, predicted)[source]#

Bases: object

Both sides of the residue theorem \(\oint_\gamma f = 2\pi i \sum_k n(\gamma, z_k)\operatorname{Res}(f, z_k)\).

Parameters:
integral: complex#

The contour integral, computed directly.

Type:

complex

poles: ndarray#
predicted: complex#

\(2\pi i \sum_k n(\gamma, z_k)\operatorname{Res}(f, z_k)\).

Type:

complex

residues: ndarray#

\(\operatorname{Res}(f, z_k)\) for each pole, computed on a small circle.

Type:

ndarray

winding_numbers: ndarray#

\(n(\gamma, z_k)\), the number of times the contour winds around each pole.

Type:

ndarray of int

mathematicskit.complex_analysis.argument_principle(f, contour, fprime=None, n_points=4000)[source]#

Zeros minus poles of f inside contour, \(N - P = \frac{1}{2\pi i}\oint_\gamma \frac{f'(z)}{f(z)}\,dz\).

With fprime the logarithmic derivative is integrated directly. Without it, \(N - P\) is the winding number of the image curve \(f(\gamma)\) about 0, read off from the unwrapped phase (numpy.unwrap()) of \(f\) sampled along the contour. Both count with multiplicity. See Cauchy (1831, 1855), discussed in F. Smithies, Cauchy and the Creation of Complex Function Theory (Cambridge University Press, 1997), Ch. 6.

Parameters:
  • f (Callable) – Meromorphic, with no zeros or poles on the contour.

  • contour (Contour) – Positively oriented and simple.

  • fprime (Callable | None) – \(f'\).

  • n_points (int) – Samples used for the phase-unwrapping method; must resolve every turn of \(f(\gamma)\) about 0.

Return type:

int

Returns:

int

Examples

>>> from mathematicskit.complex_analysis.systems.contours import circle_contour
>>> p = lambda z: z**3 - 0.25 * z  # zeros at 0 and +-0.5
>>> argument_principle(p, circle_contour()), argument_principle(p, circle_contour(), fprime=lambda z: 3 * z**2 - 0.25)
(3, 3)
mathematicskit.complex_analysis.cauchy_integral_formula(f, contour, z0, n=0)[source]#

Cauchy’s integral formula \(f^{(n)}(z_0) = \frac{n!}{2\pi i}\oint_\gamma \frac{f(z)}{(z - z_0)^{n+1}}\,dz\).

Recovers the value (n = 0) or any derivative of a holomorphic f at z0 from its values on a contour winding once around z0. See A.-L. Cauchy, “Sur la mécanique céleste et sur un nouveau calcul appelé calcul des limites” (Turin, 1831); Ahlfors, Complex Analysis, Ch. 4, Sec. 2.3.

Parameters:
  • f (Callable) – Holomorphic inside and on contour.

  • contour (Contour) – Positively oriented, winding once around z0.

  • z0 (complex)

  • n (int) – Derivative order, n >= 0.

Return type:

complex

Returns:

complex

Examples

>>> value = cauchy_integral_formula(np.exp, circle_contour(), 0.3, n=2)
>>> round(value.real, 10) == round(float(np.exp(0.3)), 10)
True
mathematicskit.complex_analysis.cauchy_riemann(f, z, h=1e-06)[source]#

The partials \(u_x, u_y, v_x, v_y\) of \(f = u + iv\) at z.

CauchyRiemannResult.residual is (numerically) zero exactly where \(f\) satisfies the Cauchy-Riemann equations.

Parameters:
Return type:

CauchyRiemannResult

Returns:

CauchyRiemannResult

Examples

>>> round(cauchy_riemann(lambda z: z**2, 1 + 2j).residual, 6)
0.0
>>> round(cauchy_riemann(lambda z: z.conjugate(), 1 + 2j).residual, 6)  # u_x - v_y = 1 - (-1)
2.0
mathematicskit.complex_analysis.circle_contour(center=0.0, radius=1.0)[source]#

The counter-clockwise circle \(\gamma(t) = c + r e^{it}\), \(t \in [0, 2\pi]\).

Parameters:
Return type:

Contour

Returns:

Contour

Examples

>>> c = circle_contour(1j, 2.0)
>>> complex(np.round(c.gamma(0.0), 12))
(2+1j)
mathematicskit.complex_analysis.classify_mobius(a, b, c, d, atol=1e-12)[source]#

Classify \(T(z) = (az + b)/(cz + d)\) by \(\sigma = (a + d)^2/(ad - bc)\) and find its fixed points.

Conjugate Möbius transformations have the same \(\sigma\), and every non-identity one is conjugate to \(z \mapsto \lambda z\) (two fixed points) or \(z \mapsto z + 1\) (one). The type is elliptic (rotation about the fixed points) for real \(\sigma \in [0, 4)\), parabolic for \(\sigma = 4\), hyperbolic (flow from one fixed point to the other) for real \(\sigma > 4\), and loxodromic (spiral) otherwise. See A. F. Möbius, “Die Theorie der Kreisverwandtschaft in rein geometrischer Darstellung,” Abhandlungen der Königlich Sächsischen Gesellschaft der Wissenschaften 2 (1855), 529-595; Needham, Visual Complex Analysis, Ch. 3, Sec. VI.

Parameters:
  • a (complex) – With \(ad - bc \ne 0\).

  • b (complex) – With \(ad - bc \ne 0\).

  • c (complex) – With \(ad - bc \ne 0\).

  • d (complex) – With \(ad - bc \ne 0\).

  • atol (float) – Tolerance for deciding that \(\sigma\) is real, or equal to 4.

Return type:

MobiusClassification

Returns:

MobiusClassification

Examples

>>> [classify_mobius(*m).kind for m in [(1j, 0, 0, 1), (1, 1, 0, 1), (2, 0, 0, 1), (2j, 0, 0, 1 + 1j)]]
['elliptic', 'parabolic', 'hyperbolic', 'loxodromic']
>>> classify_mobius(0, 1, 1, 0).fixed_points.tolist()  # z -> 1/z fixes +-1
[(-1+0j), (1+0j)]
mathematicskit.complex_analysis.complex_derivative(f, z, h=1e-06)[source]#

\(f'(z) \approx \frac{f(z+h) - f(z-h)}{2h}\), differencing along the real axis.

Only meaningful where f is holomorphic; check with cauchy_riemann().

Parameters:
Return type:

complex

Returns:

complex

Examples

>>> d = complex_derivative(lambda z: z**3, 1 + 1j)  # 3 z^2 = 6i
>>> round(d.real, 6), round(d.imag, 6)
(0.0, 6.0)
mathematicskit.complex_analysis.complex_grid(x_range, y_range, nx, ny=None)[source]#

An (ny, nx) grid of points \(x + iy\) covering a rectangle.

Row j has constant imaginary part (a horizontal line), column k constant real part (a vertical line), matching the imshow convention with origin="lower".

Parameters:
  • x_range (tuple of float) – (min, max) of the real and imaginary parts.

  • y_range (tuple of float) – (min, max) of the real and imaginary parts.

  • nx (int) – Points along the real axis.

  • ny (int | None) – Points along the imaginary axis; defaults to nx.

Return type:

ndarray

Returns:

ndarray of complex

Examples

>>> z = complex_grid((-1, 1), (0, 2), 3)
>>> z.shape
(3, 3)
>>> complex(z[0, 0]), complex(z[-1, -1])
((-1+0j), (1+2j))
mathematicskit.complex_analysis.contour_integral(f, contour, epsabs=1e-10, epsrel=1e-08, limit=200)[source]#

\(\oint_\gamma f(z)\,dz = \int f(\gamma(t))\,\gamma'(t)\,dt\), via scipy.integrate.quad().

Parameters:
Return type:

complex

Returns:

complex

Examples

>>> value = contour_integral(lambda z: 1 / z, circle_contour())
>>> complex(np.round(value, 10)) == complex(0, round(2 * np.pi, 10))
True
mathematicskit.complex_analysis.domain_coloring(f, x_range=(-2.0, 2.0), y_range=(-2.0, 2.0), resolution=400, saturation=0.9)[source]#

Sample f on a rectangle and compute its domain-coloring image.

Parameters:
  • f (Callable) – Vectorized f(z) -> w. Samples where w is non-finite (at a pole) or exactly zero are colored white.

  • x_range (tuple of float)

  • y_range (tuple of float)

  • resolution (int) – Samples along the real axis; the imaginary axis gets the same spacing.

  • saturation (float) – HSV saturation in \([0, 1]\).

Return type:

DomainColoringResult

Returns:

DomainColoringResult

Examples

>>> result = domain_coloring(lambda z: z, (-1, 1), (-1, 1), resolution=5)
>>> result.rgb.shape
(5, 5, 3)
>>> np.round(result.rgb[2, 4], 3).tolist()  # w = 1: arg 0 -> red, |w| = 1 -> darkest band
[0.7, 0.07, 0.07]
mathematicskit.complex_analysis.joukowski_map(z, c=1.0)[source]#

The Joukowski map \(J(z) = z + c^2/z\).

It flattens the circle \(|z| = c\) onto the segment \([-2c, 2c]\); a circle through \(z = c\) that encloses \(-c\) maps to an airfoil with a sharp trailing edge. See N. E. Joukowski, “Über die Konturen der Tragflächen der Drachenflieger,” Zeitschrift für Flugtechnik und Motorluftschiffahrt 1 (1910), 281-284.

Parameters:
Returns:

complex or ndarray of complex

Examples

>>> w = joukowski_map(np.exp(1j * np.linspace(0, np.pi, 5)))
>>> np.round(w, 12).tolist()  # 2 cos(theta)
[(2+0j), (1.414213562373+0j), 0j, (-1.414213562373+0j), (-2+0j)]
mathematicskit.complex_analysis.laurent_coefficients(f, z0, radius, n_max, n_points=256)[source]#

Laurent coefficients \(a_{-n}, \ldots, a_n\) of f about z0, sampled on \(|z - z_0| = r\).

The circle must lie inside the annulus of convergence being studied: the same f has different Laurent series on different annuli.

Parameters:
  • f (Callable) – Vectorized f(z) -> complex.

  • z0 (complex)

  • radius (float)

  • n_max (int) – Largest \(|k|\) returned; must be less than n_points // 2.

  • n_points (int)

Return type:

LaurentSeriesResult

Returns:

LaurentSeriesResult

Examples

>>> series = laurent_coefficients(lambda z: np.exp(z) / z**2, 0.0, 1.0, 3)
>>> [round(series.coefficient(k).real, 10) for k in (-2, -1, 0, 1)]  # 1/z^2 + 1/z + 1/2 + z/6
[1.0, 1.0, 0.5, 0.1666666667]
mathematicskit.complex_analysis.map_grid(f, x_range, y_range, n_lines=11, n_points=200)[source]#

Sample horizontal and vertical grid lines over a rectangle and map them through f.

Parameters:
  • f (Callable) – Vectorized f(z) -> w.

  • x_range (tuple of float)

  • y_range (tuple of float)

  • n_lines (int) – Grid lines in each direction.

  • n_points (int) – Samples along each line.

Return type:

MappedGrid

Returns:

MappedGrid

Examples

>>> grid = map_grid(lambda z: 2 * z, (0, 1), (0, 1), n_lines=3, n_points=5)
>>> grid.horizontal.shape, complex(grid.vertical_image[-1, -1])
((3, 5), (2+2j))
mathematicskit.complex_analysis.mobius_transform(z, a, b, c, d)[source]#

The Möbius transformation \(T(z) = \frac{az + b}{cz + d}\), \(ad - bc \ne 0\).

Möbius transformations are exactly the conformal bijections of the Riemann sphere; they map circles and lines to circles and lines. The Cayley transform \((z - i)/(z + i)\) (a, b, c, d = 1, -1j, 1, 1j) maps the upper half-plane onto the unit disk.

Parameters:
Returns:

complex or ndarray of complex

Examples

>>> w = mobius_transform(np.array([1j, 0, 1]), 1, -1j, 1, 1j)  # Cayley transform
>>> np.round(np.abs(w), 12).tolist()
[0.0, 1.0, 1.0]
mathematicskit.complex_analysis.polygon_contour(vertices)[source]#

The closed polygon through vertices in order, returning to the first.

Side \(k\) is parametrized as \(v_k + (t - k)(v_{k+1} - v_k)\) for \(t \in [k, k+1]\). Listing the vertices counter-clockwise gives a positively oriented contour; clockwise gives winding number \(-1\) about interior points.

Parameters:

vertices (Sequence[complex]) – At least three vertices; do not repeat the first at the end.

Return type:

Contour

Returns:

Contour

Examples

>>> square = polygon_contour([0, 1, 1 + 1j, 1j])
>>> complex(square.gamma(2.5))
(0.5+1j)
mathematicskit.complex_analysis.residue(f, z0, radius=0.01, n_points=256)[source]#

The residue of f at an isolated singularity z0.

Parameters:
  • f (Callable) – Vectorized f(z) -> complex.

  • z0 (complex)

  • radius (float) – Radius of the integration circle; must be smaller than the distance from z0 to any other singularity.

  • n_points (int) – Trapezoidal-rule nodes; must exceed the order of the pole.

Return type:

complex

Returns:

complex

Examples

>>> r = residue(lambda z: np.exp(z) / z**3, 0.0)  # coefficient of 1/z is 1/2!
>>> bool(np.isclose(r, 0.5))
True
mathematicskit.complex_analysis.residue_theorem(f, contour, poles, residue_radius=None)[source]#

Compare \(\oint_\gamma f\) with \(2\pi i \sum_k n(\gamma, z_k)\operatorname{Res}(f, z_k)\).

Parameters:
  • f (Callable) – Vectorized and meromorphic, with singularities only at poles.

  • contour (Contour) – Closed, not passing through any pole.

  • poles (Sequence[complex]) – Every singularity of f (those outside the contour contribute winding number zero).

  • residue_radius (float | None) – Radius used by residue(); defaults to a quarter of the smallest distance between poles, capped at 0.1.

Return type:

ResidueTheoremResult

Returns:

ResidueTheoremResult

Examples

>>> from mathematicskit.complex_analysis.systems.contours import circle_contour
>>> f = lambda z: 1 / ((z - 0.5) * (z - 3))
>>> result = residue_theorem(f, circle_contour(), [0.5, 3])
>>> result.winding_numbers.tolist()
[1, 0]
>>> bool(np.isclose(result.integral, result.predicted))
True
mathematicskit.complex_analysis.rouche_condition(f, g, contour, n_points=4000)[source]#

Whether \(|f(z) - g(z)| < |g(z)|\) at every sampled point of contour.

By Rouché’s theorem, when this holds, \(f\) and \(g\) have the same number of zeros inside the contour (with multiplicity), so a hard-to-count f can be compared with a simple dominant term g. The check is on n_points samples, not a proof. See E. Rouché, “Mémoire sur la série de Lagrange,” Journal de l’École Polytechnique 22 (1862), 193-224; Ahlfors, Complex Analysis, Ch. 4, Sec. 5.2.

Parameters:
  • f (Callable) – Vectorized and holomorphic inside and on the contour.

  • g (Callable) – Vectorized and holomorphic inside and on the contour.

  • contour (Contour)

  • n_points (int)

Return type:

bool

Returns:

bool

Examples

>>> from mathematicskit.complex_analysis.systems.contours import circle_contour
>>> f = lambda z: z**5 + 3 * z**2 + 1
>>> rouche_condition(f, lambda z: 3 * z**2, circle_contour()), rouche_condition(f, lambda z: z**5, circle_contour(0, 2))
(True, True)
mathematicskit.complex_analysis.winding_number(contour, z0)[source]#

The winding number \(n(\gamma, z_0) = \frac{1}{2\pi i}\oint_\gamma \frac{dz}{z - z_0}\).

Parameters:
  • contour (Contour) – A closed contour not passing through z0.

  • z0 (complex)

Return type:

int

Returns:

int – The integral rounded to the nearest integer (it is an integer in exact arithmetic).

Examples

>>> winding_number(circle_contour(), 0.5j), winding_number(circle_contour(), 2.0)
(1, 0)