Skip to content

qten.pointgroups.finite

Module reference for qten.pointgroups.finite.

finite

Finite point groups generated by exact symbolic matrices.

This module defines FinitePointGroup, the multi-generator counterpart to PointGroupElement. A finite point group stores exact generator matrices, closes under composition to obtain all group elements, and optionally uses packaged Bilbao character tables to build irrep projectors and polynomial sector bases.

Repository usage

Named crystallographic groups are usually constructed through pointgroup(). Use this module directly when assembling a custom finite group from exact matrices.

FinitePointGroup dataclass

FinitePointGroup(
    generators: tuple[PointGroupElement, ...],
    axes: tuple[Symbol, ...],
    symbol: str | None = None,
    irreps: dict[str, Any] | None = None,
    spinor_irreps: dict[str, Any] | None = None,
    spin: str = "electron",
    class_indices: tuple[int, ...] | None = None,
)

Finite point group represented by exact generator matrices.

FinitePointGroup stores one or more exact generators as PointGroupElement objects on a shared ordered axis tuple. Closing under composition yields the full group of linear actions. Ordinary and spinor class tables come from the packaged catalog when present; otherwise they are computed from the generated group. With those tables the group can project homogeneous polynomials onto irreducible sectors.

Mathematical meaning

Let \(G\) be the finite matrix group generated by the stored generators. On a representation \(D\), [ P^{\mathrm{triv}} = \frac{1}{|G|}\sum_{g\in G} D(g),\qquad P^\mu = \frac{d_\mu}{|G|}\sum_{g\in G}\chi^\mu(g)^* D(g). ] irrep_projector takes \(D(g)\) to be the Euclidean polynomial representation of degree order. Hilbert-space projectors use the same formula with \(D(g)=D_{\mathrm{orb}}(g)\) or \(D(g)=D_{\mathrm{orb}}(g)\otimes u(g)\).

Spinor characters are projective for the section \(u\): \(u(g)u(h)=\omega(g,h)\,u(gh)\). Packaged class rows average \(\chi\) over each ordinary conjugacy class and write \(0\) on non-\(\omega\)-regular classes. Projectors instead use the element-wise hat-table section (spinor_irrep_characters_by_element).

Attributes:

Name Type Description
generators tuple[PointGroupElement, ...]

Exact linear generators that share the same ordered axes.

axes tuple[Symbol, ...]

Ordered coordinate symbols for every generator and group element.

symbol str | None

Optional Hermann-Mauguin symbol, such as "4mm".

irreps dict[str, Any] | None

Optional packaged character-table payload with class_labels, multiplicities, and per-irrep character rows. Included in equality and hashing so table-dependent caches cannot collide.

spinor_irreps dict[str, Any] | None

Optional class-wise projective spinor character data, same shape as irreps. Included in equality and hashing.

spin str

Construction-time spin policy. "electron" (default) uses the \(SU(2)\) lift; "trivial" uses \(u(g)=I\).

class_indices tuple[int, ...] | None

Optional map from generated element index to irreps["class_labels"] index. Set by reoriented_by so Bilbao characters follow conjugation. Included in equality and hashing so alignment-dependent caches cannot collide.

Notes

Character-table labels on a standard xyz group are aligned to generated conjugacy classes by matrix invariants (order, determinant, trace, and common mirror geometry). After a conjugation, the stored class_indices are used instead of re-matching geometry in the new frame. Ordinary and spinor tables are computed from the generated group when they are not packaged. Alignment raises ValueError when a packaged table cannot be matched to the generated conjugacy classes.

generators instance-attribute

generators: tuple[PointGroupElement, ...]

axes instance-attribute

axes: tuple[Symbol, ...]

symbol class-attribute instance-attribute

symbol: str | None = None

irreps class-attribute instance-attribute

irreps: dict[str, Any] | None = field(
    default=None, compare=False, hash=False
)

spinor_irreps class-attribute instance-attribute

spinor_irreps: dict[str, Any] | None = field(
    default=None, compare=False, hash=False
)

spin class-attribute instance-attribute

spin: str = 'electron'

class_indices class-attribute instance-attribute

class_indices: tuple[int, ...] | None = field(
    default=None, compare=False, hash=False
)

order

order() -> int

__post_init__

