fix(precision): evaluation complies with its arguments instead of refusing them - #394
Open
ofloveandhate wants to merge 1 commit into
Open
fix(precision): evaluation complies with its arguments instead of refusing them#394ofloveandhate wants to merge 1 commit into
ofloveandhate wants to merge 1 commit into
Conversation
…using them
A System was WEDGED after anything moved the ambient default precision, with no way
out. An SLP's Memory takes its precision from DefaultPrecision() when the program is
lazily compiled, while the owning System keeps whatever it was told, so the two diverge
the moment an AMP tracker or endgame moves the ambient default -- which they do as a
matter of course. Both directions then threw:
eval at the system's OWN precision -> "variable_values and SLP must be of same
precision. respective precisions: 30 16"
eval at the ambient precision -> "precision of input point in SetVariables
(16) must match the precision of the
system (30)."
and neither setter could repair it, because System::precision(n) and
StraightLineProgram::precision(n) both short-circuit when handed the value they already
hold. Measured: the endgame -> Jacobian-spectrum path hits this every single time,
since the endgame is exactly what moved the default.
The Memory's precision is an ARTIFACT OF THE CURRENT EVALUATION, never an invariant to
defend: the compiled Program is a precision-independent tape of operations, and only the
Memory holding values carries digits. So evaluating at whatever precision the caller
brings is always meaningful, and the right response to a mismatch is to re-tag, not to
refuse. Likewise a System, whose precision is the fan-out point to its blocks' working
coefficients and its patch -- each of which already keeps an exact master
(constant_recipes_, coefficients_highest_precision_) beside its working copy.
- SetVariableValues re-tags the SLP's memory to the incoming precision, reusing
precision(), which REFILLS CONSTANTS FROM THEIR EXACT RECIPES rather than padding
them with zeros -- so accuracy is rebuilt, not faked.
- SetPathVariable only ever RAISES: the variables are already in memory by then, so
re-tagging downward would truncate them. Memory ends an evaluation at the max of
its arguments' precisions, and the time slot is re-tagged after assignment because
assigning an mp value adopts the source's precision.
- System::SetVariables aligns the System to the incoming point, pushing the precision
down to every block and the patch. Deliberately NOT behind
BERTINI_DISABLE_PRECISION_CHECKS: alignment is required behaviour, not an assertion.
Threading is unaffected: ZeroDimSolver already gives every worker its own deep-cloned
System, precisely because "residual evaluation mutates System precision state".
Tests in both languages. Beyond the wedge reproduction for Eval and Jacobian, one
system evaluated up and down a ladder of precisions, and the max-of-arguments rule for
the path variable, the mean one asserts that a constant INEXACT in decimal -- 1/3 --
compiled at 16 digits returns residual 0 when evaluated at 100 digits. That only holds
if re-tagging REBUILDS constants from their recipes; padding a 16-digit 1/3 would leave
the tail zero and the residual at ~1e-17.
Part of #377. Does not yet address its asks 2 and 3 (removing System precision as
load-bearing caller-managed state, and one documented ownership rule) -- with evaluation
now aligning on its own, System::precision() has nothing left that callers need, so
removing it is a follow-up sweep over ~161 call sites plus an ADR.
Verified: ctest 10/10 suites pass, pytest 933 passed 1 skipped.
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.
Fixes ask 1 of #377.
A System doesn't just complain — it gets WEDGED
An SLP's
Memorytakes its precision from the ambientDefaultPrecision()when theprogram is lazily compiled, while the owning
Systemkeeps whatever it was told. Theydiverge the moment anything moves the ambient default — which an AMP tracker or endgame
does as a matter of course. Once they have, both directions throw:
That third line is the point.
System::precision(n)cannot repair it: it andStraightLineProgram::precision(n)both short-circuit when handed the value they alreadyhold, so the System — already believing it is at 30 — pushes nothing down to the SLP. The
only escape is setting the ambient global, which means
System::precision()is a report ofsomething that may no longer be true.
Comply, don't refuse
The doctrine: a Memory's precision is an artifact of the current evaluation, never an
invariant to defend. The compiled Program is a precision-independent tape of operations;
only the Memory holding values carries digits. Likewise a System's precision is the fan-out
point to its blocks' working coefficients and its patch — each of which already keeps an
exact master (
constant_recipes_,coefficients_highest_precision_) beside its workingcopy. Nothing irreplaceable lives in either.
SetVariableValuesre-tags the SLP's memory to the incoming precision, reusingprecision(), which refills constants from their exact recipes rather than paddingwith zeros — accuracy is rebuilt, not faked.
SetPathVariableonly ever raises: the variables are already in memory by then, sore-tagging downward would truncate them. Memory ends an evaluation at the max of its
arguments' precisions. The time slot is re-tagged after assignment, since assigning an mp
value adopts the source's precision.
System::SetVariablesaligns the System to the incoming point, pushing precision downto every block and the patch. Deliberately not behind
BERTINI_DISABLE_PRECISION_CHECKS— alignment is required behaviour, not an assertion.Threading
Unaffected.
ZeroDimSolveralready hands each worker a deep-cloned System, and the commentthere says why: "a target-system copy (residual evaluation mutates System precision
state)". The codebase already accepts that evaluating mutates precision state and solves it
by ownership.
Tests, both languages
core/test/classes/precision_propagation_test.cppandpython/test/classes/slp_precision_follows_args_test.py: the wedge reproduction forEvaland
Jacobian, one system evaluated up and down a ladder of precisions, and themax-of-arguments rule for the path variable.
The pointed one asserts that a constant inexact in decimal —
1/3— compiled at 16digits returns residual exactly 0 when evaluated at 100 digits. That holds only if
re-tagging rebuilds constants from their recipes; padding a 16-digit
1/3out to 100would leave the tail zero and the residual at ~1e-17.
Verification
ctest --test-dir build/core— 10/10 suites pass, includingtest_tracking_basics,test_endgamesandtest_nag_algorithms, the heaviest users of precision changespytest python/test/— 933 passed, 1 skippedcounts, warning counts, Euler characteristic — fail byte-identically with and without
this change: same face count, same midslice count, same warning count, same χ. The
baseline binary was confirmed to be the baseline by checking that it still refuses, rather
than by trusting build timing.
Scope
This is ask 1 only. Asks 2 and 3 — removing
System::precisionas load-bearingcaller-managed state, and one documented ownership rule — are now cheaper rather than
harder: with evaluation aligning on its own, the fan-out
System::precision()performs istriggered by the arguments instead of by hand, so removal is a sweep over ~161 call sites
(69
core/include, 46core/test, 37python, 1 binding) plus an ADR, not new machinery.Left for its own PR since it is an API break.
ADR
None proposed. Both non-obvious choices are load-bearing but explained inline at their
sites: why
SetPathVariablemay only raise, and why re-tagging must rebuild constantsrather than pad. The regression tests fail loudly if either is undone. The ADR that is
warranted is the ownership rule from ask 3, which belongs with the removal PR.