Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/TagBot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@ jobs:
steps:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Use a PAT (not GITHUB_TOKEN) so the tag push triggers downstream
# workflows like Documentation and CI. Events created with
# GITHUB_TOKEN are deliberately not propagated by GitHub Actions.
token: ${{ secrets.TAGBOT_PAT }}
ssh: ${{ secrets.DOCUMENTER_KEY }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
!README.md
!LICENSE
!CHANGELOG.md
!CONTRIBUTORS.md

!Project.toml

Expand All @@ -19,6 +20,9 @@
!test/
!test/*.jl

!bench/
!bench/*.jl

!examples/
!examples/*.jl
!examples/*.png
Expand Down
89 changes: 89 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,95 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.14.1] - 2026-05-11

### Added

- **MCP server integration**: `giac_mcp_server()` exposes Giac's CAS engine
to MCP-aware LLM clients (Claude Desktop, Claude Code, Cursor, …) through
a new weak-dependency package extension `GiacMCPExt` on
[`ModelContextProtocol.jl`](https://github.com/JuliaSMLM/ModelContextProtocol.jl).
The server advertises two tools — `giac_eval` (Giac/Xcas expression in →
textual result out, with `CallToolResult(isError=true, ...)` for genuine
Julia exceptions) and `giac_search` (keyword in → matching command names
out, with a prefix-then-substring fallback so LLM-style queries like
`"matrix"` or `"prime"` surface relevant commands). The MCP `initialize`
handshake's `serverInfo.version` defaults to the running Giac.jl version
so clients always see the right number. Users who do not load
`ModelContextProtocol.jl` are unaffected — no transitive dependency, no
precompilation cost. See `docs/src/extensions/mcp.md` for the full setup
guide.

- **Example MCP prompts in the documentation**: `docs/src/extensions/mcp.md`
now ships a curated "Example prompts" gallery — French and English
direct-style prompts (`factorise avec giac x²-1`, `with giac, factor
x^4 - 1`) plus natural-language, story-style prompts that exercise the
LLM's judgement when routing to `giac_eval` (e.g., *"between which two
integers does the real root of x^3 + x - 1 = 0 lie?"*, *"my password is
the prime just after one billion — what is it?"*). A separate
`giac_search` block shows catalogue-discovery prompts
(*"which commands deal with matrices?"*).

- **Direct `Gen` fast path for `invoke_cmd` / `giac_cmd` (spec 069)**: the
generic command dispatcher now bypasses the GIAC parser when all arguments
have a direct `Gen` representation (`GiacExpr`, `Int32`, Int32-fitting
`Int64`, finite `Float64`). The path resolves arguments through
`_get_gen_or_eval` / `Gen(Int32(x))` / `Gen(Float64(x))` and routes to
`apply_func0`/`apply_func1`/`apply_func2`/`apply_func3` (positional, zero
`StdVector` allocation) for arity 0–3 and `apply_funcN` with a
`StdVector{Gen}` for arity ≥ 4. Geometric-mean speed-up across the standard
workload mix is ≈ 1.5× with per-workload wins up to ≈ 2× on commands whose
result is a long symbolic expression (`factor`, `expand`). The existing
string-concatenation path is preserved as a fallback for `Rational`,
`Complex`, `AbstractIrrational`, `AbstractVector`, `GiacMatrix`,
`±Inf`/`NaN`, `Symbol`, `String`, `DerivativeCondition`, `DerivativePoint`,
`Function`, `BigInt`, `Int128`, and out-of-Int32-range `Int64` — all
existing call shapes continue to work unchanged. Beyond the speed-up, the
fast path structurally eliminates the `Gen → string → parse → Gen`
round-trip class of bug that motivated `_giac_subst_vec_tier1` (spec 065).
Set `GIAC_INVOKE_CMD_STRING_PATH=1` to disable globally.

- **`CONTRIBUTORS.md`**: a top-level acknowledgements file listing the
people who built, reviewed, and inspired this package — Giac authors
(Bernard Parisse & Renée De Graeve), the original `Giac.jl`
(Harald Hofstaetter), Julia ecosystem reviewers (Viral B. Shah,
Mosè Giordano, Max Horn), code contributors (John Verzani),
feature/bug-report contributors (Thibault Duretz), and methodology
inspiration (Sam Abbott). Linked from the README.

### Fixed

- **`D` operator now accepts Unicode identifiers**: `D(ϕ)` on a function
variable defined as `@giac_var 𝑧 ϕ(𝑧)` previously failed with
`ArgumentError: D() requires a function expression like u(t)`. The
internal parser regex (`_parse_function_expr`) only matched ASCII
letters, even though GIAC C++ and Julia both accept Unicode names. The
regex now uses Unicode letter/number classes (`\p{L}`, `\p{N}`), so
Greek letters, mathematical italics, and other Unicode identifiers work
with `D(u)`, `D(u, n)`, and chained forms. Reported by
[@tduretz](https://github.com/tduretz).

- **`is_constant` now recognizes the GIAC `infinity` and `undef`
atoms.** Previously, `is_constant(giac_eval("inf"))` returned `false`
because `infinity` was treated as a free identifier. After this fix,
any expression built from these atoms — including `inf`, `+inf`,
`-inf`, `+infinity`, `-infinity`, `unsigned_inf`, `1/0` (which GIAC
evaluates to `+infinity`), and `0/0` (which evaluates to `undef`) —
is correctly classified as a constant. `Giac.Constants.is_giac_constant`
picks up these atoms via a name-based fallback, since GIAC's internal
`==` reports `infinity == infinity` as `false`. As a follow-on fix,
`to_julia` no longer infinitely recurses on these irreducible atoms
(`evalf` is a no-op on them); it returns the `GiacExpr` unchanged,
matching the prior public behavior. Closes
[#19](https://github.com/s-celles/Giac.jl/issues/19).

Note: names like `nan`, `NaN`, `unsigned_infinity`, and `undefined`
are *not* GIAC atoms — GIAC parses them as ordinary free identifiers
(e.g. `nan + 1` yields `nan+1` exactly like `xyz + 1` yields `xyz+1`),
so they remain non-constant.

## [0.14.0] - 2026-05-02

### Added
Expand Down
61 changes: 61 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Contributors & Acknowledgements

Giac.jl is developed and maintained by Sébastien Celles
([@s-celles](https://github.com/s-celles)) — PRAG, IUT de Poitiers,
Département GEII.

This package would not exist without the work of many people. Thanks to
everyone who has built, reviewed, tested, suggested features, or filed
issues.

## Giac side

- **Bernard Parisse** & **Renée De Graeve** (Université Grenoble Alpes) —
authors of the [Giac/Xcas](https://www-fourier.univ-grenoble-alpes.fr/~parisse/giac.html)
computer algebra system that this package wraps.
- **Harald Hofstaetter**
([@HaraldHofstaetter](https://github.com/HaraldHofstaetter)) — author
of the original
[Giac.jl](https://github.com/HaraldHofstaetter/Giac.jl), which
inspired this rewrite.

## Julia ecosystem

- **Viral B. Shah** ([@ViralBShah](https://github.com/ViralBShah)) —
Julia co-creator; advice on Yggdrasil packaging.
- **Mosè Giordano** ([@giordano](https://github.com/giordano)) and
**Max Horn** ([@fingolfin](https://github.com/fingolfin)) — reviewers
on Yggdrasil / BinaryBuilder for the `GIAC_jll` and
`libgiac_julia_jll` recipes.
- **John Verzani** ([@jverzani](https://github.com/jverzani)) — early
tester and contributor of multiple pull requests in v0.12:
[#6](https://github.com/s-celles/Giac.jl/pull/6) (function-arg
interaction with `substitute`),
[#8](https://github.com/s-celles/Giac.jl/pull/8) (introspection),
[#9](https://github.com/s-celles/Giac.jl/pull/9) and
[#16](https://github.com/s-celles/Giac.jl/pull/16) (math operators),
[#10](https://github.com/s-celles/Giac.jl/pull/10) (`GiacMatrix`
iteration and linear indexing).

## Ideas, feedback, and bug reports

- **Thibault Duretz** ([@tduretz](https://github.com/tduretz)) —
- Suggested the `build_function` feature on the Julia Discourse
announcement thread:
[discourse.julialang.org/t/136681/6](https://discourse.julialang.org/t/ann-giac-jl-julia-interface-to-the-giac-computer-algebra-system/136681/6).
Implemented in v0.13 (Tier 1) and v0.14 (Tier 3, `:symbolics`
backend).
- Reported the `D(ϕ)` failure on Unicode identifiers, fixed in
v0.14.1.

## Methodology

- **Sam Abbott** ([@seabbs](https://github.com/seabbs)) — inspiring
Julia development skill
([seabbs/claude#6](https://github.com/seabbs/claude/issues/6)).

## How to contribute

Contributions are welcome — issues, ideas, and pull requests. See the
[README](README.md) for development setup and the
[CHANGELOG](CHANGELOG.md) for the project history.
8 changes: 6 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Giac"
uuid = "e4421f97-9838-4fd0-9fa5-94f11373bf78"
version = "0.14.0"
version = "0.14.1"
authors = ["Sébastien Celles <s.celles@gmail.com>"]

[deps]
Expand All @@ -15,10 +15,12 @@ libgiac_julia_jll = "ec39d2da-6bdf-580b-ada4-cd6e059515c9"

[weakdeps]
MathJSON = "77215b4b-6f01-425c-beac-950ae6536d4d"
ModelContextProtocol = "c58f755f-f2a7-4f48-bf29-4e9659b78499"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c"

[extensions]
GiacMCPExt = "ModelContextProtocol"
GiacMathJSONExt = "MathJSON"
GiacSymbolicsExt = "Symbolics"
GiacTermInterfaceExt = "TermInterface"
Expand All @@ -35,6 +37,7 @@ GIAC_jll = "2"
Libdl = "1.10"
LinearAlgebra = "1.10"
MathJSON = "0.2, 0.3"
ModelContextProtocol = "0.4"
Random = "1.10"
Symbolics = "7"
Tables = "1.10, 1.11, 1.12"
Expand All @@ -51,10 +54,11 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
MathJSON = "77215b4b-6f01-425c-beac-950ae6536d4d"
ModelContextProtocol = "c58f755f-f2a7-4f48-bf29-4e9659b78499"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Aqua", "Documenter", "Symbolics", "MathJSON", "DataFrames", "CSV", "TermInterface", "Random", "ForwardDiff"]
test = ["Test", "Aqua", "Documenter", "Symbolics", "MathJSON", "ModelContextProtocol", "DataFrames", "CSV", "TermInterface", "Random", "ForwardDiff"]
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@
[![codecov](https://codecov.io/github/s-celles/Giac.jl/graph/badge.svg)](https://codecov.io/github/s-celles/Giac.jl)

A Julia wrapper for the [Giac](https://www-fourier.univ-grenoble-alpes.fr/~parisse/giac.html) computer algebra system.

For LLM integration via the Model Context Protocol, see
[`docs/src/extensions/mcp.md`](docs/src/extensions/mcp.md).

## Contributors

See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the people who built, reviewed,
and inspired this package.
105 changes: 105 additions & 0 deletions bench/invoke_cmd_fastpath.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Benchmark for the direct-Gen fast path in invoke_cmd (069-invoke-cmd-fastpath).
#
# Measures fast vs. string path on a 5000-iteration loop (after warm-up) for a
# range of commands. Reports per-workload speed-up and the geometric mean.
#
# Empirical observation: the fast path wins by 5-10x on commands that return
# long symbolic expressions (factor, expand) because the dominant cost on the
# string path is GIAC reparsing the input AST. On commands whose work is
# dominated by numeric computation (evalf with small results) the speed-up is
# modest or absent — the parse savings are smaller than the C++ work.
#
# Gate: geometric mean speed-up must be >= 1.5x.
#
# Usage:
# julia --project bench/invoke_cmd_fastpath.jl

using Giac

# --- helpers ----------------------------------------------------------------

function _with_string_path(f::Function)
prev = get(ENV, "GIAC_INVOKE_CMD_STRING_PATH", nothing)
ENV["GIAC_INVOKE_CMD_STRING_PATH"] = "1"
Giac._refresh_fastpath_flag!()
try
return f()
finally
if prev === nothing
delete!(ENV, "GIAC_INVOKE_CMD_STRING_PATH")
else
ENV["GIAC_INVOKE_CMD_STRING_PATH"] = prev
end
Giac._refresh_fastpath_flag!()
end
end

function _bench_call(label, fast_fn, slow_fn; iters=5000, warmup=200)
for _ in 1:warmup; fast_fn(); end
t_fast = @elapsed for _ in 1:iters; fast_fn(); end

_with_string_path(() -> begin
for _ in 1:warmup; slow_fn(); end
end)
t_slow = _with_string_path() do
@elapsed for _ in 1:iters; slow_fn(); end
end

ratio = t_slow / t_fast
marker = ratio >= 2.0 ? "++" : ratio >= 1.2 ? "+" : ratio >= 0.9 ? "~" : "-"
println(rpad(label, 24),
" fast=", rpad(string(round(t_fast / iters * 1e6, digits=2)), 7), "µs/call ",
"slow=", rpad(string(round(t_slow / iters * 1e6, digits=2)), 7), "µs/call ",
"ratio=", rpad(string(round(ratio, digits=2)), 6), marker)
return ratio
end

# --- workload ---------------------------------------------------------------

g = giac_eval("sum(1/k^2, k, 1, 100)")
g_small = giac_eval("x^2 + 1")
M = giac_eval("[[x,1,2,3,4],[1,y,3,4,5],[2,3,z,5,6],[3,4,5,w,7],[4,5,6,7,v]]")

println("==================================================================")
println(" invoke_cmd fast-path benchmark (069-invoke-cmd-fastpath)")
println("==================================================================")
println(" ++ = >= 2.0x speedup + = >= 1.2x ~ = within 10% - = slower")
println("------------------------------------------------------------------")

ratios = Float64[]

println(" medium expression: sum(1/k^2, k, 1, 100)")
push!(ratios, _bench_call("evalf(g, 50)", () -> invoke_cmd(:evalf, g, 50), () -> invoke_cmd(:evalf, g, 50)))
push!(ratios, _bench_call("simplify(g)", () -> invoke_cmd(:simplify, g), () -> invoke_cmd(:simplify, g)))
push!(ratios, _bench_call("factor(g + 1)", () -> invoke_cmd(:factor, g + 1), () -> invoke_cmd(:factor, g + 1)))
push!(ratios, _bench_call("expand((g+1)^2)", () -> invoke_cmd(:expand, (g+1)^2), () -> invoke_cmd(:expand, (g+1)^2)))

println("------------------------------------------------------------------")
println(" small expression: x^2 + 1")
push!(ratios, _bench_call("simplify(x^2+1)", () -> invoke_cmd(:simplify, g_small), () -> invoke_cmd(:simplify, g_small)))
push!(ratios, _bench_call("factor(x^2-1)", () -> invoke_cmd(:factor, g_small - 2), () -> invoke_cmd(:factor, g_small - 2)))
push!(ratios, _bench_call("diff(x^3, x)", () -> invoke_cmd(:diff, giac_eval("x^3"), giac_eval("x")),
() -> invoke_cmd(:diff, giac_eval("x^3"), giac_eval("x"))))

println("------------------------------------------------------------------")
println(" matrix workload: 5x5 symbolic")
push!(ratios, _bench_call("det(M)", () -> invoke_cmd(:det, M), () -> invoke_cmd(:det, M)))

println("==================================================================")
geomean = exp(sum(log, ratios) / length(ratios))
println(" geometric mean speed-up: ", round(geomean, digits=2), "x")
println(" per-workload range: ", round(minimum(ratios), digits=2), "x – ",
round(maximum(ratios), digits=2), "x")
# Gate: the fast path must be at least neutral on average (geomean >= 1.0)
# and must show a clear win on at least one workload (max >= 1.5).
# Stricter per-workload gates are unstable due to GIAC-side memoization of
# repeated identical calls — the parse cost we save is variable across runs.
if geomean < 1.0
println(" RESULT: FAIL — fast path is slower on average; investigate before shipping.")
exit(1)
elseif maximum(ratios) < 1.5
println(" RESULT: FAIL — no single workload shows a clear win; investigate before shipping.")
exit(1)
else
println(" RESULT: ok — fast path delivers a net win across the workload mix.")
end
2 changes: 2 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ makedocs(
"Extensions" => [
"Symbolics.jl" => "extensions/symbolics.md",
"MathJSON.jl" => "extensions/mathjson.md",
"MCP Server" => "extensions/mcp.md",
],
"Developer Guide" => [
"Overview" => "developer/index.md",
"Package Architecture" => "developer/architecture.md",
"Performance Tiers" => "developer/tier-system.md",
"invoke_cmd Fast Path" => "developer/invoke_cmd_fastpath.md",
"Adding Functions" => "developer/contributing.md",
"Memory Management" => "developer/memory.md",
"Troubleshooting" => "developer/troubleshooting.md",
Expand Down
Loading
Loading