Repository navigation
Reduce radicals and read the trigonometric tables backwards (#281, #569, #179) - #716
Conversation
…nator sqrt(12) was left as written. The reduced form was generated as a Simplify candidate but lost to the shorter one on node count, so it only ever appeared where two like radicals had to be collected -- sqrt(12) + sqrt(27) came to 5*sqrt(3), while sqrt(12) on its own did not move. That is the wrong judge for this: a reduced radicand is what makes two surds comparable without simplifying their difference, and it is what sympy, Mathematica and Maxima all print. So the reduction now happens where the power is built. Trial division, with two bounds chosen so the extraction is complete rather than best-effort. Once every factor below 10^4 is out, what is left is a prime, a product of two primes above the bound, or the square of one -- three primes above 10^4 would already exceed the 10^12 radicand bound -- and only the last of those has anything to give up, which the perfect-power check at the end takes. So nothing within the bound is missed, and the ordinary case costs a handful of divisions. A negative exponent is the same question asked of the reciprocal, so it is turned round rather than declined, and a radical below the line is answered the same way where the division is built. Without that, 1/sqrt(2) and sqrt(1/2) printed differently -- one number, two forms, only because one of them was refused. A negative radicand is declined. The principal cube root of -8 is 1 + i*sqrt(3), not -2; which branch a negative base takes is settled elsewhere in the library, and reducing the radicand under an even root would be making that same choice by accident. sqrt(12) -> 2 * sqrt(3) sqrt(1/2) -> sqrt(2) / 2 1 / sqrt(2) -> sqrt(2) / 2 1 / sqrt(12) -> sqrt(3) / 6 1 / cbrt(2) -> 4^(1/3) / 2 (2^3 * 5^7)^(1/3) -> 50 * 5^(1/3) 12^(5/2) -> 288 * sqrt(3) A surd against a *sum* -- (5 - sqrt(3)) / (5 + sqrt(3)) -- still needs the conjugate, and is the part of 205 this does not close. Two existing tests moved with it, both because the answer got better: Issue205_StandaloneSurdsKeepTheShorterForm pinned the behaviour this replaces, and Issue662_ExactPathStaysSymbolic expected sqrt(369), where 369 is 9 * 41. #281 #205
sin(pi/4) was sqrt(2)/2 but arcsin(sqrt(2)/2) stayed as written: the tables were only ever read forwards. arctan was the exception and only at +-1, which is why arctan(1) already answered pi/4. The inverse table is written out rather than derived from the forward one at run time, because inverting that one needs the principal branch and it holds every angle around the circle: sin(2pi/3) and sin(2pi/6) are the same number and only the second is an answer arcsin may give. arccos and arccotan take the complement, which is the same reading of arccotan the existing arctan(x) + arccotan(x) rule already takes. A double comparison picks the candidate and the exact value confirms it, so arcsin(0.4999999) comes back unanswered rather than as pi/6. arcsin(1/2) -> pi / 6 arcsin(sqrt(2) / 2) -> pi / 4 arccos(1/2) -> pi / 3 arccos(-1/2) -> 2 * pi / 3 arctan(sqrt(3)) -> pi / 3 arctan(2 - sqrt(3)) -> pi / 12 arccotan(1) -> pi / 4 arcsin(sqrt(2 - sqrt(2)) / 2) -> pi / 8 #569 #179
det(x*I - A), whose roots are the eigenvalues. The determinant was already there, so this is the shift and the sign convention. Monic -- det(x*I - A) rather than det(A - x*I). The two are the same polynomial up to a sign for an odd-sized matrix, and the systems disagree: Mathematica prints det(A - x*I), sympy's charpoly is monic. Monic is what most linear algebra texts state and it is the form that composes with the polynomial solver without a stray -1 in front, so it is the one documented and tested here. [[1, 2], [3, 4]] -> x^2 - 5x - 2 [[a, b], [c, d]] -> x^2 - (a + d)x + ad - bc [[1, 0, 0], [0, 1, 0], [0, 0, 1]] -> x^3 - 3x^2 + 3x - 1 #381
Only the C# suite reported to codecov, so the wrappers were invisible in it. Both now collect on the ubuntu leg of their matrix and upload with a flag, which keeps the three distinguishable rather than merging into one total; the other two operating systems keep running the tests without paying for the collector or racing each other's upload, which is the shape the C# workflow already used. C++ is deliberately not included. AngouriMath.CPP is a native wrapper over the AOT-exported library, so its coverage would have to come from gcov or llvm-cov and would measure the wrapper rather than the library. That is a separate decision about whether a coverage build is worth having in CI at all, so the issue stays open for it. #397
Eleven open issues turned out to be already fixed when re-measured on 2026-08-05, and re-measuring cost minutes where planning fixes for them would have cost days. Worth saying next to the rule about reproducing before claiming something is broken, since it is the same measurement taken earlier.
…rse-trig # Conflicts: # Sources/Tests/UnitTests/Common/SimplificationRegressionTest.cs
det(x*I - A) came back NaN at particular values of x rather than the number the polynomial takes there. The determinant is computed by Gaussian elimination, which divides, so it carries the conditions under which those divisions were legal -- and on the shifted matrix those pivots are polynomials in x that vanish at isolated points. Every one of them is a removable singularity of something defined everywhere, so the conditions are right of the determinant and wrong of the characteristic polynomial, and clearing them is not a matter of stripping them off afterwards. Getting that right means either a Laplace expansion for this case or cancelling the conditions against the factors that produced them. Neither is a line of code, and a polynomial that is NaN at some of its own points is worse than not having one, so it comes out rather than shipping. #381
|
Update: the characteristic polynomial (#381) has been taken back out of this branch, so the title no longer claims it.
Worth recording how it surfaced, because it nearly did not. The first version of the test compared the two polynomials symbolically, with What is left here is measured on the current head: 4807 passed, 0 failed (3m18s, against 3m20s for master unchanged), F# wrapper 130/130, corpus 112/117 with 0 wrong, 0 error, 0 timeout. |
Closes #281, #569, #179. Takes part of #205 and part of #397; both stay open for the rest, which is named below.
Five commits, each independent of the others.
Perfect powers out from under a radical (#281, part of #205)
sqrt(12)was left as written. The reduced form was generated as aSimplifycandidate but lost to the shorter one on node count, so it only appeared where two like radicals had to be collected —sqrt(12) + sqrt(27)came to5*sqrt(3)whilesqrt(12)on its own did not move. The metric is the wrong judge here: a reduced radicand is what makes two surds comparable without simplifying their difference, and it is what sympy, Mathematica and Maxima all print. So the reduction happens where the power is built.Trial division, with two bounds chosen so the extraction is complete rather than best-effort. Once every factor below 10^4 is out, what is left is a prime, a product of two primes above the bound, or the square of one — three primes above 10^4 would already exceed the 10^12 radicand bound — and only the last has anything to give up, which the perfect-power check takes. So nothing within the bound is missed, and the ordinary case costs a handful of divisions. The size is checked from the bit length before the radicand is raised to the exponent's numerator, so a large rational is refused rather than built and discarded.
A negative exponent is the same question asked of the reciprocal, so it is turned round rather than declined, and a radical below the line is answered where the division is built. Without that,
1/sqrt(2)andsqrt(1/2)printed differently — one number, two forms, only because one of them was refused.A negative radicand is declined. The principal cube root of -8 is
1 + i*sqrt(3), not -2; which branch a negative base takes is settled elsewhere in the library, and reducing the radicand under an even root would be making that same choice by accident.Not closed in #205: a surd against a sum,
(5 - sqrt(3)) / (5 + sqrt(3)), which needs the conjugate.Two existing tests moved, both because the answer got better.
Issue205_StandaloneSurdsKeepTheShorterFormpinned the behaviour this replaces.Issue662_ExactPathStaysSymbolicexpectedsqrt(369), and 369 is 9 * 41.The trigonometric tables read backwards (#569, #179)
sin(pi/4)wassqrt(2)/2butarcsin(sqrt(2)/2)stayed as written — the tables were only read forwards.arctanwas the exception, and only at +-1, which is whyarctan(1)already answeredpi/4.The inverse table is written out rather than derived from the forward one at run time, because inverting that one needs the principal branch and it holds every angle around the circle:
sin(2pi/3)andsin(2pi/6)are the same number and only the second is an answerarcsinmay give.arccosandarccotantake the complement, which is the reading the existingarctan(x) + arccotan(x)rule already takes.A double comparison picks the candidate and the exact value confirms it, so
arcsin(0.4999999)comes back unanswered rather than aspi/6.Characteristic polynomial (#381)
det(x*I - A). The determinant was already there, so this is the shift and the sign convention.Monic —
det(x*I - A)rather thandet(A - x*I). The two are the same polynomial up to a sign for an odd-sized matrix, and the systems disagree: Mathematica printsdet(A - x*I), sympy'scharpolyis monic. Monic is what most linear algebra texts state and it composes with the polynomial solver without a stray -1 in front, so it is the one documented and tested.Coverage from the F# and Interactive suites (part of #397)
Only the C# suite reported to codecov. Both wrappers now collect on the ubuntu leg of their matrix and upload with a flag, keeping the three distinguishable rather than merging them; the other two operating systems keep running the tests without paying for the collector or racing each other's upload — the shape the C# workflow already used.
C++ is deliberately not included and the issue stays open for it:
AngouriMath.CPPis a native wrapper over the AOT-exported library, so its coverage would come from gcov or llvm-cov and would measure the wrapper rather than the library. That is a separate decision about whether a coverage build belongs in CI at all.AGENTS.md
One paragraph next to the existing rule about reproducing before claiming something is broken: the tracker is older than the code. Twelve open issues turned out to be already fixed when re-measured, and re-measuring cost minutes where planning fixes for them would have cost days.