# 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 `size()`

which is the dimension of the representation, `rho(M)`

which is a mapping from the group elements to the representation matrix, as well `__eq__`

and `__hash__`

to distinguish different representations. Itβs also a good idea to implement a
`__str__`

function to improve readability. All representations implemented this way should have the `.G`

attribute specifying the symmetry group.

The implementation also requires you to specify whether the representation is regular (whether `rho(M)`

outputs a permutaiton matrix) with the `is_regular`

attribute, and also the `.T`

property that returns the dual of the representation. We plan on removing these two requirements in a later release.

## 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 """
is_regular=False
def __init__(self,order):
assert order>0, "Use Scalar for πβ"
self.G=SO(2)
self.order = order
def size(self):
return 2
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)
def __eq__(self,other):
return type(self)==type(other) and self.G==other.G and self.order==other.order
def __hash__(self):
return hash((type(self),self.G,self.order))
@property
def T(self):
return self
```

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 2.58e-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):
is_regular=False
def __init__(self,G=None):
self.G=G
def __call__(self,G):
return PseudoScalar(G)
def size(self):
return 1
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 __eq__(self,other):
return type(self)==type(other) and self.G==other.G
def __hash__(self):
return hash((type(self),self.G))
@property
def T(self):
return self
```

```
[18]:
```

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

We can then build up pseudotensors with multiplication. As expected pseudovectors incur a -1 for odd permutations.

```
[19]:
```

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

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

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

```
[11]:
```

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

```
[11]:
```

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

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

```
[12]:
```

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

```
[13]:
```

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

```
[14]:
```

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

And of course we can verify the equivariance:

```
[15]:
```

```
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 6.21e-08
```

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

```
[16]:
```

```
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ΒΆ

Several other functions may be optionally implemented to improve performance such as the Lie Algebra representation `drho(A)`

which by default is calculated automatically from `rho`

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

. However, these functions are optional and the representation can function fine without them.