Differential Equations

Giac.jl provides symbolic solving of ordinary differential equations (ODEs) using GIAC's desolve command. The package includes a Differential operator following SciML/ModelingToolkit conventions for expressing derivatives naturally, plus a deprecated D alias kept during the transition window.

The Differential Operator

Differential(var) is the canonical, SciML-style partial-differentiation operator. It captures a single variable and is applied as a callable. For a mono-variable function declared with @giac_var t u(t), Differential(t)(u) represents u'(t). Repeated application gives higher orders; mixed variables compose for partial derivatives. See Calculus for a multi-variable walkthrough.

using Giac
using Giac.Commands: desolve

@giac_var t u(t)
Dt = Differential(t)

Dt(u)              # u'(t)
Dt(Dt(u))          # u''(t)  (adjacent same-var steps collapse)

Comparison with raw GIAC syntax

Differential formRaw GIACDescription
Differential(t)(u)diff(u(t), t)First derivative
Differential(t)(Differential(t)(u))diff(u(t), t, 2)Second derivative
Differential(t)(u)(0) ~ 1"u'(0)=1"Initial condition for u'(0)

Unicode identifiers

Function and variable names may use any Unicode letters that Julia and GIAC accept:

@giac_var 𝑧 ϕ(𝑧)
Dz = Differential(𝑧)
Dz(ϕ)        # diff(ϕ(𝑧), 𝑧)
Dz(Dz(ϕ))    # diff(ϕ(𝑧), 𝑧, 2)

The deprecated D alias

Earlier versions of Giac.jl exposed a D operator (e.g. D(u), D(u, 2), D(D(u))). D is still available during a one-release deprecation window, with each call site emitting a Base.depwarn pointing to the canonical replacement. D will be removed in the next published release. See docs/migration/d_to_differential.md for the full mapping table.

The one D call shape that no longer works even with a warning: D(f) for a multi-argument function like @giac_var x y f(x, y) now raises ArgumentError instead of silently differentiating against the first variable. Use Differential(x)(f) (or Differential(y)(f)) explicitly.

First-Order ODEs

Basic Example

Solve τu' + u = U₀ with initial condition u(0) = 1:

using Giac
using Giac.Commands: desolve

@giac_var t u(t) tau U0
Dt = Differential(t)

# Define ODE: τu' + u = U₀
ode = tau * Dt(u) + u ~ U0

# Initial condition: u(0) = 1
initial = u(0) ~ 1

# Solve
result = desolve([ode, initial], t, :u)
# Returns: U0+(-U0+1)*exp(-t/tau)

RC Circuit Example

@giac_var t V(t) R C Vs
Dt = Differential(t)

# Capacitor voltage ODE: RC·V' + V = Vs
ode = R * C * Dt(V) + V ~ Vs
initial = V(0) ~ 0

result = desolve([ode, initial], t, :V)
# Returns: Vs*(1-exp(-t/(R*C)))

Second-Order ODEs

Harmonic Oscillator

Solve u'' + u = 0 with u(0) = 1, u'(0) = 0:

using Giac
using Giac.Commands: desolve

@giac_var t u(t)
Dt = Differential(t)

# Define ODE
ode = Dt(Dt(u)) + u ~ 0

# Initial conditions
u0 = u(0) ~ 1            # u(0) = 1
du0 = Dt(u)(0) ~ 0       # u'(0) = 0

# Solve
result = desolve([ode, u0, du0], t, :u)
# Returns: cos(t)

Damped Oscillator

Solve u'' + 2ζω₀u' + ω₀²u = 0:

@giac_var t u(t) zeta omega0
Dt = Differential(t)

ode = Dt(Dt(u)) + 2*zeta*omega0*Dt(u) + omega0^2*u ~ 0
result = desolve([ode, u(0) ~ 1, Dt(u)(0) ~ 0], t, :u)

Third-Order ODEs

