Purpose and Scope

This resource presents a computational engine and visualisation tools for the study of addition chains – sequences of integers starting at 1 where each subsequent term is the sum of two earlier terms, terminating at a given target n. The minimal length of such a chain is denoted ℓ(n) and is the subject of OEIS sequence A003313.

The primary objective is to provide an open, reproducible platform for exploring the topological properties of optimal addition chains. By offering both a high‑performance C++ generator and interactive visualisations, we aim to bridge computational number theory with accessible educational tools. The code is designed for verification against the OEIS b‑file (b003313.txt), ensuring mathematical parity up to n = 100 000.

This project is part of a STEM initiative to encourage alternative perspectives on data interpolation and visualisation, fostering deeper understanding of algorithmic complexity and integer structures.

References:
OEIS Foundation Inc. (2025), The On‑Line Encyclopedia of Integer Sequences, A003313.
Knuth, D. E. (1998), The Art of Computer Programming, Vol. 2, §4.6.3.

>> SUB-ROUTINE: OPTIMAL ADDITION CHAINS

An addition chain for a target number n is a sequence of integers starting with 1, where every subsequent number is the sum of two earlier numbers in the sequence, ultimately ending precisely at n.

Target: 15

Standard Path (6 steps): 1 → 2 → 3 → 6 → 12 → 15
Optimal Path (5 steps): 1 → 2 → 3 → 5 → 10 → 15

The central goal of this discipline is finding the shortest possible chain, denoted as $l(n)$. It is a fundamental problem in computational mathematics, directly applicable to optimizing computer algorithms, particularly modular exponentiation in cryptography. Calculating the shortest path is computationally expensive, making pre-calculated registries highly valuable.

>> OEIS A003313 – VERIFICATION FRAMEWORK

The OEIS A003313 sequence contains the minimal length of an addition chain for each integer n. This serves as the mathematical ground truth for validating our engine's output.

The b-file (b003313.txt) provides the first 100,000 terms. Our verify_db.cpp utility cross‑checks the generated Fat Index against this official dataset, ensuring 100% parity.

Verification Workflow

1. Download b003313.txt from OEIS.
2. Place it in the same directory as chain_index.bin.
3. Run ./verify_db – it will report any mismatches.
4. Output: [SUCCESS] 100% PARITY ACHIEVED!

This process guarantees that our computed chain lengths (stored in the 16‑bit length field of the Fat Index) are mathematically correct for all n up to the limit of the OEIS b‑file.

Note: The OEIS b‑file is updated by the mathematical community. Our engine and repacker tools are designed to be easily re‑verified whenever a new b‑file is published.

>> SOURCE CODE – C++ ENGINE

The addition chain ecosystem is built on three C++ tools, each serving a distinct role in the pipeline. Click the file names to expand and view the source code, or use the download link to save the raw file.

offset_engine.cpp

⬇ Download

The core sliding‑window solver. Uses parallel IDDFS with ordered I/O to generate optimal addition chains for a given range. Produces the chain_index.bin and chain_blob.bin databases.

▼ View source
#include <iostream>
#include <vector>
#include <fstream>
#include <cmath>
#include <cstdint>
#include <chrono>
#include <csignal>
#include <omp.h>
#include <iomanip>

using namespace std;

// ==========================================
// 1. CONFIGURATION
// ==========================================
const string FILE_INDEX = "chain_index.bin";
const string FILE_BLOB  = "chain_blob.bin";
const int RUN_LIMIT     = 100000;
const int FLUSH_CHUNK   = 120; // Multiple of 6 for clean batching

volatile sig_atomic_t keep_running = 1;
void sig_handler(int signum) { keep_running = 0; }

// ==========================================
// 2. ANALYZER & MATH HELPERS
// ==========================================
bool is_prime_n(uint32_t n) {
    if (n < 2) return false;
    if (n == 2 || n == 3) return true;
    if (n % 2 == 0 || n % 3 == 0) return false;
    for (uint32_t i = 5; i * i <= n; i += 6) {
        if (n % i == 0 || n % (i + 2) == 0) return false;
    }
    return true;
}