__post_init__() -> None
Source code in src/qten/pointgroups/finite.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
def __post_init__(self) -> None:
    if not self.generators:
        raise ValueError("FinitePointGroup requires at least one generator.")
    if any(generator.axes != self.axes for generator in self.generators):
        raise ValueError("All generators must share the group's ordered axes.")
    dim = len(self.axes)
    if any(generator.irrep.shape != (dim, dim) for generator in self.generators):
        raise ValueError("Generator matrix shape must match the axis dimension.")
    if self.spin not in {"electron", "trivial"}:
        raise ValueError("spin must be 'electron' or 'trivial'.")
    if any(generator.spin != self.spin for generator in self.generators):
        raise ValueError("All generators must share the group's spin policy.")
    has_rotation3 = [
        generator.rotation3 is not None for generator in self.generators
    ]
    if any(has_rotation3) and not all(has_rotation3):
        raise ValueError(
            "All generators must store rotation3, or none of them. "
            "A missing rotation3 is not the identity."
        )

__hash__

__hash__() -> int
Source code in src/qten/pointgroups/finite.py
205
206
207
208
209
210
211
212
213
214
215
216
def __hash__(self) -> int:
    return hash(
        (
            self.generators,
            self.axes,
            self.symbol,
            _freeze_table(self.irreps),
            _freeze_table(self.spinor_irreps),
            self.spin,
            self.class_indices,
        )
    )

from_matrices classmethod

from_matrices(
    matrices: Iterable[ImmutableDenseMatrix],
    axes: tuple[Symbol, ...],
    *,
    symbol: str | None = None,
    irreps: dict[str, Any] | None = None,
    spinor_irreps: dict[str, Any] | None = None,
    rotation3s: Iterable[ImmutableDenseMatrix | None]
    | None = None,
    spin: str = "electron",
    class_indices: tuple[int, ...] | None = None,
) -> FinitePointGroup

Build a finite point group from exact generator matrices.

Parameters:

Name Type Description Default
matrices Iterable[ImmutableDenseMatrix]

Generator matrices expressed on the ordered axes basis.

required
axes tuple[Symbol, ...]

Ordered coordinate symbols shared by every generator.

required
symbol str | None

Optional Hermann-Mauguin symbol for display and sector labels.

None
irreps dict[str, Any] | None

Optional packaged character-table payload.

None
spinor_irreps dict[str, Any] | None

Optional class-wise projective spinor character data.

None
rotation3s Iterable[ImmutableDenseMatrix | None] | None

Optional Cartesian \(O(3)\) matrix for each generator, used when the spatial matrices are not already 3D.

None
spin str

Construction-time spin policy.

'electron'
class_indices tuple[int, ...] | None

Optional generated-element to character-table class map.

None

Returns:

Type Description
FinitePointGroup

Finite group whose generators are the corresponding PointGroupElement wrappers.

Source code in src/qten/pointgroups/finite.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
@classmethod
def from_matrices(
    cls,
    matrices: Iterable[sy.ImmutableDenseMatrix],
    axes: tuple[sy.Symbol, ...],
    *,
    symbol: str | None = None,
    irreps: dict[str, Any] | None = None,
    spinor_irreps: dict[str, Any] | None = None,
    rotation3s: Iterable[sy.ImmutableDenseMatrix | None] | None = None,
    spin: str = "electron",
    class_indices: tuple[int, ...] | None = None,
) -> "FinitePointGroup":
    r"""
    Build a finite point group from exact generator matrices.

    Parameters
    ----------
    matrices : Iterable[sy.ImmutableDenseMatrix]
        Generator matrices expressed on the ordered `axes` basis.
    axes : tuple[sy.Symbol, ...]
        Ordered coordinate symbols shared by every generator.
    symbol : str | None, optional
        Optional Hermann-Mauguin symbol for display and sector labels.
    irreps : dict[str, Any] | None, optional
        Optional packaged character-table payload.
    spinor_irreps : dict[str, Any] | None, optional
        Optional class-wise projective spinor character data.
    rotation3s : Iterable[sy.ImmutableDenseMatrix | None] | None, optional
        Optional Cartesian \(O(3)\) matrix for each generator, used when the
        spatial matrices are not already 3D.
    spin : str, optional
        Construction-time spin policy.
    class_indices : tuple[int, ...] | None, optional
        Optional generated-element to character-table class map.

    Returns
    -------
    FinitePointGroup
        Finite group whose generators are the corresponding
        [`PointGroupElement`][qten.pointgroups.elements.PointGroupElement]
        wrappers.
    """

    matrix_list = tuple(matrices)
    if rotation3s is None:
        if len(axes) == 3:
            rotation_list = matrix_list
        else:
            rotation_list = (None,) * len(matrix_list)
    else:
        rotation_list = tuple(rotation3s)
        if len(rotation_list) != len(matrix_list):
            raise ValueError("rotation3s must contain one matrix per generator.")
    if spin not in {"electron", "trivial"}:
        raise ValueError("spin must be 'electron' or 'trivial'.")
    generators = tuple(
        PointGroupElement(irrep=matrix, axes=axes, rotation3=rotation3, spin=spin)
        for matrix, rotation3 in zip(matrix_list, rotation_list)
    )
    return cls(
        generators=generators,
        axes=axes,
        symbol=symbol,
        irreps=irreps,
        spinor_irreps=spinor_irreps,
        spin=spin,
        class_indices=class_indices,
    )