Solve y''' - y = 0 with y(0) = 1, y'(0) = 1, y''(0) = 1:

using Giac
using Giac.Commands: desolve

@giac_var t y(t)
Dt = Differential(t)

# Define ODE
ode = Dt(Dt(Dt(y))) - y ~ 0

# Initial conditions
y0 = y(0) ~ 1
dy0 = Dt(y)(0) ~ 1
d2y0 = Dt(Dt(y))(0) ~ 1

# Solve
result = desolve([ode, y0, dy0, d2y0], t, :y)
# Returns: exp(t)

Using Differential in ODE Expressions

The Differential operator's result type supports arithmetic, making it natural to build ODE expressions:

@giac_var t u(t) a b c
Dt = Differential(t)

# Build complex ODE expressions
ode1 = Dt(Dt(u)) + a*Dt(u) + b*u ~ c
ode2 = Dt(Dt(u)) - 4*Dt(u) + 4*u ~ 0

# Combine with other GiacExpr
forcing = sin(t)
ode3 = Dt(Dt(u)) + u ~ forcing

Important Notes

Function Name as Symbol

When calling desolve, pass the function name as a Symbol (:u, :y) rather than the function expression (u, y):

# Correct
desolve([ode, u(0) ~ 1], t, :u)

# Incorrect - GIAC expects just the name, not u(t)
desolve([ode, u(0) ~ 1], t, u)  # May not work as expected

Initial Conditions with Differential

The Differential(t)(u)(0) syntax creates an unevaluated derivative condition that GIAC interprets correctly:

Dt = Differential(t)
Dt(u)(0) ~ 1            # Creates "u'(0)=1" for GIAC
Dt(Dt(u))(0) ~ 0        # Creates "u''(0)=0" for GIAC

This works because the underlying DerivativeExpr is mono-variable. Point-evaluation syntax for partial derivatives of multi-variable functions (e.g. PDE boundary conditions) is not supported in this release; see spec 068 Open Questions for the planned path.

Systems of ODEs

GIAC can solve systems of first-order ODEs:

@giac_var t x(t) y(t)
Dt = Differential(t)

# dx/dt = y, dy/dt = -x
sys = [Dt(x) ~ y, Dt(y) ~ -x]
initial = [x(0) ~ 1, y(0) ~ 0]

# Solve as a system (pass both variables)
result = desolve([sys..., initial...], t, [:x, :y])

Physics Applications

Exponential Decay

Model radioactive decay: dN/dt = -λN

@giac_var t N(t) lambda N0
Dt = Differential(t)

# Decay equation
ode = Dt(N) + lambda * N ~ 0
initial = N(0) ~ N0

result = desolve([ode, initial], t, :N)
# Returns: N0*exp(-lambda*t)

Population Growth

Exponential growth model: dP/dt = rP

@giac_var t P(t) r P0
Dt = Differential(t)

ode = Dt(P) - r * P ~ 0
initial = P(0) ~ P0

result = desolve([ode, initial], t, :P)
# Returns: P0*exp(r*t)

Newton's Law of Cooling

Temperature change: dT/dt = -k(T - T_env)

@giac_var t T(t) k T_env T0
Dt = Differential(t)

ode = Dt(T) + k * (T - T_env) ~ 0
initial = T(0) ~ T0

result = desolve([ode, initial], t, :T)
# Returns exponential approach to T_env

Limitations

  • ODEs only: GIAC's desolve is designed for ordinary differential equations. For PDEs, consider Symbolics.jl + MethodOfLines.jl or other specialized packages.
  • Symbolic solutions: desolve finds closed-form analytical solutions when possible. For numerical solutions of ODEs, use DifferentialEquations.jl.

API Reference

Giac.Differential — Type
Differential(var::GiacExpr)
Differential(var::GiacExpr, order::Int)

Canonical partial-differentiation operator, SciML/ModelingToolkit-style.

Differential(var) captures a single differentiation variable; the resulting callable applies a first-order partial derivative to its operand. The two-argument form Differential(var, n) produces an operator that applies the n-th order partial derivative in one step — equivalent to applying Differential(var) n times — and matches Symbolics.jl's Differential(x, n) shape.

Two operand forms

  • Function-form: Differential(x)(f) for @giac_var x y f(x,y) returns a DerivativeExpr representing ∂f/∂x. Compose by chaining: Differential(y)(Differential(x)(f)) is ∂²f/∂y∂x (see Notation below). The order-2 shape Differential(x, 2)(f) produces the same result as Differential(x)(Differential(x)(f)) (steps collapse).
  • Bare-expression: Differential(x)(x^2 + y*x) returns a plain GiacExpr (already simplified by GIAC) — 2*x + y in this case. The order-2 shape Differential(x, 2)(x^3) returns 6*x (after simplification). Aligns with SymPy.jl's diff(expr, var) and Symbolics.jl's Differential.

Notation (right-to-left Leibniz)

Giac.jl uses the right-to-left Leibniz convention for ∂ⁿf/∂v₁…∂vₙ: the rightmost variable is applied first, the leftmost last — consistent with the operator product (∂/∂v₁)·…·(∂/∂vₙ) f. So Differential(y)(Differential(x)(f)) (apply Differential(x) first, then Differential(y)) is written ∂²f/∂y∂x, and pretty-printed 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 steps field track the order of application.

Composition

Repeated application on the same variable collapses adjacent steps: Differential(x)(Differential(x)(f)) stores [("x", 2)] rather than [("x", 1), ("x", 1)]. Mixed variables compose freely: Differential(y)(Differential(x)(f)) stores [("x", 1), ("y", 1)] (innermost-first) and prints as diff(diff(f(x,y),x),y) for GIAC.

The operators ^, *, and ∘ are also supported for compact composition, matching Symbolics.jl: Differential(t)^2 === Differential(t, 2), Differential(y) * Differential(x) ≡ Differential(y) ∘ Differential(x), and Differential(t)^0 === identity.

Examples

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

Differential(x)(f)              # ∂f/∂x
Differential(y)(Differential(x)(f))  # ∂²f/∂y∂x  (x first, y next)

Differential(t)(u)              # u'(t)  (mono-variable case)

# Order-n shorthand (Symbolics.jl-style)
Differential(t, 2)(u)           # u''(t)  — same as Differential(t)(Differential(t)(u))
Differential(x, 3)(f)           # ∂³f/∂x³

Differential(x)(x^2 + y*x)      # 2*x + y  (bare expression)
Differential(x, 2)(x^3)         # 6*x      (bare expression, second derivative)

Name collision with Symbolics.jl

Symbolics.Differential exists too. When both packages are loaded, qualify: Giac.Differential(x)(f) vs Symbolics.Differential(x). The two are independent types — there is no implicit interop in this release. See docs/migration/d_to_differential.md for the recipe.

See also

  • D: Deprecated mono-variable operator. D is being removed in the next published release; migrate D(u) → Differential(t)(u).
  • DerivativeExpr: The function-form result type.
source
Giac.D — Function
D(expr::GiacExpr) -> DerivativeExpr
D(expr::GiacExpr, n::Int) -> DerivativeExpr
D(d::DerivativeExpr) -> DerivativeExpr

Derivative operator following SciML/ModelingToolkit conventions.

Creates a DerivativeExpr that can be:

  • Used in ODEs: D(D(u)) + u ~ 0 (converts to diff notation)
  • Called for initial conditions: D(u)(0) ~ 1 (produces prime notation u'(0)=1)

Arguments

  • expr::GiacExpr: A function expression created with @giac_var u(t)
  • n::Int: Optional derivative order (default: 1)
  • d::DerivativeExpr: A derivative expression to differentiate further

Examples

using Giac
using Giac.Commands: desolve

@giac_var t u(t)

# First derivative
D(u)              # Represents u'

# Second derivative (two ways)
D(D(u))           # Chain D operators
D(u, 2)           # Specify order directly

# ODE with initial conditions
ode = D(D(u)) + u ~ 0       # u'' + u = 0
u0 = u(0) ~ 1               # u(0) = 1
du0 = D(u)(0) ~ 0           # u'(0) = 0

desolve([ode, u0, du0], t, u)  # Returns: cos(t)

# Third order example
@giac_var t y(t)
ode = D(y, 3) - y ~ 0          # y''' - y = 0
desolve([ode, y(0) ~ 1, D(y)(0) ~ 1, D(y,2)(0) ~ 1], t, y)

