codexcyclic-representation-decomposition.py
cyclic-representation-decomposition.py
"""Mean/contrast decomposition of a three-coordinate cyclic representation."""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
p = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]])
b = np.column_stack((np.array([2, -1, -1]) / np.sqrt(6),
np.array([0, 1, -1]) / np.sqrt(2)))
rotation = np.array([[-0.5, -np.sqrt(3) / 2],
[np.sqrt(3) / 2, -0.5]])
assert np.allclose(b.T @ b, np.eye(2))
assert np.allclose(b.T @ p @ b, rotation)
assert np.array_equal(np.linalg.matrix_power(p, 3), np.eye(3))
x = np.array([4, 1, 1])
mean = np.full(3, x.mean())
contrast = x - mean
assert np.array_equal(contrast, [2, -1, -1])
fig, axes = plt.subplots(1, 2, figsize=(10.5, 5.3), dpi=120)
fig.patch.set_facecolor("white")
sites = np.arange(3)
colors = ["#333e60", "#147c67", "#c76716"]
for offset, values, color, label in zip(
[-0.24, 0, 0.24], [x, mean, contrast], colors,
["original", "fixed mean", "zero-sum contrast"]):
bars = axes[0].bar(sites + offset, values, 0.23, color=color, label=label)
axes[0].bar_label(bars, padding=3, fontsize=11)
axes[0].axhline(0, color="#444444", lw=0.8)
axes[0].set_xticks(sites, ["1", "2", "3"])
axes[0].set_xlabel("Coordinate label")
axes[0].set_ylabel("Value")
axes[0].set_ylim(-1.6, 4.8)
axes[0].set_title(r"$(4,1,1)=2(1,1,1)+(2,-1,-1)$", fontsize=13)
axes[0].legend(loc="upper center", bbox_to_anchor=(0.5, -0.24),
ncol=3, frameon=False, fontsize=10, columnspacing=1)
ax = axes[1]
ax.set_aspect("equal")
ax.set_xlim(-3.1, 3.7)
ax.set_ylim(-3.1, 3.1)
t = np.linspace(0, 2 * np.pi, 400)
ax.plot(np.sqrt(6) * np.cos(t), np.sqrt(6) * np.sin(t),
ls="--", color="#c5cbd1", lw=1)
vectors = [np.linalg.matrix_power(p, n) @ contrast for n in range(3)]
labels = [r"$v=(2,-1,-1)$", r"$Pv=(-1,2,-1)$",
r"$P^2v=(-1,-1,2)$"]
for vector, label, color in zip(vectors, labels, colors):
xy = b.T @ vector
ax.annotate("", xy=xy, xytext=(0, 0),
arrowprops={"arrowstyle": "-|>", "color": color,
"lw": 2.5, "mutation_scale": 18})
pos = xy * 1.08
ax.text(*pos, label, color=color, fontsize=10,
ha="right" if xy[0] >= 0 else "center",
va="bottom" if xy[1] >= 0 else "top")
ax.axhline(0, color="#eeeeee", zorder=0)
ax.axvline(0, color="#eeeeee", zorder=0)
ax.set_xlabel(r"Coefficient of $u=(2,-1,-1)/\sqrt{6}$")
ax.set_ylabel(r"Coefficient of $v_{\rm basis}=(0,1,-1)/\sqrt{2}$")
ax.set_title("The contrast plane: rotation by 120 degrees", fontsize=13)
fig.suptitle("A symmetry-preserved line plus a symmetry-preserved plane",
fontsize=16, y=0.98)
fig.subplots_adjust(left=0.07, right=0.98, bottom=0.24, top=0.84, wspace=0.36)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()