Skip to content

qten.pointgroups

Package reference for qten.pointgroups.

pointgroups

Point-group symmetry helpers.

Two rules decide spin and geometry:

  1. Spin follows the Hilbert space. If a basis already carries Spin, projection uses the \(SU(2)\) lift. If it does not, the group is ordinary. There is no .with_spin.
  2. Geometry is written in the constructor. plane= / axis= / spin= belong on that one pointgroup(...) call. Move the origin later with fixpoint= on the single-group helpers, or fixpoint_at on a PointGroupOpr. Joint projection has no fixpoint=; center each operator first.

pointgroup chooses the object from the query, not from whether the group is abelian.

How to construct

Named crystallographic symbols return a FinitePointGroup with ordinary Bilbao characters and spinor characters from QTen's lift. Affine queries such as c4-xy:xy return one PointGroupElement.

from qten.pointgroups import pointgroup

td = pointgroup("Td")                   # tetrahedral; rotation3 is the spatial matrix
c4v = pointgroup("C4v", plane="xy")     # 2x2 spatial, 3D C4v kept for spin
c3v = pointgroup("C3v", axis=(1, 1, 1)) # still 3D; C3 about [111]
mirror = pointgroup("Cs", plane="x")    # 1D spatial [[-1]], rotation3 = σ_yz
c4 = pointgroup("C4", plane="xy")       # fourfold in xy
flavor = pointgroup("C4v", spin="trivial")  # u(g)=I; define-time only

C4v-xy is the same as plane="xy". A 2D or 1D custom matrix group with no rotation3 cannot lift spin: rewrite the constructor with plane= / axis=, do not pad the small matrix later.

How to use with spin

Put Spin on the basis, then symmetrize. The group is not told twice.

from qten.phys import Spin
from qten.pointgroups import (
    PointGroupOpr,
    hilbert_repr,
    point_group_column_symmetrize,
    point_group_operator_symmetrize,
)
from qten.symbolics import U1Basis

# diamond, C_R, seed, center, and space come from the surrounding model
A_up = U1Basis.new(diamond.at("A"), Spin.up)
td = pointgroup("-43m")
C_sym = point_group_operator_symmetrize(td, C_R, fixpoint=center)
w = point_group_column_symmetrize(td, seed, fixpoint=center)
D = hilbert_repr(PointGroupOpr(td.elements()[0]).fixpoint_at(center), space)
Mathematics

On a spinless space, \(D(g)=D_{\mathrm{orb}}(g)\). On a spinful space [ D(g)=D_{\mathrm{orb}}(g)\otimes u(g), ] where \(u(g)\in SU(2)\) is the principal lift of \(R_+(g)=(\det R(g))\,R(g)\). The section is a 2-cocycle, \(u(g)u(h)=\omega(g,h)\,u(gh)\) with \(\omega(g,h)\in\{\pm 1\}\).

Finite groups use the character projector [ P^\mu=\frac{d_\mu}{|G|}\sum_{g\in G}\chi^\mu(g)^*D(g). ] Ordinary (linear) \(\chi\) if the space has no Spin; projective spinor \(\chi\) if it does, unless the group was built with spin="trivial". Class-wise spinor rows vanish on non-\(\omega\)-regular classes; the projector uses the element-wise hat-table section, not those averages.

A cyclic PointGroupOpr of spatial order \(n\) is the abelian case of the same formula, [ P_\zeta=\frac{1}{N}\sum_{k=0}^{N-1}\zeta^{-k}D(g)^k,\qquad\zeta^N=1, ] with \(N=n\) spinless and \(N=2n\) spinful (\(u(2\pi)=-I\)). Operator twirling needs no \(\chi\): \(A_G=|G|^{-1}\sum_g D(g)AD(g)^\dagger\). The sign of each lift cancels between \(D(g)\) and \(D(g)^\dagger\).

Joint spinful projection of several operators needs group= the same already-defined \(G\).

Dispatch
  • Affine queries (c4-xy:xy, m-xyz:yz) → one element, phase-sector projection after wrapping in PointGroupOpr.
  • Named symbols (4mm, C4v, -43m) → finite group, packaged ordinary table plus QTen spinor table.
  • plane="xy" or suffix -xy shrinks spatial matrices only. A group that is not faithful on that plane (for example 4/m) raises.
  • axis=(1,1,1) reorients a 3D group; ordinary class labels follow conjugation. plane=(1,1,1) is a 2D cut of that reoriented group, still with the 3D rotation3.
Core exports

pointgroup parses a compact query or named symbol. PointGroupElement is one linear operation (irrep on the model, rotation3 in \(O(3)\)). PointGroupOpr adds a translation and fixpoint_at. FinitePointGroup is the closure of generators, with ordinary / spinor class tables. PointGroupBasis labels polynomial sectors. FiniteIrrepSector / SpinorIrrepSector / SpinfulPhaseSector / JointSpinfulPhaseSector label projected columns. SymmetryDegeneracy tags repeated copies of the same sector. hilbert_repr assembles \(D(g)\). spinful_hilbert_opr_repr / spinful_transform_basis are the spinful fast path used by that assembler. point_group_column_symmetrize projects columns. point_group_operator_symmetrize twirls an operator (no \(\chi\) needed). joint_point_group_column_symmetrize and joint_point_group_basis handle a commuting family.

Exported API

pointgroup

pointgroup(
    query: str,
    *,
    plane: str | tuple[float, ...] | None = None,
    axis: tuple[float, ...] | None = None,
    spin: Literal["trivial", "electron"] = "electron",
) -> PointGroupElement | FinitePointGroup

Build a point-group object from a compact query string.

This is a user-facing constructor for common point operations and named crystallographic point groups in Cartesian axes (x, y, z). Compact affine queries such as c4-xy:xy return a PointGroupElement with no character table. Named crystallographic queries such as C4v or -43m return a FinitePointGroup with packaged ordinary Bilbao characters. Spinor characters come from QTen's \(SU(2)\) lift: packaged for catalog groups, computed live otherwise. Use plane= / axis= at construction to fix the spatial frame; spin="trivial" is a define-time exception that sets u(g)=I. Hilbert spaces that already contain Spin use the spinor table.

Query grammar

The accepted format is "<group>-<ambient>:<target>".

Group tokens

Use c{n} for a cyclic rotation of order n, such as c2, c3, or c6. Use m for a mirror reflection. Named crystallographic point groups can also be queried by Hermann-Mauguin symbol (4mm, mmm) or Schoenflies alias (C4v, D2h). Named groups use packaged generator data and may include a trailing axis suffix such as C4v-xy.

Axis tokens

<ambient> is an ordered ambient axis string using x, y, and z without repeats. It defines the space dimension and basis-axis order in the returned transform. <target> is an axis subset selecting where the group action lives.

Group semantics

Cyclic groups are interpreted as 2D rotation blocks with angle \(\theta = 2\pi/n\). For cyclic groups, <target> must have exactly two axes and defines the rotation plane. In 2D ambient spaces, the cyclic target plane must use the same two axes as the ambient space. Cyclic target order controls orientation: c3-xy:xy and c3-xy:yx act on the same plane with inverse orientation. In 3D cyclic rotations, the remaining axis is unchanged.

The active plane receives the block \(R(\theta) = \begin{pmatrix}\cos\theta & -\sin\theta \\ \sin\theta & \cos\theta\end{pmatrix}\), where \(\theta = 2\pi/n\).

In code, this block is inserted into the returned irrep matrix; target axis order chooses the sign of theta.

In 1D mirrors, <target> must match the ambient axis and the action is a sign flip. In 2D mirrors, <target> has one axis and denotes the fixed axis. In 3D mirrors, <target> has two axes and denotes the fixed plane.

Validation rules

ambient and target cannot contain repeated axis letters. target must be a subset of ambient. Invalid dimensional or group combinations raise ValueError.

Parameters:

Name Type Description Default
query str

Compact point-group query of the form "<group>-<ambient>:<target>", or a named Hermann-Mauguin / Schoenflies symbol.

required
plane str | tuple[float, ...] | None

Construction-time plane. A string such as "xy" reduces spatial matrices while keeping the 3D rotations for spin. A vector is the plane normal.

None
axis tuple[float, ...] | None

Reorient a 3D named group so its standard z-axis maps to this vector.

None
spin Literal['trivial', 'electron']

Define-time spin policy. "electron" lifts rotation3; "trivial" uses u(g)=I.

"electron"

Returns:

Type Description
PointGroupElement | FinitePointGroup

Compact affine queries return a single abelian operation. Named crystallographic queries return a finite point group generated by one or more exact matrix operations.

Raises:

Type Description
ValueError

If the query format, group token, axis token, or dimensional combination is unsupported.

Examples:

from qten.pointgroups import pointgroup

rotation = pointgroup("C6", plane="xy")         # sixfold in xy
inverse = pointgroup("C6", plane=(0, 0, -1))    # opposite orientation
mirror = pointgroup("Cs", plane="x")            # 1D spatial, σ_yz spin
td = pointgroup("Td")                      # tetrahedral
c4v = pointgroup("C4v", plane="xy")        # 2D spatial, 3D spin
c3v = pointgroup("C3v", axis=(1, 1, 1))    # C3 about [111]
flavor = pointgroup("C4v", spin="trivial") # u(g)=I
Source code in src/qten/pointgroups/_pointgroups.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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
287
288
def pointgroup(
    query: str,
    *,
    plane: str | tuple[float, ...] | None = None,
    axis: tuple[float, ...] | None = None,
    spin: Literal["trivial", "electron"] = "electron",
) -> PointGroupElement | FinitePointGroup:
    r"""
    Build a point-group object from a compact query string.

    This is a user-facing constructor for common point operations and named
    crystallographic point groups in Cartesian axes (`x`, `y`, `z`). Compact
    affine queries such as `c4-xy:xy` return a
    [`PointGroupElement`][qten.pointgroups.elements.PointGroupElement] with no
    character table. Named crystallographic queries such as `C4v` or `-43m`
    return a [`FinitePointGroup`][qten.pointgroups.finite.FinitePointGroup]
    with packaged ordinary Bilbao characters. Spinor characters come from
    QTen's \(SU(2)\) lift: packaged for catalog groups, computed live otherwise.
    Use `plane=` / `axis=` at construction to fix the spatial frame;
    `spin="trivial"` is a define-time exception that sets `u(g)=I`. Hilbert
    spaces that already contain `Spin` use the spinor table.

    Query grammar
    -------------
    The accepted format is `"<group>-<ambient>:<target>"`.

    Group tokens
    ------------
    Use `c{n}` for a cyclic rotation of order `n`, such as `c2`, `c3`, or
    `c6`. Use `m` for a mirror reflection. Named crystallographic point groups
    can also be queried by Hermann-Mauguin symbol (`4mm`, `mmm`) or Schoenflies
    alias (`C4v`, `D2h`). Named groups use packaged generator data and may
    include a trailing axis suffix such as `C4v-xy`.

    Axis tokens
    -----------
    `<ambient>` is an ordered ambient axis string using `x`, `y`, and `z`
    without repeats. It defines the space dimension and basis-axis order in the
    returned transform. `<target>` is an axis subset selecting where the group
    action lives.

    Group semantics
    ---------------
    Cyclic groups are interpreted as 2D rotation blocks with angle
    \(\theta = 2\pi/n\).
    For cyclic groups, `<target>` must have exactly two axes and defines the
    rotation plane. In 2D ambient spaces, the cyclic target plane must use the
    same two axes as the ambient space. Cyclic target order controls
    orientation: `c3-xy:xy` and `c3-xy:yx` act on the same plane with inverse
    orientation. In 3D cyclic rotations, the remaining axis is unchanged.

    The active plane receives the block
    \(R(\theta) = \begin{pmatrix}\cos\theta & -\sin\theta \\
    \sin\theta & \cos\theta\end{pmatrix}\), where \(\theta = 2\pi/n\).

    In code, this block is inserted into the returned `irrep` matrix; target
    axis order chooses the sign of `theta`.

    In 1D mirrors, `<target>` must match the ambient axis and the action is a
    sign flip. In 2D mirrors, `<target>` has one axis and denotes the fixed
    axis. In 3D mirrors, `<target>` has two axes and denotes the fixed plane.

    Validation rules
    ----------------
    `ambient` and `target` cannot contain repeated axis letters. `target` must
    be a subset of `ambient`. Invalid dimensional or group combinations raise
    `ValueError`.

    Parameters
    ----------
    query : str
        Compact point-group query of the form `"<group>-<ambient>:<target>"`,
        or a named Hermann-Mauguin / Schoenflies symbol.
    plane : str | tuple[float, ...] | None, optional
        Construction-time plane. A string such as `"xy"` reduces spatial
        matrices while keeping the 3D rotations for spin. A vector is the
        plane normal.
    axis : tuple[float, ...] | None, optional
        Reorient a 3D named group so its standard z-axis maps to this vector.
    spin : Literal["trivial", "electron"], default "electron"
        Define-time spin policy. `"electron"` lifts `rotation3`; `"trivial"`
        uses `u(g)=I`.

    Returns
    -------
    PointGroupElement | FinitePointGroup
        Compact affine queries return a single abelian operation. Named
        crystallographic queries return a finite point group generated by one
        or more exact matrix operations.

    Raises
    ------
    ValueError
        If the query format, group token, axis token, or dimensional
        combination is unsupported.

    Examples
    --------
    ```python
    from qten.pointgroups import pointgroup

    rotation = pointgroup("C6", plane="xy")         # sixfold in xy
    inverse = pointgroup("C6", plane=(0, 0, -1))    # opposite orientation
    mirror = pointgroup("Cs", plane="x")            # 1D spatial, σ_yz spin
    td = pointgroup("Td")                      # tetrahedral
    c4v = pointgroup("C4v", plane="xy")        # 2D spatial, 3D spin
    c3v = pointgroup("C3v", axis=(1, 1, 1))    # C3 about [111]
    flavor = pointgroup("C4v", spin="trivial") # u(g)=I
    ```
    """
    if not _is_affine_query(query):
        return named_pointgroup(
            query,
            plane=_hashable_axis(plane),
            axis=_hashable_axis(axis),
            spin=spin,
        )

    if plane is not None or axis is not None:
        raise ValueError(
            "plane= and axis= apply to named point groups, not affine queries."
        )

    group, ambient, target = _parse_affine_query(query)

    axes_symbols = {
        "x": sy.Symbol("x"),
        "y": sy.Symbol("y"),
        "z": sy.Symbol("z"),
    }
    axes = tuple(axes_symbols[c] for c in ambient)
    if group.startswith("c"):
        n = int(group[1:])
        irrep = _build_cyclic_irrep(n=n, ambient=ambient, target=target)
    elif group == "m":
        irrep = _build_mirror_irrep(ambient=ambient, target=target)
    else:
        raise ValueError(
            f"Unsupported group '{group}'. Supported groups are cyclic and mirror."
        )

    if len(ambient) < 3:
        rotation3 = _embed_in_cartesian_xyz(irrep, tuple(ambient))
    else:
        rotation3 = irrep
    return PointGroupElement(irrep=irrep, axes=axes, rotation3=rotation3, spin=spin)

