Repository navigation
Nothing writes an expression as a single quotient: no together / ratsimp #1239
Description
There is no operation that writes an expression as a single quotient. Simplify never combines a sum over a common denominator, at any level:
"1 + 2/(1+t^2)" Simplify -> 1 + 2 / (1 + t ^ 2)
"3 + 10*t/(t^2+1)" Simplify -> 3 + 10 * t / (t ^ 2 + 1)
"1/(t^2+1) + 1/(t+1)" Simplify -> 1 / (1 + t ^ 2) + 1 / (1 + t)
"a + b/c" Simplify -> a + b / c
Simplify(5) gives the same. Transformation.Rewriting(RewriteRules.CollapseMultipleFractions) does not do it either — that one moves nesting around without combining terms. Transformation.Rationalization is about clearing a surd from a denominator, which is a different job.
This is not a defect in Simplify. Putting a sum over a common denominator is a choice that makes some expressions worse, which is why other systems keep it as a separate operation — SymPy has together(), Maxima has ratsimp, Mathematica has Together. We do not have it at all, and that is the gap.
Where it bites
It is the blocker for the rest of the half-angle substitution family added in #1238. That rewrite turns a rational function of sine and cosine into a rational function of t = tan(x/2), and partial fractions answers the result — but only when the result is a single Divf of two polynomials. Where the rewrite leaves a sum with a fraction in it, nothing downstream can read it:
| integrand | rewrites to | outcome |
|---|---|---|
1/(1+cos(x)) |
1 |
answered, tan(x/2) |
1/sin(x) |
2/(1+t^2) / (2t/(1+t^2)), which collapses |
answered, ln(tan(x/2)) |
1/(1-sin(x)) |
2/((t^2+1) * (1 + (-2)t/(t^2+1))) |
declined |
1/(1+cos(x)/2) |
2/((t^2+1) * (1 + (2/(t^2+1) - 1)/2)) |
declined |
1/(2cos(x)+3sin(x)) |
same shape | declined |
Every declined row needs one step: distribute the outer factor into the bracket. (t^2+1) * (1 + (-2)t/(t^2+1)) is t^2 - 2t + 1, and then partial fractions answers it immediately.
I checked by hand that the algebra is otherwise right — building the quotient manually gives 2/(1 + t^2 - 2t) for the third row, which is 2/(t-1)^2 — so the substitution and the integrator are both fine and only the intermediate shape is in the way.
What it would take
A transformation that writes an expression as numerator / denominator with a single division at the root: recursively, a sum of quotients becomes one quotient over the product of the denominators, and a product of quotients multiplies through. It wants to be an explicit operation rather than part of Simplify, for the reason every other system keeps it separate.
Beyond unblocking the above, it is independently useful — it is a thing users ask a CAS to do — and it would feed partial fractions generally, since that already wants its input in exactly this shape and currently just declines anything that is not.
Activity
An expression can be written as one quotient, and 51 more Rubi integr…
A logarithm of a nested quotient is written over one bar under the ex…
Rafael-SOWNet commentedon Sep 30, 2026
Measured on master at 2eeb163: #1240 does this internally for the integrator (
SingleQuotient), but nothing public does it yet.CommonDenominatorputs a sum of quotients over one denominator, and a whole term beside a quotient stays as written.Proposal: expose
SingleQuotientasEntity.Together(), withMathS.TogetherandTransformation.Together, which is the name SymPy and Mathematica use. It returns one numerator over one denominator, cancels nothing, and is never applied bySimplify, as the issue says. OK to add?Happypig375 commentedon Sep 30, 2026
"Together" sounds too vague, do mathematicians use this terminology, or is it a jargon artifact of engineers?
Rafael-SOWNet commentedon Sep 30, 2026
Jargon, mostly:
Togetheris Mathematica's name, and SymPy copied it. A mathematician says write it as a single fraction, or put it over a common denominator. So I'd name the result:AsSingleFraction(), withMathS.AsSingleFractionandTransformation.AsSingleFraction.OverCommonDenominator()would name the method instead, but it sits too close toRewriteRules.CommonDenominator, which does only part of the job. Which do you prefer?Happypig375 commentedon Sep 30, 2026
AsSingleFractionseems better because ofasfeeling like seeing from a different perspective.You should update the instructions to do critical thinking on naming and logic instead of copying blindly too.
Rafael-SOWNet commentedon Sep 30, 2026
AsSingleFractionit is, and I'm building it now. The instructions are in #1617: a name is what a textbook calls the operation, and states the result; another system is only corroboration; and logic taken from one is checked against the mathematics before it is adopted.Rafael-SOWNet commentedon Sep 30, 2026
A correction to my first comment. "Nothing public does it yet" was wrong: I measured the rule sets and missed
Transformation.RationalCanonicalization(#934), which already does this for rational functions. On master at 2eeb163:It is a canonical form: reduced by the gcd, the denominator expanded and monic, and nothing outside rational functions over QQ. So
AsSingleFractionhas a narrower job than I said. It writes any expression as one fraction, functions included, sosin(x) + 1/cos(x)becomes(sin(x) cos(x) + 1) / cos(x). It cancels and expands nothing, so the denominator stays(1 + t)(1 + t^2). I'll build it with that scope and point its docs atRationalCanonicalizationfor the reduced form, unless you'd rather not have both.Happypig375 commentedon Sep 30, 2026
It feels like the same operation at two different cost levels, maybe there is a better modeling approach here
Rafael-SOWNet commentedon Sep 30, 2026
I don't think they are one operation at two costs. They share their first step and then promise different things. I'd model them as one pipeline with named stages:
AsSingleFraction. Any expression, and nothing cancelled.provided g ≠ 0.CanonicalizeAsRationalFunctionis 1 → 2 → 3, and it answers only for rational functions over ℚ. That is where lowest terms is unique, and uniqueness is the point of it: two equal rational functions give the identical tree, so comparing trees decides equality.A cost setting on one method doesn't fit, because the higher setting would answer less than the lower one.
sin(x) + 1/cos(x)can be written as one fraction, and the canonical form declines it, as it has to.sinandcosare not independent, so a gcd that treats them as separate letters gives a form that isn't unique:sin(x)^2 + cos(x)^2and1would come out different. Falling back to stage 1 there would hand back something that looks canonical and isn't. The costs don't separate them either. On four small inputs, three of them the issue's, the canonical form was the faster of the two: 8–14 µs against 9–18 µs, best of five passes of 2000 calls.What the code doesn't have yet is the pipeline.
RationalFunctiongathers into one quotient itself, in polynomial arithmetic, andSingleQuotientdoes it again on expressions. Both have the same defect in the same place. Dividing by a fraction moves its denominator into the numerator and loses the fact that it was nonzero. On master at fc9c0c2 (neither file has changed since f633765, where I measured):That breaks O4 of the simplification contract: "what is forbidden is turning undefined into a value". The
AsSingleFractionbranch had the same gap. It now answers1/(1/x)withx provided not x = 0, and(1/x)/(1/x)staysx / xwith no condition, becausexis still in the denominator (branchas-single-fraction, 2f28c3f).So I'll:
1/x + 1/x^2is gathered overx · x^2, where a textbook usesx^2. Then I'll open theAsSingleFractionPR;Simplifycalls the canonical form inside the interval-membership rule (RewriteRules.Common does not terminate: a three-cycle on -x * 1/2 #1056);Does that model work for you?
A name, like a behaviour, is chosen and not copied (#1617)
An expression can be written as a single fraction: AsSingleFraction
Rafael-SOWNet commentedon Sep 30, 2026
#1622 is up and green: stage 1, with the least common denominator and the
provideda turned-over denominator owes. I'm holding the merge until you've decided on the model above, because merging closes this issue. The canonical form's domain fix (#1618) follows it.Happypig375 commentedon Sep 30, 2026
Looks good to me