elements cached

elements(
    max_order: int = 512,
) -> tuple[PointGroupElement, ...]

Generate all group elements by closure under the generators.

Parameters:

Name Type Description Default
max_order int

Safety bound on the number of distinct elements discovered while closing the group.

512

Returns:

Type Description
tuple[PointGroupElement, ...]

All distinct group elements, starting with the identity.

Raises:

Type Description
ValueError

If closure exceeds max_order.

Source code in src/qten/pointgroups/finite.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
@lru_cache
def elements(self, max_order: int = 512) -> tuple[PointGroupElement, ...]:
    """
    Generate all group elements by closure under the generators.

    Parameters
    ----------
    max_order : int, default 512
        Safety bound on the number of distinct elements discovered while
        closing the group.

    Returns
    -------
    tuple[PointGroupElement, ...]
        All distinct group elements, starting with the identity.

    Raises
    ------
    ValueError
        If closure exceeds `max_order`.
    """

    dim = len(self.axes)
    rotation3 = None
    if any(generator.rotation3 is not None for generator in self.generators):
        rotation3 = sy.ImmutableDenseMatrix.eye(3)
    identity = PointGroupElement(
        irrep=sy.ImmutableDenseMatrix.eye(dim),
        axes=self.axes,
        rotation3=rotation3,
        spin=self.spin,
    )
    elements = [identity]
    seen = {_matrix_key(identity.irrep)}
    frontier = [identity]

    while frontier:
        current = frontier.pop(0)
        for generator in self.generators:
            for candidate in (generator @ current, current @ generator):
                key = _matrix_key(candidate.irrep)
                if key in seen:
                    continue
                seen.add(key)
                elements.append(candidate)
                frontier.append(candidate)
                if len(elements) > max_order:
                    raise ValueError(
                        "Failed to close finite point group within "
                        f"max_order={max_order}."
                    )

    return tuple(elements)

is_abelian

is_abelian() -> bool

Return whether all generated elements commute.

Source code in src/qten/pointgroups/finite.py
348
349
350
351
352
353
354
355
356
357
358
359
def is_abelian(self) -> bool:
    """Return whether all generated elements commute."""

    elements = self.elements()
    zero = sy.zeros(len(self.axes), len(self.axes))
    for i, left in enumerate(elements):
        for right in elements[i + 1 :]:
            if not sy.simplify(
                left.irrep @ right.irrep - right.irrep @ left.irrep
            ).equals(zero):
                return False
    return True

conjugacy_classes cached

conjugacy_classes() -> tuple[tuple[int, ...], ...]

Return conjugacy classes as tuples of element indices.

Returns:

Type Description
tuple[tuple[int, ...], ...]

Each inner tuple lists indices into elements() that form one conjugacy class.

