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
81 changes: 81 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **`Differential` operator** (spec 068): canonical SciML/ModelingToolkit-style
partial-differentiation operator. `Differential(x)(f)` for a declared function
`f(x, y)` returns a `DerivativeExpr` representing `∂f/∂x`. Composition handles
cross and higher-order partials: `Differential(y)(Differential(x)(f))` is
`∂²f/∂y∂x` (right-to-left Leibniz convention — see *Fixed* below);
`Differential(x)(Differential(x)(f))` is `∂²f/∂x²` (adjacent same-variable
steps collapse). Mono-variable functions also work: `Differential(t)(u)`.
- **Symbolics-compatible algebraic surface on `Differential`**:
`Differential(t)^n` for `n ≥ 0` returns an order-`n` operator —
`Differential(t)^2 === Differential(t, 2)` and `Differential(t)^0 === identity`,
matching Symbolics.jl. `Differential * Differential` composes via Julia's
generic `∘`, so `(Differential(y) * Differential(x))(f) ==
(Differential(y) ∘ Differential(x))(f) == Differential(y)(Differential(x)(f))`.
- **`expand_derivatives`** (Symbolics.jl parity): forces evaluation of a
`DerivativeExpr` to a plain `GiacExpr` (delegates to GIAC's `diff`), and is
the identity on any other value. Lets code written in the Symbolics.jl style
(`expand_derivatives(Differential(x)(expr))`) run unchanged on Giac.jl. Note
that Giac.jl's bare-expression branch already evaluates eagerly, so the
no-op fallback covers the common case.
- **Two-argument `Differential(var, n)` shorthand**: `Differential(x, 2)(f)`
is exactly equivalent to `Differential(x)(Differential(x)(f))`, matching
Symbolics.jl's `Differential(x, 2)` form. The default `Differential(var)`
is `Differential(var, 1)`. Applies uniformly to function-form and
bare-expression operands.
- **Bare-expression differentiation** via `Differential` (spec 068):
`Differential(x)(x^2 + y*x)` returns the plain `GiacExpr` `2*x + y`,
matching SymPy.jl's `diff(expr, var)` and Symbolics.jl's `Differential`.
- **Multi-variable partial derivative support**: `DerivativeExpr` now records
an ordered sequence of `(variable, order)` differentiation steps, enabling
cross partials and arbitrary-depth composition. The single-step case is the
original mono-variable behavior.
- **Math-convention `n`-th derivative display**: high-order derivatives
(`n ≥ 4`) now render with parenthesized superscript notation in `Base.show`
rather than long strings of primes. So `D(u, 5)` displays as `D: u⁽⁵⁾(t)`
(instead of `D: u'''''(t)`). The same applies to `DerivativePoint` display.
`Base.string` for `DerivativePoint` keeps prime notation regardless of order
because GIAC consumes prime-form initial-condition strings.
- See `docs/migration/d_to_differential.md` for a full mapping table from the
deprecated `D` shapes to `Differential`.
- **`examples/07_odes_pdes.jl`**: new Pluto notebook walking through symbolic
ODE solutions (first-order, harmonic, damped, third-order, RLC, forced) and
PDE expressions (heat, wave, transport, separation of variables for the heat
equation) using the canonical `Differential` operator. Linked from
`docs/src/pluto.md`.

### Deprecated

- **`D` operator** (spec 068): every call shape (`D(u)`, `D(u, n)`, `D(D(u))`,
`D(d::DerivativeExpr, n)`) now emits a one-time-per-call-site
`Base.depwarn` pointing to the `Differential` replacement. `D` will be
removed in the next published release. The multi-arg ambiguity case
(`D(f)` on `f(x, y)`) raises `ArgumentError` instead of warning, since
the previous silent-default-to-first-variable behavior was a correctness
hazard. The `D(d::DerivativeExpr)` chained call also now requires the
underlying derivative to be mono-variable; partial derivatives of mixed
variables must be composed via `Differential(var)(d)`.

### Changed

- **`DerivativeExpr` internal layout** (spec 068): the `varname::String` and
`order::Int` fields are replaced by `steps::Vector{Tuple{String, Int}}`.
This is an internal change — no user code is expected to construct
`DerivativeExpr` directly. Public arithmetic (`+`, `-`, `*`, `/`, `^`),
equation (`~`), and string-conversion behavior is preserved.

### Fixed

- **Partial-derivative pretty-print convention (right-to-left Leibniz)**: the
multi-step `Base.show` for `DerivativeExpr` previously printed
`Differential(y)(Differential(x)(f))` as `D: ∂²f/∂x∂y`, which is consistent
with the index/`f_{xy}` convention but inconsistent with the operator-product
reading `(∂/∂y)(∂/∂x)f = ∂²f/∂y∂x`. The display now uses the right-to-left
Leibniz convention — the **rightmost** variable in `∂ⁿf/∂v₁…∂vₙ` is applied
**first**, the leftmost last — so the same expression now prints as
`D: ∂²f/∂y∂x`. The `steps` field (innermost-first) and the GIAC-side
`diff(diff(f(x,y),x),y)` string are unchanged; only the human-facing
∂-notation flips. By Schwarz/Clairaut the underlying value is symmetric
for sufficiently smooth functions, so this is purely a notation fix.

## [0.14.1] - 2026-05-11

### Added
Expand Down
3 changes: 3 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ makedocs(
],
"Held Commands" => "held_commands.md",
"Tables.jl Compatibility" => "tables.md",
"Migration" => [
"D → Differential" => "migration/d_to_differential.md",
],
"Extensions" => [
"Symbolics.jl" => "extensions/symbolics.md",
"MathJSON.jl" => "extensions/mathjson.md",
Expand Down
57 changes: 57 additions & 0 deletions docs/src/mathematics/calculus.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,63 @@ diff(x^2 * y^3, y)
# Output: 3*x^2*y^2
```

### Using the `Differential` operator

Beyond the function-form `Giac.Commands.diff`, Giac.jl also exposes a SciML/ModelingToolkit-style operator: `Differential(var)` is a callable that captures a single differentiation variable and applies partial differentiation to its operand. This pairs naturally with declared functions (`@giac_var f(x, y)`) and with bare symbolic expressions.

```julia
using Giac

@giac_var x y t f(x, y) u(t)

# Bare-expression form (matches SymPy.jl `diff(expr, var)`)
Differential(x)(x^2 + y*x) # Returns: 2*x + y
Differential(x)(sin(x) * exp(x)) # Returns: cos(x)*exp(x) + sin(x)*exp(x)

# Function-form: declared function, returns a DerivativeExpr
Dx = Differential(x)
Dy = Differential(y)
Dx(f) # ∂f/∂x
Dy(Dx(f)) # ∂²f/∂y∂x (cross partial — see "Notation" below)
Dx(Dx(f)) # ∂²f/∂x² (adjacent same-var steps collapse)

# Mono-variable functions work the same way
Dt = Differential(t)
Dt(u) # u'(t)
Dt(Dt(u)) # u''(t)

# Two-argument shorthand for n-th order (matches Symbolics.jl's Differential(x, 2))
Differential(x, 3)(f) # ∂³f/∂x³ — same as Dx(Dx(Dx(f)))
Differential(t, 2)(u) # u''(t) — same as Dt(Dt(u))
Differential(x, 2)(x^3) # 6*x — bare-expression second derivative

# Symbolics-style algebraic surface
Differential(t)^2 # === Differential(t, 2)
Differential(t)^0 # === identity
Differential(y) * Differential(x) # composition — same as Differential(y) ∘ Differential(x)
(Differential(y) ∘ Differential(x))(f) # ∂²f/∂y∂x

# Force evaluation of a lazy DerivativeExpr (no-op on plain GiacExpr —
# matches Symbolics.jl's expand_derivatives)
expand_derivatives(Dx(f)) # → diff(f(x,y), x) as a GiacExpr
expand_derivatives(x^2 + y*x) # → x^2 + y*x (identity)
```

#### Notation: right-to-left Leibniz convention

In partial-derivative output (`Base.show` and the math comments above),
Giac.jl uses the **right-to-left Leibniz convention**: in `∂ⁿf/∂v₁…∂vₙ`,
the **rightmost** variable in the denominator is applied **first**, the
**leftmost** last. This matches the operator product
`(∂/∂v₁)·…·(∂/∂vₙ) f` read as a chain of left-multiplications:
`Differential(y)(Differential(x)(f))` (apply `Differential(x)` first, then
`Differential(y)`) is written `∂²f/∂y∂x` and pretty-prints as
`D: ∂²f/∂y∂x`. By Schwarz/Clairaut the value is symmetric for
sufficiently smooth `f`, but the notation, the `Base.show` output, and the
internal `DerivativeExpr.steps` field track the order of application.

The function-form path returns a `DerivativeExpr` that records the differentiation history as an ordered sequence of `(variable, order)` steps (innermost-first). The bare-expression path returns a plain `GiacExpr` (already simplified by GIAC). For high-order derivatives (`n ≥ 4`), the human-readable display switches from primes to math-convention parenthesized superscripts: `D(u, 5)` shows as `u⁽⁵⁾(t)` rather than `u'''''(t)`. See [Differential Equations](differential_equations.md) for ODE/PDE workflows and [the migration guide](../migration/d_to_differential.md) for porting from the deprecated `D` operator.

## Integration

### Indefinite Integrals
Expand Down
Loading
Loading