duckdb-snapreg

May 14, 2026 · View on GitHub

A DuckDB extension that finds exact rational linear relationships in numeric data.

It combines OLS regression with rational snapping — converting floating-point regression output into exact rational hypotheses (1/3, 2/1, 5/4, ...) and then validating that the resulting outputs are integers. This makes it useful anywhere you expect exact arithmetic relationships to exist in data but are working with measurements that carry floating-point noise.


The Idea

Standard linear regression on 3–5 data points gives you a slope like 0.33333.... That float is useless on its own. But snap it to 1/3, and now you have a testable hypothesis: does feature×(1/3)+intercept\text{feature} \times (1/3) + \text{intercept} produce an exact integer for every data point?

Rather than regression, snapreg uses a pairwise candidate search. Every pair of data points defines an exact slope; every point combined with a candidate slope defines an exact intercept. Each (slope, intercept) pair is then validated against all points using exact __int128 arithmetic. The candidate with fewest failures wins.

Points: (x=1,y=3), (x=2,y=5), (x=3,y=7)

Pair (1→2): slope_raw = (5-3)/(2-1) = 2 → snaps to 2/1
Pair (1→3): slope_raw = (7-3)/(3-1) = 2 → same

For slope=2, intercept from point 1: 3 - 2*1 = 1 → snaps to 1/1

Validate (slope=2, intercept=1) against all points:
  2*1+1=3 ✓  2*2+1=5 ✓  2*3+1=7 ✓ → 0 mismatches

This approach is robust to high-leverage outliers — a single point with an extreme feature value that would destroy a regression doesn't affect the slope candidates derived from the other pairs. The validation step uses exact __int128 arithmetic, so there is no floating-point error in the confirmation. A result either passes exactly or it does not.


Functions

snap_rational(v DOUBLE, max_den INTEGER)

Snaps a float to the nearest rational with denominator ≤ max_den. Returns NULL if no rational is within tolerance.

SELECT snap_rational(0.3333333, 16);
-- → {'num': 1, 'den': 3}

SELECT snap_rational(1.6666667, 16);
-- → {'num': 5, 'den': 3}

SELECT snap_rational(0.3333333, 2);
-- → NULL  (1/3 needs denominator 3, which exceeds max_den=2)

Returns STRUCT(num BIGINT, den BIGINT).


equation_agg(target, feature, example_id, row_idx, max_den)

Aggregate function. Group by feature name; one row per feature. Fits a linear equation target = slope × feature + intercept, snaps both slope and intercept to rationals, and validates every point with exact arithmetic.

SELECT
    feat_name,
    eq.slope_num,
    eq.slope_den,
    eq.intercept_num,
    eq.intercept_den,
    eq.consistent,       -- true iff every example has zero mismatches
    eq.mismatches,
    eq.bad_indices       -- row_idx values that failed validation
FROM (
    SELECT feat_name,
           equation_agg(target, feature, example_id, row_idx, 16) AS eq
    FROM feature_library
    GROUP BY feat_name
) t
WHERE eq IS NOT NULL
ORDER BY eq.mismatches ASC, eq.r2 DESC;

Arguments:

ColumnTypeDescription
targetDOUBLEThe value to explain
featureDOUBLEThe candidate predictor
example_idINTEGERWhich example this row belongs to
row_idxINTEGERCaller-assigned row identifier, returned in index lists
max_denINTEGERMaximum denominator for snapping (16 is a good default)

Result struct:

FieldTypeDescription
slope_numBIGINTSlope numerator
slope_denBIGINTSlope denominator (always positive)
intercept_numBIGINTIntercept numerator
intercept_denBIGINTIntercept denominator
mismatchesINTEGERTotal points where validation failed
n_goodINTEGERPoints that passed
n_badINTEGERPoints that failed
consistentBOOLEANTrue iff every example individually has zero mismatches
r2DOUBLEOLS R² on the raw floats before snapping
per_example_mmINTEGER[]Mismatch count per example, sorted by example_id
good_indicesINTEGER[]row_idx values that passed
bad_indicesINTEGER[]row_idx values that failed

Returns NULL when: fewer than 2 points, feature is constant (degenerate OLS), or no rational snap exists within tolerance for slope or intercept.

Constant features (all feature values equal) are handled as slope=0, intercept=mean(target). If the mean snaps cleanly this still returns a valid result — useful for discovering that a hole is simply a constant.


residual_hint(vals DOUBLE[])

Analyses a residual vector and returns structural hints. Intended to help an orchestrator decide what to do next with unexplained residuals rather than brute-forcing deeper search.

SELECT h.*
FROM residual_hint([3.0, 4.0, 4.0, 3.0, 4.0]) h;
FieldTypeMeaning
range_tightBOOLEANmax − min ≤ 2
nearly_constantBOOLEANvariance < 0.25
nearly_linearBOOLEANR² vs index > 0.90
binary_valuedBOOLEANexactly 2 distinct rounded values
n_distinctINTEGERnumber of distinct rounded values
min_valDOUBLEminimum
max_valDOUBLEmaximum
mean_valDOUBLEmean

A binary_valued residual is a predicate in disguise: output = base + boolean × range. Pass bad_indices to a predicate solver rather than searching deeper for an equation.


Multi-example consistency

The consistent field and per_example_mm array exist because a globally zero mismatch count can hide failures that cancel across examples. consistent = true means every example independently has zero failures — which is the correct condition for a solution that generalises.

-- Only accept equations that hold in every example
WHERE eq.consistent = true

-- Or inspect per-example breakdown for partial matches
WHERE list_max(eq.per_example_mm) <= 1

Building

git clone --recurse-submodules https://github.com/duckdb/extension-template duckdb-snapreg
cd duckdb-snapreg

# Copy src/, test/, extension_config.cmake, vcpkg.json from this repo
python3 scripts/bootstrap-template.py equation_search

# Build (add GEN=ninja for faster incremental builds)
make

# Run tests
make test

Outputs:

build/release/duckdb                                            # shell with extension pre-loaded
build/release/extension/equation_search/equation_search.duckdb_extension

Loading

-- Unsigned local build
-- Start DuckDB with: duckdb -unsigned
LOAD 'build/release/extension/equation_search/equation_search.duckdb_extension';

-- Or once published to community extensions
INSTALL equation_search FROM community;
LOAD equation_search;

Design notes

Why pairwise candidate search instead of regression? OLS minimizes squared error globally. One outlier or high-leverage point (a feature value far from the cluster) shifts the slope enough that the snap lands on a wrong rational, causing integer validation to fail everywhere — not just at the outlier. Pairwise search derives slope candidates directly from point pairs, so the correct slope appears as a candidate as long as any two good points exist. Complexity is O(n³) vs O(n) for regression, but for for our use cases n ≤ 20 this is still microseconds.

r2 is always 0.0 The r2 column is retained for API compatibility but is no longer computed. Pairwise search ranks candidates by mismatch count, not R². Callers should use mismatches and consistent for quality assessment.


License

MIT