Utils
cax.utils.render
Utilities for rendering.
rgba_to_rgb(array)
Convert a premultiplied RGBA image to RGB by alpha compositing over white.
RGBA arrays in CAX are premultiplied: colour is already scaled by alpha, so a pixel
holds the light it emits and compositing over white is rgb + (1 - alpha).
Targets from get_emoji_array and the RGBA channels of a neural cellular
automaton state follow this convention.
The function assumes the last dimension encodes channels and that the input is
normalized to the range [0, 1] with shape (..., 4). The output preserves the
input shape except for the channel dimension, which becomes 3.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array
|
Array
|
Premultiplied RGBA image with shape |
required |
Returns:
| Type | Description |
|---|---|
Array
|
RGB image with shape |
Source code in src/cax/utils/render.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |
rgb_to_hsv(rgb)
Convert RGB to HSV.
Input and output are in the range [0, 1] and use channel-last layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rgb
|
Array
|
RGB image with shape |
required |
Returns:
| Type | Description |
|---|---|
Array
|
HSV image with shape |
Source code in src/cax/utils/render.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
hsv_to_rgb(hsv)
Convert HSV to RGB.
Input and output are in the range [0, 1] and use channel-last layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hsv
|
Array
|
HSV image with shape |
required |
Returns:
| Type | Description |
|---|---|
Array
|
RGB image with shape |
Source code in src/cax/utils/render.py
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
clip_and_uint8(frame)
Clip a floating-point image to [0, 1] and convert to uint8.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Array
|
Image-like array with values expected in or near |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Array of dtype |
Source code in src/cax/utils/render.py
119 120 121 122 123 124 125 126 127 128 129 130 | |
render_array_with_channels_to_rgb(array)
Render an array with channels as an RGB image.
This function processes an input array and converts it into an RGB image based on the number of channels present in the array. The conversion logic is as follows: - If the array has 1 channel, it is repeated across the RGB channels to produce a grayscale image. - If the array has 2 channels, the first channel is interpreted as hue and the second as saturation. These are converted to RGB using a fixed brightness value, resulting in a colorful representation. - If the array has 3 or more channels, the last three channels are used directly as the RGB values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array
|
Array
|
Input array with shape |
required |
Returns:
| Type | Description |
|---|---|
Array
|
RGB array with shape |
Source code in src/cax/utils/render.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
render_array_with_channels_to_rgba(array)
Render an array with channels as an RGBA image.
This function processes an input array and converts it into an RGBA image based on the number of channels present in the array. The conversion logic is as follows: - If the array has 1 channel, it is repeated across the RGBA channels. - If the array has 2 channels, the first channel is used for RGB, and the second for alpha. - If the array has 3 channels, the first channel is interpreted as hue and the second as saturation. These are converted to RGB using a fixed brightness value, and the last channel is used as the alpha channel. - If the array has 4 or more channels, the last four channels are used directly as RGBA.
The result is premultiplied, as every RGBA array in CAX is (see rgba_to_rgb):
the colour built from one to three channels is scaled by the alpha before it is
returned, and four or more channels are taken to be premultiplied already.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array
|
Array
|
Input array with shape |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Premultiplied RGBA array with shape |
Source code in src/cax/utils/render.py
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | |
pixel_grid(resolution, *, low=0.0, high=1.0)
Build a square grid of pixel-center coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resolution
|
int
|
Number of pixels along each side. |
required |
low
|
float
|
Coordinate of the first pixel along each axis. |
0.0
|
high
|
float
|
Coordinate of the last pixel along each axis. |
1.0
|
Returns:
| Type | Description |
|---|---|
Array
|
Array with shape |
Source code in src/cax/utils/render.py
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | |
nearest_point(grid, points)
Find the nearest of points for every grid pixel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
Array
|
Pixel coordinates with shape |
required |
points
|
Array
|
Point coordinates with shape |
required |
Returns:
| Type | Description |
|---|---|
tuple[Array, Array]
|
A |
Source code in src/cax/utils/render.py
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | |
soft_disk_mask(min_distance_sq, radius)
Anti-aliased disk coverage from squared distances to the nearest point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_distance_sq
|
Array
|
Squared distance to the nearest point per pixel. |
required |
radius
|
float
|
Disk radius in the grid's coordinate space. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Coverage in |
Source code in src/cax/utils/render.py
257 258 259 260 261 262 263 264 265 266 267 268 | |
hex_to_square(array)
Resample a triangular-lattice array onto a square pixel grid.
A triangular lattice is stored in an ordinary square array whose axes stand for the
lattice vectors (1, 0) and (1/2, sqrt(3)/2) rather than for a Cartesian frame.
Drawn directly such an array leans over, because the viewer reads its axes as
perpendicular when they are sixty degrees apart. This maps each output pixel back
through the basis and samples there, so what is drawn is the lattice as it actually
sits in the plane.
The lattice is treated as periodic, matching the wrap-around a cellular automaton on a torus already assumes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array
|
Array
|
Values on a triangular lattice, with shape |
required |
Returns:
| Type | Description |
|---|---|
Array
|
An array of the same shape, holding the lattice resampled onto square pixels. |
Source code in src/cax/utils/render.py
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | |
square_to_hex(array)
Resample a square-pixel array onto a triangular lattice.
The inverse of hex_to_square, and what a picture needs before it is placed on a
triangular lattice: written in directly it would be sheared, and a shape that is no
longer itself is no longer sustained by a rule that was tuned to it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array
|
Array
|
Values on square pixels, with shape |
required |
Returns:
| Type | Description |
|---|---|
Array
|
An array of the same shape, holding the picture resampled onto the lattice. |
Source code in src/cax/utils/render.py
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | |
render_states(cs, states, **kwargs)
Render every state of a trajectory, one frame at a time.
Rendering a particle system costs one (resolution^2, num_particles) array per
frame, which is far larger than the frame it produces. Vectorizing over a whole
trajectory asks for all of them at once — terabytes for a long run — and survives
only where the compiler happens to fuse the intermediate away, so the same notebook
runs on an accelerator and dies on a CPU. Scanning over the trajectory bounds the
peak at a single frame whatever the backend, and costs nothing: the frames are
independent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cs
|
_Renderable
|
The complex system, whose |
required |
states
|
Any
|
A trajectory: a pytree whose leaves have the time steps on axis 0. |
required |
**kwargs
|
Any
|
Forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
Array
|
The rendered frames, with shape |
Source code in src/cax/utils/render.py
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | |
cax.utils.emoji
Utilities for emojis.
get_image_from_url(url)
Fetch an image from a given URL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The URL of the image to fetch. |
required |
Returns:
| Type | Description |
|---|---|
Image
|
The fetched image as a PIL Image object. |
Raises:
| Type | Description |
|---|---|
ConnectionError
|
If the download fails — most commonly because the machine is offline. The original error is chained. |
Source code in src/cax/utils/emoji.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
get_emoji_filename(emoji)
Build the Noto Emoji filename for an emoji.
Noto names a glyph after the codepoints that spell it, in lowercase hexadecimal, joined by underscores. Sequences are spelled out in full, so the zero-width joiner of a glyph like 👨💻 is part of the name, while the variation selector that merely asks for an emoji presentation is not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
emoji
|
str
|
The emoji character or sequence. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The filename, such as |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/cax/utils/emoji.py
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
get_emoji(emoji)
cached
Fetch and return an emoji as a PIL Image.
The glyph is downloaded from Google's Noto Emoji (PNG, 128 px) and cached in memory, so repeated calls for the same emoji fetch once. The image is returned without further processing; callers may convert to arrays or resize as needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
emoji
|
str
|
The emoji character or sequence to fetch. |
required |
Returns:
| Type | Description |
|---|---|
Image
|
A |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the emoji has no Noto glyph under this naming scheme. |
ConnectionError
|
If the download fails. |
Source code in src/cax/utils/emoji.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
get_emoji_array(emoji, size, pad_width=0)
Fetch an emoji as a padded, premultiplied RGBA array.
The glyph is resized to size and framed in transparent pixels, which is what a
growing cellular automaton needs: the target sits in the middle of a larger grid, so
the automaton has somewhere to overshoot into and can be penalised for doing so.
Colour is premultiplied by alpha, CAX's convention for RGBA arrays: each pixel
holds the light it emits, so a transparent pixel is zero in every channel and a
loss on the array measures what is seen rather than the colour a PNG stores behind
invisible pixels. rgba_to_rgb composites arrays in this convention.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
emoji
|
str
|
The emoji character or sequence to fetch. |
required |
size
|
int
|
Width and height, in pixels, to resize the glyph to. |
required |
pad_width
|
int
|
Transparent pixels to add on each side. |
0
|
Returns:
| Type | Description |
|---|---|
Array
|
An array of shape |
Array
|
premultiplied RGBA values in the unit interval. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the emoji has no Noto glyph under this naming scheme. |
ConnectionError
|
If the download fails. |
Source code in src/cax/utils/emoji.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
cax.utils.numerics
Numerically safe primitives for differentiable simulation.
JAX propagates cotangents through both branches of jnp.where, so masking an invalid
value after it has been computed leaves nan in the gradient even when the forward pass
is finite — the documented "where-NaN" trap. The library-wide convention is therefore to
sanitize the input of the unsafe operation, not its output: every division, norm, or
singular kernel evaluated where its argument can be degenerate goes through one of these
helpers (or repeats their double-where pattern inline, with a comment naming it).
safe_divide(numerator, denominator, *, where)
Divide two arrays, returning zero and a clean gradient where invalid.
Uses the double-where pattern: the denominator is replaced by one at invalid
positions before dividing, so neither the forward value nor the gradient ever
touches the singular point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
Array
|
Numerator array. |
required |
denominator
|
Array
|
Denominator array, broadcastable against |
required |
where
|
Array
|
Boolean mask, true where the division is valid. Broadcastable against the result; invalid positions yield zero. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
|
Source code in src/cax/utils/numerics.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
safe_norm(vector, *, axis=-1, keepdims=False)
Euclidean norm with a finite gradient at the origin.
jnp.linalg.norm differentiates to x / ||x||, which is nan at zero. This
computes the same value but returns a zero gradient at the origin, which is the
convention steering and force computations want: a vanished vector exerts no pull.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vector
|
Array
|
Input array. |
required |
axis
|
int
|
Axis holding the vector components. |
-1
|
keepdims
|
bool
|
Whether the reduced axis is kept with size one. |
False
|
Returns:
| Type | Description |
|---|---|
Array
|
Norm of |
Source code in src/cax/utils/numerics.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
cax.utils.dynamics
Shared dynamics primitives for particle systems on the unit torus.
Boids and Particle Life integrate the same way: exponential velocity damping, a semi-implicit Euler step, and periodic boundary conditions. These helpers hold that shared physics in one place; each system keeps its own defaults and state types.
toroidal_difference(position_1, position_2, *, period=1.0)
Minimum-image vector from position_1 to position_2 on a torus.
Applies periodic boundary conditions component-wise so each component of the
result lies in [-period / 2, period / 2] — the shortest displacement on a
torus of the given period.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position_1
|
Array
|
Start positions. |
required |
position_2
|
Array
|
End positions, broadcastable against |
required |
period
|
float
|
Length of the torus along every axis. |
1.0
|
Returns:
| Type | Description |
|---|---|
Array
|
Component-wise shortest displacement from |
Source code in src/cax/utils/dynamics.py
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
damped_euler_step(position, velocity, acceleration, *, dt, friction_factor, period=1.0)
Semi-implicit Euler step with velocity damping and periodic boundaries.
The velocity is damped by friction_factor (typically 0.5 ** (dt / half_life))
and accelerated, then the position is advanced with the new velocity and wrapped
onto the torus.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position
|
Array
|
Positions on the torus. |
required |
velocity
|
Array
|
Velocities. |
required |
acceleration
|
Array
|
Accelerations from the perception step. |
required |
dt
|
float
|
Time step. |
required |
friction_factor
|
float
|
Multiplicative velocity decay per step. |
required |
period
|
float
|
Length of the torus along every axis. |
1.0
|
Returns:
| Type | Description |
|---|---|
tuple[Array, Array]
|
A |
Source code in src/cax/utils/dynamics.py
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |