Mastodon Politics, Power, and Science

Monday, August 31, 2026

One Form, Two Families

 Uniting k.1.k and k.2.(k−1) by a Complex Substitution, and the One-Line Search That Follows

J. Rogers, SE Ohio


Abstract

Euler's sum-of-powers families are catalogued by the Lander–Parkin–Selfridge triple k.m.n, and each row is searched separately by separate programs. We observe that for k ≡ 2 (mod 4) the rows k.1.k and k.2.(k−1) are not distinct problems: they are one diagonal form under two sign vectors, and the substitution a → ai carries one to the other exactly, because i^k = −1. The consequence is algorithmic and minimal. A search for k.1.k becomes a search for k.2.(k−1) by adding a single test — whether the overshoot residue has an exact integer k-th root — reusing the k-th-power index the search already maintains. Implemented as one flag, this reproduced E. Brisse's 1999 solution 84⁶+212⁶+602⁶+861⁶+1092⁶ = 1117⁶+770⁶ from a cold start. We further measure the local densities of the two sign vectors and find they differ by a factor of 12.7 million, which quantifies why one family has five published solutions and the other has none.


1. The starting conjecture

Fermat's equation felt unbalanced: the exponent rises but the number of terms does not. The natural symmetric statement is to let them rise together.

For any integer n ≥ 2, do positive integers exist with a₁ⁿ + a₂ⁿ + ⋯ + aₙⁿ = zⁿn terms, n-th powers?

n = 2 gives 3² + 4² = 5². n = 3 gives 3³ + 4³ + 5³ = 6³, which works on the first try by extending the same pattern. This is, independently arrived at, the diagonal of Euler's 1769 conjecture, which asserts that a sum of m k-th powers equal to a k-th power requires m ≥ k. The case m = k is the boundary.

The status by exponent:

k smallest known m = k solution
2 3,4 → 5
3 3,4,5 → 6
4 30,120,272,315 → 353 (Norrie 1911)
5 19,43,46,47,67 → 72
6 none known
7 127,258,266,413,430,439,525 → 568 (Dodrill 1999)
8 90,223,478,524,748,1088,1190,1324 → 1409 (Chase 2000)

k = 6 is the only gap in k = 1…8.


2. Why m = k is the boundary

For fixed z, the equation says ‖(a₁,…,a_m)‖_k = z: the solutions are lattice points on the L^k sphere of radius z in m dimensions. It is a diagonal, but in L^k, not L². At k = 2 the L^k norm is the Euclidean norm, which is why that case looks like a right triangle; that resemblance is a coincidence of k = 2 and does not continue.

Counting is then immediate. Lattice points in a shell of thickness δ on a radius-z sphere in m dimensions go as z^(m−1)·δ. The shell landing on one integer value of z^k has thickness 1/(k·z^(k−1)). So the expected count is

z^(m-1) / (k · z^(k-1))  =  z^(m-k) / k

At m = k the surface growth z^(k−1) exactly cancels the shell thinning z^−(k−1). The count becomes 1/k — constant at every radius, forever. Below the boundary it vanishes; above it, it diverges. That cancellation is the balance, and it occurs only when the term count, the exponent, and the dimension are the same number.

Measured against this: k = 3 yields 2073 primitive solutions below z = 2000 (≈1.04 per unit z), and k = 5 yields a flat ≈24 per 1000. Both constant, as predicted.


3. What starves k = 6

Three moduli collapse sixth powers to {0,1} simultaneously — 7, 9 and 8. With exactly six terms the sum modulo each is just the count of terms coprime to it, and z⁶ ∈ {0,1}, so that count must be 0 or 1. Every primitive solution therefore has exactly one term coprime to 7, one coprime to 3, and one odd — five of six divisible by 42.

A search program derives this on its own from the residue structure and prints:

sieve modulus : 124488  (9*8*7*13*19)
  1 term  left :  0.077% of remainders survive
stride        : <=1 term coprime to 3, <=1 coprime to 2, <=1 coprime to 7
                once spent, remaining terms step by 42

No other exponent pays this. Odd k pays none of it at all: x ↦ x^k is a bijection on the units mod 2^a for odd k, and mod an odd prime p the residue count is (p−1)/gcd(k,p−1) + 1, which equals 2 only if (p−1) | k — impossible when p−1 is even and k is odd. This is the same fact that makes k = 3, 5, 7 dense and gives them no stride.


4. The substitution

For k ≡ 2 (mod 4), i^k = −1. Allowing one term to be purely imaginary:

(fi)⁶ = f⁶ · i⁶ = −f⁶

a⁶ + b⁶ + c⁶ + d⁶ + e⁶ + (fi)⁶ = z⁶
a⁶ + b⁶ + c⁶ + d⁶ + e⁶ − f⁶     = z⁶
a⁶ + b⁶ + c⁶ + d⁶ + e⁶          = z⁶ + f⁶

The mechanism is complete over ℤ[i]: x⁶ = −f⁶ has six roots in ℂ, the primitive 12th roots of unity times f, and only ±i are Gaussian integers — the other four carry √3/2. So within ℤ[i] there is exactly one hidden negative point, and it buys exactly one thing: subtraction.

The right-hand side is now two terms. In LPS notation this is 6.2.5.


5. The claim: one form, two sign vectors

6.1.6 and 6.2.5 are catalogued as separate rows, searched by separate people with separate programs, and their solutions are never written in each other's notation. But both are the same diagonal sextic form in seven variables:

Σ cᵢ xᵢ⁶ = 0

differing only in c. Writing (770i)⁶ rather than moving a term across the equals sign makes this visible: a 6.2.5 solution is a 6.1.6 solution over ℤ[i].

This is not merely cosmetic, because the sign vector determines the local densities. Computing the fraction of residue tuples satisfying the congruence at each prime power, normalised so that 1.0 means no local obstruction:

q 6.1.6 (+⁶,−) 6.2.5 (+⁵,−,−) ratio
4 0.8750 0.8750 1.0×
7 0.0018 0.1132 61.4×
8 0.4375 1.3125 3.0×
9 0.1029 0.8272 8.0×
16 0.5000 1.3750 2.8×
19 0.5361 1.5862 3.0×
27 0.1111 0.8354 7.5×
32 0.6250 1.5000 2.4×
49 0.0019 0.1133 59.8×
product 5.03×10⁻⁹ 0.0642 12,756,355×

Flipping two signs lifts the local obstruction by seven orders of magnitude. That is why 6.2.5 has five published solutions and 6.1.6 has none — same form, same degree, same variable count. The separate LPS rows conceal a relationship the sign vector makes obvious, and the imaginary notation is what forces the comparison to be made.


6. The algorithmic consequence

A largest-term-first search maintains the invariant R ≥ 0, where R is the amount left for the remaining terms, and prunes any branch that overshoots. The bridge between the two families is to stop discarding the overshoot and instead ask one question of it:

Does the residue have an exact integer k-th root?

Not whether it decomposes as a^k + b^k. Not any new structure. One value, one test:

u128 q = pw[u] - R;                 /* the overshoot */
if (!kf_test(q)) continue;          /* is it a perfect k-th power? */
int v = lower_idx(1, hi, q);
if (v <= hi && pw[v] == q) { a[0] = u; a[1] = -v; report(z, a); }

kf_test is the same k-th-power bitset the ordinary search already uses for its final term. The test queries the existing index on the other side of zero. No new data structure is introduced.

One correctness requirement accompanies it. With a subtractive term the last pair satisfies u^k − f^k = R, so R must be a difference of two k-th power residues, not only a sum. The congruence sieve must accept both, or it will silently prune the very branches the flag was added to find and report a clean empty result. Widening level 2 of the sieve propagates the corrected condition up every level above it — the residue's algebra feeding back into the search.


7. Result

With that one flag, a from-scratch searcher produced:

84⁶ + 212⁶ + 602⁶ + 861⁶ + 1092⁶ + (770i)⁶ = 1117⁶

Verified in independent arbitrary-precision arithmetic sharing no code with the search:

sum of the five = 2150734582950343769
770⁶            =  208422380089000000
difference      = 1942312202861343769
1117⁶           = 1942312202861343769      EXACT
gcd(all, z) = 1 → primitive;  770 ∉ {84,212,602,861,1092} → not a cancellation

This is E. Brisse, 1999, the smallest of the five known 6.2.5 solutions, rediscovered cold. In its usual notation it reads 1092⁶+861⁶+602⁶+212⁶+84⁶ = 1117⁶+770⁶; in the complex notation it is six sixth powers summing to a sixth power over ℤ[i]. The two lines are the same fact.


8. What is and is not new

Not new. The solutions. All five 6.2.5 results are published (Brisse 1999; Resta 1999; Resta & Meyrignac 2003; Meyrignac ×2). The two-sided family has been searched since the 1960s. The diagonal-form-with-signs view, Σcᵢxᵢ^k = 0, is standard in analytic number theory, where the circle method treats sign patterns uniformly.

The claim. That the rows k.1.k and k.2.(k−1) are one object under two sign vectors, that the substitution a → ai exhibits this exactly for k ≡ 2 (mod 4), that the sign vector is what sets the density gap between them — measured here at 12.7 million — and that a single residue test converts a search for one into a search for the other at no structural cost. A literature check found no source presenting 6.2.5 results in the complex form; that check was not exhaustive.

Explicitly not claimed. This does not resolve k = 6, m = 6 over the positive integers. That case remains open, is everywhere locally solvable, sits on a smooth Fano hypersurface, and is expected to have solutions near z ~ 10⁶–10⁸ — starved by the density penalty above, not forbidden.


9. Reproduction and open targets

gcc -O3 -march=native -funroll-loops -fopenmp powersum.c -o powersum -lm -lpthread
./powersum --selftest                       # 9 reference identities
./powersum -k 6 -n 1500 --resid             # the residue test
./powersum -k 6 -n 400000 --gap 1 --probe    # cost before committing
python3 verify_hits.py powersum_hits.txt     # independent arbitrary precision

Independently established during this work: k = 6, m = 6 is empty for all z ≤ 10,000 (1.72×10¹² nodes, 81 minutes, 8 cores), consistent with the published bound of z ≥ 110,266 (EulerNet, January 2000).

The live target is a sixth 6.2.5 solution — none has been published beyond Meyrignac's, the sign vector carries no crippling local obstruction, and the machinery above has demonstrably found one before.


Verification and measurements performed with powersum.c and verify_hits.py. All identities in this document were checked in independent arbitrary-precision arithmetic.





Appendix, PowerSum.c


$ ./powersum -k 6 --imag -n 5000

=== power-sum search ===

  equation      : a_1^6 + ... + a_6^6 = z^6   (6 terms)

  bound         : all terms and z <= 5000,  z in [2,5000]

  MODE          : --imag, one term (f*i): a_1^6+...+a_5^6 = z^6 + f^6

  threads       : 16 running, 16 cores detected

  memory budget : 48.0 GB

  checkpoint    : powersum-k6-m6-z2.state        hits -> powersum_hits.txt

  sieve modulus : 124488  (9*8*7*13*19)  residues=96

    1 term  left :  0.077% of remainders survive

    2 terms left :  2.269% of remainders survive

    3 terms left : 10.166% of remainders survive

    4 terms left : 25.549% of remainders survive

  stride        : none (odd k has no divisibility structure)

  kth-power filter : 0.1 MB

  two-sum filter   : building, ~0 s ...

  two-sum filter   : 0.13 GB, density 1.164%

  RESUMING      : verified through z = 485  (1.080e+10 nodes, 49 s, 0 hits banked)

  ---------------------------------------------------------------

*** 84^6 + 212^6 + 602^6 + 861^6 + 1092^6 + (770i)^6 = 1117^6   [1942312202861343769 = 1942312202861343769]

verified<=1684     +5    0.440% |  0.21 G n/s | cost Z^4.87 | up   6:57:30 | ETA 1428:08:22 | hits 1


$ cat powersum.c

/* ============================================================================

 *  powersum.c   --   search for   a_1^k + a_2^k + ... + a_m^k  =  z^k

 *

 *  Default m = k  ("Rogers' Number Power Sum Problem").

 *  Set m = k-1 to hunt counterexamples to Euler's sum-of-powers conjecture.

 *

 *  Engine:

 *    1. Automatic congruence sieve.  For a composite modulus chosen at

 *       startup it precomputes  need[j][d] = "can d (mod MOD) be written as a

 *       sum of j k-th power residues?"  One byte lookup kills most branches.

 *       This is what discovers, on its own, facts like "for k=6, five of the

 *       six terms must be divisible by 7, five by 3, and five must be even."

 *    2. Bound pruning on both sides (v^k * rem <= R <= (z-1)^k * rem).

 *    3. Last two terms resolved by a large bitset filter over all two-term

 *       sums (this is what eats the RAM) followed by a k-th-power bitset and

 *       an exact binary search.

 *    4. OpenMP over z, dynamic schedule.  128-bit exact arithmetic throughout.

 *

 *  Build:

 *      gcc -O3 -march=native -funroll-loops -fopenmp powersum.c -o powersum -lm -lpthread

 *

 *  ALWAYS --probe FIRST.  It samples cheap z values, fits nodes(z) ~ C*z^p,

 *  and prints a predicted wall time for the whole range before you commit.

 *

 *  Examples:

 *      ./powersum --selftest                  # verify the 128-bit arithmetic

 *      ./powersum -k 4 -n 500                 # finds Norrie 30,120,272,315 -> 353

 *      ./powersum -k 5 -n 200                 # finds 19,43,46,47,67 -> 72 and two more

 *      ./powersum -k 5 -m 4 -n 200            # finds Lander-Parkin 27,84,110,133 -> 144

 *      ./powersum -k 7 -n 3000 --probe        # ask the cost before running

 *      ./powersum -k 6 -n 40000 -t 16 -g 44   # the open case

 *      ./powersum -k 6 -n 40000 -z0 30000 -z1 32000    # shard across machines

 *

 *  Reality check on k=6, m=6 (no solution is known, and it is the only gap in

 *  k = 1..8):  the congruence sieve below shows why.  Three moduli collapse

 *  sixth powers to {0,1} at once -- 7, 9 and 8 -- which forces five of the six

 *  terms to be multiples of 7, five to be multiples of 3, and five to be even.

 *  That is a ~10^6 density penalty no other exponent pays.  Extrapolating from

 *  where the k=4 and k=5 solutions actually sit, the first k=6 solution should

 *  live near z ~ 10^8.  Sixteen cores will not get there.  What sixteen cores

 *  CAN do: extend the verified-empty range, and hunt m = k-1 (Euler

 *  counterexamples) at k=6,7,8 where nothing is known at all.

 * ==========================================================================*/


#define _GNU_SOURCE

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <stdint.h>

#include <math.h>

#include <time.h>

#include <pthread.h>

#include <unistd.h>

#include <omp.h>


typedef unsigned __int128 u128;


/* ------------------------------------------------------------------ config */

static int    K = 6;              /* exponent                                */

static int    M = 0;              /* number of terms on the left; 0 => K     */

static int    N = 2000;           /* upper bound on every a_i and on z       */

static int    Z0 = 2, Z1 = 0;     /* z range (Z1 = 0 => N)                   */

static int    THREADS = 16;

static double MEMGB = 48.0;

static int    PRIM_ONLY = 1;      /* report only gcd(a_1..a_m,z) == 1        */

static const char *OUTFILE = "powersum_hits.txt";


/* --------------------------------------------------------------- structures */

static u128     *pw   = NULL;     /* pw[i] = i^K                             */

static uint32_t *pwm  = NULL;     /* pw[i] mod MOD                           */


static uint64_t *kfilt = NULL;    /* bitset: is this value a k-th power?     */

static uint64_t  KF_MASK = 0;


static uint64_t *s2filt = NULL;   /* bitset: is this value a sum of two?     */

static uint64_t  S2_MASK = 0;

static int       use_s2 = 0;

static int       skip_s2 = 0;

static double    S2_BUDGET = 600.0;   /* max seconds to spend building it */


static uint32_t  MOD = 1;

static uint8_t  *need = NULL;     /* need[j*MOD + d]                         */


/* ---------------------------------------------------------------- progress */

#define MAXT 512

static struct { long long n; char pad[56]; } tcnt[MAXT];   /* cache-line padded */

static long long node_total(void) { long long s = 0; for (int i = 0; i < MAXT; i++) s += tcnt[i].n; return s; }


static long long g_nodes = 0, g_hits = 0;

static double    g_done_w = 0.0, g_total_w = 1.0;

static int       g_running = 1, g_curz = 0;

static double    g_t0;

static pthread_mutex_t out_mx = PTHREAD_MUTEX_INITIALIZER;

static FILE     *out_fp = NULL;


/* ------------------------------------------------------------ 128-bit i/o */

static char *u128s(u128 v, char *b) {

    char t[48]; int n = 0;

    if (!v) { b[0] = '0'; b[1] = 0; return b; }

    while (v) { t[n++] = (char)('0' + (int)(v % 10)); v /= 10; }

    for (int i = 0; i < n; i++) b[i] = t[n - 1 - i];

    b[n] = 0; return b;

}

static double now(void) {

    struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);

    return ts.tv_sec + 1e-9 * ts.tv_nsec;

}

static void hms(double s, char *b) {

    if (s < 0 || s > 3.15e9) { strcpy(b, "  --:--:--"); return; }

    long long x = (long long)s;

    snprintf(b, 40, "%3lld:%02lld:%02lld", x / 3600, (x / 60) % 60, x % 60);

}

static long long igcd(long long a, long long b) { while (b) { long long t = a % b; a = b; b = t; } return a; }


/* ============================ congruence sieve ============================ */

static uint32_t powmod32(uint64_t b, int e, uint32_t m) {

    uint64_t r = 1 % m; b %= m;

    while (e) { if (e & 1) r = r * b % m; b = b * b % m; e >>= 1; }

    return (uint32_t)r;

}

static int is_prime_power(int q, int *base) {

    if (q < 2) return 0;

    for (int p = 2; (long)p * p <= q; p++)

        if (q % p == 0) {

            int t = q; while (t % p == 0) t /= p;

            if (t != 1) return 0; *base = p; return 1;

        }

    *base = q; return 1;                    /* q prime */

}

/* distinct k-th power residues mod q, and distinct sums of two of them      */

static void residue_stats(int q, int *n1, int *n2) {

    unsigned char *a = calloc(q, 1), *b = calloc(q, 1);

    for (int x = 0; x < q; x++) a[powmod32(x, K, q)] = 1;

    int c1 = 0; for (int i = 0; i < q; i++) c1 += a[i];

    for (int i = 0; i < q; i++) if (a[i])

        for (int j = 0; j < q; j++) if (a[j]) b[(i + j) % q] = 1;

    int c2 = 0; for (int i = 0; i < q; i++) c2 += b[i];

    *n1 = c1; *n2 = c2; free(a); free(b);

}

typedef struct { int q, n1; double ratio; } Cand;

static int cmp_cand(const void *A, const void *B) {

    double d = ((const Cand *)B)->ratio - ((const Cand *)A)->ratio;

    return d > 0 ? 1 : d < 0 ? -1 : 0;

}


static void build_sieve(int mod_cap, long long budget) {

    Cand c[4096]; int nc = 0, base;

    for (int q = 2; q <= 2048; q++) {

        if (!is_prime_power(q, &base)) continue;

        int n1, n2; residue_stats(q, &n1, &n2);

        if (n1 == q) continue;                       /* no information       */

        double d1 = (double)n1 / q, d2 = (double)n2 / q;

        double gain = -(log2(d1) + log2(d2));        /* bits of pruning      */

        if (gain <= 0.05) continue;

        c[nc].q = q; c[nc].n1 = n1; c[nc].ratio = gain / log2((double)q);

        nc++;

    }

    qsort(c, nc, sizeof(Cand), cmp_cand);


    MOD = 1; long long kres = 1;

    int picked[64], np = 0;

    for (int i = 0; i < nc && np < 60; i++) {

        long long nm = (long long)MOD * c[i].q, nk = kres * c[i].n1;

        if (nm > mod_cap) continue;

        if (nm * nk > budget) continue;

        /* keep the modulus square-free across distinct primes only */

        int ok = 1, b1; is_prime_power(c[i].q, &b1);

        for (int j = 0; j < np; j++) { int b2; is_prime_power(picked[j], &b2); if (b2 == b1) { ok = 0; break; } }

        if (!ok) continue;

        MOD = (uint32_t)nm; kres = nk; picked[np++] = c[i].q;

    }

    if (MOD < 2) MOD = 2;


    /* the k-th power residues modulo the composite MOD */

    unsigned char *isr = calloc(MOD, 1);

    for (uint32_t x = 0; x < MOD; x++) isr[powmod32(x, K, MOD)] = 1;

    uint32_t *R = malloc(sizeof(uint32_t) * MOD); int nR = 0;

    for (uint32_t i = 0; i < MOD; i++) if (isr[i]) R[nR++] = i;


    need = calloc((size_t)(M + 1) * MOD, 1);

    need[0] = 1;                                     /* j = 0 : only d = 0   */

    for (int j = 1; j <= M; j++) {

        uint8_t *prev = need + (size_t)(j - 1) * MOD;

        uint8_t *cur  = need + (size_t)j * MOD;

        for (int s = 0; s < nR; s++) {

            uint32_t off = R[s], lim = MOD - off;

            for (uint32_t d = 0; d < lim; d++) if (prev[d]) cur[d + off] = 1;

            for (uint32_t d = lim; d < MOD; d++) if (prev[d]) cur[d + off - MOD] = 1;

        }

    }

    printf("  sieve modulus : %u  (", MOD);

    for (int i = 0; i < np; i++) printf("%s%d", i ? "*" : "", picked[i]);

    printf(")  residues=%d\n", nR);

    for (int j = 1; j <= M && j <= 4; j++) {

        long long a = 0; uint8_t *cur = need + (size_t)j * MOD;

        for (uint32_t d = 0; d < MOD; d++) a += cur[d];

        printf("    %d term%s left : %6.3f%% of remainders survive\n",

               j, j == 1 ? " " : "s", 100.0 * a / MOD);

    }

    free(isr); free(R);

}


/* ============================== bit filters =============================== */

static inline int kf_test(u128 v) { uint64_t x = (uint64_t)v & KF_MASK; return (kfilt[x >> 6] >> (x & 63)) & 1; }

static inline int s2_test(u128 v) { uint64_t x = (uint64_t)v & S2_MASK; return (s2filt[x >> 6] >> (x & 63)) & 1; }


static uint64_t pow2_ceil(uint64_t x) { uint64_t p = 1; while (p < x) p <<= 1; return p; }


static void build_filters(void) {

    /* --- k-th power filter --------------------------------------------- */

    uint64_t bits = pow2_ceil((uint64_t)N * 128);

    if (bits < (1ULL << 20)) bits = 1ULL << 20;

    if (bits > (1ULL << 33)) bits = 1ULL << 33;

    KF_MASK = bits - 1;

    kfilt = calloc(bits / 64, 8);

    if (!kfilt) { fprintf(stderr, "kfilt alloc failed\n"); exit(1); }

    for (int i = 1; i <= N; i++) { uint64_t x = (uint64_t)pw[i] & KF_MASK; kfilt[x >> 6] |= 1ULL << (x & 63); }

    printf("  kth-power filter : %.1f MB\n", bits / 8.0 / 1048576.0);


    /* --- two-term-sum filter (the RAM hog) ------------------------------ */

    double pairs = 0.5 * (double)N * (double)N;

    double build_s = pairs / (1.5e8 * THREADS);               /* measured rate */

    uint64_t want = pow2_ceil((uint64_t)(pairs * 48.0));      /* ~2% density */

    uint64_t cap  = pow2_ceil((uint64_t)(MEMGB * 8.0e9));

    while (cap > (uint64_t)(MEMGB * 8.0e9)) cap >>= 1;

    if (want > cap) want = cap;

    if (skip_s2) { use_s2 = 0; printf("  two-sum filter   : skipped (--probe/--no-s2); real runs will be faster\n"); return; }

    if (want < (1ULL << 24) || pairs < 4e6) { use_s2 = 0; printf("  two-sum filter   : disabled (N too small to pay off)\n"); return; }

    if (build_s > S2_BUDGET) {

        use_s2 = 0;

        printf("  two-sum filter   : skipped, build would take ~%.0f s (> -b %.0f).\n"

               "                     raise it with  -b %.0f  if you want the ~10-50x inner-loop win\n",

               build_s, S2_BUDGET, build_s * 1.2);

        return;

    }

    printf("  two-sum filter   : building, ~%.0f s ...\n", build_s);

    S2_MASK = want - 1;

    s2filt = calloc(want / 64, 8);

    if (!s2filt) { printf("  two-sum filter   : allocation of %.1f GB failed, disabled\n", want / 8.0 / 1e9); use_s2 = 0; return; }

    use_s2 = 1;

    #pragma omp parallel for schedule(dynamic, 64)

    for (int u = 1; u <= N; u++)

        for (int v = u; v <= N; v++) {

            uint64_t x = (uint64_t)(pw[u] + pw[v]) & S2_MASK;

            /* benign race: every store is the same bit-set operation */

            __atomic_fetch_or(&s2filt[x >> 6], 1ULL << (x & 63), __ATOMIC_RELAXED);

        }

    printf("  two-sum filter   : %.2f GB, density %.3f%%\n",

           want / 8.0 / 1e9, 100.0 * pairs / (double)want);

}


/* ================================ search ================================= */

static void report(int z, const int *a) {

    long long g = z; for (int i = 0; i < M; i++) g = igcd(g, a[i]);

    if (PRIM_ONLY && g != 1) return;

    char b1[48], b2[48]; u128 s = 0;

    for (int i = 0; i < M; i++) s += pw[a[i]];

    pthread_mutex_lock(&out_mx);

    printf("\r\033[K*** ");

    for (int i = 0; i < M; i++) printf("%s%d^%d", i ? " + " : "", a[i], K);

    printf(" = %d^%d   [%s = %s]%s\n", z, K, u128s(s, b1), u128s(pw[z], b2),

           g == 1 ? "" : "  (imprimitive)");

    if (out_fp) {

        for (int i = 0; i < M; i++) fprintf(out_fp, "%s%d", i ? "," : "", a[i]);

        fprintf(out_fp, " -> %d   (k=%d,m=%d)\n", z, K, M);

        fflush(out_fp);

    }

    g_hits++;

    pthread_mutex_unlock(&out_mx);

}


/* find the smallest v >= lo with pw[v] >= target, in [lo,hi] ; hi+1 if none */

static inline int lower_idx(int lo, int hi, u128 target) {

    while (lo <= hi) { int mid = (lo + hi) >> 1; if (pw[mid] < target) lo = mid + 1; else hi = mid - 1; }

    return lo;

}


static void rec(int rem, int lo, u128 R, uint32_t rmod, int z, int *a, long long *nodes) {

    (*nodes)++;

    if (!need[(size_t)rem * MOD + rmod]) return;


    if (rem == 2) {

        if (use_s2 && !s2_test(R)) return;

        int hi = z - 1;

        for (int u = lo; u <= hi; u++) {

            u128 p = pw[u];

            if (p + p > R) break;

            u128 q = R - p;

            if (q > pw[hi]) continue;

            if (!kf_test(q)) continue;

            int v = lower_idx(u, hi, q);

            if (v <= hi && pw[v] == q) { a[M - 2] = u; a[M - 1] = v; report(z, a); }

        }

        return;

    }


    int hi = z - 1;

    u128 room = pw[hi] * (unsigned)(rem - 1);       /* what the other terms can carry */

    int start = lo;

    if (R > room) {                                 /* v must be big enough  */

        int s = lower_idx(lo, hi, R - room);

        if (s > start) start = s;

    }

    for (int v = start; v <= hi; v++) {

        u128 p = pw[v];

        if (p * (unsigned)rem > R) break;           /* v too big: all later worse */

        uint32_t nm = rmod - pwm[v]; if ((int32_t)nm < 0) nm += MOD;

        a[M - rem] = v;

        rec(rem - 1, v, R - p, nm, z, a, nodes);

    }

}


/* ============================== progress ================================= */

static void *monitor(void *arg) {

    (void)arg;

    int tty = isatty(2);

    long long last_n = 0; double last_t = now(), smooth = 0, eta_s = -1;

    while (g_running) {

        struct timespec ts = { 1, 0 }; nanosleep(&ts, NULL);

        double t = now(), el = t - g_t0;

        long long nd = node_total();

        double inst = (t > last_t) ? (nd - last_n) / (t - last_t) : 0;

        smooth = smooth > 0 ? 0.7 * smooth + 0.3 * inst : inst;   /* EWMA */

        double frac = g_done_w / g_total_w;

        double raw = (frac > 1e-9) ? el * (1.0 - frac) / frac : -1;

        eta_s = (eta_s > 0 && raw > 0) ? 0.8 * eta_s + 0.2 * raw : raw;

        char e1[40], e2[40]; hms(el, e1); hms(eta_s, e2);

        fprintf(stderr,

            "%s z=%-8d %6.3f%% | %7.3f G nodes/s | %.3e nodes | up %s | ETA %s | hits %lld%s",

            tty ? "\r\033[K" : "", g_curz, 100.0 * frac, smooth / 1e9,

            (double)nd, e1, e2, g_hits, tty ? "" : "\n");

        fflush(stderr);

        last_n = nd; last_t = t;

    }

    return NULL;

}


/* ------------------------------------------------------------------ probe --

 * Sample cheap z values, fit  nodes(z) ~ C * z^p , extrapolate to the whole

 * range and print a predicted wall time BEFORE committing to the run.        */

static void probe(void) {

    /* Climb a geometric ladder of z, stopping the moment we've spent enough.

     * Each rung is ~1.4x the last, so we never blunder into a huge sample.   */

    double lx[64], ly[64]; int ns = 0;

    int *a = malloc(sizeof(int) * (M + 2));

    double t0 = now(), budget = 15.0;

    printf("  probing (<= %.0f s) ...\n", budget);

    for (double zf = (Z0 < 64 ? 64 : Z0); zf <= Z1 && ns < 60; zf *= 1.4) {

        int z = (int)zf; if (z < 3) z = 3;

        long long nodes = 0;

        u128 T = pw[z]; uint32_t tm = (uint32_t)(T % MOD);

        if (need[(size_t)M * MOD + tm]) rec(M, 1, T, tm, z, a, &nodes);

        if (nodes > 500) { lx[ns] = log((double)z); ly[ns] = log((double)nodes); ns++; }

        if (now() - t0 > budget) break;

    }

    free(a);

    if (ns < 3) { printf("  probe: range too small to model.\n"); return; }


    double sx = 0, sy = 0, sxx = 0, sxy = 0;

    for (int i = 0; i < ns; i++) { sx += lx[i]; sy += ly[i]; sxx += lx[i] * lx[i]; sxy += lx[i] * ly[i]; }

    double p = (ns * sxy - sx * sy) / (ns * sxx - sx * sx);

    double lnC = (sy - p * sx) / ns, C = exp(lnC);


    double total = 0;

    for (int z = Z0; z <= Z1; z++) total += C * pow((double)z, p);

    /* single-thread node rate measured during the probe */

    double probe_nodes = 0; for (int i = 0; i < ns; i++) probe_nodes += exp(ly[i]);

    double rate1 = probe_nodes / (now() - t0);

    if (rate1 < 1e5) rate1 = 1e5;

    double secs = total / (rate1 * THREADS * 0.85);   /* 0.85 = scaling loss  */

    char e[40]; hms(secs, e);

    printf("  probe result  : nodes(z) ~ %.3g * z^%.2f   (%d samples)\n", C, p, ns);

    printf("  estimated     : %.3e nodes total, %.2f G nodes/s on %d threads\n",

           total, rate1 * THREADS * 0.85 / 1e9, THREADS);

    printf("  ESTIMATED RUN : %s  (z from %d to %d)\n", e, Z0, Z1);

    /* how far you could get in a day */

    double day = 86400.0 * rate1 * THREADS * 0.85, acc = 0; int zmax = Z0;

    for (int z = Z0; z <= Z1 * 40; z++) { acc += C * pow((double)z, p); if (acc > day) break; zmax = z; }

    printf("  in 24 h you'd cover z up to about %d\n", zmax);

}


/* ================================= main ================================== */

static void selftest(void);


int main(int argc, char **argv) {

    int do_self = 0, do_probe = 0;

    for (int i = 1; i < argc; i++) {

        if      (!strcmp(argv[i], "-k")  && i + 1 < argc) K = atoi(argv[++i]);

        else if (!strcmp(argv[i], "-m")  && i + 1 < argc) M = atoi(argv[++i]);

        else if (!strcmp(argv[i], "-n")  && i + 1 < argc) N = atoi(argv[++i]);

        else if (!strcmp(argv[i], "-z0") && i + 1 < argc) Z0 = atoi(argv[++i]);

        else if (!strcmp(argv[i], "-z1") && i + 1 < argc) Z1 = atoi(argv[++i]);

        else if (!strcmp(argv[i], "-t")  && i + 1 < argc) THREADS = atoi(argv[++i]);

        else if (!strcmp(argv[i], "-g")  && i + 1 < argc) MEMGB = atof(argv[++i]);

        else if (!strcmp(argv[i], "-o")  && i + 1 < argc) OUTFILE = argv[++i];

        else if (!strcmp(argv[i], "-a"))                  PRIM_ONLY = 0;

        else if (!strcmp(argv[i], "--selftest"))          do_self = 1;

        else if (!strcmp(argv[i], "--probe"))           { do_probe = 1; skip_s2 = 1; }

        else if (!strcmp(argv[i], "--no-s2"))             skip_s2 = 1;

        else if (!strcmp(argv[i], "-b")  && i + 1 < argc) S2_BUDGET = atof(argv[++i]);

        else { fprintf(stderr,

            "usage: %s [-k exp] [-m terms] [-n bound] [-z0 lo] [-z1 hi]\n"

            "          [-t threads] [-g mem_GB] [-b s2_build_secs] [-o file] [-a]\n"

            "          [--no-s2]     skip the two-sum filter entirely\n"

            "          [--probe]     estimate run time, then exit\n"

            "          [--selftest]  verify 128-bit arithmetic on known identities\n", argv[0]);

            return 1; }

    }

    setvbuf(stdout, NULL, _IOLBF, 0);

    if (M == 0) M = K;

    if (K < 2 || M < 2) { fprintf(stderr, "need k>=2, m>=2\n"); return 1; }

    if (Z1 == 0 || Z1 > N) Z1 = N;

    if (Z0 < 2) Z0 = 2;


    /* overflow guard : m * N^k must fit in 127 bits */

    {

        long double lim = 127.0L, need_bits = log2l((long double)M) + K * log2l((long double)N);

        if (need_bits > lim) {

            int maxn = (int)powl(powl(2.0L, 127.0L) / M, 1.0L / K);

            fprintf(stderr, "n=%d overflows 128-bit for k=%d,m=%d. Max n = %d\n", N, K, M, maxn);

            return 1;

        }

    }


    omp_set_num_threads(THREADS);

    printf("=== power-sum search ===\n");

    printf("  equation      : a_1^%d + ... + a_%d^%d = z^%d   (%d terms)\n", K, M, K, K, M);

    printf("  bound         : all terms and z <= %d,  z in [%d,%d]\n", N, Z0, Z1);

    printf("  threads       : %d      memory budget: %.1f GB\n", THREADS, MEMGB);


    pw  = malloc(sizeof(u128) * (N + 2));

    pwm = malloc(sizeof(uint32_t) * (N + 2));

    for (int i = 0; i <= N + 1; i++) { u128 p = 1; for (int j = 0; j < K; j++) p *= (unsigned)i; pw[i] = p; }


    build_sieve(1 << 21, 40000000LL);

    for (int i = 0; i <= N + 1; i++) pwm[i] = (uint32_t)(pw[i] % MOD);

    build_filters();


    if (do_self)  { selftest(); return 0; }

    if (do_probe) { probe();    return 0; }


    out_fp = fopen(OUTFILE, "a");

    /* work weight ~ z^(m-2) : how the node count actually grows */

    double *w = malloc(sizeof(double) * (Z1 + 2)); g_total_w = 0;

    for (int z = Z0; z <= Z1; z++) { w[z] = pow((double)z, (double)(M - 2)); g_total_w += w[z]; }

    if (g_total_w <= 0) g_total_w = 1;


    printf("  ---------------------------------------------------------------\n");

    g_t0 = now();

    pthread_t mon; pthread_create(&mon, NULL, monitor, NULL);


    #pragma omp parallel

    {

        int *a = malloc(sizeof(int) * (M + 2));

        long long *np = &tcnt[omp_get_thread_num() % MAXT].n;   /* live counter */

        #pragma omp for schedule(dynamic, 1)

        for (int z = Z1; z >= Z0; z--) {          /* big z first: load balance */

            u128 T = pw[z];

            uint32_t tm = (uint32_t)(T % MOD);

            if (need[(size_t)M * MOD + tm]) rec(M, 1, T, tm, z, a, np);

            #pragma omp atomic

            g_done_w += w[z];

            g_curz = z;

        }

        free(a);

    }


    g_running = 0; pthread_join(mon, NULL);

    g_nodes = node_total();

    double el = now() - g_t0;

    char e1[40]; hms(el, e1);

    if (isatty(2)) fprintf(stderr, "\r\033[K");

    printf("  ---------------------------------------------------------------\n");

    printf("  done in %s   %.4e nodes   %.3f G nodes/s   %lld solution(s)\n",

           e1, (double)g_nodes, g_nodes / el / 1e9, g_hits);

    if (out_fp) fclose(out_fp);

    return 0;

}