uint16_t calculate_enrichment_mask(uint32_t target_n, const vector<uint32_t>& chain) {
    uint16_t mask = 0;
    
    // Bit 0: Is Prime
    if (is_prime_n(target_n)) mask |= (1 << 0);
    
    // Bit 1: Is Safe Prime (Prime Double)
    if (is_prime_n(target_n) && is_prime_n((target_n - 1) / 2)) mask |= (1 << 1);
    
    // Bit 2: High Hamming Weight (More 1s than 0s)
    int bit_length = (int)std::log2((double)target_n) + 1;
    if (__builtin_popcount(target_n) > bit_length / 2) mask |= (1 << 2);

    return mask;
}

// ==========================================
// 3. FAST-BREAK IDDFS SEARCH
// ==========================================
bool search_chain(int target, int current_depth, int max_depth, vector<uint32_t>& chain, vector<uint32_t>& best_chain) {
    if (chain.back() == target) {
        best_chain = chain;
        return true; 
    }
    if (current_depth == max_depth) return false;
    if ((chain.back() << (max_depth - current_depth)) < target) return false;

    for (int i = chain.size() - 1; i >= 0; --i) {
        for (int j = i; j >= 0; --j) {
            uint32_t next_val = chain[i] + chain[j];
            if (next_val > chain.back() && next_val <= target) {
                chain.push_back(next_val);
                if (search_chain(target, current_depth + 1, max_depth, chain, best_chain)) return true;
                chain.pop_back(); 
            }
        }
    }
    return false;
}

vector<uint32_t> solve_n(uint32_t target_n) {
    vector<uint32_t> best_chain;
    int target_depth = (int)std::ceil(std::log2((double)target_n));
    
    while (best_chain.empty() && keep_running) {
        vector<uint32_t> initial_chain = {1};
        search_chain(target_n, 0, target_depth, initial_chain, best_chain);
        if (best_chain.empty()) target_depth++;
    }
    return best_chain;
}

// ==========================================
// 4. MAIN SLIDING WINDOW ENGINE
// ==========================================
int main() {
    signal(SIGINT, sig_handler);
    omp_set_num_threads(6); 
    
    // Both buffers are raw bytes to ensure perfect Little-Endian packing
    vector<uint8_t> buf_index;
    vector<uint8_t> buf_blob;

    uint32_t start_n = 2;
    uint64_t current_blob_offset = 0;

    // RESUME LOGIC (Fat Index is 12 bytes per N)
    ifstream check_index(FILE_INDEX, ios::binary | ios::ate);
    if (check_index.is_open()) {
        start_n = check_index.tellg() / 12; 
        check_index.close();
        
        ifstream check_blob(FILE_BLOB, ios::binary | ios::ate);
        if (check_blob.is_open()) {
            current_blob_offset = check_blob.tellg();
            check_blob.close();
        }
        cout << "[RESUME] Starting at N=" << start_n << " | Blob Offset: " << current_blob_offset << endl;
    } else {
        cout << "[NEW RUN] Formatting Fat-Index Database..." << endl;
        // Pad N=0 and N=1 with 24 blank bytes total (12 bytes each)
        for(int i=0; i<24; i++) buf_index.push_back(0); 
    }

    uint32_t last_processed_n = start_n - 1; 
    auto batch_start = chrono::high_resolution_clock::now();

    cout << "\n[SLIDING WINDOW MODE] 6 Cores Hunting Asynchronously..." << endl;

    // THE SLIDING WINDOW: Out-of-order math, In-order loop resolution
    #pragma omp parallel for ordered schedule(dynamic, 1)
    for (uint32_t n = start_n; n <= RUN_LIMIT; ++n) {
        
        if (!keep_running) continue;

        // ---------------------------------------------------------
        // ASYNC PHASE: 6 Cores calculate at maximum speed
        // ---------------------------------------------------------
        vector<uint32_t> best_chain = solve_n(n);
        uint16_t length = (uint16_t)(best_chain.size() - 1);
        uint16_t flags = calculate_enrichment_mask(n, best_chain);

        // ---------------------------------------------------------
        // ORDERED PHASE: Strictly Sequential File Packing
        // ---------------------------------------------------------
        #pragma omp ordered
        {
            if (keep_running) {
                last_processed_n = n; 

                // 1. PACK FAT INDEX (12 Bytes)
                // A. Offset (8 Bytes)
                for (int b = 0; b < 8; ++b) buf_index.push_back((current_blob_offset >> (b * 8)) & 0xFF);
                // B. Length (2 Bytes)
                buf_index.push_back(length & 0xFF);
                buf_index.push_back((length >> 8) & 0xFF);
                // C. Flags (2 Bytes)
                buf_index.push_back(flags & 0xFF);
                buf_index.push_back((flags >> 8) & 0xFF);

                // 2. PACK BLOB (Length Prefix + 32-bit Integers)
                buf_blob.push_back(best_chain.size() & 0xFF);
                buf_blob.push_back((best_chain.size() >> 8) & 0xFF);
                current_blob_offset += 2;

                for (uint32_t num : best_chain) {
                    buf_blob.push_back(num & 0xFF);
                    buf_blob.push_back((num >> 8) & 0xFF);
                    buf_blob.push_back((num >> 16) & 0xFF);
                    buf_blob.push_back((num >> 24) & 0xFF);
                    current_blob_offset += 4;
                }

                // 3. CHUNK FLUSH SAFETY
                if (n % FLUSH_CHUNK == 0 || n == RUN_LIMIT) {
                    ofstream out_idx(FILE_INDEX, ios::binary | ios::app);
                    out_idx.write(reinterpret_cast<const char*>(buf_index.data()), buf_index.size());
                    out_idx.close();

                    ofstream out_blob(FILE_BLOB, ios::binary | ios::app);
                    out_blob.write(reinterpret_cast<const char*>(buf_blob.data()), buf_blob.size());
                    out_blob.close();

                    buf_index.clear(); 
                    buf_blob.clear();
                    
                    auto batch_end = chrono::high_resolution_clock::now();
                    chrono::duration<double> diff = batch_end - batch_start;
                    
                    cout << ">>> FLUSHED up to N=" << n 
                         << " | Blob: " << current_blob_offset / 1024 << " KB"
                         << " | Time: " << fixed << setprecision(2) << diff.count() << "s <<<" << endl;
                         
                    batch_start = chrono::high_resolution_clock::now(); 
                }
            }
        }
    }

    // SAFE EXIT DUMP
    if (!buf_index.empty()) {
        ofstream out_idx(FILE_INDEX, ios::binary | ios::app);
        out_idx.write(reinterpret_cast<const char*>(buf_index.data()), buf_index.size());
        ofstream out_blob(FILE_BLOB, ios::binary | ios::app);
        out_blob.write(reinterpret_cast<const char*>(buf_blob.data()), buf_blob.size());
    }
    
    cout << "\n[SAFE EXIT] Engine halted cleanly at N=" << last_processed_n << endl;
    return 0;
}

