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.
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.
>> ACTIVE TERMINALS
Select an engine to interact with the addition chain datasets.
Addition Chain Terminal Engine (ACT)
Execute real-time synthesis to find optimal steps, calculate defects (δ), and identify topological anomalies like Safe Primes, Fibonacci Steps, and Brauer Factor traces.
Advanced Topological Grid (Visualizer)
Launch the D3.js powered multi-layer visualization tool. Explore addition chains through geometric projection, plotting sequences against complexity graphs.
Plain Text Viewer (Academic)
Minimalist, monochrome interface for querying addition chain data without visual distractions. Includes range tables, copy-to-clipboard, and CSV export.
>> 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.
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
⬇ DownloadThe 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
⬇ DownloadTopological 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
⬇ DownloadIntegrity 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;
}
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.