Skip to content

Releases: aratraw/delta_analysis

Δ‑analysis v0.2: Full‑stack exact numerics — from lazy series summations to DEC on arbitrary grids

04 May 04:33
268c5f7

Choose a tag to compare

Δ‑analysis Library — v0.2 Release Notes

Date: 2026‑05‑04
DOI (paper): 10.5281/zenodo.18761044
DOI (software, v0.1): 10.5281/zenodo.18934082
License: PolyForm Small Business License 1.0.0


Version 0.2 represents a comprehensive re‑engineering of the Δ‑analysis library. The codebase has been transformed from an initial research prototype into a stable, high‑performance platform for exact constructive computation. This release incorporates a complete redesign of the arithmetic engine, integrates the geometry and numerical modules into a unified framework, and provides extensive human‑written documentation backed by over 400 test cases.

Below we provide a technical summary of the implementation work and the resulting capabilities of the library.


1. Rational Arithmetic Engine — Complete Redesign

The rational number subsystem has been rewritten from the ground up to achieve three simultaneous goals: exactness of all arithmetic operations, compactness of internal representations, and performance that meets or exceeds the raw Boost.Multiprecision backend on which it is built.

1.1 Eager Rational: Zero‑Overhead Wrapper

The Rational class is implemented as a thin wrapper over boost::multiprecision::rational_adaptor<cpp_int_backend<...>>. Backend parameters were tuned empirically:

  • MinBits = 128 — integers up to 128 bits (approximately 38 decimal digits) are stored directly on the stack via Boost's small‑object optimisation, avoiding heap allocation for the vast majority of intermediate values
  • MaxBits = 0 — unlimited precision is available when needed; no artificial bound on representable magnitude
  • signed_magnitude with unchecked — standard representation without runtime overhead for sign checks or range validation
  • et_off — expression templates are disabled, giving the library full control over when evaluation occurs and eliminating the indirection cost of Boost's own lazy layer

The result is an eager rational type whose performance is indistinguishable from raw Boost.Multiprecision (measured difference ≤ 1%, within benchmark noise), validated on workloads of up to 500 000 terms with random, dyadic, and harmonic‑series fractions.

1.2 Lazy Rational Engine: Architecture

The LazyRational class introduces a mutable, move‑only expression graph with deferred evaluation. Key design elements:

  • Mutable tree construction. Binary arithmetic operators mutate the left operand in place: a + b absorbs the tree of b into a, amortised O(1) per operation. This enables chained accumulation acc + term1 + term2 + ... without constructing intermediate objects. Copy semantics are intentionally deleted; explicit deep copies are obtained via .clone()

  • Dirty / clean state machine. A lazy expression begins in a dirty state — a local, un‑validated tree of DirtyNode objects. The canonicalize() method converts it into a clean state, where the entire expression is represented by a single integer index into the global node pool. The transition involves algebraic simplification and hash‑consing, after which the node is immutable and shared

  • Heterogeneous SUM and PRODUCT nodes. Leaf constants are stored directly in leaf_values vectors rather than as separate CONST child nodes. This reduces node count, flattens the tree, and is essential for efficient batching during evaluation

1.3 Global Node Pool and Hash‑Consing

The clean node pool (delta::internal::NodePool) is a thread‑local, append‑only vector with several hash‑consing caches:

  • constant_cache: maps Value → CONST node index
  • sum_product_cache: maps canonicalised operand sets to SUM/PRODUCT node indices
  • unary_cache: maps (LazyOp, children, eps_idx) tuples to indices for NEG, RECIP, SQRT, EXP, LOG, SIN, COS, ACOS, PI, E, POW

The caches guarantee that structurally identical sub‑expressions are represented by a single node. Structural equality between clean expressions reduces to integer comparison of their clean_index_ values.

Pool allocation uses a next_free_index hint with chunked growth of 4096 nodes. Slots are only reclaimed by the garbage collector, never reused individually. This simplifies reference counting and makes pool indices stable between GC cycles.

1.4 Garbage Collection as Deferred Computation

When the pool occupancy exceeds 90% of max_size (default 1 000 000 nodes), collect_garbage() is triggered:

  1. All live clean LazyRational objects are identified via the clean object registry (g_clean_rationals, a thread‑local unordered set)
  2. Each live root's entire subtree is evaluated to a single Value
  3. A new pool is created; each evaluated value is stored as a CONST node at the same index as the original root
  4. The old pool is discarded

Thus, garbage collection is the computational step that forces deferred evaluation. It is not an incidental cleanup; it is the moment all outstanding lazy work is settled.

The clean object registry also enables reset_pool() — a full teardown that destroys all clean trees, reinstates every LazyRational object as a dirty zero, and creates an empty pool. This is used between independent computation phases.

1.5 Algebraic Simplification