repack_index.cpp

⬇ Download

Topological Enricher – augments the Fat Index with 16 additional topological flags (prime, safe prime, Hamming weight, Fibonacci, perfect square, etc.). Produces chain_index_enriched.bin for advanced analysis.

▼ View source
#include <iostream>
#include <vector>
#include <fstream>
#include <cmath>
#include <cstdint>
#include <omp.h>

using namespace std;

#pragma pack(push, 1)
struct FatIndexEntry {
    uint64_t blob_offset;
    uint16_t length;
    uint16_t flags;
};
#pragma pack(pop)

// ==========================================
// FAST MATH HELPERS
// ==========================================
bool is_prime_n(uint32_t n) {
    if (n < 2) return false;
    if (n == 2 || n == 3) return true;
    if (n % 2 == 0 || n % 3 == 0) return false;
    for (uint32_t i = 5; i * i <= n; i += 6) {
        if (n % i == 0 || n % (i + 2) == 0) return false;
    }
    return true;
}

bool is_perfect_square(uint32_t n) {
    uint32_t sq = std::round(std::sqrt(n));
    return (sq * sq == n);
}

bool is_square_free(uint32_t n) {
    if (n % 4 == 0) return false;
    for (uint32_t i = 3; i * i <= n; i += 2) {
        if (n % (i * i) == 0) return false;
    }
    return true;
}

bool is_binary_palindrome(uint32_t n) {
    uint32_t reversed = 0, temp = n;
    while (temp > 0) {
        reversed = (reversed << 1) | (temp & 1);
        temp >>= 1;
    }
    return reversed == n;
}

bool is_small_base_power(uint32_t n) {
    if (n < 9) return false; // Exclude 2^k since we have a dedicated bit for that
    for (uint32_t b = 3; b <= 7; ++b) {
        uint32_t temp = b;
        while (temp < n) temp *= b;
        if (temp == n) return true;
    }
    return false;
}

