# Implementing New RepresentationsΒΆ

As our solver treats objects very generally, implementing new representations is surprisingly easy. To implement a new Representation you need to implement a `rho(M)`

which is a mapping from the group elements to the representation matrix, as well as make sure to specify a `self.G`

attribute for the given symmetry group. Itβs also a good idea to implement a `__str__`

function to improve readability. You
can optionally specify `self.is_permutation=True`

if the representation acts as a permutation matrix, which enables pointwise Swish nonlinearities in EMLP.

We will automatically generate implementations of `drho(A)`

using autograd, as well as some bookkeeping functions `size()`

which is the dimension of the representation, `__eq__`

and `__hash__`

for distinguishing different representations. In some fringe cases you may want to implement your own `__eq__`

and `__hash__`

functions.

## Example 1: Irreducible Representations of SO(2)ΒΆ

As a first example, we show one can implement the real irreducible representations of the group SO(2). All of irreducible representations \(\psi_n\) of SO(2) are \(2\)-dimensional (except for \(\psi_0\) which is the same as Scalar \(= \mathbb{R} = \psi_0\)). These representations can be written \(\psi_n(R_\theta) = \begin{bmatrix}\cos(n\theta) &\sin(n\theta)\\-\sin(n\theta) & \cos(n\theta) \end{bmatrix}\) or simply: \(\psi_n(R) = R^n\).

```
[1]:
```

```
import jax.numpy as jnp
from emlp.reps import Rep,vis,V,equivariance_error
from emlp.groups import SO,S
class SO2Irreps(Rep):
""" (Real) Irreducible representations of SO2 """
def __init__(self,order):
assert order>0, "Use Scalar for πβ"
self.G=SO(2)
self.order = order
def rho(self,M):
return jnp.linalg.matrix_power(M,self.order)
def __str__(self):
number2sub = str.maketrans("0123456789", "ββββββ
ββββ")
return f"π{self.order}".translate(number2sub)
```

Thatβs it! Now we can use the SO(2) irreps in the type system, and solve for equivariant bases that contain them.

```
[2]:
```

```
psi1 = SO2Irreps(1)
psi2 = SO2Irreps(2)
psi3 = SO2Irreps(3)
```

```
[3]:
```

```
psi1*psi2+psi3
```

```
[3]:
```

```
πβ+πββπβ
```

We can verify schurβs lemma, that there are no nontrivial equivariant linear maps from one irrep to another:

```
[4]:
```

```
print((psi1>>psi2).equivariant_basis(),(psi2>>psi3).equivariant_basis(),(psi1>>psi3).equivariant_basis())
```

```
[] [] []
```

And we can include non irreducibles in our representation too. For example computing equivariant maps from \(T_4 \rightarrow \psi_2\).

```
[5]:
```

```
vis(V(SO(2))**4,psi2,False)
Wrep = V(SO(2))**4>>psi2
Q = Wrep.equivariant_basis()
print("{} equivariant maps with r={} basis elements".format(Wrep,Q.shape[-1]))
```

```
Vβ΄βπβ equivariant maps with r=8 basis elements
```

```
[6]:
```

```
import numpy as np
W = Q@np.random.randn(Q.shape[-1])
print("With equivariance error {:.2e}".format(equivariance_error(W,V(SO(2))**4,psi2,SO(2))))
```

```
With equivariance error 1.56e-07
```

## Example 2: PseudoScalars, PseudoVectors, and PseudoTensorsΒΆ

With a slightly more sophisticated example, weβll now implement the representations known as PseudoScalars, PseudoVectors, and other PseudoTensor representations. These representations commonly occur in physics when working with cross products or the Hodge star, and also describe the Fermi statistics of spin 1/2 particles that are antisymmetric under exchange.

A pseudoscalar is like a scalar `Scalar`

\(=\mathbb{R}\), but incurs a \(-1\) under orientation reversing transformations: \(\rho(M) = \mathrm{sign}(\mathrm{det}(M))\). Similarly, pseudovectors are like ordinary vectors but can pick up this additional \(-1\) factor. In fact, we can convert any representation into a pseudorepresentation by multiplying by a pseudoscalar.

```
[7]:
```

```
from emlp.reps import Rep,V,T,vis,Scalar
```

```
[8]:
```

```
class PseudoScalar(Rep):
def __init__(self,G=None):
self.G=G
def __str__(self):
return "P"
def rho(self,M):
sign = jnp.linalg.slogdet(M@jnp.eye(M.shape[0]))[0]
return sign*jnp.eye(1)
def __call__(self,G):
return PseudoScalar(G)
```

Here we implement an additional `__call__`

method so that we can initialize the representation without specifying the group and instead instantiate it later if we want to. For example, we can work with the representation in the type system before specifying the group.

```
[9]:
```

```
P = PseudoScalar()
print((P+V*P)(S(4)))
```

```
P+PβV
```

```
[10]:
```

```
G = S(4)
P = PseudoScalar(G)
W = V(G)
```

We can build up pseudotensors with the tensor product (multiplication). As expected pseudovectors incur a -1 for odd permutations.

```
[11]:
```

```
pseudovector = P*W
g = G.sample()
print(f"Sample g = \n{g}")
print(f"Pseudovector π = \n{pseudovector.rho_dense(g)}")
```

```
Sample g =
[[0. 1. 0. 0.]
[1. 0. 0. 0.]
[0. 0. 0. 1.]
[0. 0. 1. 0.]]
Pseudovector π =
[[0. 1. 0. 0.]
[1. 0. 0. 0.]
[0. 0. 0. 1.]
[0. 0. 1. 0.]]
```

Again, we can freely mix and match these new representations with existing ones.

```
[12]:
```

```
P*(W**2 +P)+W.T
```

```
[12]:
```

```
PΒ²+V+PβVΒ²
```

Equivariant maps from matrices to pseodovectors yield a different set of solutions from maps from matrices to vectors.

```
[13]:
```

```
vis(W**2,pseudovector,cluster=False)
```

```
[14]:
```

```
vis(W**2,W,cluster=False)
```

```
[15]:
```

```
vis(P*W**2,W**2,cluster=False)
```

And of course we can verify the equivariance:

```
[16]:
```

```
rin = P*W**2
rout = W**2
Q = (rin>>rout).equivariant_basis()
print(f"With equivariance error {equivariance_error(Q,rin,rout,G):.2e}")
```

```
With equivariance error 5.94e-08
```

We can even mix and match with the irreducible representations above.

```
[17]:
```

```
P = PseudoScalar(SO(2))
W = V(SO(2))
rep = psi2>>P*W**2
print(rep)
print(rep.equivariant_basis().shape)
```

```
PβVΒ²βπβ
(8, 2)
```

## Additional InformationΒΆ

If you really want to (for performance reasons) you can manually specify the Lie Algebra representation `drho(A)`

instead of the default is calculation from `rho`

using autograd as \(d\rho(A) := d\rho(M)|_{M=I}(A) = \frac{d}{dt} \rho(e^{tA})|_{t=0}\). Similarly, you can override the dual representation `.T`

if there is good reason.

```
[ ]:
```

```
```