# dsfb-add — Algebraic Deterministic Dynamics Sweep
`dsfb-add` is the empirical sweep and figure-generation companion for the Algebraic Deterministic Dynamics (ADD) stack. It runs deterministic lambda sweeps, exports structural observables as CSV, and feeds a Colab notebook that regenerates the paper-facing figures from those CSVs.
At a high level, the crate does three things:
1. It evolves four deterministic toy models across a shared lambda grid.
2. It writes reproducible sweep outputs into `output-dsfb-add/<timestamp>/` at the repo root.
3. It provides the numerical substrate for the ADD paper's figures: cross-layer response curves, transport transition summaries, structural-law fits, robustness checks, and finite-size scaling diagnostics.
The stack it sweeps is:
- Algebraic Echo Theory (AET)
- Topological Charge Propagation (TCP)
- Resonance Lattice Theory (RLT)
- Invariant Word-Length Thermodynamics (IWLT)
## Citation
Primary paper for this crate:
- de Beer, R. (2026). *Algebraic Deterministic Dynamics (ADD): A Non-Stochastic Structural Extension of DSFB* (v1.0). Zenodo. DOI: [10.5281/zenodo.18830567](https://doi.org/10.5281/zenodo.18830567)
Zenodo record:
- https://zenodo.org/records/18830567
Suggested BibTeX:
```bibtex
@misc{debeer2026add,
author = {Riaan de Beer},
title = {Algebraic Deterministic Dynamics (ADD): A Non-Stochastic Structural Extension of DSFB},
year = {2026},
publisher = {Zenodo},
doi = {10.5281/zenodo.18830567},
url = {https://doi.org/10.5281/zenodo.18830567},
version = {1.0}
}
```
## Motivation
ADD extends the deterministic style of DSFB upward into structural dynamics. Instead of using stochastic primitives as the default language for memory, disorder, spread, and irreversibility, the ADD paper frames those effects through exact evolution rules on words, graphs, and trajectory-generated complexes.
`dsfb-add` is the empirical side of that argument. It is not a full physics engine and it does not claim microscopic fidelity. Its job is narrower and more useful for the paper: it provides a deterministic numerical laboratory in which the four layers can be swept together, perturbed in a controlled way, compared across finite trajectory lengths, and summarized with a small number of interpretable structural observables.
In practice that means the crate can answer questions like:
- how echo growth changes across lambda,
- how invariant word-length entropy tracks that echo growth,
- where the resonance transport transition occurs,
- how persistent-topology summaries respond across the same sweep,
- whether the AET-IWLT structural law survives deterministic rule perturbations,
- and whether those claims stabilize as `steps_per_run` increases.
## Mathematical Model
The ADD paper defines a common deterministic template for all four layers. The abstract state evolves by
```text
S_{k+1} = Φ(S_k),
```
with a discrete invariant
```text
I(S_{k+1}) = I(S_k),
```
and a monotone structural functional
```text
L(S_{k+1}) >= L(S_k).
```
`dsfb-add` is the numerical sweep implementation of that idea. It does not attempt to symbolically prove the paper's theorems inside Rust; instead it instantiates deterministic toy models whose exported diagnostics are direct empirical proxies for the functionals defined in the paper.
### Algebraic Echo Theory (AET)
In the paper, AET works on words in a free monoid `G*` with a terminating, confluent rewriting system `R`. The echo of a word is its normal form,
```text
Echo(w) = NF(w),
```
and the echo length is
```text
L_AET(w) = len(Echo(w)).
```
The paper's AET evolution is left-multiplicative:
```text
w_{k+1} = NF(g* w_k),
```
with increment sequence
```text
Delta_k = L_k+1 - L_k,
L_k = L_AET(w_k),
```
and asymptotic survival rate
```text
sigma = lim inf_{n -> inf} (1 / n) sum_{k=0}^{n-1} Delta_k.
```
In `dsfb-add`, the `aet` module implements exactly this style of deterministic word evolution on a small alphabet with terminating, confluent local rules. The exported sweep statistics are empirical summaries of the paper's `L_AET` dynamics:
- `echo_slope(lambda)` is the finite-run slope estimate
```text
echo_slope ~= (L_final - L_initial) / steps_per_run
```
- `avg_increment(lambda)` is the finite-run average of the increments
```text
avg_increment = (1 / N) sum Delta_k.
```
So the AET CSVs are a sampled lambda-family of the paper's echo-length growth law.
### Topological Charge Propagation (TCP)
In the paper, TCP starts from a deterministic trajectory, builds a filtration of simplicial complexes,
```text
K_{alpha_1} subseteq K_{alpha_2} whenever alpha_1 <= alpha_2,
```
and defines the topological charge vector
```text
Q(alpha) = (beta_0(K_alpha), beta_1(K_alpha), beta_2(K_alpha), ...).
```
The paper also uses the Euler-characteristic identity
```text
chi(K_alpha) = sum_{k >= 0} (-1)^k beta_k(K_alpha),
```
and a topological-disorder functional of the form
```text
L_TCP(t) = sum_k w_k * #{persistent classes in dimension k with lifetime >= delta}.
```
`dsfb-add` keeps persistent homology itself in the notebook rather than in Rust. The Rust crate exports deterministic point clouds for each lambda and each deterministic run window, and the notebook computes the paper-facing PH summaries:
- `betti1_mean(lambda)`
- `betti1_std(lambda)`
- `total_persistence_mean(lambda)`
- `total_persistence_std(lambda)`
The smooth TCP observable used in the figures is total persistence, which is the notebook's empirical surrogate for the paper's `L_TCP`.
### Resonance Lattice Theory (RLT)
In the paper, RLT evolves on a locally finite resonance graph `G = (V, E)` with deterministic dynamics
```text
v_{k+1} = Psi(v_k).
```
From an initial configuration `v_0`, the reachable component is
```text
C(v_0) = { v in V | v = Psi^(k)(v_0) for some k >= 0 },
```
and the resonance spread is
```text
L_RLT(v_0) = |C(v_0)|.
```
The paper further defines escape rate
```text
lambda(v_0) = lim inf_{n -> inf} (1 / n) d_G(v_0, v_n),
```
and resonance expansion ratio
```text
rho(v_0, n) = |V_n| / (n + 1),
rho(v_0) = lim inf_{n -> inf} rho(v_0, n),
```
where `V_n = {v_0, ..., v_n}` is the visited set up to time `n`.
The `rlt` module exports finite-run deterministic proxies for these exact paper objects:
- `escape_rate(lambda)` is the sampled version of `lambda(v_0)`
- `expansion_ratio(lambda)` is the sampled version of `rho(v_0, n)`
- `rlt_examples/...csv` stores representative bounded and expanding trajectories
The crate also exports the paper-facing phase-boundary summary:
```text
lambda_star = first lambda with expansion_ratio >= 0.5
lambda_0_1 = first lambda with expansion_ratio >= 0.1
lambda_0_9 = first lambda with expansion_ratio >= 0.9
transition_width = lambda_0_9 - lambda_0_1
```
These are the deterministic transport-transition metrics used by the notebook's RLT scaling plots and hero figure annotation.
### Invariant Word-Length Thermodynamics (IWLT)
In the paper, IWLT defines the equivalence class of a history word under a rewriting system as
```text
[w] = { u in E* | u is reachable from w by finitely many rewrites },
```
and the word-length entropy as the minimal representative length
```text
S_IWLT(w) = min_{u in [w]} len(u).
```
The evolution is append-only,
```text
w_{k+1} = w_k e_{i_{k+1}},
```
and the paper proves existence of the entropy-density limit
```text
s_inf = lim_{k -> inf} S_IWLT(w_k) / k.
```
Under the paper's local-irreversibility assumptions, IWLT obeys the deterministic entropy-density law
```text
S_IWLT(w_k) >= (m - BC) k,
s_inf >= m - BC > 0.
```
The `iwlt` module implements a deterministic append-and-reduce history system in exactly this spirit. Its exported diagnostics are finite-run empirical surrogates of the paper quantities:
- `entropy_density(lambda)` is the sampled estimate
```text
entropy_density ~= S_IWLT(w_final) / steps_per_run
```
- `avg_increment(lambda)` is the mean per-step increase in the minimal representative length.
### Cross-Layer Structural Law
The main empirical claim tested by this crate is that the AET and IWLT functionals lock together numerically across the same lambda sweep. Concretely, for every run length `N`, the notebook merges
- `echo_slope(lambda)` from AET, and
- `entropy_density(lambda)` from IWLT,
then fits the linear law
```text
entropy_density ~= a * echo_slope + b.
```
From that fit it computes:
- Pearson correlation,
- Spearman rank correlation,
- regression slope `a`,
- intercept `b`,
- `R^2`,
- mean-squared residual,
- and the dimensionless ratio
```text
entropy_density / echo_slope.
```
This is the structural-law pipeline behind `aet_iwlt_law_summary.csv`, the finite-size scaling summaries, the residual diagnostics, the universality comparison, and the bottom panel of `fig_hero_add_stack.png`.
### What The Crate Actually Computes
Putting the paper mathematics and the Rust implementation together:
- the Rust crate generates deterministic trajectories, words, and graph walks parameterized by lambda and optional multiple `steps_per_run` values,
- the CSVs store finite-run samples of `L_AET`, `S_IWLT`, TCP persistence summaries, and RLT spread/escape observables,
- perturbed sweeps test whether those laws are stable under small deterministic rule changes,
- and the notebook reconstructs the paper-facing diagnostics from those exported deterministic samples.
So the crate is not merely a plotting harness. It is the executable empirical realization of the paper's algebraic-topological stack: the place where the formal quantities from AET, TCP, RLT, and IWLT are turned into concrete sweep data, regression summaries, phase-boundary estimates, and final figures.
## Architecture
The crate is split into four simulation modules plus shared configuration, output, and orchestration code.
- `aet`:
deterministic word evolution on a finite alphabet with terminating, confluent local rewrite rules. It tracks irreducible echo length growth and average increment statistics.
- `tcp`:
deterministic 2D trajectory generation indexed by lambda. Rust writes per-lambda point clouds and coarse topological proxies; the Colab notebook can then compute richer persistent-homology diagnostics from those point clouds.
- `rlt`:
deterministic walks on a synthetic resonance lattice. It measures graph escape rate and expansion ratio from the visited component.
- `iwlt`:
append-only symbolic evolution with local length-non-increasing rewrites. It tracks minimal representative length, entropy density, and average increment.
- `config`:
defines `SimulationConfig`, including the lambda sweep bounds, step count, seed, and per-subtheory toggles.
- `output`:
creates `output-dsfb-add/<timestamp>/` using `chrono::Utc::now()` and writes sweep, phase-boundary, and robustness CSV files.
- `sweep`:
orchestrates baseline and perturbed sweeps, optional multi-`N` runs, phase-boundary extraction, and robustness summaries.
- `analysis/rlt_phase`:
extracts `lambda_star`, transition brackets, and transition width from the RLT expansion curve.
`SimulationConfig::lambda_grid()` produces evenly spaced lambda values on `[lambda_min, lambda_max]`. With the default configuration the sweep is deterministic and reproducible across runs because each sub-theory derives all pseudo-random choices from `random_seed` and the lambda index.
If `multi_steps_per_run` is populated, the crate repeats the full sweep for every requested trajectory length. This is what supports the paper's finite-size scaling story: the exact same lambda grid and deterministic rules are re-run at multiple `N`, and the notebook then measures how regression slope, `R^2`, residual variance, and phase-boundary location stabilize.
The crate also depends on the workspace `dsfb` crate. That dependency is used to build a small deterministic drive signal shared across the ADD toy models, so the sweep remains aligned with the DSFB repository's observer-first philosophy without modifying any existing DSFB source code.
## Code-Level Walkthrough
The crate is intentionally split so the Rust side handles deterministic data generation and the notebook side handles heavier plotting and PH post-processing.
### Library Entry Points
The main library entry point is:
```rust
pub fn run_all_sweeps(config: &SimulationConfig) -> Result<(), AddError>;
```
This is the simplest API when embedding `dsfb-add` from another Rust program: pass a `SimulationConfig`, let the crate create a fresh timestamped output directory, and write the full sweep there.
The lower-level entry point used by the binary is:
```rust
pub fn run_sweeps_into_dir(
config: &SimulationConfig,
output_dir: &Path,
) -> Result<SweepResult, AddError>;
```
This is the better choice when you want explicit control over where outputs are written or when integrating ADD sweeps into a larger orchestration layer.
### CLI Binary
The binary target is:
```text
dsfb_add_sweep
```
It accepts:
- `--config path/to/config.json`
- `--steps-per-run-list 512,5000,10000,20000`
If a `config.json` exists in the current working directory, it is loaded automatically. Otherwise the binary uses `SimulationConfig::default()`.
### SimulationConfig Parameters
`SimulationConfig` is the central contract for the crate. Its fields are:
- `num_lambda`:
number of lambda samples in the sweep grid
- `lambda_min`, `lambda_max`:
inclusive sweep bounds used by `lambda_grid()`
- `steps_per_run`:
fallback single run length when no multi-`N` sweep is requested
- `multi_steps_per_run`:
optional list of run lengths for finite-size scaling; if non-empty this takes precedence over `steps_per_run`
- `random_seed`:
deterministic seed for all pseudo-random branch choices
- `enable_aet`, `enable_tcp`, `enable_rlt`, `enable_iwlt`:
per-layer switches that allow focused runs
Default values are chosen to make the crate useful out of the box:
- `num_lambda = 360`
- `lambda_min = 0.0`
- `lambda_max = 1.0`
- `steps_per_run = 512`
- `multi_steps_per_run = [512, 5000, 10000, 20000]`
That means the default binary run is already a finite-size scaling experiment across four trajectory lengths. For a one-off local production run, you can override this with:
```bash
cargo run --release -p dsfb-add --bin dsfb_add_sweep -- \
--steps-per-run-list 512,5000,10000,20000,50000,100000
```
### What Each Rust Module Does
- `src/aet.rs`
implements the AET word-evolution sweep and its perturbed variant
- `src/iwlt.rs`
implements the IWLT append-and-reduce entropy sweep and its perturbed variant
- `src/rlt.rs`
implements deterministic resonance walks, phase-transition proxies, and example trajectories
- `src/tcp.rs`
implements deterministic 2D trajectory generation and exports multi-run point clouds for notebook-side PH
- `src/analysis/rlt_phase.rs`
extracts `lambda_star`, transition-width brackets, and related phase-boundary quantities
- `src/analysis/structural_law.rs`
fits the AET-IWLT structural law and computes the confidence interval used in downstream summaries
- `src/output.rs`
owns CSV schema definitions and file-writing helpers
- `src/sweep.rs`
is the orchestration layer: it loops over enabled `steps_per_run` values, runs baseline and perturbed sweeps, aggregates summaries, and writes the full output set
- `src/bin/dsfb_add_sweep.rs`
is the CLI wrapper around `SimulationConfig` loading and sweep execution
### Progress Reporting
The sweep binary emits percentage-based progress updates while it runs. This is especially useful for large local finite-size runs where `steps_per_run` may extend up to `100000` or higher.
The progress tracker reports:
- the current subsystem,
- whether the run is baseline or perturbed,
- the current `steps_per_run`,
- and the overall percentage through the entire ADD job.
## Running The Sweep
From the repo root:
```bash
cargo run -p dsfb-add --bin dsfb_add_sweep
```
Optional config override:
```bash
cargo run -p dsfb-add --bin dsfb_add_sweep -- --config crates/dsfb-add/config.json
```
Finite-size scaling run:
```bash
cargo run -p dsfb-add --bin dsfb_add_sweep -- --steps-per-run-list 512,5000,10000,20000,50000,100000
```
If `config.json` exists in the current working directory, the binary loads it automatically. Otherwise it uses `SimulationConfig::default()`.
By default the crate now runs a finite-size scaling sweep across:
- `512`
- `5000`
- `10000`
- `20000`
and writes per-`N` files with `_N<steps>` suffixes, while also keeping unsuffixed canonical CSVs for the reference run. If `--steps-per-run-list` is provided, those values override the configured sweep list for that run.
Each run creates:
```text
output-dsfb-add/<YYYY-MM-DDTHH-MM-SSZ>/
```
inside the workspace root and writes the requested CSV outputs there.
## Why The Crate Is Split Between Rust And Colab
The Rust crate is responsible for deterministic generation, reproducibility, and explicit CSV schemas. The notebooks are responsible for figure production, persistent-homology post-processing, and exploratory views that would be awkward or heavyweight to maintain directly in Rust.
That split is intentional:
- Rust owns deterministic execution and file layout.
- CSVs provide stable interchange points for the paper workflow.
- Colab owns presentation, diagnostics, and final publication figures.
This design keeps the core ADD experiment reproducible from the command line while still making the paper's figure pipeline easy to rerun in the cloud.
## Using The Colab Notebook
Workflow:
1. Run the Rust sweep locally so the CSV files, point clouds, and trajectory examples are generated.
2. Zip, upload, or sync `output-dsfb-add/<timestamp>/` into your Colab environment, or let the notebook generate a fresh run in Colab.
3. Open `crates/dsfb-add/dsfb_add_sweep.ipynb` using the Colab badge in the main repo README.
4. Set `OUTPUT_DIR` only if you intentionally want an existing run directory; otherwise the notebook can bootstrap a fresh one.
5. Run the notebook cells to regenerate all PNG figures and derived summary CSVs in the same directory as the sweep outputs.
The notebook is structured so Rust remains the authoritative simulation layer and Colab remains the analysis and figure-generation layer. The Rust side produces deterministic sweeps and structural summaries; the notebook performs the paper-facing regression, finite-size scaling, residual diagnostics, universality comparison, and hero-figure assembly.
For Colab specifically, the notebook uses a minimal reproducible default sweep profile of `512` when it bootstraps a fresh Rust run. A dropdown near the top of the notebook selects the active `steps_per_run` value for both bootstrap and figure display, and it defaults to `512` so free CPU sessions remain practical. If you want the full production profile in Colab, set `RUST_MULTI_STEPS = [512, 5000, 10000, 20000, 50000, 100000]` in the notebook before running the bootstrap cell. If you run the large multi-`N` sweep locally and upload the results, that same dropdown lets you switch the displayed analysis to any available `N` without editing the plotting code.
For the completed local production run currently present in this workspace, use the dedicated replay notebook:
- `crates/dsfb-add/dsfb_add_results_replay.ipynb`
That notebook is preconfigured for:
- timestamp: `2026-03-01T18-19-22Z`
- expected archive: `output-dsfb-add/dsfb-add-2026-03-01T18-19-22Z.zip`
Its default behavior is analysis-only:
- `RUN_RUST_SWEEP_IN_COLAB = False`
- `OUTPUT_DIR = /content/output-dsfb-add/2026-03-01T18-19-22Z`
Upload that zip in Colab, run the helper cell `upload_and_unpack_replay_zip()`, and then run the remainder of the notebook. The same `steps_per_run` dropdown can then switch between `512`, `5000`, `10000`, `20000`, `50000`, and `100000` using the uploaded local results.
## Outputs
Expected runtime files:
- `aet_sweep.csv`
- `aet_sweep_perturbed.csv`
- `aet_sweep_N<steps>.csv`
- `aet_sweep_perturbed_N<steps>.csv`
- `tcp_sweep.csv`
- `tcp_sweep_N<steps>.csv`
- `rlt_sweep.csv`
- `rlt_sweep_perturbed.csv`
- `rlt_sweep_N<steps>.csv`
- `rlt_sweep_perturbed_N<steps>.csv`
- `iwlt_sweep.csv`
- `iwlt_sweep_perturbed.csv`
- `iwlt_sweep_N<steps>.csv`
- `iwlt_sweep_perturbed_N<steps>.csv`
- `tcp_points/lambda_<idx>_run_<r>.csv`
- `tcp_points_N<steps>/lambda_<idx>_run_<r>.csv`
- `rlt_examples/trajectory_bounded_lambda_<idx>.csv`
- `rlt_examples/trajectory_expanding_lambda_<idx>.csv`
- `rlt_examples_N<steps>/trajectory_bounded_lambda_<idx>.csv`
- `rlt_examples_N<steps>/trajectory_expanding_lambda_<idx>.csv`
- `rlt_phase_boundary.csv`
- `cross_layer_thresholds.csv`
- `tcp_phase_alignment.csv`
- `robustness_metrics.csv`
- `aet_iwlt_law_summary.csv`
- `aet_iwlt_scaling_summary.csv`
- `aet_iwlt_diagnostics_summary.csv`
- `tcp_ph_summary.csv` (written by the Colab notebook after persistent-homology post-processing)
Expected notebook figure outputs:
- `fig_aet_echo_slope_vs_lambda.png`
- `fig_aet_robustness.png`
- `fig_iwlt_entropy_density_vs_lambda.png`
- `fig_iwlt_robustness.png`
- `fig_rlt_escape_rate_vs_lambda.png`
- `fig_rlt_expansion_ratio_vs_lambda.png`
- `fig_rlt_expansion_ratio_vs_lambda_zoom.png`
- `fig_rlt_robustness.png`
- `fig_rlt_trajectory_bounded.png`
- `fig_rlt_trajectory_expanding.png`
- `fig_tcp_betti1_mean_vs_lambda.png`
- `fig_tcp_total_persistence_vs_lambda.png`
- `fig_aet_iwlt_structural_law.png`
- `fig_aet_iwlt_universality.png`
- `fig_aet_iwlt_scaling_slope_vs_N.png`
- `fig_aet_iwlt_scaling_r2_vs_N.png`
- `fig_aet_iwlt_scaling_resid_vs_N.png`
- `fig_aet_iwlt_residuals_vs_echo.png`
- `fig_aet_iwlt_residual_hist.png`
- `fig_aet_iwlt_ratio_vs_lambda.png`
- `fig_aet_iwlt_ratio_hist.png`
- `fig_aet_iwlt_loglog.png`
- `fig_cross_layer_summary_vs_lambda.png`
- `fig_rlt_phase_lambda_star_vs_N.png`
- `fig_rlt_phase_width_vs_N.png`
- `fig_rlt_phase_sharpness_vs_N.png`
- `fig_rlt_vs_aet_threshold.png`
- `fig_rlt_vs_iwlt_threshold.png`
- `fig_tcp_phase_alignment_vs_N.png`
- `fig_hero_add_stack.png`
`tcp_sweep.csv` includes coarse Rust-side topological proxies (`betti0`, `betti1`, `l_tcp`) plus radius statistics. The notebook augments those proxies with `ripser`-based H1 summary statistics computed from the exported per-lambda run clouds, with total persistence treated as the main smooth TCP observable.
The perturbed sweep CSVs are small deterministic robustness experiments: they nudge the update laws without changing the overall structural regime picture. The Rust sweep now writes the core numerical summaries directly:
- `aet_iwlt_law_summary.csv` contains the linear AET-IWLT fit per `N` and per mode, including `R^2`, residual variance, and slope confidence interval.
- `aet_iwlt_scaling_summary.csv` isolates the baseline finite-size scaling branch across `N`.
- `aet_iwlt_diagnostics_summary.csv` stores residual and ratio statistics per `N`.
- `rlt_phase_boundary.csv` stores `lambda_star`, the 0.1-0.9 transition width, and a finite-difference sharpness estimate.
- `cross_layer_thresholds.csv` records the AET and IWLT structural values at the RLT transport transition.
- `tcp_phase_alignment.csv` records how the TCP peak observables align with the RLT phase transition.
- `robustness_metrics.csv` compresses baseline-vs-perturbed deltas for the structural law and the RLT transition.
The notebook then turns those summaries into paper-ready figures, adds PH-derived TCP summaries, overlays diagnostics, and rebuilds the fully annotated hero figure.
Taken together, the outputs are meant to support the numerical section of the ADD paper:
- baseline and perturbed sweeps show the main structural response,
- multi-`N` runs show finite-size convergence,
- phase-boundary summaries quantify the RLT transport transition,
- structural-law summaries quantify the AET-IWLT coupling,
- and the hero figure condenses the stack into a single paper-facing panel.
## Publishability Notes
The crate is intended to be publishable on crates.io as a normal Rust package:
- it exposes a reusable library API,
- it ships a documented CLI binary,
- it includes the notebooks that consume the exported CSVs,
- and it depends on the published `dsfb` crate rather than on unpublished workspace-only internals.
The crate is therefore usable in two ways:
1. as a command-line experiment runner for ADD paper reproduction, and
2. as a library dependency for projects that want to orchestrate ADD sweeps programmatically.
## Relationship To The DSFB / ADD Papers
The DSFB crate provides the deterministic observer philosophy already present in this monorepo. The ADD paper extends that philosophy into structural dynamics: irreducible word growth, deterministic topological complexity, resonance spread, and entropy production without stochastic assumptions.
`dsfb-add` turns that argument into a repeatable experiment. Its outputs are the empirical curves used to study echo slopes, entropy densities, resonance spreads, and topology-vs-lambda structure for the ADD stack.