circle_bundles.synthetic

Synthetic datasets and geometric models used for demonstrations and validation.

Typical usage

>>> from circle_bundles.synthetic import sample_s2_trivial, make_tri_prism, mesh_to_density
circle_bundles.synthetic.mesh_to_density(mesh, *, grid_size=32, sigma=0.05, n_surface_samples=5000, normalize=True, rng=None, eps=1e-12)[source]

Convert a mesh into a flattened density on a cubic grid in [-1,1]^3 by sampling the surface and placing a Gaussian in distance-to-surface.

Notes

  • The mesh is copied, centered (center_mass), and scaled into ~unit ball.

  • Deterministic sampling via rng is not guaranteed across trimesh versions; rng is accepted for API consistency but is best-effort.

Returns:

density – Flattened density values.

Return type:

(grid_size^3,) ndarray

Parameters:
circle_bundles.synthetic.get_density_axes(flat_densities, *, grid_size=32, smallest=True, return_eigs=False, eps=1e-12)[source]

Compute principal axes for a batch of 3D densities on a [-1,1]^3 grid.

For each density rho, compute weighted covariance/inertia:

M = Σ rho(x) (x - μ)(x - μ)^T

and return either:
  • smallest-eigenvalue direction (least spread), or

  • largest-eigenvalue direction (most spread).

Returns:

  • directions ((N,3))

  • ratios ((N,), optional) – If smallest: (λ_min / λ_mid), else: (λ_max / λ_mid) with a small stabilizer.

Parameters:
Return type:

ndarray | Tuple[ndarray, ndarray]

circle_bundles.synthetic.get_mesh_sample(mesh, O3_data)[source]

Apply each 3x3 matrix in O3_data to mesh vertices and return flattened vertex arrays.

Parameters:
  • mesh (trimesh.Trimesh)

  • O3_data ((n_samples, 9)) – Flattened 3x3 matrices (SO(3) or O(3)).

Returns:

mesh_samples – Each row is rotated vertices flattened in (x0,y0,z0,x1,y1,z1,…) order.

Return type:

(n_samples, 3*N)

circle_bundles.synthetic.make_tri_prism(*, height=1.0, radius=1.0)[source]

Create a triangular prism with base an equilateral triangle in the (y,z)-plane and extrusion along the x-axis.

Returns:

  • mesh (trimesh.Trimesh)

  • face_groups (list[(start, end_exclusive)]) –

    Ranges of triangle faces belonging to the 5 prism faces, in order:

    0: bottom triangle 1: top triangle 2: side face between vertices 0-1 3: side face between vertices 1-2 4: side face between vertices 2-0

Parameters:
Return type:

Tuple[Trimesh, List[Tuple[int, int]]]

circle_bundles.synthetic.make_star_pyramid(*, n_points=5, radius_outer=1.0, radius_inner=0.5, height=1.0)[source]
Create a star-based pyramid mesh:
  • base is a 2D star polygon in the yz-plane at x=0

  • apex at (height, 0, 0)

Notes

  • Triangulates the base polygon using trimesh.creation.triangulate_polygon.

  • Then connects the apex to the boundary cycle (2*n_points edges).

Returns:

mesh

Return type:

trimesh.Trimesh

Parameters:
circle_bundles.synthetic.mesh_vertex_normals(X, *, n_vertices=None, vertex_dim=3, idx=(0, 1, 2), eps=1e-12)[source]

Compute the oriented unit normal determined by three vertices from flattened mesh-vertex data.

Raises a ValueError if any triple is colinear or degenerate.

Parameters:
  • X (ndarray) – Shape (D,) for one mesh or (N, D) for batch.

  • n_vertices (int | None) – Optional expected vertex count.

  • vertex_dim (int) – Usually 3.

  • idx (tuple[int, int, int]) – (i, j, k) vertex indices used. Orientation follows cross(vj-vi, vk-vi).

  • eps (float) – Tolerance for detecting degeneracy.

Returns:

Shape (3,) or (N,3) of unit normals.

Return type:

normals

circle_bundles.synthetic.make_density_visualizer(*, grid_size=32, axis='x', cmap='inferno', normalize=False, figsize=(3.0, 3.0), dpi=150)[source]

Returns a visualization function for densities on a grid_size^3 voxel grid.

Parameters:
  • grid_size (int)

  • axis ({'x','y','z'}) – Axis to sum over.

  • cmap (str)

  • normalize (bool) – If True, normalize projection to max=1 for display.

  • figsize (Figure sizing)

  • dpi (Figure sizing)

