JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Reproducibility: Seeds, Versions, Provenance

A result you cannot reproduce is a rumour, not a finding. This guide shows how three humble habits — pinning your library versions, recording your random seed, and keeping a provenance trail from raw input to final number — turn a one-off run into a result anyone (including future you) can rebuild bit for bit.

Why a number alone is not evidence

The earlier guides in this rung taught you to do the computation right. You learned to lean on the tested numerical software stack instead of rolling your own; to separate verification (am I solving the equations right?) from validation (am I solving the right equations?) via verification versus validation; and to prove your code's order with a convergence study and the method of manufactured solutions. This guide adds the habit that makes all of that count: making a result you can get back. A correct answer you cannot reproduce six months later is, for the purposes of science, no answer at all.

Reproducibility, the computational reproducibility standard, means: given your code, your inputs, and your recorded environment, someone else can run the pipeline and get the same numbers you reported — ideally bit for bit, at least within a stated tolerance. It sounds obvious, yet it is shockingly easy to lose. A figure in a paper depends on a script, which depends on twenty libraries at particular versions, which depend on a compiler and a chip, plus a stream of "random" numbers and a handful of paths to data files. Change any one silently and your reproduction drifts — or simply crashes. The fix is not heroics; it is three plain habits.

Those three habits map to the title. Seeds make the random parts repeatable. Versions pin down the software and hardware so the deterministic parts compute the same way. Provenance records the chain from raw input to final figure so the whole thing can be replayed and audited. Keep them in mind as a trio: a result is reproducible exactly when all three are nailed down. Lose any one and you are trusting luck.

Seeds: pinning down the 'random'

Start with the part that frightens beginners: how can a Monte Carlo run, full of randomness, ever be reproducible? Because the randomness is fake. A computer's pseudorandom number generator is a deterministic recipe: it holds an internal state, and each call mixes that state by a fixed arithmetic rule and hands you a number that merely looks random. The whole sequence is decided the instant you choose the starting state, called the seed. Set the seed to 42 and you get one fixed stream; set it to 42 again tomorrow and you get the very same stream, in the same order, forever.

# Same seed -> identical stream, every run, every machine (same generator)
seed = 42
rng  = Generator(seed)
rng.random()  -> 0.6394267984...
rng.random()  -> 0.0250108004...
rng.random()  -> 0.2750929132...

# No seed set -> library seeds from the clock / OS entropy:
# the stream changes every run, and the run is NOT reproducible.

# A linear congruential generator, the simplest PRNG, is literally:
#   state_{n+1} = (a * state_n + c) mod m     (deterministic arithmetic)
A seeded generator is a deterministic function of its seed; the same seed replays the same 'random' numbers. Leaving the seed unset is what makes a run non-reproducible.

So the rule is simple: record the seed you used, and set it explicitly at the top of the run. The classic generators make this concrete — a linear congruential generator is just state_{n+1} = (a*state_n + c) mod m, while the widely used Mersenne Twister keeps a larger state with a vastly longer period. Both are fully determined by their seed. A subtle trap: do not seed once and reuse the SAME seed for many supposedly independent trials — you would replay one identical stream and fool yourself with fake repetition. Pick one seed for the whole experiment, log it, and let the generator march forward.

Versions: same code, same answer?

You seeded everything, yet a colleague runs your script and gets a slightly different number. Usually nothing is broken — the software underneath changed. Your code sits on a tall stack: an interpreter, the numerical libraries, the BLAS/LAPACK kernels beneath them, the compiler that built those, and the very chip. Bump any layer and a computation can shift, because floating-point arithmetic is not real arithmetic. Floating-point addition is not associative: (a + b) + c can differ from a + (b + c) in the last bits. A library update that reorders a sum, or a faster CPU that uses a wider fused instruction, changes those last bits — small, but enough to break a bit-for-bit match.