The simplify_tree() function (in simplify_impl.h) performs constructive rewrites on a TempNode tree during canonicalization:

  • Flattening of nested SUM and PRODUCT nodes
  • Removal of neutral elements (0 in sums, 1 in products)
  • Grouping of identical scalar constants (a + a → 2*a) and identical sub‑expressions (A + A → 2*A, A * A → A^2)
  • Distributivity: a*b + a*c → a*(b+c) when common factors are detected
  • Cancellation: x + NEG(x) → 0, x * RECIP(x) → 1
  • Inverse functions: NEG(NEG(x)) → x, EXP(LOG(x)) → x, LOG(EXP(x)) → x
  • Power simplifications: x^0 → 1, x^1 → x, 1^y → 1, (x^a)^b → x^(a*b) for integer exponents

Simplification is not a default — it must be requested or is triggered implicitly by eval(). The destructive eval_inplace(true) path skips it entirely, avoiding its cost for unstructured sums where it provides no benefit. Benchmarks show that simplification can reduce evaluation time by 10–1000× on structured expressions, but is a net loss for flat sums.

1.6 Evaluation and Pyramidal Compact Reduction

Dirty tree evaluation uses evaluate_tree(), which performs post‑order traversal with intermediate result caching. For SUM nodes, pyramidal compact reduction (PCR) groups terms into batches of 32 and reduces them hierarchically. This minimises intermediate rational growth by ensuring that additions occur in a balanced tree rather than a linear chain.

  • eval_inplace(true) uses destructive PCR: it moves leaf values out of the node, modifies them in place, and produces a single constant result, replacing the dirty tree with a clean CONST node
  • eval() copies the necessary data and preserves the original tree

PCR with batch size 32 was chosen because it keeps the results of summing 32 random fractions within 128 bits for typical inputs, allowing the stack‑allocated small‑integer path of Boost's backend to dominate.


2. Transcendental Functions — Implementation and Mutual Consistency

All elementary transcendental functions are provided in both eager (returning Rational) and lazy (creating LazyRational nodes) forms. They accept an explicit absolute error bound ε (default 1e‑30).

2.1 Hybrid Evaluation Strategy

A function‑by‑function decision was made on when to use a floating‑point path versus a pure rational series path, based on extensive benchmarks:

  • Float path (cpp_dec_float_100): Used for sin, cos, exp, π, acos, asin, atan, tan when ε ≥ 1e‑35. The float path is 2–3× faster at moderate precision but loses relative accuracy for large arguments or very small ε
  • Series path (pure rational): Used for sqrt, log, e at all precisions; used for all functions when ε < 1e‑35. For exp, the series path is also forced when |x| > 20 because the float path's mantissa cannot preserve relative accuracy at extreme magnitudes

2.2 Specific Implementations

  • Square root: Newton's method with an integer floor‑sqrt initial guess (isqrt(num*den) / den). This yields compact rationals (a few hundred bits for typical inputs) and avoids the representation bloat that plagued earlier versions using x/2 as the initial guess. The integer root is extracted via a fast Newton method on dumb_int, with a preliminary mod‑256 filter for quick rejection of non‑squares

  • Exponential: Scaling‑and‑squaring combined with a Padé approximant, with the order chosen adaptively based on ε. Argument reduction multiplies ε by 2^(exp_bits + k + 2) to guarantee that, after squaring, the absolute error remains within tolerance. The reduction threshold is 2.0 — lowering it to 1.0 was empirically shown to produce bloated intermediate fractions that cripple downstream operations (log(exp(x)) became catastrophically slow)

  • Logarithm: Argument reduction to [1/2, 2] via k·ln(2), then the arctanh series ln((1+y)/(1-y)) = 2(y + y³/3 + y⁵/5 + …) with y = (m‑1)/(m+1). ln(2) is computed once and cached

  • Sine and Cosine: Binary splitting of the Maclaurin series with exact rational reduction modulo 2π. The binary splitting formula:

    P, Q, T = merge((P_L, Q_L, T_L), (P_R, Q_R, T_R))
    P = P_L * P_R
    Q = Q_L * Q_R
    T = T_L * Q_R + P_L * T_R
    

    prevents intermediate rational swell far more effectively than iterative term‑by‑term summation

  • π (Pi): Chudnovsky algorithm with binary splitting, providing approximately 14.18 decimal digits per term. For small N (< 16), a recurrence is used; ...

Read more

delta_analysis v0.1: Initial implementation of discrete analysis foundations

10 Mar 07:27
4defa83

Choose a tag to compare

Release v0.1.0 – Δ‑analysis Library: A Constructive Refounding of Mathematical Analysis

We are thrilled to announce the first public release of the Δ‑analysis library, a modern C++20 implementation of a radical reformulation of mathematical analysis. Instead of assuming a pre‑existing continuum, Δ‑analysis builds everything from a single elementary operation: between any two addresses a new one can be inserted. This process generates finite grids that converge to a continuum – but the continuum is never a primitive; it remains a regulative horizon.

This library strives to translate the theoretical framework (detailed in our 900‑page monograph, DOI:10.5281/zenodo.18761044) into a practical tool for constructive numerical methods, adaptive algorithms, and explorations of non‑classical geometries (p‑adic, tree‑based, matrix spaces, etc.).