Return type:

vis_func(density) -> matplotlib Figure

circle_bundles.synthetic.make_tri_prism_visualizer(mesh, face_groups=None, *, face_colors_list=None, alpha=1.0, show_edges=True, edge_color='black', edge_width=2.5, elev=0.0, azim=0.0, figsize=(4.0, 4.0), dpi=150, depth_sort=True)[source]

Visualize a triangular prism-style mesh with custom face group coloring.

Parameters:
  • mesh (trimesh.Trimesh-like) – Must have .vertices and .faces.

  • face_groups (groups of face indices (explicit lists or (start,end_excl) ranges))

  • depth_sort (bool) – If True, manually sorts triangles back-to-front using projected depth (helps with alpha blending / occlusion in Matplotlib).

  • face_colors_list (Sequence[str] | None)

  • alpha (float)

  • show_edges (bool)

  • edge_color (str)

  • edge_width (float)

  • elev (float)

  • azim (float)

  • figsize (Tuple[float, float])

  • dpi (int)

Returns:

flat_mesh is expected to be (n_vertices*3,) giving vertex positions.

Return type:

vis_func(flat_mesh) -> Figure

circle_bundles.synthetic.make_star_pyramid_visualizer(mesh, *, base_color='#94A3B8', edge_color='gray', alpha=1.0, colormap=None, figsize=(4.0, 4.0), dpi=150, elev=0.0, azim=0.0)[source]

Visualizer for a star pyramid mesh with a smooth gradient on side faces.

FIX: side-face ordering is computed ONCE from the template mesh vertices, so colors stay attached to the same faces under rotation.

Parameters:
Return type:

Callable[[ndarray], Figure]

circle_bundles.synthetic.sample_nat_img_kb(n_points, *, n=3, noise=0.0, rng=None, eps=1e-12, return_angles=False)[source]

Synthetic “natural image” patch model parameterizing a Klein bottle.

Returns mean-centered and L2-normalized patches built from a 1D quadratic + linear profile along a direction in RP^1 (theta folded to [0, pi)), with a corresponding alpha “fiber” angle adjusted so the model is continuous under the identification.

Parameters:
Return type:

Tuple[ndarray, ndarray] | Tuple[ndarray, ndarray, ndarray, ndarray]

circle_bundles.synthetic.get_gradient_dirs(patches, n=None, eps=1e-12)[source]

Compute predominant GRADIENT-axis angles in RP^1 for n×n grayscale patches. Returns (thetas, strengths).

Parameters:
Return type:

Tuple[ndarray, ndarray]

circle_bundles.synthetic.sample_opt_flow_torus(n_points, *, dim=3, sigma=0.0, rng=None, sample_r=False, r_min=0.6, r_lam=8.0, r_values=None, return_r=True, contrast_renorm=True, eps=1e-12)[source]

Sample the optical-flow patch torus model (dim x dim DCT basis, flattened length 2*dim^2), with RP^1 base angle theta folded to [0, pi).

Optionally extends the model with an r-parameter in [r_min, 1] that mixes each patch with its perpendicular patch, using:

perp patch = (alpha + pi/2, theta - pi/2) (then re-fold), lambda = 1/sqrt(2 - r), patch_mix = lambda*patch + sqrt(1-lambda^2)*perp.

Default behavior:
  • sample_r=True

  • r ~ truncated exponential away from 1 on [r_min, 1] (so virtually all mass near 1, but never below r_min).

Noise:
  • if sigma > 0 and contrast_renorm=True, we add Gaussian noise then contrast-renormalize.

Returns:

  • data ((n_points, 2*dim^2) ndarray)

  • base_points ((n_points, 2) ndarray (cos(theta), sin(theta)), theta in [0, pi))

  • alpha ((n_points,) ndarray)

  • r ((n_points,) ndarray if return_r=True)

Parameters:
Return type:

Tuple[ndarray, ndarray, ndarray] | Tuple[ndarray, ndarray, ndarray, ndarray]

circle_bundles.synthetic.make_flow_patches(alpha, theta, r=None, *, contrast_renorm=False, eps=1e-12)[source]

Generate samples from the extended torus optical-flow patch model (3x3, length 18).

Folds theta into [0, pi) (RP^1 base) and adjusts alpha accordingly.

If r is provided, it mixes a patch with a perpendicular patch:

patches_mix = λ * patches + sqrt(1-λ^2) * patches_perp