See also

  • DerivativeExpr: The derivative expression type
  • desolve: Solving differential equations (via Giac.Commands)
source
Giac.DerivativeExpr — Type
DerivativeExpr

Represents a derivative expression for use in ODE initial conditions.

This type enables the D operator syntax following SciML conventions:

  • D(u) represents the first derivative u'
  • D(D(u)) or D(u, 2) represents the second derivative u''
  • D(u)(0) produces "u'(0)" for GIAC initial conditions

Fields

  • base_expr::GiacExpr: The original function expression (e.g., u(t))
  • funcname::String: The function name (e.g., "u")
  • varname::String: The differentiation variable (e.g., "t")
  • order::Int: The derivative order (1 for first derivative, 2 for second, etc.)

Example

@giac_var t u(t)

# Create derivative expressions
du = D(u)           # First derivative
d2u = D(D(u))       # Second derivative
d2u = D(u, 2)       # Alternative syntax

# Use in initial conditions
D(u)(0) ~ 1         # u'(0) = 1
D(u, 2)(0) ~ 0      # u''(0) = 0

# Use in ODEs (converts to diff notation)
ode = D(D(u)) + u ~ 0   # Equivalent to diff(u,t,2) + u = 0

See also

  • D: The derivative operator function
  • @giac_var: For creating function variables