PointGroupElement dataclass

PointGroupElement(
    irrep: ImmutableDenseMatrix,
    axes: tuple[Symbol, ...],
    rotation3: ImmutableDenseMatrix | None = None,
    spin: str = "electron",
)

Bases: Opr

Linear point operation represented on Cartesian coordinate functions.

PointGroupElement stores the linear part g of a symmetry/operator as an exact matrix irrep acting on the coordinate axes axes. It provides the order-dependent polynomial representations induced by that linear action and the corresponding eigen-basis functions (PointGroupBasis). A single element may belong to an abelian or a non-abelian group.

Mathematical meaning

Let the coordinate vector be \(x = (x_1, \ldots, x_d)^{\mathsf{T}}\). The matrix irrep defines a linear action \(x \mapsto Gx\), where \(G\) is the stored irrep matrix.

From this degree-1 action, the class constructs higher-order polynomial representations on homogeneous monomials of total degree order. For example:

For order = 0, the representation acts on constant functions and is always the trivial 1x1 representation [1]. For order = 1, the representation is the original Euclidean representation irrep. For order = 2, the representation acts on quadratic monomials such as x^2, xy, and y^2.

Because coordinate symbols commute, the raw tensor-product representation is symmetrized onto the commuting monomial basis. The resulting matrix is returned by euclidean_repr(order).

For a homogeneous monomial basis \(\phi_m(x)\), the derived representation acts by rewriting \(\phi_m(Gx)\) back in the commuting monomial basis.

Parameters:

Name Type Description Default
irrep ImmutableDenseMatrix

Exact linear representation matrix of the operator in the coordinate basis defined by axes.

required
axes Tuple[Symbol, ...]

Ordered coordinate symbols on which irrep acts.

required
rotation3 ImmutableDenseMatrix | None

The same physical operation as a Cartesian \(O(3)\) matrix, used to lift spin-1/2. Three-dimensional groups may leave this unset and use irrep. Lower-dimensional groups must set it at construction.

None
spin str

Construction-time spin policy. "electron" lifts rotation3; "trivial" uses \(u(g)=I\).

`"electron"`

Attributes:

Name Type Description
irrep ImmutableDenseMatrix

Exact linear representation matrix of the operator in the coordinate basis defined by axes.

axes Tuple[Symbol, ...]

Ordered coordinate symbols on which irrep acts.

rotation3 ImmutableDenseMatrix | None

Stored 3D rotation used for the \(SU(2)\) lift, or None when irrep is already that 3D matrix.

spin str

"electron" or "trivial", fixed at construction.

Main API

euclidean_repr(order) returns the symmetrized linear action on homogeneous commuting monomials of degree order. basis(order) returns eigen-basis functions of that representation as PointGroupBasis objects keyed by eigenvalue. basis_table collects representative eigen-basis functions across increasing polynomial orders until all characters of the finite represented element are found. group_order(max_order=128) returns the smallest positive integer n such that irrep**n = I.

Notes

PointGroupElement is the linear object. To obtain an affine operator of the form \(x \mapsto gx + t\), wrap it in PointGroupOpr. In that sense, PointGroupOpr is the affine extension of PointGroupElement.

PointGroupElement @ PointGroupElement composes linear maps in the same algebraic order as every other Opr: (a @ b) @ x == a(b(x)). When the two groups use different but compatible ordered axis tuples, composition first embeds both matrices into a common axis basis. The merged basis preserves the full left-axis order and appends only unseen right axes. Missing axes act by the identity, while shared axes are aligned by symbol and reordered as needed. Both operands must share the same spin policy, and either both store rotation3 or neither does.

The group_order() and basis_table utilities assume the represented element has finite order. They apply to a single finite-order operation, not only to abelian groups, and may fail or be incomplete for infinite-order linear maps.

irrep instance-attribute

irrep: ImmutableDenseMatrix

Exact linear representation matrix of the operator in the coordinate basis defined by axes. This is the degree-1 action from which higher polynomial representations are constructed.

axes instance-attribute

axes: tuple[Symbol, ...]

Ordered coordinate symbols on which irrep acts. Their order fixes the ambient coordinate basis for all derived polynomial representations.

rotation3 class-attribute instance-attribute

rotation3: ImmutableDenseMatrix | None = None

The same physical operation as a Cartesian \(O(3)\) matrix, used to lift spin-1/2. Three-dimensional groups may leave this unset and use irrep. Lower-dimensional groups must set it at construction; it is not inferred by padding irrep at lift time.

spin class-attribute instance-attribute

spin: str = 'electron'

Spin policy fixed at construction. "electron" uses the \(SU(2)\) lift of rotation3. "trivial" uses \(u(g)=I\).

basis_table cached property

basis_table: FrozenDict[Expr, PointGroupBasis]

Build a complete eigen-basis lookup table across polynomial orders.

The table is accumulated by increasing homogeneous order, starting from 0, until enough eigen-basis functions have been found to cover the full finite group order returned by group_order.

Returns:

Type Description
FrozenDict

Mapping from eigenvalue/character to a representative PointGroupBasis.

Raises:

Type Description
ValueError

If no complete table is found up to order group_order() - 1.

register classmethod

register(obj_type: type)

Register a function defining the action of the Functional on a specific object type.

This method returns a decorator. The decorated function should accept the functional instance as its first argument and an object of obj_type as its second argument. Any keyword arguments passed to invoke() are forwarded to the decorated function.

Dispatch is resolved at call time via MRO, so only the exact (obj_type, cls) key is stored here. Resolution later searches both:

  • the MRO of the runtime object type,
  • the MRO of the runtime functional type.

This means registrations on a functional superclass are inherited by subclass functionals unless a more specific registration overrides them.

Parameters:

Name Type Description Default
obj_type type

The type of object the function applies to.

required

Returns:

Type Description
Callable

A decorator that registers the function for the specified object type.

Examples:

@MyFunctional.register(MyObject)
def _(functional: MyFunctional, obj: MyObject) -> MyObject:
    ...
Source code in src/qten/abstracts.py
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
@classmethod
def register(cls, obj_type: type):
    """
    Register a function defining the action of the [`Functional`][qten.abstracts.Functional] on a specific object type.

    This method returns a decorator. The decorated function should accept
    the functional instance as its first argument and an object of
    `obj_type` as its second argument. Any keyword arguments passed to
    [`invoke()`][qten.abstracts.Functional.invoke] are forwarded to the
    decorated function.

    Dispatch is resolved at call time via MRO, so only the exact
    `(obj_type, cls)` key is stored here. Resolution later searches both:

    - the MRO of the runtime object type,
    - the MRO of the runtime functional type.

    This means registrations on a functional superclass are inherited by
    subclass functionals unless a more specific registration overrides them.

    Parameters
    ----------
    obj_type : type
        The type of object the function applies to.

    Returns
    -------
    Callable
        A decorator that registers the function for the specified object type.

    Examples
    --------
    ```python
    @MyFunctional.register(MyObject)
    def _(functional: MyFunctional, obj: MyObject) -> MyObject:
        ...
    ```
    """

    def decorator(func: Callable):
        cls._registered_methods[(obj_type, cls)] = func
        cls._invalidate_resolved_methods(obj_type)
        return func

    return decorator

get_applicable_types staticmethod

get_applicable_types() -> tuple[type, ...]

Get all object types that can be applied by this Functional.

Parameters:

Name Type Description Default
cls Type[Functional]

Functional class whose direct registrations should be inspected.

required

Returns:

Type Description
Tuple[Type, ...]

A tuple of all registered object types that this Functional can handle.

Source code in src/qten/abstracts.py
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
@staticmethod
def get_applicable_types(cls) -> Tuple[Type, ...]:
    """
    Get all object types that can be applied by this [`Functional`][qten.abstracts.Functional].

    Parameters
    ----------
    cls : Type[Functional]
        Functional class whose direct registrations should be inspected.

    Returns
    -------
    Tuple[Type, ...]
        A tuple of all registered object types that this [`Functional`][qten.abstracts.Functional] can handle.
    """
    types = set()
    for obj_type, functional_type in cls._registered_methods.keys():
        if functional_type is cls:
            types.add(obj_type)
    return tuple(types)

allows

allows(obj: Any) -> bool

Check if this Functional can be applied on the given object.

Parameters:

Name Type Description Default
obj Any

The object to check for applicability.

required

Returns:

Type Description
bool

True if this Functional can be applied on the object, False otherwise.

Notes

Applicability is checked using the same inherited dispatch rules as invoke(): both the object's MRO and the functional-class MRO are searched.

Source code in src/qten/abstracts.py
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
def allows(self, obj: Any) -> bool:
    """
    Check if this [`Functional`][qten.abstracts.Functional] can be applied on the given object.

    Parameters
    ----------
    obj : Any
        The object to check for applicability.

    Returns
    -------
    bool
        True if this [`Functional`][qten.abstracts.Functional] can be applied on the object, False otherwise.

    Notes
    -----
    Applicability is checked using the same inherited dispatch rules as
    [`invoke()`][qten.abstracts.Functional.invoke]: both the object's MRO
    and the functional-class MRO are searched.
    """
    return self._resolve_method(type(obj), type(self)) is not None

invoke

invoke(v: _T, **kwargs: Any) -> _T | Multiple[_T]

Apply the operator while preserving QTen's symbolic output invariants.

Parameters:

Name Type Description Default
v _T

Input object to transform.

required
**kwargs Any

Extra keyword arguments forwarded to the resolved registration.

{}

Returns:

Type Description
_T | Multiple[_T]

Transformed object, or a factored result carrying an explicit scalar coefficient.

Raises:

Type Description
AssertionError

If a registered implementation returns a value outside the expected same-type / Multiple[same-type] contract.

Source code in src/qten/symbolics/hilbert_space.py
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
@override
def invoke(  # type: ignore[override]
    self, v: _T, **kwargs
) -> Union[_T, Multiple[_T]]:
    """
    Apply the operator while preserving QTen's symbolic output invariants.

    Parameters
    ----------
    v : _T
        Input object to transform.
    **kwargs : Any
        Extra keyword arguments forwarded to the resolved registration.

    Returns
    -------
    _T | Multiple[_T]
        Transformed object, or a factored result carrying an explicit
        scalar coefficient.

    Raises
    ------
    AssertionError
        If a registered implementation returns a value outside the expected
        same-type / `Multiple[same-type]` contract.
    """
    if type(v) is Multiple:
        result = super().invoke(v.base, **kwargs)
        if type(result) is Multiple:
            return Multiple((v.coef * result.coef).simplify(), result.base)
        return Multiple(v.coef, result)
    result = super().invoke(v, **kwargs)
    if isinstance(v, (U1Basis, U1Span, HilbertSpace)):
        assert type(result) is not Multiple, (
            f"Operator {type(self)} acting on {type(v).__name__} should not yield a Multiple!"
        )
    assert isinstance(result, type(v)) or (
        type(result) is Multiple and isinstance(result.base, type(v))
    ), (
        f"Operator {type(self)} acting on {type(v).__name__} should yield same typed object"
        f"or Multiple[{type(v).__name__}]"
    )
    return result

__call__

__call__(obj: Any, **kwargs) -> Any

Apply this functional to obj.

This is a thin wrapper around invoke().

Parameters:

Name Type Description Default
obj Any

Runtime object to dispatch on.

required
**kwargs Any

