I’m writing a module where a user may create and analyze ** arbitrary** “compositions” (expressions) of so-called

`CrystalOperators`

.Right now, the manual workflow to analyze an arbitrary composition is quite laborious. I am pretty sure it is possible to completely automate the procedure via metaprogramming/macros. I tried many things but unfortunately I always run into problems which I’m not able to solve. Hopefully, you can tell me if and how such an automation is possible.

## Workflow example

I give an example of a workflow of a potential user.

A user defines instances of CrystalOperators:

`x_1::CrystalOperator, x_2::CrystalOperator, x_3::CrystalOperator`

and for example other variables such as `z::Int`

He/she wants to analyze a “composition” or expression of these variables, for example f = `x_1*x_2+z*inv(x_3)`

. To this end, he has to carry out the following steps manually. (I want this to be automated):

- CrystalOperators are mathematical functions defined with respect to a “lattice”. In the first step we need to find a (least) common sublattice and convert all Crystaloperators accordingly. ( You may think of this step as a conversion to the same datatype x::Float64 + y::Int is computed via x::Float64 + y::Float64. I need to store x::Float64 and y::Float64 to proceed further. ):

```
co_vec = [x_1, x_2, x_3] # Create and Array of the CrystylOperators within the expression f.
# Find the least common sublattice A:
A = nothing
for x in co_vec
if A == nothing
A = x.C.L.A
else
A = lcm(A, x.C.L.A)
end
end
# Rewrite all Operators with respect to the least common sublattice A:
x_1 = wrtLattice(x_1, A)
x_2 = wrtLattice(x_2, A)
x_3 = wrtLattice(x_3, A)
```

- We need to make sure that this Expression f makes sense. (As an analogy you may think of these crystaloperators as matrices. Computing
`x_1*x_2+z*inv(x_3)`

makes only sense if the corresponding number of rows and columns are equal.)

```
try
x_1*x_2+z*inv(x_3)
catch e
@show e
end
```

- If the above test was succesful, we may proceed with step 3. We define a function based on the expression f ( we basically replace each appearance of a CrystalOperator x with symbol(x,k) ).

```
sym_f = (k) -> symbol(x_1,k)*symbol(x_2, k)+z*inv(symbol(x_3, k))
```

- Finally, we are able to analyze our operator composition. Oftentimes we are interested in the eigenvalues of sym_f(k) (which is nothing else than a (square) matrix.) for many different values of k:

```
kvec = 0:0.1:1 # this
eigs = [eigvals(sym_f(k)) for k in kvec]
```

## How to automate this?

Is it possible (how?) to automate these steps? It is important that steps 1 and 2 are only executed *once* as the computation can be very complex. I would like to have a macro or function which executes steps 1-3 and spits out the function sym_f:

```
sym_f = @create_sym(x_1*x_2+z*inv(x_3))
```

The problem is, that I need to access and manipulate the variables of the current workspace.

Ideally, I would like to store copies of the variables and functions such as “sym_f” within a struct:

```
struct OperatorComposition
sym_f::Expr
mydict::Dict
end
```

Then I would like to have a macro which creates such an OperatorComposition via

`oc = @createOperatorComposition x_1*x_2+z*inv(x_3)`

such that

```
oc.dict = Dict(:x_1 => x_1, :x_2 => x_2, :x_3 => x_3)` # i.e., oc.dict[:x_i] is the output of step 1 (wrtLattice(x_1, A)
```

and

```
oc.sym_f = (k) -> symbol(Ref(oc.mydict[:x_1]),k)*symbol(Ref(oc.mydict[:x_2]), k)+z*inv(symbol(Ref(oc.mydict[:x_3]), k))
```

Please let me know if anything is unclear. I’m grateful for any advice.