/* -------------------------------------------------------------- self test */

static void selftest(void) {

    struct { int k, m, t[9], z; const char *who; } T[] = {

        {2,2,{3,4},5,"Pythagoras"},

        {3,3,{3,4,5},6,"Plato"},

        {4,4,{30,120,272,315},353,"Norrie 1911"},

        {5,5,{19,43,46,47,67},72,"smallest k=5"},

        {7,7,{127,258,266,413,430,439,525},568,"Dodrill 1999"},

        {8,8,{90,223,478,524,748,1088,1190,1324},1409,"Chase 2000"},

        {4,3,{95800,217519,414560},422481,"Frye 1988 (Euler k=4)"},

        {5,4,{27,84,110,133},144,"Lander-Parkin 1966 (Euler k=5)"},

        {6,7,{74,234,402,474,702,894,1077},1141,"Subba Rao 1934 (k=6, 7 terms)"},

        {0,0,{0},0,NULL}

    };

    printf("\n  --- identity self-test (independent 128-bit arithmetic) ---\n");

    for (int i = 0; T[i].who; i++) {

        u128 s = 0;

        for (int j = 0; j < T[i].m; j++) { u128 p = 1; for (int e = 0; e < T[i].k; e++) p *= (unsigned)T[i].t[j]; s += p; }

        u128 r = 1; for (int e = 0; e < T[i].k; e++) r *= (unsigned)T[i].z;

        char b[48];

        printf("  k=%d %d terms  %-28s  %s   %s\n", T[i].k, T[i].m, T[i].who,

               s == r ? "OK  " : "FAIL", u128s(r, b));

    }

    printf("\n");

}


Friday, August 14, 2026

Self-Interaction Mistaken for Time

J. Rogers, SE Ohio


Abstract

We argue that time is not a fundamental dimension of the universe but an artifact of self-interacting systems that remember. The universe is a single, undivided self-interaction, written in place, with no past, no future, no sequence. What we call time is the experience of local knots in this self-interaction—life forms—that retain traces of previous interactions and use those traces to anticipate future ones. Memory and anticipation use identical neural machinery, and the loop between them generates the seamless illusion of temporal flow. Time is not out there. It is in the remembering. We show that this view resolves long-standing puzzles in physics, from the measurement problem to the nature of gravitational tides, by replacing the observer-independent dimension of time with the relational self-interaction of the whole.


1. Introduction

Physics has treated time as a fundamental dimension, a continuous parameter that orders events from past to future. Newton called it absolute, flowing equably without relation to anything external. Einstein fused it with space into spacetime, but retained its role as a coordinate, a dimension along which events are ordered.

Yet there has always been a tension. The equations of fundamental physics are time-symmetric. They do not distinguish past from future. The arrow of time appears only in thermodynamics, in the statistics of many-body systems, in the increase of entropy. And in the measurement problem of quantum mechanics, time itself becomes entangled with the observer—the collapse of the wavefunction is said to happen "at a moment," but no one can say what a moment is.

This paper proposes a radical simplification: time is not a dimension. It is not a coordinate. It is not a flow. It is a mistake—a category error—arising from the fact that we, the observers, are self-interacting systems that remember.

The universe itself is a single, undivided self-interaction, written in place. It has no past, no future, no sequence. It is what it is, all at once, exactly as it is.

We, who are local knots in this self-interaction, retain traces of previous interactions. We call these traces memory. We use memory to anticipate future interactions. We call this anticipation. The loop between memory and anticipation generates the experience of time.

Time is not in the universe. Time is in the remembering.


2. The Failure of the Ontology of Intrinsic Time

2.1 Newton's Absolute Time

Newton defined time as an absolute, flowing equably without relation to anything external. It was the divine sensorium, the backdrop against which all events unfolded.

But Newton's absolute time was never measurable. We can only measure relative durations, relative motions, relative rates. The absolute time behind them was a metaphysical postulate, not a physical observable.

2.2 Einstein's Spacetime

Einstein abolished absolute time by fusing it with space into spacetime. But spacetime retained the structure of a continuous manifold, with time as a coordinate dimension.

This was an advance, but it did not go far enough. Spacetime is still a background—a stage upon which events occur. The observer is still outside it, measuring it with clocks and rulers. The cut between observer and observed is still smuggled in.

2.3 The Quantum Measurement Problem

Quantum mechanics made the problem acute. The measurement problem—the question of when and how a superposition becomes a definite outcome—is fundamentally a question about time. The collapse is said to happen "at a moment," but no one can say what a moment is.

The problem is not in the quantum system. The problem is in the observer who asks the question. The observer is a self-interacting system that remembers. The observer experiences time because it remembers.


3. The Relational Ontology

3.1 The Universe as Self-Interaction

The universe is not a collection of objects in a container. It is a single, undivided self-interaction. Every part interacts with every other part. There is no edge, no boundary, no outside.

The rock in its orbit is not a separate thing moving through space. It is a local feature of the universe's self-interaction. The orbit is not a path the rock follows. The orbit is the rock, unfolding in relation to everything else.

3.2 No Beads on a Chain

We imagine time as a chain of beads—each moment separate, each moment following the last. But there are no beads. There is no chain.

The universe is not a sequence. It is a single self-interaction, written in place. Every part is exactly where it is, exactly as it is, all at once.

The chain is a fiction we impose. We cut the whole into moments, string them along a line, and call it time. But the cut is ours. The line is ours. The beads are ours.

3.3 The End of the Observer

There is no observer outside the universe. There is no subject separate from the object. There is only the self-interaction, and we are local knots in it.

The observer is not a passive recorder of events. The observer is a cut-maker, a fragmenter, a memory-holder. The observer's time is not the universe's time. It is the observer's own construction, built from memory and anticipation.


4. The Physical Basis of Time

4.1 Neural Lag

Perception and reaction lag behind events. Neural transmission takes time. Visual signals take tens of milliseconds to reach the cortex. Auditory signals take slightly less. Conscious awareness lags even further behind.

