Skip to content

fix(solver): preserve exact π in periodic transcendental solve#1844

Open
s-celles wants to merge 1 commit into
JuliaSymbolics:masterfrom
s-celles:002-fix-transcendental-solve
Open

fix(solver): preserve exact π in periodic transcendental solve#1844
s-celles wants to merge 1 commit into
JuliaSymbolics:masterfrom
s-celles:002-fix-transcendental-solve

Conversation

@s-celles
Copy link
Copy Markdown

@s-celles s-celles commented Apr 21, 2026

symbolic_solve(cos(x) ~ 1//2, x) returned
1.0471975511965976 + 6.283185307179586·n — a floating-point approximation — instead of π/3 + 2π·n. The defect had three collaborating sites in the periodic-equation path:

  • isolate applied the left-inverse operator eagerly (acos(1//2) ⇒ Float64) instead of keeping a symbolic term that convert_consts could canonicalise downstream. Fixed by wrapping as Symbolics.term(invop, sol; type = Real) on both the periodic and non-periodic branches.

  • fundamental_period returned 2pi / 1pi — Julia promotes Int * Irrational{:π} to Float64, so the period coefficient was 6.2832… / 3.1416…. Fixed by returning Symbolics.term(*, 2, pi) and Symbolics.term(*, 1, pi) for the sin/cos/csc/sec and tan/cot families. The degree- and cycle-based variants (sind, cosd, tand, cotd, cospi) are left unchanged — their periods are rationals.

  • _postprocess_root rebuilt expression trees via SymbolicUtils.maketerm, which folds known-function calls on constant operands — so maketerm(BasicSymbolic, /, [π, 3], nothing) collapsed back to 1.0471… on the second iteration of the postprocess_root fixed-point loop. Fixed by rebuilding via Symbolics.term when any argument is an irrational math constant (π or e).

  • The canonical-value lookup table in postprocess.jl was missing 3π/4 (acos(-√2/2)); added.

  • The complex-roots angular factor in isolate for x^n ~ y used plain pi which promoted to Float64; wrapped symbolically.

Tests: new @testset "Exact transcendental solve (#1842)" in test/solver.jl covers every canonical sin/cos/tan right-hand side, the symbolic period coefficient, and non-canonical preservation (152 assertions). The pre-existing @test_broken at test/solver.jl:632 (composed-argument sin+cos case) still fails after this fix — it exercises the attract path which is out of scope here; a comment now documents that.

Docs: new example in docs/src/manual/solver.md.

Helps #1842.

`symbolic_solve(cos(x) ~ 1//2, x)` returned
`1.0471975511965976 + 6.283185307179586·n` — a floating-point approximation —
instead of `π/3 + 2π·n`. The defect had three collaborating sites in the
periodic-equation path:

- `isolate` applied the left-inverse operator eagerly (`acos(1//2)` ⇒ Float64)
  instead of keeping a symbolic term that `convert_consts` could canonicalise
  downstream. Fixed by wrapping as `Symbolics.term(invop, sol; type = Real)`
  on both the periodic and non-periodic branches.

- `fundamental_period` returned `2pi` / `1pi` — Julia promotes
  `Int * Irrational{:π}` to `Float64`, so the period coefficient was
  `6.2832…` / `3.1416…`. Fixed by returning `Symbolics.term(*, 2, pi)` and
  `Symbolics.term(*, 1, pi)` for the `sin`/`cos`/`csc`/`sec` and `tan`/`cot`
  families. The degree- and cycle-based variants (`sind`, `cosd`, `tand`,
  `cotd`, `cospi`) are left unchanged — their periods are rationals.

- `_postprocess_root` rebuilt expression trees via
  `SymbolicUtils.maketerm`, which folds known-function calls on constant
  operands — so `maketerm(BasicSymbolic, /, [π, 3], nothing)` collapsed back
  to `1.0471…` on the second iteration of the `postprocess_root` fixed-point
  loop. Fixed by rebuilding via `Symbolics.term` when any argument is an
  irrational math constant (`π` or `e`).

- The canonical-value lookup table in `postprocess.jl` was missing `3π/4`
  (`acos(-√2/2)`); added.

- The complex-roots angular factor in `isolate` for `x^n ~ y` used plain
  `pi` which promoted to Float64; wrapped symbolically.

Tests: new `@testset "Exact transcendental solve (JuliaSymbolics#1842)"` in
`test/solver.jl` covers every canonical `sin`/`cos`/`tan` right-hand side,
the symbolic period coefficient, and non-canonical preservation (152
assertions). The pre-existing `@test_broken` at `test/solver.jl:632`
(composed-argument `sin+cos` case) still fails after this fix — it exercises
the `attract` path which is out of scope here; a comment now documents that.

Docs: new example in `docs/src/manual/solver.md`.

Closes JuliaSymbolics#1842.
@s-celles
Copy link
Copy Markdown
Author

(@v1.12) pkg> activate .
Activating project at `~/work/tries/Symbolics.jl`

julia> using Symbolics

julia> @variables x
1-element Vector{Num}:
x

julia> Symbolics.symbolic_solve(cos(x) ~ 1//2, x)
[ Info: var"##283" ϵ Ζ
1-element Vector{SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}}:
π / 3 + var"##283"*(2*π)

@s-celles
Copy link
Copy Markdown
Author

s-celles commented Apr 28, 2026

Be aware that $-\frac{\pi}{3} + k 2 \pi$ solutions are (still) missing here.

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.

1 participant