Transformation under Euclidean symmetries#

In this section, we will show how the action of a symmetry element denoted by g can be tailored to respect symmetries when knowing the irreducible representation type Irrep of the input data.

For example, we take the irreducible representation obtained through decomposing an arbitrary rank-2 tensor: “1x0e + 1x1o + 1x2o” from the previous section. Irrep objects are represented as a vector by concatenating irreducible componentes of increasing order l. Remember that a l-irrep is of dimension R2l+1, the irrep “1x0e + 1x1o + 1x2o” can be expressed as a vector of R9:

Irrep=[I0I1I2]=[c1v1v2v3l1l2l3l4l5]R9

Under this definition, any symmetry elements g such as rotations can be represented as a block diagonal square matrix D(g) that can be constructed based on the irreducible representation type. For the irrep “1x0e + 1x1o + 1x2o”, D(g) will be a square matrix of R9×9:

D(g)=[1D(g)l=1D(g)l=2] where D(g)lR(2l+1)×(2l+1) is the transformation matrix for l-irrep

Notice that D(g)l=0 for irrep “n× 0e” of n scalars will be In×n since scalar values do not change under rotation.

For the illustration below we will use the example of a particle sitting at location (x,y,z) with four vector properties v0,v1,v2,v3R3 (velocity, acceleration, spin etc.) This particle and all of its properties can be represented by an Irrep of “3x0e + 4x1o” expressed as a vector of R15. Its corresponding transformation matrix D(g) given an arbitrary symmetry opertation g will be of R15×15.

The illustration plotted here showcases how the block diagonal matrix D(g) and the particle properties transforms under a rotation operation with α=β=θ varing from 0 to 2π.

Click to show
from numpy import *
import numpy as np

n_vec = 4
x = np.zeros(n_vec)
y = np.zeros(n_vec)
z = np.zeros(n_vec)

# phi = 2*np.pi*(np.random.random(n_vec)*0.05+0.125)
phi = 2*np.pi*(0.125)
# theta = np.arccos(-1 + 2*(np.random.random(n_vec)*0.05+sqrt(2)/2))
theta = np.arccos(-1 + 2*(sqrt(2)/2))
R = np.ones(n_vec)
u = np.abs(R * sin(theta) * cos(phi)) * np.array([-1,1,-1,1])
v = np.abs(R * sin(theta) * sin(phi)) * np.array([1,-1,-1,1])
w = np.abs(R * cos(theta)) * np.array([1,1,-1,-1])
Click to show
import plotly.figure_factory as ff
import plotly.graph_objects as go
import plotly.express as px
from plotly.express.colors import sample_colorscale

c = px.colors.qualitative.Plotly
# c = sample_colorscale('Viridis', np.linspace(0,1,4))

vector_flattened = np.concatenate((u[None,:],v[None,:],w[None,:]), axis=0).flatten("F")
vector_in = np.concatenate(([0,0,0], vector_flattened))

from e3nn.o3 import Irreps

irreps = Irreps("3x0e + 4x1o")

import plotly.graph_objects as go
import numpy as np
from plotly.subplots import make_subplots
import torch
t = torch.tensor

# Create figure
fig = make_subplots(rows=1, cols=2,
    specs=[[{"type": "xy"}, {"type": "scene"}]],
    column_widths=[0.45, 0.55])

# Add traces, one for each slider step
rotations = np.linspace(0, 2*np.pi, 101)
for step in rotations:
    # a small rotation around the y axis
    D = irreps.D_from_angles(alpha=t(float(step)), beta=t(float(step)), gamma=t(float(step)), k=t(0))

    # Display Heatmap
    fig.add_trace(
        go.Heatmap(visible=False, z=D, colorscale="RdYlBu", zmin=-1, zmax=1, 
        colorbar=dict(x=0.40, 
            y=0, 
            xanchor='left', 
            yanchor='bottom',
            len=1, 
            thickness=20)),
        row=1, col=1)

    # Calculate vector after rotation
    v_out = vector_in @ np.array(D)

    # Plot Points and Vectors after rotation
    fig = fig.add_trace(go.Scatter3d(
        x=v_out[0:1], y=v_out[1:2], z=v_out[2:3],
        mode='markers',
        name='point (3x0e)',
        marker=dict(
            size=12,
            color=c[0]),
        visible=False),
        row=1,
        col=2
    )
    for idx in range(n_vec):
        x_vec = np.array([v_out[0], v_out[3*idx+3]])
        y_vec = np.array([v_out[1], v_out[3*idx+4]])
        z_vec = np.array([v_out[2], v_out[3*idx+5]])
        fig.add_trace(go.Scatter3d(
            visible=False,
            x=x_vec, y=y_vec, z=z_vec,
            mode='lines', name='vector {} (1x1o)'.format(idx),
            line=dict(color=c[idx],width=2)
        ), row=1, col=2)
        x_cone = [v_out[3*idx+3]*0.9]
        y_cone = [v_out[3*idx+4]*0.9]
        z_cone = [v_out[3*idx+5]*0.9]
        u_cone = [v_out[3*idx+3]*0.3]
        v_cone = [v_out[3*idx+4]*0.3]
        w_cone = [v_out[3*idx+5]*0.3]
        fig.add_trace(go.Cone(
            x=x_cone, y=y_cone, z=z_cone, u=u_cone, v=v_cone, w=w_cone,
            showscale=False, colorscale=[[0, c[idx]], [1, c[idx]]], sizemode="absolute", visible=False
        ), row=1, col=2)

# Make first set of traces visible
# for idx in range(410, 420):
for idx in range(10):
    fig.data[idx].visible = True

# Create and add slider
steps = []
for i in range(len(rotations)):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig.data)},
              {"title": 'Rotation with angle α = β = γ = {:.2f}π'.format(0.02*i)}],  # layout attribute
            # {"title":  'α =β=γ=π'}]
    )
    for idx in range(10):
        step["args"][0]["visible"][10*i+idx] = True  # Toggle i'th trace to "visible"
    steps.append(step)

sliders = [dict(
    active=0,
    currentvalue={"prefix": "Rotation "},
    steps=steps
)]

fig.update_layout(
    sliders=sliders, 
    title_text='Rotation with angle α = β = γ = 0π'
)
fig.update_yaxes(autorange="reversed")
fig.update_layout(
    scene = dict(
        xaxis = dict(nticks=4, range=[-1,1]),
        yaxis = dict(nticks=4, range=[-1,1]),
        zaxis = dict(nticks=4, range=[-1,1]),
    ),
    scene_aspectmode='cube',
    autosize=False,
    width=1000,
    height=500)

fig.show()
# fig.write_image('figs/transformation.pdf')
/Users/killiansheriff/opt/miniconda3/lib/python3.9/site-packages/e3nn/o3/_wigner.py:92: UserWarning:

An output with one or more elements was resized since it had shape [1, 3, 3], which does not match the required output shape [1, 1, 3, 3]. This behavior is deprecated, and in a future PyTorch release outputs will not be resized unless they have zero elements. You can explicitly reuse an out tensor t by resizing it, inplace, to zero elements with t.resize_(0). (Triggered internally at  /Users/runner/work/pytorch/pytorch/pytorch/aten/src/ATen/native/Resize.cpp:24.)
051014121086420
point (3x0e)vector 0 (1x1o)vector 1 (1x1o)vector 2 (1x1o)vector 3 (1x1o)Rotation step-0step-0step-9step-18step-27step-36step-45step-54step-63step-72step-81step-90step-99−1−0.500.51Rotation with angle α = β = γ = 0π