Skip to content

Fix reciprocal power simplification loop - #3701

Open
belomaxorka wants to merge 1 commit into
josdejong:developfrom
belomaxorka:fix/simplify-reciprocal-power-loop
Open

belomaxorka wants to merge 1 commit into
josdejong:developfrom
belomaxorka:fix/simplify-reciprocal-power-loop

Conversation

@belomaxorka

@belomaxorka belomaxorka commented Sep 20, 2026

Copy link
Copy Markdown

Fixes #3693.

simplify never terminates on (1/(x*(y-1)))^(1/(y-1)), and on symbolic powers of any reciprocal product.

Cause

n/n1 -> n*n1^-1 rewrites 1/X as 1 * X^-1. Distributing a power over that product leaves a 1^n factor, and nothing in the rule set removes it. Each pass creates a fresh one and merges it into the accumulated factor through n^n1 * n^n2 -> n^(n1+n2), so the exponent keeps growing and the expression string never repeats:

(1 / (x * y)) ^ z
(1 ^ z) * (((1 / x) / y) ^ z)
((1 ^ z) ^ 2) * (((1 / x) / y) ^ z)
(1 ^ (3 * z)) * (((1 / x) / y) ^ z)
(1 ^ (4 * z)) * (((1 / x) / y) ^ z)
...

_simplify stops when it revisits a string, so it loops forever. simplifyCore already folds n^0 and n^1, but not 1^n.

Change

One rule, 1^n -> 1, placed after the power distribution rules so the unit factor is removed where it is created.

Effect on existing results

Checked 1766 expressions against the current develop:

  • 1285 unchanged;
  • 143 previously non-terminating, now converge — no expression that converged before loops now, and no new errors;
  • 338 changed, and in every one of them the develop output contained a stray 1 ^ ... factor that is now gone, for example 1 ^ z * (x + 1) ^ z / 2 ^ z becomes (x + 1) ^ z / 2 ^ z.

All 338 changed outputs were compared numerically against the develop output at 20 points each with complex-aware comparison: no differences. The 143 newly converging results were compared against their original expressions the same way: no differences.

The output form of expressions that already worked is otherwise untouched — 1/x^2 stays 1 / x ^ 2 and x^(-2) stays x ^ (-2).

Tests

should terminate when simplifying symbolic powers of reciprocals covers five looping expressions with a pass counter that fails instead of hanging the runner, asserts the simplified form and checks it numerically against the original. should remove unit factors raised to a power covers the rule itself. Both fail on develop and pass here. One case was added to the non-commutative-multiplication block.

npm run test:all passes on Node 26: 6654 source, 36 generated and 295 node tests, plus tsc and the TypeScript tests. eslint --max-warnings 0 is clean.

`n/n1 -> n*n1^-1` rewrites `1/X` as `1 * X^-1`, and distributing a power
over that product creates a `1^n` factor. Nothing eliminates it, so every
pass merges a freshly created `1^n` into the accumulated one through
`n^n1 * n^n2 -> n^(n1+n2)`. The exponent keeps growing, the expression
string never repeats, and `_simplify` never reaches its fixed point.

Add `1^n -> 1` after the power distribution rules, so the unit factor is
removed where it appears. Expressions that already converged keep their
results, except that stray `1^...` factors are no longer left behind in
the output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@belomaxorka
belomaxorka force-pushed the fix/simplify-reciprocal-power-loop branch from 747b5b6 to 78c9f28 Compare September 22, 2026 04:34
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.

Infinite loop when simplifying

1 participant