That is why versions are part of reproducibility, not an afterthought. The remedy is to pin: write down the exact version of every package your run depends on, and the means to recreate that set — a lockfile, a container image, a recorded environment. Then "reproduce" means "rebuild this exact stack, then run." Honesty matters here: bit-for-bit reproducibility across different chips and compilers is often unrealistic for floating-point work, and that is fine. The achievable, useful goal is reproducibility within a stated tolerance — the same answer up to a known number of digits — plus a recorded environment that makes EXACT reproduction possible for anyone who rebuilds the same stack.

How do you catch a version drift before it silently corrupts a result? With a regression test. The idea from regression testing is to lock in a known-good output and check, automatically, that future runs still match it within tolerance. Save the answer your verified code produces today — a small reference value, ideally one you trust because a manufactured-solution check confirmed the method's order — and let a test re-run it after every change to the code or the stack. If a library upgrade nudges the answer past the tolerance, the test goes red and you investigate on purpose, instead of discovering the drift in a published figure.

Provenance: the chain from input to figure

Seeds and versions pin the run; provenance records the whole story around it. Provenance is the chain of custody for a number: which exact input data went in, which version of which script processed it, with what parameters and what seed, on what date, producing which output and which figure. The gold standard is that every plot in a report can be traced back, step by step, to the raw bytes it came from — and regenerated from them by one command. If you cannot say where a curve came from, you cannot defend it, and you certainly cannot rebuild it after the laptop that made it is gone.

The everyday tool that does most of this is version control, almost always git. Committing your code gives every state a unique fingerprint (a commit hash), so "the script that made Figure 3" becomes a precise, immutable reference rather than "final_v2_REALfinal.py". Stamp each output with the commit hash that produced it, the parameters, and the seed, and you have provenance for free. Two cautions, honestly stated: version control is for code and small configuration, not for gigabytes of raw data (use a data store plus a recorded checksum so you can prove the bytes never changed), and a result is only as reproducible as its messiest manual step. Every undocumented click is a hole in the chain.

  1. Put the code under version control and commit a clean state; note the commit hash for this run.
  2. Pin the environment: record exact library versions in a lockfile or container so the stack can be rebuilt.
  3. Set and log an explicit random seed at the top of the run, so the pseudorandom stream is fixed.
  4. Record the raw input together with a checksum, and the exact parameters passed in.
  5. Stamp each output and figure with the hash, seed, and parameters that produced it.
  6. Add a regression test that re-runs the pipeline and checks the output stays within tolerance.

What reproducibility does and does not buy you

Be clear-eyed about the boundary of this habit, because it is easy to oversell. Reproducibility guarantees that you can get the SAME number again; it says nothing about whether that number is RIGHT. A buggy script run from a fixed seed on a pinned stack reproduces its bug perfectly, forever. This is exactly why the earlier guides come first: verification (manufactured solutions, convergence studies) and validation (does it match reality?) judge correctness, while reproducibility merely guarantees repeatability. They are partners, not substitutes — reproducibility is what makes a verification claim checkable by someone other than you.

There is also a deeper humility under all of this. Even a perfectly reproduced, perfectly verified result is still approximate, because every numerical answer is. The floating-point floor, the discretization error you measured in your convergence study, the O(1/sqrt(N)) Monte Carlo noise tamed by your seeds — none of those vanish just because the run repeats. Reproducibility makes your approximation honest and inspectable; it does not make it exact. The mature stance is to report the number, the environment that made it, and the error estimate that bounds it — together.

That last phrase is the bridge to the final guide of this rung. We have made the run repeatable; next we ask how much to trust the number it produces. Uncertainty quantification, sensitivity analysis, and honest error bars take a reproducible pipeline and wrap it in a quantified statement of doubt — and for inverse problems, where small data wobbles can blow up the answer, regularization keeps that doubt from exploding. Reproducibility is the foundation that makes those error bars meaningful: a confidence interval is only credible if the computation behind it can be re-run and checked.