source
Giac.DerivativePoint — Type
DerivativePoint

Represents a derivative evaluated at a specific point, for use in ODE initial conditions.

This type is created when calling a DerivativeExpr with arguments, e.g., D(u)(0). It delays evaluation until used with the ~ operator to create an equation, because GIAC interprets prime notation differently in isolation vs. within desolve.

Fields

  • funcname::String: The function name (e.g., "u")
  • order::Int: The derivative order
  • point_args::Vector{String}: The point arguments as strings

Example

@giac_var t u(t)
dp = D(u)(0)          # Returns DerivativePoint, not GiacExpr
eq = D(u)(0) ~ 1      # Creates equation: "u'(0)=1"
source
Giac.DerivativeCondition — Type
DerivativeCondition

Represents an unevaluated derivative initial condition for ODEs.

This type holds the string representation of a derivative condition (e.g., "u'(0)=1") without evaluating it through GIAC. When passed to desolve in an array, it gets converted to its string form, which GIAC interprets correctly.

Example

@giac_var t u(t)
dc = D(u)(0) ~ 1     # Returns DerivativeCondition: "u'(0)=1"

# Pass to desolve - the string is used directly
desolve([D(D(u)) + u ~ 0, u(0) ~ 1, D(u)(0) ~ 0], t, u)
source
Giac.expand_derivatives — Function
expand_derivatives(x) -> x

Force evaluation of any lazy derivative wrapper, returning a plain GiacExpr.

For Differential(var)(f) where f is a declared user function (DerivativeExpr), expand_derivatives calls GIAC's diff and returns the resulting GiacExpr. For everything else (already a GiacExpr produced by Giac.jl's eager bare-expression branch, or any value not wrapped in DerivativeExpr), it is the identity — so code written in the Symbolics.jl style (expand_derivatives(Differential(x)(expr))) stays correct on Giac.jl without behavioral change.

Examples

@giac_var x y f(x, y)

expand_derivatives(x^2 + y)            # identity → x^2+y
expand_derivatives(Differential(x)(x^2))  # identity → 2*x (already a GiacExpr)
expand_derivatives(Differential(x)(f))    # → diff(f(x,y),x) as a GiacExpr

See also

source