How the arithmetic is checked
Every number this site computes is checked, automatically, every time the code changes. "Pinned" means the answer is written down in a test beside the code: if the code ever stops producing that answer, the change does not ship. This page lists what is written down, what it was checked against, and how close the two have to be — including the places where the check is weaker than it looks.
- The first column is what was compared with what. Where the reference is another program it is named; where it is a field whose answer is known in closed form, it says so.
- The middle column is the bound an automated test enforces. It is the real strength of each claim, and it is stated even where it is embarrassing — one bound on this page is a factor of three, and it says so rather than being left out.
- The last column is what would slip through if that check were deleted. It is there because a tolerance on its own tells you nothing about which mistakes it would catch.
- Under every comparison are the test files that assert it, by path. Those files are in the repository, and a build in which one of them stops containing the assertion named here fails rather than quietly going out of date.
Interpolation
Two ways of turning wells into a surface, both checked against arithmetic done somewhere else. The kriging numbers are pinned against two independent implementations at once, and the reference values they are pinned to are written by a script committed beside them rather than typed in by hand.
Inverse distance weighting, against the same formula in NumPy
- Method.
- Three synthetic well sets are interpolated at a grid of query points and at every well itself, and each value is compared with the same closed form evaluated in NumPy over the same double-precision numbers.
- Reference.
- NumPy, driven by the generator committed at src/engine/__tests__/fixtures/make_fixtures.py. There is no published worked example for an inverse-distance surface, and this does not pretend there is one: the reference is a second implementation of a formula both sides agree on.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Every interpolated value, against the NumPy evaluation | 1e-12 relative | a wrong power, a wrong neighbour set, or a missing normalisation |
| A query sitting exactly on a well | that well’s own value, exactly | the exact-hit rule quietly becoming an average |
| The weights anywhere away from a well | never negative, and summing to 1 to 12 decimal places | a surface no longer bounded by the readings that made it |
| The two nearest wells’ share of the weight | their distance ratio raised to the power, to 8 decimal places | the power applied to the wrong quantity |
| A query with no well inside the search radius | not a number, exactly as the reference reports it | an empty neighbourhood filled in with something invented |
- Agreement here says the formula was transcribed correctly. It says nothing about whether inverse distance weighting is the right choice for a water table, which is a judgement about the site and not about the code.
Asserted by src/engine/__tests__/idw.test.ts
Ordinary kriging, against PyKrige and against an independent solve
- Method.
- The same wells, the same variogram parameters and the same query points go through three different pieces of algebra — an explicit matrix inverse, a from-scratch solve, and this engine’s factorisation of the covariance form — and the three sets of answers are compared with each other.
- Reference.
- PyKrige 1.7.3, OrdinaryKriging, which forms an explicit inverse of the indefinite bordered system. · A from-scratch NumPy solve of the bordered system, written for this repository and run in the same script.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Every estimate, against both references | 1e-9 relative | a sign error on the Lagrange multiplier, which moves an answer by about a hundredth |
| Every kriging variance | 1e-9 times the sill, as an absolute bound | a variance that is right at the wells and wrong between them |
| The estimate at a well, with a nugget and without one | that well’s own value, to 1e-9 relative | an interpolator that has stopped honouring its own data |
| The weights at any query point | summing to 1 to 10 decimal places | the unbiasedness constraint dropped somewhere in the solve |
| Kriging asked for with fewer than 8 wells | refused in plain words, pointing at inverse distance weighting | a variogram fitted to almost nothing, presented as a surface |
- The three implementations are algebraically identical and numerically distinct, so this checks the transcription and the solve. All three would agree on the same wrong model, and none of them knows whether the variogram describes your aquifer.
- Kriging here is global and isotropic. Every well enters every node’s system, and there is no anisotropy anywhere in the engine, so a site with a strong directional structure is not being modelled as one.
Asserted by src/engine/__tests__/kriging.test.ts
The reference values themselves, and where they come from
- Method.
- No reference value in the suite was written from memory: one committed Python script produces all of them, cross-checks the two kriging implementations against each other before writing anything, and refuses to write at all if they disagree.
- Reference.
- src/engine/__tests__/fixtures/make_fixtures.py, committed beside the reference file it wrote.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| PyKrige against the from-scratch solve, before any value is written | 1e-8 relative, or the generator writes nothing at all | a reference file that is wrong in exactly the same way as the code |
| Whether PyKrige was available on the run that wrote the file | recorded in the file, and asserted true by the suite | a page claiming two references while pinning against one |
| The three well sets | drawn from the fixed seeds 20260815, 7771 and 31337 | fixtures that change under you, and a suite nobody can re-derive |
- That guard is not decorative. It fired on the first run and caught a real defect: PyKrige’s list form of the variogram parameters subtracts the nugget from the sill, which its own docstring denies, and a five per cent error was already sitting in the fixtures when the cross-check refused them. The generator now passes a dictionary and asserts what was stored.
Asserted by src/engine/__tests__/fixtures/make_fixtures.py, src/engine/__tests__/kriging.test.ts
Fitting the variogram
The fitted variogram is the artefact that goes in the appendix, so it is checked in three separate places — the model functions, the binning, and the fit — and the one place this engine deliberately does not match PyKrige is published as a measured divergence rather than hidden inside a generous tolerance.
The model functions, and the range convention
- Method.
- Spherical, exponential and gaussian semivariances are evaluated over a spread of separations and compared with the same expressions written in NumPy.
- Reference.
- PyKrige’s variogram_models.py, read rather than remembered. The range is the PRACTICAL range, so the exponential model uses a third of it as its length scale and the gaussian four sevenths — which is the convention this engine follows.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Every model value, against the NumPy expression | 1e-14 relative | a range convention taken from a textbook instead — an error of several per cent that still looks plausible on a plot |
| Semivariance at zero separation, with a nugget in the model | exactly 0 | kriging quietly ceasing to be an exact interpolator |
| Every model at the practical range | above 0.94 of the sill | a range that means a different distance in each model |
| The spherical model at its range | exactly the sill | the cap applied in the wrong place |
- A tolerance this tight is not a claim about precision. Both sides evaluate the same elementary expression, so anything but the last bit or two would be a mistake — and the mistake this row exists to catch is the length-scale convention, which is worth several per cent and would sail through a loose bound.
Asserted by src/engine/__tests__/variogram.test.ts
The empirical variogram, binned the way PyKrige bins it
- Method.
- Pair distances are binned twice — once with this engine’s own defaults, and once with a helper that reproduces PyKrige’s bin edges exactly — and the lag, the semivariance and the pair count of every bin are compared.
- Reference.
- PyKrige’s _initialize_variogram_model, read for the binning rules: equal-width bins, and a reported lag that is the mean pair distance in the bin rather than the bin centre.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Lag and semivariance, bin for bin | 1e-13 relative | a binning or estimator mismatch — bin centre for mean lag, or a robust estimator for the plain one |
| The number of pairs in each bin | equal bin for bin, exactly | a pair falling out of a bin on a last-bit difference |
| The automatic lag count | 5 bins for 10 pairs, 10 for 300, 15 for 100000 | bins holding too few pairs to mean anything |
- The closest pair used to fall out of the first bin, because that bin edge sits exactly on it and the two implementations round the distance differently in the last bit. The outer edges are now widened by a relative epsilon. The defect was real, and it is why the pair counts are asserted rather than assumed.
Asserted by src/engine/__tests__/variogram.test.ts
The fit, against SciPy attacking this engine’s own objective
- Method.
- For every combination of well set, binning and model, SciPy’s trust-region least squares is run on the same weighted residual over the same feasible set, and both the objective value and the fitted parameters are compared with what this engine found.
- Reference.
- SciPy least_squares with a linear loss, run in the fixture generator on the residual this engine minimises. An independent optimiser attacking OUR objective, which is a different and weaker claim than reproducing somebody else’s fit.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| The weighted sum of squares both optimisers minimise | 2e-6 relative | a convention or algebra error, which moves this by orders of magnitude |
| Our minimum against SciPy’s | never worse by more than 2e-6 | a search that stops early and calls it an answer |
| Nugget, partial sill and range | 1e-3 relative, scaled by the sill | a fit landing in a different basin altogether |
| A model fitted to bins it generated itself | nugget 0.4, partial sill 3.0 and range 250 recovered | a fitter that cannot find an answer it was handed |
- The parameters are pinned an order looser than the objective, and that is deliberate rather than a concession. In a flat basin the argument of the minimum moves much further than the minimum does, so the objective value is the load-bearing assertion and the parameters are a sanity bound on it.
Asserted by src/engine/__tests__/variogram.test.ts
Where the fit deliberately does not match PyKrige
- Method.
- PyKrige fits its variogram with a robust loss and this engine fits plain weighted least squares, so the two are minimising different things and their fitted parameters genuinely differ; the suite therefore measures the gap instead of asserting it away.
- Reference.
- PyKrige’s _calculate_variogram_model, which passes a soft-L1 loss to SciPy. Read in the source, because the difference it makes is the whole reason this check exists.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Our total sill against PyKrige’s | between 0.5 and 2.0 times it | a fit that has stopped being about the same data |
| Our range against PyKrige’s | between 0.33 and 3.0 times it | a length scale that no longer describes the site |
- This is the loosest bound on the page and it is the honest one. A monitor is not an equality claim: no tolerance would make two different objectives agree, and widening one until they did would be this page lying about what was checked.
- The closed-form step is what the choice buys. Keeping the objective linear in the nugget and the partial sill at a fixed range reduces fitting to a one-dimensional search with no optimiser, no seed and no dependency — which is also why the same file always fits the same variogram.
Asserted by src/engine/__tests__/variogram.test.ts
Drawing the contours
Marching squares is not pinned against another implementation at all. It is checked against fields whose isolines are known in closed form — a plane, a cone, a single ambiguous cell — with the closed form computed inside the test rather than remembered. There is nothing to transcribe, so there is nothing to transcribe wrongly.
Planes, where the isoline is a straight line
- Method.
- A plane is sampled onto a grid and contoured; because interpolating a linear field along a grid edge is exact, every vertex must satisfy the line equation to machine precision.
- Reference.
- No published source, and none is needed. The reference is the equation of the field, evaluated in the test beside the assertion.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Every vertex of a vertical isoline | within 1e-12 of x = 5 | an interpolation along the wrong edge, or a wrong crossing parameter |
| Every vertex of a sloped plane, at three levels | on the exact line to 1e-12 | the same defect in a case where a sign error would otherwise cancel |
| The direction each line is walked | the high side always on the left | contour labels and fills ending up on the wrong side of the line |
| A level above every value, and one below | no lines at all | phantom contours outside the range of the data |
Asserted by src/engine/__tests__/contours.test.ts
Cones, where the isoline is a circle of known radius
- Method.
- A cone is contoured at a radius the test chose, and the vertices are measured against that radius; because interpolating a curved field is not exact, the assertion is on how the error shrinks rather than on how big it is.
- Reference.
- No published source. The reference is the radius of the circle, which is an input to the test.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| The worst radius error on a grid 101 nodes a side | under 0.01 | a crossing computed on the wrong edge of a cell |
| Halving the cell size | cuts that error by more than 3.2 times | a method that is not second order, which no absolute bound would reveal |
| The area enclosed by the ring | within 1e-3 of the circle’s own area | segments assembled into the wrong ring |
- The convergence row is the strong one. It needs no remembered constant, and a first-order defect fails it while a generous absolute bound would let it through.
Asserted by src/engine/__tests__/contours.test.ts
Saddles, where the answer is a choice and the choice is stated
- Method.
- The two ambiguous marching-squares cases are built as single cells, and the resulting segments are asserted coordinate by coordinate for the default rule and for each of the three overrides.
- Reference.
- No published source. The crossing parameters are worked out in the test’s own comment, from the corner values, and asserted against that working.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| A cell whose centre the default rule calls high | two named segments, to 3 decimal places | a saddle joined the wrong way, which merges two highs into one |
| A cell whose centre it calls low | crossings at a third and two thirds of each edge, to 6 decimal places | the same defect in the opposite direction |
| The asymptotic rule against the corner mean | a different topology, each matching its forced option | an option that is documented and does nothing |
- Marching squares skips any cell with a missing corner, so an isoline stops at the edge of the masked region rather than being clipped exactly to the hull polygon. The difference is drawn honestly — the fringe is hatched, legended and named in the method note — but it is not sub-cell clipping and this page will not call it that.
Asserted by src/engine/__tests__/contours.test.ts
The hydraulic gradient
The three-point problem is the one calculation on this site whose answer a reader can work out on paper, so it is asserted against the closed form as well as against an independent solve. Everything else about flow direction is checked against the analytic gradient of a field the test built.
The three-point problem, against a closed form worked by hand
- Method.
- Three wells are placed so that the fitted plane has an exact answer in binary floating point, and the gradient, the flow direction and the azimuth are asserted against that answer rather than against a stored number.
- Reference.
- No published source. The closed form is worked out in the test’s own comment, from the three heads, and the same cases are also compared with a NumPy solve of the same system.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| A(0, 0, 100), B(100, 0, 99), C(0, 100, 100) | gradient exactly 0.01 and azimuth exactly 90, to 15 and 12 places | a transposed system, or a gradient reported pointing uphill |
| A(0, 0, 100), B(100, 0, 98), C(0, 100, 96) | azimuth exactly the arctangent of a half, to 12 places | an azimuth measured from the wrong axis |
| The same three heads put back through the fitted plane | each one recovered to 9 decimal places | a plane that does not pass through its own data |
| Three wells on a straight line | refused, with no jargon in the message | a determinant near zero turned into a confident answer |
Asserted by src/engine/__tests__/flow.test.ts
The least-squares plane, and the gradient of a surface
- Method.
- The plane fit is checked against the three-point answer where three wells make them the same problem, against a plane it was built from, and against the analytic gradient of a planar head field sampled onto a grid.
- Reference.
- No published source. Every reference here is the field the test generated, differentiated in closed form.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| The plane fit through exactly three wells | the three-point answer, to 10 decimal places | two calculations of the same thing disagreeing on one sheet |
| Thirty noiseless wells on a known plane | slopes of −0.03 and 0.01 recovered to 10 places | a normal-equations error that averages itself away |
| Central differences over a linear surface | −0.02 and 0.005 at every node, to 12 places | an edge case at the grid boundary or beside a hole |
| The gradient read off a planar head field | the analytic magnitude to 12 places, the azimuth to 9 | cancellation in the differencing, unnoticed |
| Compass bearings | 0 north, 90 east, 180 south, 270 west | a figure whose flow arrows point the wrong way |
- Azimuths are reported as the direction water flows, not the direction the gradient vector points. Those are opposite, and a sheet that had them the wrong way round would look entirely reasonable to anybody who had not checked.
Asserted by src/engine/__tests__/flow.test.ts
The same file, twice
A figure that has to be defended two years later has to be reproducible two years later. The whole pipeline is run twice and compared as raw bytes, and every engine file is scanned for anything that could make it answer differently on a different day.
The same wells, the same figure
- Method.
- The default pipeline — grid, interpolation, clip, levels, contours, labels, arrows, gradient — is run twice over the same wells by both methods, and the grid buffers are compared byte for byte along with the serialised geometry.
- Reference.
- No published source. The reference is the previous run, which is the only reference a reproducibility claim can have.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| The interpolated grid and the kriging variances | identical, compared as raw bytes | iteration over a set, or a hash order leaking into a number |
| Contours, labels, flow arrows and the gradient readout | identical, compared as serialised text | a tie broken differently on the second run |
| Flow arrows from a given seed | the same arrows, from seed 1 at a target of 40 | a figure whose arrows move when nothing else did |
| Every engine source file | no clock, and no unseeded random source | a figure nobody can reproduce next quarter |
| Every import inside the engine | relative — nothing at all from outside the folder | the engine acquiring an opinion about the browser it is in |
- This is a claim about one machine and one build. It is what lets a reviewer re-run a quarterly figure and get the same drawing; it is not a claim that two different browsers produce identical bytes, which nobody here has measured.
Asserted by src/engine/__tests__/determinism.test.ts
Stiff diagrams
Every arm of every polygon on a Stiff map is one multiplication: a laboratory result in milligrams per litre, times the ion’s charge, divided by its formula weight. So the whole figure rests on a short table of atomic weights and on getting that arithmetic the right way up, and both are checked against sources that printed them.
The conversion factors, derived from IUPAC’s own table rather than remembered
- Method.
- The nine atomic weights a major-ion suite needs are asserted against the published table one by one, each formula weight is summed from them in code rather than written down, and the equivalent weights that fall out are compared with the divisors an independent author printed beside their own analysis.
- Reference.
- IUPAC’s Commission on Isotopic Abundances and Atomic Weights, Standard Atomic Weights of the Elements 2021, as tabulated with the Technical Report in Pure and Applied Chemistry. The conventional value is used for the six elements IUPAC publishes as an interval, which is the value IUPAC provides for a material of unknown provenance. · Claude E. Boyd, Anion-cation balance in water, Global Seafood Alliance, 2002, for the equivalent weights: the article prints the divisor it used for each ion, so a disagreement can be attributed to one ion rather than to the method.
IUPAC — Standard Atomic Weights of the Elements 2021
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Each of the nine atomic weights | the published value exactly — sodium 22.98976928, calcium 40.078 | a table copied from a textbook of a different vintage, wrong in the third figure |
| Bicarbonate’s formula weight, summed from those weights | 61.016, to 10 decimal places | a wrong subscript, which is a whole arm’s worth of error on every diagram |
| Every equivalent weight, against the published divisors | within 0.005 relative of 20.04 for calcium, 12.16 for magnesium, 61 for bicarbonate | one ion converted at the wrong weight, on a figure whose content is the shape |
| One milliequivalent per litre of alkalinity as calcium carbonate | 50.043 milligrams per litre, to 3 decimal places | the number every water chemist would notice, quietly wrong |
| Alkalinity reported as calcium carbonate against alkalinity as bicarbonate | a factor of 1.22, to 2 decimal places | a middle anion arm 22 per cent out, and a balance broken on an analysis that was fine |
- The check that would catch the most damaging mistake is the second one, and it is worth saying why: sulfate written with three oxygens instead of four sums to 80.06, which is a plausible-looking number that draws every sulfate arm a fifth too long. Summing the formula in code from a checkable table is what makes that a test failure rather than a figure.
- Agreement here is about transcription. It says nothing about whether the laboratory result you fed in is right, and a Stiff diagram drawn from a bad analysis is a bad diagram drawn correctly.
Asserted by src/tools/stiff-map/__tests__/ions.test.ts
The whole conversion, against an analysis somebody else converted
- Method.
- A published major-ion analysis of a saline aquifer, reported in milligrams per litre and converted to milliequivalents in the article itself, is put through this tool’s conversion again — ion by ion, and then as both of the sums the author arrived at.
- Reference.
- Claude E. Boyd, Anion-cation balance in water, Global Seafood Alliance, 1 April 2002. Used rather than a tidier textbook table because it covers all seven ions this map reads, it states the divisor used for each one, and it is a real water rather than a set of round numbers.
Boyd (2002) — Anion-cation balance in water
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Each of the seven ions, against the printed meq/L | within 0.005, or half of the last digit the author printed | one ion converted wrongly, which is one short arm on every diagram on the map |
| The sum of the cations | 66.74 meq/L, to 1 decimal place | an error that cancels ion by ion and survives to the total |
| The sum of the anions | 66.29 meq/L, to 1 decimal place | the same, on the other side of the diagram |
| The charge balance those two sums give | inside 0.5 per cent by the formula this tool prints | a balance figure that would mark a sound analysis as suspect |
| The direction of the multiplication | 120 mg/L of calcium is 6 meq/L, to 1 decimal place | dividing by the charge instead of multiplying — every divalent arm halved, and the figure still looks like a figure |
- The two ions this tool differs from the article on at all are the two where the author rounded the equivalent weight rather than the answer — sodium at 23 and sulfate at 48 — and both land inside the tolerance above rather than being excused by it.
Asserted by src/tools/stiff-map/__tests__/ions.test.ts
The charge balance, in the form aqion and PHREEQC state it
- Method.
- The balance is the difference between the two sums over their total, in percent, and it is asserted on hand-worked pairs at both signs, on the symmetry that form implies, and on the one input for which there is no answer at all.
- Reference.
- aqion’s statement of the charge-balance error, which is the form PHREEQC uses: the difference over the SUM of the two sides rather than over one of them, which is the only version of the formula that gives the same magnitude when the two sums are swapped.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| Two sums that agree | exactly 0, with no floating-point residue | a formula that reports an error on a water that balances |
| Cations 6 against anions 4 | 20 per cent, to 10 decimal places | the difference taken over one side rather than over the total |
| The same two sums, swapped | the same magnitude with the sign reversed, to 12 decimal places | a balance that depends on which side you write first |
| A water with nothing in it | not a number, rather than a division by zero reported as a balance | an empty analysis presented as the best one on the map |
- The balance is a DISCLOSURE and never a filter. Nothing is dropped for failing it: the diagram is drawn either way, with a dashed outline, and the figure says how many analyses did not balance and against what threshold.
Asserted by src/tools/stiff-map/__tests__/ions.test.ts
Trends, well by well
Every arrow on a trend map is one Mann-Kendall test on one well’s own record, and every arrow length is one Sen slope. Both are pinned against a textbook that prints its intermediate values — the sign count, the tie-corrected variance, the standard deviation, the statistic and the confidence limits — so what is checked is the working and not just the answer.
The statistic and its variance, against four printed worked examples
- Method.
- Four examples from one chapter are reproduced: the four-point sequence of its Table 16.2, the twenty-two monthly readings of its Example 16.2, the doubly-tied Example 16.3, and the tie groups the chapter works out for a sequence containing traces.
- Reference.
- R. O. Gilbert, Statistical Methods for Environmental Pollution Monitoring, Van Nostrand Reinhold, 1987, chapter 16. Chosen because it prints its intermediate arithmetic rather than only its conclusions, which is what makes the variance and the continuity correction pinnable rather than merely plausible. · HydroGeoLogic, Inc., Mann-Kendall Analysis for the Fort Ord Site, 2005, appendix D of an annual groundwater monitoring report — an independent statement of the same formulae in a document a regulator accepted, walking the same arithmetic to the same answer.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| S for the sequence 10, 15, 14, 20 | exactly 4, as Table 16.2 works it out term by term | a sign count that disagrees with every published implementation |
| The one-sided probability of that S | 0.167, which is 4/24, to 12 decimal places | a tabled probability read off the wrong tail |
| The tie-corrected variance at twenty-two readings | 1227.33 from Eq. 16.3, to 2 decimal places | the tie correction omitted, which is a conservative p that will not match ProUCL |
| The statistic, with the continuity correction | 3.1, to 1 decimal place, from an unrounded 3.0545 | the correction dropped, or applied in the wrong direction |
| S where three readings share one time period and two share another | 19, scoring 24 of the 28 possible pairs | same-period pairs scored as though they were ordered in time |
| The variance from Eq. 16.5, and the statistic it gives | 58.1 and 2.4, each to 1 decimal place | a time-tie formula that does not reduce to the untied one |
- The concentrations behind Example 16.2 are printed only as a scatter plot, so what is pinned there is the arithmetic the book prints beside it — the tie groups, the variance, the standard deviation and the statistic — rather than twenty-two values read off a scanned figure. Reading them off would be pinning an act of eyesight.
- The tie correction subtracts from the variance, so for a fixed S it makes the statistic larger rather than smaller. That is the opposite of the usual intuition and it is asserted as a direction as well as as a value, because an implementation that omitted it would still produce a plausible p-value.
Asserted by src/tools/trend-map/__tests__/mk.test.ts
The Sen slope and its confidence interval, against the same chapter
- Method.
- The eight observations of that same example go through the slope estimator: every pairwise slope that spans two different time periods, the median of them, and the two interpolated ranks that bound it — which is the assertion that earns the estimator its interval rather than just its point.
- Reference.
- R. O. Gilbert, Statistical Methods for Environmental Pollution Monitoring, 1987, Example 16.5, which tabulates the pairwise slopes, states the rank arithmetic and prints both confidence limits — so the interpolation between ranks is checkable and not merely asserted.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| How many pairwise slopes the record gives | 24, the same set the chapter tabulates | pairs within one time period counted, which would bias the median |
| The median of them | 5.5 units per time period, to 10 decimal places | a mean quietly substituted for the median, which one bad result would then move |
| The lower and upper confidence limits | 2.6 and 9.3, each to 1 decimal place | ranks rounded instead of interpolated, which is a different interval |
| The normal quantile the chapter read off its own table | 1.645, to 3 decimal places | an interval built on the wrong critical value |
| Where the fitted line sits | through the median time and the median value, to 10 decimal places | a slope that is right and a line drawn somewhere else |
- Rounding the ranks rather than interpolating between them gives 3.3 and 9.0, which are also perfectly plausible-looking numbers. Matching the printed limits to one decimal place is only possible if both the rank arithmetic and the interpolation are right, which is why the interval is pinned and not only the slope.
- The slope is what this map draws as the arrow’s LENGTH, so the estimator’s resistance to a single wild reading is a property of the drawing and not only of the number. It is asserted separately, against a record with one result inflated by a factor of a hundred.
Asserted by src/tools/trend-map/__tests__/mk.test.ts
Which p-value a record gets, and the floor a short one cannot get under
- Method.
- The exact permutation distribution is used where it is defined and the normal approximation where it is not; the boundary between them is the one the regulator’s own software draws, and the smallest p a record of a given length can reach is read off that same distribution rather than assumed.
- Reference.
- USEPA, ProUCL 5.1 Technical Guide, section 10.2, equations 10-14 to 10-18, cross-checked as formulae and as the boundary at which the exact tail gives way to the approximation. Its own worked example prints its results only inside a screenshot, so there is no number in it to pin. · Kendall’s published tail probabilities, which appear in Gilbert (1987)’s own table and in every rank-correlation table since — the reference for the exact distribution at small sample sizes, checked value for value.
USEPA — ProUCL software and documentation
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| The published tail probabilities at five readings | 0.008, 0.042 and 0.117, each to 3 decimal places | a distribution that agrees with nothing anybody has tabulated |
| The exact distribution as a distribution | sums to 1 to 10 decimal places, and is symmetric about zero to 12 | a rising well and its mirror image getting different p-values, which on a map is two different arrows |
| The largest attainable statistic | exactly 1 over n factorial, to 12 decimal places, at every length up to 10 | a tail computed one step out, which nothing else would reveal |
| Which branch a record gets | exact up to 22 readings and untied, the approximation above 22 or with any tie | a boundary that disagrees with the software a regulator is holding the figure to |
| The two branches at that boundary | agreeing to 2 decimal places at 22 readings | a p-value that visibly jumps when one more quarterly round is added to the file |
| The smallest p a four-round record can reach | 0.083, so four rounds cannot clear 0.05 however perfect the trend | an arrow drawn on evidence the test never produced |
| The far tail of the normal approximation | 9.865876e-10 at −6, to 16 decimal places | a p reported as zero where it should be a small number, which no statistician would sign |
- The floor is a fact about the test and not about the well. A four-round record that rises perfectly still cannot produce a p below 0.083, so this map draws no arrow there at the usual level and says on the figure that the level could not be reached even in principle — rather than leaving it to be discovered by whoever checks.
- Non-detects are carried as a tied group below the highest reporting limit, after the chapter cited above, and nothing is substituted. That is a defensible choice rather than the only one, and the figure names it; a reader whose programme requires a different treatment is not being offered one here.
Asserted by src/tools/trend-map/__tests__/compute.test.ts, src/tools/trend-map/__tests__/mk.test.ts
What comes out of the export menu
Two exports, checked two different ways. The picture is checked by rendering every finished map twice and comparing the markup; the drawing is checked by reading it back as group-code pairs and asserting the structure an R12 reader requires. Both are checks on the file, not on how it looks once somebody opens it.
The SVG is the same file with the editor above it and without it
- Method.
- Every finished map is rendered over its own sample site, once with the click-to-edit layer mounted above it and once without, and the markup the download button would write is compared.
- Reference.
- No published source. The claim is about this product rather than about a format: the svg element is the deliverable, so nothing that exists to help somebody edit it may travel into it.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| The serialised figure, with the editor and without it | identical, byte for byte | editing state exported into a client’s deliverable |
| Every attribute the editing layer adds | absent from the file | the same defect in a form nobody would notice |
| The hit boxes, the dotted underline and the input | absent from the file | browser affordances baked into a drawing |
| A title-block cell the reader rewrote | in the file, and the wording it replaced is not | a reader’s own words silently dropped at the point of export |
- One thing legitimately differs between two renders and is normalised before the comparison: the clip path’s identifier, which is numbered per sheet so two figures on one page cannot capture each other. Everything else is compared exactly as it stands.
- The exported file does not embed its fonts. The type stack is deliberately web-safe and the layout is computed from estimated metrics, so a substitution shifts glyphs by a fraction and never reflows the sheet — but a print system with no sans-serif at all would render it wrongly, and nobody here has tested that case.
Asserted by src/figure/__tests__/figureTextExport.test.tsx
The DXF is a drawing an R12 reader can open
- Method.
- The exported file is read back into group-code pairs by a strict reader written inside the test, and the sections, the layer table, the entities and every coordinate are asserted against what the R12 specification requires.
- Reference.
- Autodesk’s own R12 DXF reference — the acad_r12_dxf archive PDF, which Autodesk no longer hosts at a stable address. It is what the exporter was written against, and the file header in src/figure/exportDxf.ts says so. · ezdxf’s notes on DXF file structure, used as a second reading of the same rules.
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| The version in the header | AC1009, which is R12 and nothing later | a file that declares one version and uses another’s entities |
| The polyline entity used | the old form with its own vertices, never the lightweight one R12 has no word for | an entity an older importer silently drops |
| The sections | header, tables and entities in that order, ending at the end-of-file marker | a file a strict reader rejects at the first section |
| The layer table | 5 layers on a contour drawing, counted honestly in the table header | a drawing whose layers the importer has to invent |
| A published line that is not a contour | on a layer named for what it is, and declared — 6 layers on a Stiff drawing | a Stiff polygon filed on the contour layer, so turning the contours off empties the sheet |
| Every group code and its value | an even number of lines, and every code an integer | one lost half of a pair, which turns the rest of the file into nonsense |
| Every coordinate in the entities section | inside the site’s own extent — −1 to 21 on the fixture | sheet points exported as though they were survey coordinates |
| A non-detect printed beside a well | folded to plain ASCII, still a limit and not a reading | a reporting limit arriving in somebody’s CAD as a measured number |
| Two exports of an unchanged figure | identical, with no date anywhere in the file | a diff between two quarters that shows every line as changed |
| Any string the file carries | trimmed to 250 characters, which is the R12 limit | a method note that overruns what a reader will accept |
- Structure is not appearance. These assertions say the file is a valid R12 drawing carrying the geometry the figure drew, in the site’s own coordinates. Nobody has opened one in a civil package and looked at it, and that is a gap only a person can close.
- The drawing unit is whatever unit the file arrived in, and the height of a contour is its own value rather than an elevation — so a head in feet contoured over a grid in metres puts two units in one drawing. That is the convention every contour DXF follows, and the file says which is which in its comment block rather than leaving it to be found out.
Asserted by src/figure/__tests__/exportDxf.test.ts
The shapefile is a shapefile, read back file by file
- Method.
- The archive is opened with this project’s own zip reader — the one written to open Excel workbooks, which knows nothing about the writer — and every file inside it is parsed back by a reader written in the test from the published specification rather than from the code it checks.
- Reference.
- ESRI Shapefile Technical Description, an ESRI White Paper, July 1998. It is the specification the writer was built from and the one the readers in the test were written from, independently. · dBASE III for the attribute table, which the ESRI paper leaves as “a standard DBF file”. The layout was taken from three published descriptions that agree on every offset used — dbase.com’s own file-format note, the dBase III structure published by Promotic, and Erik Bachmann’s Xbase file format description.
ESRI Shapefile Technical Description
| What is compared | What the test enforces | What a failure would mean |
|---|---|---|
| The main file header | the file code 9994 big endian and the version 1000 little endian, in one 100-byte header | a file every reader rejects on its first four bytes |
| Every length in the file | counted in 16-bit words rather than in bytes | a length field that looks plausible and is out by a factor of two |
| The first record’s offset in the index | 50 words, which is where the 100-byte header ends | an index that seeks into the middle of a record |
| A point record’s contents | 10 words — a shape type and two doubles | a record whose declared length walks the reader off the end of the file |
| Every offset the index publishes | followed back into the main file, where the record number found has to be the one expected | an index out by the size of a record header, which no round trip through the writer would notice |
| The bounding box | recomputed from the records themselves, in the order the specification gives | a box written as two corners rather than interleaved — accepted by every reader, drawn in the wrong place |
| The attribute table | one record per shape, in the same order, with the declared header and record lengths adding up | a table read as a wall of shifted characters |
| The date in the attribute table | 80 — the year since 1900, which the specification requires | a header a strict dBASE reader refuses |
| A well the figure posted no reading beside | blanks, never a zero | a measurement that never happened, on every row of a well location map |
| The projection file | written only where the coordinate system is genuinely known, and byte-identical to what ArcGIS writes for the same zone | a site placed somewhere else on the planet, silently, by a projection file that guessed |
| Two exports of an unchanged figure | identical, with no date in the archive or in the attribute table | a diff between two quarters that shows every byte as changed |
- Structure is not appearance, the same caveat the drawing carries. These assertions say the archive holds valid shapefiles carrying the geometry the figure drew, with the attributes the figure printed. Nobody has opened one in ArcGIS or in a state agency’s submittal portal, and that is a gap only a person can close.
- The convex hull of the wells is not in the archive. A shapefile holds one geometry type, so it would be a third layer of a single feature; the DXF and the GeoJSON both carry it, and the README inside the archive says where it went.
- Attribute text is folded to plain ASCII, because dBASE III has no way to declare an encoding. The fold is lossless for everything this product prints — a non-detect stays a limit and never becomes a bare number — and it would drop an accent from a well name nobody here has ever seen in a file.
Asserted by src/figure/__tests__/exportShapefile.test.ts
Where the reference values came from
The reference values are not stored answers somebody once printed. They are produced by a script committed beside them, which records the versions it ran under, cross-checks its two independent kriging implementations against each other, and refuses to write anything if they disagree by more than the guard below. The suite reads that record back, so these versions cannot drift away from the numbers they produced.
- Python
- 3.13.13
- NumPy
- 2.4.6
- SciPy
- 1.17.1
- PyKrige
- 1.7.3
- The two references, against each other
- 2.83e-14
- What the generator would have refused at
- 1e-8
What is NOT validated
Nothing here has been read by a hydrogeologist. Every claim on every figure is checked against a computation; none of it has been reviewed by somebody who signs these drawings, and no figure from this site is known to have survived a regulator.
There is no anisotropy anywhere. Variograms and kriging are isotropic and there are no directional variograms, so a site whose structure runs along a valley or a fracture set is being modelled as though it did not.
Kriging is global. Every well enters every grid node’s system, which is correct and slow and fine to a few hundred wells; there is no moving neighbourhood, so nothing here is validated at thousands.
This engine does not reproduce PyKrige’s fitted variogram, and no tolerance would make it. PyKrige minimises a robust loss and this fits plain weighted least squares. The fit is pinned against an independent optimiser attacking OUR objective, and the distance to PyKrige’s answer is monitored rather than asserted away.
Contours are not clipped exactly to the hull. Any cell with a missing corner is skipped, so an isoline stops at the edge of the masked region; the fringe is hatched and named on the sheet, which is honest, but it is not sub-cell clipping.
How the pages and the pictures LOOK is checked by a person in a browser. There is no screenshot pass. The small figures on the landing page are baked by a script and tested for the things a test can see — a missing one, lettering, a colour that is not the sheet’s ink, a size budget — but whether any of them is a good picture of that map is nobody’s assertion, and a stale-yet-valid one would pass.
The number of flow arrows is approximate by design: the packing that places them depends on the shape of the domain, so asking for a count gets you roughly that many.
Agreement with another implementation is evidence that the equations were transcribed and wired up correctly. It is not evidence that they are the right equations for your site, that the data you gave them is defensible, or that the answer means what you would like it to. All of this is a calculation aid and none of it is a substitute for professional judgement.