where λ = 1/sqrt(2-r).

If contrast_renorm=True, rescales each patch to unit “contrast norm”.

Parameters:
Return type:

ndarray

circle_bundles.synthetic.sample_sphere(n, dim=2, *, rng=None)[source]

Sample n points ~uniformly from S^{dim} ⊂ R^{dim+1} via Gaussian normalization.

Examples

dim=2 -> S^2 in R^3, output shape (n,3) dim=3 -> S^3 in R^4, output shape (n,4)

Parameters:
Return type:

ndarray

circle_bundles.synthetic.hopf_projection(data, *, v=None, eps=1e-12)[source]

Generalized Hopf projection defined by q ↦ q v q^{-1}, where v ∈ S^2 ⊂ Im(H).

Parameters:
  • data ((n,4) real or (n,2) complex) – Quaternion coordinates: q = (a,b,c,d) with z1=a+ib, z2=c+id.

  • v ((3,) array-like, optional) – Axis vector in R^3. Will be normalized. Default is e1 = (1,0,0).

  • eps (float)

Return type:

(n,3) array on S^2.

circle_bundles.synthetic.sample_s2_trivial(n_points, *, sigma=0.0, rng=None, radius_mean=1.0, radius_clip=(0.0, 5.0))[source]

Product bundle S^2 × S^1 embedded as (base ∈ R^3, fiber ∈ R^2) in R^5.

Returns:

  • data ((n_points, 5) = [base_x, base_y, base_z, fiber_u, fiber_v])

  • base_points ((n_points, 3) points on S^2)

  • angles ((n_points,) fiber angles in radians)

Parameters:
Return type:

Tuple[ndarray, ndarray, ndarray]

circle_bundles.synthetic.sample_so3(n_samples, *, rule=None, v=None, rng=None, eps=1e-12)[source]

Sample SO(3) (flattened rotation matrices) with optional structured rules.

Parameters:
  • n_samples (int) – Number of samples.

  • rule (None | 'fiber' | 'equator') –

    • None: Haar-random rotations; returns (data, base_points) where base_points are first columns.

    • ’fiber’: fix first column to v (or random) and sample a fiber angle θ;

      returns (data, theta) with theta shape (n_samples,).

    • ’equator’: choose u on great circle orthogonal to v via angle φ, then choose fiber angle θ;

      returns (data, angles) with angles shape (n_samples,2) = (phi, theta).

  • v ((3,) array-like, optional) – If None, sampled randomly for the structured rules.

  • rng (np.random.Generator, optional)

  • eps (float) – Stability floor.

Returns:

  • data ((n_samples, 9) ndarray) – Flattened rotation matrices (row-major from (n,3,3) reshape).

  • extra (ndarray) – Depends on rule (base_points / theta / [phi,theta]).

Return type:

Tuple[ndarray, ndarray]

circle_bundles.synthetic.project_o3(O3_data, v=None)[source]

Given flattened O(3) matrices (N, 9), return the image of v under each matrix.

Parameters:
  • O3_data ((N,9) ndarray) – Row-major flattened 3x3 matrices.

  • v ((3,) ndarray, optional) – Vector to project. Default is e1.

Returns:

out – (M_i @ v) for each matrix M_i.

Return type:

(N,3) ndarray

circle_bundles.synthetic.get_patch_types_list()[source]

Generate the 28 possible filament patch types (legacy convention).

Each patch type is a list of [i,j] pixel coordinates (0..2) where a vector arrow lives. For a 3x3 patch, there are 28 “filament” patterns; we later include sign flips to get 56.

Return type:

List[List[List[int]]]

circle_bundles.synthetic.make_step_edges(n_patches, spots, *, angle_range=(0.0, 6.283185307179586), normalize=True, rng=None, eps=1e-12)[source]

Generate optical-flow step edge patches as flattened vectors of length 18 (3x3x2).

Conventions

  • Patch array shaped (n, 3, 3, 2) with last axis = (u,v).

  • Flattening order is ‘F’ to match legacy notebooks.

param n_patches:

Number of patches to generate.

type n_patches:

int

param spots:

Pixel coordinates where the flow vector is placed.

type spots:

sequence of (i,j)

param angle_range:

Sample directions uniformly from [a_min, a_max]. If a_min == a_max, all patches use that fixed angle.

type angle_range:

(float, float)

param normalize:

If True, contrast-normalize using get_contrast_norms and mean-center each channel.

type normalize:

bool

param rng:

Random generator for reproducibility.

type rng:

np.random.Generator, optional

param eps:

Stability floor for normalization.

type eps:

float

returns:
  • patch_vectors ((n_patches, 18))

  • angles ((n_patches,))

Parameters:
Return type:

Tuple[ndarray, ndarray]

circle_bundles.synthetic.make_all_step_edges(angle=None, *, normalize=True)[source]

Return the 56 canonical step-edge patterns (28 types + sign flips).

If angle is None:

Returns scalar +/-1 edge patterns as (56, 9) using legacy ‘F’ convention.

If angle is not None:

Returns flow vectors (56, 18) at that fixed direction (cos(angle), sin(angle)).

Parameters:
Return type:

ndarray

circle_bundles.synthetic.sample_binary_step_edges(samples_per_filament, *, rng=None)[source]

Sample step-edge patches across all 28 filament types.

Returns:

  • patches ((28*samples_per_filament, 18))

  • angles ((28*samples_per_filament,))

Parameters:
Return type:

Tuple[ndarray, ndarray]

circle_bundles.synthetic.mean_center(patch_vector, *, copy=True)[source]

Mean-center a single patch vector (length 9 or 18).

For length 18 (flow):
  • mean-center u-channel entries (first 9)

  • mean-center v-channel entries (last 9)

Parameters:
Return type:

ndarray

circle_bundles.synthetic.sample_step_edge_torus(n_samples, *, d=3, m=10, thresh=0.01, rng=None, eps=1e-12)[source]

Generate step-edge torus samples.

Construction (legacy):
  • sample half-planes parameterized by (offset r, direction phi) inside a disk

  • build range patches by averaging sign over m×m subgrid per pixel

  • create flow patches by multiplying by (cos theta, sin theta)

  • keep high-contrast patches (contrast norm > thresh)

  • mean-center & contrast normalize

Parameters:
  • n_samples (int) – Number of candidate samples before thresholding.

  • d (int) – Patch width/height (typically 3).

  • m (int) – Subgrid resolution per pixel for averaging.

  • thresh (float) – Keep only samples with contrast norm > thresh.

  • rng (np.random.Generator, optional)

  • eps (float) – Stability floor.

Returns:

  • flow_patches ((N_kept, 2*d^2))

  • coords ((N_kept, 3) where columns are (offset, phi, theta))

  • range_patches ((N_kept, d^2))

  • norms ((N_kept,) contrast norms (pre-normalization))

Return type:

Tuple[ndarray, ndarray, ndarray, ndarray]

circle_bundles.synthetic.sample_foldy_klein_bottle(n, *, amp_fiber=1.0, amp_base=1.0, base_radius=1.0, noise=0.05, rigid_motion=True, translate_scale=0.0, det_sign=1, rng=None)[source]
Like sample_foldy_klein_bottle, but optionally:
  • embed the base circle nonlinearly in R^4 (using folded_circle_r4),

  • then apply a global rigid motion in ambient space to intermix coordinates.

Returns:

  • X ((n, D) array) – Ambient coordinates. D=6 if base is 2D circle; D=8 if base is 4D folded.

  • t ((n,) array) – Base parameter (ground truth).

Parameters:
Return type:

Tuple[ndarray, ndarray]

circle_bundles.synthetic.sample_R3_torus(n_points, *, R=2.0, r_center=0.8, r_amplitude=0.3, r_frequency=2, r_phase=0.0, sigma=0.0, rng=None, require_ring_torus=True, return_theta=False, return_alpha=False)[source]

Sample a torus-of-revolution in R^3 where the tube (fiber) radius varies sinusoidally with base angle theta.

Parameterization:

r(theta) = r_center + r_amplitude * sin(r_frequency * theta + r_phase)

x = (R + r(theta) cos(alpha)) cos(theta) y = (R + r(theta) cos(alpha)) sin(theta) z = r(theta) sin(alpha)

Returns:

  • data ((n_points, 3))

  • base_points ((n_points, 2) = (cos(theta), sin(theta)))

  • alpha ((n_points,) fiber angle)

  • theta ((n_points,) base angle (optional))

  • r_vals ((n_points,) realized tube radius after noise (only returned when) – return_theta and return_alpha are both True)

Parameters:
Return type:

Tuple[ndarray, ndarray, ndarray] | Tuple[ndarray, ndarray, ndarray, ndarray] | Tuple[ndarray, ndarray, ndarray, ndarray, ndarray]