int main() {
    cout << "=========================================" << endl;
    cout << " 16-BIT TOPOLOGICAL ENRICHER / REPACKER  " << endl;
    cout << "=========================================" << endl;

    // 1. Read Index
    ifstream index_in("chain_index.bin", ios::binary | ios::ate);
    if (!index_in.is_open()) {
        cout << "[ERROR] Could not find chain_index.bin!" << endl;
        return 1;
    }
    
    streampos file_size = index_in.tellg();
    int total_records = file_size / sizeof(FatIndexEntry);
    
    index_in.seekg(0, ios::beg);
    vector<FatIndexEntry> ledger(total_records);
    index_in.read(reinterpret_cast<char*>(ledger.data()), file_size);
    index_in.close();

    cout << "Loaded " << total_records << " records. 6-Core Enrichment engaged..." << endl;

    // 2. Parallel Processing
    #pragma omp parallel for schedule(dynamic)
    for (uint32_t n = 2; n < total_records; ++n) {
        uint16_t mask = 0;
        uint16_t steps = ledger[n].length;
        
        // Bit 0: Is Prime
        bool is_p = is_prime_n(n);
        if (is_p) mask |= (1 << 0);
        
        // Bit 1: Safe Prime (Prime Double)
        if (is_p && is_prime_n((n - 1) / 2)) mask |= (1 << 1);
        
        // Bit 2: High Hamming Weight
        int bit_length = (int)std::log2((double)n) + 1;
        if (__builtin_popcount(n) > bit_length / 2) mask |= (1 << 2);

        // Bit 3: Peak Anomaly (Requires looking at neighbors)
        if (n > 2 && n < total_records - 1) {
            if (steps > ledger[n-1].length && steps > ledger[n+1].length) mask |= (1 << 3);
        }

        // Bit 4: Fibonacci Step
        uint64_t n2 = (uint64_t)n * n;
        uint64_t f1 = 5 * n2 + 4, f2 = 5 * n2 - 4;
        if (is_perfect_square(f1) || is_perfect_square(f2)) mask |= (1 << 4);

        // Bit 5: Factor Method Match
        // Instantly checks if L(N) == L(A) + L(B) for its smallest prime factor
        if (!is_p && n > 3) {
            for (uint32_t i = 2; i * i <= n; ++i) {
                if (n % i == 0) {
                    if (steps == ledger[i].length + ledger[n/i].length) {
                        mask |= (1 << 5);
                    }
                    break; // Only test smallest factor
                }
            }
        }

        // Bit 6: Perfect Square
        if (is_perfect_square(n)) mask |= (1 << 6);

        // Bit 7: Square-Free
        if (is_square_free(n)) mask |= (1 << 7);

        // Bit 8: Triangular Number (8n + 1 is square)
        if (is_perfect_square(8 * n + 1)) mask |= (1 << 8);

        // Bit 9: Exact Power of 2
        if ((n & (n - 1)) == 0) mask |= (1 << 9);

        // Bit 10: Mersenne Number (All 1s)
        if (((n + 1) & n) == 0) mask |= (1 << 10);

        // Bit 11: Binary Palindrome
        if (is_binary_palindrome(n)) mask |= (1 << 11);

        // Bit 12: Heavy Divisors (Composite focus)
        int divs = 0;
        for (uint32_t i = 1; i * i <= n; i++) {
            if (n % i == 0) { divs += (i * i == n) ? 1 : 2; }
        }
        if (divs >= 12) mask |= (1 << 12);

        // Bit 13: Base 3-7 Power
        if (is_small_base_power(n)) mask |= (1 << 13);

        ledger[n].flags = mask;
    }

    // 3. Save the new Enriched Index
    ofstream index_out("chain_index_enriched.bin", ios::binary);
    index_out.write(reinterpret_cast<const char*>(ledger.data()), file_size);
    index_out.close();

    cout << "[SUCCESS] Saved chain_index_enriched.bin!" << endl;
    cout << "Overwrite 'chain_index.bin' with this file." << endl;
    cout << "=========================================" << endl;

    return 0;
}

verify_db.cpp

⬇ Download

Integrity checker – loads the OEIS b‑file and the generated Fat Index, then performs a byte‑perfect verification of every chain length. Essential for ensuring mathematical accuracy.

▼ View source
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <cstdint>
#include <algorithm>

using namespace std;