Source code in src/qten/pointgroups/finite.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
@lru_cache
def conjugacy_classes(self) -> tuple[tuple[int, ...], ...]:
    """
    Return conjugacy classes as tuples of element indices.

    Returns
    -------
    tuple[tuple[int, ...], ...]
        Each inner tuple lists indices into [`elements()`][qten.pointgroups.finite.FinitePointGroup.elements]
        that form one conjugacy class.
    """

    elements = self.elements()
    key_to_index = {
        _matrix_key(element.irrep): i for i, element in enumerate(elements)
    }
    unassigned = set(range(len(elements)))
    classes: list[tuple[int, ...]] = []
    while unassigned:
        i = min(unassigned)
        representative = elements[i]
        class_indices = set()
        for h in elements:
            conjugated = h @ representative @ h.inv()
            class_indices.add(key_to_index[_matrix_key(conjugated.irrep)])
        classes.append(tuple(sorted(class_indices)))
        unassigned -= class_indices
    return tuple(classes)

ordinary_table cached

ordinary_table() -> dict[str, Any]

Return ordinary irreps, computing them when no table is packaged.

Source code in src/qten/pointgroups/finite.py
504
505
506
507
508
509
510
511
@lru_cache
def ordinary_table(self) -> dict[str, Any]:
    """Return ordinary irreps, computing them when no table is packaged."""
    if self.irreps:
        return self.irreps
    from ._characters import compute_ordinary_irreps

    return compute_ordinary_irreps(self)

element_class_indices

element_class_indices() -> tuple[int, ...]

Return the aligned character-table class index for each element.

Returns:

Type Description
tuple[int, ...]

For each index into elements(), the matching index into irreps["class_labels"].

Raises:

Type Description
ValueError

If character-table data is missing or cannot be aligned.

Source code in src/qten/pointgroups/finite.py
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
def element_class_indices(self) -> tuple[int, ...]:
    """
    Return the aligned character-table class index for each element.

    Returns
    -------
    tuple[int, ...]
        For each index into [`elements()`][qten.pointgroups.finite.FinitePointGroup.elements],
        the matching index into `irreps["class_labels"]`.

    Raises
    ------
    ValueError
        If character-table data is missing or cannot be aligned.
    """

    return self._class_label_index_by_element()

irrep_characters_by_element

irrep_characters_by_element(
    irrep: str,
) -> tuple[sy.Expr, ...]

Return irrep characters ordered by generated group elements.

Parameters:

Name Type Description Default
irrep str

Irrep label from the packaged character table, such as "A1" or "E".

required

Returns:

Type Description
tuple[Expr, ...]

Character values aligned to elements().

Raises:

Type Description
ValueError

If the irrep is unknown or character-table data is incomplete.

Source code in src/qten/pointgroups/finite.py
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
def irrep_characters_by_element(self, irrep: str) -> tuple[sy.Expr, ...]:
    """
    Return irrep characters ordered by generated group elements.

    Parameters
    ----------
    irrep : str
        Irrep label from the packaged character table, such as `"A1"` or
        `"E"`.

    Returns
    -------
    tuple[sy.Expr, ...]
        Character values aligned to [`elements()`][qten.pointgroups.finite.FinitePointGroup.elements].

    Raises
    ------
    ValueError
        If the irrep is unknown or character-table data is incomplete.
    """

    table = self.ordinary_table()
    irrep_table = table["irreps"]
    if irrep not in irrep_table:
        raise ValueError(f"Unknown irrep '{irrep}' for point group {self.symbol}.")

    labels = table["class_labels"]
    characters = tuple(
        _character_expr(character) for character in irrep_table[irrep]["characters"]
    )
    if len(characters) != len(labels):
        raise ValueError(
            f"Character row length for irrep '{irrep}' does not match class labels."
        )

    class_by_element = self._class_label_index_by_element()
    return tuple(characters[class_index] for class_index in class_by_element)

spinor_table cached

spinor_table() -> dict[str, Any]

Return class-wise spinor data, computing it from the \(SU(2)\) lift if needed.

Each row is a projective irrep of \(G\) for the principal section \(u\). Class entries are averages of the hat-table characters; they are \(0\) on non-\(\omega\)-regular classes.

Source code in src/qten/pointgroups/finite.py
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
@lru_cache
def spinor_table(self) -> dict[str, Any]:
    r"""Return class-wise spinor data, computing it from the \(SU(2)\) lift if needed.

    Each row is a projective irrep of \(G\) for the principal section
    \(u\). Class entries are averages of the hat-table characters; they
    are \(0\) on non-\(\omega\)-regular classes.
    """
    if self.spin != "electron":
        raise ValueError(
            f"Point group {self.symbol} was defined with spin={self.spin!r}."
        )
    if self.spinor_irreps:
        return self.spinor_irreps
    from ._characters import compute_spinor_irreps

    return compute_spinor_irreps(self)

spinor_irrep_characters_by_element

spinor_irrep_characters_by_element(
    irrep: str,
) -> tuple[complex, ...]

Return projective spinor characters in generated-element order.

Class-wise packaged rows set \(\chi=0\) on non-\(\omega\)-regular classes, where \(\omega(g,h)\) is the 2-cocycle of the \(SU(2)\) section. Projectors need the value on each generated element, taken from the hat-group character table of the double cover of order \(2|G|\).

Source code in src/qten/pointgroups/finite.py
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
def spinor_irrep_characters_by_element(self, irrep: str) -> tuple[complex, ...]:
    r"""Return projective spinor characters in generated-element order.

    Class-wise packaged rows set \(\chi=0\) on non-\(\omega\)-regular
    classes, where \(\omega(g,h)\) is the 2-cocycle of the \(SU(2)\) section.
    Projectors need the value on each generated element, taken from the
    hat-group character table of the double cover of order \(2|G|\).
    """
    table = self.spinor_table()
    if not table:
        raise ValueError(
            f"No spinor character-table data is available for {self.symbol}."
        )
    irrep_table = table["irreps"]
    if irrep not in irrep_table:
        raise ValueError(
            f"Unknown spinor irrep '{irrep}' for point group {self.symbol}."
        )
    labels = table.get("class_labels")
    if not labels:
        raise ValueError(f"Spinor table for {self.symbol} is missing class labels.")
    if self.irreps and labels != list(self.irreps["class_labels"]):
        raise ValueError(
            "Spinor class labels must match the ordinary class labels "
            f"for {self.symbol}."
        )
    characters = self._spinor_element_characters()[irrep]
    if len(characters) != self.order:
        raise ValueError(
            f"Spinor characters for '{irrep}' do not match the group order."
        )
    return characters

reoriented_by

reoriented_by(rotation: Matrix) -> FinitePointGroup

Return the same abstract group with every matrix conjugated by rotation.

Writes \(g'=QgQ^{-1}\). Ordinary characters are class functions, so each conjugated element keeps the Bilbao class index of its preimage. Spinor characters are not transported: the principal lift of \(R(g')\) need not be \(u(Q)u(g)u(Q)^{-1}\), so the table is recomputed from the new section.

Source code in src/qten/pointgroups/finite.py
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
def reoriented_by(self, rotation: sy.Matrix) -> "FinitePointGroup":
    r"""
    Return the same abstract group with every matrix conjugated by `rotation`.

    Writes \(g'=QgQ^{-1}\). Ordinary characters are class functions, so
    each conjugated element keeps the Bilbao class index of its preimage.
    Spinor characters are not transported: the principal lift of \(R(g')\)
    need not be \(u(Q)u(g)u(Q)^{-1}\), so the table is recomputed from
    the new section.
    """
    matrix = sy.ImmutableDenseMatrix(sy.simplify(rotation))
    dim = len(self.axes)
    if matrix.shape != (dim, dim):
        raise ValueError(
            f"Reorientation matrix must be {dim}x{dim}, got {matrix.shape}."
        )
    inverse = sy.ImmutableDenseMatrix(sy.simplify(matrix.inv()))

    def _reoriented_rotation3(
        generator: PointGroupElement,
    ) -> sy.ImmutableDenseMatrix | None:
        if generator.rotation3 is None:
            return None
        if dim == 3:
            return sy.ImmutableDenseMatrix(
                sy.simplify(matrix @ generator.rotation3 @ inverse)
            )
        from ..phys.spin import _embed_in_cartesian_xyz

        axis_names = tuple(getattr(axis, "name", str(axis)) for axis in self.axes)
        embedded = _embed_in_cartesian_xyz(matrix, axis_names)
        embedded_inv = _embed_in_cartesian_xyz(inverse, axis_names)
        return sy.ImmutableDenseMatrix(
            sy.simplify(embedded @ generator.rotation3 @ embedded_inv)
        )

    new_generators = tuple(
        PointGroupElement(
            irrep=sy.ImmutableDenseMatrix(
                sy.simplify(matrix @ generator.irrep @ inverse)
            ),
            axes=self.axes,
            rotation3=_reoriented_rotation3(generator),
            spin=self.spin,
        )
        for generator in self.generators
    )
    reoriented = FinitePointGroup(
        generators=new_generators,
        axes=self.axes,
        symbol=self.symbol,
        irreps=self.irreps,
        spinor_irreps=None,
        spin=self.spin,
    )
    transported = self._transported_class_indices(reoriented, matrix, inverse)
    if transported is None:
        return reoriented
    return FinitePointGroup(
        generators=reoriented.generators,
        axes=reoriented.axes,
        symbol=reoriented.symbol,
        irreps=reoriented.irreps,
        spinor_irreps=None,
        spin=reoriented.spin,
        class_indices=transported,
    )

trivial_projector cached

trivial_projector(order: int) -> sy.ImmutableDenseMatrix

Project the polynomial representation onto the invariant sector.

Parameters:

Name Type Description Default
order int

Homogeneous polynomial degree used for the Euclidean representation.

required

Returns:

Type Description
ImmutableDenseMatrix

Exact projector \(P^{\mathrm{triv}}\) onto group-invariant polynomials of degree order.

Source code in src/qten/pointgroups/finite.py
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
@lru_cache
def trivial_projector(self, order: int) -> sy.ImmutableDenseMatrix:
    r"""
    Project the polynomial representation onto the invariant sector.

    Parameters
    ----------
    order : int
        Homogeneous polynomial degree used for the Euclidean representation.

    Returns
    -------
    sy.ImmutableDenseMatrix
        Exact projector \(P^{\mathrm{triv}}\) onto group-invariant
        polynomials of degree `order`.
    """

    reps = [element.euclidean_repr(order) for element in self.elements()]
    projector = sy.zeros(reps[0].rows, reps[0].cols)
    for rep in reps:
        projector += rep
    return sy.ImmutableDenseMatrix(sy.simplify(projector / len(reps)))

irrep_projector cached

irrep_projector(
    order: int, irrep: str
) -> sy.ImmutableDenseMatrix

Project the polynomial representation onto a character-table sector.

Uses ordinary (linear) \(\chi\) and the Euclidean \(D(g)\) of degree order. This is not the spinor Hilbert-space projector.

Parameters:

Name Type Description Default
order int

Homogeneous polynomial degree used for the Euclidean representation.

required
irrep str

Irrep label from the packaged or computed character table.

required

Returns:

Type Description
ImmutableDenseMatrix

Exact projector \(P^\mu = \frac{d_\mu}{|G|}\sum_g \chi^\mu(g)^* D(g)\).

Raises:

Type Description
ValueError

If the irrep is unknown or character-table data is incomplete.

Source code in src/qten/pointgroups/finite.py
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
@lru_cache
def irrep_projector(self, order: int, irrep: str) -> sy.ImmutableDenseMatrix:
    r"""
    Project the polynomial representation onto a character-table sector.

    Uses ordinary (linear) \(\chi\) and the Euclidean \(D(g)\) of degree
    `order`. This is not the spinor Hilbert-space projector.

    Parameters
    ----------
    order : int
        Homogeneous polynomial degree used for the Euclidean representation.
    irrep : str
        Irrep label from the packaged or computed character table.

    Returns
    -------
    sy.ImmutableDenseMatrix
        Exact projector
        \(P^\mu = \frac{d_\mu}{|G|}\sum_g \chi^\mu(g)^* D(g)\).

    Raises
    ------
    ValueError
        If the irrep is unknown or character-table data is incomplete.
    """

    table = self.ordinary_table()
    irrep_table = table["irreps"]
    if irrep not in irrep_table:
        raise ValueError(f"Unknown irrep '{irrep}' for point group {self.symbol}.")

    labels = table["class_labels"]
    row = irrep_table[irrep]
    characters = tuple(
        _character_expr(character) for character in row["characters"]
    )
    if len(characters) != len(labels):
        raise ValueError(
            f"Character row length for irrep '{irrep}' does not match class labels."
        )

    elements = self.elements()
    class_by_element = self._class_label_index_by_element()
    reps = [element.euclidean_repr(order) for element in elements]
    projector = sy.zeros(reps[0].rows, reps[0].cols)
    for element_index, rep in enumerate(reps):
        class_index = class_by_element[element_index]
        projector += sy.conjugate(characters[class_index]) * rep

    dim = sy.Integer(row["dim"])
    return sy.ImmutableDenseMatrix(sy.simplify((dim / len(elements)) * projector))

