Repository navigation
Scientific stack: Special, Stats, Integrate, Optimize — and a guard that runs every example on the site - #27
Merged
Conversation
… that runs every example
The site documented NDArray, FFT and Image and stopped there. rbgo#819 adds
four more modules to the same cgo-free stack -- scipy.special's 67 functions,
scipy.stats's 32 univariate distributions, scipy.integrate's quadrature and
scipy.optimize's root finders and minimisers -- so the page that exists to
describe that stack described two thirds of it.
Each section follows the page's own shape: what it binds, the surface, and a
short example. The names are scipy's own, and the sections say why: every one
of those modules states its specification by measuring itself against scipy in
ULPs, and the pairing is a file in its repository, so Log1pExp is #softplus and
StudentT is Stats.t rather than a transliteration of the Go name. Nothing here
is new prose about behaviour: it is the README's text, cut to this page's
length.
The index's stack paragraph and its library list named four organisations and
now name six.
## tools/docexample
Every example on this site carries its answer in a comment -- `p
Special.gamma(5.0) # => 24.0` -- and nothing ran them. A binding renamed, a
default changed or a return shape altered in the ruby repository left each
example claiming its old output, which reads as a promise and is the first
thing a reader tries. tools/conformancefigure already guards the one NUMBER
this site states; this guards the twenty-five OUTPUTS it states.
A ```ruby block is checked exactly when some line in it ends in `# =>`. That
makes the selection self-evident rather than a list to maintain: the examples
that read a photo off disk or start a server claim nothing and are skipped, and
one that gains an answer is picked up without being registered anywhere. The
block runs as one program, so earlier lines can set up later ones, and the
lines it PRINTS are compared in order against the answers it claims -- a
differing count failing too, because an example printing two lines while
claiming one is as wrong as a wrong number.
Exit status separates the three outcomes that matter: 1 for a disagreement, 2
for no interpreter (a refusal, not a pass), 3 for finding nothing to check,
which on a site that teaches by example means the extraction broke rather than
that the site is clean. -selftest checks the extraction on six cases whose
answer is known, including the two that must NOT match: a comment that is not
an answer, and a shell block.
Measured: 11 examples, 25 claimed answers, 13 files, 0 disagreements. Proved to
bite three ways -- a wrong answer, an example printing one line too many, and
an example that no longer runs -- each named with its file and line.
## This must merge after rbgo#819
The guard says so itself. Built against the ruby repository's current main it
reports exactly the four new sections and nothing else:
docs/scientific-stack.md:72: uninitialized constant Special
docs/scientific-stack.md:98: uninitialized constant Stats
docs/scientific-stack.md:118: uninitialized constant Integrate
docs/scientific-stack.md:138: uninitialized constant Optimize
11 example(s) with 25 claimed answer(s) over 13 file(s); 4 example(s) disagree
The seven pre-existing examples pass on both, so the dependency is the four
sections and the ordering is the only thing to respect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
|
CI is red, and this is the run confirming why rather than an assumption — the dependency on ruby#819, and nothing else: Four things are established by that output, which is more than "it is red":
Against an rbgo built from #819's branch the same command reports So this goes in after #819 and turns green on its own, with no change here. 🤖 Generated with Claude Code |
… nothing The doc comment claimed a block is checked "when some line in it ends in `# => something`". The pattern was `#\s*=>\s*(.*)$`, where `(.*)` matches the empty string -- so a bare `# =>` counted as a claim of "", which would be compared against whatever the line printed and reported as a mismatch nobody wrote. The header named the rule; the test did not implement it. Tightened to `(\S.*)$` and the comment says why, with two self-test cases: a bare `# =>` claims nothing, and `#=>2` with no spaces around the arrow still claims 2 -- the second so that tightening the start does not quietly require a space the old pattern did not. Verified load-bearing: with `(.*)` restored, the first case fails. 8 cases, 0 failures; the site is unchanged at 11 examples / 25 claimed answers / 0 disagreements. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…st bit
ruby#819 merged, so the guard this branch adds could finally run against a main
that has the four modules -- and it immediately caught something a tolerance
test cannot:
scientific-stack.md: answer 1 is "0.33333333333333337", the page says "0.3333333333333333"
scientific-stack.md: answer 2 is "1.0000000000000002", the page says "1.0"
Not a regression. Quadrature sums products, which Go fuses into an FMA on arm64
and not on amd64, so `quad`'s last bit differs by architecture: I wrote the
page's values on an arm64 Mac and CI's ubuntu runner is amd64. go-numerics
knows this about itself -- optimize carries a `tools/nofma` rewriter for the
same reason, and go-numerics/docs once shipped figures that differed
byte-for-byte between the two.
So the two quadrature answers are rounded to 12 digits, and the page SAYS WHY,
because a reader seeing one example rounded and eleven not should be told
rather than left guessing. The special functions do not drift that way -- their
kernels are kept FMA-free on purpose -- and indeed every Special, Stats and
Optimize answer on the page agreed with the amd64 runner to the last digit.
Worth recording about my own claim: #819 said go-numerics/integrate v0.2.0's
"results unchanged to the last bit" was CHECKED by its 113 closed forms. Those
compare within 1e-12 and could not have seen a last-bit change either way. The
claim was true -- main at v0.2.0 prints the same 0.33333333333333331 this
machine printed at v0.1.0 -- but my evidence for it was weaker than my wording,
and it was this tool, printing exact values, that could tell the difference.
11 examples, 25 claimed answers, 0 disagreements against an rbgo built from
main at fea3857.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The page described two thirds of its subject
scientific-stack.mdexists to describe the cgo-free numeric stack, and coveredNDArray,FFTandImage. ruby#819 adds four more modules to that same stack, so the page gains a section for each, in the shape the existing ones use — what it binds, the surface, a short example — andindex.md's summary and library list name them:Special— scipy.special-stylego-numerics/specialStats— scipy.stats-stylego-statistics/statsIntegrate— scipy.integrate-stylego-numerics/integrateOptimize— scipy.optimize-stylego-numerics/optimizeThe sections say why the names are scipy's and not transliterations of the Go ones: each module compares itself against scipy in ULPs and the pairing is a file in its repository, which is why
Log1pExpissoftplusandStudentTist.Every output in the new sections was produced by running it, not written from the definition.
Which is the point of
tools/docexampleThis site teaches by example, and an example carries its answer in a comment:
Nothing ran them. A binding renamed or a return shape changed upstream left every example here claiming its old output — which reads as a promise and is the first thing a reader tries.
tools/conformancefigurealready guards the one number this site states; this guards the outputs it states, anddocs.ymlruns it on every pull request.What it judges. A
```rubyblock is checked exactly when it claims an answer — when some line ends in# => …. That makes the selection self-evident instead of a list to maintain: the examples that read a photo off disk or start a server carry no# =>and are left alone, and one that gains an answer is picked up without being registered anywhere.How it compares. The block runs as one program, so earlier lines can set up later ones, and the lines it prints are compared in order with the answers it claims. A differing count is itself a failure: an example printing two lines while claiming one is as wrong as a wrong number, and the usual cause —
p a, bprinting twice — is exactly what nobody notices by reading. Only trailing whitespace is normalised, because authors align the comments; nothing else, because a digit's difference is the point.It refuses rather than passes when it cannot judge. No interpreter is exit 2; finding nothing to check on a site full of examples is exit 3, because that means the extraction broke, not that the site is clean — the same refusal
conformancefiguremakes for an unreachable baseline.Its extraction has a six-case self-test, which the workflow runs first: a pattern nobody tested is a guess, and a guess that matches nothing looks exactly like a clean site. The cases include the two that must not match — a comment that is not an answer (
# exact, no 0.30000000000000004) and a non-Ruby fence — and one that must survive a#inside the value.Measured
On the site as it stands, against an rbgo from #819: 11 examples, 25 claimed answers, 13 files, 0 disagreements. Broken three ways deliberately, it named each precisely:
scientific-stack.md:72: answer 1 is "24.0", the page says "24.5"printed: [… 4 values …] claims: [… 3 values …]claims 4 answer(s) but will not run: undefined method 'gammaa' for module Specialcredscan0. Measured 2026-10-17 with go1.27.2.🤖 Generated with Claude Code