// Force the compiler to pack this struct exactly to 12 bytes with no padding
#pragma pack(push, 1)
struct FatIndexEntry {
    uint64_t blob_offset; // 8 Bytes
    uint16_t length;      // 2 Bytes (This is the step count)
    uint16_t flags;       // 2 Bytes
};
#pragma pack(pop)

const string FILE_INDEX = "chain_index.bin";
const string OEIS_FILE = "b003313.txt";

int main() {
    cout << "=========================================" << endl;
    cout << " FAT INDEX DATABASE VERIFIER (v6.0)      " << endl;
    cout << "=========================================" << endl;

    // 1. Load the OEIS Gold Standard Database
    vector<uint8_t> oeis_cache(100005, 0); 
    ifstream gold_file(OEIS_FILE);
    int max_oeis = 0;
    
    if (gold_file.is_open()) {
        string line;
        while (getline(gold_file, line)) {
            if (line.empty() || line[0] == '#') continue;
            stringstream ss(line);
            int n, steps;
            if (ss >> n >> steps && n <= 100000) {
                oeis_cache[n] = steps;
                if (n > max_oeis) max_oeis = n;
            }
        }
        cout << "[System] Loaded OEIS A003313 up to N=" << max_oeis << endl;
    } else {
        cout << "[ERROR] Could not find " << OEIS_FILE << "!" << endl;
        return 1;
    }

    // 2. Load the New Fat Index Ledger
    ifstream index_file(FILE_INDEX, ios::binary | ios::ate);
    if (!index_file.is_open()) {
        cout << "[ERROR] Could not open " << FILE_INDEX << "!" << endl;
        return 1;
    }

    // Calculate how many numbers are actually mapped
    streampos file_size = index_file.tellg();
    int computed_count = file_size / sizeof(FatIndexEntry); // Divide by 12 bytes
    
    cout << "[System] Loaded Fat Index: " << computed_count << " records found." << endl;

    // Read the entire index into RAM (it's only ~1.2MB for 100k records, very fast)
    index_file.seekg(0, ios::beg);
    vector<FatIndexEntry> ledger(computed_count);
    index_file.read(reinterpret_cast<char*>(ledger.data()), file_size);
    index_file.close();

    if (computed_count <= 2) {
        cout << "[WARNING] Database only contains padding. No records found." << endl;
        return 0;
    }

    // 3. The Cross-Check
    int errors = 0;
    int verified = 0;
    
    // We check up to the smaller of the two databases
    int limit = min(max_oeis, computed_count - 1);

    cout << "\nVerifying N=2 to " << limit << "..." << endl;

    for (int n = 2; n <= limit; ++n) {
        if (oeis_cache[n] > 0) {
            // Read the step length directly from the Fat Index struct
            uint16_t engine_steps = ledger[n].length; 
            
            if (engine_steps != oeis_cache[n]) {
                if (errors < 15) { 
                    cout << " MISMATCH at N=" << n << " | OEIS: " << (int)oeis_cache[n] << " | Engine: " << (int)engine_steps << endl;
                }
                errors++;
            }
            verified++;
        }
    }

    // 4. The Verdict
    cout << "=========================================" << endl;
    if (errors == 0 && verified > 0) {
        cout << " [SUCCESS] 100% PARITY ACHIEVED! " << endl;
        cout << " All " << verified << " records perfectly match mathematical ground-truth." << endl;
    } else if (errors > 0) {
        cout << " [FAILED] " << errors << " Errors detected in the dataset." << endl;
        cout << " Check topological calculations." << endl;
    } else {
        cout << " [SKIPPED] No overlapping records to verify." << endl;
    }
    cout << "=========================================" << endl;

    return 0;
}
⚠ WARNING – 32‑BIT LIMITATION

The engine uses 32‑bit integers for chain values and internal calculations. It has been tested and verified only for n ≤ 100,000 (the range of the OEIS b‑file). Running it beyond this limit may lead to integer overflow or other undefined behaviour. If you need to generate a larger database, you must rebuild the tool with 64‑bit integers and adjust the binary format accordingly.

We do not provide or support a version for n > 100,000 at this time.

Compilation hints: All tools are built with g++ -std=c++11 -O3 -fopenmp (or -std=c++14). The engine and repacker benefit from OpenMP parallelism. Ensure the OEIS b‑file is placed in the working directory before running verify_db.