IFS Encyclopedia

ifslib — Advanced API

This page covers the analytics and advanced features of ifslib. For the basic rendering API (instantiation, rendering pipeline, minimal example), see ifslib.

Mathematical Capabilities

Beyond rendering, ifslib implements several non-trivial algorithms from IFS theory:

Hausdorff dimension

information("Dimension") computes the Hausdorff dimension of the attractor using the substitution matrix of the IFS graph. For a self-similar IFS satisfying the open set condition (OSC), the dimension d is the solution to the Moran equation ∑ rid = 1. For more general (non-conformal, GIFS) systems, ifslib builds the substitution matrix, finds its spectral radius λ with respect to the expansion factor p, and returns d = log λ / log p. The output also includes the minimal polynomial of λ.

Geometric measure

information("Measure") computes the natural self-similar measure on the attractor and from it extracts: the centroid C, the principal inertia tensor I, and the eigenvectors Q. Together these give the aspect ratio and natural orientation of the attractor — useful for canonical placement in illustrations. Fixed points (dimension-0 components) are also listed.

Affine subspace structure

information("Subspaces") computes, for each attractor set, the minimal affine subspace (or union thereof) that contains it. Output is a set of spanning points per piece. This determines whether boundary pieces are truly straight (contained in a hyperplane of dimension lower than the ambient space) — a key step in verifying that a tiling is polyhedral.

Neighbor intersection graph and OSC

calc_neighbor_graph() computes the complete self-similar neighbor graph of the IFS: for each pair of tiles that share a boundary, it finds the conjugacy map between them using exact rational arithmetic. The result includes the total number of neighbours (m_neigh in the output struct) — the number of distinct neighbor conjugacy classes in the graph. As a side-product it verifies the open set condition (OSC): if any two tiles overlap in a set of positive measure, the algorithm detects this and reports the depth at which OSC is violated. The computation is exact for IFS with rational (integer or fractional) coefficients.

Boundary IFS and boundary dimension

To compute only the boundary dimension value, use calc_neighbor_graph() followed by calc_boundary_dim() — this is the simplest path and is used by the analytics worker on this site.

For the full boundary IFS program, custom_ifs(0, 2) uses the neighbor graph to construct a new IFS — the boundary IFS — whose attractor is exactly the boundary ∂A of the original tile. Each boundary piece corresponds to the intersection of two neighboring copies of the tile. Loading this boundary IFS back into ifslib and running information("Dimension") on it yields dimH(∂A). This full pipeline is needed when you also want the boundary AIFS program or the characteristic polynomial of the dimension.

Higher-order intersections

Passing lim ≥ 3 to custom_ifs extends the construction to higher-order intersections: triples, quadruples, … of simultaneously neighboring tiles. The j* variables in the output IFS are the attractors of these higher-order intersection pieces.

If any j* component has positive Hausdorff dimension, some triple intersection of neighboring tiles contains more than one point, which means the tile is not disk-like. Conversely, if all triple intersections are at most single points (dimension 0), that is a necessary condition for disk-likeness — but not sufficient on its own.

API Reference

Attractor metrics

These functions return numeric properties of the currently selected root attractor. All must be called after set_block(). Returned pointers are valid until the next ifslib call and must not be freed.

ExportSignatureDescription
root_hdim() () → f64 Hausdorff dimension of the current root attractor. Returns 0 for a single fixed point, 2 for a solid 2D tile, etc. Returns −1 if the attractor is empty, NaN on failure.
root_measure() () → f64 Normalized d-dimensional Hausdorff measure of the current root attractor, where the normalization is chosen so that the sum of measures over all attractors in the same connected component of the IFS graph equals 1 (where d = root_hdim()). For dim=0: 1 for a single point, 2 for finitely many, ∞ for infinitely many.
root_enclosing_ball() () → i32 Approximate enclosing ball. Returns a pointer to DIM+1 f64 values: [radius, cx, cy, …]. The radius is at most 3/2 of the minimal enclosing ball. Returns 0 (null) if no block/root is selected or the attractor is empty.
root_mass_center() () → i32 Center of mass of the root attractor. Returns a pointer to DIM f64 values [x, y, …] in rendering subspace coordinates. Returns 0 on error.
root_mass_moments() () → i32 Eigenvalues of the inertia tensor (principal moments), in ascending order. Returns a pointer to DIM f64 values. Equal moments indicate rotational symmetry. Returns 0 on error.
root_mass_matrix() () → i32 Principal-axes matrix of the inertia tensor, stored column-major (DIM×DIM f64 values). Column k is the unit eigenvector for root_mass_moments()[k]. Returns 0 on error.
calc_diams(max_queue, max_result) (i32, i32) → i32 Compute all geometric diameters of the root attractor. Returns a pointer to an array [N, a1[0]…a1[DIM-1], b1[0]…b1[DIM-1], …] where N is the number of diameter pairs and each pair is two endpoints (2×DIM values). Total elements: 1 + N×2×DIM. max_queue controls search budget; max_result caps the number of pairs. Returns 0 on error.
calc_dists(pt, dim, max_queue, max_result) (i32, i32, i32, i32) → i32 Compute all points on the root attractor farthest from the given point pt (pointer to dim f64 values in WASM memory, where dim must equal the rendering dimension). Returns a pointer to [N, a1[0]…a1[DIM-1], …], total 1 + N×DIM elements. Returns 0 on error.

Analytics

ExportDescription
information(what) Compute analytics for the selected block. what is a C string. Output goes to get_last_output(). Values: "Measure", "Dimension", "Subspaces", "Projection", "Balls", "Evaluation", "Components", "AST".
calc_neighbor_graph(iresPtr, settingsPtr)