Additional keyword arguments forwarded to the resolved implementation.

{}

Returns:

Type Description
Any

Result produced by the resolved registered method.

Raises:

Type Description
NotImplementedError

If no registration exists for the runtime pair after MRO fallback.

See Also

invoke(obj, **kwargs) Full dispatch method used by this call wrapper.

Source code in src/qten/abstracts.py
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
def __call__(self, obj: Any, **kwargs) -> Any:
    """
    Apply this functional to `obj`.

    This is a thin wrapper around [`invoke()`][qten.abstracts.Functional.invoke].

    Parameters
    ----------
    obj : Any
        Runtime object to dispatch on.
    **kwargs : Any
        Additional keyword arguments forwarded to the resolved
        implementation.

    Returns
    -------
    Any
        Result produced by the resolved registered method.

    Raises
    ------
    NotImplementedError
        If no registration exists for the runtime pair after MRO fallback.

    See Also
    --------
    [`invoke(obj, **kwargs)`][qten.abstracts.Functional.invoke]
        Full dispatch method used by this call wrapper.
    """
    return self.invoke(obj, **kwargs)

with_irrep

with_irrep(
    irrep: ImmutableDenseMatrix,
    axes: tuple[Symbol, ...] | None = None,
    *,
    rotation3: ImmutableDenseMatrix | None | object = ...,
) -> PointGroupElement

Return a copy with a new spatial matrix, keeping spin metadata.

Source code in src/qten/pointgroups/elements.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def with_irrep(
    self,
    irrep: sy.ImmutableDenseMatrix,
    axes: Tuple[sy.Symbol, ...] | None = None,
    *,
    rotation3: sy.ImmutableDenseMatrix | None | object = ...,
) -> "PointGroupElement":
    """Return a copy with a new spatial matrix, keeping spin metadata."""
    return PointGroupElement(
        irrep=irrep,
        axes=self.axes if axes is None else axes,
        rotation3=self.rotation3 if rotation3 is ... else rotation3,
        spin=self.spin,
    )

euclidean_basis cached

euclidean_basis(order: int) -> sy.ImmutableDenseMatrix

Return commuting Euclidean monomials spanning the polynomial basis.

Parameters:

Name Type Description Default
order int

Homogeneous polynomial degree. order=0 returns the constant monomial basis.

required

Returns:

Type Description
ImmutableDenseMatrix

Row matrix whose entries are monomials formed from canonical commuting indices of degree order.

Source code in src/qten/pointgroups/elements.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
@lru_cache
def euclidean_basis(self, order: int) -> sy.ImmutableDenseMatrix:
    """
    Return commuting Euclidean monomials spanning the polynomial basis.

    Parameters
    ----------
    order : int
        Homogeneous polynomial degree. `order=0` returns the constant
        monomial basis.

    Returns
    -------
    sy.ImmutableDenseMatrix
        Row matrix whose entries are monomials formed from canonical
        commuting indices of degree `order`.
    """
    indices = self._commute_indices(order)
    return sy.ImmutableDenseMatrix([sy.prod(idx) for idx in indices]).T

euclidean_repr cached

euclidean_repr(order: int) -> sy.ImmutableDenseMatrix

Symmetrized representation on the commuting polynomial basis.

Parameters:

Name Type Description Default
order int

Homogeneous polynomial degree for the induced representation. order=0 returns the trivial one-dimensional representation.

required

Returns:

Type Description
ImmutableDenseMatrix

Matrix representation after contracting permutation-equivalent tensor-product monomials and selecting canonical representatives.

Source code in src/qten/pointgroups/elements.py
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
@lru_cache
def euclidean_repr(self, order: int) -> sy.ImmutableDenseMatrix:
    """
    Symmetrized representation on the commuting polynomial basis.

    Parameters
    ----------
    order : int
        Homogeneous polynomial degree for the induced representation.
        `order=0` returns the trivial one-dimensional representation.

    Returns
    -------
    sy.ImmutableDenseMatrix
        Matrix representation after contracting permutation-equivalent
        tensor-product monomials and selecting canonical representatives.
    """
    indices = self._full_indices(order)
    contract_indices, select_indices = self._get_contract_select_rules(indices)

    contract_matrix = sy.zeros(len(indices), len(select_indices))
    for i, j in contract_indices:
        contract_matrix[i, j] = 1

    select_matrix = sy.zeros(len(indices), len(select_indices))
    for i, j in select_indices:
        select_matrix[i, j] = 1

    return select_matrix.T @ self._raw_euclidean_repr(order) @ contract_matrix

group_order cached

group_order(max_order: int = 128) -> int

Return the order of this represented group element.

The order is the smallest positive integer n such that \(G^n = I\), where \(G\) is irrep and \(I\) is the identity matrix of matching size.

Parameters:

Name Type Description Default
max_order int

Maximum positive exponent to test during the exact search.

128

Returns:

Type Description
int

The smallest positive exponent for which the represented matrix returns to the identity.

Raises:

Type Description
ValueError

If no finite order is found within the bounded exact search.

Notes

This computes the order of the matrix image under the representation. For a faithful representation, this equals the abstract group-element order; otherwise it may be smaller.

Source code in src/qten/pointgroups/elements.py
353
354
355
356
357
358
359
360
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
389
390
391
392
@lru_cache
def group_order(self, max_order: int = 128) -> int:
    r"""
    Return the order of this represented group element.

    The order is the smallest positive integer `n` such that \(G^n = I\),
    where \(G\) is `irrep` and \(I\) is the identity matrix of matching size.

    Parameters
    ----------
    max_order : int, default 128
        Maximum positive exponent to test during the exact search.

    Returns
    -------
    int
        The smallest positive exponent for which the represented matrix
        returns to the identity.

    Raises
    ------
    ValueError
        If no finite order is found within the bounded exact search.

    Notes
    -----
    This computes the order of the matrix image under the representation.
    For a faithful representation, this equals the abstract group-element
    order; otherwise it may be smaller.
    """
    ident = sy.ImmutableDenseMatrix.eye(self.irrep.rows)
    power = ident
    for n in range(1, max_order + 1):
        power = sy.ImmutableDenseMatrix(sy.simplify(power @ self.irrep))
        if power.equals(ident):
            return n
    raise ValueError(
        f"Failed to determine a finite group order within max_order={max_order} "
        f"for irrep={self.irrep!r}."
    )

inv cached

inv() -> PointGroupElement

Return the inverse linear operator in the same ordered axis basis.

The inverse is computed exactly from irrep.inv() and keeps the same axes, so self @ self.inv() and self.inv() @ self both represent the identity map on that coordinate system.

Source code in src/qten/pointgroups/elements.py
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
@lru_cache
def inv(self) -> "PointGroupElement":
    """
    Return the inverse linear operator in the same ordered axis basis.

    The inverse is computed exactly from `irrep.inv()` and keeps the same
    `axes`, so `self @ self.inv()` and `self.inv() @ self` both represent
    the identity map on that coordinate system.
    """
    rotation3 = None
    if self.rotation3 is not None:
        rotation3 = sy.ImmutableDenseMatrix(sy.simplify(self.rotation3.inv()))
    return self.with_irrep(
        sy.ImmutableDenseMatrix(sy.simplify(self.irrep.inv())),
        rotation3=rotation3,
    )

basis cached

basis(order: int) -> FrozenDict[sy.Expr, PointGroupBasis]

Compute abelian eigen-basis functions from euclidean_repr(order) eigenvectors.

Parameters:

Name Type Description Default
order int

Homogeneous polynomial degree used to build the Euclidean representation before diagonalization.

required

Returns:

Type Description
FrozenDict

Mapping from eigenvalue to normalized PointGroupBasis eigenfunction. Normalization is fixed by dividing by the first non-zero coefficient in each eigenvector.

Source code in src/qten/pointgroups/elements.py
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
@lru_cache
def basis(self, order: int) -> FrozenDict:
    """
    Compute abelian eigen-basis functions from [`euclidean_repr(order)`][qten.pointgroups.elements.PointGroupElement.euclidean_repr] eigenvectors.

    Parameters
    ----------
    order : int
        Homogeneous polynomial degree used to build the Euclidean
        representation before diagonalization.

    Returns
    -------
    FrozenDict
        Mapping from eigenvalue to normalized [`PointGroupBasis`][qten.pointgroups.basis.PointGroupBasis] eigenfunction.
        Normalization is fixed by dividing by the first non-zero coefficient
        in each eigenvector.
    """
    transform = self.euclidean_repr(order)
    eig = transform.eigenvects()

    tbl = {}
    for v, _, vec_group in eig:
        vec = vec_group[0]
        tbl[v] = PointGroupBasis.from_rep(
            rep=sy.ImmutableDenseMatrix(vec),
            euclidean_basis=self.euclidean_basis(order),
            axes=self.axes,
            order=order,
            irrep=sy.simplify(v),
        )

    return FrozenDict(tbl)

PointGroupOpr dataclass

PointGroupOpr(
    g: PointGroupElement, offset: Offset | None = None
)

Bases: Opr, HasBase[AffineSpace]

Affine point operation acting on polynomial coordinate functions.

This class combines a linear PointGroupElement with a translation: \(x \mapsto gx + t\), where \(t\) is stored on offset. The linear part may belong to an abelian or a non-abelian group.

Parameters:

Name Type Description Default
g PointGroupElement

Linear part of the affine transformation.

required
offset Offset | None

Not accepted as a constructor argument. Passing a value raises TypeError. The constructor always starts at the origin; call fixpoint_at afterwards. The offset attribute stores the resulting translation.

None

Attributes:

Name Type Description
g PointGroupElement

Linear part of the affine transformation.

offset Offset

Translation part of the affine transformation, stored in the same affine space on which g acts.

Notes

The operator is initialized at the canonical origin of the identity affine basis. To center it at a specific point, construct it first and then call fixpoint_at(...). Applying the operator to a Spin label or a spinful U1Basis raises: a generic \(SU(2)\) factor produces a superposition. Use expand_spin or hilbert_repr.

Source code in src/qten/pointgroups/elements.py
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
def __init__(
    self,
    g: PointGroupElement,
    offset: Offset | None = None,
):
    if offset is not None:
        raise TypeError(
            "PointGroupOpr does not accept offset=... directly. "
            "Construct PointGroupOpr(g) and use fixpoint_at(...) to set its center."
        )
    dim = g.irrep.rows
    base = AffineSpace(basis=sy.ImmutableDenseMatrix.eye(dim))
    offset = Offset(rep=sy.ImmutableDenseMatrix([0] * dim), space=base)
    object.__setattr__(self, "g", g)
    object.__setattr__(self, "offset", offset)

g instance-attribute

g: PointGroupElement

Linear part of the affine transformation, represented exactly on the ordered coordinate axes of the operator's ambient affine space.

offset instance-attribute

offset: Offset

Translation part of the affine transformation, stored in the same affine space on which g acts so the full map has the form \(x \mapsto gx + \mathrm{offset}\).

register classmethod

register(obj_type: type)

Register a function defining the action of the Functional on a specific object type.

This method returns a decorator. The decorated function should accept the functional instance as its first argument and an object of obj_type as its second argument. Any keyword arguments passed to invoke() are forwarded to the decorated function.

Dispatch is resolved at call time via MRO, so only the exact (obj_type, cls) key is stored here. Resolution later searches both:

  • the MRO of the runtime object type,
  • the MRO of the runtime functional type.

This means registrations on a functional superclass are inherited by subclass functionals unless a more specific registration overrides them.

Parameters:

Name Type Description Default
obj_type type

The type of object the function applies to.

required

Returns:

Type Description
Callable

A decorator that registers the function for the specified object type.

Examples:

@MyFunctional.register(MyObject)
def _(functional: MyFunctional, obj: MyObject) -> MyObject:
    ...
Source code in src/qten/abstracts.py
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
@classmethod
def register(cls, obj_type: type):
    """
    Register a function defining the action of the [`Functional`][qten.abstracts.Functional] on a specific object type.

    This method returns a decorator. The decorated function should accept
    the functional instance as its first argument and an object of
    `obj_type` as its second argument. Any keyword arguments passed to
    [`invoke()`][qten.abstracts.Functional.invoke] are forwarded to the
    decorated function.

    Dispatch is resolved at call time via MRO, so only the exact
    `(obj_type, cls)` key is stored here. Resolution later searches both:

    - the MRO of the runtime object type,
    - the MRO of the runtime functional type.

    This means registrations on a functional superclass are inherited by
    subclass functionals unless a more specific registration overrides them.

    Parameters
    ----------
    obj_type : type
        The type of object the function applies to.

    Returns
    -------
    Callable
        A decorator that registers the function for the specified object type.

    Examples
    --------
    ```python
    @MyFunctional.register(MyObject)
    def _(functional: MyFunctional, obj: MyObject) -> MyObject:
        ...
    ```
    """

    def decorator(func: Callable):
        cls._registered_methods[(obj_type, cls)] = func
        cls._invalidate_resolved_methods(obj_type)
        return func

    return decorator

get_applicable_types staticmethod

get_applicable_types() -> tuple[type, ...]

Get all object types that can be applied by this Functional.

Parameters:

Name Type Description Default
cls Type[Functional]

Functional class whose direct registrations should be inspected.

required

Returns:

Type Description
Tuple[Type, ...]

