Skip to content

A constant evaluates to a real number whatever the downcasting says, a complex number with a zero imaginary part is real-valued, and the evaluation cache is stamped rather than cleared - #1371

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
downcasting-epoch
Sep 16, 2026

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Follow-up to #1368, found while running the hyperbolic Rubi sample: seven answers with sgn(tanh(c + d x)) in them — all right — came back as errors because their derivatives could not be evaluated.

Cause. With the downcasting off, e and pi evaluated through the EDecimal → Complex conversion to a Complex with a zero imaginary part (that is the documented contract of DowncastingEnabled = false: no type reduction at all), and the constant is one object whose cached Evaled kept it. After any scope with the downcasting off — the integrator's sampled checks open one around every candidate — IsRealValued(e^(2x) − 1) was false, so sgn(...)' stayed derivative(sgn(...), x) for the rest of the process, and e^x was no longer recognised as the exponential of x in Pow (a logarithm of the constant and an exponential instead).

Fix, three parts:

  • The constants are Real.Create of their digits — a Real in either mode (Real.Create with the downcasting off is new Real, no rational search), so nothing that is cached on them depends on the setting.
  • IsRealValued takes a complex number whose imaginary part is zero for the real number it is (Complex.Create(1.5, 0) with the downcasting off).
  • The Evaled cache is an explicit field stamped with the epoch it was computed under and its owner, rather than the lazy slot cleared on a stale read: a precision scope opened inside an evaluation (the factorial opens one) advanced the epoch under a computation still running, and a re-entrant read cleared the slot being filled and read back null — 54 NullReferenceExceptions in the suite once a second setting advanced the epoch. The owner stamp is what the lazy slot carried (a record's with copies the field).

Tried and rejected: making DowncastingEnabled advance the epoch too. It made every cached evaluation in the process stale at each of the integrator's checks; the unit suite went from 6 to 22 minutes. Recorded in the epoch's doc comment.

Gate: every row within the band (+0.1% to +1.1% bytes, the same as #1368's run — that PR did not move the baseline). Four suites green (11,350 / 134 / 18 / 41).

Part of #1367.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

…a complex number with a zero imaginary part is real-valued, and the evaluation cache is stamped with its epoch rather than cleared

With the downcasting off, e and pi evaluated through the conversion
from a decimal to a Complex with a zero imaginary part, and the
constant is one object whose cached Evaled kept it: after any scope
with the downcasting off -- the integrator's sampled checks open one
around every candidate -- sgn(e^x - 1) was not real-valued, so its
derivative, which is zero, stayed unevaluated for the rest of the
process (the hyperbolic sample reported seven right answers with
sgn(tanh(c + d x)) in them as errors), and e^x was no longer the
exponential of x but a logarithm of the constant and an exponential.
The constants are Real.Create of their digits now, a Real in either
mode, and IsRealValued takes a complex number whose imaginary part is
zero for the real number it is.

The downcasting setting does not advance the evaluation epoch: tried,
it made every cached evaluation in the process stale at each of the
integrator's checks, and the unit suite took four times as long. And
the epoch is read before an evaluation and recorded with its result,
rather than clearing the lazy slot on a stale read: a precision scope
opened inside an evaluation -- the factorial opens one -- advanced the
epoch under a computation still running, and a re-entrant read cleared
the slot it was filling and read back null.

Four suites green.

Part of #1367.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet
Rafael-SOWNet merged commit 2b4a525 into master Sep 16, 2026
31 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the downcasting-epoch branch September 16, 2026 06:40
Rafael-SOWNet added a commit that referenced this pull request Oct 9, 2026
… 2.5.0

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
Rafael-SOWNet added a commit that referenced this pull request Oct 9, 2026
* Breaking-changes entries for #1828 to #1832 and #1374

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

* And for #1368, #1611 and #1517, each measured on 2.5.0

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

* And for #1289, #1312, #1315, #1377, #1380 and #1371, each measured on 2.5.0

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant