Skip to content

Modulus scaling

How |f(z)| becomes height or displacement. The most consequential setting in any 3D render.

complexplorer.ModulusScaling

Collection of modulus scaling methods for visualization.

These methods map the modulus |f(z)| to a radius value, allowing visualizations to show both phase and magnitude information.

constant staticmethod

constant(moduli: ndarray, radius: float = 1.0) -> np.ndarray

Constant radius regardless of modulus.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
radius float

Constant radius value.

1.0

Returns:

Type Description
ndarray

Array of constant radius values.

linear staticmethod

linear(moduli: ndarray, scale: float = 0.1) -> np.ndarray

Linear scaling: r = 1 + scale * |f(z)|.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
scale float

Scaling factor.

0.1

Returns:

Type Description
ndarray

Linearly scaled radius values.

arctan staticmethod

arctan(moduli: ndarray, r_min: float = 0.5, r_max: float = 1.5) -> np.ndarray

Smooth scaling using arctangent.

Maps [0, ∞) to [r_min, r_max] smoothly.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
r_min float

Minimum radius value.

0.5
r_max float

Maximum radius value.

1.5

Returns:

Type Description
ndarray

Smoothly scaled radius values.

logarithmic staticmethod

logarithmic(moduli: ndarray, base: float = np.e, r_min: float = 0.5, r_max: float = 1.5) -> np.ndarray

Logarithmic scaling for large dynamic range.

Good for functions with exponential growth.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
base float

Logarithm base.

e
r_min float

Minimum radius value.

0.5
r_max float

Maximum radius value.

1.5

Returns:

Type Description
ndarray

Logarithmically scaled radius values.

log_mixture staticmethod

log_mixture(moduli: ndarray, scale: float = 2.0, boost: float = 3.0, weight: float = 0.5, r_min: float = 0.2, r_max: float = 1.0) -> np.ndarray

Two-scale logistic in the log-modulus: bulk contrast without blunting the tips.

A single logistic makes one parameter do two jobs. Lowering scale adds contrast across the bulk of the surface but blunts the tips (the tip exponent mu / scale rises above 1); raising it sharpens the tips but squeezes the whole body toward mid-radius. On a shape with only a few widely separated features the result is a near-perfect sphere with features poked into it.

So this mixes two: a narrow logistic at scale / boost carrying bulk contrast, and the original wide one at scale carrying the tips, weighted weight to 1 - weight.

Both terms are odd in log|f| about zero and the weights sum to one, so the transfer stays self-dual -- a zero of order k carves the mirror image of what a pole of order k raises, and normalization keeps working. It is also smooth at |f| = 1, which matters more than it sounds: a signed power of the log modulus is monotone, odd and tempting, but has infinite derivative at sea level, creases the surface along every sea-level contour, and splits the mesh seam badly enough that the weld fails and the solid never closes.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
scale float

The wide logistic's e-folding in log modulus, as in logarithmic's ln(base). The tip exponent is mu / scale for a feature of order mu.

2.0
boost float

How much narrower the bulk-contrast logistic is. Must be greater than zero; values around 3 to 4 are what the reference collection uses.

3.0
weight float

Share given to the narrow term, in [0, 1].

0.5
r_min float

Radius bounds. r_min is the relief depth.

0.2
r_max float

Radius bounds. r_min is the relief depth.

0.2

Returns:

Type Description
ndarray

Radius values in [r_min, r_max].

Raises:

Type Description
ValidationError

If scale or boost is not positive, or weight is outside [0, 1].

Notes

Honest limit: asymptotically the wide term still sets the tip exponent, but that asymptote lies below mesh resolution, so in practice the tip climbs through only the last 1 - weight of the range and the visible point is measurably shorter than the exponent implies.

linear_clamp staticmethod

linear_clamp(moduli: ndarray, m_max: float = 10, r_min: float = 0.5, r_max: float = 1.5) -> np.ndarray

Linear scaling with clamping.

Linear up to m_max, then constant.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
m_max float

Maximum modulus value before clamping.

10
r_min float

Minimum radius value.

0.5
r_max float

Maximum radius value.

1.5

Returns:

Type Description
ndarray

Linearly scaled and clamped radius values.

power staticmethod

power(moduli: ndarray, exponent: float = 0.5, r_min: float = 0.5, r_max: float = 1.5) -> np.ndarray

Power scaling: r = r_min + (r_max - r_min) * (|f|/|f|_max)^exponent.

Exponent < 1 compresses large values, > 1 expands them.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
exponent float

Power exponent.

0.5
r_min float

Minimum radius value.

0.5
r_max float

Maximum radius value.

1.5

Returns:

Type Description
ndarray

Power-scaled radius values.

custom staticmethod

custom(moduli: ndarray, scaling_func: Callable[[ndarray], ndarray], r_min: float = 0.5, r_max: float = 1.5) -> np.ndarray

Custom scaling function.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
scaling_func callable

User-defined function that maps moduli to [0, 1].

required
r_min float

Minimum radius value.

0.5
r_max float

Maximum radius value.

1.5

Returns:

Type Description
ndarray

Custom-scaled radius values.

sigmoid staticmethod

sigmoid(moduli: ndarray, steepness: float = 2.0, center: float = 1.0, r_min: float = 0.5, r_max: float = 1.5) -> np.ndarray

Sigmoid (S-curve) scaling.

Provides smooth transition with adjustable steepness and center. Good general-purpose scaling for most functions.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
steepness float

Controls transition sharpness (higher = steeper).

2.0
center float

Center of transition (where r ≈ (r_min + r_max) / 2).

1.0
r_min float

Minimum radius value.

0.5
r_max float

Maximum radius value.

1.5

Returns:

Type Description
ndarray

Sigmoid-scaled radius values.

adaptive staticmethod

adaptive(moduli: ndarray, low_percentile: float = 10, high_percentile: float = 90, r_min: float = 0.5, r_max: float = 1.5) -> np.ndarray

Adaptive percentile-based scaling.

Automatically adjusts to data range, ignoring outliers. Excellent for unknown functions or those with extreme values.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
low_percentile float

Lower percentile for mapping to r_min.

10
high_percentile float

Upper percentile for mapping to r_max.

90
r_min float

Minimum radius value.

0.5
r_max float

Maximum radius value.

1.5

Returns:

Type Description
ndarray

Adaptively scaled radius values.

hybrid staticmethod

hybrid(moduli: ndarray, transition: float = 1.0, r_min: float = 0.5, r_max: float = 1.5) -> np.ndarray

Hybrid linear-logarithmic scaling.

Linear for |f| < transition, logarithmic for larger values. Ideal for functions with detailed behavior near zero.

Parameters:

Name Type Description Default
moduli ndarray

Modulus values |f(z)|.

required
transition float

Transition point between linear and logarithmic.

1.0
r_min float

Minimum radius value.

0.5
r_max float

Maximum radius value.

1.5

Returns:

Type Description
ndarray

Hybrid-scaled radius values.

complexplorer.get_scaling_preset

get_scaling_preset(name: str) -> dict

Get a predefined scaling configuration.

Parameters:

Name Type Description Default
name str

Name of the preset. Available presets: - 'balanced': General purpose sigmoid scaling - 'detail_near_zero': Emphasizes small values - 'auto': Adaptive scaling for unknown functions - 'high_contrast': High contrast with steep transition - 'poles_emphasis': Emphasizes pole behavior

required

Returns:

Type Description
dict

Dictionary with 'method' and 'params' keys.

Raises:

Type Description
ValidationError

If preset name is not recognized.

Normalization

Sea level sits at |f| = 1 for every self-dual transfer, so the arbitrary constant in front of a function changes the shape of a relief rather than only its labels. These compute the constant that puts the area-weighted geometric mean of |f| over the sphere at 1, which removes that dependence.

normalization_constant is the exact closed form for a rational function, and needs a complete divisor. sampled_normalization_constant estimates the same quantity from samples, needs no divisor, and is what the ornament path uses by default.

complexplorer.normalization_constant

normalization_constant(zeros: Sequence[complex] | ndarray, poles: Sequence[complex] | ndarray, gain: complex = 1.0) -> float

Closed-form constant placing the geometric mean of |c * f| over the sphere at 1.