A tuple of all registered object types that this Functional can handle.

Source code in src/qten/abstracts.py
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
@staticmethod
def get_applicable_types(cls) -> Tuple[Type, ...]:
    """
    Get all object types that can be applied by this [`Functional`][qten.abstracts.Functional].

    Parameters
    ----------
    cls : Type[Functional]
        Functional class whose direct registrations should be inspected.

    Returns
    -------
    Tuple[Type, ...]
        A tuple of all registered object types that this [`Functional`][qten.abstracts.Functional] can handle.
    """
    types = set()
    for obj_type, functional_type in cls._registered_methods.keys():
        if functional_type is cls:
            types.add(obj_type)
    return tuple(types)

allows

allows(obj: Any) -> bool

Check if this Functional can be applied on the given object.

Parameters:

Name Type Description Default
obj Any

The object to check for applicability.

required

Returns:

Type Description
bool

True if this Functional can be applied on the object, False otherwise.

Notes

Applicability is checked using the same inherited dispatch rules as invoke(): both the object's MRO and the functional-class MRO are searched.

Source code in src/qten/abstracts.py
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
def allows(self, obj: Any) -> bool:
    """
    Check if this [`Functional`][qten.abstracts.Functional] can be applied on the given object.

    Parameters
    ----------
    obj : Any
        The object to check for applicability.

    Returns
    -------
    bool
        True if this [`Functional`][qten.abstracts.Functional] can be applied on the object, False otherwise.

    Notes
    -----
    Applicability is checked using the same inherited dispatch rules as
    [`invoke()`][qten.abstracts.Functional.invoke]: both the object's MRO
    and the functional-class MRO are searched.
    """
    return self._resolve_method(type(obj), type(self)) is not None

invoke

invoke(v: _T, **kwargs: Any) -> _T | Multiple[_T]

Apply the operator while preserving QTen's symbolic output invariants.

Parameters:

Name Type Description Default
v _T

Input object to transform.

required
**kwargs Any

Extra keyword arguments forwarded to the resolved registration.

{}

Returns:

Type Description
_T | Multiple[_T]

Transformed object, or a factored result carrying an explicit scalar coefficient.

Raises:

Type Description
AssertionError

If a registered implementation returns a value outside the expected same-type / Multiple[same-type] contract.

Source code in src/qten/symbolics/hilbert_space.py
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
@override
def invoke(  # type: ignore[override]
    self, v: _T, **kwargs
) -> Union[_T, Multiple[_T]]:
    """
    Apply the operator while preserving QTen's symbolic output invariants.

    Parameters
    ----------
    v : _T
        Input object to transform.
    **kwargs : Any
        Extra keyword arguments forwarded to the resolved registration.

    Returns
    -------
    _T | Multiple[_T]
        Transformed object, or a factored result carrying an explicit
        scalar coefficient.

    Raises
    ------
    AssertionError
        If a registered implementation returns a value outside the expected
        same-type / `Multiple[same-type]` contract.
    """
    if type(v) is Multiple:
        result = super().invoke(v.base, **kwargs)
        if type(result) is Multiple:
            return Multiple((v.coef * result.coef).simplify(), result.base)
        return Multiple(v.coef, result)
    result = super().invoke(v, **kwargs)
    if isinstance(v, (U1Basis, U1Span, HilbertSpace)):
        assert type(result) is not Multiple, (
            f"Operator {type(self)} acting on {type(v).__name__} should not yield a Multiple!"
        )
    assert isinstance(result, type(v)) or (
        type(result) is Multiple and isinstance(result.base, type(v))
    ), (
        f"Operator {type(self)} acting on {type(v).__name__} should yield same typed object"
        f"or Multiple[{type(v).__name__}]"
    )
    return result

__call__

__call__(obj: Any, **kwargs) -> Any

Apply this functional to obj.

This is a thin wrapper around invoke().

Parameters:

Name Type Description Default
obj Any

Runtime object to dispatch on.

required
**kwargs Any

Additional keyword arguments forwarded to the resolved implementation.

{}

Returns:

Type Description
Any

Result produced by the resolved registered method.

Raises:

Type Description
NotImplementedError

If no registration exists for the runtime pair after MRO fallback.

See Also

invoke(obj, **kwargs) Full dispatch method used by this call wrapper.

Source code in src/qten/abstracts.py
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
def __call__(self, obj: Any, **kwargs) -> Any:
    """
    Apply this functional to `obj`.

    This is a thin wrapper around [`invoke()`][qten.abstracts.Functional.invoke].

    Parameters
    ----------
    obj : Any
        Runtime object to dispatch on.
    **kwargs : Any
        Additional keyword arguments forwarded to the resolved
        implementation.

    Returns
    -------
    Any
        Result produced by the resolved registered method.

    Raises
    ------
    NotImplementedError
        If no registration exists for the runtime pair after MRO fallback.

    See Also
    --------
    [`invoke(obj, **kwargs)`][qten.abstracts.Functional.invoke]
        Full dispatch method used by this call wrapper.
    """
    return self.invoke(obj, **kwargs)

base

base() -> AffineSpace

Get the affine space where this element acts.

Returns:

Type Description
AffineSpace

Acting space, identical to offset.space.

Source code in src/qten/pointgroups/elements.py
665
666
667
668
669
670
671
672
673
674
def base(self) -> AffineSpace:
    """
    Get the affine space where this element acts.

    Returns
    -------
    AffineSpace
        Acting space, identical to `offset.space`.
    """
    return self.offset.space

rebase cached

rebase(new_base: AffineSpace) -> PointGroupOpr

Re-express this transform in a different affine space basis.

Parameters:

Name Type Description Default
new_base AffineSpace

Target affine space for the transformed representation.

required

Returns:

Type Description
PointGroupOpr

New element with both linear and translation parts expressed in new_base coordinates.

Source code in src/qten/pointgroups/elements.py
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
@lru_cache(maxsize=None)
def rebase(self, new_base: AffineSpace) -> "PointGroupOpr":
    """
    Re-express this transform in a different affine space basis.

    Parameters
    ----------
    new_base : AffineSpace
        Target affine space for the transformed representation.

    Returns
    -------
    PointGroupOpr
        New element with both linear and translation parts expressed in
        new_base coordinates.
    """
    old_base = self.offset.space
    B_old = old_base.basis
    if not isinstance(B_old, sy.ImmutableDenseMatrix):
        B_old = sy.ImmutableDenseMatrix(B_old)
    B_new = new_base.basis
    if not isinstance(B_new, sy.ImmutableDenseMatrix):
        B_new = sy.ImmutableDenseMatrix(B_new)

    irrep = self.g.irrep
    if not isinstance(irrep, sy.ImmutableDenseMatrix):
        irrep = sy.ImmutableDenseMatrix(irrep)

    change = B_new.inv() @ B_old
    new_irrep = change @ irrep @ change.inv()
    return PointGroupOpr._from_parts(
        g=self.g.with_irrep(sy.ImmutableDenseMatrix(new_irrep)),
        offset=self.offset.rebase(new_base),
    )

fixpoint_at

fixpoint_at(
    r: Offset, rebase: bool = False
) -> PointGroupOpr

Return a transform with the same linear part whose invariant fixed point is r.

For the affine action \(x \mapsto R x + t\), requiring \(r\) to be fixed means \(Rr + t = r\), so the translation must be \(t = (I - R)r\).

Parameters:

Name Type Description Default
r Offset

Desired fixed point.

required
rebase bool

Base-handling mode when r.space differs from this transform's base: if False, rebase r to this transform's base and keep the returned transform in its current base; if True, rebase the transform to r.space and return the result there.

`False`

Returns:

Type Description
PointGroupOpr

A new affine operator with the same linear part and with r as an invariant point.

Source code in src/qten/pointgroups/elements.py
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
def fixpoint_at(self, r: Offset, rebase: bool = False) -> "PointGroupOpr":
    r"""
    Return a transform with the same linear part whose invariant fixed point is `r`.

    For the affine action \(x \mapsto R x + t\), requiring \(r\) to be
    fixed means \(Rr + t = r\), so the translation must be
    \(t = (I - R)r\).

    Parameters
    ----------
    r : Offset
        Desired fixed point.
    rebase : bool, default `False`
        Base-handling mode when `r.space` differs from this transform's base:
        if `False`, rebase `r` to this transform's base and keep the
        returned transform in its current base; if `True`, rebase the
        transform to `r.space` and return the result there.

    Returns
    -------
    PointGroupOpr
        A new affine operator with the same linear part and with `r` as an
        invariant point.
    """
    t = self.rebase(r.space) if rebase and r.space != self.offset.space else self
    r_target = r if t.offset.space == r.space else r.rebase(t.offset.space)

    irrep = t.g.irrep
    if not isinstance(irrep, sy.ImmutableDenseMatrix):
        irrep = sy.ImmutableDenseMatrix(irrep)

    r_rep = r_target.rep
    if not isinstance(r_rep, sy.ImmutableDenseMatrix):
        r_rep = sy.ImmutableDenseMatrix(r_rep)

    ident = sy.eye(irrep.rows)
    if not isinstance(ident, sy.ImmutableDenseMatrix):
        ident = sy.ImmutableDenseMatrix(ident)

    fixed_offset = Offset(
        rep=sy.ImmutableDenseMatrix((ident - irrep) @ r_rep),
        space=t.offset.space,
    )
    return PointGroupOpr._from_parts(
        g=t.g.with_irrep(irrep),
        offset=fixed_offset,
    )

PointGroupBasis dataclass

PointGroupBasis(
    expr: Expr,
    axes: tuple[Symbol, ...],
    order: int,
    rep: ImmutableDenseMatrix,
    group: str = "generic",
    irrep: Expr | str | tuple[Expr, ...] = sy.Integer(1),
    irrep_dim: int = 1,
    copy_index: int = 0,
    component_index: int = 0,
)

Bases: Spatial

Polynomial basis label belonging to a point-group representation sector.

PointGroupBasis pairs an exact homogeneous polynomial expr with its coefficient vector rep in a fixed Euclidean monomial basis. Sector metadata distinguishes abelian phase labels from finite-group irrep labels.

Attributes:

Name Type Description
expr Expr

Exact polynomial expression reconstructed from rep.

axes tuple[Symbol, ...]

Ordered coordinate symbols used by the polynomial.

order int

Homogeneous degree of the polynomial.

rep ImmutableDenseMatrix

Coefficient vector in the Euclidean monomial basis of degree order, normalized so the first nonzero coefficient has unit magnitude.

group str

Group tag used in string labels. Defaults to "generic" for abelian eigen-bases and to a Hermann-Mauguin symbol for finite-group sectors.

irrep Expr | str | tuple[Expr, ...]

Sector label. Abelian eigen-bases store a phase eigenvalue; finite-group sectors store a character-table irrep name such as "A1" or "E".

irrep_dim int

Dimension of the irrep sector. Equals 1 for abelian phase labels.

copy_index int

Copy index when the same irrep appears with multiplicity.

component_index int

Component index within an irrep multiplet.

Notes

String rendering collapses to the bare polynomial when group == "generic" and the sector is the trivial abelian phase 1. Otherwise the label is formatted as group:irrep:expr.

expr instance-attribute

expr: Expr

axes instance-attribute

axes: tuple[Symbol, ...]

order instance-attribute

order: int

rep instance-attribute

rep: ImmutableDenseMatrix

group class-attribute instance-attribute

group: str = 'generic'

irrep class-attribute instance-attribute

irrep: Expr | str | tuple[Expr, ...] = sy.Integer(1)

irrep_dim class-attribute instance-attribute

irrep_dim: int = 1

copy_index class-attribute instance-attribute

copy_index: int = 0

component_index class-attribute instance-attribute

component_index: int = 0

dim property

dim: int

Number of coordinate axes for this basis label.

register_plot_method classmethod

register_plot_method(name: str, backend: str = 'plotly')

Register a backend plotting function for this plottable class.

The returned decorator stores the function in the global plotting registry. Registered functions receive the object being plotted as their first argument, followed by any extra positional and keyword arguments supplied to plot().

Parameters:

Name Type Description Default
name str

User-facing plot method name, such as scatter, structure, or heatmap.

required
backend str

Backend name that selects the implementation. The qten-plots extension currently uses plotly and matplotlib.

'plotly'

Returns:

Type Description
Callable

Decorator that registers the provided plotting function and returns it unchanged.

Source code in src/qten/plottings/_plottings.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@classmethod
def register_plot_method(cls, name: str, backend: str = "plotly"):
    """
    Register a backend plotting function for this plottable class.

    The returned decorator stores the function in the global plotting
    registry. Registered functions receive the object being plotted as their
    first argument, followed by any extra positional and keyword arguments
    supplied to [`plot()`][qten.plottings.Plottable.plot].

    Parameters
    ----------
    name : str
        User-facing plot method name, such as `scatter`, `structure`, or
        `heatmap`.
    backend : str
        Backend name that selects the implementation. The `qten-plots`
        extension currently uses `plotly` and `matplotlib`.

    Returns
    -------
    Callable
        Decorator that registers the provided plotting function and returns
        it unchanged.
    """

    def decorator(func: Callable):
        # We register against 'cls' - the class this method was called on.
        Plottable._registry[(cls, name, backend)] = func
        return func

    return decorator

