Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Coagulation project documentation

Continuation of the M.Sc. thesis Investigating the Feasibility of utilizing Stochastic Sampling of Colliding Circumstellar Dust Particle Pairs for Computational Cost Optimization of the Smoluchowski Coagulation Equation’s Numerical Integration (V. Mader, Heidelberg University, 2024; supervisor C. P. Dullemond): a Rust re-implementation of the local dust coagulation-fragmentation model, a suite of time integrators up to an own Radau IIA, a systematic study of Monte Carlo kernel sampling, an interactive web visualization, and a joint paper.

Where to start

  1. Vision and research goals: goals, research questions, hypotheses, paper pitch.
  2. Thesis review: what the thesis established and the findings (F1 to F10) that change the method.
  3. Roadmap: milestones M0 to M11 with tasks and acceptance criteria.

Contents

FolderDocumentPurpose
planning/00-vision.mdWhy, what, research questions, hypotheses
01-thesis-review.mdCritical review of thesis and code, with file references
02-roadmap.mdMilestones, tasks, acceptance criteria, dependency graph, timeline
03-verification.mdOracles, invariants, error metrics, test budget
04-experiments.mdEstimator theory and experiments E1 to E16
05-architecture.mdCrates, core abstractions, data flow, config sketch
06-risks-and-open-questions.mdOpen questions, decision log of the grill session, risks (X1 to X11)
07-visualization.mdRequirements for the web tool (design comes first)
08-paper-and-outreach.mdCollaboration, paper outline, reproducibility
adr/README.mdArchitecture Decision Records 0001 to 0016
reference/literature.mdAll sources with DOIs, local PDF links, role, reading status
glossary.mdTerms across astrophysics, numerics, sampling, software
notation.mdSymbol table mapping thesis, coag_py, and Rust names

