The @Lie macro
The macro @Lie is a thin, readable front end over ad and Poisson. It uses bracket notation that mirrors the mathematics:
square brackets
[X, Y]denote a Lie bracket of vector fields;curly braces
{H, G}denote a Poisson bracket of Hamiltonians.
Brackets may be nested and combined with ordinary arithmetic. Operands may be plain Functions or typed objects. Evaluation points — including 2-element vector literals such as [1.0, 2.0] — may appear directly inside the macro expression.
Lie and Poisson brackets
X = VectorField(x -> [x[2], 2x[1]]; is_autonomous=true)
Y = VectorField(x -> [3x[2], -x[1]]; is_autonomous=true)
(@Lie [X, Y])([1.0, 2.0])2-element Vector{Float64}:
7.0
-14.0F = Hamiltonian((x, p) -> p[1]^2 / 2 + x[1]^2; is_autonomous=true)
G = Hamiltonian((x, p) -> x[1] * p[1]; is_autonomous=true)
(@Lie {F, G})([1.0, 2.0], [0.5, 1.0])-1.75The macro returns exactly what the underlying function returns — a typed VectorField for […], a typed Hamiltonian for {…}:
(@Lie [X, Y]) isa VectorField, (@Lie {F, G}) isa Hamiltonian(true, true)Nested brackets
(@Lie [[X, Y], Y])([1.0, 2.0])2-element Vector{Float64}:
-84.0
-14.0(@Lie {{F, G}, G})([1.0, 2.0], [0.5, 1.0])4.5Evaluation at literal points
Vector literals can appear directly as arguments inside a @Lie expression. The macro resolves the ambiguity at runtime through dispatch: if the two elements of a [a, b] are not field-like objects, the macro reconstructs the vector as data and applies it.
@Lie [X, Y]([1.0, 2.0])2-element Vector{Float64}:
7.0
-14.0H0 = Hamiltonian((x, p) -> 0.5*(2x[1]^2 + x[2]^2 + p[1]^2); is_autonomous=true)
H1 = Hamiltonian((x, p) -> 0.5*(3x[1]^2 + x[2]^2 + p[2]^2); is_autonomous=true)
@Lie {H0, H1}([1.0, 2.0], [2.0, 1.0])4.0Arithmetic on bracket values
Brackets can be evaluated and combined inside a single @Lie expression. With the Bloch-equation fields
F0 = VectorField(x -> [-2x[1], -2x[2], 1 - x[3]]; is_autonomous=true)
F1 = VectorField(x -> [0.0, -x[3], x[2]]; is_autonomous=true)
F2 = VectorField(x -> [x[3], 0.0, -x[1]]; is_autonomous=true)
x3 = [1.0, 2.0, 3.0]we have
@Lie [F0, F1](x3) + 4 * [F1, F2](x3)3-element Vector{Float64}:
8.0
-8.0
-2.0The same works for Poisson brackets:
H2 = Hamiltonian((x, p) -> 0.5*(4x[1]^2 + x[2]^2 + p[1]^3 + p[2]^2); is_autonomous=true)
@Lie {H0, H1}([1.0, 2.0], [2.0, 1.0]) - {H1, H2}([1.0, 2.0], [2.0, 1.0])22.0Trailing keywords bind to @Lie
A macro greedily consumes the keyword=value arguments that follow it. So in @Lie [F, G](x) ≈ v atol=1e-6, the atol=1e-6 is handed to @Lie (and rejected), not to ≈. Wrap the bracket expression in parentheses to scope the macro:
(@Lie [F, G](x)) ≈ v atol=1e-6 # atol now belongs to the comparisonPlain functions and trait keywords
The operands may be plain Functions. As elsewhere, a bare function is assumed autonomous and fixed; use the keyword flags is_autonomous and is_variable to say otherwise. They are passed after the bracket expression:
f = x -> [x[2], 2x[1]]
g = x -> [3x[2], -x[1]]
(@Lie [f, g])([1.0, 2.0]) # autonomous by default2-element Vector{Float64}:
7.0
-14.0ft = (t, x) -> [t + x[2], -2x[1]]
gt = (t, x) -> [t + 3x[2], -x[1]]
(@Lie [ft, gt] is_autonomous=false)(1.0, [1.0, 2.0])2-element Vector{Float64}:
-5.0
11.0fv = (x, v) -> [x[2] + v, 2x[1]]
gv = (x, v) -> [3x[2], v - x[1]]
(@Lie [fv, gv] is_variable=true)([1.0, 2.0], 1.0)2-element Vector{Float64}:
6.0
-15.0A third keyword, ad_backend, selects the AD backend for this call only (see Limitations & configuration).
When operands are typed, their traits are read directly and no keyword is needed — indeed, a keyword that contradicts the operands' traits is an error (next section).
Error cases
The macro validates its input and raises CTBase.Exceptions.IncorrectArgument in the following situations. These snippets are not executed (they would throw):
Wrong bracket kind for a typed operand. Using a Hamiltonian inside [...] (Lie bracket) or a VectorField inside {...} (Poisson bracket) is detected at runtime and raises an error:
H = Hamiltonian((x, p) -> p[1]*x[1])
F = VectorField(x -> [x[2], -x[1]])
@Lie [H, F] # ❌ Hamiltonian is not a valid Lie bracket operand
@Lie [F, H] # ❌ same
@Lie {F, H} # ❌ VectorField is not a valid Poisson bracket operand
@Lie {H, F} # ❌ sameAn unknown keyword argument:
@Lie [F, G] invalid_arg=true # ❌ unknown @Lie argumentA keyword that conflicts with typed operands, or operands whose traits disagree:
Xa = VectorField(x -> [x[2], -x[1]]; is_autonomous=true)
Xt = VectorField((t, x) -> [x[2], -x[1]]; is_autonomous=false)
@Lie [Xa, Xt] # ❌ time-dependence mismatch between operands
@Lie [Xa, Xa] is_autonomous=false # ❌ flag conflicts with operand traitsHow the macro works
@Lie is a macro-time rewrite: every [a, b] in the expression is replaced by a call to the runtime worker _lie_mac(a, b, …), and every {c, d} by _poisson_mac(c, d, …). The bracket kind and the trait arguments are baked in at expansion time. Whether [a, b] is a genuine Lie bracket or a data vector is resolved at runtime through multiple dispatch:
Both operands are field-like (
FunctionorAbstractVectorField) → Lie bracket.Either operand is an
AbstractHamiltonian→IncorrectArgument.Otherwise → the two values are reassembled as
[a, b](data vector).
This means the macro is not type-stable when it encounters a data vector [a, b]: the return type depends on the runtime values of a and b. When both operands are known to be field-like (e.g. typed VectorField objects), the macro is type-stable.
See also
@Lie— full docstring.Limitations & configuration — trait matching, backends, prefixes.