plot

plot(method: str, backend: str = 'plotly', *args, **kwargs)

Dispatch a named plot method to a registered backend implementation.

The dispatcher first loads plotting entry points, then searches the instance type and its base classes for a matching (type, method, backend) registration. Additional arguments are forwarded unchanged to the selected backend function.

Parameters:

Name Type Description Default
method str

Plot method name registered for this object's type.

required
backend str

Backend implementation to use. The qten-plots extension currently registers plotly and matplotlib.

'plotly'
args

Positional arguments forwarded to the registered plotting function.

()
kwargs

Keyword arguments forwarded to the registered plotting function.

{}

Returns:

Type Description
object

Backend-specific figure object returned by the registered plotting function, such as a Plotly or Matplotlib figure.

Raises:

Type Description
ValueError

If no plotting function is registered for the requested method and backend on this object.

See Also

qten_plots.plottables.PointCloud Public plottable helper object provided by the plotting extension.

Source code in src/qten/plottings/_plottings.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def plot(self, method: str, backend: str = "plotly", *args, **kwargs):
    """
    Dispatch a named plot method to a registered backend implementation.

    The dispatcher first loads plotting entry points, then searches the
    instance type and its base classes for a matching `(type, method,
    backend)` registration. Additional arguments are forwarded unchanged to
    the selected backend function.

    Parameters
    ----------
    method : str
        Plot method name registered for this object's type.
    backend : str
        Backend implementation to use. The `qten-plots` extension currently
        registers `plotly` and `matplotlib`.
    args
        Positional arguments forwarded to the registered plotting function.
    kwargs
        Keyword arguments forwarded to the registered plotting function.

    Returns
    -------
    object
        Backend-specific figure object returned by the registered plotting
        function, such as a Plotly or Matplotlib figure.

    Raises
    ------
    ValueError
        If no plotting function is registered for the requested method and
        backend on this object.

    See Also
    --------
    qten_plots.plottables.PointCloud
        Public plottable helper object provided by the plotting extension.
    """
    Plottable._ensure_backends_loaded()

    # Iterate over the MRO (Method Resolution Order) of the instance
    for class_in_hierarchy in type(self).__mro__:
        key = (class_in_hierarchy, method, backend)

        # Check the central registry
        if key in Plottable._registry:
            plot_func = Plottable._registry[key]
            return plot_func(self, *args, **kwargs)

    # If we reach here, no method was found. Provide a helpful error.
    self._raise_method_not_found(method, backend)

from_rep classmethod

from_rep(
    rep: ImmutableDenseMatrix,
    euclidean_basis: ImmutableDenseMatrix,
    axes: tuple[Symbol, ...],
    order: int,
    *,
    group: str = "generic",
    irrep: Expr | str | tuple[Expr, ...] = sy.Integer(1),
    irrep_dim: int = 1,
    copy_index: int = 0,
    component_index: int = 0,
) -> PointGroupBasis

Build a normalized basis label from a Euclidean coefficient vector.

Parameters:

Name Type Description Default
rep ImmutableDenseMatrix

Coefficient vector in the Euclidean monomial basis.

required
euclidean_basis ImmutableDenseMatrix

Row matrix of monomials matching rep.

required
axes tuple[Symbol, ...]

Ordered coordinate symbols.

required
order int

Homogeneous polynomial degree.

required
group str

Group tag stored on the returned label.

`"generic"`
irrep Expr | str | tuple[Expr, ...]

Sector label stored on the returned basis.

Integer(1)
irrep_dim int

Dimension of the irrep sector.

1
copy_index int

Copy index for repeated irreps.

0
component_index int

Component index within an irrep multiplet.

0

Returns:

Type Description
PointGroupBasis

Basis label whose rep is magnitude-normalized by the first nonzero coefficient while preserving overall sign.

Source code in src/qten/pointgroups/basis.py
 71
 72
 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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@classmethod
def from_rep(
    cls,
    rep: sy.ImmutableDenseMatrix,
    euclidean_basis: sy.ImmutableDenseMatrix,
    axes: tuple[sy.Symbol, ...],
    order: int,
    *,
    group: str = "generic",
    irrep: sy.Expr | str | tuple[sy.Expr, ...] = sy.Integer(1),
    irrep_dim: int = 1,
    copy_index: int = 0,
    component_index: int = 0,
) -> "PointGroupBasis":
    """
    Build a normalized basis label from a Euclidean coefficient vector.

    Parameters
    ----------
    rep : sy.ImmutableDenseMatrix
        Coefficient vector in the Euclidean monomial basis.
    euclidean_basis : sy.ImmutableDenseMatrix
        Row matrix of monomials matching `rep`.
    axes : tuple[sy.Symbol, ...]
        Ordered coordinate symbols.
    order : int
        Homogeneous polynomial degree.
    group : str, default `"generic"`
        Group tag stored on the returned label.
    irrep : sy.Expr | str | tuple[sy.Expr, ...], optional
        Sector label stored on the returned basis.
    irrep_dim : int, default 1
        Dimension of the irrep sector.
    copy_index : int, default 0
        Copy index for repeated irreps.
    component_index : int, default 0
        Component index within an irrep multiplet.

    Returns
    -------
    PointGroupBasis
        Basis label whose `rep` is magnitude-normalized by the first nonzero
        coefficient while preserving overall sign.
    """

    principle_term = next(x for x in rep if x != 0)
    normalized = sy.ImmutableDenseMatrix(sy.simplify(rep / sy.Abs(principle_term)))
    expr = sy.simplify(normalized.dot(euclidean_basis))
    return cls(
        expr=expr,
        axes=axes,
        order=order,
        rep=normalized,
        group=group,
        irrep=irrep,
        irrep_dim=irrep_dim,
        copy_index=copy_index,
        component_index=component_index,
    )

__str__

__str__() -> str
Source code in src/qten/pointgroups/basis.py
137
138
139
140
141
142
143
144
145
146
147
148
def __str__(self) -> str:
    if sy.simplify(self.expr - 1) == 0:
        expr = "e"
    else:
        expr = str(self.expr)
    if (
        self.group == "generic"
        and isinstance(self.irrep, sy.Basic)
        and sy.simplify(self.irrep - 1) == 0
    ):
        return expr
    return f"{self.group}:{self.irrep}:{expr}"

__repr__

__repr__() -> str
Source code in src/qten/pointgroups/basis.py
150
151
def __repr__(self) -> str:
    return self.__str__()

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
)

__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)

order

order() -> int

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)

FiniteIrrepSector dataclass

FiniteIrrepSector(group: str, irrep: str, dim: int)

Label for a finite-group non-abelian symmetry sector.

group instance-attribute

group: str

irrep instance-attribute

irrep: str

dim instance-attribute

dim: int

JointSpinfulPhaseSector dataclass

JointSpinfulPhaseSector(
    phases: tuple[Expr, ...],
    spatial_orders: tuple[int, ...],
)

Label for simultaneous spinorial phases of a commuting family.

Each \(\zeta_i\) satisfies \(\zeta_i^{2n_i}=1\) for the corresponding spatial order \(n_i\).

phases instance-attribute

phases: tuple[Expr, ...]

spatial_orders instance-attribute

spatial_orders: tuple[int, ...]

SpinorIrrepSector dataclass

SpinorIrrepSector(
    group: str,
    irrep: str,
    dim: int,
    source: str = "qten-su2-principal-v1",
)

Label for a projective spinor irrep sector of a finite point group.

group instance-attribute

group: str

irrep instance-attribute

irrep: str

dim instance-attribute

dim: int

source class-attribute instance-attribute

source: str = 'qten-su2-principal-v1'

SpinfulPhaseSector dataclass

SpinfulPhaseSector(phase: Expr, spatial_order: int)

Label for an abelian spinorial sector.

The phase \(\zeta\) satisfies \(\zeta^{2n}=1\), where \(n\) is the spatial order of the generator. The extra factor of two is the safe period of \(u(g)\), since \(u(2\pi)=-I\).

phase instance-attribute

phase: Expr

spatial_order instance-attribute

spatial_order: int

SymmetryDegeneracy dataclass

SymmetryDegeneracy(index: int)

Typed copy index for repeated symmetry-sector labels.

index instance-attribute

index: int

get_direct_transform

get_direct_transform(
    opr: PointGroupOpr,
    space: HilbertSpace,
    *,
    device: Optional[Device] = None,
) -> Tensor

Build the external basis-mapping tensor from a Hilbert space to its transformed image.

Unlike hilbert_opr_repr(), this helper does not require opr to preserve the ray structure of space. Instead it explicitly constructs the transformed output HilbertSpace and returns a one-hot mapping matrix with dims (space, out_space).

Spinful Hilbert spaces are rejected: a generic \(SU(2)\) factor maps one basis state to a superposition, which this one-to-one mapping cannot express. Use hilbert_repr instead.

When a basis state contains a PointGroupBasis irrep, that irrep is transformed directly in the Euclidean polynomial basis. In particular, no eigen-phase is factored out. For example, a basis function x rotated by C4 is mapped to y in the output space rather than left as x with a phase in the tensor data.

Parameters:

Name Type Description Default
opr PointGroupOpr

Point-group operator used to transform basis labels.

required
space HilbertSpace

Input Hilbert space whose ordered basis defines the source axis.

required
device Optional[Device]

Device on which to allocate the returned mapping tensor.

None

Returns:

Type Description
Tensor

Rank-2 tensor with dimensions (space, out_space) and only 1 numerical entries at the mapped basis positions.

Raises:

Type Description
NotImplementedError

If any basis state in space carries a spin-1/2 label.

Source code in src/qten/pointgroups/ops.py
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
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
def get_direct_transform(
    opr: PointGroupOpr,
    space: HilbertSpace,
    *,
    device: Optional[Device] = None,
) -> Tensor:
    r"""
    Build the external basis-mapping tensor from a Hilbert space to its transformed image.

    Unlike [`hilbert_opr_repr()`][qten.symbolics.ops.hilbert_opr_repr], this helper does not require `opr` to preserve the ray structure of
    `space`. Instead it explicitly constructs the transformed output
    [`HilbertSpace`][qten.symbolics.hilbert_space.HilbertSpace] and returns a one-hot mapping matrix with dims `(space, out_space)`.

    Spinful Hilbert spaces are rejected: a generic \(SU(2)\) factor maps one
    basis state to a superposition, which this one-to-one mapping cannot
    express. Use [`hilbert_repr`][qten.pointgroups.ops.hilbert_repr] instead.

    When a basis state contains a [`PointGroupBasis`][qten.pointgroups.basis.PointGroupBasis] irrep, that irrep is transformed directly in the Euclidean polynomial basis.
    In particular, no eigen-phase is factored out. For example, a basis
    function `x` rotated by `C4` is mapped to `y` in the output space rather
    than left as `x` with a phase in the tensor data.

    Parameters
    ----------
    opr : PointGroupOpr
        Point-group operator used to transform basis labels.
    space : HilbertSpace
        Input Hilbert space whose ordered basis defines the source axis.
    device : Optional[Device], optional
        Device on which to allocate the returned mapping tensor.

    Returns
    -------
    Tensor
        Rank-2 tensor with dimensions `(space, out_space)` and only `1`
        numerical entries at the mapped basis positions.

    Raises
    ------
    NotImplementedError
        If any basis state in `space` carries a spin-1/2 label.
    """
    transformed = {psi: _ext_transform_basis(opr, psi) for psi in space.elements()}
    out_space = space.map(lambda psi: transformed[psi])
    return mapping_matrix(space, out_space, transformed, device=device)

hilbert_repr

hilbert_repr(
    opr: PointGroupOpr,
    space: HilbertSpace,
    *,
    device: Optional[Device] = None,
) -> Tensor

Assemble the Hilbert-space representation \(D(g)\) of a point operation.

