Skip to content

Drop the absolute value from an antiderivative unless the codomain is real (#946) - #952

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
fix/ln-abs-by-codomain
Aug 15, 2026
Merged

Drop the absolute value from an antiderivative unless the codomain is real (#946)#952
Rafael-SOWNet merged 1 commit into
masterfrom
fix/ln-abs-by-codomain

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator

Closes #946.

ln(abs(f)) is an antiderivative of f'/f on the real line and nowhere elseabs is not holomorphic, so differentiating it off the line does not return the integrand. The integral table produced it unconditionally, including under the default codomain, which is Domain.Complex. So the default answer to the simplest integral in the library was not an antiderivative of anything.

@Happypig375 answered the issue with "Might need to separate by codomain", which is what this does.

"1/x".Integrate("x")        // was: ln(abs(x)) + C        now: ln(x) + C
"tan(x)".Integrate("x")     // was: -ln(abs(cos(x))) + C  now: -ln(cos(x)) + C

The old answer is still available, and is now a statement about where you are working rather than the only thing on offer:

using var _ = MathS.Settings.Codomain.Set(Domain.Real);
"1/x".Integrate("x")        // ln(abs(x)) + C, as before

The setting already had precedent — Patterns.Power.cs asks the same question the same way, and this uses the same form.

Blast radius, measured rather than assumed

The issue reads as one rule. It is twelve sites, of which eleven change. All eleven introduce the absolute value themselves, and all now route through one helper:

∫ 1/x (the issue's own case) IndefiniteIntegralSolver
∫ csc, ∫ tan, ∫ cot IntegralPatterns
∫ arctan, ∫ arccotan IntegralPatterns
linear/linear, constant/linear, linear/quadratic IntegralPatterns
the radical and negative-discriminant cases IntegralPatterns

The twelfth is deliberately left alone. ∫ ln(abs(ax + b)) dx = ((ax+b)/a)(ln(abs(ax+b)) − 1) keeps its absolute value, because there it comes from the integrand the caller wrote rather than from the rule. Changing it would alter an answer that is already correct on both domains. The applying script asserts that rule is still present, so the distinction cannot rot silently.

Behaviour

Under Domain.Real every one of the eleven returns exactly what it returned before, so this is a no-op for a caller who has said where they are working. Only the default changes, and it changes from a non-antiderivative to an antiderivative.

Recorded in BREAKING-CHANGES.md, marked Silent — the call still succeeds and quietly returns a different expression, which is the class of change worth reading first.

Evidence

  • suite 7234 passed, 0 failed, 14 skipped
  • casbench 116/119, 0 wrong / 0 error / 0 timeout — identical to its baseline. This is the harness that matters most here, because it verifies an antiderivative by differentiating it back rather than by comparing to stored text.
  • probed on a build: 1/x → ln(x) + C, tan(x) → -ln(cos(x)) + C, 1/(2x+5) → ln(2x+5)/2 + C, arctan(x) → x·arctan(x) − ln(1+x²)/2 + C

Nineteen existing tests failed before the test changes, all from this one cause. They asserted the real-line forms, so they are now scoped to Domain.Real and keep binding rather than being edited to agree with the new output — the two InnerSimplifyTest cases are the exception, because they pinned the default and so their expected value is what changed. Two new theories cover the default and the real setting explicitly, so both branches of the separation are tested.

What it does not do

  • Does not touch Simplify or evaluation — only the integral table.
  • Does not revisit the piecewise decomposition of the quadratic-denominator cases, whose branch conditions are real-ordering tests. Those branches now emit a plain logarithm under a complex codomain, which is an improvement on emitting a non-antiderivative, but whether that decomposition is the right shape off the real line is a separate question and not this PR's.
  • Does not change Domain.Real behaviour at all.

🤖 Generated with Claude Code

… real (#946)

ln(abs(f)) is an antiderivative of f'/f on the real line and nowhere else: abs is not
holomorphic, so differentiating it off the line does not return the integrand. The
integral table produced it unconditionally, including under the default codomain,
which is the complex plane -- so the default answer to the simplest integral in the
library was not an antiderivative.

#946, answered there with
"might need to separate by codomain", which is what this does.

Eleven rules introduced the absolute value themselves and all eleven now ask
AntiderivativeLog. The rule for the integral of ln(abs(ax + b)) is deliberately not
among them: there the absolute value is the integrand the caller wrote rather than
something the rule added, so it stays.

Under Domain.Real every one of them returns exactly what it returned before, so the
change is a no-op for a caller who has said where they are working. The existing
integration tests asserted the real-line forms, so they are now scoped to Domain.Real
and keep binding; two new theories cover the default and the real setting explicitly.
The two InnerSimplify tests pinned the default and record the new value.

Suite 7234 passed, 0 failed. casbench 116/119 with 0 wrong, 0 error, 0 timeout --
unchanged, and it verifies an antiderivative by differentiating it back.
@Rafael-SOWNet
Rafael-SOWNet merged commit 1a529fe into master Aug 15, 2026
25 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the fix/ln-abs-by-codomain branch August 15, 2026 20:37
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.

The integral of 1/x is ln(abs(x)), which is not an antiderivative off the real line

1 participant