For a rational f(z) = gain * prod(z - z_j) / prod(z - p_k) over its finite zeros and poles, the constant is 1 / |gain| times the product over the poles of sqrt(1 + |p_k|^2), divided by the product over the zeros of sqrt(1 + |z_j|^2). That closed form exists because the log of the chordal distance to a fixed point integrates to -1/2 over the sphere, whatever that point is.

Normalizing by this constant makes the relief independent of the arbitrary scalar in front of f. Sea level sits at |f| = 1 for every self-dual transfer, so that scalar otherwise changes the ornament's shape rather than only its labels.

The constant is self-dual: exchanging the zeros and the poles returns its reciprocal, so the relief of 1/f is the relief of f turned inside out.

Parameters:

Name Type Description Default
zeros sequence of complex

The finite zeros of f, with multiplicity. A zero at infinity is not listed.

required
poles sequence of complex

The finite poles of f, with multiplicity.

required
gain complex

The leading coefficient. Only its magnitude matters.

1.0

Returns:

Type Description
float

The normalization constant.

Raises:

Type Description
ValidationError

If gain is zero, or a zero or pole is not finite.

Warnings

This is exact only for a rational function given by a complete divisor: every finite zero and every finite pole, with multiplicity. It does not apply to a transcendental function, nor to a partial list of singularities, and it fails silently in both cases -- it returns a confidently wrong number. Fed the three listed zeros of sin(z), which has infinitely many, it returns 0.092 against a true 0.853. In particular, do not assemble a divisor from a function preset's singularities field, which is illustrative rather than complete. When the divisor is not known to be complete, estimate the constant from samples instead, with :func:sampled_normalization_constant, which has no such failure mode.

Examples:

>>> import numpy as np
>>> round(normalization_constant([0.0], np.exp(2j * np.pi * np.arange(10) / 10)), 6)
32.0
>>> round(normalization_constant([], [0.0], gain=3.0), 6)
0.333333

complexplorer.sampled_normalization_constant

sampled_normalization_constant(modulus: ndarray, sphere_z: ndarray, *, statistic: str = 'geometric') -> float

Estimate the normalization constant from moduli sampled on a Riemann-sphere grid.

Takes the samples a relief is already built from, so no second pass over f is needed. Unlike :func:normalization_constant it needs no divisor, and so has no silent failure mode on a transcendental function or an incomplete list of singularities.

Both grid corrections below are matters of correctness rather than precision:

  • Area weighting. A latitude/longitude grid crowds the poles, so each sample is weighted by sin(theta) = sqrt(1 - z^2). Unweighted, the constant for z/(z^10-1) -- whose feature of order ten sits at infinity, at a pole of the grid -- comes out about ten times wrong.
  • The duplicated seam. sphere_coordinates builds phi as linspace(0, 2*pi, resolution), whose first and last entries are the same meridian, so that meridian is sampled twice. It lies along the positive real axis, exactly where a function with real coefficients puts its features: counted twice it biases (z-1)/(z+1) by about 1%, against 0.01% with it dropped.

Samples where log|f| is not finite -- the zeros and the poles themselves -- are excluded.

Parameters:

Name Type Description Default
modulus ndarray

|f| on a sample_sphere grid, shaped (n_phi, n_theta): axis 0 is longitude, whose first and last rows are the duplicated seam meridian.

required
sphere_z ndarray

The z coordinate of each sample, the same shape, i.e. field.sphere_xyz[..., 2].

required
statistic (geometric, median)

Which location statistic of log|f| to centre on. 'geometric' gives the area-weighted geometric mean of |f|; 'median' gives the area-weighted median, which is more robust when a high-order feature at infinity covers enough of the sphere to drag sea level away from the structure worth seeing. Both are self-dual.

'geometric'

Returns:

Type Description
float

The estimated constant, or 1.0 when no usable sample remains.

Raises:

Type Description
ValidationError

If statistic is not recognized, or the two arrays disagree in shape.

Notes

Accuracy is limited by sample_sphere's avoid_poles clamp, which is fixed rather than shrinking with resolution, so the estimate converges to a slightly biased value rather than to the exact one. The error stays far below what a printer can resolve -- under 0.1% on the functions measured -- but a test comparing this against the closed form must use a loose tolerance and must not tighten it as resolution grows.