Spinless spaces use \(D(g)=D_{\mathrm{orb}}(g)\). Spinful spaces use [ D(g)\,|\mathrm{orb},s\rangle =\sum_{s'}u(g)_{s's}\,|g\cdot\mathrm{orb},\,s'\rangle, ] i.e. \(D(g)=D_{\mathrm{orb}}(g)\otimes u(g)\). Each basis irrep is transformed on its own: lattice Offset labels may be folded back into the unit cell, PointGroupBasis polynomials are canonicalized onto labels already present in space, and Spin is expanded by the \(SU(2)\) lift. This function has no fixpoint=; recenter opr with fixpoint_at first.

Parameters:

Name Type Description Default
opr PointGroupOpr

Point operation, including any affine center already set on it.

required
space HilbertSpace

Ordered basis. Must be closed under opr.

required
device Optional[Device]

Device for the returned tensor.

None

Returns:

Type Description
Tensor

Square tensor of \(D(g)\) on space. On a spinful space this is \(D_{\mathrm{orb}}(g)\otimes u(g)\).

Source code in src/qten/pointgroups/ops.py
 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
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
def hilbert_repr(
    opr: PointGroupOpr, space: HilbertSpace, *, device: Optional[Device] = None
) -> Tensor:
    r"""
    Assemble the Hilbert-space representation \(D(g)\) of a point operation.

    Spinless spaces use \(D(g)=D_{\mathrm{orb}}(g)\). Spinful spaces use
    \[
    D(g)\,|\mathrm{orb},s\rangle
    =\sum_{s'}u(g)_{s's}\,|g\cdot\mathrm{orb},\,s'\rangle,
    \]
    i.e. \(D(g)=D_{\mathrm{orb}}(g)\otimes u(g)\). Each basis irrep is
    transformed on its own: lattice
    [`Offset`][qten.geometries.spatials.Offset] labels may be folded back into
    the unit cell, [`PointGroupBasis`][qten.pointgroups.basis.PointGroupBasis]
    polynomials are canonicalized onto labels already present in `space`, and
    [`Spin`][qten.phys.spin.Spin] is expanded by the \(SU(2)\) lift. This
    function has no `fixpoint=`; recenter `opr` with
    [`fixpoint_at`][qten.pointgroups.elements.PointGroupOpr.fixpoint_at]
    first.

    Parameters
    ----------
    opr : PointGroupOpr
        Point operation, including any affine center already set on it.
    space : HilbertSpace
        Ordered basis. Must be closed under `opr`.
    device : Optional[Device], optional
        Device for the returned tensor.

    Returns
    -------
    Tensor
        Square tensor of \(D(g)\) on `space`. On a spinful space this is
        \(D_{\mathrm{orb}}(g)\otimes u(g)\).
    """
    _validate_hilbert_basis(space)
    if contains_spin(space):
        return spinful_hilbert_opr_repr(opr, space, device=device)
    if not _contains_point_group_basis(space):
        return hilbert_opr_repr(opr, space, device=device)

    ray_to_basis = {psi.rays(): psi for psi in space.elements()}
    precision = get_precision_config()
    torch_device = device.torch_device() if device is not None else None
    data = torch.zeros(
        (space.dim, space.dim),
        dtype=precision.torch_complex,
        device=torch_device,
    )

    fold_offsets = _space_uses_fractional_offsets(space)
    for source in space.elements():
        images = _transform_nons_spin_irreps(
            opr, source.base, space=space, fold_offsets=fold_offsets
        )
        j = space.structure[source]
        for spatial_coef, new_base in images:
            constructed = U1Basis(sy.simplify(source.coef * spatial_coef), new_base)
            target = ray_to_basis.get(constructed.rays())
            if target is None:
                raise ValueError("opr does not preserve the ray structure of space.")
            i = space.structure[target]
            matrix_element = sy.simplify(sy.conjugate(target.coef) * constructed.coef)
            data[i, j] += complex(sy.N(matrix_element))

    return Tensor(data=data, dims=(space, space))

joint_point_group_basis

joint_point_group_basis(
    oprs: Sequence[PointGroupElement | PointGroupOpr],
    order: int,
) -> FrozenDict[
    tuple[sy.Expr, ...], tuple[PointGroupBasis, ...]
]

Compute common Euclidean eigenfunctions for a commuting family of abelian operators.

The returned table is keyed by one phase per input operator. Each value is the tuple of normalized PointGroupBasis functions spanning the simultaneous eigenspace for that joint phase sector.

Parameters:

Name Type Description Default
oprs Sequence[PointGroupElement | PointGroupOpr]

Non-empty sequence of operators. Affine PointGroupOpr inputs contribute only their linear part.

required
order int

Homogeneous polynomial degree used for all Euclidean representations.

required

Returns:

Type Description
FrozenDict[tuple[Expr, ...], tuple[PointGroupBasis, ...]]

Mapping from joint phase tuple to the simultaneous eigen-basis functions for that sector.

Raises:

Type Description
ValueError

If oprs is empty, if the operators do not share the same ordered axes, or if their Euclidean representations at order do not commute.

Source code in src/qten/pointgroups/ops.py
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
def joint_point_group_basis(
    oprs: Sequence[PointGroupElement | PointGroupOpr], order: int
) -> FrozenDict[tuple[sy.Expr, ...], tuple[PointGroupBasis, ...]]:
    """
    Compute common Euclidean eigenfunctions for a commuting family of abelian operators.

    The returned table is keyed by one phase per input operator. Each value is
    the tuple of normalized [`PointGroupBasis`][qten.pointgroups.basis.PointGroupBasis]
    functions spanning the simultaneous eigenspace for that joint phase sector.

    Parameters
    ----------
    oprs : Sequence[PointGroupElement | PointGroupOpr]
        Non-empty sequence of operators. Affine
        [`PointGroupOpr`][qten.pointgroups.elements.PointGroupOpr] inputs contribute
        only their linear part.
    order : int
        Homogeneous polynomial degree used for all Euclidean representations.

    Returns
    -------
    FrozenDict[tuple[sy.Expr, ...], tuple[PointGroupBasis, ...]]
        Mapping from joint phase tuple to the simultaneous eigen-basis
        functions for that sector.

    Raises
    ------
    ValueError
        If `oprs` is empty, if the operators do not share the same ordered
        axes, or if their Euclidean representations at `order` do not commute.
    """
    if not oprs:
        raise ValueError("oprs must be non-empty.")

    groups = tuple(opr.g if isinstance(opr, PointGroupOpr) else opr for opr in oprs)
    axes = groups[0].axes
    if any(g.axes != axes for g in groups[1:]):
        raise ValueError("All operators must share the same ordered axes.")

    transforms = tuple(g.euclidean_repr(order) for g in groups)
    zero = sy.zeros(transforms[0].rows, transforms[0].cols)
    for i, left in enumerate(transforms):
        for right in transforms[i + 1 :]:
            if not sy.simplify(left @ right - right @ left).equals(zero):
                raise ValueError(
                    "All operators must commute in the Euclidean representation "
                    f"of order {order}."
                )

    euclidean_basis = groups[0].euclidean_basis(order)
    ident = sy.ImmutableDenseMatrix.eye(transforms[0].rows)
    all_sector_projectors: list[list[tuple[sy.Expr, sy.ImmutableDenseMatrix]]] = []
    for g, transform in zip(groups, transforms):
        powers = [ident]
        for _ in range(1, g.group_order()):
            powers.append(sy.ImmutableDenseMatrix(sy.simplify(powers[-1] @ transform)))

        sector_projectors: list[tuple[sy.Expr, sy.ImmutableDenseMatrix]] = []
        for phase in g.basis(order):
            projector = sy.zeros(transform.rows, transform.cols)
            for k, power in enumerate(powers):
                projector += sy.simplify((phase ** (-k)) * power)
            sector_projectors.append(
                (
                    sy.simplify(phase),
                    sy.ImmutableDenseMatrix(sy.simplify(projector / g.group_order())),
                )
            )
        all_sector_projectors.append(sector_projectors)

    tbl: dict[tuple[sy.Expr, ...], tuple[PointGroupBasis, ...]] = {}
    for sector_product in product(*all_sector_projectors):
        phases = tuple(phase for phase, _ in sector_product)
        projector = ident
        for _, sector_projector in sector_product:
            projector = sy.ImmutableDenseMatrix(
                sy.simplify(sector_projector @ projector)
            )

        basis_vectors = projector.columnspace()
        if not basis_vectors:
            continue

        labels: list[PointGroupBasis] = []
        seen_reps = set()
        for vec in basis_vectors:
            rep = sy.ImmutableDenseMatrix(vec)
            if all(entry == 0 for entry in rep):
                continue
            basis = PointGroupBasis.from_rep(
                rep=rep,
                euclidean_basis=euclidean_basis,
                axes=axes,
                order=order,
                irrep=phases,
            )
            rep_key = tuple(basis.rep)
            if rep_key in seen_reps:
                continue
            seen_reps.add(rep_key)
            labels.append(basis)

        if labels:
            tbl[phases] = tuple(labels)

    return FrozenDict(tbl)

joint_point_group_column_symmetrize

joint_point_group_column_symmetrize(
    oprs: Sequence[PointGroupOpr],
    w: Tensor,
    full_sector: bool = False,
    *,
    group: FinitePointGroup | None = None,
) -> Tensor

Symmetrize columns of w into simultaneous sectors of abelian operators.

The operators in oprs are expected to commute on w.dims[0]. For each operator this builds the same \(P_\zeta\) as point_group_column_symmetrize. A joint sector is the product projector \(P_{\zeta_1}\cdots P_{\zeta_m}\) over the Cartesian product of those roots of unity.

When full_sector is True, every nonzero joint-sector component is returned. When False, only the dominant nonzero joint-sector component of each input column is kept. Spinless columns carry a representative common PointGroupBasis for the corresponding joint phase sector. Spinful columns carry one JointSpinfulPhaseSector.

This helper has no fixpoint= argument. Center each operator with fixpoint_at before calling it.

Parameters:

Name Type Description Default
oprs Sequence[PointGroupOpr]

Non-empty sequence of finite-order abelian operators. They are expected to commute on the row Hilbert space of w.

required
w Tensor

Rank-2 tensor whose first dimension is a HilbertSpace and whose columns are vectors to project.

required
full_sector bool

If True, return every nonzero joint-sector component of each input column. If False, keep only the largest nonzero joint-sector component per input column.

False
group FinitePointGroup | None

Parent finite point group. Required when w is spinful: every operator must be an element of this group, so the joint family stays inside one double cover.

None

Returns:

Type Description
Tensor

Rank-2 tensor with the same row Hilbert space and a column HilbertSpace labelled by representative joint-sector basis data.

Raises:

Type Description
ValueError

If oprs is empty, if w is not rank 2, if w.dims[0] is not a HilbertSpace, or if w.dims[1] is neither an IndexSpace nor a HilbertSpace.

Source code in src/qten/pointgroups/ops.py
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
def joint_point_group_column_symmetrize(
    oprs: Sequence[PointGroupOpr],
    w: Tensor,
    full_sector: bool = False,
    *,
    group: FinitePointGroup | None = None,
) -> Tensor:
    r"""
    Symmetrize columns of `w` into simultaneous sectors of abelian operators.

    The operators in `oprs` are expected to commute on `w.dims[0]`. For each
    operator this builds the same \(P_\zeta\) as
    [`point_group_column_symmetrize`][qten.pointgroups.ops.point_group_column_symmetrize].
    A joint sector is the product projector
    \(P_{\zeta_1}\cdots P_{\zeta_m}\) over the Cartesian product of those
    roots of unity.

    When `full_sector` is `True`, every nonzero joint-sector component is
    returned. When `False`, only the dominant nonzero joint-sector component of
    each input column is kept. Spinless columns carry a representative common
    [`PointGroupBasis`][qten.pointgroups.basis.PointGroupBasis] for the
    corresponding joint phase sector. Spinful columns carry one
    [`JointSpinfulPhaseSector`][qten.pointgroups.sectors.JointSpinfulPhaseSector].

    This helper has no `fixpoint=` argument. Center each operator with
    [`fixpoint_at`][qten.pointgroups.elements.PointGroupOpr.fixpoint_at]
    before calling it.

    Parameters
    ----------
    oprs : Sequence[PointGroupOpr]
        Non-empty sequence of finite-order abelian operators. They are expected
        to commute on the row Hilbert space of `w`.
    w : Tensor
        Rank-2 tensor whose first dimension is a
        [`HilbertSpace`][qten.symbolics.hilbert_space.HilbertSpace] and whose
        columns are vectors to project.
    full_sector : bool, default False
        If `True`, return every nonzero joint-sector component of each input
        column. If `False`, keep only the largest nonzero joint-sector
        component per input column.
    group : FinitePointGroup | None, optional
        Parent finite point group. Required when `w` is spinful: every operator
        must be an element of this group, so the joint family stays inside one
        double cover.

    Returns
    -------
    Tensor
        Rank-2 tensor with the same row Hilbert space and a column
        [`HilbertSpace`][qten.symbolics.hilbert_space.HilbertSpace] labelled by
        representative joint-sector basis data.

    Raises
    ------
    ValueError
        If `oprs` is empty, if `w` is not rank 2, if `w.dims[0]` is not a
        `HilbertSpace`, or if `w.dims[1]` is neither an `IndexSpace` nor a
        `HilbertSpace`.
    """
    if not oprs:
        raise ValueError("oprs must be non-empty.")
    if len(oprs) == 1:
        return point_group_column_symmetrize(oprs[0], w, full_sector=full_sector)
    row_dim, seeds = _column_symmetrize_context(w)
    spinful = contains_spin(row_dim)
    if group is not None:
        _require_joint_oprs_in_group(oprs, group)
    elif spinful:
        raise ValueError(
            "Joint spinful projection requires group= the FinitePointGroup "
            "that contains every operator."
        )

    single_col = IndexSpace.linear(1)
    joint_sector_bases = None if spinful else _joint_phase_basis(oprs)
    all_sector_projectors: list[list[tuple[sy.Expr, Tensor]]] = []
    representations: list[Tensor] = []
    dtype = w.data.dtype
    device = w.device
    for opr in oprs:
        g_full = _hilbert_opr_repr(opr, row_dim, device=w.device).to_device(w.device)
        dtype = g_full.data.dtype
        device = g_full.device
        tolerance = _relative_tolerance(dtype, row_dim.dim)
        for previous in representations:
            commutator = g_full.data @ previous.data - previous.data @ g_full.data
            if not torch.allclose(
                commutator,
                torch.zeros_like(commutator),
                rtol=0.0,
                atol=tolerance,
            ):
                max_error = float(torch.max(torch.abs(commutator)).item())
                raise ValueError(
                    "Joint point-group projection requires commuting Hilbert-space "
                    f"representations; max |[D_i,D_j]|={max_error:.3e}."
                )
        representations.append(g_full)
        spatial_order = opr.g.group_order()
        order = 2 * spatial_order if spinful else spatial_order
        ident = eye((row_dim, row_dim)).astype(dtype).to_device(device)

        g_powers: list[Tensor] = [ident]
        for _ in range(1, order):
            g_powers.append(g_powers[-1] @ g_full)
        closure = g_powers[-1] @ g_full
        if not torch.allclose(closure.data, ident.data, rtol=0.0, atol=tolerance):
            raise ValueError(
                f"Operator representation does not close at expected order {order}"
            )

        sector_projectors: list[tuple[sy.Expr, Tensor]] = []
        for m in range(order):
            phase_exact = sy.simplify(sy.exp(2 * sy.pi * sy.I * m / order))
            phase_scalar = complex(sy.N(phase_exact))

            projector = 0 * ident
            for k, g_power in enumerate(g_powers):
                projector = projector + (phase_scalar ** (-k)) * g_power
            sector_projectors.append((phase_exact, projector / order))
        all_sector_projectors.append(sector_projectors)

    projected_cols: list[Tensor] = []
    raw_labels: list[U1Basis] = []
    for j, seed in enumerate(seeds):
        col = w[:, j : j + 1].clone().replace_dim(1, single_col).astype(dtype)
        input_norm = abs(col.norm().item())
        projection_cutoff = _relative_tolerance(dtype, row_dim.dim) * input_norm
        candidates: list[tuple[float, Tensor, U1Basis]] = []
        for sector_product in product(*all_sector_projectors):
            phases = tuple(sy.simplify(phase) for phase, _ in sector_product)
            if spinful:
                label = _attach_sector_label(
                    seed,
                    JointSpinfulPhaseSector(
                        phases=phases,
                        spatial_orders=tuple(opr.g.group_order() for opr in oprs),
                    ),
                )
            else:
                assert joint_sector_bases is not None
                basis = joint_sector_bases.get(phases)
                if basis is None:
                    continue
                label = _attach_basis_label(seed, basis)
            projected = col
            for _, projector in sector_product:
                projected = projector @ projected

            projected_norm = projected.norm()
            norm_value = abs(projected_norm.item())
            if norm_value <= projection_cutoff:
                continue

            candidates.append((norm_value, projected / norm_value, label))

        if full_sector:
            for _, projected, label in candidates:
                projected_cols.append(projected)
                raw_labels.append(label)
        elif candidates:
            _, projected, label = max(candidates, key=lambda item: item[0])
            projected_cols.append(projected)
            raw_labels.append(label)

    if not projected_cols:
        return Tensor(
            data=w.data.new_empty((row_dim.dim, 0), dtype=dtype),
            dims=(row_dim, IndexSpace.linear(0)),
        )

    out_dim = HilbertSpace.new(_labels_with_degeneracy(raw_labels))
    return cat(projected_cols, dim=-1).replace_dim(-1, out_dim)

point_group_column_symmetrize

point_group_column_symmetrize(
    opr: PointGroupOpr | FinitePointGroup,
    w: Tensor,
    full_sector: bool = False,
    *,
    fixpoint: Offset | None = None,
    rebase_fixpoint: bool = False,
) -> Tensor

Symmetrize the columns of w by projecting each one onto symmetry sectors.

For a finite-order abelian operator opr of spatial order \(n\), each exact spinless sector is labeled by a root of unity \(\zeta^n=1\). For spin-1/2 the safe common period is \(N=2n\), because a \(2\pi\) proper spin rotation is \(-I\), and the sector is a SpinfulPhaseSector. The projector on \(G=D(g)\) is [ P_\zeta=\frac{1}{N}\sum_{k=0}^{N-1}\zeta^{-k}G^k,\qquad\zeta^N=1, ] with \(N=n\) spinless and \(N=2n\) spinful. The period \(2n\) need not be minimal when the proper spin factor is already the identity.

If opr is a FinitePointGroup, this routine uses [ P^\mu=\frac{d_\mu}{|G|}\sum_{g\in G}\chi^\mu(g)^*D(g). ] Spinless spaces use ordinary (linear) \(\chi\). Spaces that already contain Spin use the group's \(SU(2)\) section and element-wise projective \(\chi\), unless the group was defined with spin="trivial". A cyclic PointGroupOpr is the abelian special case: one-dimensional characters \(\zeta^k\).

The projector is applied to each input column separately. When full_sector is True, every nonzero projected sector component is returned. When full_sector is False, only the dominant nonzero sector component of each input column is kept, so the output column count does not exceed the input count. Returned columns carry a sector label: FiniteIrrepSector for ordinary finite-group irreps, SpinorIrrepSector for projective spinor irreps, or SpinfulPhaseSector / PointGroupBasis for abelian phase sectors.

The output column count can differ from the input one only when full_sector=True, because symmetry projection may split one approximate column into multiple exact sectors.

Parameters:

Name Type Description Default
opr PointGroupOpr | FinitePointGroup

Symmetry descriptor. PointGroupOpr uses the abelian phase-sector path; FinitePointGroup uses finite-group irrep projectors.

required
w Tensor

Rank-2 tensor whose first dimension is a HilbertSpace and whose columns are vectors to project.

required
full_sector bool

If True, return every nonzero sector component of each input column. If False, keep only the largest nonzero sector component per input column.

False
fixpoint Offset | None

Desired invariant point. When set, each group element is wrapped as a PointGroupOpr and recentered with fixpoint_at before D(g) is assembled.

None
rebase_fixpoint bool

Forwarded to PointGroupOpr.fixpoint_at as rebase.

False

Returns:

Type Description
Tensor

Rank-2 tensor with the same row Hilbert space and a column HilbertSpace labelled by symmetry-sector basis data.

Raises:

Type Description
ValueError

If w is not rank 2, if w.dims[0] is not a HilbertSpace, or if w.dims[1] is neither an IndexSpace nor a HilbertSpace.

Source code in src/qten/pointgroups/ops.py
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
def point_group_column_symmetrize(
    opr: PointGroupOpr | FinitePointGroup,
    w: Tensor,
    full_sector: bool = False,
    *,
    fixpoint: Offset | None = None,
    rebase_fixpoint: bool = False,
) -> Tensor:
    r"""
    Symmetrize the columns of `w` by projecting each one onto symmetry sectors.

    For a finite-order abelian operator `opr` of spatial order \(n\), each
    exact spinless sector is labeled by a root of unity \(\zeta^n=1\). For
    spin-1/2 the safe common period is \(N=2n\), because a \(2\pi\) proper
    spin rotation is \(-I\), and the sector is a
    [`SpinfulPhaseSector`][qten.pointgroups.sectors.SpinfulPhaseSector].
    The projector on \(G=D(g)\) is
    \[
    P_\zeta=\frac{1}{N}\sum_{k=0}^{N-1}\zeta^{-k}G^k,\qquad\zeta^N=1,
    \]
    with \(N=n\) spinless and \(N=2n\) spinful. The period \(2n\) need not
    be minimal when the proper spin factor is already the identity.

    If `opr` is a [`FinitePointGroup`][qten.pointgroups.finite.FinitePointGroup],
    this routine uses
    \[
    P^\mu=\frac{d_\mu}{|G|}\sum_{g\in G}\chi^\mu(g)^*D(g).
    \]
    Spinless spaces use ordinary (linear) \(\chi\). Spaces that already
    contain [`Spin`][qten.phys.spin.Spin] use the group's \(SU(2)\) section and
    element-wise projective \(\chi\), unless the group was defined with
    `spin="trivial"`. A cyclic
    [`PointGroupOpr`][qten.pointgroups.elements.PointGroupOpr] is the
    abelian special case: one-dimensional characters \(\zeta^k\).

    The projector is applied to each input column separately. When
    `full_sector` is `True`, every
    nonzero projected sector component is returned. When `full_sector` is
    `False`, only the dominant nonzero sector component of each input column is
    kept, so the output column count does not exceed the input count.
    Returned columns carry a sector label:
    [`FiniteIrrepSector`][qten.pointgroups.sectors.FiniteIrrepSector] for ordinary
    finite-group irreps,
    [`SpinorIrrepSector`][qten.pointgroups.sectors.SpinorIrrepSector] for
    projective spinor irreps, or
    [`SpinfulPhaseSector`][qten.pointgroups.sectors.SpinfulPhaseSector] /
    [`PointGroupBasis`][qten.pointgroups.basis.PointGroupBasis] for abelian
    phase sectors.

    The output column count can differ from the input one only when
    `full_sector=True`, because symmetry projection may split one approximate
    column into multiple exact sectors.

    Parameters
    ----------
    opr : PointGroupOpr | FinitePointGroup
        Symmetry descriptor. `PointGroupOpr` uses the abelian phase-sector path;
        `FinitePointGroup` uses finite-group irrep projectors.
    w : Tensor
        Rank-2 tensor whose first dimension is a
        [`HilbertSpace`][qten.symbolics.hilbert_space.HilbertSpace] and whose
        columns are vectors to project.
    full_sector : bool, default False
        If `True`, return every nonzero sector component of each input column.
        If `False`, keep only the largest nonzero sector component per input
        column.
    fixpoint : Offset | None, optional
        Desired invariant point. When set, each group element is wrapped as
        a `PointGroupOpr` and recentered with `fixpoint_at` before `D(g)`
        is assembled.
    rebase_fixpoint : bool, default False
        Forwarded to
        [`PointGroupOpr.fixpoint_at`][qten.pointgroups.elements.PointGroupOpr.fixpoint_at]
        as `rebase`.

    Returns
    -------
    Tensor
        Rank-2 tensor with the same row Hilbert space and a column
        [`HilbertSpace`][qten.symbolics.hilbert_space.HilbertSpace] labelled by
        symmetry-sector basis data.

    Raises
    ------
    ValueError
        If `w` is not rank 2, if `w.dims[0]` is not a `HilbertSpace`, or if
        `w.dims[1]` is neither an `IndexSpace` nor a `HilbertSpace`.
    """
    if isinstance(opr, FinitePointGroup):
        return _finite_point_group_column_symmetrize(
            opr,
            w,
            full_sector=full_sector,
            fixpoint=fixpoint,
            rebase_fixpoint=rebase_fixpoint,
        )

    if fixpoint is not None:
        opr = opr.fixpoint_at(fixpoint, rebase=rebase_fixpoint)

    row_dim, seeds = _column_symmetrize_context(w)

    g_full = _hilbert_opr_repr(opr, row_dim, device=w.device).to_device(w.device)
    spatial_order = opr.g.group_order()
    spinful = contains_spin(row_dim)
    order = 2 * spatial_order if spinful else spatial_order
    ident = eye((row_dim, row_dim)).astype(g_full.data.dtype).to_device(g_full.device)
    single_col = IndexSpace.linear(1)
    tol = _relative_tolerance(g_full.data.dtype, row_dim.dim)

    g_powers: list[Tensor] = [ident]
    for _ in range(1, order):
        g_powers.append(g_powers[-1] @ g_full)

    closure = g_powers[-1] @ g_full
    if not torch.allclose(closure.data, ident.data, rtol=0.0, atol=tol):
        raise ValueError(
            f"Operator representation does not close at expected order {order}"
        )

    sector_projectors: list[tuple[Any, Tensor]] = []
    for m in range(order):
        phase_exact = sy.simplify(sy.exp(2 * sy.pi * sy.I * m / order))
        sector_label: Any
        if spinful:
            sector_label = SpinfulPhaseSector(
                phase=phase_exact,
                spatial_order=spatial_order,
            )
        else:
            sector_label = _phase_basis(opr, phase_exact)
        phase_scalar = complex(sy.N(phase_exact))

        projector = 0 * ident
        for k, g_power in enumerate(g_powers):
            projector = projector + (phase_scalar ** (-k)) * g_power
        sector_projectors.append((sector_label, projector / order))

    projected_cols: list[Tensor] = []
    raw_labels: list[U1Basis] = []
    for j, seed in enumerate(seeds):
        col = (
            w[:, j : j + 1].clone().replace_dim(1, single_col).astype(g_full.data.dtype)
        )
        input_norm = abs(col.norm().item())
        projection_cutoff = tol * input_norm
        candidates: list[tuple[float, Tensor, U1Basis]] = []
        for sector_label, projector in sector_projectors:
            projected = projector @ col

            projected_norm = projected.norm()
            norm_value = abs(projected_norm.item())
            if norm_value <= projection_cutoff:
                continue

            candidates.append(
                (
                    norm_value,
                    projected / norm_value,
                    _attach_sector_label(seed, sector_label),
                )
            )

        if full_sector:
            for _, projected, label in candidates:
                projected_cols.append(projected)
                raw_labels.append(label)
        elif candidates:
            _, projected, label = max(candidates, key=lambda item: item[0])
            projected_cols.append(projected)
            raw_labels.append(label)

    if not projected_cols:
        return Tensor(
            data=w.data.new_empty((row_dim.dim, 0), dtype=g_full.data.dtype),
            dims=(row_dim, IndexSpace.linear(0)),
        )

    out_dim = HilbertSpace.new(_labels_with_degeneracy(raw_labels))
    return cat(projected_cols, dim=-1).replace_dim(-1, out_dim)

point_group_operator_symmetrize

point_group_operator_symmetrize(
    group: FinitePointGroup,
    operator: Tensor,
    *,
    fixpoint: Offset | None = None,
    rebase_fixpoint: bool = False,
) -> Tensor

Average an operator over a finite point group by unitary conjugation.

This computes [ A_G = |G|^{-1}\sum_{g\in G} D(g)\,A\,D(g)^\dagger. ] The average commutes with every \(D(h)\). Unlike character projection of state vectors, conjugation averaging does not need ordinary or spinor characters: if a lift is replaced by \(\eta(g)u(g)\) with \(\eta(g)\in\{\pm 1\}\), the same factor appears in \(D(g)\) and \(D(g)^\dagger\) and cancels.

The two matrix dimensions of operator must describe the same HilbertSpace. That space must be closed under every point-group operation; otherwise representation assembly raises ValueError.

Parameters:

Name Type Description Default
group FinitePointGroup

Finite point group whose elements generate the average.

required
operator Tensor

Rank-2 tensor whose two dimensions are the same ordered HilbertSpace.

required
fixpoint Offset | None

Desired invariant point. When set, each group element is wrapped as a PointGroupOpr and recentered with fixpoint_at before D(g) is assembled.

None
rebase_fixpoint bool

Forwarded to PointGroupOpr.fixpoint_at as rebase.

False

Returns:

Type Description
Tensor

The conjugation-averaged operator on the same Hilbert space.

Raises:

Type Description
ValueError

If operator is not a square Hilbert-space tensor, or if some D(g) is not numerically unitary.

Source code in src/qten/pointgroups/ops.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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
def point_group_operator_symmetrize(
    group: FinitePointGroup,
    operator: Tensor,
    *,
    fixpoint: Offset | None = None,
    rebase_fixpoint: bool = False,
) -> Tensor:
    r"""
    Average an operator over a finite point group by unitary conjugation.

    This computes
    \[
    A_G = |G|^{-1}\sum_{g\in G} D(g)\,A\,D(g)^\dagger.
    \]
    The average commutes with every \(D(h)\). Unlike character projection of
    state vectors, conjugation averaging does not need ordinary or spinor
    characters: if a lift is replaced by \(\eta(g)u(g)\) with
    \(\eta(g)\in\{\pm 1\}\), the same factor appears in \(D(g)\) and
    \(D(g)^\dagger\) and cancels.

    The two matrix dimensions of `operator` must describe the same
    [`HilbertSpace`][qten.symbolics.hilbert_space.HilbertSpace]. That space
    must be closed under every point-group operation; otherwise representation
    assembly raises `ValueError`.

    Parameters
    ----------
    group : FinitePointGroup
        Finite point group whose elements generate the average.
    operator : Tensor
        Rank-2 tensor whose two dimensions are the same ordered
        [`HilbertSpace`][qten.symbolics.hilbert_space.HilbertSpace].
    fixpoint : Offset | None, optional
        Desired invariant point. When set, each group element is wrapped as
        a `PointGroupOpr` and recentered with `fixpoint_at` before `D(g)`
        is assembled.
    rebase_fixpoint : bool, default False
        Forwarded to
        [`PointGroupOpr.fixpoint_at`][qten.pointgroups.elements.PointGroupOpr.fixpoint_at]
        as `rebase`.

    Returns
    -------
    Tensor
        The conjugation-averaged operator on the same Hilbert space.

    Raises
    ------
    ValueError
        If `operator` is not a square Hilbert-space tensor, or if some
        `D(g)` is not numerically unitary.
    """
    if operator.rank() != 2:
        raise ValueError("operator must be a rank-2 Hilbert-space tensor.")
    row_dim, col_dim = operator.dims
    if not isinstance(row_dim, HilbertSpace) or not isinstance(col_dim, HilbertSpace):
        raise ValueError("operator dimensions must both be HilbertSpace objects.")
    if row_dim != col_dim:
        raise ValueError(
            "operator dimensions must use the same ordered Hilbert-space basis, "
            "including identical U(1) gauges."
        )

    elements = group.elements()
    if not elements:
        raise ValueError("Finite point group contains no elements.")

    averaged: Tensor | None = None
    for element in elements:
        element_opr = PointGroupOpr(element)
        if fixpoint is not None:
            element_opr = element_opr.fixpoint_at(fixpoint, rebase=rebase_fixpoint)
        representation = _hilbert_opr_repr(
            element_opr, row_dim, device=operator.device
        ).to_device(operator.device)
        identity = torch.eye(
            representation.data.shape[0],
            dtype=representation.data.dtype,
            device=representation.data.device,
        )
        tolerance = _relative_tolerance(
            representation.data.dtype, representation.data.shape[0]
        )
        gram = representation.data.conj().T @ representation.data
        if not torch.allclose(gram, identity, rtol=tolerance, atol=tolerance):
            max_error = float(torch.max(torch.abs(gram - identity)).item())
            raise ValueError(
                "Point-group operator averaging requires a unitary Hilbert-space "
                f"representation; max |D†D-I|={max_error:.3e} for {element}."
            )
        transformed = representation @ operator @ representation.h(-2, -1)
        averaged = transformed if averaged is None else averaged + transformed

    assert averaged is not None
    return averaged / len(elements)

spinful_hilbert_opr_repr

spinful_hilbert_opr_repr(
    opr: PointGroupOpr,
    space: HilbertSpace,
    *,
    device: Optional[Device] = None,
) -> Tensor

Matrix of \(D(g)=D_{\mathrm{orb}}(g)\otimes u(g)\) on a spinful Hilbert space.

Columns of \(u(g)\) are ordered \((\uparrow,\downarrow)\). Fast path: compute the \(SU(2)\) factor once, cache the orbital image of each distinct non-spin irrep tuple, and scatter numerical amplitudes into a dense matrix. This avoids per-basis SymPy Gram assembly used by the earlier prototype and is suitable for full finite-group symmetrization.

When every Offset label in space is lattice-backed and already intra-cell fractional, orbital images are folded with Offset.fractional() before lookup so primitive-lattice translations match the stored unit-cell basis. This is a local/Γ-point representation; nonzero-momentum Bloch phases require an explicit momentum-dependent representation and are not inserted here.

Parameters:

Name Type Description Default
opr PointGroupOpr

Point operation, including any affine center already set on it.

required
space HilbertSpace

Spinful Hilbert space. Every basis state must carry exactly one Spin label, and the space must be closed under opr.

required
device Optional[Device]

Device for the returned tensor.

None

Returns:

Type Description
Tensor

Square tensor of \(D_{\mathrm{orb}}(g)\otimes u(g)\) on space.

Source code in src/qten/pointgroups/ops.py
633
634
635
636
637
638
639
640
641
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
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
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
730
731
def spinful_hilbert_opr_repr(
    opr: PointGroupOpr,
    space: HilbertSpace,
    *,
    device: Optional[Device] = None,
) -> Tensor:
    r"""
    Matrix of \(D(g)=D_{\mathrm{orb}}(g)\otimes u(g)\) on a spinful Hilbert space.

    Columns of \(u(g)\) are ordered \((\uparrow,\downarrow)\). Fast path:
    compute the \(SU(2)\) factor once, cache the orbital image of each
    distinct non-spin irrep tuple, and scatter numerical amplitudes into a dense
    matrix. This avoids per-basis SymPy Gram assembly used by the earlier
    prototype and is suitable for full finite-group symmetrization.

    When every [`Offset`][qten.geometries.spatials.Offset] label in `space` is
    lattice-backed and already intra-cell fractional, orbital images are
    folded with `Offset.fractional()` before lookup so primitive-lattice
    translations match the stored unit-cell basis. This is a local/Γ-point
    representation; nonzero-momentum Bloch phases require an explicit
    momentum-dependent representation and are not inserted here.

    Parameters
    ----------
    opr : PointGroupOpr
        Point operation, including any affine center already set on it.
    space : HilbertSpace
        Spinful Hilbert space. Every basis state must carry exactly one
        [`Spin`][qten.phys.spin.Spin] label, and the space must be closed
        under `opr`.
    device : Optional[Device], optional
        Device for the returned tensor.

    Returns
    -------
    Tensor
        Square tensor of \(D_{\mathrm{orb}}(g)\otimes u(g)\) on `space`.
    """
    _validate_spinful_space(space)
    precision = get_precision_config()
    torch_device = device.torch_device() if device is not None else None
    n = space.dim
    data = torch.zeros(
        (n, n),
        dtype=precision.torch_complex,
        device=torch_device,
    )

    # u[out, in] in the ordered basis (Spin.up, Spin.down)
    u = torch.tensor(
        su2_numeric(opr),
        dtype=precision.torch_complex,
        device=torch_device,
    )
    spin_list = (Spin.up, Spin.down)
    spin_index = {Spin.up: 0, Spin.down: 1}

    elements = list(space.elements())
    fold_offsets = _space_uses_fractional_offsets(space)
    # Lookup: (non-spin irrep tuple, Spin) -> (basis index, basis coefficient)
    index_of: dict[tuple[tuple[Any, ...], Spin], tuple[int, complex]] = {}
    parsed: list[tuple[tuple[Any, ...], Spin, complex, int]] = []
    for psi in elements:
        nons, spin, coef = _split_spin_basis(psi)
        j = space.structure[psi]
        numeric_coef = complex(sy.N(coef))
        index_of[(nons, spin)] = (j, numeric_coef)
        parsed.append((nons, spin, numeric_coef, j))

    # Cache orbital images of unique non-spin labels
    nons_image: dict[tuple[Any, ...], list[tuple[complex, tuple[Any, ...]]]] = {}
    for nons, _, _, _ in parsed:
        if nons in nons_image:
            continue
        spatial_terms = _transform_nons_spin_irreps(
            opr, nons, space=space, fold_offsets=fold_offsets
        )
        nons_image[nons] = [
            (complex(sy.N(spatial_coef)), new_nons)
            for spatial_coef, new_nons in spatial_terms
        ]

    for nons, spin_in, psi_coef, j in parsed:
        s_in = spin_index[spin_in]
        for spatial_coef, new_nons in nons_image[nons]:
            for s_out, spin_out in enumerate(spin_list):
                amp = u[s_out, s_in]
                if amp.abs().item() == 0.0:
                    continue
                key = (new_nons, spin_out)
                if key not in index_of:
                    raise ValueError(
                        f"Spinful image ({new_nons}, {spin_out}) is not in space "
                        f"{space}. Space is not closed under {opr}."
                    )
                i, target_coef = index_of[key]
                data[i, j] += (target_coef.conjugate() * spatial_coef * psi_coef) * amp

    return Tensor(data=data, dims=(space, space))

spinful_transform_basis

spinful_transform_basis(
    opr: PointGroupOpr, psi: U1Basis
) -> U1Span

Apply \(D_{\mathrm{orb}}(g)\otimes u(g)\) to a single basis state.

On a product label \(|\mathrm{orb},s\rangle\), [ D(g)|\mathrm{orb},s\rangle =\sum_{s'}u(g)_{s's}\,|g\cdot\mathrm{orb},\,s'\rangle. ] Spatial irreps that opr allows are transformed as usual. The Spin irrep is expanded with the \(SU(2)\) factor. Returns a U1Span because spin mixing generally produces a superposition.

Source code in src/qten/pointgroups/ops.py
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
def spinful_transform_basis(opr: PointGroupOpr, psi: U1Basis) -> U1Span:
    r"""
    Apply \(D_{\mathrm{orb}}(g)\otimes u(g)\) to a single basis state.

    On a product label \(|\mathrm{orb},s\rangle\),
    \[
    D(g)|\mathrm{orb},s\rangle
    =\sum_{s'}u(g)_{s's}\,|g\cdot\mathrm{orb},\,s'\rangle.
    \]
    Spatial irreps that `opr` allows are transformed as usual. The
    [`Spin`][qten.phys.spin.Spin] irrep is expanded with the \(SU(2)\) factor.
    Returns a [`U1Span`][qten.symbolics.hilbert_space.U1Span] because spin
    mixing generally produces a superposition.
    """
    nons, spin, psi_coef = _split_spin_basis(psi)
    orbital_terms = _transform_nons_spin_irreps(opr, nons)
    # Standalone transform keeps the raw geometric image. The Hilbert-space
    # representation can fold unit-cell labels for local/Γ-point matching;
    # nonzero-momentum Bloch translation phases are not represented.

    terms: list[U1Basis] = []
    for spatial_coef, new_nons in orbital_terms:
        for amp, spin_out in expand_spin(opr, spin):
            coef = sy.simplify(psi_coef * spatial_coef * amp)
            if coef == 0:
                continue
            terms.append(U1Basis(coef, new_nons + (spin_out,)))

    if not terms:
        raise RuntimeError(f"spinful image of {psi} vanished")

    merged: dict[U1Basis, U1Basis] = {}
    for term in terms:
        ray = term.rays()
        if ray in merged:
            prev = merged[ray]
            merged[ray] = U1Basis(sy.simplify(prev.coef + term.coef), term.base)
        else:
            merged[ray] = term
    return U1Span(tuple(merged.values()))