How to write a Macro to define the multi-index symbols

I write a Macro to define the symbols with one index as following

macro varjs(x, j::Int64)
    for i = 1:j
        push!(vars.args, Expr(:(=), esc(Symbol("$x$i")), Expr(:quote,Symbol("$x$i"))))
    push!(vars.args,  Expr(:tuple,map(esc, "$x".*map(string,1:j).|>Symbol)...))

The macro is called as following

@varjs α 16
The output is

(:α1, :α2, :α3, :α4, :α5, :α6, :α7, :α8, :α9, :α10, :α11, :α12, :α13, :α14, :α15, :α16)

Each αi is defined as a symbol variable.

Then I plan to define the macro for multi-index variables. I have no ideal about this except for writing the “for loop” one by one. Is there any better way to realize the feature?
For example, 3-index varibales with three value for each index, the macro should looks like @varjs α 3 3 3 and automatically define the symbol varialbes α111, α112, α113,α121,α122,α123,α131,α132,α133,α211, α212, α213,α221,α222,α223,α231,α232,α233,α311, α312, α313,α321,α322,α323,α331,α332,α333.

A variable-dimensional loop can be implemented using Iterators.product, e.g.

varjs(s,j...) = [:($(Symbol(s,i...)) = $(Expr(:quote,Symbol(s,i...)))) for i in Iterators.product((1:k for k in j)...)]

But also, the thing you are trying to achieve seems suspicious. Why would you want to create variables named α11, α12, α21 etc., rather than have the values inside a single array called α?

1 Like

Thanks. I use the Multi-index symbol to denote the component of tensors.

You can have the components in an array α, and refer to them in code as α[1,1] and so on. If α is a StaticArray it will probably be roughly as efficient as a variable for each component (and if the size of α only becomes known at runtime, the macro approach is probably hopeless anyway).

Have you tried any of the existing tensor packages (e.g. Tensors.jl, TensorOperations.jl)? They don’t have the symbolic indexing you’re describing, but they have a solid base of functionality that you’d otherwise have to build yourself.

If symbolic field names are critical, consider using FieldArrays from StaticArrays.jl.

You can also snag the @unpack macro from Parameters.jl in conjunction with FieldArrays:

julia> using StaticArrays, Parameters

julia> @with_kw struct Stress <: FieldMatrix{2,2,Float64}

julia> function f(σ::Stress)
           @unpack_Stress σ
           return xx + yy - sqrt(xy * yx)

julia> σ = Stress(3, 1, 1, 2)
2×2 Stress with indices SOneTo(2)×SOneTo(2):
 3.0  1.0
 1.0  2.0

julia> f(σ)

You also get free functionality via StaticArrays.jl:

julia> using LinearAlgebra

julia> det(σ) + tr(σ)

You should look at the Base.Cartesian module. I think it already has all the functionality you could want along these dimensions.

1 Like

Your method works perfectly.

macro varjst(x, j...)
    for i in Iterators.product((1:k for k in j)...)
        push!(vars.args, Expr(:(=), esc(Symbol("$x$(i...)")), Expr(:quote,Symbol("$x$(i...)"))))
    push!(vars.args,  Expr(:tuple,map(esc, "$x".*map(string,["$(i...)" for i in Iterators.product((1:k for k in j)...) ]).|>Symbol)...))

> vars=@varjst α 3 3 3
(:α111, :α211, :α311, :α121, :α221, :α321, :α131, :α231, :α331, :α112, :α212, :α312, :α122, :α222, :α322, :α132, :α232, :α332, :α113, :α213, :α313, :α123, :α223, :α323, :α133, :α233, :α333)
> length(vars)

> α231

Thanks for the help from all of you. The problem is solved.

Interesting module. It seems also useful. Thanks

Thanks for the suggestion. TensroOperations.jl does related to my following project. Nice to know it in advance.

What happens with @varjst α 11 11 11…?
Is α1111 (1,11,1), (11,1,1), or (1,1,11)?

it is α1111 … So do not use this if the dimension>=10. In genral space dimesion is 3 (2 for surface). Even encluding time, 4 is enough.

I was just pointing out the ambiguity. The first case you showed went up to length 16.
Although it is many more characters, it shouldn’t be hard to modify the macro to produce α_1_11_1 to avoid this.

1 Like