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.
using CTLie # Lift, ad, Poisson, ∂ₜ, @Lie
using CTBase: Data # Data.VectorField, Data.Hamiltonian, …
# AD backend: CTBase's DifferentiationInterface extension + ForwardDiff
using DifferentiationInterface: DifferentiationInterface
using ForwardDiff: ForwardDiffLie and Poisson brackets
X = Data.VectorField(x -> [x[2], 2x[1]]; is_autonomous=true)
Y = Data.VectorField(x -> [3x[2], -x[1]]; is_autonomous=true)
(@Lie [X, Y])([1.0, 2.0])2-element Vector{Float64}:
7.0
-14.0F = Data.Hamiltonian((x, p) -> p[1]^2 / 2 + x[1]^2; is_autonomous=true)
G = Data.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 Data.VectorField, (@Lie {F, G}) isa Data.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 = (x, p) -> (2x[1]^2 + x[2]^2 + p[1]^2) / 2
h1 = (x, p) -> (3x[1]^2 + x[2]^2 + p[2]^2) / 2
H0 = Data.Hamiltonian(h0; is_autonomous=true)
H1 = Data.Hamiltonian(h1; 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 = Data.VectorField(x -> [-2x[1], -2x[2], 1 - x[3]]; is_autonomous=true)
F1 = Data.VectorField(x -> [0.0, -x[3], x[2]]; is_autonomous=true)
F2 = Data.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 = (x, p) -> (4x[1]^2 + x[2]^2 + p[1]^3 + p[2]^2) / 2
H2 = Data.Hamiltonian(h2; is_autonomous=true)
x0, p0 = [1.0, 2.0], [2.0, 1.0]
@Lie {H0, H1}(x0, p0) - {H1, H2}(x0, p0)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. The errors below are captured when the documentation is built.
Wrong bracket kind for a typed operand — a Hamiltonian inside [...] (Lie bracket) or a VectorField inside {...} (Poisson bracket). The operand itself is wrong for that bracket, so this raises CTBase.Exceptions.IncorrectArgument:
julia> H = Data.Hamiltonian((x, p) -> p[1] * x[1]);
julia> F = Data.VectorField(x -> [x[2], -x[1]]);
julia> @Lie [H, F]
ERROR: IncorrectArgument
│
│ @Lie: AbstractHamiltonian cannot be a Lie bracket operand
│
│ Context @Lie macro runtime dispatch
│ Hint Use {H, G} (Poisson bracket) for Hamiltonians
└─
julia> @Lie [F, H]
ERROR: IncorrectArgument
│
│ @Lie: AbstractHamiltonian cannot be a Lie bracket operand
│
│ Context @Lie macro runtime dispatch
│ Hint Use {H, G} (Poisson bracket) for Hamiltonians
└─
julia> @Lie {F, H}
ERROR: IncorrectArgument
│
│ @Lie: AbstractVectorField cannot be a Poisson bracket operand
│
│ Context @Lie macro runtime dispatch
│ Hint Use [X, Y] (Lie bracket) for VectorFields
└─
julia> @Lie {H, F}
ERROR: IncorrectArgument
│
│ @Lie: AbstractVectorField cannot be a Poisson bracket operand
│
│ Context @Lie macro runtime dispatch
│ Hint Use [X, Y] (Lie bracket) for VectorFields
└─An unknown keyword argument also raises IncorrectArgument:
julia> @Lie [F, F] invalid_arg=true
ERROR: IncorrectArgument
│
│ @Lie: unknown keyword argument
│
│ Got invalid_arg
│ Expected is_autonomous, is_variable, or ad_backend
│
│ Context @Lie macro keyword parsing
└─Operands whose traits disagree, or a keyword that contradicts the operands' traits. Each piece is valid on its own — it is their combination that is rejected — so these raise CTBase.Exceptions.PreconditionError:
julia> Xa = Data.VectorField(x -> [x[2], -x[1]]; is_autonomous=true);
julia> Xt = Data.VectorField((t, x) -> [x[2], -x[1]]; is_autonomous=false);
julia> @Lie [Xa, Xt]
ERROR: PreconditionError
│
│ ad: TD/VD mismatch between X and Y
│
│ Reason X: CTBase.Traits.Autonomous/CTBase.Traits.Fixed ≠ Y: CTBase.Traits.NonAutonomous/CTBase.Traits.Fixed — both arguments must share the same TimeDependence and VariableDependence
│
│ Context ad on AbstractVectorField
│ Hint Ensure both vector fields have the same time and variable dependence traits
└─
julia> @Lie [Xa, Xa] is_autonomous=false
ERROR: PreconditionError
│
│ @Lie: is_autonomous conflicts with operand trait
│
│ Reason `is_autonomous` specifies CTBase.Traits.NonAutonomous but operand has CTBase.Traits.Autonomous
│
│ Context @Lie consistency check
└─How 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, AD backends, hygiene.