Skip to content

Commit 0fbef54

Browse files
authored
Merge pull request #15 from s-celles/feat/commonsolve-integration
feat: integrate CommonSolve.jl (subsume PR #7)
2 parents 3a631bc + 52ada8a commit 0fbef54

6 files changed

Lines changed: 125 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6262
`true` for free identifiers, ordinary symbolic expressions, and finite
6363
numbers; `false` for `inf`, `-inf`, and `1/0` (which GIAC normalizes to
6464
infinity). Implemented as `!to_julia(isinf(expr))::Bool`.
65+
- **CommonSolve.jl integration**: `Giac.Commands.solve` is now the same generic
66+
function as `CommonSolve.solve` (`Giac.Commands.solve === CommonSolve.solve`),
67+
so Giac's `solve` participates in the broader Julia "solve" verb ecosystem
68+
alongside `DifferentialEquations.jl`, `NLsolve.jl`, `Symbolics.jl`, etc.
69+
Dispatch is by argument type, so there is no conflict — `solve(::GiacExpr, …)`
70+
routes to GIAC, `solve(::ODEProblem, …)` routes to DifferentialEquations,
71+
and so on. `CommonSolve` is a tiny hard dependency (~50 LOC, compat `0.2`).
72+
Note that `CommonSolve` also exports `init` and `solve!` as part of an
73+
iterative-solver protocol; Giac is a symbolic CAS (non-iterative) so only
74+
`solve` is extended — `init` and `solve!` are left untouched and remain
75+
available for other packages to extend without conflict.
76+
Contributed by [@jverzani](https://github.com/jverzani) in
77+
[PR #7](https://github.com/s-celles/Giac.jl/pull/7).
6578

6679
### Changed
6780

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.12.0"
44
authors = ["Sébastien Celles <s.celles@gmail.com>"]
55

66
[deps]
7+
CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
78
CxxWrap = "1f15a43c-97ca-5a2a-ae31-89f07a497df4"
89
GIAC_jll = "cf749d6c-42f5-550d-8800-4812740c2942"
910
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
@@ -25,6 +26,7 @@ GiacTermInterfaceExt = "TermInterface"
2526
[compat]
2627
Aqua = "0.8"
2728
CSV = "0.10"
29+
CommonSolve = "0.2"
2830
CxxWrap = "0.16, 0.17"
2931
DataFrames = "1.6, 1.7, 1.8"
3032
Documenter = "1"

docs/src/mathematics/linear_algebra.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,46 @@ solve([x + y ~ 3, x - y ~ 1], [x, y])
260260
# Returns the solution
261261
```
262262

263+
`Giac.Commands.solve` is the same generic function as
264+
[`CommonSolve.solve`](https://github.com/SciML/CommonSolve.jl), so it shares
265+
the "solve" verb with `DifferentialEquations.jl`, `NLsolve.jl`, `Symbolics.jl`,
266+
etc. Dispatch is by argument type, so there is no name conflict when both
267+
packages are loaded — `solve(eq::GiacExpr, var)` routes to Giac,
268+
`solve(prob::ODEProblem, …)` routes to DifferentialEquations, and so on.
269+
270+
```julia
271+
using Giac.Commands: solve # this is the same function as CommonSolve.solve
272+
273+
solve(x^2 - 1 ~ 0, x) # → list[-1, 1]
274+
# The `~` operator builds a symbolic equation; Giac dispatches the
275+
# CommonSolve.solve verb to its CAS solver.
276+
```
277+
278+
Either side of the equation works:
279+
280+
```julia
281+
solve(x^2 ~ 1, x) # → list[-1, 1]
282+
solve(x^2 - 1, x) # → list[-1, 1] (Giac infers `= 0`)
283+
```
284+
285+
!!! note "Why `solve` only, and not `init` / `solve!`"
286+
287+
`CommonSolve` defines three verbs — `init`, `solve`, and `solve!` — to
288+
support the iterative-solver protocol used by `DifferentialEquations.jl`
289+
and friends (`init(prob, …)` builds an integrator, `solve!` advances it,
290+
`solve` is a one-shot wrapper that does both). Giac is a one-shot
291+
symbolic solver — there is no integrator state to advance — so only
292+
`solve` has a natural meaning here. `Giac` therefore extends
293+
`CommonSolve.solve` and leaves `init` and `solve!` untouched, so
294+
packages that *do* implement the iterative protocol can extend them
295+
without any conflict.
296+
297+
Giac also has its own ODE solver, `Giac.Commands.desolve` (symbolic) and
298+
`Giac.Commands.odesolve` (numerical) — see
299+
[Differential Equations](differential_equations.md). Those are
300+
intentionally separate verbs (not aliased to `solve`) because their
301+
signatures don't fit the algebraic `solve(eq, var)` shape.
302+
263303
## Matrix Rank
264304

265305
Compute the rank of a matrix using `invoke_cmd(:rank, ...)`:

src/Commands.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ using ..Giac: GiacExpr, GiacMatrix, GiacInput, GiacError, giac_eval, with_giac_l
6262
HeldCmd
6363
import LinearAlgebra
6464

65+
import CommonSolve: solve
66+
6567
# ============================================================================
6668
# Core Command Invocation (invoke_cmd)
6769
# ============================================================================

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ using LinearAlgebra
3333
# Commands submodule tests (009-commands-submodule)
3434
include("test_commands_submodule.jl")
3535

36+
# CommonSolve integration (PR #7)
37+
include("test_commonsolve.jl")
38+
3639

3740
# Macro tests (011-giac-symbol-macro)
3841
include("test_macros.jl")

test/test_commonsolve.jl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Tests for CommonSolve.solve integration (PR #7 by @jverzani)
2+
# Verifies that Giac.Commands.solve and CommonSolve.solve refer to the same
3+
# generic function, so Giac plays well with the broader Julia "solve" verb
4+
# ecosystem (DifferentialEquations.jl, NLsolve.jl, Symbolics.jl, …).
5+
6+
using CommonSolve
7+
8+
@testset "CommonSolve integration" begin
9+
10+
@testset "solve identity: Giac.Commands.solve === CommonSolve.solve" begin
11+
@test Giac.Commands.solve === CommonSolve.solve
12+
end
13+
14+
@testset "solve dispatches Giac methods through CommonSolve" begin
15+
@giac_var x
16+
17+
# Calling CommonSolve.solve on a GiacExpr equation routes to Giac's
18+
# auto-generated solve method (added to CommonSolve.solve via Julia's
19+
# extend-imported-function mechanism).
20+
result = CommonSolve.solve(x^2 - 1, x)
21+
@test result isa GiacExpr
22+
23+
result_str = string(result)
24+
@test occursin("-1", result_str)
25+
@test occursin("1", result_str)
26+
end
27+
28+
@testset "solve via Giac.Commands and via CommonSolve produce equal results" begin
29+
using Giac.Commands: solve as gsolve
30+
@giac_var x y
31+
32+
# Single equation
33+
@test string(gsolve(x^2 - 4, x)) == string(CommonSolve.solve(x^2 - 4, x))
34+
35+
# System of equations
36+
@test string(gsolve([x + y ~ 3, x - y ~ 1], [x, y])) ==
37+
string(CommonSolve.solve([x + y ~ 3, x - y ~ 1], [x, y]))
38+
end
39+
40+
@testset "GiacMatrix path also extends CommonSolve.solve" begin
41+
# The auto-generator emits a GiacMatrix overload for every command, so
42+
# CommonSolve.solve(::GiacMatrix, …) should dispatch the same way.
43+
@giac_var x
44+
# `solve` on a matrix isn't a typical use case, but the method must
45+
# exist for dispatch consistency.
46+
@test hasmethod(CommonSolve.solve, Tuple{GiacMatrix, Vararg{Any}})
47+
end
48+
49+
@testset "init / solve! are NOT extended by Giac" begin
50+
# Giac is a symbolic CAS (non-iterative), so only CommonSolve.solve is
51+
# integrated. CommonSolve.init and CommonSolve.solve! must remain
52+
# un-shadowed by anything in Giac so packages implementing the
53+
# iterative-solver protocol can extend them without conflict.
54+
@giac_var x
55+
56+
# No Giac method on init or solve! for symbolic inputs.
57+
@test !hasmethod(CommonSolve.init, Tuple{GiacExpr, Vararg{Any}})
58+
@test !hasmethod(CommonSolve.solve!, Tuple{GiacExpr, Vararg{Any}})
59+
60+
# And the bindings themselves are not aliased by Giac (they live in
61+
# CommonSolve only).
62+
@test parentmodule(CommonSolve.init) === CommonSolve
63+
@test parentmodule(CommonSolve.solve!) === CommonSolve
64+
end
65+
end

0 commit comments

Comments
 (0)