Skip to content

Quickstart

This tutorial walks you through measuring the fractal dimension of a graph, understanding when and why the instrument refuses to produce a number, and applying post-hoc quality gates to your results.

Your first measurement

from navi_fractal import make_grid_graph, estimate_sandbox_dimension

grid = make_grid_graph(30, 30)
result = estimate_sandbox_dimension(grid, seed=42)

print(f"Dimension: {result.dimension:.3f}")       # 1.620
print(f"R²:        {result.powerlaw_fit.r2:.4f}")  # 0.9999
print(f"Window:    r=[{result.window_r_min}, {result.window_r_max}]")
print(f"Reason:    {result.reason.value}")          # accepted

estimate_sandbox_dimension runs the full measurement pipeline: it compiles the graph into a deterministic internal representation, samples BFS neighborhoods from random centers, computes mean mass at each radius, and searches for the best contiguous scaling window in log-log space. A chain of statistical quality gates decides whether the scaling evidence is strong enough to emit a dimension estimate.

When the gates pass, result.dimension holds the power-law slope and result.reason is Reason.ACCEPTED. Every other field on the result provides the audit trail --- the raw data, the fit diagnostics, and the window metrics that led to this conclusion.

When measurement is refused

from navi_fractal import Graph, estimate_sandbox_dimension

# Complete graph -- trivially non-fractal
K50 = Graph()
for i in range(50):
    for j in range(i + 1, 50):
        K50.add_edge(i, j)

result = estimate_sandbox_dimension(K50, seed=42)
print(result.dimension)     # None
print(result.reason.value)  # trivial_graph

When dimension is None, the instrument is telling you it found no credible evidence of power-law scaling. This is not an error --- it is the correct output. The reason field gives you a machine-readable code explaining exactly why the measurement was refused.

Some common refusal reasons:

Reason What happened
trivial_graph Graph is too small or has diameter <= 1
no_valid_radii Not enough distinct radii to fit a window
no_window_passes_r2 No contiguous window achieves the \( R^2 \) threshold
aicc_prefers_exponential An exponential model fits the data better than a power law
curvature_guard The log-log plot has significant curvature (quadratic beats linear)

The refusal is the feature. A tool that always produces a number is not a measurement instrument --- it is a number generator. navi-fractal earns your trust by staying silent when the evidence is not there.

Confidence intervals

result = estimate_sandbox_dimension(grid, seed=42, bootstrap_reps=200)
if result.dimension_ci:
    lo, hi = result.dimension_ci
    print(f"D = {result.dimension:.3f} [{lo:.3f}, {hi:.3f}]")

When you set bootstrap_reps > 0, the estimator resamples the set of BFS centers with replacement and re-fits the power-law slope for each replicate. The confidence interval reports the 2.5th and 97.5th percentiles of the bootstrap distribution, giving you a 95% interval for the dimension estimate.

The bootstrap_valid_reps field tells you how many replicates produced valid fits. If this number is low relative to bootstrap_reps, the confidence interval may be unreliable --- see Interpreting Results for guidance on what to watch for.

Bring your own graph

from navi_fractal import Graph, estimate_sandbox_dimension

g = Graph()
with open("edges.csv") as f:
    for line in f:
        u, v = line.strip().split(",")
        g.add_edge(u, v)

result = estimate_sandbox_dimension(g, seed=42)

Graph accepts any hashable type as node labels --- strings, integers, tuples, whatever your data uses. Edges are undirected and self-loops are silently ignored. When you pass a Graph to the estimator, it compiles it into a frozen internal representation with deterministic traversal order before measurement begins.

For more loading patterns --- NetworkX interop, adjacency matrices, and large-scale graphs --- see the how-to guides.

Post-hoc quality gate

from navi_fractal import sandbox_quality_gate

passed, reason, detail = sandbox_quality_gate(result, preset="inclusive")
if not passed:
    print(f"Quality gate failed: {reason.value} -- {detail}")

There are two layers of quality control in navi-fractal, and they serve different purposes:

Built-in gates (part of estimate_sandbox_dimension) decide whether to emit a dimension at all. They are the instrument's own judgment: "Is there positive evidence of power-law scaling?" If these gates refuse, dimension is None.

Post-hoc quality gate (sandbox_quality_gate) is a policy layer that you apply after measurement. It can reject what the estimator accepted, but it never accepts what the estimator refused. This is where you encode your study's standards: "Is this measurement good enough for my purpose?"

Two presets are available:

Preset \( R^2 \) min Stderr max Radius ratio \( \Delta \)AICc Log span
inclusive 0.85 0.25 3.0 1.5 log(3)
strict 0.95 0.20 4.0 3.0 log(4)

All thresholds are individually overridable via keyword arguments, so you can mix a preset with custom values for specific checks.

What's next

  • Interpreting Results --- a field-by-field guide to every value on SandboxResult, including red flags and what they mean.
  • How-to guides --- advanced usage patterns including NetworkX interop, custom radii schedules, and null-model comparison.