irrep_basis cached

irrep_basis(
    order: int, irrep: str
) -> tuple[PointGroupBasis, ...]

Return polynomial basis labels spanning a finite point-group irrep sector.

Parameters:

Name Type Description Default
order int

Homogeneous polynomial degree for the Euclidean representation.

required
irrep str

Irrep label from the packaged or computed character table.

required

Returns:

Type Description
tuple[PointGroupBasis, ...]

Normalized PointGroupBasis labels spanning the image of the corresponding irrep projector.

Raises:

Type Description
ValueError

If the irrep is unknown or character-table data is incomplete.

Source code in src/qten/pointgroups/finite.py
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
@lru_cache
def irrep_basis(self, order: int, irrep: str) -> tuple[PointGroupBasis, ...]:
    """
    Return polynomial basis labels spanning a finite point-group irrep sector.

    Parameters
    ----------
    order : int
        Homogeneous polynomial degree for the Euclidean representation.
    irrep : str
        Irrep label from the packaged or computed character table.

    Returns
    -------
    tuple[PointGroupBasis, ...]
        Normalized
        [`PointGroupBasis`][qten.pointgroups.basis.PointGroupBasis]
        labels spanning the image of the corresponding irrep projector.

    Raises
    ------
    ValueError
        If the irrep is unknown or character-table data is incomplete.
    """

    table = self.ordinary_table()
    if irrep not in table["irreps"]:
        raise ValueError(f"Unknown irrep '{irrep}' for point group {self.symbol}.")
    irrep_data = table["irreps"][irrep]
    irrep_dim = int(irrep_data["dim"])
    projector = self.irrep_projector(order, irrep)
    euclidean_basis = self.generators[0].euclidean_basis(order)
    labels: list[PointGroupBasis] = []
    seen: set[tuple[sy.Expr, ...]] = set()
    for vec in projector.columnspace():
        rep = sy.ImmutableDenseMatrix(vec)
        if all(entry == 0 for entry in rep):
            continue
        basis = PointGroupBasis.from_rep(
            rep=rep,
            euclidean_basis=euclidean_basis,
            axes=self.axes,
            order=order,
            group=self.symbol or "<anonymous>",
            irrep=irrep,
            irrep_dim=irrep_dim,
            copy_index=len(labels) // max(irrep_dim, 1),
            component_index=len(labels) % max(irrep_dim, 1),
        )
        key = tuple(basis.rep)
        if key in seen:
            continue
        seen.add(key)
        labels.append(basis)
    return tuple(labels)

invariant_basis cached

invariant_basis(order: int) -> tuple[PointGroupBasis, ...]

Return invariant polynomial basis functions of the requested degree.

Parameters:

Name Type Description Default
order int

Homogeneous polynomial degree for the Euclidean representation.

required

Returns:

Type Description
tuple[PointGroupBasis, ...]

Normalized invariant PointGroupBasis labels spanning the image of the trivial projector.

Source code in src/qten/pointgroups/finite.py
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
@lru_cache
def invariant_basis(self, order: int) -> tuple[PointGroupBasis, ...]:
    """
    Return invariant polynomial basis functions of the requested degree.

    Parameters
    ----------
    order : int
        Homogeneous polynomial degree for the Euclidean representation.

    Returns
    -------
    tuple[PointGroupBasis, ...]
        Normalized invariant
        [`PointGroupBasis`][qten.pointgroups.basis.PointGroupBasis]
        labels spanning the image of the trivial projector.
    """

    projector = self.trivial_projector(order)
    euclidean_basis = self.generators[0].euclidean_basis(order)
    labels: list[PointGroupBasis] = []
    seen: set[tuple[sy.Expr, ...]] = set()
    for vec in projector.columnspace():
        rep = sy.ImmutableDenseMatrix(vec)
        if all(entry == 0 for entry in rep):
            continue
        basis = PointGroupBasis.from_rep(
            rep=rep,
            euclidean_basis=euclidean_basis,
            axes=self.axes,
            order=order,
        )
        key = tuple(basis.rep)
        if key in seen:
            continue
        seen.add(key)
        labels.append(basis)
    return tuple(labels)