Quality Gates¶
navi-fractal applies a chain of quality gates before reporting a fractal dimension. A result is only produced when every gate passes. This page explains each gate: what it checks, why it exists, and what kinds of graphs trigger it.
The gates are applied in order during the exhaustive window search. A candidate window must survive every gate to be considered. If no window survives, the result is a refusal with the reason code of the highest-priority gate that rejected the best candidate.
\( R^2 \) threshold¶
Parameter: r2_min (default 0.85)
What it checks: The coefficient of determination (\( R^2 \)) for the power-law fit within the candidate window. \( R^2 \) measures what fraction of the variance in \( \log M \) is explained by the linear relationship with \( \log r \).
Why it exists: A low \( R^2 \) means the data points in the window don't follow a straight line in log-log space. If \( \log M \) vs \( \log r \) isn't linear, the "dimension" extracted from the slope is not meaningful --- you're fitting a line to something that isn't a line. The 0.85 default is deliberately permissive; most genuine fractal networks produce \( R^2 \) above 0.99 in their best windows. The threshold exists to catch clear non-power-law behavior, not to enforce perfection.
Example triggers: Random graphs (Erdős-Rényi, Barabási-Albert) typically fail this gate because their ball-mass growth follows an exponential or sigmoidal curve in log-log space, not a straight line.
Reason code: NO_WINDOW_PASSES_R2
AICc model selection¶
Parameter: delta_power_win (default 1.5)
What it checks: The Akaike Information Criterion (corrected for small samples) is computed for two competing models fit to the same window data:
- Power-law (linear in log-log):
- Exponential (linear in \( r \)):
The power-law model must have an AICc at least delta_power_win lower than
the exponential model. Lower AICc means better fit after penalizing for model
complexity (both models have the same number of parameters here, so the
comparison reduces to which functional form fits the data better).
Why it exists: Some networks show ball-mass growth that looks vaguely power-law over a narrow window but is actually better described by exponential growth. Reporting a "dimension" for such networks would be misleading. The AICc comparison is a principled way to ask: "is power-law scaling actually the right model, or does exponential fit just as well?"
Example triggers: Networks where ball-mass growth is exponential rather
than power-law. The (1,2)-flower is a transfractal network with u=1 where
mass growth saturates too rapidly to construct valid radii (it is refused
earlier with no_valid_radii). The AICc gate catches networks that do
produce enough radii for window construction but where exponential growth
fits better than power-law.
Reason code: AICC_PREFERS_EXPONENTIAL
Curvature guard¶
Parameters: curvature_guard (default True), delta_quadratic_win
(default 3.0)
What it checks: Within the candidate window, fit a quadratic model in log-log space:
If the quadratic model's AICc is more than delta_quadratic_win lower than
the linear model's AICc, the window is rejected. The quadratic has one extra
parameter, so AICc already penalizes it for complexity --- if it still wins
convincingly, there is genuine curvature in the data.
Why it exists: True power-law scaling produces a straight line in log-log space. Curvature (a bend) indicates that the scaling exponent is changing across the window --- you're not in a single scaling regime. This happens naturally at the boundaries of the fractal scaling range: at small radii the discrete lattice structure dominates, and at large radii saturation effects (running out of graph) flatten the curve. The curvature guard forces the window search to find the regime where scaling is genuinely linear, rather than averaging over a curved region and calling the average slope a "dimension."
Example triggers: Large-generation flower networks at wide windows. At gen 8 of the (2,2)-flower, the widest windows that pass \( R^2 \) still show detectable curvature from boundary effects. The curvature guard correctly rejects these, forcing the algorithm to select a narrower (but straighter) window. This is one mechanism behind the gen 7 to 8 reversal in the calibration table.
Reason code: CURVATURE_GUARD
Slope stability guard¶
Parameters: slope_stability_guard (default False),
slope_stability_sub_len (default None, falls back to min_points),
max_slope_range (default 0.5)
What it checks: Divide the candidate window into overlapping sub-windows
of length slope_stability_sub_len, fit each sub-window independently, and
compute the range (max - min) of the resulting slopes. If this range exceeds
max_slope_range, the window is rejected.
Why it exists: A stable fractal dimension should look roughly the same regardless of which part of the scaling window you examine. If the slope changes dramatically from the left half to the right half of the window, the "dimension" is not a stable property of the network --- it's an artifact of which radii you happened to include. This gate catches networks with multiple scaling regimes (different dimensions at different length scales) and windows that straddle two regimes.
Why it's off by default: The curvature guard already catches the most common case (smoothly varying slope), and the slope stability guard can be overly aggressive on small networks where statistical noise in sub-windows is large. It is available for users who want an extra layer of scrutiny.
Example triggers: Networks with hierarchical community structure sometimes show one dimension within communities and a different dimension between communities. The slope stability guard would reject windows that span both regimes.
Reason code: SLOPE_STABILITY_GUARD
Negative slope guard¶
Parameter: require_positive_slope (default True)
What it checks: After the best window is found and all other gates have passed, this gate rejects windows where the best-fit slope is zero or negative.
Why it exists: In a connected graph, ball mass is a monotonically non-decreasing function of radius --- you can only reach more nodes by looking farther. A negative slope in log-log space would mean mass decreases with radius, which is geometrically impossible in a single connected component. If the aggregated data shows a negative slope, something has gone wrong with the aggregation (e.g., the geometric mean was pulled down by centers with anomalously low mass at large radii), and the result should not be trusted.
Why it's a post-search gate: Unlike the other gates, this one is applied after the window search completes, to the single best window. This is because negative slopes are rare enough that checking inside the inner loop would add complexity without practical benefit.
Reason code: NEGATIVE_SLOPE
Minimum delta-y guard¶
Parameter: min_delta_y (default 0.5)
What it checks: The vertical span of the candidate window in log-log
space: \( \max(\log M) - \min(\log M) \) across the radii in the window. If this
span is less than min_delta_y, the window is skipped.
Why it exists: When ball mass barely changes across a range of radii --
a nearly flat region in log-log space --- the slope estimate becomes
unreliable. A tiny vertical span means the signal-to-noise ratio is poor:
small perturbations in mass produce large changes in the fitted slope. The
min_delta_y threshold ensures that the window contains enough "dynamic
range" in mass growth for the slope to be meaningful.
The default of 0.5 corresponds to a mass ratio of about \( e^{0.5} \approx 1.65\times \) from the smallest to largest radius in the window. This is conservative --- most genuine scaling windows show mass changing by orders of magnitude.
How it interacts with other gates: The delta-y check is applied early in the window search loop, before regression. This is an optimization: windows with tiny vertical span are almost certain to fail \( R^2 \) or produce unstable slopes, so rejecting them early saves computation.