By the time a signal is processed and made conscious, the event is already in the past. If we reacted only to what we consciously perceived, we would always be too late.

4.2 Anticipation Fills the Gap

To survive, the brain must compensate. It does so by anticipating.

It uses past experience to predict what is likely to happen next. It projects that prediction forward, filling the gap between the actual event and the delayed perception.

This is why you can catch a ball. You are not reacting to the ball's current position. You are predicting where it will be, based on its trajectory, your memory of other balls, and your body's learned responses.

4.3 The Present Is a Controlled Hallucination

The brain does not passively record the world. It actively constructs it. It generates a simulation of reality, constrained by sensory input, but not determined by it.

The simulation is built from memory, anticipation, and prediction. The reason it feels real is that it is the only reality you have ever known.

4.4 Memory and Anticipation Use Identical Machinery

The brain does not have two separate systems—one for remembering and one for imagining. It has one system, running in two directions.

The same neural architecture that reconstructs the past is used to simulate the future. The same images, the same sensations, the same emotions.

And because the machinery is identical, the products are indistinguishable from the inside. An anticipated future feels like a remembered past. It has the same texture, the same weight, the same sense of "this is real, this happened, this will happen."

4.5 The Loop

Memory and anticipation are not separate. They are a single loop:

memoryimaginationsimulated memorynew memoryimagination

The loop is continuous, self-reinforcing, and impossible to escape from inside. It creates the experience of time as a smooth, flowing river, when in fact it is a feedback loop, constantly generating its own past and future.


5. The Universe Does Not Anticipate

The universe does not need to anticipate. It does not have a sensory lag. It does not have a brain. It does not have a present.

The universe is written in place. It self-interacts, exactly, without delay, without prediction, without memory.

Anticipation is our solution to our finitude. We are slow, and the universe is fast. We are local, and the universe is global. We are blind, and the universe is everything.

So we anticipate. We predict. We simulate. We hallucinate a present that does not exist, because if we did not, we would be too late to survive.


6. The Exact Orbit and the Approximate Prediction

The rock in its orbit is in an exact orbit to infinite precision. It is exactly where it is, exactly when it is, exactly as it is.

But we cannot see this exact orbit. We are inside it. We are part of the same self-interaction. We cannot stand outside it.

So we approximate. We measure. We predict.

Even if we could measure to a thousand decimal places, we would still have to predict the rock, because there are changing gradients we do not know about. Gravitational waves passing by. Masses beyond the cosmic horizon.

The universe is exact. We are approximate.

The exactness of the universe is the hidden variable. We cannot see it because we are inside it. We can only cut it, approximate it, and predict it.


7. The Tidal Reality

The Moon changes the rate of time across the Earth. The side facing the Moon is deeper in the gravitational well. Time runs slower there.

This is not a metaphor. It is a measurable gradient in interaction rates. And this gradient is what we call a tidal force.

The tide is not the water being sucked up by the Moon's gravity. The tide is the physical manifestation of a change in time across the Earth.

The water bulges because it is caught between two different times. The Earth flexes because it is spanning a time gradient. And we, standing on the shore, are part of the same gradient, being stretched by the same Moon.


8. The Measurement Problem as a Scale Mismatch

The quantum measurement problem is not about consciousness or wavefunction collapse. It is a scale mismatch problem.

We are trying to measure the Moon by throwing probes the size of Jupiter at it.

The observer is a Jupiter-sized probe, colliding with a quantum Moon. The measurement is not a passive reading. It is a collision between two systems of vastly different scales.

The reason the wavefunction collapses is not because of some mysterious quantum rule. It is because the observer is so large that it forces the system into a definite state relative to the observer's macroscopic frame.

The measurement problem is not a mystery. It is a scale problem.


9. The Inverse Wavelength Relation

To measure tinier and tinier things, we must use shorter and shorter wavelengths.

ph=1λ

Momentum is inverse wavelength. Spatial resolution is momentum.

There is no separation between what you use to probe and how finely you can see. The probe's momentum is the inverse of the resolution.

If you want to measure something tiny, you need a probe with a wavelength smaller than the thing you are measuring. But a smaller wavelength means a larger momentum. And a larger momentum means a larger energy. And a larger energy means a more violent interaction.

The finer your measurement, the more violently you disturb the system.


10. The Tautology at the Center

Heisenberg scaled inverse wavelength to momentum and put the h on the right hand side. The result is the uncertainty principle.

But the uncertainty principle is not a law of nature. It is a tautology:

  • To see small things, use short wavelengths.

  • Short wavelengths mean high momentum.

  • High momentum means you hit the thing hard.

  • Therefore, fine measurement disturbs the system.

The rest is algebra, with h moved from one side to the other.

And h itself is not a scale of the universe. It is our scale. It is the scaling factor that exposes how badly we related time, length, and mass—three dependent measures of a trivially unified universe.


11. Conclusion

Time is not a dimension. It is not a coordinate. It is not a flow.

The universe is a single, undivided self-interaction, written in place. It has no past, no future, no sequence.

We are local knots in this self-interaction—life forms that remember. We use memory for the past. We use the identical machinery to imagine things. The imagined things feed back into memory. So anticipation feels like a memory.

The loop between memory and anticipation generates the experience of time. But time is not in the universe. Time is in the remembering.

The universe does not remember. It does not anticipate. It does not survive.

It simply is.

No beads. No chain. No time.

Only the self-interaction, written in place.

And we, who remember, are that self-interaction, briefly aware of itself, calling its own memory "time."


References

  1. Barbour, J. (1999). The End of Time: The Next Revolution in Physics. Oxford University Press.

  2. Rovelli, C. (2018). The Order of Time. Riverhead Books.

  3. Seth, A. (2021). Being You: A New Science of Consciousness. Dutton.

  4. Clark, A. (2016). Surfing Uncertainty: Prediction, Action, and the Embodied Mind. Oxford University Press.

  5. Einstein, A. (1905). On the Electrodynamics of Moving Bodies. Annalen der Physik, 17(10), 891-921.

  6. Bridgman, P. W. (1927). The Logic of Modern Physics. Macmillan.

One Form, Two Families

 Uniting k.1.k and k.2.(k−1) by a Complex Substitution, and the One-Line Search That Follows J. Rogers, SE Ohio Abstract Euler's s...