Conventions

  • IDs: goals Gn, research questions RQn, hypotheses Hn, findings Fn, milestones Mn and tasks Mn.m, experiments En, questions Qn, risks Xn. IDs are stable; retired items are struck through, not renumbered.
  • Status lines: every planning document starts with its status and version; bump the version when content changes materially.
  • Decisions: a choice with alternatives becomes an ADR (Proposed until Vincent accepts it); smaller scope decisions go into the decision log.
  • Diagrams: Mermaid in Markdown, so they render on GitHub and in most editors and stay diffable.
  • Language: English; symbols as in notation.md; SI units.
  • Sources: ../related/ (project root) holds symlinks to the thesis (msc-thesis/), its code (msc-thesis.py/), the thesis working folder with papers (Write Master's Thesis./), and the paper inbox (Papers/). They are read-only: nothing there is edited, and what we need is rebuilt in this repo, mostly from scratch in Rust.

Vision and research goals

Status: v0.2 (2026-11-10), scope settled in the grill session (decision log in 06). Iterate in place; record decisions as ADRs.

1. One-paragraph summary

Solving the Smoluchowski coagulation-fragmentation equation on a grid costs O(N³) per right-hand side once fragmentation is included, and O(N^{3p}) when dust particles carry p properties (mass, filling factor, composition). That cost is why almost all disk-scale dust evolution codes describe grains by mass alone. Dullemond proposed estimating the double sum by Monte Carlo sampling of colliding pairs, while keeping the grid, detailed balance, and implicit time stepping. The M.Sc. thesis (Mader 2024) tested a variant of this and found a qualitatively correct evolution but poor accuracy. Our review shows that the thesis estimator was biased and its proposal distribution was not the one documented (01-thesis-review.md). This project rebuilds the full pipeline in Rust as a clean, tested, and fast research code. It then answers the open question properly: when, and by how much, does unbiased and variance-reduced kernel sampling with implicit time integration beat full summation, for one and for two particle properties? The result is a methods paper with Dullemond, plus an interactive web tool that makes every component of the model visible and switchable.

2. Goals

IDGoalMeasure of done
G1Faithful, modular re-implementation of the thesis model (disk, kinematics, outcomes, kernels, integrators) in Rust.Parity tests against thesis Python and coag_py pass to stated tolerances; thesis figures reproduced.
G2Integrator suite from explicit Euler to an own Radau IIA, behind one trait.Convergence orders verified on analytic kernels; Radau matches scipy to 10⁻⁸ relative at equal tolerances.
G3Correct and efficient MC sampling: unbiased estimators, optimal proposals, variance reduction, implicit sampled form, detailed balance per sample.Bias consistent with zero at 3σ over 10³ replicas; mass error at round-off; documented efficiency gains.
G4Answer the scaling question for p = 1 and p = 2 (mass, filling factor).Work-precision diagrams and a break-even N where sampling wins (or a proof that it does not).
G5Interactive web visualization of every model layer and integrator, with toggles for each physical component.Public at https://coag.mader.xyz (mk11), growing with every milestone from M2 on; design approved first.
G6Paper with C. P. Dullemond (and possibly group members), with a reproducibility package.Draft accepted by co-authors; submitted to A&A or a computational journal.

Non-goals for the paper: global radial transport (1D disk), gas evolution, radiative transfer, GPU kernels. The architecture stays ready for a non-local model (ADR 0015): radial transport and a global disk are the most likely follow-up, so nothing in the local code may assume a single cell.

3. Research questions

IDQuestionExperiments
RQ1How much of the thesis accuracy error is bias and how much is variance?E5, E6
RQ2Which proposal p_ij minimizes variance per unit cost, and how close do cheap approximations (ρ_i ρ_j, ΔM^net N_i N_j, factorized F_i F_j) come to it?E7
RQ3Does sampling the same pairs for all k (one recipe per draw, in detailed balance) beat independent samples per k? (Dullemond’s open “COMMENT ON WHICH WORKS BEST”.)E8
RQ4How should samples interact with the integrator: frozen per step, averaged over several draws, resampled per stage, or used in the implicit form of Eq. 1.71? What does each do to stability, step-size control, and the noise floor at steady state?E9, E10, E11
RQ5Does MC noise destabilize the coagulation-fragmentation balance at the small-grain end, and can a hybrid estimator (exact sum over dominant pairs, sampled tail) or a balance-preserving sampling remove the problem?E11, E12
RQ6What does the implicit linear solve cost with sampled kernels, and can sparse or approximate Jacobians (W-methods, simplified Newton, JFNK) keep the whole scheme sub-cubic?E13
RQ7Where is the break-even point between full summation and sampling, as a function of N_m, N_φ, required accuracy, and physics (with or without fragmentation and bouncing)?E14, E15
RQ8How does the method compare with representative particle MC (RPMC), low-rank / tensor-train methods, and DG schemes at equal accuracy?E16 (scope to be set with Dullemond)

4. Working hypotheses

IDHypothesisFalsified if
H1The slower evolution in the thesis is caused by the missing 1/π weights; an unbiased estimator removes the lag.Unbiased runs still lag the full-kernel solution by more than their noise.
H2p_ij ∝ ‖K_·ij‖ N_i N_j (mass-weighted) is near-optimal and clearly better than ρ_i ρ_j.Measured variance ratio < 1.5 in favor of the optimal proposal.
H3A hybrid estimator (exact over the pairs holding a fraction q of the proposal mass, sampled rest) gives most of the variance reduction at a small cost overhead.Efficiency η does not improve over plain IS for any q.
H4With implicit stepping, MC noise acts as a bounded forcing; the steady-state noise floor scales as S^{-1/2} and is independent of Δt once Δt is large.Noise floor grows with Δt or the implicit solve diverges.
H5For p = 1 sampling wins only at large N_m or low accuracy targets; for p = 2 it wins at practical resolutions (N_m ≈ 100, N_φ ≈ 20 to 50) for 1 % mass-weighted accuracy, provided the Jacobian is treated sparsely or approximately.Break-even N lies beyond what any study would use.

5. What is new compared with prior work

Prior workWhat it doesWhat this project adds
Thesis (Mader 2024)Biased pair sampling, scipy Radau, mass onlyUnbiased estimators, optimal proposals, variance reduction, cost model including the linear solve, two properties
Dullemond notes and coag_py prototype (2021)Formulates IS (Eq. 1.70) and implicit form (Eq. 1.71); explicit/implicit demos on constant/linear kernelsSystematic study on physical kernels with fragmentation and bouncing, answers the listed TODOs, scalable implementation
DustPy, Birnstiel 2010, Brauer 2008Full summation, implicit, mass onlySub-cubic RHS and Jacobian, multi-property path
Okuzumi 2009, Schöll et al. 2026Second property as averaged or dependent variable φ(m, t)Keeps φ as an independent dimension at affordable cost
Ormel 2007, Zsom and Dullemond 2008, Beutel and Dullemond 2023, Gurrutxaga et al. 2026Particle MC, explicit in timeGrid-based MC that keeps implicit time stepping and deterministic mass conservation
Matveev et al. 2015, 2016Low-rank / tensor-train deterministic speed-upDifferent trade-off (no rank assumption); compare honestly, possibly combine
Lombart and Laibe 2021, Lombart et al. 2024High-order DG, few binsOrthogonal (fewer bins vs cheaper sum); discuss combination

6. Paper pitch (to refine with Dullemond)

Working title options:

  1. Unbiased Monte Carlo sampling of the coagulation kernel for implicit dust evolution with multiple particle properties
  2. Stochastic kernel summation for the Smoluchowski equation: bias, variance, and implicit time integration

Draft abstract (to be rewritten once results exist):

Grid-based solutions of the coagulation-fragmentation equation scale as N³ per time step and as N^{3p} when grains carry p properties. We study a Monte Carlo estimator of the collision sum that samples colliding pairs, preserves mass conservation for every sample, and remains compatible with implicit time integration. We show that sampling without reweighting, as used in earlier tests, underestimates all collision rates, and we derive the variance-optimal proposal distribution. Combined with a hybrid exact/sampled split and sparse Jacobians in a Radau IIA integrator, the method reproduces full-kernel steady states of growth and fragmentation to X % at Y times lower cost for p = 1 and Z times lower cost for a mass and filling-factor model.

Candidate venues: A&A (where the group publishes, e.g. Beutel and Dullemond 2023), or J. Comput. Phys. if the numerical analysis dominates. Decide with Dullemond.

Planned key figures: (1) model overview diagram; (2) bias vs variance decomposition of the thesis setup; (3) proposal comparison; (4) noise floor vs S and Δt; (5) work-precision diagrams p = 1 and p = 2; (6) break-even surface over (N_m, N_φ, accuracy); (7) steady-state distributions with and without bouncing.

7. Working principles

  • Reproduce before improving. Every new method is compared against the faithful baseline, and the baseline against the thesis.
  • One switch per physical assumption. Every component (velocity term, outcome model, boundary policy, estimator, integrator) is a config toggle, so its effect can be isolated and visualized.
  • Correctness is tested, not assumed. Analytic solutions, parity with coag_py/scipy, conservation laws, and statistical tests of estimators run in CI.
  • Decisions are written down. Architecture and method choices go into ADRs with alternatives and consequences.
  • Results are reproducible. Every output carries its config hash, git revision, and RNG seed.

8. Settled scope (2026-11-10)

TopicDecision
Spatial scopeLocal model at one radius for the paper; ready for non-local models (ADR 0015).
Reference physicsCorrected thesis model (findings F6 to F8 fixed) as primary setup; a DustPy-like setup as robustness check; the exact thesis model only in the reproduction (R).
BouncingDominik and Dullemond (2024) local recipe, verbatim; headline experiments run with and without bouncing.
Second propertyFilling factor φ on a log grid, minimal porosity physics behind a swappable trait, richer physics later.
Precisionf64 default, double-double and nightly f128 switchable (ADR 0016).
MetricsWork-normalized efficiency η on snapshots; work-precision diagrams at e_acc = 1 % (headline), 10 %, and 0.1 % where affordable, transient and steady state.
ComparisonsLiterature numbers and reproduced test problems; RPMC via Dullemond if possible; no own competitor implementations unless a referee asks.
Go/no-go for p = 2Criteria fixed in the roadmap (M8) before any results.
WebPublic at coag.mader.xyz from the first view, English only, views ship with their milestones.
ContactShort note to Dullemond once kernel views are live (after M4b); full pitch after M7.

Critical review of the M.Sc. thesis and its code

Purpose: record what the thesis established, what it left open, and what in its method or code must change before the results can go into a paper. Every finding cites the source so it can be checked. Paths are relative to the project root (related/ holds the symlinks).

Sources reviewed in full: thesis TeX (related/msc-thesis/src/chapters/*.tex), thesis Python (related/msc-thesis.py/src), Dullemond’s coag_py (related/msc-thesis.py/lib/coag_py), Dullemond’s notes Dust evolution with binning methods (19 pp.).

1. What the thesis did

flowchart LR
    A[Disk model<br/>irradiated power law,<br/>r0 = 10 au midplane] --> B[Dust kinematics<br/>BR, RD, AZ, TU]
    B --> C[Collision rates<br/>sigma dv]
    C --> D[Outcome probabilities<br/>MB-smoothed v_frag = 1 m/s,<br/>no bouncing]
    D --> E[Kernel K_kij<br/>Kovetz-Olund + cancellation,<br/>MRN fragmentation]
    E --> F[Integration<br/>scipy Radau per macro step]
    E --> G[Sampled kernel<br/>pairs without replacement]
    G --> F
    F --> H[Diagnostics<br/>mass error, accuracy error,<br/>RMS dN/dt, sparsity]
AreaContentKey parameters
Disk (ch. 2)M_* = M_⊙, L_* = L_⊙, M_disk = 0.01 M_*, ε_dg = 0.01, μ = 2.3, irradiated T_mid with φ_f = 0.05, Σ_g ∝ r^-0.8 normalized to M_gas on 10⁻² to 10² au, Gaussian vertical structure, isothermal, hydrostatic. Single location r₀ = 10 au, z = 0, static in time.N_r = 100
Dust kinematicsCompact spheres, ρ_s = 1600 kg m⁻³, Epstein St, Δv from Brownian, radial drift (Birnstiel 2010), azimuthal drift (Nakagawa 1986), turbulence (Ormel and Cuzzi 2007), combined in quadrature. Settling disabled.α = 10⁻³, E_d = 1
Collisionsσ = π(a_i+a_j)², C = σ Δv, P_frag = (1+x)e^{-x} with x = 1.5 (v_frag/Δv)², P_bounce = 0.v_frag = 1 m/s
Kernel (ch. 3)Gain/loss split, Kovetz and Olund, near-cancellation handling, fragmentation as pulverization or MRN power law up to m_i+m_j, kernel mass error ΔX^rel ≈ 10⁻¹². Pairs with m_i+m_j beyond the grid are dropped.m = 10⁻¹⁵ to 10⁻⁸ kg, N_m = 50 to 100
IntegrationExplicit Euler (linear grid demo), scipy Radau through coag_py.react_0d.solve_react_0d_equation, one call per macro step of a log time grid.t = 1 s to 3·10¹⁰ s, N_t = 200
Sampling (ch. 4)P_ij ∝ ΔM^net_ij N_i N_j m_i m_j (as written), lower triangle, threshold 10⁻⁹, sampling without replacement, new sample per macro step, sampled kernel rebuilt from the sampled pairs only.ρ_sample = 0.1 to 1
ResultsQualitative evolution reproduced, equilibrium reached after a few hundred years, mass error ≤ 10⁻¹², but slower evolution at low ρ_sample, accuracy error > 1, RMS dN/dt does not settle, peaks at the upper edge of the equilibrium distribution.
ConclusionTrade-off between cost and accuracy; likely worthwhile only for multi-property models (porosity), where noise grows faster with dimension than the number of relevant entries.

2. What is solid and carries over

  1. The kernel construction (Kovetz and Olund plus cancellation handling) matches coag_py.create_coag_kernel and conserves mass per collision to about 10⁻¹². It becomes the reference for M4 parity tests.
  2. The physical model is a reasonable, documented “standard model” of local dust evolution at one radius (close to Birnstiel 2010 and DustPy). Good enough as the testbed for a numerical-methods paper.
  3. The diagnostics (mass error, RMS dN/dt, sparsity, sampled-pair maps) are the right ones to keep; some need better definitions (F9).
  4. The main conclusion (sampling pays off when the particle state has more than one property) is plausible and is exactly the argument Dullemond’s notes build toward. It has not been tested yet. Testing it is the core of the paper.

3. Findings that change the method

Severity: A invalidates a quantitative result, B biases or weakens a result, C presentation or citation.

F1 (A): the sampling estimator is biased low; this explains the “slower evolution”

Thesis ch. 4, “Sampling of the Total Kernel”, Observation 3, attributes the slower approach to equilibrium at low ρ_sample to “effectively lower collision rates”. That is a direct consequence of the estimator, not a property of sampling in general:

  • Pairs are drawn without replacement (np.random.choice(..., replace=False), related/msc-thesis.py/src/models/kernel/sampled.py:94-95, config allow_duplicate_sampling = false).
  • The sampled kernel is the full kernel restricted to the drawn pairs, with no reweighting (sampled.py:69-70, Kernel(..., ijs=ijs)).
  • Hence E[F̂_k] = Σ_ij π_ij K_kij N_i N_j with inclusion probability π_ij ≤ 1. Every term is scaled down by its inclusion probability, so the rate of every process is underestimated. A Horvitz-Thompson weight 1/π_ij, or importance sampling with replacement and weight 1/(S p_ij), removes the bias.
  • The with-replacement branch (sampled.py:71-72) divides by P_ij but not by S, and it is applied to a kernel built from the set of unique pairs, so multiplicities are lost. It is not an unbiased estimator either, and the thesis runs did not use it.
  • Dullemond’s notes (Eq. 1.70) and his 2021 prototype (coag_py/mc_sampling/coag.py, sample_kernel_sum) already use the unbiased form K/(F_i F_j) with draws with replacement. The thesis diverged from it.

Consequence: the accuracy error in thesis Fig. 106 mixes bias (systematic lag) and variance (noise). Only the variance part is intrinsic to sampling. The headline negative result of the thesis is partly an artifact and should be re-measured with an unbiased estimator. This is the strongest single argument for a follow-up paper.

F2 (A): the documented sampling probability is not what the runs used

The thesis defines P_ij ∝ ΔM^net_ij N_i N_j m_i m_j with ΔM^net_ij = Σ_k m_k |K_kij|. In the code path used for all sampling figures (examples 104, 106, 107 import SolverV2):

  • SolverV2.__init__ sets self.W_ij = weights(self.mg, self.kernels[0]) (related/msc-thesis.py/src/models/solver/solver_v2.py:91).
  • weights() (solver_v2.py:223-226) calls np.sum([...]) without axis=0, which reduces to a scalar.
  • SampledKernel uses this scalar (sampled.py:39-44), so P_ij ∝ N_i N_j m_i m_j. The ΔM^net factor is a constant and drops out after normalization.

So the runs sampled by N_i N_j m_i m_j, i.e. by the product of bin masses ρ_i ρ_j, not by the documented relevance measure. The per-pair throughput (which vanishes for pairs that are cut at the grid edge, and is tiny where Δv is small) never entered. This may explain the “peaks at the upper edge” (Observation 4), since pairs near m_max carry most mass but partially have zero kernel.

F3 (B): the documented proposal is not the variance-optimal one

Even as documented, P_ij ∝ ΔM^net N_i N_j m_i m_j has an extra m_i m_j factor justified only heuristically. For the unbiased IS estimator of the vector F = Σ_ij K_·ij N_i N_j, the proposal that minimizes E‖F̂ − F‖ in a weighted norm ‖·‖w is p_ij ∝ ‖K·ij‖_w N_i N_j (standard result for vector-valued IS; derivation in 04-experiments.md, E7). With the mass-weighted L1 norm this is exactly p_ij ∝ ΔM^net_ij N_i N_j, i.e. the thesis’ relevance measure without m_i m_j. The m_i m_j factor double counts mass and oversamples big-big pairs.

F4 (B): one frozen sample per macro step, with long steps

Each of the 200 macro steps calls scipy Radau once over [t_n, t_{n+1}] with a kernel sampled once at t_n (solver_v2.py:126-155, N_subst = 1). On a log time grid the late macro steps span about 10⁸ s, so a single random draw drives the solution for a long interval and MC noise does not average out within that interval. Noise averaging needs either more, shorter steps, or several independent samples per step (averaged), or resampling per integrator step. This is a design variable to study (E9), not a fixed choice.

F5 (B): integrator tolerances and order

  • scipy Radau is the 3-stage Radau IIA method of order 5 (also stated in the react_0d.py docstring). The thesis abstract, ch. 3, and ch. 5 say “4th order”. Fix in any write-up.
  • solve_react_0d_equation passes no rtol/atol, so scipy defaults apply (rtol = 10⁻³, atol = 10⁻⁶). N_k is in m⁻³ and spans many decades (rough estimate: about 3·10² m⁻³ for monomers at 10 au, many orders less for the largest bins). An absolute tolerance of 10⁻⁶ m⁻³ means bins below that are not error-controlled. Needs a scaled state or per-component atol (ADR 0003). To verify in M6.
  • The professor’s react_0d.py is a wrapper around scipy Radau with an analytic Jacobian plus a Newton-iterated backward Euler. It is not a custom Radau implementation. Our own Radau IIA (ADR 0008) is new work.

F6 (B): molecular viscosity in the radial gas velocity

The thesis (ch. 2, “Gas Viscosity” and “Radial Gas Velocity”) and the code (related/msc-thesis.py/src/models/disk/disk.py:54,72, physics.viscosity(u_th, lambda_mfp)) use the molecular viscosity ν = ½ u_th λ_mfp in the Lynden-Bell and Pringle formula for u_g. Birnstiel et al. (2010) use the turbulent viscosity ν_t = α c_s H_p. ν_mol is smaller than ν_t by the Reynolds number (a rough estimate for this model gives Re ≈ 10⁵ at 10 au), so u_g is underestimated by about five orders of magnitude. Impact on Δv is small (the drag term of radial drift scales with u_g and differs between grains only through St), but it is physically wrong and must be fixed in M2 with a toggle for both choices so the effect can be shown.

F7 (B): negative densities are clipped and the mass is redistributed silently

fix_negative_densities (related/msc-thesis.py/src/functions/solver/__init__.py, called at solver_v2.py:157) zeroes negative bins and rescales all positive bins to restore total mass. This conserves mass by construction, so the reported mass error after such a fix does not measure the integrator’s conservation. Negative values are a symptom (step too large, noisy estimator, tolerance) and must be reported, not hidden (global rule: no fallbacks that hide bugs). New code: count and report negativity events, and fail in tests.

F8 (B): kernel boundary and fragmentation choices

  • Pairs with m_i + m_j beyond the grid are skipped entirely for coagulation (kernel.py, near_upper_bound, k_h >= N_m), including their loss term. Particles in the top bins become inert, which creates the “traffic jam” the thesis describes. Make the policy explicit and configurable (ADR 0005).
  • MRN fragments extend up to m_i + m_j. Dullemond’s notes argue for independent shattering of each body up to max(i, j), and add cratering for extreme mass ratios. Both are in scope for M4.
  • Bouncing is absent (P_bounce = 0). It is part of the target model (Dominik and Dullemond 2024) and changes the equilibrium substantially.

F9 (C): diagnostic definitions

  • Δ_stab is defined as a global relative mass error in ch. 3 but as a sum of per-bin relative errors in ch. 4. Use the global one.
  • Δ_acc divides by ρ^c_i(t), which is zero in many bins (the thesis footnote mentions gaps). Replace by a mass-weighted norm that is well defined (see 03-verification.md).
  • assert N.all() >= 0 (sampled.py:49) compares a boolean with 0 and is always true.

F10 (C): text and citation errors to fix before reuse

PlaceIssue
ch. 2, turbulence, Eqs. A to CPrefactor written as u_g (radial gas velocity); code correctly uses v_t = sqrt(α) c_s (relative_velocity/turbulence.py).
ch. 2, “M_disk = M_gas + M_disk”Should be M_disk = M_gas + M_dust.
ch. 2, Σ₀ normalizationSum index written 𝒩_m, should be 𝒩_r.
ch. 1, log gridm_i^c/m_i^c ratio printed with identical indices (should be m_{i+1}/m_i).
bib dullemond_dominik_2004Title given is the settling paper, but the content cited (Brownian Δv, small-grain depletion) and the local PDF are Dust coagulation in protoplanetary disks: a rapid depletion of small grains, A&A 434, 971 (2005).
ch. 2, v_frag“2003 paper by Dullemond and Dominik” cites dullemond_dominik_2023, which is the 2024 bouncing-barrier paper by Dominik and Dullemond.
bib brauer_dullemond_henning_2007Published 2008 (A&A 480, 859).
bib ormel_2006Published 2007 (A&A 461, 215).
Radau orderSee F5.

4. What the thesis left open (and Dullemond’s notes flag as TODO)

Open questionSourceWhere we address it
Unbiased estimator, sampling with replacement and normalizationthesis ch. 4 and 5 (“could be in a future study”); notes Eq. 1.70M7, E6
Same random pairs for all k or new ones per k (“COMMENT ON WHICH WORKS BEST”)notes 1.3.2M7, E8
Implicit integration of the sampled equation (Eq. 1.71)notes 1.3.3M7, E10
Detailed balance per sample, including fragmentation products, and sampling over k for fragmentationnotes 1.3.4M7, ADR 0006
Does MC noise upset the growth-fragmentation balance at the small-grain end? Can sampling be designed to balance opposing terms automatically? (“TODO”)notes 1.3.4M8, E11, E12
Better sampling density against upper-edge peaksthesis ch. 5M8, E7
Second particle property (porosity)thesis ch. 5; notes 1.2M9
Cost of the implicit linear solvenot discussed anywhereM8, E13 (see below)

5. A gap nobody has addressed yet: the linear solve

For p particle properties the flattened state has N = N_m N_φ ⋯ entries, the full kernel sum costs O(N³) with fragmentation, and so does a dense LU of the N × N Jacobian in any implicit method. Sampling reduces the RHS cost to O(S · L) (S draws, L recipe entries per draw) but not the linear algebra. An implicit sampled scheme only wins if the Jacobian is also treated cheaply:

  • Sparse Jacobian assembled from the same sampled recipes (O(S · L) non-zeros), solved by sparse LU or by Krylov (GMRES/BiCGStab) with a cheap preconditioner.
  • Approximate Jacobians, which W-methods (Rosenbrock-W) and simplified-Newton Radau tolerate by design.
  • Jacobian-free Newton-Krylov using sampled RHS evaluations.

This turns “does sampling pay off” from an RHS question into a whole-scheme question, and it is a natural, novel section of the paper (E13).

6. Summary for the plan

  1. Re-implement the thesis model faithfully (with F6 to F8 fixed behind toggles) and reproduce its figures as a regression baseline.
  2. Replace the biased estimator by unbiased IS / HT estimators, use the variance-optimal proposal, and re-measure the cost-accuracy trade-off with work-normalized efficiency.
  3. Add variance reduction, the implicit sampled form, and cheap Jacobian strategies.
  4. Move to two particle properties, where the payoff is expected, and compare against full summation, tensor-train or other fast deterministic methods where feasible, and the group’s RPMC.

Roadmap: milestones, tasks, acceptance criteria

Status: v0.2 (2026-11-10), updated with the grill decisions. Durations are rough estimates for part-time work and will be revised after M1.

Conventions: milestone Mn, task Mn.m. A milestone is done when all its acceptance criteria (AC) are checked by an automated test or a recorded experiment, just check is green, and the docs reflect the result. Experiments En are specified in 04-experiments.md, tests in 03-verification.md, crates in 05-architecture.md.

1. Overview

flowchart TD
    M0[M0 Plan and literature] --> M1[M1 Workspace, core types, CI]
    M1 --> M2[M2 Disk model]
    M2 --> M3[M3 Kinematics, rates, outcomes]
    M1 --> M4a[M4a Kernel framework + analytic kernels]
    M3 --> M4b[M4b Physical kernels:<br/>coag, frag, bouncing]
    M4a --> M4b
    M4a --> M5[M5 Explicit integrators<br/>+ analytic benchmarks]
    M5 --> M6[M6 Implicit integrators,<br/>own Radau IIA]
    M4b --> M6
    M6 --> R[R Thesis reproduction<br/>baseline]
    R --> M7[M7 Unbiased sampling,<br/>implicit sampled form]
    M7 --> M8[M8 Variance reduction,<br/>noise, Jacobians, cost]
    M8 --> M9[M9 Second property:<br/>filling factor]
    M0 --> V1[M10a Web app: design]
    V1 --> V2[M10b Views ship with<br/>each milestone M2 to M9]
    M2 --> V2
    V2 --> V3[M10c Web app: integration,<br/>sampling, comparisons]
    M8 --> V3
    M4b --> P0[M11a Short note to Dullemond<br/>with website URL]
    M7 --> P1[M11b Full pitch:<br/>bias result, paper proposal]
    M9 --> P2[M11c Paper draft,<br/>reproducibility package]
    P1 --> P2
gantt
    title Indicative timeline (part-time)
    dateFormat YYYY-MM-DD
    axisFormat %b %y
    section Foundations
    M0 Plan and literature          :done, m0, 2026-11-10, 14d
    M1 Workspace and core           :m1, after m0, 14d
    section Physics
    M2 Disk model                   :m2, after m1, 14d
    M3 Kinematics and outcomes      :m3, after m2, 21d
    M4a Kernel framework            :m4a, after m1, 14d
    M4b Physical kernels            :m4b, after m3, 21d
    section Numerics
    M5 Explicit integrators         :m5, after m4a, 14d
    M6 Implicit integrators, Radau  :m6, after m4b, 28d
    R Thesis reproduction           :r, after m6, 14d
    section Research
    M7 Unbiased sampling            :m7, after r, 28d
    M8 Variance, noise, cost        :m8, after m7, 42d
    M9 Second property              :m9, after m8, 42d
    section Visualization
    M10a Design                     :m10a, after m0, 28d
    M10b Views per milestone        :m10b, after m2, 280d
    M10c Integration and sampling   :m10c, after m8, 28d
    section Paper
    M11a Short note to Dullemond    :milestone, m11a, after m4b, 0d
    M11b Full pitch                 :milestone, m11b, after m7, 0d
    M11c Draft and package          :m11c, after m9, 42d

2. Milestones

M0: Plan and literature (this document set)

  • M0.1 Read thesis, code, coag_py, Dullemond notes, local papers. Done.
  • M0.2 Thesis review with findings (01-thesis-review.md). Done.
  • M0.3 Literature map, glossary, notation. Done.
  • M0.4 Vision, roadmap, verification, experiments, architecture, ADR set. Done (drafts).
  • M0.5 Iterate on the plan with Vincent (grill session, 27 decisions in the decision log); ADRs 0001 to 0016 accepted. Done.
  • M0.6 Decide audience and licence, repo hosting. Done: private until submission, open-source with the paper (06-risks-and-open-questions.md, Q1).

AC: Vincent has accepted the vision, roadmap, and ADRs. Done 2026-11-10.

M1: Workspace, core types, CI

  • M1.1 Done. git init in repo/ (done with this plan; related/ stays outside and read-only), Cargo workspace per ADR 0002, justfile (check, test, bench, run), lints, deny.toml, project AGENTS.md.
  • M1.2 Done (constants moved to M2.1, where the disk model needs them and the thesis-vs-CODATA parity question arises). coag-core: Real trait (f64, double-double; f128 behind a nightly feature, ADR 0016), physical constants (CODATA, IAU), Axis (linear, logarithmic; edges, centers, widths, index lookup), ParticleGrid with flattened multi-index (ADR 0004), State (N per cell and bin; one cell for now, ADR 0015) with mass and number moments.
  • M1.3 Done. Config schema with serde + TOML, every toggle explicit, config hash (ADR 0010).
  • M1.4 Done (coag-sim: .npy writer and reader, manifest.json with SHA-256 config hash). Output writer and run manifest (ADR 0011).
  • M1.5 Done (ChaCha8 keyed by the seed, injective stream ids, golden values pinned). Deterministic RNG streams (ADR 0009).
  • M1.6 Repository and CI done (vincentmader/coagulation, .github/workflows/check.yml); docs site built and tested locally (site/docs), deploy pending (DNS points to mk1, Caddy site file not yet in vps-infra). Private GitHub repository coagulation; CI runs just check; a deploy job publishes a docs page rendered from doc/ to https://coag.mader.xyz (static site on mk11, Caddy site file in vps-infra, per hetzner-deploy; DNS is set up). Local just check before each commit stays the primary gate.

AC: axis round-trip index(value(k)) = k for 10⁶ random k; geometric centers and q_m match the thesis grid to 10⁻¹⁵; manifest written and parsed back; same seed gives bit-identical draws on macOS and wasm32; f64 and double-double builds of coag-core pass the same tests; https://coag.mader.xyz serves the rendered docs.

M2: Disk model

  • M2.1 Physical constants (CODATA, IAU; thesis values behind a toggle for parity). Star, gas surface density power law with normalization to M_gas (thesis) and alternative normalization (Σ at 1 au), radial grid.
  • M2.2 Temperature (irradiated, optional floor), c_s, Ω_K, H_p, ρ_g(r, z), n_g, λ_mfp, u_th, ν_mol, ν_t, Re, pressure gradient, u_n, u_g with both viscosity choices (finding F6).
  • M2.3 LocalConditions evaluated at (r₀, z₀) and passed down as plain data (no lower crate calls the disk model, ADR 0015); the cell position is a config key; analytic derivatives where available instead of finite differences.
  • M2.4 Parity with thesis Disk/DiskRegion and with Dullemond’s diskmodel.py (for σ = 1).
  • M2.5 Export radial profiles for the web app and for figures (thesis Figs. temperature, sound speed, gas density 2×2).
  • M2.6 Disk view live on coag.mader.xyz (first app view, after the M10a design is approved).

AC: all profiles match thesis Python to 10⁻¹² relative (same inputs, molecular viscosity); u_g with ν_t matches an independent closed form for a power-law disk; profile figures regenerated.

M3: Dust kinematics, collision rates, outcomes

  • M3.1 Particle properties per bin: a(m, φ), St (Epstein; Stokes regime as toggle), stopping time.
  • M3.2 Relative velocities as separate, toggleable terms: Brownian, radial drift, azimuthal drift, turbulence (Ormel and Cuzzi 2007, all three regimes, cross checked with coag_py/dv_rel.py), differential settling (fixed), combination rule (quadrature; option for a velocity PDF).
  • M3.3 Cross sections (compact; porous form for M9) and C_ij.
  • M3.4 Outcome models: hard threshold, Maxwell-Boltzmann fragmentation probability (Stammler and Birnstiel 2022), bouncing with the Dominik and Dullemond (2024) local recipe implemented verbatim (ambiguities logged for the first contact) and validated against one of their local figures, v_frag scaling options.
  • M3.5 Parity with thesis relative_velocity, collision_rate, collision_outcome_probabilities.
  • M3.6 DustPy (uv, project-local) as an external oracle at one radius with transport off; matched settings documented; the DustPy-like robustness setup defined.
  • M3.7 Δv and collision views live on the website.

AC: each Δv term matches thesis Python to 10⁻¹² (thesis settings); turbulent Δv reproduces Ormel and Cuzzi limiting cases; P^coag + P^bounce + P^frag = 1 to round-off for all pairs; Δv and C_ij maps regenerated (thesis Figs. relative velocity, cross section, rate coefficient).

M4a: Kernel framework and analytic kernels

  • M4a.1 Sparse collision recipes (ADR 0005): per pair a list of (k, weight); assembly into RHS, Jacobian, dense K_kij export.
  • M4a.2 Kovetz and Olund mapping with near-cancellation handling; upper boundary policies (drop pair, as thesis; pile up; outflow with accounting).
  • M4a.3 Analytic test kernels: constant, sum, product.
  • M4a.4 Diagnostics: per-pair mass defect, ΔX^rel, sparsity, ΔM^net_ij.
  • M4a.5 f64 vs double-double comparison of per-pair mass defects and cancellation handling (E3).

AC: dense export equals coag_py.create_coag_kernel to 10⁻¹⁴ relative on its test grids (symmetric and asymmetric forms); per-pair mass defect < 10 ε_mach (m_i + m_j) for all pairs with cancellation handling; with handling disabled, the 10²⁰-span test from the coag_py docstring shows the breakdown.

M4b: Physical kernels

  • M4b.1 Coagulation kernel R^coag X^coag.
  • M4b.2 Fragmentation: pulverization; power-law shattering with configurable slope (MRN -11/6, Kun and Herrmann -3.27), upper cut-off m_i + m_j (thesis) or independent shattering up to max(i, j) (notes Eqs. 1.37 to 1.39); cratering with reverse Kovetz and Olund and its cancellation handling; bouncing with mass loss.
  • M4b.3 Total kernel = Σ over outcomes; component kernels kept separately for visualization.
  • M4b.4 Parity with thesis Kernel for coag, frag (MRN), total; thesis kernel figures (101, 102, 34) regenerated.
  • M4b.5 Kernel and recipe-inspector views live; this triggers the short note to Dullemond (M11a).

AC: kernel parity to 10⁻¹² with thesis settings; ΔX^rel ≤ 10⁻¹² for coag, frag, total (thesis claim reproduced); sparsity 3 % / 30 % at N_m = 100 reproduced.

M5: Explicit integrators and analytic benchmarks

  • M5.1 Integrator trait (ADR 0008); explicit Euler and Dormand-Prince 5(4) with step control (Heun and RK4 dropped: no experiment needs them).
  • M5.2 Explicit stability limit estimator (CFL-like, Laibe and Lombart 2021).
  • M5.3 Benchmarks: constant, sum, product kernels against analytic solutions (Ohtsuki 1990; coag_py examples), including coarse-grid artificial acceleration; an aggregation-fragmentation benchmark with known steady state (Sekiyama et al. 2017) if feasible.

AC: observed convergence orders 1 and 5 within ±0.2 on the constant kernel; Euler on the thesis linear-grid example reproduces thesis Fig. 112; analytic solutions matched to the discretization error documented by coag_py tests.

M6: Implicit integrators, own Radau IIA

  • M6.1 Backward Euler with Newton (matches coag_py backwardeuler, niter), linearized one-step form (Brauer 2008 App. C).
  • M6.2 One Rosenbrock-W method (tolerates approximate Jacobians; needed for E13).
  • M6.3 Radau IIA order 5 after Hairer and Wanner: transformed stage system, simplified Newton, step-size control and error estimate, Jacobian reuse, dense output; analytic Jacobian from recipes.
  • M6.4 Scaled variables and per-component tolerances (ADR 0003); positivity reporting instead of silent clipping (F7).
  • M6.5 Parity with scipy Radau (via the Python bridge, ADR 0013) on the full thesis kernel.
  • M6.6 Stiffness study: eigenvalue spectrum of J along the trajectory; step sizes chosen by each method.
  • M6.7 Integration view live (trajectories, mass error, step sizes).

AC: Radau matches scipy Radau to 10⁻⁸ relative at rtol = 10⁻¹⁰; convergence order 5 on a smooth test; steady state of the thesis full model reached with mass error ≤ 10⁻¹² and no negativity events at default tolerances.

R: Thesis reproduction baseline

  • R.1 Re-run thesis chapter 3 integrations (pure coag lin/log, pure frag, full model) and chapter 4 sampling runs with the thesis estimator (without replacement, scalar weights, as found in F1/F2) and with the documented proposal.
  • R.2 Write results/baseline/ with all figures and a short report: what matches, what differs and why.

AC: thesis Figs. 103, 104, 106, 107, 109 reproduced qualitatively (same features); quantitative deviations explained.

M7: Unbiased sampling and implicit sampled form

  • M7.1 Estimator trait (ADR 0007): full sum; thesis estimator (baseline); IS with replacement; HT without replacement (e.g. Poisson or conditional Poisson sampling); 1D (index) sampling for hit-and-stick.
  • M7.2 Proposals: uniform, ρ_i ρ_j, F_i F_j factorized (Dullemond, W_i = 1, m_i, m_i²), ΔM^net N_i N_j, ‖K_·ij‖ N_i N_j; alias-method sampler.
  • M7.3 Detailed balance per sample: each draw adds its full recipe (ADR 0006).
  • M7.4 Implicit sampled kernel (Eq. 1.71) as a sparse quadratic kernel, usable by all implicit integrators.
  • M7.5 Statistical test harness: replicas, bias with confidence intervals, variance, CRN.
  • M7.6 Experiments E5, E6, E7, E8.

AC: for all unbiased estimators, |bias| < 3 standard errors over 10³ replicas on fixed N; mass conservation per step at round-off for every sampled step; E6 answers RQ1 with a figure.

Checkpoint M11b: after M7, the full pitch to Dullemond: bias finding, corrected estimator, first plots, paper proposal (German draft per email-writing, Vincent approves and sends). Sampling views go live on the website at the same time.

M8: Variance reduction, noise, Jacobians, cost

  • M8.1 Hybrid split estimator (exact top-q pairs + sampled remainder), stratified sampling by k-blocks or by outcome type, control variates (previous-step sum, coarse-grid sum), common random numbers across replicas and stages.
  • M8.2 Sample handling in integrators: frozen per step, averaged batches, resampling per step with step-size control decoupled from noise.
  • M8.3 Noise floor and stability at steady state (E11, E12); balance-preserving sampling for the growth-fragmentation equilibrium.
  • M8.4 Jacobian strategies: dense exact, sparse from sampled recipes, approximate (dominant pairs), JFNK with GMRES; preconditioners (E13).
  • M8.5 Cost model: counted recipe evaluations, flops, wall-clock; work-precision diagrams (E14).
  • M8.6 Performance pass: Rayon parallelism over pairs, SIMD-friendly layouts, benchmarks (criterion).

AC: η (work-normalized efficiency) reported for every estimator and integrator combination at N_m ∈ {50, 100, 200, 400}; the best combination beats full summation in η at some N_m, or a documented negative result with reasons.

Go/no-go gate for M9 (criteria fixed 2026-11-10, before any results): go if the best p = 1 scheme reaches η ≥ 2 × full summation at N_m = 200 at the 1 % target, or if the measured cost model projects ≥ 5 × for p = 2 at N_m × N_φ = 100 × 20. Otherwise publish p = 1 as a methods paper (bias analysis, optimal proposal, Jacobian study) and move p = 2 to follow-up work. The decision and its numbers are written into this file. Compute for the p = 2 sweeps is estimated here; paid server time needs Vincent’s OK.

M9: Second particle property (filling factor)

  • M9.1 Filling factor grid, φ-dependent radius, St, cross section.
  • M9.2 Minimal porosity physics behind a swappable PorosityModel trait, so richer physics (static compression after Kataoka et al. 2013, Ormel et al. 2007 regimes) can be added later: the Okuzumi et al. (2012) hit-and-stick volume rule, one published collisional compaction rule, compact fragments; φ enters radius, St, and cross section.
  • M9.3 2D recipes (bilinear Kovetz and Olund in (m, V) or (m, φ)) with conservation of mass and the chosen second moment.
  • M9.4 Full 2D sum as reference at modest resolution; sampled schemes at production resolution (E15).
  • M9.5 Compare with φ(m, t) as dependent variable (Schöll et al. 2026) where the comparison is meaningful.

AC: 2D full and sampled results agree within noise at N_m × N_φ = 50 × 20; break-even surface over (N_m, N_φ, accuracy) computed; H5 decided.

M10: Interactive web visualization

See 07-visualization.md. Public at https://coag.mader.xyz from M2 on. Design before code (global rule).

  • M10a Requirements, information architecture, and a design in Claude Design (or similar), in parallel with M1 and M2; Vincent approves.
  • M10b Views ship with their milestones (disk M2, Δv and collisions M3, kernels M4b, integration M6, sampling M7), each with a screenshot review per ui-design.
  • M10c Comparison views and run browser for experiment outputs (M8).
  • M10d Shared Impressum and privacy pages for the mader.xyz family (static-website), linked from the app before the first public view.

AC: every config toggle from M2 to M8 reachable in the UI; live recomputation for N_m ≤ 100 under 1 s for kernels; screenshot review passed.

M11: Paper and outreach

See 08-paper-and-outreach.md.

  • M11a Short note to Dullemond once the kernel views are live (after M4b): the thesis work continues, here is the URL, a paper is planned. German draft, Vincent approves and sends.
  • M11b Full pitch after M7 (see the M7 checkpoint).
  • M11c Paper draft (LaTeX or Typst per journal), figures from experiment outputs only, reproducibility package (tagged release, configs, scripts, data DOI e.g. Zenodo).
  • M11d Internal review loop with co-authors; submission.

AC: every figure regenerates from a tagged commit with one command; co-authors sign off.

3. Task tracking

Until the repo has an issue tracker, track tasks in this file: mark finished tasks with “Done (commit abc1234)”. When GitHub is set up (Q1), convert each milestone into a GitHub milestone and each task into an issue with the same ID.

Verification and validation

How we know each layer is right. Tests live next to the code (unit), in crates/*/tests (integration), and in experiments/ (studies whose outputs are checked in CI at small size). Policy details follow the global testing skill.

1. Test pyramid

flowchart BT
    U[Unit tests<br/>axes, constants, formulas,<br/>single recipes] --> P[Property tests<br/>conservation per pair,<br/>symmetry, positivity]
    P --> O[Oracle and parity tests<br/>thesis Python, coag_py,<br/>scipy Radau, analytic solutions]
    O --> S[Statistical tests<br/>estimator bias and variance<br/>over replicas]
    S --> E[Experiment regression<br/>small-size versions of E1 to E16<br/>with stored reference outputs]

2. Oracles

OracleUsed forHow
Analytic solutionsConstant, sum, product kernels (pure coagulation); aggregation-fragmentation steady state if a closed form is available (Sekiyama et al. 2017)Closed forms from Ohtsuki 1990, Wattis 2006, coag_py/mc_sampling/mc_run.py (with its Bessel-function asymptotics).
Thesis Python (msc-thesis.py)Disk, kinematics, outcomes, kernels, thesis-estimator runsFixture generator script runs the thesis code on fixed configs and stores arrays as .npy under tests/fixtures/thesis/ with the source commit hash. Generated once, committed.
coag_py (Dullemond)Kovetz and Olund kernel, cancellation handling, backward Euler, MC prototypeSame fixture approach, tests/fixtures/coag_py/.
scipy solve_ivp(Radau)Our Radau IIA, full trajectoriesPython bridge (PyO3) or fixture files; equal rtol/atol, analytic Jacobian.
DustPy (Stammler and Birnstiel 2022)Steady state of growth and fragmentation at one radius with transport off; the DustPy-like robustness setupProject-local uv environment; matched settings recorded; steady-state distributions compared.
Conservation lawsEvery kernel, every stepBuilt-in assertions in debug builds; property tests in CI.

Fixtures are generated data from deterministic configs, not real observational data.

3. Invariants checked automatically

InvariantToleranceWhere
Per-pair mass defect |Σ_k m_k X_kij| (mass gained equals mass lost)≤ 10 ε_mach (m_i + m_j) with cancellation handlingProperty test over all pairs, every outcome model.
Per-pair number change for hit-and-stickexactly -1 per collisionProperty test.
Symmetry K_kij = K_kji (symmetrized form)exactProperty test.
Global mass error δ_M(t)≤ 10⁻¹² (coag), documented bound (frag), at round-off growth rate √(steps) εEvery integration test.
Positivity of N_kno negative values beyond -10⁻³⁰ relative; negativity events counted and reportedEvery integration test (replaces silent clipping, finding F7).
Sampled step mass conservationsame as full kernel, for every sampleEvery sampled integration test.
Estimator unbiasedness|mean − truth| < 3 SE over R replicas (R = 10³ in CI, 10⁵ in studies)Statistical tests with fixed seeds.
Precision independencef64 and double-double agree within the f64 error budget; the mass error floor drops with double-doubleExtended-precision tests on small grids (ADR 0016).
Determinismbit-identical output for identical config and seed, across thread countsCI runs with 1 and N threads.

Statistical tests use fixed seeds, so they are deterministic; their thresholds are chosen so a correct estimator fails with probability below 10⁻⁶ per test, documented next to each test.

4. Error metrics (replacing the thesis definitions)

Let ρ_k = m_k N_k and ρ = Σ ρ_k.

  • Mass error: δ_M(t) = (ρ(t) - ρ(0)) / ρ(0). (Thesis chapter 3 definition.)
  • Accuracy error vs reference (full kernel, same integrator, tight tolerances): mass-weighted L1 distance of the normalized distributions, e_acc(t) = ½ Σ_k | ρ_k(t)/ρ(t) − ρ^ref_k(t)/ρ^ref(t) |, which lies in [0, 1], is defined when bins are empty, and equals the fraction of mass “in the wrong bins”. Report also the log-space L2 error on bins above a mass floor for the shape at the small-mass tail.
  • Time-lag diagnostic: mass-weighted mean log-mass ⟨ln m⟩(t) vs reference; separates a systematic lag (bias, F1) from noise.
  • Steady-state noise floor: time-RMS of ‖dN/dt‖ (mass weighted) over the last decade in time, and the spread of ρ_k across replicas.
  • Estimator diagnostics on fixed N: bias B_k, variance V_k, and the mass-weighted mean squared error MSE = Σ_k m_k² (B_k² + V_k) / ρ².
  • Cost: recipe entries evaluated, kernel entries touched, Jacobian non-zeros, LU/Krylov flops, wall-clock (single thread, release build, fixed machine noted in the manifest).
  • Work-normalized efficiency: η = 1 / (MSE × cost); the comparison metric for estimators. For full runs, work-precision diagrams (e_acc at t_end vs cost).
  • Accuracy targets: e_acc = 1 % (headline), 10 % (quick look), 0.1 % where affordable; each at transient times and at steady state.

5. Integrator verification

CheckMethod
Order of convergenceConstant kernel with analytic solution; fixed step sequences; slope fit. Euler 1, DOPRI 5, backward Euler 1, linearized implicit Euler 1, Rosenbrock-W 2 or 3 (per chosen scheme), Radau IIA 5 (3 for stiff order reduction in some components, documented).
Stability on stiff problemsFull thesis kernel at large Δt: implicit methods must stay positive and bounded; explicit methods must fail beyond their CFL estimate.
Step-size controlAccepted/rejected step statistics match scipy within a factor of 2 on identical problems.
JacobianAnalytic vs finite difference on random states, relative error < 10⁻⁷.
Steady stateFull model: RMS dN/dt decays to round-off level, matching thesis Fig. 103 behavior.

6. Physics validation

CheckReference
Disk profilesThesis figures (temperature, sound speed, gas density 2×2); diskmodel.py.
Δv maps, St, C_ijThesis Figs. relative velocity (4 components, total), cross section, rate coefficient; Ormel and Cuzzi (2007) limiting cases.
Fragmentation probabilityThesis Fig. collision outcome probabilities; Stammler and Birnstiel (2022).
Steady state with fragmentationQualitative comparison with Birnstiel 2010 / DustPy at similar settings (peak size set by v_frag); optional direct DustPy run at one radius.
BouncingDominik and Dullemond (2024) qualitative results (narrow distribution, small-grain removal).

7. Continuous integration budget

just check must finish in under 5 minutes on the M1 Pro: unit, property, parity, and statistical tests at small sizes (N_m ≤ 50, R ≤ 10³). Full studies run through heavy and are not part of check.

Experiments and the theory behind them

Each experiment has an ID, the question it answers, a config under experiments/En_<slug>/config.toml, a script that writes outputs with a run manifest, and one or more figures. Sizes given are the study sizes; CI runs a reduced version (see 03-verification.md).

1. Estimator theory (basis for E5 to E13)

Fix the state N (one evaluation of the RHS). The target is the vector

F_k = Σ_(i,j) K_kij N_i N_j = Σ_(i,j) a_ij v^(ij)_k, with a_ij = N_i N_j R_ij and v^(ij)_k = X_kij,

where v^(ij) is the per-collision recipe of pair (i, j) (its gain/loss vector, ADR 0005). Write T_ij = a_ij v^(ij) for the term vector.

Thesis estimator (without replacement, no weights). Drawing a set 𝒮 of distinct pairs with inclusion probabilities π_ij and summing T_ij over 𝒮 gives E[F̂] = Σ π_ij T_ij. With π_ij < 1 every process is slowed down by its own factor π_ij, so the bias is B = −Σ (1 − π_ij) T_ij. This is finding F1.

Horvitz-Thompson (HT). F̂ = Σ_{(i,j)∈𝒮} T_ij / π_ij is unbiased for any design with π_ij > 0 wherever T_ij ≠ 0.

Importance sampling with replacement (IS). Draw S pairs i.i.d. from p, F̂ = (1/S) Σ_s T_{(ij)s} / p{(ij)_s}. Unbiased; covariance (1/S)(Σ T T^⊤/p − F F^⊤). This is Dullemond’s Eq. 1.70 with p_ij = F_i F_j.

Variance-optimal proposal. For a weighted norm ‖x‖²_w = Σ_k w_k x_k², E‖F̂ − F‖²_w = (1/S)(Σ_ij ‖T_ij‖²_w / p_ij − ‖F‖²_w). Minimizing over p with Σp = 1 (Lagrange, Cauchy-Schwarz) gives

p*_ij ∝ ‖T_ij‖_w = N_i N_j R_ij ‖v^(ij)‖_w.

With the mass weighting w_k = m_k² (error in mass per bin) and replacing L2 by L1 (a common robust choice) this is p_ij ∝ N_i N_j Σ_k m_k |K_kij| = N_i N_j ΔM^net_ij: the relevance measure the thesis wrote down, without the extra m_i m_j factor (F3). Cost per draw differs between pairs (recipe length L_ij is 3 to 4 for coagulation, up to N_m for shattering), so the cost-aware optimum is p_ij ∝ ‖T_ij‖ / sqrt(L_ij). E7 tests this.

Hybrid estimator. Sort pairs by p*_ij; sum the top set 𝒟 holding a fraction q of the proposal mass exactly, sample the rest. Unbiased, variance from the tail only. For peaked distributions (the typical case, notes section 1.3.1) a small 𝒟 removes most variance. E12.

Implicit sampled form. Replacing a_ij = N_i N_j R_ij by a_ij evaluated at the unknown end-of-step state, but with weights from the start-of-step proposal (Dullemond Eq. 1.71), gives a sparse quadratic kernel K̃ with one entry block per drawn pair. Unbiased at the start of the step; first-order bias in Δt that vanishes as the step shrinks. E10 measures it.

Detailed balance per sample. Because each draw contributes a full recipe vector v^(ij) whose mass moment is zero, every sampled RHS conserves mass exactly, independently of which pairs were drawn. Mass conservation does not depend on estimator quality. ADR 0006.

2. Experiment catalogue

Physics and baseline

IDQuestionSetupOutput
E1Do our disk profiles match the thesis and diskmodel.py? What does the viscosity fix (F6) change?Thesis disk, r ∈ [10⁻², 10²] au; ν_mol vs ν_tProfiles 2×2; Δv_RD with both choices
E2Which Δv component dominates where, and how do toggles change the kernel?N_m = 100, m ∈ [10⁻¹⁵, 10⁻⁸] kg; each component on/offΔv maps per component, C_ij, P_frag, P_bounce maps
E3Kernel structure and conservationcoag, frag (pulverization, MRN up to m_i+m_j, independent shattering, cratering), bouncing; with/without cancellation handling; f64 vs double-doubleKernel slices (thesis Figs. 101, 102), ΔX^rel maps, sparsity vs N_m
E4Integrator correctness and cost on deterministic kernelsAnalytic kernels; thesis full model; all integrators; f64 vs double-double long-term mass error; DustPy steady state at matched settingsConvergence plots; work-precision; steady state; stiffness spectrum
E5Faithful reproduction of thesis chapter 4Thesis estimator with scalar weights (as run) and with documented weightsThesis Figs. 104, 106, 107, 109 regenerated

Estimators on a fixed state (no time integration)

IDQuestionSetupOutput
E6RQ1: bias vs variance of the thesis estimatorSnapshots N(t) from the full-kernel run at 6 times; R = 10⁴ replicas per estimator; ρ_sample ∈ {0.05, 0.1, 0.25, 0.5}Bias and SE per bin; decomposition of MSE into bias² and variance; same for HT and IS
E7RQ2: proposal comparisonIS with uniform, ρ_iρ_j, F_iF_j (W = 1, m, m²), ΔM^net N_iN_j, p*, cost-aware p*η vs S for each proposal and snapshot
E8RQ3: shared draws for all k vs independent draws per kIS with p*; same total costMSE and mass conservation per variant

Estimators inside time integration

IDQuestionSetupOutput
E9RQ4: sample handlingFrozen per macro step (thesis), frozen per integrator step, batch-averaged B draws, resampled per stepe_acc(t), lag ⟨ln m⟩(t), negativity events, step statistics
E10RQ4: implicit sampled formEq. 1.71 vs explicit sampled RHS inside backward Euler, Rosenbrock, RadauBias vs Δt, stability at large Δt
E11RQ5: noise at steady stateFull model to t = 10⁴ yr; S scan; Δt scanNoise floor vs S and Δt (test H4); replica spread of ρ_k
E12RQ5: hybrid and balance-preserving estimatorsq scan for the hybrid; stratification by outcome type (coag vs frag)η and noise floor vs q; small-grain tail stability
E13RQ6: Jacobian strategiesDense exact, sparse sampled, dominant-pair approximate, JFNK; with W-method and simplified-Newton RadauCost split RHS/Jacobian/solve; work-precision

Scaling and comparison

IDQuestionSetupOutput
E14RQ7, p = 1N_m ∈ {50, 100, 200, 400, 800}; with and without fragmentation and bouncing; best estimator and integrator from E7 to E13Work-precision diagrams; break-even N_m vs accuracy
E15RQ7, p = 2N_m × N_φ ∈ {50, 100} × {10, 20, 40}; full 2D sum as reference where affordableBreak-even surface; test of H5
E16RQ8, comparisonLiterature numbers and reproduced test problems for low-rank/TT and DG; RPMC via Beutel’s code if Dullemond can share it; no own implementations unless a referee asksComparison table and plot at matched accuracy

3. Experiment hygiene

  • Headline experiments (E9 to E15) run in two physics setups (corrected thesis model, DustPy-like), each with and without bouncing.

  • Every run writes a manifest (config hash, git revision, seed, machine, timings). No figure in the paper without a manifest.

  • Experiments that use replicas use independent RNG streams per replica (ADR 0009) so results are reproducible and parallel.

  • Heavy runs go through heavy (global rules) and are logged in experiments/LOG.md with date, command, duration, and outcome.

Software architecture

Status: v0.2 (2026-11-10). The binding decisions are the ADRs in ../adr; this document shows how they fit together. Crate versions are chosen and pinned in M1 after checking the current releases (global rule against outdated APIs); none are fixed here.

1. Layering

The model is a pipeline of pure stages. Each stage is a crate with a narrow interface, so it can be tested alone, swapped by config, and visualized on its own.

flowchart LR
    subgraph core [coag-core]
        C1[constants]
        C2[Axis, ParticleGrid]
        C3[State N_k]
        C4[Config types]
    end
    subgraph phys [physics]
        D[coag-disk<br/>star, gas, DiskRegion]
        K[coag-dust<br/>a, St, dv terms,<br/>C_ij, outcomes]
    end
    subgraph num [numerics]
        R[coag-kernel<br/>collision recipes,<br/>K assembly, Jacobian]
        S[coag-sampling<br/>proposals, estimators,<br/>sampled kernels]
        I[coag-integrate<br/>Integrator trait,<br/>Euler ... Radau IIA]
    end
    subgraph app [applications]
        X[coag-sim<br/>runs, diagnostics,<br/>outputs, manifest]
        CLI[coag-cli]
        W[coag-web<br/>Leptos + wasm]
        PY[coag-py<br/>PyO3 bridge]
    end
    core --> D --> K --> R
    core --> R
    R --> S
    R --> I
    S --> I
    I --> X
    S --> X
    X --> CLI
    X --> W
    X --> PY

Dependency rule: arrows point from lower to higher layers only. coag-kernel knows nothing about disks (it takes rate matrices); coag-integrate knows nothing about dust (it takes an Rhs); coag-sampling depends only on recipes. This is what makes analytic test kernels, the thesis kernel, and 2D kernels interchangeable.

2. Workspace layout (planned for M1)

repo/
  AGENTS.md  CLAUDE.md  README.md  LICENSE  justfile  Cargo.toml  deny.toml
  crates/
    coag-core/        constants, axes, grids, state, config schema, errors
    coag-disk/        disk model and DiskRegion
    coag-dust/        particle properties, relative velocities, rates, outcomes
    coag-kernel/      recipes, Kovetz-Olund, fragmentation models, analytic kernels
    coag-sampling/    proposals, alias sampler, estimators, sampled kernels
    coag-integrate/   integrators, step control, linear solvers, Jacobian strategies
    coag-sim/         run orchestration, diagnostics, outputs, manifest
    coag-cli/         binary: run, sweep, inspect
    coag-py/          PyO3 bindings (oracles, paper figures)
  web/                Leptos SPA (after design approval, M10)
  experiments/        E1 ... E16: config.toml, script, README, LOG.md
  tests/fixtures/     thesis and coag_py reference arrays (+ generator scripts)
  paper/              manuscript and figure scripts (M11)
  doc/                this documentation

../related/ (outside the repo) holds read-only reference material (thesis, its code, papers, coag_py). Nothing there is edited, and nothing is copied by default: what we need is rebuilt here, mostly from scratch in Rust.

3. Core abstractions

classDiagram
    class Axis {
        +edges() &[f64]
        +center(k) f64
        +width(k) f64
        +index_below(x) Option~usize~
    }
    class ParticleGrid {
        +axes: Vec~Axis~
        +len() usize
        +flat(multi) usize
        +multi(flat) MultiIndex
        +mass(flat) f64
    }
    class State {
        +n: Vec~f64~
        +mass_density(grid) f64
        +number_density() f64
    }
    class RateMatrices {
        +collision: Matrix
        +by_outcome: OutcomeMatrices
    }
    class Recipe {
        +pair: PairIndex
        +rate: f64
        +entries: Vec~RecipeEntry~
        +mass_defect(grid) f64
    }
    class RecipeSet {
        +recipes: Vec~Recipe~
        +rhs(state, out)
        +jacobian(state) SparseMatrix
        +dense() Tensor3
    }
    class Proposal {
        <<trait>>
        +weights(state, recipes) Vec~f64~
    }
    class Estimator {
        <<trait>>
        +draw(state, rng) SampledKernel
    }
    class SampledKernel {
        +recipes: Vec~WeightedRecipe~
        +rhs(state, out)
        +jacobian(state) SparseMatrix
    }
    class Rhs {
        <<trait>>
        +eval(t, y, out)
        +jacobian(t, y) JacobianRepr
    }
    class Integrator {
        <<trait>>
        +step(rhs, t, y, dt) StepResult
    }
    ParticleGrid o-- Axis
    RecipeSet o-- Recipe
    RecipeSet ..|> Rhs
    SampledKernel ..|> Rhs
    Estimator --> Proposal
    Estimator --> SampledKernel
    Integrator --> Rhs

Key choices, each backed by an ADR:

  • State is N_k (particles per volume per bin) on a flattened multi-index, so p = 1 and p = 2 share all numerics (ADR 0004).
  • Recipes are the unit of physics: one per pair (i, j) and outcome, a short list of (k, weight) with a guaranteed zero mass moment. RHS, Jacobian, dense tensor, sampled kernels, and visualizations are all assembled from recipes (ADR 0005, ADR 0006).
  • Estimators return a SampledKernel, which implements the same Rhs trait as the full RecipeSet. Integrators never know whether they integrate a full or a sampled kernel (ADR 0007).
  • Integrators are own implementations behind one trait; scipy is an oracle, not a dependency (ADR 0008).
  • Cells: the state is cells × particle grid (one cell for now); physics below coag-disk receives LocalConditions and never calls the disk model, so radial transport can be added later by operator splitting or a coupled Jacobian (ADR 0015).
  • Precision: numerics are generic over Real (f64 default, double-double, nightly f128) with an f64-specialized fast path (ADR 0016).
  • Jacobian representation is an enum (dense, sparse, approximate, matrix-free) so E13 can swap strategies (ADR 0014).

4. One time step with sampling

sequenceDiagram
    participant Sim as coag-sim Runner
    participant Est as Estimator
    participant RNG as RngStream
    participant Int as Integrator (Radau IIA)
    participant SK as SampledKernel
    participant Diag as Diagnostics
    Sim->>Est: draw(state_n, recipes)
    Est->>RNG: stream(run, replica, step)
    Est->>Est: proposal weights p_ij, alias table
    Est-->>Sim: SampledKernel (weighted recipes, implicit form)
    Sim->>Int: step(SK, t_n, N_n, dt)
    loop simplified Newton
        Int->>SK: eval(t, y)
        Int->>SK: jacobian(t, y) (reused)
    end
    Int-->>Sim: N_n+1, error estimate, dt_next
    Sim->>Diag: mass error, negativity, cost counters
    Diag-->>Sim: record (manifest + arrays)

The sample is frozen within the step by default (deterministic ODE step, clean step-size control). E9 compares alternatives; the Runner owns that policy, not the integrator.

5. Configuration and toggles

One TOML file describes a run. Every physical and numerical choice is explicit; defaults reproduce the thesis (ADR 0010). Sketch:

[grid.mass]
min = 1e-15          # kg
max = 1e-8
bins = 100
scale = "log"

[disk]
star_mass = 1.0      # solar masses
radius = 10.0        # au
surface_density = { profile = "power_law", exponent = 0.8, normalization = "disk_mass", disk_to_star = 0.01 }
gas_velocity_viscosity = "turbulent"   # "molecular" reproduces the thesis (finding F6)

[dust.relative_velocity]
brownian = true
radial_drift = true
azimuthal_drift = true
turbulence = "ormel_cuzzi_2007"
settling = false
combine = "quadrature"

[collisions]
outcome = "maxwell_boltzmann"          # "threshold", "dominik_dullemond_2024"
v_frag = 1.0                           # m/s
bouncing = false
fragments = { model = "power_law", slope = -1.8333, upper = "sum" }   # "max_body", "pulverization"
cratering = false
upper_boundary = "drop_pair"           # "pile_up", "outflow"
cancellation_handling = true

[sampling]
estimator = "importance"               # "full", "thesis", "horvitz_thompson", "hybrid"
proposal = "optimal_mass_l1"
samples = 2000
hold = "per_step"                      # "per_macro_step", "batch_average"
implicit_form = true

[numerics]
precision = "f64"                     # "double_double", "f128" (nightly only)

[integrator]
method = "radau_iia5"
rtol = 1e-6
atol_scaled = 1e-12
jacobian = "sparse_sampled"

[time]
start = 1.0          # s
end = 3e10
outputs = 200
scale = "log"

[run]
seed = 42
replicas = 1

6. Performance plan

ConcernApproachWhen
Kernel assembly O(N_m² L)Recipes built once per rate change; Rayon over pairsM4
Full RHSLoop over recipes, contiguous arrays, no allocation per callM4
SamplingAlias method O(1) per draw after O(P) setup; reuse tables while the proposal is stale-tolerantM7
Jacobian and solveDense LU for small N; sparse LU or GMRES with preconditioner for large NM6, M8
Parallel replicasIndependent RNG streams, Rayon over replicasM7
Benchmarkscriterion benchmarks per crate; results recorded in experiments, not in CIM8

7. Web application (M10, after design approval)

flowchart LR
    subgraph browser [Browser]
        UI[Leptos SPA<br/>panels, toggles, plots]
        WASM[coag core in wasm<br/>disk, dv, kernels,<br/>small integrations]
        WK[Web Worker<br/>long computations]
    end
    subgraph server [mk11: coag.mader.xyz]
        ST[Static files<br/>Caddy]
        RES[Experiment outputs<br/>npy + JSON manifests]
    end
    UI <--> WASM
    UI <--> WK
    WK --> WASM
    UI -->|fetch| RES
    ST --> UI

Small problems (N_m ≤ 100, kernels and short integrations) run live in wasm; large studies are computed offline with coag-cli and loaded from files. No backend server is needed. Details in 07-visualization.md.

8. Error handling

  • Library crates return Result with typed errors (thiserror); the CLI reports them (anyhow at the binary edge only).
  • Invalid physics (negative densities beyond tolerance, mass error above bound, non-finite values) is an error with context, never silently corrected (finding F7).
  • Debug builds assert conservation per recipe; release builds check it per step at negligible cost.

Risks and open questions

Open questions for Vincent

IDQuestionOptionsRecommendation
Q1Decided 2026-11-10: (a). Audience, licence, hosting of the code(a) personal and private until submission, open-source at submission; (b) open from the start; (c) stays private(a): referees increasingly expect public code at submission, while an early public repo invites scooping of an unpublished idea. Licence at release: MIT or BSD-3 (common in astro codes).
Q2Decided 2026-11-10: (a). When to contact Dullemond(a) after M7 with the bias result and corrected estimator; (b) now, with this plan; (c) after M9 with a near-complete paper(a): a concrete, checkable result (the bias explains the thesis’ slow evolution) is a strong opener, shows the work is serious, and lets him shape scope (porosity model, comparison with RPMC) before the expensive phases.
Q3Decided 2026-11-10: filling factor φ. Second property for M9filling factor φ (Okuzumi), volume V, ice fractionFilling factor or volume: it is what the thesis and Dullemond’s notes motivate, and Schöll et al. (2026) in his group just argued it is too expensive as an independent dimension.
Q4JournalA&A vs J. Comput. Phys. vs ApJSDecide with Dullemond; A&A default.
Q5Decided 2026-11-10: public research tool at coag.mader.xyz, English only. Web app scoperesearch tool for us and the group vs public teaching toolResearch tool first (personal audience, no i18n or legal pages); a public version only if it turns into a teaching asset.
Q6Decided 2026-11-10: bridge for oracles and figures. Python in the looponly oracles and figures (ADR 0013) vs a full Python API for group membersBridge for oracles and figures now; a polished Python API only if co-authors ask.

Risks

IDRiskLikelihoodImpactMitigation
X1Sampling does not beat full summation even when unbiased and variance reduced (H5 false)mediumhighA clean negative result with a cost model is still publishable as part of a methods paper; the bias analysis, Radau/W-method Jacobian study, and 2D cost surface stand on their own. Decide go/no-go for M9 at the end of M8.
X2Linear solve dominates cost for p = 2 regardless of samplinghighhighPlanned from the start: E13 (sparse, approximate, matrix-free Jacobians).
X3MC noise destabilizes implicit steps at steady statemediummediumHybrid estimator, batch averaging, implicit sampled form, step-size control decoupled from noise (E11, E12).
X4Porosity physics recipes are contested and outside our expertisemediummediumUse one published recipe verbatim (Okuzumi 2012) and keep it swappable; the paper is about numerics, not about the best porosity model.
X5Overlap with other groups (TT methods, Gurrutxaga et al. 2026, RPMC improvements)mediummediumTrack arXiv (astro-ph.EP, math.NA) monthly; position clearly (grid-based, implicit, unbiased). Early contact with Dullemond. The public website makes the work visible early; Vincent accepts this as low risk (Q22).
X6Scope creep (global disk, transport, GPU)highmediumNon-goals in the vision; each extension needs an explicit milestone.
X7Own Radau implementation has subtle bugsmediumhighscipy parity tests on identical problems, order tests, step statistics comparison (M6).
X8Parity with thesis code is hard where the thesis code has bugshighlowParity tests reproduce the thesis behavior behind toggles (e.g. gas_velocity_viscosity = "molecular", estimator = "thesis"); fixes are separate toggles.
X9Time (part-time project)highmediumMilestones produce standalone value (reproduction, web tool, bias note); paper can be split (method paper first, porosity application second).
X10Machine load (16 GB shared Mac)mediumlowheavy lock for studies; CI sizes small; offload large sweeps to a server only with explicit OK.
X11Native f128 stays nightly-only longer than expectedhighlowDouble-double is the stable extended type; f128 is an optional nightly feature (ADR 0016).

Decisions log

Accepted decisions move from here into ADRs. Rejected options stay in the ADR’s “Alternatives” section.

DateDecision
2026-11-10Q1: private GitHub repository until submission, open-source (MIT or BSD-3, chosen at release) with the paper. Project audience: personal until then.
2026-11-10Q2: first contact with Dullemond after M7, leading with the bias finding and a corrected run.
2026-11-10Q1 (grill): local model only for the paper, architecture ready for non-local models (ADR 0015).
2026-11-10Q2: corrected thesis model as primary setup, DustPy-like setup as robustness check, exact thesis model only in R.
2026-11-10Q3: bouncing included; headline runs with and without it.
2026-11-10Q4: second property is the filling factor φ (log grid; mass and volume conserved per collision).
2026-11-10Q5: η on snapshots plus work-precision diagrams (transient and steady state), cost as counts and wall-clock.
2026-11-10Q6: comparisons from literature plus RPMC via Dullemond; no own competitor implementations.
2026-11-10Q7: thesis behavior kept permanently behind toggles with parity fixtures.
2026-11-10Q8: repo/ is the git root; related/ is read-only reference, never edited; what we need is rebuilt in the repo, mostly from scratch in Rust.
2026-11-10Q9 and Q27: web design runs in parallel with M1 and M2; deploy pipeline in M1 (docs page); views ship with their milestones.
2026-11-10Q10: DustPy run as an external oracle at one radius.
2026-11-10Q11: Dominik and Dullemond (2024) bouncing recipe verbatim, validated against one of their figures.
2026-11-10Q12: minimal, swappable porosity physics; richer physics possible later.
2026-11-10Q13: accuracy targets 1 %, 10 %, 0.1 %.
2026-11-10Q14: integrators: explicit Euler, DOPRI 5(4), backward Euler (Newton), linearized implicit Euler, one Rosenbrock-W, own Radau IIA 5.
2026-11-10Q15: p = 1 studies on the Mac; p = 2 compute estimated after M8, paid time only with OK.
2026-11-10Q16 and Q21: switchable precision: f64, double-double, nightly f128 (ADR 0016).
2026-11-10Q17 and Q22: web app public at https://coag.mader.xyz on mk11 from the first view, not staged (DNS set up).
2026-11-10Q18: go/no-go gate for M9 with fixed criteria (roadmap, M8).
2026-11-10Q19: cells × particle grid state, LocalConditions interface, cell position as config key.
2026-11-10Q20: no wholesale copying of related/; third-party material is not committed.
2026-11-10Q23: shared Impressum and privacy pages for the mader.xyz family.
2026-11-10Q24: static hosting on mk11 via the shared Caddy, deployed by GitHub Actions.
2026-11-10Q25: English-only UI (documented exception).
2026-11-10Q26: two-step contact: short note after M4b, full pitch after M7.

Interactive visualization: requirements

Status: requirements draft. No frontend code before a design is approved (global ui-design rule); M10a produces that design from this document, in parallel with M1 and M2; views then ship with their milestones.

1. Purpose

A browser tool in which every layer of the model can be inspected, every component toggled, and every integrator and estimator compared side by side. Two uses: (1) our own research instrument while developing M2 to M9; (2) a demonstration for Dullemond and the group, and possibly figures for talks. It is public at https://coag.mader.xyz from the first view on, so it can be checked live and shared early.

2. Views

ViewShowsControlsLive in wasm?
DiskΣ_g, T, c_s, H_p, ρ_g(r, z), λ_mfp, ν_mol vs ν_t, Re, u_g, u_n over r; marker at r₀star, disk mass, profile exponent, flaring angle, α, viscosity choice, r₀yes
Particlesa(m), St(m), regime boundaries (Epstein/Stokes), for p = 2 a(m, φ)ρ_s, grid range, resolutionyes
Relative velocitiesΔv_ij heat maps per component and total; slices; dominant-component mapeach component on/off, turbulence model, combination ruleyes
Collisionsσ_ij, C_ij, P^coag, P^bounce, P^frag maps; outcome regime mapoutcome model, v_frag, v_bounceyes
KernelK_kij slices by k (gain, loss, total, per outcome), recipe inspector for a clicked pair (i, j), ΔX^rel map, sparsityfragment model, slope, cratering, boundary policy, cancellation handlingyes (N_m ≤ 100)
Integrationm²f(m) or ρ_k vs time (animated and as a heat map), δ_M(t), RMS dN/dt, step sizes, rejected stepsintegrator, tolerances, Jacobian strategy, time rangeyes for small runs; loaded results otherwise
Samplingp_ij maps, drawn pairs per step, estimator bias/variance on a snapshot, noise floorestimator, proposal, S, hold policy, seed, replicassnapshot yes; full runs loaded
CompareTwo or more runs overlaid; difference maps; e_acc(t); work-precision diagramsselect runs from manifest listloaded results
Run browserAll experiment outputs with manifests, configs, figuresfilter by experiment, date, configloaded

3. Interaction principles

  • Every toggle maps 1:1 to a config key; the current UI state can be exported as a config.toml and imported again, so anything seen in the browser can be rerun with coag-cli.
  • Linked views: clicking a pair (i, j) in any matrix highlights the same pair in Δv, C_ij, P, kernel, and sampling views.
  • Long computations run in a Web Worker; the UI never blocks.
  • Every plot can be exported as SVG/PNG plus the data array.

4. Technical constraints

  • Stack: Leptos SPA, core crates compiled to wasm (ADR 0012); plotting library chosen per the data-visualization skill in M10a (heat maps and animated lines with 10⁴ points must stay smooth).
  • Deterministic results identical to the CLI for the same config and seed (same RNG, ADR 0009).
  • Hosting: static files on mk11 behind the shared Caddy (site file in vps-infra/mk11/caddy/sites/), deployed by GitHub Actions (hetzner-deploy); no backend. DNS for coag.mader.xyz is set up.
  • Public and English only (documented exception to the German/English default: the audience is physicists). No analytics, no cookies, all assets bundled, so the privacy policy stays minimal; shared Impressum and privacy pages for the mader.xyz family are linked from the app.

5. Acceptance

  • All toggles from M2 to M8 reachable.
  • Kernel recomputation at N_m = 100 below 1 s in the browser on the M1 Pro.
  • Screenshot review per ui-design passed (symmetric spacing, consistent typography, readable color scales in light and dark mode).

Paper and outreach plan

1. Collaboration

  • Primary contact: Prof. Dr. Cornelis P. Dullemond (ITA, Heidelberg), thesis supervisor and author of the method’s original formulation (notes section 1.3, coag_py MC prototype). Co-authorship is natural: the method idea is his, the systematic study and implementation are ours.
  • Possible further co-authors / contacts in or near the group: M. Beutel (RPMC, comparison), authors of Schöll et al. 2026 (porosity, fractal dimension). Only via Dullemond.
  • Timing, in two steps: (1) a short note once the kernel views are live on https://coag.mader.xyz (after M4b): the thesis work continues, here is the URL, a paper is planned. (2) The full pitch after M7: the bias finding (F1) with one figure, the corrected estimator matching the full kernel, the plan for p = 2, and the proposal to write a paper together. Ask for his view on scope (porosity recipe, RPMC comparison, journal), on open points of the bouncing recipe (M3.4), and on whether coag_py may be cited as code or redistributed.
  • Form: German email (email-writing skill). Drafted by Claude, approved and sent by Vincent. Register (“Sie” or “du”) per Vincent’s past correspondence with him.

2. Paper structure (draft)

  1. Introduction: multi-property dust evolution and its cost; grid vs particle methods; contribution.
  2. The coagulation-fragmentation equation on a grid: recipes, detailed balance, kernels used (coag, shattering, cratering, bouncing).
  3. Monte Carlo estimation of the collision sum: estimators (thesis, HT, IS, hybrid), bias of unweighted sampling, optimal proposal, mass conservation per sample.
  4. Time integration with sampled kernels: implicit form, Radau IIA and W-methods, Jacobian strategies, noise at steady state.
  5. Results for p = 1: bias/variance, proposals, noise floor, work-precision, break-even.
  6. Results for p = 2 (mass, filling factor): cost surface, accuracy, physical results.
  7. Comparison with other approaches (RPMC, low-rank/TT, DG).
  8. Discussion and outlook (radial transport, GPUs, more properties). Appendices: kernel construction details, Radau implementation notes, test suite.

3. Reproducibility package

  • Tagged release of the repository with the exact commit used for the paper.
  • experiments/ configs and scripts for every figure; one command per figure (just figure <id>).
  • Output data archived with a DOI (Zenodo), including manifests.
  • Code licence decided at release (Q1).

4. Writing tooling

LaTeX with the journal class (A&A provides aa.cls), since co-authors and the journal expect it; figures from Python (matplotlib) via the PyO3 bridge (ADR 0013). Typst only for internal notes.

5. Side outputs

  • A short technical note or blog post on the web tool (optional, after the paper).
  • A talk at the group meeting (ask Dullemond).

Architecture Decision Records

Format and rules: ADR 0001. Template: 0000-template.md.

ADRTitleStatus
0001Record architecture decisionsAccepted
0002Rust Cargo workspace with layered cratesAccepted
0003SI units at interfaces, scaled variables inside integratorsAccepted
0004State is N_k on a flattened multi-property indexAccepted
0005Kernels are built from sparse per-pair collision recipesAccepted
0006Detailed balance per sampleAccepted
0007Unbiased estimators as the default, thesis estimator as baselineAccepted
0008Own integrators behind one trait, scipy as oracleAccepted
0009Reproducible random number streamsAccepted
0010One TOML config per run, every assumption a toggleAccepted
0011Outputs as npy arrays plus JSON manifestAccepted
0012Web frontend: Leptos SPA with the core compiled to wasmAccepted
0013Python only for figures and test oraclesAccepted
0014Linear algebra with a pluggable Jacobian representationAccepted
0015Ready for non-local models without building themAccepted
0016Switchable floating-point precisionAccepted

ADR 0001: Record architecture decisions

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: doc/README.md

Context

The project will run for months or years, with gaps, several AI agents, and later co-authors who need to understand why the code looks the way it does. Method choices (estimators, integrators, units) are research decisions whose reasons must survive.

Decision

Record every significant architecture or method decision as an ADR in doc/adr/, numbered sequentially, using 0000-template.md (a light MADR variant). Statuses: Proposed, Accepted, Superseded, Rejected. ADRs are never edited in substance after acceptance; a new ADR supersedes an old one and both link to each other. The index is README.md.

Alternatives considered

  • Decisions only in commit messages: not discoverable.
  • A single design document: loses the history of alternatives.

Consequences

  • Small overhead per decision.
  • Agents and co-authors can read the reasons before changing code (project AGENTS.md will point here).

ADR 0002: Rust Cargo workspace with layered crates

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: 05-architecture.md; ADR 0012, 0013

Context

The thesis code is Python with dense N³ arrays and Python loops, which limits resolution and makes the p = 2 study impossible. The global default stack is Rust. The same core must run natively (studies), in wasm (web app), and from Python (oracles, figures).

Decision

A Cargo workspace in repo/ (the git root; a private GitHub repository coagulation until submission) with the crates coag-core, coag-disk, coag-dust, coag-kernel, coag-sampling, coag-integrate, coag-sim, coag-cli, coag-py, and later web/. Dependencies point only from lower to higher layers as drawn in the architecture document. Library crates are #![forbid(unsafe_code)] unless a measured hotspot justifies otherwise (then an ADR). publish = false until the licence question (Q1) is decided. Workspace lints and edition per the rust skill, versions pinned in M1 after checking current releases.

Alternatives considered

  • One crate: simpler at first, but couples physics to numerics and slows compile times; splitting later is a bigger refactor.
  • Python + Numba (as in early thesis versions): fast to write, but no wasm path, weaker typing, and the cost study needs control over memory layout.
  • C++/Fortran (common in astro codes): no advantage over Rust for this project and against the default stack.

Consequences

  • Clear test boundaries per layer; analytic kernels and 2D kernels plug in without touching integrators.
  • Some boilerplate for crate interfaces.
  • Co-authors who only use Python need the coag-py bridge (ADR 0013).

ADR 0003: SI units at interfaces, scaled variables inside integrators

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: Finding F5; notation.md

Context

The thesis uses SI, Dullemond’s code uses cgs. N_k spans many decades (about 3·10² m⁻³ for monomers at 10 au, far less for large bins) and time spans 1 s to 10¹¹ s. scipy defaults (rtol 10⁻³, atol 10⁻⁶) were used without scaling, so small bins were not error controlled.

Decision

All public APIs, configs, and outputs use SI units (kg, m, s, K), with documented convenience units only in config input (au, solar mass, year), converted at parse time. Integrators work on scaled variables: y_k = N_k m_k / ρ₀ (mass fractions per bin) by default, time in units of a reference collision time; tolerances are given in scaled form (rtol, atol_scaled). Conversions live in coag-core and are tested for round trips. Comparisons with coag_py convert cgs at the fixture boundary.

Alternatives considered

  • cgs throughout (astro convention): conflicts with the thesis and its figures, invites mixed-unit bugs at the thesis parity boundary.
  • A typed units crate (uom): strong guarantees but heavy generics in hot loops and awkward in wasm/PyO3 interfaces; reconsider if unit bugs appear.
  • Unscaled N_k in the integrator: reproduces the thesis tolerance problem.

Consequences

  • Error control becomes meaningful for every bin (mass-weighted).
  • Every interface must say its unit; doc comments state units for each quantity.
  • Parity tests with the thesis must use the thesis’ unscaled tolerances when reproducing its behavior.

ADR 0004: State is N_k on a flattened multi-property index

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: Dullemond notes section 1.3.1; M9

Context

The Smoluchowski equation is solved for N_k (particles per volume per bin) in both the thesis and coag_py. The p = 2 study (mass and filling factor) needs the same numerics on a 2D grid; Dullemond’s notes propose flattening (i, ε) into one index.

Decision

The solver state is a contiguous Vec<f64> of N over a ParticleGrid, which owns one Axis per property and maps between a flat index and the multi-index (row-major, mass fastest-varying is decided in M1 by benchmark). Kernels, estimators, integrators, and diagnostics only see flat indices; physics crates use multi-indices and per-property values. Moments (number, mass, second property) are computed by the grid.

Alternatives considered

  • Separate code paths for 1D and 2D: duplicates numerics and makes the comparison unfair.
  • State n(m) (density per unit mass): needs Δm factors everywhere; N_k is what the discrete kernel acts on.
  • Mass densities ρ_k as state: natural for plots, but the kernel is bilinear in N; we scale to mass fractions only inside the integrator (ADR 0003).

Consequences

  • p = 1 and p = 2 share all numerics and tests.
  • Index arithmetic is centralized and tested.
  • Recipes for p = 2 need a 2D Kovetz and Olund mapping (bilinear), designed in M9.

ADR 0005: Kernels are built from sparse per-pair collision recipes

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: Findings F8; 04-experiments.md section 1

Context

The thesis stores K_kij as dense N_m³ arrays (7.5·10⁶ doubles at N_m = 200; impossible for p = 2: (N_m N_φ)³). Hit-and-stick touches 3 to 4 bins per pair, fragmentation up to N_m. Sampling needs per-pair units with zero mass moment, and the Jacobian is assembled from the same pairs.

Decision

The kernel is a RecipeSet: for every unordered pair (i ≤ j) and every outcome with non-zero rate, a Recipe { pair, rate, entries: [(k, w)] } where w are the per-collision changes of N_k (loss -1 at i and j, Kovetz and Olund gains, fragment distribution, cratering remnant). Each recipe is constructed so Σ_k m_k w_k = 0 to round-off (checked in tests). RHS, Jacobian, the dense tensor (export for plots and parity), and sampled kernels are assembled from recipes. Boundary behavior when m_i + m_j exceeds the grid is an explicit policy: drop_pair (thesis), pile_up, outflow (mass leaves and is accounted).

Alternatives considered

  • Dense tensor: simple, but memory and cost scale as N³ even for sparse physics, and a sampled kernel would still need per-pair grouping.
  • Generic sparse tensor (COO/CSF): loses the per-pair grouping that detailed balance and sampling need.
  • Computing recipes on the fly per evaluation: saves memory, but costs repeated work; possible later as an option for very large p = 2 grids.

Consequences

  • Memory O(N_pairs · L) instead of O(N³).
  • Per-pair conservation becomes a unit-testable property.
  • The visualization can show a recipe for a clicked pair directly.
  • Changing rates (e.g. time-dependent disk) requires rebuilding rates, not entries; recipes store rates separately from entries for that reason.

ADR 0006: Detailed balance per sample

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: Dullemond notes section 1.3.4; ADR 0005

Context

Mass conservation to round-off is the property that keeps long integrations stable. A sampled RHS must not trade conservation for speed. Dullemond’s notes require that each sampled pair contributes to all k it affects.

Decision

A drawn pair always contributes its complete recipe (all k entries, loss and gain) with one common weight. Sampling over k inside a fragmentation recipe (to reduce cost for long recipes) is allowed only if the sampled sub-recipe is itself mass conserving (e.g. a sampled fragment set renormalized to the pair’s mass). Every sampled step is checked for mass conservation like a full step.

Alternatives considered

  • Independent samples per k (one option in the notes): breaks conservation per step; kept only as a comparison variant in E8, never as default.
  • Post-hoc rescaling to restore mass (as fix_negative_densities did): hides errors (finding F7).

Consequences

  • Mass conservation is independent of estimator quality, so errors in experiments are purely estimator bias and variance.
  • Recipe length L enters the cost per draw; the cost-aware proposal accounts for it.

ADR 0007: Unbiased estimators as the default, thesis estimator as baseline

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: Findings F1 to F4; RQ1 to RQ3; E5 to E8

Context

The thesis estimator samples without replacement and does not reweight, so it underestimates every rate by its inclusion probability (F1). The runs also used a proposal different from the documented one (F2). Dullemond’s notes and prototype use an unbiased importance-sampling form.

Decision

An Estimator trait returns a SampledKernel that implements Rhs. Implementations: Full (exact), Thesis (faithful baseline, without replacement, no weights, configurable weights including the scalar bug), ImportanceSampling (with replacement, weights 1/(S p)), HorvitzThompson (without replacement, weights 1/π), Hybrid (exact dominant set plus sampled tail). Proposals are a separate trait (uniform, ρ_iρ_j, factorized F_iF_j with weight W, ΔM^net N_iN_j, optimal ‖T‖ with cost awareness). Sampling uses the alias method. Default for new studies: Hybrid or ImportanceSampling with the optimal proposal, decided after E7 and E12.

Alternatives considered

  • Keep only the thesis estimator and tune ρ_sample: cannot remove the bias.
  • Only factorized index sampling (Dullemond’s sample2d): cheaper proposals, but cannot express pair-level relevance such as Δv-dependent rates; kept as a proposal option.

Consequences

  • Statistical tests (bias, variance) become part of CI.
  • The thesis results are reproducible as a baseline and the bias can be quantified (E6).

ADR 0008: Own integrators behind one trait, scipy as oracle

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: Finding F5; M5, M6; E4, E9, E10, E13

Context

The thesis used scipy Radau through coag_py.react_0d, one call per macro step, with default tolerances. Studying sampled kernels needs control over stages, Newton iterations, Jacobian reuse, and step-size control (noise must not drive step rejection). Rust ODE crates exist, but none is known to offer Radau IIA with the hooks we need; this is re-checked in M6 before writing code.

Decision

Integrators are implemented in coag-integrate behind trait Integrator { fn step(&mut self, rhs: &dyn Rhs, t, y, dt) -> StepResult } with a separate step controller. Methods: explicit Euler, Dormand-Prince 5(4), backward Euler with Newton, linearized implicit Euler (Brauer 2008), one Rosenbrock-W method, Radau IIA order 5 following Hairer and Wanner (transformed stage system, simplified Newton, embedded error estimate). The Jacobian is requested from the Rhs in a representation chosen by config (ADR 0014). scipy Radau is used only as a test oracle (ADR 0013).

Alternatives considered

  • scipy via Python bridge in production: no wasm, little control, Python overhead in the loop.
  • Wrap Fortran RADAU5: fast and proven, but foreign build chain, no wasm, still limited control.
  • An existing Rust crate: adopt if one passes the M6 parity tests and exposes Jacobian and stage hooks; otherwise use it as an extra oracle.

Consequences

  • Significant implementation and testing effort in M6 (order tests, scipy parity, step statistics).
  • Full control for the research questions RQ4 and RQ6.
  • Note for write-ups: Radau IIA (3 stages) has order 5, not 4.

ADR 0009: Reproducible random number streams

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: E6 to E16; ADR 0012

Context

Estimator studies need many independent replicas, parallel execution, and identical results in the CLI and the browser. The thesis used the global np.random state without seeds.

Decision

Use a seedable, platform-independent generator from the rand ecosystem (a counter-based or PCG/ChaCha generator, chosen in M1 by speed and wasm support). Each random stream is derived deterministically from (run seed, replica, time step, purpose) so results do not depend on thread scheduling or on the number of draws in other streams. The seed and derivation scheme are recorded in the run manifest.

Alternatives considered

  • One global RNG: results depend on execution order and thread count.
  • OS entropy: not reproducible.

Consequences

  • Replicas run in parallel with identical results to serial runs.
  • Common random numbers across estimator variants become possible (variance reduction in comparisons).

ADR 0010: One TOML config per run, every assumption a toggle

Context

The thesis config (config.toml) already exposed many switches but not all (e.g. the viscosity choice, boundary policy, estimator details were code edits). The research needs to isolate each assumption, and the web UI needs a 1:1 mapping to config keys.

Decision

Each run is described by one TOML file deserialized with serde into typed structs with deny_unknown_fields. Every physical and numerical choice is an explicit key with a documented default; defaults reproduce the thesis setup, and fixes of thesis behavior are separate keys. Convenience units in input are converted at parse time (ADR 0003). The canonical serialized config is hashed and stored in the run manifest. The web UI reads and writes the same format.

Alternatives considered

  • Hard-coded settings per experiment script: not reproducible from outputs.
  • Command line flags only: unwieldy for dozens of options.

Consequences

  • Any figure can be regenerated from its manifest.
  • Adding a physics option means adding a config key, a default, a test, and a UI control.

ADR 0011: Outputs as npy arrays plus JSON manifest

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: 03-verification.md; ADR 0010, 0012, 0013

Context

Outputs must be readable by numpy/matplotlib (figures), by the browser (web app), and by our own tests, and must carry provenance. Sizes are moderate (N_t × N up to about 10⁷ doubles per run).

Decision

Each run writes a directory with one .npy file per array (little-endian f64, readable by numpy without extra dependencies and parsable in a few lines in Rust/wasm) and a manifest.json with config, config hash, git revision, seed, machine, timings, and diagnostics summaries. Large studies may later add Arrow/Parquet if needed (new ADR).

Alternatives considered

  • HDF5: standard in astro, but a heavy C dependency and poor wasm support.
  • Parquet/Arrow: good for tables, less natural for N-d arrays; overkill now.
  • Plain text (as in the thesis np.savetxt): slow, large, loses precision.

Consequences

  • Simple tooling everywhere; easy to archive on Zenodo.
  • Many small files per run; acceptable.

ADR 0012: Web frontend: Leptos SPA with the core compiled to wasm

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: 07-visualization.md; global ui-design rule

Context

Goal G5 asks for an interactive tool in which every model component can be toggled and visualized. The default web stack is a Leptos SPA. The physics and kernel code should be identical to the CLI to avoid divergent results.

Decision

The web app in web/ is a Leptos SPA that links the core crates compiled to wasm32 and runs heavier work in a Web Worker. Precomputed experiment outputs are loaded as static files (ADR 0011). Hosting: a public static site at https://coag.mader.xyz on mk11 (shared Caddy), deployed by GitHub Actions; English only. No frontend code is written before a design is approved (M10a); the design sprint runs in parallel with M1 and M2, and views then ship with their milestones. The plotting library is chosen in M10a per the data-visualization skill.

Alternatives considered

  • Python dashboard (Dash, Panel, Streamlit): fast to build, but needs a server, duplicates computation in Python, and diverges from the Rust core.
  • Plain TypeScript app with a wasm module: possible, but against the default stack and splits the codebase.
  • Desktop app (Tauri): no advantage for sharing with the group.

Consequences

  • Same numbers in browser and CLI for the same config and seed.
  • Core crates must stay wasm-compatible (no threads required, no file system in core, feature-gated Rayon).

ADR 0013: Python only for figures and test oracles

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: Global stack rule (Python only where its ecosystem is the reason); ADR 0008

Context

Paper figures are expected in matplotlib quality, and scipy plus the thesis and coag_py code are the oracles for parity tests. Neither justifies Python in the simulation loop.

Decision

A thin PyO3 crate coag-py exposes run, kernel, and estimator entry points to Python. Python (managed with uv, linted per the python skill) is used for: fixture generation from the thesis and coag_py code, scipy Radau parity checks, DustPy runs as an external oracle, and figure scripts for the paper. The simulation itself never depends on Python.

Alternatives considered

  • Matplotlib-free figures in Rust (plotters): possible, but slower to iterate to publication quality.
  • Calling Python from Rust tests: fragile in CI; fixtures are generated once and committed instead.

Consequences

  • Two toolchains in the repo (cargo, uv), both driven from the justfile.
  • Fixtures pin the oracle behavior at a known commit of the thesis code.

ADR 0014: Linear algebra with a pluggable Jacobian representation

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: RQ6, E13; ADR 0008

Context

Implicit steps need solves with I − h γ J. For p = 1 dense LU on N ≤ 800 is cheap; for p = 2 (N up to about 4000 to 16000) dense LU costs as much as the full kernel sum (thesis review section 5). Sampled kernels yield sparse Jacobians.

Decision

Jacobians are represented by an enum: Dense, Sparse (CSR, assembled from recipes), Approximate (from a dominant-pair subset), MatrixFree (Jacobian-vector products by recipe sums). Solvers: dense LU, sparse LU, GMRES/BiCGStab with simple preconditioners (diagonal, ILU(0) on the dominant-pair Jacobian). Use the faer crate for dense and sparse factorizations if it fits after the M6 check of its current API and wasm support; otherwise nalgebra for dense plus own Krylov code.

Alternatives considered

  • Dense only: makes the p = 2 cost study meaningless.
  • Binding LAPACK/SuiteSparse: fast, but C dependencies and no wasm.

Consequences

  • E13 can compare strategies under one interface.
  • Some own Krylov and preconditioner code to test (against dense solves on small problems).

ADR 0015: Ready for non-local models without building them

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: vision section 2 (non-goals); ADR 0004; grill decision Q19

Context

The paper studies a local model at one radius (r₀ = 10 au). Radial transport and a global disk model are the most likely follow-up projects. The most expensive refactor in codes of this kind is physics that reads global disk state directly, so that adding a spatial dimension touches every layer.

Decision

  • The solver state is organized as cells × particle grid. Today the number of cells is one.
  • coag-disk evaluates the disk model at a cell position and returns a plain LocalConditions value (T, ρ_g, c_s, H_p, Ω_K, α, ν, pressure gradient, u_g, u_n, …). Everything below it (kinematics, rates, recipes, estimators, integrators) receives LocalConditions and never calls the disk model.
  • Rhs implementations and the run loop operate on a list of cells, so a transport operator can be added later, either by operator splitting or as additional terms in a coupled Jacobian.
  • The cell position (radius, height) is a config key from the start.
  • No transport, advection, or diffusion code is written for the paper.

Alternatives considered

  • Build a 1D radial model now: multiplies cost by N_r and adds a second research question to a paper that already has several.
  • Ignore non-locality: fastest now, but locks physics to a single global point and forces a broad refactor later.

Consequences

  • Small overhead (one extra loop level, one plain-data struct).
  • Recipes and rate matrices are built per cell; for one cell this is identical to the local model.
  • A later transport milestone needs a new ADR on splitting vs coupled integration.

ADR 0016: Switchable floating-point precision

  • Status: Accepted (2026-11-10)
  • Date: 2026-11-10
  • Deciders: Vincent Mader
  • Related: findings F5, F9; experiments E3, E4; ADR 0003, 0014; grill decision Q21

Context

The thesis reports mass errors around 10⁻¹² and relies on analytic cancellation handling because f64 cannot represent m_i + m_j when m_i ≪ m_j. Comparing f64 against an extended precision answers whether that error is a round-off floor or a method error. As of November 2026 Rust’s native f128 is nightly-only, with stabilization targeted for late 2026 at the earliest. Double-double arithmetic (about 106 bits of mantissa) is available on stable Rust, in pure Rust, and works in wasm.

Decision

  • Numerics are generic over a Real trait defined in coag-core: axes, recipes, kernel assembly, RHS, explicit integrators, and the implicit integrators with a small own dense LU.
  • Supported types: f64 (default), double-double (the stable extended type; the crate is chosen in M1 after checking current releases), and native f128 behind a nightly-only cargo feature until it stabilizes.
  • The production path stays specialized for f64: faer-based linear algebra, alias-table sampling, and probabilities are f64 only.
  • Precision is a config key ([numerics] precision), recorded in the run manifest.
  • E3 and E4 compare f64 against double-double for per-pair mass defects, cancellation handling, and long-term mass error.

Alternatives considered

  • f64 only: simplest, but leaves the round-off question unanswered.
  • Arbitrary precision (MPFR via rug): C dependency, no wasm, much slower than needed.
  • Native f128 only: blocked on nightly.

Consequences

  • Generic code in the core crates (trait bounds, constants via Real::from_f64).
  • Extended-precision runs are limited to small grids (own dense LU is O(N³), double-double arithmetic is several times slower than f64, to be measured in M1).
  • Tests run for both f64 and double-double in CI on small sizes.
  • Found in M1: twofloat documents its exp, ln, and powf as not accurate to full double-double precision, so the Real trait offers only arithmetic, sqrt, and abs. Grids are defined in f64 and promoted exactly, so f64 and extended runs use bit-identical bin masses; physics that needs transcendental functions computes in f64.
  • Found in M1: on nightly 1.101 for aarch64-apple-darwin, f128::sqrt returns 0. Real::sqrt for f128 uses two Newton steps from the f64 root instead (tested to f128 accuracy).

ADR NNNN:

  • Status: Accepted (2026-11-10) | Accepted | Superseded by NNNN | Rejected
  • Date: YYYY-MM-DD
  • Deciders: Vincent Mader
  • Related: findings, experiments, other ADRs

Context

What problem forces a decision, with the facts that matter.

Decision

What we do, stated so it can be checked.

Alternatives considered

Each alternative with the reason it lost.

Consequences

What becomes easier, what becomes harder, what we must now do.

Literature and source map

Every source this project builds on, with its role, where it lives locally (if anywhere), and its reading status. Paths are relative to this file; related/ is the folder of symlinks at the project root.

Bibliographic details were checked against publisher or ADS records on 2026-11-10.

Status: read (studied for this plan), skimmed (abstract and relevant sections), to read (needed before the milestone named in brackets), cited (only known from the thesis bibliography).

1. Primary sources of this project

KeySourceLocalRoleStatus
mader_2024V. Mader, Investigating the Feasibility of utilizing Stochastic Sampling of Colliding Circumstellar Dust Particle Pairs for Computational Cost Optimization of the Smoluchowski Coagulation Equation’s Numerical Integration, M.Sc. thesis, Heidelberg University (ITA), supervisor C. P. Dullemond, Feb 2024PDF, TeXThe starting point. Reviewed in 01-thesis-review.md.read
msc-thesis.pyThesis code (Python), github.com/vincentmader/msc-thesis.pyrepoReference implementation for parity tests (disk, kinematics, kernels).read (core modules)
mc_coagRust/PyO3 stub crate from the thesiscrateOnly a skeleton (169 lines); nothing to port.read
dullemond_notesC. P. Dullemond, Dust evolution with binning methods (lecture notes / draft, 19 pp.)PDFThe research program we continue. Kovetz and Olund, near-cancellation, fragmentation models (pulverization, shattering, cratering, bouncing with mass loss), cost scaling N^{3p}, MC kernel sampling (Eq. 1.70), implicit form (Eq. 1.71), detailed balance per sample, open TODOs.read
coag_pyC. P. Dullemond, coag_py (Python), incl. react_0d.py (Radau via scipy, Newton backward Euler), coag.py (Kovetz and Olund kernel), mc_sampling/ (2021 MC prototype)repoReference for kernel parity (create_coag_kernel), integrator semantics, and the importance-sampling prototype (sample_kernel_sum).read
dullemond_sizedistC. P. Dullemond, Dust physics: size distributions (notes, 2 pp.)PDFConventions for n(a), n(m), dimensionless bin widths, m²f(m) plots.skimmed
dullemond_diskmodeldiskmodel.py, natconst.py (Dullemond)diskmodel.pyMinimal irradiated power-law disk; cross check for M2 (note: Σ ∝ r⁻¹ there, r⁻⁰·⁸ in thesis).read

2. Coagulation physics and dust evolution in disks

KeyReferenceDOI / URLLocalRoleStatus
smoluchowski_1916M. v. Smoluchowski, Drei Vorträge über Diffusion, Brownsche Bewegung und Koagulation von Kolloidteilchen, Phys. Z. 17, 557 (1916)ADS 1916ZPhy…17..557SThe equation; constant kernel solution.cited
kovetz_olund_1969A. Kovetz, B. Olund, J. Atmos. Sci. 26, 1060 (1969)10.1175/1520-0469(1969)026<1060:TEOCAC>2.0.CO;2Mass and number conserving mapping of m_i+m_j onto a log grid.cited (method read via dullemond_notes)
dullemond_dominik_2005C. P. Dullemond, C. Dominik, Dust coagulation in protoplanetary disks: a rapid depletion of small grains, A&A 434, 971 (2005)arXiv:astro-ph/0412117PDFBrownian Δv, need for fragmentation to sustain small grains. The thesis bib entry dullemond_dominik_2004 carries a different title (the settling paper); fix in the paper.skimmed
brauer_2008F. Brauer, C. P. Dullemond, Th. Henning, Coagulation, fragmentation and radial motion of solid particles in protoplanetary disks, A&A 480, 859 (2008)10.1051/0004-6361:20077759PDFΣ_g profile, modified Podolak algorithm (App. A), implicit differencing (App. C), 10⁴ speed-up.skimmed (App. A, C read)
birnstiel_2010T. Birnstiel, C. P. Dullemond, F. Brauer, Gas- and dust evolution in protoplanetary disks, A&A 513, A79 (2010)10.1051/0004-6361/200913731PDFDisk + kinematics recipes (St, radial drift, u_g with α viscosity, σ_H₂, ρ_s), fully implicit unsplit scheme (App. A), test cases (App. B).skimmed; to read App. A, B [M6]
birnstiel_2011T. Birnstiel, PhD thesis (2011), arXivLong-form derivations.cited
ormel_cuzzi_2007C. W. Ormel, J. N. Cuzzi, Closed-form expressions for particle relative velocities induced by turbulence, A&A 466, 413 (2007); arXiv:astro-ph/070230310.1051/0004-6361:20066899Turbulent Δv regimes (their Eqs. 26 to 29).to read [M3]
weidenschilling_1977S. J. Weidenschilling, MNRAS 180, 57 (1977)10.1093/mnras/180.2.57Maximum drift velocity u_n.cited
nakagawa_1986Y. Nakagawa, M. Sekiya, C. Hayashi, Icarus 67, 375 (1986)10.1016/0019-1035(86)90121-1Azimuthal drift.cited
lynden-bell_pringle_1974D. Lynden-Bell, J. E. Pringle, MNRAS 168, 603 (1974)10.1093/mnras/168.3.603Viscous radial gas velocity.cited
stammler_birnstiel_2022S. M. Stammler, T. Birnstiel, DustPy, ApJ 935, 35 (2022)10.3847/1538-4357/ac7d58Maxwell-Boltzmann fragmentation probability; state-of-the-art 1D Smoluchowski code to benchmark against. Run as an external oracle at one radius with transport off (M3.6).to read [M3, M6]
dominik_dullemond_2024C. Dominik, C. P. Dullemond, The bouncing barrier revisited, A&A 682, A144 (2024)10.1051/0004-6361/202347716, arXiv:2312.06000Sticking/bouncing/fragmentation probabilities, v_frag scaling; source for the bouncing model. The thesis cites it as “2003”.to read [M3]
mathis_1977J. S. Mathis, W. Rumpl, K. H. Nordsieck, ApJ 217, 425 (1977)10.1086/155591MRN slope for fragment distribution (p = -11/6 in mass).cited
kun_herrmann_1999F. Kun, H. J. Herrmann, Phys. Rev. E 59, 2623 (1999)Alternative fragment slope p ≈ -3.27.cited via dullemond_notes
guettler_2010C. Güttler et al., A&A 513, A56 (2010)Laboratory based collision outcome recipe.to read [M3]
blum_wurm_2008J. Blum, G. Wurm, ARA&A 46, 21 (2008)Laboratory review.to read [M3]
wada_2009K. Wada et al., ApJ 702, 1490 (2009)10.1088/0004-637X/702/2/1490Collision growth conditions (BPCA).cited
laibe_lombart_2021G. Laibe, M. Lombart, On the CFL condition for numerical solvers of the coagulation equation, MNRAS (2021)10.1093/mnras/stab3499Explicit time step limits; needed for the Euler/RK study.to read [M5]

3. Porosity and additional particle properties (the regime where sampling should pay off)

KeyReferenceDOI / URLRoleStatus
ormel_2007C. W. Ormel, M. Spaans, A. G. G. M. Tielens, Dust coagulation in protoplanetary disks: porosity matters, A&A 461, 215 (2007)10.1051/0004-6361:20065949Direct MC with mass + porosity; collision regimes (hit-and-stick, compaction, fragmentation). Thesis key ormel_2006.to read [M9]
okuzumi_2009S. Okuzumi, H. Tanaka, M. Sakagami, ApJ 707, 1247 (2009)10.1088/0004-637X/707/2/1247, arXiv:0911.0239Extended Smoluchowski with volume as second variable; volume-averaging method.to read [M9]
okuzumi_2012S. Okuzumi et al., Rapid coagulation of porous dust aggregates outside the snow line, ApJ 752, 106 (2012); 10.1088/0004-637X/752/2/106Porosity evolution recipe used widely.to read [M9]
kataoka_2013 / kataoka_2017A. Kataoka et al. (2013, A&A 557, L4 (Fluffy dust forms icy planetesimals by static compression)); A. Kataoka, ASSL 445, 143 (2017)10.1007/978-3-319-60609-5_5Static compression, porosity evolution review.to read [M9]
schoell_2026Schöll, Dullemond, Dominik, The influence of the fractal dimension on dust evolution in protoplanetary disks (2026)arXiv:2604.26794Same group. States that an independent filling-factor dimension is “often computationally too expensive” and uses φ(m,t) as a dependent variable instead. Direct motivation for our M9.to read [M8]

4. Numerical methods for the coagulation equation

KeyReferenceDOI / URLRoleStatus
hairer_wanner_1996E. Hairer, G. Wanner, Solving Ordinary Differential Equations II: Stiff and Differential-Algebraic Problems, 2nd ed., Springer (1996)Radau IIA (RADAU5), simplified Newton, step control, W-methods. Basis of our own Radau implementation.to read [M6]
radau5_fE. Hairer, radau5.f reference codehttps://www.unige.ch/~hairer/prog/stiff/radau5.fReference implementation, referenced by react_0d.py.to read [M6]
scipy_radauSciPy solve_ivp(method='Radau')scipy docsOracle for parity tests (what the thesis used). Order 5.known
filbet_laurencot_2004F. Filbet, P. Laurençot, SIAM J. Sci. Comput. (2004)10.1137/S1064827503429132Conservative finite volume scheme for Smoluchowski.cited, to read [M5]
lombart_laibe_2021M. Lombart, G. Laibe, Grain growth for astrophysics with discontinuous Galerkin schemes, MNRAS 501, 4298 (2021)arXiv:2011.12298High-order DG coagulation, few bins, machine precision mass. Competing approach.to read [M6]
lombart_2024M. Lombart, C.-E. Bréhier, M. Hutchison, Y.-N. Lee, General non-linear fragmentation with discontinuous Galerkin methods, MNRAS (2024)arXiv:2404.11851DG fragmentation, 0.1 to 1 % accuracy with 20 bins over 9 decades. Competing approach.to read [M6]
ohtsuki_1990K. Ohtsuki, Y. Nakagawa, K. Nakazawa, Icarus 83, 205 (1990)10.1016/0019-1035(90)90015-2Analytic solutions (constant, linear) and coarse-grid artificial acceleration. Used by coag_py tests.to read [M5]
wattis_2006J. A. D. Wattis, Physica D 222, 1 (2006)10.1016/J.PHYSD.2006.07.024Coagulation-fragmentation review, product kernel gelation.to read [M5]
safronov_1963/1972V. S. SafronovLinear kernel solution.cited
spouge_1985J. L. Spouge, J. Phys. A 18, 3063 (1985)10.1088/0305-4470/18/15/028Analytic solutions, combinatorial view.cited
dubovskii_1994P. B. Dubovskiǐ, Mathematical Theory of Coagulation (lecture notes, 164 pp.)PDFExistence, uniqueness, mass conservation, gelation theory. For the paper’s theory section.to read [M8]
sekiyama_2017M. Sekiyama, T. Ohtsuki, H. Yamamoto, J. Phys. Soc. Jpn. (2017)Analytic aggregation-fragmentation solutions: a benchmark with fragmentation. Thesis key ohtsuki_2017.to read [M5]

5. Stochastic and reduced-cost methods (positioning of the paper)

KeyReferenceDOI / URLRoleStatus
ormel_2007see section 3Direct simulation Monte Carlo.to read
zsom_dullemond_2008A. Zsom, C. P. Dullemond, A representative particle approach to coagulation and fragmentation of dust aggregates and fluid droplets, A&A 489, 931 (2008); 10.1051/0004-6361:200809921, arXiv:0807.5052Representative particle MC (original).to read [M8]
beutel_2023M. Beutel, C. P. Dullemond, An improved Representative Particle Monte Carlo method, A&A 670, A134 (2023)10.1051/0004-6361/202244955RPMC; the group’s current particle method. Main “other” MC baseline.to read [M8]
gurrutxaga_2026N. Gurrutxaga, V. Vaikundaraman, J. Drążkowska, A Monte Carlo method for tracking dust properties during coagulation in protoplanetary disks, A&A (accepted, 2026)arXiv:2604.24857Very recent particle MC with strict global conservation of tracked properties. Must be discussed in the paper.to read [M8]
drazkowska_2014J. Drążkowska, F. Windmark, C. Dullemond, Modeling dust growth in protoplanetary disks: the breakthrough case, A&A 567, A38 (2014); 10.1051/0004-6361/201423708Compares Smoluchowski integration and MC codes head-to-head.to read [M8]
matveev_2015S. A. Matveev, A. P. Smirnov, E. E. Tyrtyshnikov, A fast numerical method for the Cauchy problem for the Smoluchowski equation, J. Comput. Phys. 282, 23 (2015)Low-rank kernel + fast convolution, O(N log N). Deterministic competitor.to read [M8]
matveev_2016S. A. Matveev, D. A. Zheltkov, E. E. Tyrtyshnikov, A. P. Smirnov, Tensor train versus Monte Carlo for the multicomponent Smoluchowski coagulation equation, J. Comput. Phys. 316, 164 (2016)Closest prior art for the multi-property argument. TT beats MC in their tests; our paper must address this.to read [M8]
matveev_tretyak_2024S. A. Matveev, I. Tretyak, Nonnegative tensor train for the multicomponent Smoluchowski equation (2024)arXiv:2404.10898Positivity fix for TT.to read [M8]
tree_2026A fast tree algorithm for multi-component coagulation equation (2026)arXiv:2605.18353Recent deterministic fast multi-component method.to read [M8]
eibeck_wagner_2001A. Eibeck, W. Wagner, Stochastic particle approximations for Smoluchowski’s coagulation equation, Ann. Appl. Probab. 11(4), 1137 (2001); 10.1214/aoap/1015345398Mass flow algorithm; theory of stochastic approximations.to read [M8]

6. Context references from the thesis introduction (not needed for the paper)

andrews_2007, kitamura_2002, burton_2013, natta_2006, kant_1755, laplace_1799, newton_1687, courant_friedrichs_lewy_1928, schumann_1940, ALMA/ESO/Hubble image credits. Full entries: references.bib.

7. Local material that turned out not to be relevant

FileWhy not
Bani-Hashemian 2011, Accurate and Efficient Solution of the Smoluchowski EquationTreats the diffusion Smoluchowski equation (Brownian pair PDFs for reaction-diffusion simulations, GFRD), not the coagulation equation. Same name, different equation.
related/M.Sc. Thesis/Reading/Empty folder.
Oral exam scripts in ~/Documents/org/Projects/Education/Acquire Physics M.Sc. from UHD./Sem_13/M.Sc. Oral Exam/Reading/ (Simulation Methods, Computational Physics, Computational Astrophysics)General background; may help for ODE stiffness and MC variance chapters.

Glossary

Terms as used in this project. Where the thesis, Dullemond’s notes, and the code use different words for the same thing, all are listed and the project term is in bold. Symbols are defined in notation.md.

Astrophysical setting

TermMeaning
PPD (proto-planetary disk)Disk of gas (about 99 % by mass) and dust around a young star; birthplace of planets.
Midplanez = 0 plane of the disk; all thesis runs sit at r₀ = 10 au, z₀ = 0.
Dust-to-gas ratioM_dust / M_gas, canonical 0.01.
Irradiated (passive) diskTemperature set by stellar irradiation at flaring angle φ_f, not by accretion heating.
Pressure scale height H_pc_s / Ω_K; Gaussian vertical density profile width.
α viscosityShakura-Sunyaev turbulent viscosity ν_t = α c_s H_p. Distinct from molecular viscosity ν_mol = ½ u_th λ_mfp.
Stokes number StDimensionless stopping time t_s Ω_K; measures dust-gas coupling. Epstein midplane form: St = (π/2) a ρ_s / Σ_g.
Epstein / Stokes drag regimeGas drag laws for a < (9/4) λ_mfp and larger particles. The thesis uses Epstein only.
Radial driftInward migration of dust due to the sub-Keplerian gas; peaks at St = 1.
Azimuthal driftRelative azimuthal velocity of differently coupled grains.
Differential settlingRelative vertical velocity from different settling speeds; disabled in the thesis (dv_DS marked broken).
Brownian motionThermal relative velocities, dominant for the smallest grains.
Turbulent relative velocityOrmel and Cuzzi (2007) closed-form regimes (tightly coupled, intermediate, heavy).
Fragmentation barrierGrowth limit where Δv reaches v_frag.
Bouncing barrierGrowth limit where collisions bounce instead of sticking (Dominik and Dullemond 2024).
Drift barrierGrowth limit where drift removes grains faster than they grow.
MonomerSmallest constituent grain; lower end of the mass grid.
Filling factor φ / porosity p = 1 − φFraction of an aggregate’s equivalent sphere filled by material. The natural second particle property.
BPCA / BCCABallistic particle-cluster / cluster-cluster aggregation; produce compact-ish vs fractal aggregates.
MRN distributionMathis-Rumpl-Nordsieck size distribution n(a) ∝ a^-3.5, i.e. n(m) ∝ m^-11/6. Used as fragment distribution.

Collision physics

TermMeaning
Collision rate coefficient R^coll_ij (thesis), C_ij (coag_py)σ_ij Δv_ij in m³ s⁻¹. Not a rate: multiply by N_i N_j for events per volume per time.
Collision cross section σ_ijπ (a_i + a_j)² for compact spheres.
Collision outcomeSticking (coagulation, hit-and-stick), bouncing, fragmentation, plus sub-types: pulverization (all mass to the first bin), shattering (power-law fragments), cratering / erosion (small projectile excavates a big target), bouncing with mass loss, mass transfer.
Outcome probabilities P^coag, P^bounce, P^fragSum to one per pair. Hard threshold or smooth Maxwell-Boltzmann form (Stammler and Birnstiel 2022).
Reaction rate coefficient R^coag_ij, R^frag_ijR^coll_ij times the outcome probability.
Fragment distribution function f(m)Mass distribution of fragments per collision, normalized so that ∫ m f dm = m_i + m_j (or per body for independent shattering).

Smoluchowski equation and discretization

TermMeaning
Smoluchowski coagulation equationIntegro-differential population balance ∂n/∂t = ∫∫ K(m,m’,m’‘) n(m’) n(m’‘) dm’ dm’’. Not to be confused with the Smoluchowski diffusion equation (see literature section 7).
Mass distribution function n(m)Number density per unit mass per unit volume.
Bin population N_kParticles per unit volume in bin k: N_k ≈ n(m_k) Δm_k. The state variable of the solver.
Bin mass density ρ_kN_k m_k. Plot quantity “mass per bin”.
m²f(m) plotMass per logarithmic mass interval; the standard figure axis in coag_py.
Kernel function K(m, m’, m’’)Continuous gain-minus-loss rate density.
Kernel tensor K_kijDiscrete kernel, K(m_k, m_i, m_j) Δm_k, so that dN_k/dt = Σ_ij K_kij N_i N_j.
Gain / lossPositive / negative parts of K; loss is always -R_ij on k = i and k = j.
Collision recipeProject term: for one pair (i, j), the list of (k, weight) entries that a single collision adds to and removes from. The sparse unit from which K_kij, sampled kernels, and Jacobians are assembled (ADR 0005).
Kovetz and Olund algorithmSplits m_i + m_j between bins k̄ and k̄+1 with weights (1-ε, ε) so particle number and mass are both conserved. Also “Podolak algorithm” (Brauer 2008).
Reverse Kovetz and OlundSame mapping for the surviving remnant after cratering or erosion.
Near-zero cancellation handling (fixcancel)For k̄ = max(i, j), replace (1−ε)·R − R by −ε·R analytically to avoid catastrophic cancellation when m_i ≪ m_j.
Detailed balance (Dullemond’s sense)Every removal of a colliding pair is matched exactly by the insertion of its products, so mass is conserved per collision to round-off. Not the thermodynamic meaning.
Kernel mass error ΔX^relPer-pair relative mass defect Σ_k m_k X_kij / (m_i + m_j), aggregated by RMS over pairs.
Upper grid boundary policyWhat to do when m_i + m_j > m_max: drop the pair (thesis), pile up in the last bin, or let mass leave the grid and account for it.
Kernel sparsityFraction of non-zero K_kij: about 3 % for hit-and-stick, about 30 % with fragmentation (thesis, N_m = 100).
Analytic test kernelsConstant, linear (sum), product kernels with known solutions; product kernel shows gelation.
GelationFinite-time mass loss to an infinite cluster for strongly growing kernels.

Time integration

TermMeaning
StiffnessWide spread of time scales (small grains react fast, big ones slowly); forces implicit methods for long integrations.
Explicit EulerN^{n+1} = N^n + Δt F(N^n); CFL-like step limit.
CFL condition (coagulation sense)Explicit step bound Δt < min_k N_k / |loss_k| type criteria (Laibe and Lombart 2021).
Backward (implicit) EulerSolve N^{n+1} = N^n + Δt F(N^{n+1}) by Newton iteration (coag_py backwardeuler, niter).
Linearized implicit stepOne Newton step: (I − Δt J) δ = Δt F(N^n) (Brauer 2008, App. C).
Rosenbrock / W-methodLinearly implicit Runge-Kutta; W-methods stay accurate with an approximate Jacobian, which matters for sampled kernels.
Radau IIAFully implicit collocation Runge-Kutta, 3 stages, order 5, L-stable; RADAU5 by Hairer and Wanner; scipy Radau. The thesis text says “4th order”; the method used was order 5.
Simplified NewtonNewton iteration with a frozen Jacobian across iterations and steps; standard in RADAU5.
Analytic JacobianJ_kl = Σ_j (K_klj + K_kjl) N_j for the quadratic RHS; exact and cheap to assemble from recipes.
Macro step / output stepStep of the logarithmic output time grid (200 steps over 3·10¹⁰ s in the thesis).
SubstepIntegrator steps within one macro step (adaptive in scipy, nsubst in coag_py).
Steady state / equilibriumCoagulation-fragmentation balance where dN/dt → 0; reached after a few hundred years at 10 au in the thesis model.

Monte Carlo kernel sampling

TermMeaning
Kernel samplingEstimating Σ_ij K_kij N_i N_j from a random subset of pairs (i, j).
Pair sampling vs index samplingDraw pairs (i, j) jointly from p_ij, or i and j independently from a 1D distribution F_i (Dullemond’s sample2d). The thesis samples pairs.
1D samplingDullemond’s sample2d=False: sample j only and sum over i exactly, exploiting hit-and-stick sparsity.
Sampling weight W_i (Dullemond)Arbitrary positive weight defining the proposal F_i = N_i W_i / S. W = m emphasizes mass carriers.
Proposal / importance distribution p_ijProbability of drawing pair (i, j).
Importance sampling (IS) estimatorF̂_k = (1/S) Σ_s K_k,ij N_i N_j / p_ij over S draws with replacement. Unbiased. Dullemond Eq. 1.70.
Horvitz-Thompson (HT) estimatorUnbiased estimator for sampling without replacement: weight each included pair by 1/π_ij, π_ij its inclusion probability.
Thesis estimatorWithout replacement, no reweighting: E[F̂] = Σ π_ij K N N < F. Biased low, explains the “slower evolution” the thesis observed. See thesis review.
Implicit sampled formDullemond Eq. 1.71: divide the sampled kernel by N_i⁽⁰⁾ N_j⁽⁰⁾ at the start of the step and multiply by the unknown future N_i N_j, giving a sparse quadratic kernel usable by implicit solvers.
Sampling density ρ_sampleThesis parameter: fraction of the N_m² pairs drawn per step.
Number of samples S (Dullemond N_ran)Draws per step.
Frozen sampleSame sample used for all stages and Newton iterations of one step (keeps the step a deterministic ODE step).
Variance reductionTechniques to lower estimator variance at fixed cost: optimal proposal, stratification, control variates, hybrid / split sum (exact sum over dominant pairs plus sampled tail), common random numbers, antithetic draws, systematic resampling.
Noise floorResidual RMS of dN/dt at steady state caused by MC noise (thesis Fig. 107).
Work-normalized efficiency1 / (error² × cost). The fair figure of merit for comparing estimators.

Other Monte Carlo families (for positioning)

TermMeaning
DSMC (direct simulation MC)Simulates individual collision events between computational particles (Ormel 2007).
RPMC (representative particle MC)Tracks a few representative particles interacting with a swarm (Zsom and Dullemond 2008, Beutel and Dullemond 2023). Explicit in time by construction.
Mass flow algorithmStochastic particle method with weights for the Smoluchowski equation (Eibeck and Wagner 2001).
Tensor train (TT)Low-rank tensor format; deterministic fast solver for multi-component Smoluchowski (Matveev et al.).
Discontinuous Galerkin (DG)High-order polynomial representation per bin (Lombart and Laibe 2021).

Software terms (this project)

TermMeaning
ADRArchitecture Decision Record in doc/adr.
Milestone Mn / task Mn.mUnits of the roadmap.
Experiment EnNumbered study in 04-experiments.md; each has a config file and a figure.
ToggleBoolean or enum config switch for a physical component (e.g. relative_velocity.brownian = true).
Run manifestMetadata file stored with every output: config hash, git revision, RNG seed, timings.
Parity testTest that our Rust result matches the thesis Python or coag_py result to a stated tolerance.
OracleIndependent trusted result (analytic solution, scipy, coag_py) used in tests.

Notation

One symbol table for the project, with the equivalents in the thesis, in Dullemond’s notes and coag_py, and the identifier planned for the Rust code. Units are SI (ADR 0003).

Indices

ProjectThesisDullemondRustMeaning
i, ji, ji, j (or i_l, j_l for samples)i, jColliding bins. With several properties, a flattened multi-index (ADR 0004).
kkkkReceiving bin.
k̄k̄k̄k_lowBin with m_k̄ ≤ m_i + m_j < m_k̄+1.
slsSample index, s = 1..S.
nm (time step, clashes with mass)stepTime step index.

Grids

ProjectThesisDullemond / coag_pyRustUnitMeaning
N_m𝒩_mN_m, nmn_massMass bins.
N_φ𝒩_pN_φn_fillFilling factor bins (M9).
m_km_k^cmgrain[k]mass.center(k)kgBin center (geometric mean on log grid).
m_{k±1/2}m_k^bmigrainmass.edge(k)kgBin edges.
Δm_kΔm_kdmgrainmass.width(k)kgBin width.
q_mmass.ratio()m_{k+1}/m_k; must be < 2 for cancellation handling (coag_py assert).
r, Δrr, 𝒩_rradiusmRadial grid.
t_n, Δtt, Δttime, dtt, dtsTime.

Disk and gas

SymbolThesis value / definitionUnit
M_, L_M_⊙, L_⊙kg, W
q_diskM_disk/M_* = 0.01
ε_dgM_dust/M_gas = 0.01
μ, m_g2.3, μ m_p, kg
φ_fflaring angle 0.05rad
T_mid(r)(φ_f/2 · L_*/(4π r² σ_SB))^{1/4}K
c_ssqrt(k_B T / m_g)m s⁻¹
Ω_Ksqrt(G M_*/r³)s⁻¹
H_pc_s/Ω_Km
Σ_g(r)Σ₀ r^{-σ}, σ = 0.8 (thesis), 1 (diskmodel.py)kg m⁻²
ρ_g^midΣ_g / (sqrt(2π) H_p)kg m⁻³
u_thsqrt(8/π) c_sm s⁻¹
λ_mfp1/(n_g σ_H₂), σ_H₂ = 2·10⁻¹⁹ m²m
ν_mol½ u_th λ_mfpm² s⁻¹
ν_tα c_s H_p, α = 10⁻³m² s⁻¹
Reα c_s H_p / ν_mol
u_g-3/(Σ_g sqrt r) ∂_r(Σ_g ν_t sqrt r) (must use ν_t, see thesis review F6)m s⁻¹
u_n-E_d ∂_r P_g / (2 ρ_g Ω_K), E_d = 1m s⁻¹
v_tsqrt(α) c_s (turbulent velocity of largest eddies)m s⁻¹

Dust

ProjectThesiscoag_pyUnitMeaning
ρ_sρ_s = 1600rhosolidkg m⁻³Material density.
a_ka_kmRadius, (3 m / (4π ρ_s φ))^{1/3}.
St_kSt_its*omkStokes number.
Δv_ijΔv_ijdvm s⁻¹Total relative velocity, RMS of components BR, RD, AZ, TU, (DS).
v_frag1m s⁻¹Fragmentation threshold.
v_bouncem s⁻¹Bouncing threshold (M3).

Rates, kernels, estimators

ProjectThesiscoag_py / notesUnitMeaning
C_ijR^coll_ijCij, C(m₁,m₂)m³ s⁻¹Collision rate coefficient σ_ij Δv_ij.
P^x_ijP^x_ijOutcome probability, x ∈ {coag, bounce, frag}.
R^x_ijR^x_ijm³ s⁻¹C_ij P^x_ij.
X_kijX_kijPer-collision change in N_k (recipe weights).
K_kijK_kijKkijm³ s⁻¹Σ_x R^x_ij X^x_kij (symmetrized in i, j).
F_k(N)∂N_k/∂tcompute_kernel_summ⁻³ s⁻¹Σ_ij K_kij N_i N_j.
J_klmatrix='J's⁻¹∂F_k/∂N_l.
p_ijP^sample_ijF_i F_jPair proposal probability.
π_ijInclusion probability (sampling without replacement).
Sρ_sample N_m²NranNumber of draws.
F̂_kEq. 1.70m⁻³ s⁻¹MC estimate of F_k.
ΔM^net_ijΔM^net_ijkg m³ s⁻¹Σ_k m_k |K_kij|, mass throughput of pair (i, j).

Diagnostics

SymbolDefinitionMeaning
δ_M(t)(Σ m_k N_k(t) − Σ m_k N_k(0)) / Σ m_k N_k(0)Global mass error (thesis Δ_stab, chapter 3 definition).
ΔX^relsqrt(Σ_ij (Σ_k m_k X_kij/(m_i+m_j))²)Kernel mass error.
e_acc(t)mass-weighted error vs reference, see verificationReplaces the thesis Δ_acc, which divides by zero where the reference bin is empty.
B_k, V_kE[F̂_k] − F_k, Var[F̂_k]Estimator bias and variance.
η1/(e² · cost)Work-normalized efficiency.