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.
| Export | Signature | Description |
|---|---|---|
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
| Export | Description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
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
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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_ifs → information("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.