A canonical form for rational functions over Q (#934) - #935
Merged
Conversation
The part of the language where a canonical form is possible. There is none for the whole of it --
zero-equivalence is undecidable once pi, the exponential, the trigonometric functions and abs are in
play (Richardson 1968) -- so the boundary is in the signature: anything that is not a rational
function over Q gets no answer at all, never a normalisation that resembles one.
1/x + 1/y -> (x + y) / (x * y)
(x + y)/(x * y) -> (x + y) / (x * y) the same tree, which nothing could do before
2 * x / (4 * y) -> 1/2 * x / y
x / (2 * y) -> 1/2 * x / y
(x^2 - 1) / (x - 1) -> x + 1 provided not x - 1 = 0
sin(x) / x -> refused
Two rational functions are equal exactly when this form is identical, so equality on that
sublanguage is decided by comparing nodes rather than by searching -- which is what a canonical form
is for and what `(a - b).Simplify()` against zero can never be.
Four steps and only the first is new. **Nothing in the library put an expression over a common
denominator**, measured through Simplify, InnerSimplified and Factorize alike, and Simplify actively
prefers the split form -- so 1/x + 1/y and (x+y)/(x*y) could not be brought together by any existing
route. Gathering is a structural recursion over sums, differences, products, quotients and whole
powers, taking the product of denominators rather than their least common multiple: both are
correct, the product is cheaper to build, and the greatest common divisor removes the difference
immediately afterwards.
The other three steps are existing parts. PolynomialGcd.Gcd reduces the quotient and verifies its
own division by multiplying back. MultivariatePolynomial.LeadingCoefficient, from the Groebner
half of the class, gives the lexicographic leading coefficient to scale by, so the denominator comes
out monic -- without that, 2x/(4y) and x/(2y) stay different trees for one function, since a greatest
common divisor is fixed only up to a unit.
**Cancelling carries its condition, and that is the assertion the whole form rests on.**
(x^2 - 1)/(x + 1) is undefined at x = -1 and x - 1 is not, so they are *not* the same function and
the form must not equate them, however universally the cancellation is written. There is a test
saying exactly that, next to the one saying x/x is not 1. Gathering over a common denominator widens
nothing by itself -- a sum is defined exactly where its terms are -- so no condition is invented
where nothing was cancelled, which also has a test.
Offered and not applied: nothing in the library runs it, and a test pins that.
Measured: suite 6998 passed / 0 failed, of which 38 are new; F# wrapper 130/130; casbench 116/119
with 0 wrong, 0 error, 0 timeout; propcheck 1340 checks / 0 failures; rootcheck 596/596 clean;
simpsweep 10463/10463 agree.
No BREAKING-CHANGES entry: no input gives a different answer. The new public property is in
PublicApi.txt.
#934
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ion-canonical-form # Conflicts: # Sources/AngouriMath/Core/Transformations/Transformation.Catalogue.cs
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.
Closes #934.
The part of the language where a canonical form is possible. There is none for the whole of it — zero-equivalence is undecidable once
pi, the exponential, the trigonometric functions andabsare in play (Richardson 1968) — so the boundary is in the signature: anything that is not a rational function over ℚ gets no answer at all, never a normalisation that resembles one.Two rational functions are equal exactly when this form is identical, so equality on that sublanguage is decided by comparing nodes rather than by searching — which is what a canonical form is for, and what
(a - b).Simplify()against zero can never be.What was actually missing
Four steps, and only the first is new. Nothing in the library put an expression over a common denominator — measured through
Simplify,InnerSimplifiedandFactorizealike, andSimplifyactively prefers the split form — so1/x + 1/yand(x+y)/(x*y)could not be brought together by any existing route.Gathering is a structural recursion over sums, differences, products, quotients and whole powers, taking the product of denominators rather than their least common multiple: both are correct, the product is cheaper to build, and the GCD removes the difference immediately afterwards.
The other three steps are existing parts.
PolynomialGcd.Gcdreduces the quotient and verifies its own division by multiplying back.MultivariatePolynomial.LeadingCoefficient, from the Gröbner half of the class, gives the lexicographic leading coefficient to scale by, so the denominator comes out monic — without that,2x/(4y)andx/(2y)stay different trees for one function, since a greatest common divisor is fixed only up to a unit.The assertion the whole form rests on
Cancelling carries its condition.
(x^2 - 1)/(x + 1)is undefined atx = -1andx - 1is not, so they are not the same function and the form must not equate them, however universally that cancellation is written. There is a test saying exactly that, beside one sayingx/xis not1.(I wrote the opposite first —
(x^2-1)/(x+1)equalsx-1as a same-function case — and it failed. The form was right and the test was wrong.)Gathering over a common denominator widens nothing by itself, since a sum is defined exactly where its terms are, so no condition is invented where nothing was cancelled. That has a test too.
Scope
Offered and not applied: nothing in the library runs it, and a test pins that. No
BREAKING-CHANGES.mdentry — no input gives a different answer. The new public property is inPublicApi.txt.Suite 6998 passed / 0 failed, of which 38 are new; F# 130/130; casbench 116/119 with 0 wrong, 0 error, 0 timeout; propcheck 1340/0; rootcheck 596/596; simpsweep 10463/10463.
🤖 Generated with Claude Code