Skip to content

Crystal graphs and plots

crystal

crystal.py

Combinatorial model of the Kashiwara crystal B(n) for U_q(sl_2).

In the q -> 0 limit, the basis vectors of V_n become the combinatorial objects {b_0, b_1, ..., b_n}. The generators E and F reduce to partial maps:

tilde_f(b_k) = b_{k+1},   for k < n;     tilde_f(b_n) = None
tilde_e(b_k) = b_{k-1},   for k > 0;     tilde_e(b_0) = None

Weight map: wt(b_k) = n - 2k.

This module constructs the crystal as a directed labelled graph: nodes carry (name, weight) pairs, and edges are tilde_f arrows.

CrystalNode dataclass

A single crystal node in B(n).

Source code in quantum_group/crystal.py
28
29
30
31
32
33
@dataclass
class CrystalNode:
    """A single crystal node in B(n)."""
    index: int      # k = 0, 1, ..., n
    weight: int     # n - 2k
    label: str      # "b_k"

build_crystal

build_crystal(n: int) -> nx.DiGraph

Construct the crystal graph B(n).

Parameters:

Name Type Description Default
n int

Highest weight (dimension = n + 1). n >= 0.

required

Returns:

Type Description
DiGraph

Each node "b_k" (str) has 'index', 'weight' and 'label' attributes. Edges are tilde_f arrows (b_k -> b_{k+1}).

Source code in quantum_group/crystal.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def build_crystal(n: int) -> nx.DiGraph:
    """Construct the crystal graph B(n).

    Parameters
    ----------
    n : int
        Highest weight (dimension = n + 1). n >= 0.

    Returns
    -------
    networkx.DiGraph
        Each node "b_k" (str) has 'index', 'weight' and 'label' attributes.
        Edges are tilde_f arrows (b_k -> b_{k+1}).
    """
    n = as_int(n, "n")
    if n < 0:
        raise ValueError("n must be nonnegative.")

    G = nx.DiGraph()
    for k in range(n + 1):
        node = f"b_{k}"
        G.add_node(node, index=k, weight=n - 2 * k, label=node)

    for k in range(n):
        G.add_edge(f"b_{k}", f"b_{k+1}", operator="f")

    G.graph["n"] = n
    return G

crystal_nodes

crystal_nodes(n: int) -> list[CrystalNode]

Return the nodes of B(n) as a list of CrystalNode objects.

Source code in quantum_group/crystal.py
66
67
68
69
70
def crystal_nodes(n: int) -> list[CrystalNode]:
    """Return the nodes of B(n) as a list of CrystalNode objects."""
    n = as_int(n, "n", minimum=0)
    return [CrystalNode(index=k, weight=n - 2 * k, label=f"b_{k}")
            for k in range(n + 1)]

f_tilde

f_tilde(node: CrystalNode, n: int) -> CrystalNode | None

Apply the crystal operator tilde_f.

Source code in quantum_group/crystal.py
73
74
75
76
77
78
def f_tilde(node: CrystalNode, n: int) -> CrystalNode | None:
    """Apply the crystal operator tilde_f."""
    if node.index >= n:
        return None
    return CrystalNode(index=node.index + 1, weight=n - 2 * (node.index + 1),
                       label=f"b_{node.index + 1}")

e_tilde

e_tilde(node: CrystalNode, n: int) -> CrystalNode | None

Apply the crystal operator tilde_e.

Source code in quantum_group/crystal.py
81
82
83
84
85
86
def e_tilde(node: CrystalNode, n: int) -> CrystalNode | None:
    """Apply the crystal operator tilde_e."""
    if node.index <= 0:
        return None
    return CrystalNode(index=node.index - 1, weight=n - 2 * (node.index - 1),
                       label=f"b_{node.index - 1}")

crystal_string

crystal_string(n: int) -> str

Return B(n) as text: b_0 -f-> b_1 -f-> ... -f-> b_n.

Source code in quantum_group/crystal.py
89
90
91
92
def crystal_string(n: int) -> str:
    """Return B(n) as text: b_0 -f-> b_1 -f-> ... -f-> b_n."""
    n = as_int(n, "n", minimum=0)
    return " -f-> ".join(f"b_{k}" for k in range(n + 1))

