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 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 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
¶
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 |
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.5
|
r_min
|
float
|
Radius bounds. |
0.2
|
r_max
|
float
|
Radius bounds. |
0.2
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Radius values in |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If |
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 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 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 |
required |
poles
|
sequence of complex
|
The finite poles of |
required |
gain
|
complex
|
The leading coefficient. Only its magnitude matters. |
1.0
|
Returns:
| Type | Description |
|---|---|
float
|
The normalization constant. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If |
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:
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 forz/(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_coordinatesbuildsphiaslinspace(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
|
|
required |
sphere_z
|
ndarray
|
The |
required |
statistic
|
(geometric, median)
|
Which location statistic of |
'geometric'
|
Returns:
| Type | Description |
|---|---|
float
|
The estimated constant, or 1.0 when no usable sample remains. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If |
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.