✨ Key Features Right Now

1. Regulative Ideas as First‑Class Citizens

Choose the geometric structure of your computation:

  • Linear order with Euclidean metric (classical real analysis).
  • p‑adic metric for non‑archimedean experiments.
  • Tree‑based betweenness (binary strings) with ultrametric – integrate the Dirichlet function without measure theory.
  • Matrix‑valued addresses (Eigen::MatrixXd) for tensor field approximations.
    Easily add your own by implementing simple concepts (Betweenness, Metric).

2. Flexible Grid Refinement

  • Static operators: midpoint, fixed‑λ, user‑defined.
  • Dynamic operators: change the insertion rule with the level.
  • Adaptive operators: insert points based on the function’s local variation – the library’s AdaptiveDeltaPath clusters points where they are needed most, dramatically accelerating convergence for functions with sharp features.

3. Operational Functions

Store and extend function values consistently across refinement levels. Specialised for UniformGrid, it provides O(1) access – ideal for repeated evaluations in loops.

4. Calculus on Grids

  • Riemann sums (left, right, tagged, tree‑adapted).
  • Continuity checks with arbitrary moduli (PowerModulus, LogarithmicModulus).
  • Differentiability checks comparing one‑sided difference quotients against a convergence modulus.

5. Performance & Reliability

  • OpenMP acceleration for computing maximal oscillation (optional).
  • Double buffering in DeltaPath::advance() to minimise allocations.
  • Exact rational arithmetic with configurable precision (dynamic or stack‑allocated via Boost.Multiprecision).
  • Comprehensive test suite (95%+ coverage) and Google Benchmarks to track performance.

6. Examples & Benchmarks

  • Dirichlet function on binary strings – becomes locally constant under the tree regulative idea; its integral converges to ½.
  • Adaptive refinement of |x‑0.5| – points cluster at the corner, runtime stays almost constant as accuracy increases.
  • Matrix‑valued functions – integrate the identity on [0·I, I] and obtain 0.5·I.
  • Non‑commutativity demo – applying λ=1/3 and λ=2/3 in different orders yields different intermediate grids, proving that Δ‑analysis captures the process, not just the limit.

🚀 Quick Start

#include <delta/core/adaptive_delta_path.h>
#include <delta/core/rational.h>
#include <iostream>

using namespace delta;

int main() {
    // Function with a corner: |x - 1/2|
    auto func = [](const Rational& x) {
        return abs(x - Rational(1,2));
    };

    // Adaptive operator with threshold 0.1 and epsilon 0.05
    AdaptiveOperator adapt_op(Rational(1,10), Rational(1,20));
    std::vector<Rational> init = {0_r, 1_r};

    // Build adaptive path – threshold 0.01
    auto path = AdaptiveDeltaPath<Rational, Rational, Rational,
                                  LessBetweenness, EuclideanMetric,
                                  EuclideanValueMetric, AdaptiveOperator>(
        init, func, adapt_op, Rational(1,100)
    );

    // Perform 10 refinement steps
    for (int i = 0; i < 10; ++i) path.advance();

    std::cout << "Points after adaptive refinement: " << path.size() << "\n";
    // Points cluster around 0.5, while linear parts remain coarse.
    return 0;
}

For more examples, see the examples/ and tests/ directories.


📦 Building

Requirements:

  • CMake 3.15+
  • C++20 compiler (MSVC 19.29+, GCC 11+, Clang 14+)
  • vcpkg recommended for dependencies (Boost, Eigen3, fmt, Google Test, Google Benchmark)

Using CMake Presets (Windows/Linux/macOS):

# Configure and build (example for Windows Debug)
cmake --preset x64-debug
cmake --build out/build/x64-debug

# Run tests
cd out/build/x64-debug
ctest --output-on-failure

See the README for detailed instructions.


📚 Documentation & Theory

  • Full mathematical exposition: Zenodo DOI:10.5281/zenodo.18761044 (900+ pages).
  • Doxygen comments in headers – the primary reference for the API.
  • Test suite – every feature is demonstrated and verified.

📄 License

This project is dual‑licensed:


🙏 Acknowledgements

  • Boost.Multiprecision for the Rational type.
  • Eigen for linear algebra.
  • {fmt} for modern formatting.
  • Google Test and Google Benchmark for testing and benchmarking.

🔮 What’s Next?

  • Extended regulative ideas: graphs, categories, stochastic processes.
  • Δ‑differential equations: adaptive solvers with rigorous error bounds.
  • Higher dimensions: tensor grids and finite element style refinement.
  • Performance optimisations: GPU support, parallel adaptive refinement.

We welcome issues, discussions, and ideas. Pull requests are generally not accepted (the library follows a planned roadmap), but exceptional contributions aligned with the vision may be considered.


Explore the discrete foundations of analysis and numerical methods with Δ‑analysis.
Star the repo, try the code, and let us know what you build!

GitHub Repository | Zenodo Paper