visualization

visualization.py

Visualization of U_q(sl_2) representations and crystals.

Two kinds of diagrams:

  1. Weight diagram — The K-weights of V_n (n - 2k, k = 0..n) are plotted as points on a number line. This has the same structure as the classical sl_2 weight diagram; q changes the base of the eigenvalues but not the pattern of exponents.

  2. Crystal graph — NetworkX draws the crystal B(n) as a directed path b_0 -> b_1 -> ... -> b_n.

All functions return a Matplotlib Figure; the caller handles display or saving.

plot_weight_diagram

plot_weight_diagram(n: int, ax: Axes | None = None) -> plt.Figure

Plot the weight diagram of V_n as points on a number line.

The weights are n, n-2, ..., -n+2, -n.

Source code in quantum_group/visualization.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def plot_weight_diagram(n: int, ax: plt.Axes | None = None) -> plt.Figure:
    """Plot the weight diagram of V_n as points on a number line.

    The weights are n, n-2, ..., -n+2, -n.
    """
    n = as_int(n, "n", minimum=0)
    weights = [n - 2 * k for k in range(n + 1)]

    if ax is None:
        fig, ax = plt.subplots(figsize=(max(4, n + 2), 1.8))
    else:
        # An Axes placed in a SubFigure reports that SubFigure here.
        fig = cast(plt.Figure, ax.figure)

    ax.axhline(0, color="lightgray", linewidth=1, zorder=0)
    ax.scatter(weights, [0] * len(weights), s=120, color="C0", zorder=3)

    for w in weights:
        ax.annotate(f"{w}", xy=(w, 0), xytext=(0, 10),
                    textcoords="offset points", ha="center", fontsize=10)

    pad = 1
    ax.set_xlim(min(weights) - pad, max(weights) + pad)
    ax.set_ylim(-1, 1)
    ax.set_yticks([])
    ax.set_xticks(weights)
    ax.set_xlabel("weight (q-exponent of the K-eigenvalue)")
    ax.set_title(f"V_{n} weight diagram (dimension {n+1})")
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.spines["left"].set_visible(False)
    fig.tight_layout()
    return fig

plot_crystal_graph

plot_crystal_graph(n: int, ax: Axes | None = None) -> plt.Figure

Plot the crystal graph B(n) as a horizontal directed path.

Source code in quantum_group/visualization.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def plot_crystal_graph(n: int, ax: plt.Axes | None = None) -> plt.Figure:
    """Plot the crystal graph B(n) as a horizontal directed path."""
    G = build_crystal(n)

    if ax is None:
        fig, ax = plt.subplots(figsize=(max(5, n + 2), 2.0))
    else:
        # An Axes placed in a SubFigure reports that SubFigure here.
        fig = cast(plt.Figure, ax.figure)

    pos = {f"b_{k}": (k, 0) for k in range(n + 1)}

    nx.draw_networkx_nodes(G, pos, node_color="C1", node_size=600, ax=ax)
    nx.draw_networkx_labels(G, pos, labels={node: node for node in G.nodes()},
                            font_size=10, ax=ax)
    nx.draw_networkx_edges(G, pos, arrows=True, arrowstyle="-|>",
                           arrowsize=18, edge_color="black",
                           connectionstyle="arc3", ax=ax)
    edge_labels = {(u, v): r"$\tilde f$" for u, v in G.edges()}
    nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels,
                                 font_size=9, ax=ax)

    ax.set_title(f"B({n}) crystal graph")
    ax.set_axis_off()
    ax.set_xlim(-0.5, n + 0.5)
    ax.set_ylim(-0.5, 0.5)
    fig.tight_layout()
    return fig

plot_combined

plot_combined(n: int) -> plt.Figure

Plot both the weight diagram and crystal graph in one call.

Source code in quantum_group/visualization.py
103
104
105
106
107
108
109
def plot_combined(n: int) -> plt.Figure:
    """Plot both the weight diagram and crystal graph in one call."""
    fig, axes = plt.subplots(2, 1, figsize=(max(5, n + 2), 4.0))
    plot_weight_diagram(n, ax=axes[0])
    plot_crystal_graph(n, ax=axes[1])
    fig.tight_layout()
    return fig