Compute the neighbor intersection graph (needed for boundary dimension and boundary IFS generation). Must be called after set_block. Returns 1 on success, 0 on failure.

iresPtr — output: pointer to a caller-allocated inter_result struct (24 bytes, align 4):

OffsetTypeFieldMeaning
0uint32m_gcxintersections checked
4uint32m_depthtree depth reached
8uint32m_bitsrational precision bits used; 0 = non-exact IFS (sin/cos/decimals)
12uint32m_neighnumber of neighbours found (valid when m_completed = 1)
16uint32m_over_depth0 = OSC holds; >0 = OSC violated at this depth
20uint8m_completed1 = fully computed; 0 = cut short by budget
21uint8m_overflowed1 = rational overflow occurred
22uint8m_mode0 = rational, 1 = big_rational, 2 = real
23uint8(padding)

settingsPtr — input: pointer to a settings struct (20 bytes, align 4):

OffsetTypeFieldDefaultMeaning
0uint32max_inters4000max elements in search tree; try 8000 → 50000 → 200000 for complex IFS
4uint32max_depth0xFFFFFFFFmax tree depth; no limit
8uint32max_bits63rational precision bits; >63 = big-rational (slow)
12float32prec0.00.0 = exact rational arithmetic
16uint8mode_ori01 = orientation-finding mode
17uint8stop_on_overlap1stop vertex on first overlap found
18uint8stop_on_incomplete1abort if any vertex cannot be fully processed
19uint8(padding)
custom_ifs(bitmask, lim) Generate derived AIFS output from the computed neighbor graph. bitmask=0, lim=2 generates the boundary IFS for every attractor. Individual bits: 0=intersections, 1=connections, 2=neighbourhoods, 3=neighbourhood graph, 4=relators, 5=alternative boundary. lim ≥ 3 also enumerates triple/higher-order intersections (j* variables). Output goes to get_last_output().
calc_boundary_dim() Compute dimH(∂A) directly from the neighbor graph computed by calc_neighbor_graph(). Returns the dimension as a f64. Special return values: 0 = tiles touch only at isolated points (0-dimensional contacts, e.g. Sierpiński triangle); −1 = tiles are completely disjoint (no contacts at all); NaN = error (call get_last_output() for the message). Preferred API when only the dimension value is needed — simpler than the full custom_ifsinformation("Dimension") pipeline.

Boundary Dimension — Example

Helper functions for writing/reading the C structs, plus the full boundary dimension pipeline:

// Write integer_ims::settings (20 bytes, align 4)
function writeSettings(wasm, maxInters = 4000, maxDepth = 0xFFFFFFFF, maxBits = 63, prec = 0.0) {
  const ptr = wasm.malloc(20);
  const v = new DataView(wasm.memory.buffer);
  v.setUint32 (ptr +  0, maxInters, true);
  v.setUint32 (ptr +  4, maxDepth,  true);  // 0xFFFFFFFF = no limit
  v.setUint32 (ptr +  8, maxBits,   true);
  v.setFloat32(ptr + 12, prec,      true);  // 0.0 = exact rational
  v.setUint8  (ptr + 16, 0);  // mode_ori
  v.setUint8  (ptr + 17, 1);  // stop_on_overlap
  v.setUint8  (ptr + 18, 1);  // stop_on_incomplete
  v.setUint8  (ptr + 19, 0);  // padding
  return ptr;  // caller must call wasm.free()
}

// Read inter_result (24 bytes, align 4)
function readInterResult(wasm, ptr) {
  const v = new DataView(wasm.memory.buffer);
  return {
    checked:    v.getUint32(ptr +  0, true),  // m_gcx
    depth:      v.getUint32(ptr +  4, true),  // m_depth
    bits:       v.getUint32(ptr +  8, true),  // m_bits (0 = non-exact IFS)
    neigh:      v.getUint32(ptr + 12, true),  // m_neigh
    oscDepth:   v.getUint32(ptr + 16, true),  // m_over_depth (0 = OSC holds)
    completed:  v.getUint8 (ptr + 20),        // 1 = fully computed
    overflowed: v.getUint8 (ptr + 21),        // 1 = rational overflow
    mode:       v.getUint8 (ptr + 22),        // 0=rational,1=big_rational,2=real
  };
}
// Prerequisite: init() and set_block() already called (see basic page)

// Allocate structs
const iresPtr = wasm.malloc(24);

// Compute neighbor graph — retry with increasing budget if incomplete
let ires;
for (const maxInters of [4000, 8000, 50000, 200000]) {
  const sp = writeSettings(wasm, maxInters);
  wasm.calc_neighbor_graph(iresPtr, sp);
  wasm.free(sp);
  ires = readInterResult(wasm, iresPtr);
  if (ires.completed) break;
}
wasm.free(iresPtr);

if (ires.bits === 0)    { /* non-exact IFS (sin/cos or decimal literals) — inapplicable */ }
if (ires.oscDepth > 0)  { /* OSC violated — overlap detected, result meaningless */ }
if (!ires.completed)    { /* budget exhausted even at 200 000 */ }

console.log('neighbours:', ires.neigh);

// Option A: boundary dimension only (simple)
const dim = wasm.calc_boundary_dim();
// dim === 0    → tiles touch at isolated points only
// dim === -1   → tiles are completely disjoint
// isNaN(dim)   → error — call readCString(wasm, wasm.get_last_output())

// Option B: boundary IFS program (full pipeline)
wasm.custom_ifs(0, 2);
const boundaryAifs = readCString(wasm, wasm.get_last_output());

Tiered budget: some IFS need large budgets — Danzer needs ~50 000, Quaquaversal needs ~200 000. When bits=0 and checked=0, the IFS uses non-exact coefficients (sin/cos or decimal literals) — calc_neighbor_graph is inapplicable in that case.