Skip to content

qten.topology

Package reference for qten.topology.

topology

Quantum geometry and topology of momentum-resolved band Hamiltonians.

This package computes geometric and topological properties of an isolated occupied-band subspace carried by a rank-3 Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace). Chern and quantum-geometry routines diagonalize independently at every supplied momentum. The two- and three-dimensional \(\mathbb{Z}_2\) routine Fourier-interpolates the mesh so TRIM and Wilson strings can be sampled off the original grid.

Core API
  • quantum_geometric_tensor Gauge-invariant quantum geometric tensor obtained from finite differences of the occupied projector.
  • fubini_study_metric Symmetric metric given by the real part of the quantum geometric tensor.
  • berry_curvature Local Berry curvature given by its imaginary antisymmetric part.
  • chern_number First Chern number computed either with discrete FHS link variables or by integrating the finite-difference Berry curvature.
  • z2_indices Two- and three-dimensional \(\mathbb{Z}_2\) indices from Fu--Kane inversion parities or hybrid-Wannier Wilson loops.
Submodules
  • qten.topology.chern Quantum geometric tensor, Fubini--Study metric, Berry curvature, and Chern number.
  • qten.topology.z2 Two- and three-dimensional \(\mathbb{Z}_2\) invariants.

FHSResult

Bases: TypedDict

Result of chern_number(..., method="fhs").

Discrete Fukui--Hatsugai--Suzuki Chern number on a complete 2-D reciprocal mesh. The runtime object is a plain dict.

Attributes:

Name Type Description
chern float

Sum of oriented plaquette phases divided by \(2\pi\).

nearest_integer int

numpy.rint(chern) as a convenience diagnostic, not a proof that the bundle is isolated.

direct_gap float

Minimum occupied-to-empty direct gap over the mesh.

berry_flux Tensor

Plaquette phase in radians as a labeled Tensor with dims (MomentumSpace,) and shape (N_k,). Momentum \(k\) labels the plaquette anchored at \(k\); the order matches the input Hamiltonian momentum space.

See Also

chern_number Public constructor of this mapping.

chern instance-attribute

chern: float

nearest_integer instance-attribute

nearest_integer: int

direct_gap instance-attribute

direct_gap: float

berry_flux instance-attribute

berry_flux: Tensor

QGTResult

Bases: TypedDict

Result of chern_number(..., method="qgt").

Chern number from integrated projector Berry curvature, plus the local quantum-geometric tensors. The runtime object is a plain dict.

Attributes:

Name Type Description
chern float

\((2\pi)^{-1}\sum_k\Omega_{xy}(k)\) from central finite differences. Approaches an integer only as the mesh is refined.

nearest_integer int

numpy.rint(chern) as a convenience diagnostic.

direct_gap float

Minimum occupied-to-empty direct gap over the mesh.

quantum_geometric_tensor Tensor

Complex QGT with dims (MomentumSpace, IndexSpace(2), IndexSpace(2)) and shape (N_k, 2, 2). Components are per reciprocal-grid step.

fubini_study_metric Tensor

Real part of quantum_geometric_tensor, same dims and shape.

berry_curvature Tensor

\(\Omega_{ij}=2\operatorname{Im}Q_{ij}\), same dims and shape. The \(xy\) orientation matches the FHS plaquette.

See Also

quantum_geometric_tensor Standalone QGT used to build this mapping. chern_number Public constructor of this mapping.

chern instance-attribute

chern: float

nearest_integer instance-attribute

nearest_integer: int

direct_gap instance-attribute

direct_gap: float

quantum_geometric_tensor instance-attribute

quantum_geometric_tensor: Tensor

fubini_study_metric instance-attribute

fubini_study_metric: Tensor

berry_curvature instance-attribute

berry_curvature: Tensor

Z2CombinedResult

Bases: TypedDict

Result of z2_indices(..., method="both").

Both constructions are run. indices follows the Fu--Kane parity values; a mismatch with Wilson emits a RuntimeWarning.

Attributes:

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

Copy of parity["indices"].

method {'both'}

Construction tag for this combined mapping.

parity Z2ParityResult

Full Fu--Kane Z2ParityResult.

wilson Z2WilsonResult

Full hybrid-Wannier Z2WilsonResult.

See Also

z2_indices Public constructor of this mapping.

indices instance-attribute

indices: tuple[int, ...]

method instance-attribute

method: Literal['both']

parity instance-attribute

parity: Z2ParityResult

wilson instance-attribute

wilson: Z2WilsonResult

Z2ParityResult

Bases: TypedDict

Result of z2_indices(..., method="parity").

Fu--Kane indices from inversion eigenvalues at the \(2^d\) TRIM. The runtime object is a plain dict; keys below are required.

Attributes:

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

\(\mathbb{Z}_2\) indices as integers in \(\{0,1\}\). Length 1 in two dimensions, \((\nu,)\). Length 4 in three dimensions, \((\nu_0, \nu_1, \nu_2, \nu_3)\).

method {'parity'}

Construction that produced indices.

parity_products dict[tuple[int, ...], int]

TRIM bit-tuple \(n\) to \(\delta(\Gamma_n)=\pm 1\). Each key has one 0/1 entry per spatial axis, with \(k_j=n_j/2\).

diagnostics dict[tuple[int, ...], Z2ParityTrimDiagnostics]

Per-TRIM Z2ParityTrimDiagnostics with the same keys as parity_products.

direct_gap float

Minimum finite occupied-to-empty gap over the TRIM. nan if none of those gaps are finite.

See Also

z2_indices Public constructor of this mapping.

indices instance-attribute

indices: tuple[int, ...]

method instance-attribute

method: Literal['parity']

parity_products instance-attribute

parity_products: dict[tuple[int, ...], int]

diagnostics instance-attribute

diagnostics: dict[tuple[int, ...], Z2ParityTrimDiagnostics]

direct_gap instance-attribute

direct_gap: float

Z2ParityTrimDiagnostics

Bases: TypedDict

Inversion-parity diagnostics at one time-reversal invariant momentum.

This mapping is one value in Z2ParityResult "diagnostics". TRIM are labeled by bits \(n\in\{0,1\}^d\) with \(k=n/2\).

Attributes:

Name Type Description
delta int

Fu--Kane pair-parity product \(\delta(\Gamma)=\pm 1\). Equal to \((-1)^{N_-/2}\), where \(N_-\) is the number of occupied negative inversion eigenvalues.

parity_eigenvalues Tensor

Occupied inversion eigenvalues \(\xi_n(\Gamma)\) as a labeled Tensor with dims (IndexSpace(n_occupied),) and shape (n_occupied,).

commutator_error float

Relative residual \(\|HI-IH\|/\|H\|\) at this TRIM. Large values mean the supplied or assembled inversion does not commute with \(H(\Gamma)\).

direct_gap float

Occupied-to-empty direct gap at this TRIM. nan if the occupied count leaves no empty band.

delta instance-attribute

delta: int

parity_eigenvalues instance-attribute

parity_eigenvalues: Tensor

commutator_error instance-attribute

commutator_error: float

direct_gap instance-attribute

direct_gap: float

Z2WilsonPlaneResult

Bases: TypedDict

Hybrid-Wannier data on one Wilson-loop plane.

In two dimensions this is one loop orientation over the Brillouin zone. In three dimensions it is one TRIM plane \(k_{\mathrm{normal}}=0\) or \(1/2\).

Attributes:

Name Type Description
z2 int

Plane \(\mathbb{Z}_2\) invariant in \(\{0,1\}\), from the Soluyanov--Vanderbilt largest-gap crossing count of the Wannier centers.

wcc Tensor

Hybrid Wannier charge centers \(\bar x_n(k_\perp)\in[0,1)\) as a labeled Tensor with dims (IndexSpace(n_perp), IndexSpace(n_occupied)) and shape (n_perp, n_occupied). The first axis follows sweep.

gap_pos Tensor

Largest-gap position on the Wannier circle at each sweep sample, as a labeled tensor with dims (IndexSpace(n_perp),).

sweep Tensor

Fractional \(k_\perp\) samples from \(0\) to \(1/2\), labeled by the same IndexSpace(n_perp) as wcc and gap_pos.

min_gap float

Minimum occupied-to-empty direct gap along the Wilson strings on this plane. nan if no finite gap is available.

kramers_resolved bool

Whether Wannier centers at the TRIM-plane endpoints pair into Kramers partners within kramers_tolerance.

z2 instance-attribute

z2: int

wcc instance-attribute

wcc: Tensor

gap_pos instance-attribute

gap_pos: Tensor

sweep instance-attribute

sweep: Tensor

min_gap instance-attribute

min_gap: float

kramers_resolved instance-attribute

kramers_resolved: bool

Z2WilsonResult

Bases: TypedDict

Result of z2_indices(..., method="wilson").

Hybrid-Wannier \(\mathbb{Z}_2\) indices. The runtime object is a plain dict; keys below are required.

Attributes:

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

Same layout as Z2ParityResult.indices: \((\nu,)\) in 2-D or \((\nu_0, \nu_1, \nu_2, \nu_3)\) in 3-D.

method {'wilson'}

Construction that produced indices.

planes dict[tuple[int, float], Z2WilsonPlaneResult]

Plane-resolved hybrid-Wannier data. In 2-D the key is (loop_axis, 0.0) for each loop orientation. In 3-D the key is (normal, trim) with trim in {0.0, 0.5}.

axis_z2 tuple[tuple[int, ...], ...]

Per-axis plane invariants. In 2-D each entry is (ν,) for one loop orientation. In 3-D each entry is (ν(k_j=0), ν(k_j=π)).

min_gap float

Minimum min_gap over planes. nan if none are finite.

See Also

Z2WilsonPlaneResult Value type stored in planes. z2_indices Public constructor of this mapping.

indices instance-attribute

indices: tuple[int, ...]

method instance-attribute

method: Literal['wilson']

planes instance-attribute

planes: dict[tuple[int, float], Z2WilsonPlaneResult]

axis_z2 instance-attribute

axis_z2: tuple[tuple[int, ...], ...]

min_gap instance-attribute

min_gap: float

berry_curvature

berry_curvature(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
) -> Tensor

Compute occupied-subspace Berry curvature on a 1-D, 2-D, or 3-D grid.

QTen uses \(\Omega_{ij}(k)=2\operatorname{Im}Q_{ij}(k)\). In two dimensions, the \(xy\) orientation agrees with chern_number(..., method="fhs").

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace).

required
n_occupied int | None

Number of lowest-energy occupied bands. Defaults to half the bands.

None
gap_tolerance float

Direct-gap warning threshold. Defaults to 1e-8.

1e-08

Returns:

Type Description
Tensor

Pointwise real antisymmetric curvature tensor at every momentum, with dims (MomentumSpace, IndexSpace(d), IndexSpace(d)) and shape (N_k, d, d). In two dimensions, summing curvature.data[..., 0, 1] and dividing by 2*pi gives the central-finite-difference estimate of the first Chern number.

Raises:

Type Description
TypeError

If the first tensor dimension is not a momentum space.

ValueError

If the Hamiltonian, occupied-band selection, or reciprocal grid is invalid. See quantum_geometric_tensor for the complete validation contract.

Notes

This is QGT-derived curvature, not the compact plaquette flux returned by chern_number(..., method="fhs"). Its integral need not be exactly quantized on a finite grid.

See Also

quantum_geometric_tensor Complex tensor from which the curvature is derived. chern_number FHS or curvature-integral Chern number.

Source code in src/qten/topology/chern.py
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
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
def berry_curvature(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-8,
) -> Tensor:
    r"""Compute occupied-subspace Berry curvature on a 1-D, 2-D, or 3-D grid.

    QTen uses \(\Omega_{ij}(k)=2\operatorname{Im}Q_{ij}(k)\). In two dimensions,
    the \(xy\) orientation agrees with
    [`chern_number(..., method="fhs")`][qten.topology.chern_number].

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``.
    n_occupied : int | None, optional
        Number of lowest-energy occupied bands. Defaults to half the bands.
    gap_tolerance : float, optional
        Direct-gap warning threshold. Defaults to ``1e-8``.

    Returns
    -------
    Tensor
        Pointwise real antisymmetric curvature tensor at every momentum, with dims
        ``(MomentumSpace, IndexSpace(d), IndexSpace(d))`` and shape
        ``(N_k, d, d)``. In two dimensions, summing
        ``curvature.data[..., 0, 1]`` and dividing by ``2*pi`` gives the
        central-finite-difference estimate of the first Chern number.

    Raises
    ------
    TypeError
        If the first tensor dimension is not a momentum space.
    ValueError
        If the Hamiltonian, occupied-band selection, or reciprocal grid is
        invalid. See
        [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        for the complete validation contract.

    Notes
    -----
    This is QGT-derived curvature, not the compact plaquette flux returned by
    ``chern_number(..., method="fhs")``. Its integral need not be exactly
    quantized on a finite grid.

    See Also
    --------
    [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        Complex tensor from which the curvature is derived.
    [`chern_number`][qten.topology.chern_number]
        FHS or curvature-integral Chern number.
    """
    qgt = quantum_geometric_tensor(bloch_hamiltonian, n_occupied, gap_tolerance)
    return 2.0 * qgt.imag()

chern_number

chern_number(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
    *,
    method: Literal["fhs"] = "fhs",
) -> FHSResult
chern_number(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
    *,
    method: Literal["qgt"],
) -> QGTResult

Compute the first Chern number of an occupied band subspace.

The n_occupied lowest-energy eigenstates define an occupied bundle over a complete two-dimensional periodic momentum grid. Two numerical methods are available:

  • method="fhs" computes normalized determinant link variables between neighboring occupied subspaces and sums their oriented plaquette phases. This gauge-invariant Fukui--Hatsugai--Suzuki construction is the default and the recommended finite-grid topological invariant.
  • method="qgt" computes the projector quantum geometric tensor, takes \(\Omega_{xy}=2\operatorname{Im}Q_{xy}\), and evaluates \(C=(2\pi)^{-1}\sum_k\Omega_{xy}(k)\). It additionally returns all local quantum-geometric data.

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace). The first data axis enumerates a complete two-dimensional reciprocal quotient; the last two axes are square Bloch-Hamiltonian matrices and are aligned onto a common Hilbert space.

required
n_occupied int | None

Number of lowest-energy bands defining the occupied subspace. It must lie strictly between zero and the total band count. Defaults to half the bands using integer division.

None
gap_tolerance float

Warning threshold for the minimum direct gap \(\min_k[E_{n_\mathrm{occupied}}(k)- E_{n_\mathrm{occupied}-1}(k)]\). A gap at or below this value emits a RuntimeWarning, because the occupied bundle is not isolated and its Chern number is not well-defined. Defaults to 1e-8.

1e-08
method (fhs, qgt)

Numerical construction. "fhs" uses discrete determinant link variables and is robustly quantized on a suitable finite mesh. "qgt" integrates central-finite-difference curvature and exposes local quantum geometry. Defaults to "fhs".

"fhs"

Returns:

Type Description
dict[str, Any]

Result mapping. Both methods return:

  • "chern": raw floating-point Chern value.
  • "nearest_integer": nearest integer obtained with numpy.rint.
  • "direct_gap": minimum occupied-to-empty direct gap.

For method="fhs" the mapping also contains "berry_flux", the oriented plaquette phase in radians as a labeled Tensor with dims (MomentumSpace,) and shape (N_k,). Momentum \(k\) labels the plaquette anchored at \(k\), and the order matches the input MomentumSpace.

For method="qgt" the mapping instead contains labeled Tensor values: "quantum_geometric_tensor" and "fubini_study_metric" and "berry_curvature" all have shape (N_k, 2, 2). Each retains the input momentum space as its first dimension.

Raises:

Type Description
TypeError

If the first tensor dimension is not a MomentumSpace, or either matrix dimension is not a HilbertSpace.

ValueError

If method is unsupported; the input is not a rank-3 square Bloch Hamiltonian; n_occupied is outside the valid range; the momentum space is not two-dimensional and periodic; or its points do not form a unique complete reciprocal quotient.

RuntimeError

For method="fhs", if a neighboring occupied-subspace overlap has determinant magnitude below 1e-14. This indicates a singular link; increasing the momentum-grid resolution may resolve it.

Warns:

Type Description
RuntimeWarning

If the minimum direct gap is no larger than gap_tolerance.

Notes

The FHS value satisfies

\[ C_\mathrm{FHS} = \frac{1}{2\pi} \sum_k \operatorname{Arg}\!\left[ U_x(k)U_y(k+e_x)U_x(k+e_y)^*U_y(k)^* \right], \]

where \(U_i(k)\) is the phase of the determinant of the occupied-subspace overlap between \(k\) and \(k+e_i\). Determinants make the formula invariant under arbitrary unitary changes of occupied-band basis.

nearest_integer is a convenience diagnostic, not proof that the bundle is isolated or the mesh is sufficiently resolved. Inspect direct_gap and, when necessary, repeat the calculation on finer momentum grids.

The flux tensor is intentionally flat for every cell: its symbolic MomentumSpace dimension preserves labels without implying rectangular heatmap adjacency. This is especially important for sheared cells, whose quotient-representative order is not a rectangular Brillouin-zone heatmap.

Examples:

Use the robust finite-grid method:

result = chern_number(hamiltonian, n_occupied=1)
invariant = result["nearest_integer"]
flux = result["berry_flux"]

Request the differential-geometric decomposition:

geometry = chern_number(hamiltonian, n_occupied=1, method="qgt")
metric = geometry["fubini_study_metric"]
curvature = geometry["berry_curvature"]
See Also

quantum_geometric_tensor Gauge-invariant local quantum geometric tensor. fubini_study_metric Metric part of the QGT. berry_curvature Curvature part of the QGT.

Source code in src/qten/topology/chern.py
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
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
631
632
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
def chern_number(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-8,
    *,
    method: Literal["fhs", "qgt"] = "fhs",
) -> FHSResult | QGTResult:
    r"""Compute the first Chern number of an occupied band subspace.

    The ``n_occupied`` lowest-energy eigenstates define an occupied bundle over
    a complete two-dimensional periodic momentum grid. Two numerical methods
    are available:

    - ``method="fhs"`` computes normalized determinant link variables between
      neighboring occupied subspaces and sums their oriented plaquette phases.
      This gauge-invariant Fukui--Hatsugai--Suzuki construction is the default
      and the recommended finite-grid topological invariant.
    - ``method="qgt"`` computes the projector quantum geometric tensor, takes
      \(\Omega_{xy}=2\operatorname{Im}Q_{xy}\), and evaluates
      \(C=(2\pi)^{-1}\sum_k\Omega_{xy}(k)\). It additionally returns all local
      quantum-geometric data.

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``. The first data axis
        enumerates a complete two-dimensional reciprocal quotient; the last
        two axes are square Bloch-Hamiltonian matrices and are aligned onto
        a common Hilbert space.
    n_occupied : int | None, optional
        Number of lowest-energy bands defining the occupied subspace. It must
        lie strictly between zero and the total band count. Defaults to half
        the bands using integer division.
    gap_tolerance : float, optional
        Warning threshold for the minimum direct gap
        \(\min_k[E_{n_\mathrm{occupied}}(k)-
        E_{n_\mathrm{occupied}-1}(k)]\). A gap at or below this value emits a
        `RuntimeWarning`, because the occupied bundle is not
        isolated and its Chern number is not well-defined. Defaults to
        ``1e-8``.
    method : {"fhs", "qgt"}, optional
        Numerical construction. ``"fhs"`` uses discrete determinant link
        variables and is robustly quantized on a suitable finite mesh.
        ``"qgt"`` integrates central-finite-difference curvature and exposes
        local quantum geometry. Defaults to ``"fhs"``.

    Returns
    -------
    dict[str, Any]
        Result mapping. Both methods return:

        - ``"chern"``: raw floating-point Chern value.
        - ``"nearest_integer"``: nearest integer obtained with `numpy.rint`.
        - ``"direct_gap"``: minimum occupied-to-empty direct gap.

        For ``method="fhs"`` the mapping also contains ``"berry_flux"``, the
        oriented plaquette phase in radians as a labeled `Tensor` with dims
        ``(MomentumSpace,)`` and shape ``(N_k,)``. Momentum \(k\) labels the
        plaquette anchored at \(k\), and the order matches the input
        `MomentumSpace`.

        For ``method="qgt"`` the mapping instead contains labeled `Tensor`
        values: ``"quantum_geometric_tensor"`` and
        ``"fubini_study_metric"`` and ``"berry_curvature"`` all have shape
        ``(N_k, 2, 2)``. Each retains the input momentum space as its first
        dimension.

    Raises
    ------
    TypeError
        If the first tensor dimension is not a
        [`MomentumSpace`][qten.symbolics.state_space.MomentumSpace], or either
        matrix dimension is not a `HilbertSpace`.
    ValueError
        If ``method`` is unsupported; the input is not a rank-3 square Bloch
        Hamiltonian; ``n_occupied`` is outside the valid range; the momentum
        space is not two-dimensional and periodic; or its points do not form a
        unique complete reciprocal quotient.
    RuntimeError
        For ``method="fhs"``, if a neighboring occupied-subspace overlap has
        determinant magnitude below ``1e-14``. This indicates a singular link;
        increasing the momentum-grid resolution may resolve it.

    Warns
    -----
    RuntimeWarning
        If the minimum direct gap is no larger than ``gap_tolerance``.

    Notes
    -----
    The FHS value satisfies

    \[
    C_\mathrm{FHS} = \frac{1}{2\pi}
    \sum_k \operatorname{Arg}\!\left[
      U_x(k)U_y(k+e_x)U_x(k+e_y)^*U_y(k)^*
    \right],
    \]

    where \(U_i(k)\) is the phase of the determinant of the occupied-subspace
    overlap between \(k\) and \(k+e_i\). Determinants make the formula
    invariant under arbitrary unitary changes of occupied-band basis.

    ``nearest_integer`` is a convenience diagnostic, not proof that the bundle
    is isolated or the mesh is sufficiently resolved. Inspect ``direct_gap``
    and, when necessary, repeat the calculation on finer momentum grids.

    The flux tensor is intentionally flat for every cell: its symbolic
    `MomentumSpace` dimension preserves labels without implying rectangular
    heatmap adjacency. This is especially important for sheared cells, whose
    quotient-representative order is not a rectangular Brillouin-zone heatmap.

    Examples
    --------
    Use the robust finite-grid method:

    ```python
    result = chern_number(hamiltonian, n_occupied=1)
    invariant = result["nearest_integer"]
    flux = result["berry_flux"]
    ```

    Request the differential-geometric decomposition:

    ```python
    geometry = chern_number(hamiltonian, n_occupied=1, method="qgt")
    metric = geometry["fubini_study_metric"]
    curvature = geometry["berry_curvature"]
    ```

    See Also
    --------
    [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        Gauge-invariant local quantum geometric tensor.
    [`fubini_study_metric`][qten.topology.fubini_study_metric]
        Metric part of the QGT.
    [`berry_curvature`][qten.topology.berry_curvature]
        Curvature part of the QGT.
    """
    if method == "fhs":
        return _discrete_chern_number(bloch_hamiltonian, n_occupied, gap_tolerance)
    if method != "qgt":
        raise ValueError("method must be 'fhs' or 'qgt'.")

    grid = _topology_grid(bloch_hamiltonian, n_occupied, gap_tolerance)
    if grid.momentum_dim != 2:
        raise ValueError("The first Chern number requires a two-dimensional grid.")
    qgt = _quantum_geometric_tensor_from_grid(grid)
    curvature = 2.0 * qgt.imag()
    chern = float(curvature.data[..., 0, 1].sum() / (2.0 * np.pi))
    return {
        "chern": chern,
        "nearest_integer": int(np.rint(chern)),
        "direct_gap": grid.direct_gap,
        "quantum_geometric_tensor": qgt,
        "fubini_study_metric": qgt.real(),
        "berry_curvature": curvature,
    }

fubini_study_metric

fubini_study_metric(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
) -> Tensor

Compute the occupied-subspace Fubini--Study metric on a 1-D, 2-D, or 3-D grid.

This function returns \(g_{ij}(k)=\operatorname{Re}Q_{ij}(k)\), where the QGT is computed by quantum_geometric_tensor.

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace).

required
n_occupied int | None

Number of lowest-energy occupied bands. Defaults to half the bands.

None
gap_tolerance float

Direct-gap warning threshold. Defaults to 1e-8.

1e-08

Returns:

Type Description
Tensor

Real metric with dims (MomentumSpace, IndexSpace(d), IndexSpace(d)) and shape (N_k, d, d). Components are expressed per reciprocal-grid step.

Raises:

Type Description
TypeError

If the first tensor dimension is not a momentum space.

ValueError

If the Hamiltonian, occupied-band selection, or reciprocal grid is invalid. See quantum_geometric_tensor for the complete validation contract.

See Also

quantum_geometric_tensor Complex parent tensor of the metric and curvature. berry_curvature Berry curvature from the imaginary part of the QGT.

Source code in src/qten/topology/chern.py
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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def fubini_study_metric(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-8,
) -> Tensor:
    r"""Compute the occupied-subspace Fubini--Study metric on a 1-D, 2-D,
    or 3-D grid.

    This function returns \(g_{ij}(k)=\operatorname{Re}Q_{ij}(k)\), where the
    QGT is computed by
    [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor].

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``.
    n_occupied : int | None, optional
        Number of lowest-energy occupied bands. Defaults to half the bands.
    gap_tolerance : float, optional
        Direct-gap warning threshold. Defaults to ``1e-8``.

    Returns
    -------
    Tensor
        Real metric with dims ``(MomentumSpace, IndexSpace(d),
        IndexSpace(d))`` and shape ``(N_k, d, d)``. Components are expressed
        per reciprocal-grid step.

    Raises
    ------
    TypeError
        If the first tensor dimension is not a momentum space.
    ValueError
        If the Hamiltonian, occupied-band selection, or reciprocal grid is
        invalid. See
        [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        for the complete validation contract.

    See Also
    --------
    [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        Complex parent tensor of the metric and curvature.
    [`berry_curvature`][qten.topology.berry_curvature]
        Berry curvature from the imaginary part of the QGT.
    """
    return quantum_geometric_tensor(bloch_hamiltonian, n_occupied, gap_tolerance).real()

quantum_geometric_tensor

quantum_geometric_tensor(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
) -> Tensor

Compute the occupied-subspace quantum geometric tensor on a 1-D, 2-D, or 3-D grid.

The occupied projector is built from the n_occupied lowest-energy eigenvectors at every momentum. Central differences along every primitive reciprocal-grid direction approximate \(\partial_iP\), after which \(Q_{ij}=\operatorname{Tr}[P(\partial_iP)(\partial_jP)]\) is evaluated.

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace). The final two data axes must be square Hilbert-space matrices at each momentum and are aligned onto a common Hilbert space.

required
n_occupied int | None

Number of lowest-energy bands included in the occupied projector. Defaults to half the Hamiltonian bands using integer division.

None
gap_tolerance float

Minimum acceptable direct gap between bands n_occupied - 1 and n_occupied. A gap at or below this value emits a RuntimeWarning, because the selected bundle is not numerically isolated. Defaults to 1e-8.

1e-08

Returns:

Type Description
Tensor

Complex QGT with dims (MomentumSpace, IndexSpace(d), IndexSpace(d)) and shape (N_k, d, d), where d is the momentum-space dimension. The first dimension is the input Hamiltonian's momentum space, so momentum labels and their ordering are preserved. Components are measured per reciprocal-grid step, not per Cartesian inverse-length unit.

Raises:

Type Description
TypeError

If the first tensor dimension is not a MomentumSpace, or either matrix dimension is not a HilbertSpace.

ValueError

If the tensor is not rank 3, its Hamiltonian blocks are not square, n_occupied is invalid, the momentum space is not 1-D, 2-D, or 3-D, the boundary is not periodic, or momentum points do not form a unique complete reciprocal quotient.

Notes

The projector formulation is invariant under arbitrary momentum-dependent unitary rotations within the occupied subspace. It therefore remains well-defined when occupied bands cross each other, provided the occupied subspace stays separated from the empty bands.

See Also

fubini_study_metric Real part of this tensor. berry_curvature Imaginary antisymmetric part of this tensor. chern_number Brillouin-zone topological invariant.

Source code in src/qten/topology/chern.py
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
def quantum_geometric_tensor(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-8,
) -> Tensor:
    r"""Compute the occupied-subspace quantum geometric tensor on a 1-D, 2-D,
    or 3-D grid.

    The occupied projector is built from the ``n_occupied`` lowest-energy
    eigenvectors at every momentum. Central differences along every primitive
    reciprocal-grid direction approximate \(\partial_iP\), after which
    \(Q_{ij}=\operatorname{Tr}[P(\partial_iP)(\partial_jP)]\) is evaluated.

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``. The final two data
        axes must be square Hilbert-space matrices at each momentum and are
        aligned onto a common Hilbert space.
    n_occupied : int | None, optional
        Number of lowest-energy bands included in the occupied projector.
        Defaults to half the Hamiltonian bands using integer division.
    gap_tolerance : float, optional
        Minimum acceptable direct gap between bands ``n_occupied - 1`` and
        ``n_occupied``. A gap at or below this value emits a
        `RuntimeWarning`, because the selected bundle is not
        numerically isolated. Defaults to ``1e-8``.

    Returns
    -------
    Tensor
        Complex QGT with dims ``(MomentumSpace, IndexSpace(d),
        IndexSpace(d))`` and shape ``(N_k, d, d)``, where ``d`` is the
        momentum-space dimension. The first dimension is the input
        Hamiltonian's momentum space, so momentum labels and their ordering
        are preserved. Components are measured per reciprocal-grid step, not
        per Cartesian inverse-length unit.

    Raises
    ------
    TypeError
        If the first tensor dimension is not a
        [`MomentumSpace`][qten.symbolics.state_space.MomentumSpace], or either
        matrix dimension is not a `HilbertSpace`.
    ValueError
        If the tensor is not rank 3, its Hamiltonian blocks are not square,
        ``n_occupied`` is invalid, the momentum space is not 1-D, 2-D, or 3-D,
        the boundary is not periodic, or momentum points do not form a unique
        complete reciprocal quotient.

    Notes
    -----
    The projector formulation is invariant under arbitrary momentum-dependent
    unitary rotations within the occupied subspace. It therefore remains
    well-defined when occupied bands cross each other, provided the occupied
    subspace stays separated from the empty bands.

    See Also
    --------
    [`fubini_study_metric`][qten.topology.fubini_study_metric]
        Real part of this tensor.
    [`berry_curvature`][qten.topology.berry_curvature]
        Imaginary antisymmetric part of this tensor.
    [`chern_number`][qten.topology.chern_number]
        Brillouin-zone topological invariant.
    """
    grid = _topology_grid(bloch_hamiltonian, n_occupied, gap_tolerance)
    return _quantum_geometric_tensor_from_grid(grid)

z2_indices

z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["auto"] = "auto",
    inversion: Tensor | None = None,
    inversion_center: Offset
    | Sequence[float]
    | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-05,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-08,
) -> Z2ParityResult | Z2WilsonResult
z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["parity"],
    inversion: Tensor | None = None,
    inversion_center: Offset
    | Sequence[float]
    | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-05,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-08,
) -> Z2ParityResult
z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["wilson"],
    inversion: Tensor | None = None,
    inversion_center: Offset
    | Sequence[float]
    | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-05,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-08,
) -> Z2WilsonResult
z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["both"],
    inversion: Tensor | None = None,
    inversion_center: Offset
    | Sequence[float]
    | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-05,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-08,
) -> Z2CombinedResult

Compute the 2-D or 3-D \(\mathbb{Z}_2\) indices of an occupied band subspace.

The n_occupied lowest-energy eigenstates, which must form an even number of Kramers pairs, define an occupied bundle over a complete two- or three-dimensional periodic momentum grid. Two numerical methods are available:

  • method="parity" evaluates Fu--Kane inversion eigenvalues at the \(2^d\) TRIM. Two dimensions return \((\nu,)\); three dimensions return \((\nu_0; \nu_1\nu_2\nu_3)\).
  • method="wilson" computes hybrid Wannier charge centers. In 2-D the two loop orientations should agree on \(\nu\). In 3-D the strong index is \(\nu_0=\nu(k_i=0)+\nu(k_i=\pi)\bmod 2\) and the weak indices are the three \(k_i=\pi\) plane invariants. If the three axes disagree on \(\nu_0\), the majority vote is returned.

The Hamiltonian is Fourier-interpolated from the supplied mesh, so TRIM and Wilson strings need not coincide with sampled \(k\)-points. This is the construction used for odd meshes such as \(27^3\) or \(9^2\). The periodic cell must be diagonal in the primitive basis.

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace), or with a MomentumBlockSpace of diagonal \((k,k)\) blocks whose momenta form a complete 2-D or 3-D reciprocal quotient. The last two axes are square Bloch-Hamiltonian matrices and are aligned onto a common Hilbert space.

required
n_occupied int | None

Number of lowest-energy bands defining the occupied subspace. It must be even and lie strictly between zero and the total band count. Defaults to half the bands using integer division.

None
method ('auto', 'parity', 'wilson', 'both')

Numerical construction. "auto" tries Fu--Kane parity and falls back to Wilson loops if inversion cannot be resolved (RuntimeError). "both" requires parity to succeed. Defaults to "auto".

"auto"
inversion Tensor | None

Optional rank-3 inversion operator with dims (MomentumSpace, HilbertSpace, HilbertSpace), or with a diagonal MomentumBlockSpace of \((k,k)\) blocks. Band axes are aligned onto the Hamiltonian Hilbert space. Each momentum is paired with \(H(k)\) by its label, not by data-axis order, and the labeled points must form the same complete reciprocal quotient. If omitted, spatial inversion is assembled from orbital Offset labels about inversion_center.

None
inversion_center Offset | Sequence[float] | None

Fixed point of spatial inversion, as an Offset or a \(d\)-vector in the Hamiltonian's direct-lattice coordinates. Defaults to the centroid of the unique orbital offsets.

None
n_loop int

Number of Wilson-loop samples around each closed \(k\)-string. Must be at least 8 when Wilson loops are evaluated. Defaults to 32.

32
n_perp int

Number of hybrid-Wannier samples from a TRIM plane's \(k_\perp=0\) edge to \(k_\perp=\pi\). Must be at least 5 when Wilson loops are evaluated. Defaults to 17.

17
parity_tolerance float

Maximum relative \([H,I]\) commutator and inversion-eigenvalue deviation accepted at a TRIM. Defaults to 1e-5.

1e-05
kramers_tolerance float

Maximum Wannier-center separation allowed when pairing Kramers partners on TRIM-plane endpoints. Defaults to 0.08.

0.08
gap_tolerance float

Warning threshold for the minimum sampled occupied-to-empty direct gap. Defaults to 1e-8.

1e-08

Returns:

Type Description
Z2ParityResult or Z2WilsonResult or Z2CombinedResult

Result mapping. Every method returns:

  • "indices": \((\nu,)\) in 2-D or \((\nu_0, \nu_1, \nu_2, \nu_3)\) in 3-D, as integers in \(\{0,1\}\).
  • "method": the construction that produced those indices.

For method="parity" the mapping also contains Fu--Kane "parity_products" at each TRIM, per-TRIM "diagnostics" (including a labeled "parity_eigenvalues" Tensor of shape (n_occupied,)), and "direct_gap".

For method="wilson" it contains hybrid-Wannier "planes" whose "wcc" (shape (n_perp, n_occupied)), "gap_pos", and "sweep" values are labeled Tensor objects, per-axis plane invariants "axis_z2", and "min_gap".

For method="both" it contains both "parity" and "wilson" sub-results; "indices" follows the parity values.

Raises:

Type Description
TypeError

If the Hamiltonian or inversion first dimension is not a MomentumSpace or MomentumBlockSpace, or either Hamiltonian matrix dimension is not a HilbertSpace.

ValueError

If method is unsupported; the input is not a rank-3 square Bloch Hamiltonian; n_occupied is invalid; the momentum space is not two- or three-dimensional and periodic with a diagonal cell; a MomentumBlockSpace contains off-diagonal \((k,k')\) blocks; Hamiltonian or inversion momenta do not form a unique complete reciprocal quotient; inversion band axes do not span the Hamiltonian Hilbert space; or n_loop / n_perp are below the Wilson-loop minima.

RuntimeError

For method="parity" or method="both", if inversion cannot be constructed or is not resolved at a TRIM.

Warns:

Type Description
RuntimeWarning

If the sampled minimum direct gap is no larger than gap_tolerance; if method="auto" falls back from parity to Wilson loops; if the two 2-D Wilson orientations disagree; if the three 3-D Wilson axes disagree on \(\nu_0\) (majority vote is used); if Kramers pairing of Wannier centers is unresolved; if parity and Wilson indices disagree; or if the sampled spectrum is not time-reversal / Kramers consistent.

Notes

In 2-D, Fu--Kane gives \((-1)^\nu=\prod_i\delta(\Gamma_i)\). In 3-D, \((-1)^{\nu_0}=\prod_i\delta(\Gamma_i)\) and \((-1)^{\nu_j}=\prod_{k_j=\pi}\delta(\Gamma_i)\). Wilson indices use the hybrid-Wannier plane invariants described in the module docstring. Both constructions evaluate the Fourier interpolant of the input mesh rather than requiring TRIM or Wilson strings to sit on sampled \(k\)-points.

Examples:

Use Fu--Kane parities when an inversion tensor is available:

result = z2_indices(hamiltonian, n_occupied=2, inversion=inversion, method="parity")
indices = result["indices"]

Fall back to Wilson loops on a system without inversion:

wilson = z2_indices(hamiltonian, n_occupied=2, method="wilson")
See Also

chern_number First Chern number of a 2-D occupied bundle.

Source code in src/qten/topology/z2.py
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
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
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
def z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["auto", "parity", "wilson", "both"] = "auto",
    inversion: Tensor | None = None,
    inversion_center: Offset | Sequence[float] | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-5,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-8,
) -> Z2ParityResult | Z2WilsonResult | Z2CombinedResult:
    r"""Compute the 2-D or 3-D \(\mathbb{Z}_2\) indices of an occupied band
    subspace.

    The ``n_occupied`` lowest-energy eigenstates, which must form an even
    number of Kramers pairs, define an occupied bundle over a complete
    two- or three-dimensional periodic momentum grid. Two numerical methods
    are available:

    - ``method="parity"`` evaluates Fu--Kane inversion eigenvalues at the
      \(2^d\) TRIM. Two dimensions return \((\nu,)\); three dimensions return
      \((\nu_0; \nu_1\nu_2\nu_3)\).
    - ``method="wilson"`` computes hybrid Wannier charge centers. In 2-D the
      two loop orientations should agree on \(\nu\). In 3-D the strong index
      is \(\nu_0=\nu(k_i=0)+\nu(k_i=\pi)\bmod 2\) and the weak indices are the
      three \(k_i=\pi\) plane invariants. If the three axes disagree on
      \(\nu_0\), the majority vote is returned.

    The Hamiltonian is Fourier-interpolated from the supplied mesh, so TRIM
    and Wilson strings need not coincide with sampled \(k\)-points. This is
    the construction used for odd meshes such as \(27^3\) or \(9^2\). The
    periodic cell must be diagonal in the primitive basis.

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``, or with a
        [`MomentumBlockSpace`][qten.symbolics.state_space.MomentumBlockSpace]
        of diagonal \((k,k)\) blocks whose momenta form a complete 2-D or 3-D
        reciprocal quotient. The last two axes are square Bloch-Hamiltonian
        matrices and are aligned onto a common Hilbert space.
    n_occupied : int | None, optional
        Number of lowest-energy bands defining the occupied subspace. It must
        be even and lie strictly between zero and the total band count.
        Defaults to half the bands using integer division.
    method : {"auto", "parity", "wilson", "both"}, optional
        Numerical construction. ``"auto"`` tries Fu--Kane parity and falls
        back to Wilson loops if inversion cannot be resolved
        (`RuntimeError`). ``"both"`` requires parity to succeed. Defaults to
        ``"auto"``.
    inversion : Tensor | None, optional
        Optional rank-3 inversion operator with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``, or with a diagonal
        `MomentumBlockSpace` of \((k,k)\) blocks. Band axes are aligned onto
        the Hamiltonian Hilbert space. Each momentum is paired with
        \(H(k)\) by its label, not by data-axis order, and the labeled
        points must form the same complete reciprocal quotient. If omitted,
        spatial inversion is assembled from orbital
        [`Offset`][qten.geometries.spatials.Offset] labels about
        ``inversion_center``.
    inversion_center : Offset | Sequence[float] | None, optional
        Fixed point of spatial inversion, as an `Offset` or a \(d\)-vector in
        the Hamiltonian's direct-lattice coordinates. Defaults to the centroid
        of the unique orbital offsets.
    n_loop : int, optional
        Number of Wilson-loop samples around each closed \(k\)-string.
        Must be at least 8 when Wilson loops are evaluated. Defaults to 32.
    n_perp : int, optional
        Number of hybrid-Wannier samples from a TRIM plane's \(k_\perp=0\)
        edge to \(k_\perp=\pi\). Must be at least 5 when Wilson loops are
        evaluated. Defaults to 17.
    parity_tolerance : float, optional
        Maximum relative \([H,I]\) commutator and inversion-eigenvalue
        deviation accepted at a TRIM. Defaults to ``1e-5``.
    kramers_tolerance : float, optional
        Maximum Wannier-center separation allowed when pairing Kramers
        partners on TRIM-plane endpoints. Defaults to ``0.08``.
    gap_tolerance : float, optional
        Warning threshold for the minimum sampled occupied-to-empty direct
        gap. Defaults to ``1e-8``.

    Returns
    -------
    Z2ParityResult or Z2WilsonResult or Z2CombinedResult
        Result mapping. Every method returns:

        - ``"indices"``: \((\nu,)\) in 2-D or \((\nu_0, \nu_1, \nu_2, \nu_3)\)
          in 3-D, as integers in \(\{0,1\}\).
        - ``"method"``: the construction that produced those indices.

        For ``method="parity"`` the mapping also contains Fu--Kane
        ``"parity_products"`` at each TRIM, per-TRIM ``"diagnostics"``
        (including a labeled ``"parity_eigenvalues"``
        [`Tensor`][qten.linalg.tensors.Tensor] of shape
        ``(n_occupied,)``), and ``"direct_gap"``.

        For ``method="wilson"`` it contains hybrid-Wannier ``"planes"`` whose
        ``"wcc"`` (shape ``(n_perp, n_occupied)``), ``"gap_pos"``, and
        ``"sweep"`` values are labeled
        [`Tensor`][qten.linalg.tensors.Tensor] objects, per-axis plane
        invariants ``"axis_z2"``, and ``"min_gap"``.

        For ``method="both"`` it contains both ``"parity"`` and ``"wilson"``
        sub-results; ``"indices"`` follows the parity values.

    Raises
    ------
    TypeError
        If the Hamiltonian or inversion first dimension is not a
        `MomentumSpace` or `MomentumBlockSpace`, or either Hamiltonian
        matrix dimension is not a `HilbertSpace`.
    ValueError
        If ``method`` is unsupported; the input is not a rank-3 square Bloch
        Hamiltonian; ``n_occupied`` is invalid; the momentum space is not
        two- or three-dimensional and periodic with a diagonal cell; a
        `MomentumBlockSpace` contains off-diagonal \((k,k')\) blocks;
        Hamiltonian or inversion momenta do not form a unique complete
        reciprocal quotient; inversion band axes do not span the Hamiltonian
        Hilbert space; or ``n_loop`` / ``n_perp`` are below the Wilson-loop
        minima.
    RuntimeError
        For ``method="parity"`` or ``method="both"``, if inversion cannot be
        constructed or is not resolved at a TRIM.

    Warns
    -----
    RuntimeWarning
        If the sampled minimum direct gap is no larger than ``gap_tolerance``;
        if ``method="auto"`` falls back from parity to Wilson loops; if the
        two 2-D Wilson orientations disagree; if the three 3-D Wilson axes
        disagree on \(\nu_0\) (majority vote is used); if Kramers pairing of
        Wannier centers is unresolved; if parity and Wilson indices disagree;
        or if the sampled spectrum is not time-reversal / Kramers consistent.

    Notes
    -----
    In 2-D, Fu--Kane gives \((-1)^\nu=\prod_i\delta(\Gamma_i)\). In 3-D,
    \((-1)^{\nu_0}=\prod_i\delta(\Gamma_i)\) and
    \((-1)^{\nu_j}=\prod_{k_j=\pi}\delta(\Gamma_i)\). Wilson indices use the
    hybrid-Wannier plane invariants described in the module docstring. Both
    constructions evaluate the Fourier interpolant of the input mesh rather
    than requiring TRIM or Wilson strings to sit on sampled \(k\)-points.

    Examples
    --------
    Use Fu--Kane parities when an inversion tensor is available:

    ```python
    result = z2_indices(hamiltonian, n_occupied=2, inversion=inversion, method="parity")
    indices = result["indices"]
    ```

    Fall back to Wilson loops on a system without inversion:

    ```python
    wilson = z2_indices(hamiltonian, n_occupied=2, method="wilson")
    ```

    See Also
    --------
    [`chern_number`][qten.topology.chern_number]
        First Chern number of a 2-D occupied bundle.
    """
    method_name = str(method).lower()
    if method_name not in {"auto", "parity", "wilson", "both"}:
        raise ValueError("method must be 'auto', 'parity', 'wilson', or 'both'.")

    engine = _build_engine(bloch_hamiltonian, n_occupied, inversion, inversion_center)
    engine.check_time_reversal(stacklevel=2)
    parity_result: Z2ParityResult | None = None
    wilson_result: Z2WilsonResult | None = None

    if method_name in {"parity", "both"}:
        parity_result = engine.run_parity(parity_tolerance)
    elif method_name == "auto":
        try:
            parity_result = engine.run_parity(parity_tolerance)
        except RuntimeError as exc:
            warnings.warn(
                f"Parity method unavailable ({exc}). Falling back to Wilson loops.",
                RuntimeWarning,
                stacklevel=2,
            )

    if (
        method_name == "wilson"
        or method_name == "both"
        or (method_name == "auto" and parity_result is None)
    ):
        wilson_result = engine.run_wilson(int(n_loop), int(n_perp), kramers_tolerance)

    if method_name == "both":
        if parity_result is None or wilson_result is None:
            raise RuntimeError("method='both' requires parity and Wilson results.")
        if parity_result["indices"] != wilson_result["indices"]:
            warnings.warn(
                f"Parity {parity_result['indices']} and Wilson "
                f"{wilson_result['indices']} disagree.",
                RuntimeWarning,
                stacklevel=2,
            )
        chosen: Z2ParityResult | Z2WilsonResult | Z2CombinedResult = {
            "indices": parity_result["indices"],
            "method": "both",
            "parity": parity_result,
            "wilson": wilson_result,
        }
    elif parity_result is not None and wilson_result is None:
        chosen = parity_result
    elif wilson_result is not None:
        chosen = wilson_result
    else:
        raise RuntimeError("Z2 calculation produced no result.")

    if chosen["method"] == "parity":
        min_gap = chosen["direct_gap"]
    elif chosen["method"] == "wilson":
        min_gap = chosen["min_gap"]
    else:
        sampled = [chosen["parity"]["direct_gap"], chosen["wilson"]["min_gap"]]
        finite = [gap for gap in sampled if math.isfinite(gap)]
        min_gap = min(finite) if finite else float("nan")
    if math.isfinite(min_gap) and min_gap <= gap_tolerance:
        warnings.warn(
            f"Minimum sampled direct gap is {min_gap:.6e}; the occupied "
            "bundle is not isolated, so its Z2 indices are not well-defined.",
            RuntimeWarning,
            stacklevel=2,
        )
    return chosen

Exported API

FHSResult

Bases: TypedDict

Result of chern_number(..., method="fhs").

Discrete Fukui--Hatsugai--Suzuki Chern number on a complete 2-D reciprocal mesh. The runtime object is a plain dict.

Attributes:

Name Type Description
chern float

Sum of oriented plaquette phases divided by \(2\pi\).

nearest_integer int

numpy.rint(chern) as a convenience diagnostic, not a proof that the bundle is isolated.

direct_gap float

Minimum occupied-to-empty direct gap over the mesh.

berry_flux Tensor

Plaquette phase in radians as a labeled Tensor with dims (MomentumSpace,) and shape (N_k,). Momentum \(k\) labels the plaquette anchored at \(k\); the order matches the input Hamiltonian momentum space.

See Also

chern_number Public constructor of this mapping.

chern instance-attribute

chern: float

nearest_integer instance-attribute

nearest_integer: int

direct_gap instance-attribute

direct_gap: float

berry_flux instance-attribute

berry_flux: Tensor

QGTResult

Bases: TypedDict

Result of chern_number(..., method="qgt").

Chern number from integrated projector Berry curvature, plus the local quantum-geometric tensors. The runtime object is a plain dict.

Attributes:

Name Type Description
chern float

\((2\pi)^{-1}\sum_k\Omega_{xy}(k)\) from central finite differences. Approaches an integer only as the mesh is refined.

nearest_integer int

numpy.rint(chern) as a convenience diagnostic.

direct_gap float

Minimum occupied-to-empty direct gap over the mesh.

quantum_geometric_tensor Tensor

Complex QGT with dims (MomentumSpace, IndexSpace(2), IndexSpace(2)) and shape (N_k, 2, 2). Components are per reciprocal-grid step.

fubini_study_metric Tensor

Real part of quantum_geometric_tensor, same dims and shape.

berry_curvature Tensor

\(\Omega_{ij}=2\operatorname{Im}Q_{ij}\), same dims and shape. The \(xy\) orientation matches the FHS plaquette.

See Also

quantum_geometric_tensor Standalone QGT used to build this mapping. chern_number Public constructor of this mapping.

chern instance-attribute

chern: float

nearest_integer instance-attribute

nearest_integer: int

direct_gap instance-attribute

direct_gap: float

quantum_geometric_tensor instance-attribute

quantum_geometric_tensor: Tensor

fubini_study_metric instance-attribute

fubini_study_metric: Tensor

berry_curvature instance-attribute

berry_curvature: Tensor

berry_curvature

berry_curvature(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
) -> Tensor

Compute occupied-subspace Berry curvature on a 1-D, 2-D, or 3-D grid.

QTen uses \(\Omega_{ij}(k)=2\operatorname{Im}Q_{ij}(k)\). In two dimensions, the \(xy\) orientation agrees with chern_number(..., method="fhs").

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace).

required
n_occupied int | None

Number of lowest-energy occupied bands. Defaults to half the bands.

None
gap_tolerance float

Direct-gap warning threshold. Defaults to 1e-8.

1e-08

Returns:

Type Description
Tensor

Pointwise real antisymmetric curvature tensor at every momentum, with dims (MomentumSpace, IndexSpace(d), IndexSpace(d)) and shape (N_k, d, d). In two dimensions, summing curvature.data[..., 0, 1] and dividing by 2*pi gives the central-finite-difference estimate of the first Chern number.

Raises:

Type Description
TypeError

If the first tensor dimension is not a momentum space.

ValueError

If the Hamiltonian, occupied-band selection, or reciprocal grid is invalid. See quantum_geometric_tensor for the complete validation contract.

Notes

This is QGT-derived curvature, not the compact plaquette flux returned by chern_number(..., method="fhs"). Its integral need not be exactly quantized on a finite grid.

See Also

quantum_geometric_tensor Complex tensor from which the curvature is derived. chern_number FHS or curvature-integral Chern number.

Source code in src/qten/topology/chern.py
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
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
def berry_curvature(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-8,
) -> Tensor:
    r"""Compute occupied-subspace Berry curvature on a 1-D, 2-D, or 3-D grid.

    QTen uses \(\Omega_{ij}(k)=2\operatorname{Im}Q_{ij}(k)\). In two dimensions,
    the \(xy\) orientation agrees with
    [`chern_number(..., method="fhs")`][qten.topology.chern_number].

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``.
    n_occupied : int | None, optional
        Number of lowest-energy occupied bands. Defaults to half the bands.
    gap_tolerance : float, optional
        Direct-gap warning threshold. Defaults to ``1e-8``.

    Returns
    -------
    Tensor
        Pointwise real antisymmetric curvature tensor at every momentum, with dims
        ``(MomentumSpace, IndexSpace(d), IndexSpace(d))`` and shape
        ``(N_k, d, d)``. In two dimensions, summing
        ``curvature.data[..., 0, 1]`` and dividing by ``2*pi`` gives the
        central-finite-difference estimate of the first Chern number.

    Raises
    ------
    TypeError
        If the first tensor dimension is not a momentum space.
    ValueError
        If the Hamiltonian, occupied-band selection, or reciprocal grid is
        invalid. See
        [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        for the complete validation contract.

    Notes
    -----
    This is QGT-derived curvature, not the compact plaquette flux returned by
    ``chern_number(..., method="fhs")``. Its integral need not be exactly
    quantized on a finite grid.

    See Also
    --------
    [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        Complex tensor from which the curvature is derived.
    [`chern_number`][qten.topology.chern_number]
        FHS or curvature-integral Chern number.
    """
    qgt = quantum_geometric_tensor(bloch_hamiltonian, n_occupied, gap_tolerance)
    return 2.0 * qgt.imag()

chern_number

chern_number(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
    *,
    method: Literal["fhs"] = "fhs",
) -> FHSResult
chern_number(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
    *,
    method: Literal["qgt"],
) -> QGTResult

Compute the first Chern number of an occupied band subspace.

The n_occupied lowest-energy eigenstates define an occupied bundle over a complete two-dimensional periodic momentum grid. Two numerical methods are available:

  • method="fhs" computes normalized determinant link variables between neighboring occupied subspaces and sums their oriented plaquette phases. This gauge-invariant Fukui--Hatsugai--Suzuki construction is the default and the recommended finite-grid topological invariant.
  • method="qgt" computes the projector quantum geometric tensor, takes \(\Omega_{xy}=2\operatorname{Im}Q_{xy}\), and evaluates \(C=(2\pi)^{-1}\sum_k\Omega_{xy}(k)\). It additionally returns all local quantum-geometric data.

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace). The first data axis enumerates a complete two-dimensional reciprocal quotient; the last two axes are square Bloch-Hamiltonian matrices and are aligned onto a common Hilbert space.

required
n_occupied int | None

Number of lowest-energy bands defining the occupied subspace. It must lie strictly between zero and the total band count. Defaults to half the bands using integer division.

None
gap_tolerance float

Warning threshold for the minimum direct gap \(\min_k[E_{n_\mathrm{occupied}}(k)- E_{n_\mathrm{occupied}-1}(k)]\). A gap at or below this value emits a RuntimeWarning, because the occupied bundle is not isolated and its Chern number is not well-defined. Defaults to 1e-8.

1e-08
method (fhs, qgt)

Numerical construction. "fhs" uses discrete determinant link variables and is robustly quantized on a suitable finite mesh. "qgt" integrates central-finite-difference curvature and exposes local quantum geometry. Defaults to "fhs".

"fhs"

Returns:

Type Description
dict[str, Any]

Result mapping. Both methods return:

  • "chern": raw floating-point Chern value.
  • "nearest_integer": nearest integer obtained with numpy.rint.
  • "direct_gap": minimum occupied-to-empty direct gap.

For method="fhs" the mapping also contains "berry_flux", the oriented plaquette phase in radians as a labeled Tensor with dims (MomentumSpace,) and shape (N_k,). Momentum \(k\) labels the plaquette anchored at \(k\), and the order matches the input MomentumSpace.

For method="qgt" the mapping instead contains labeled Tensor values: "quantum_geometric_tensor" and "fubini_study_metric" and "berry_curvature" all have shape (N_k, 2, 2). Each retains the input momentum space as its first dimension.

Raises:

Type Description
TypeError

If the first tensor dimension is not a MomentumSpace, or either matrix dimension is not a HilbertSpace.

ValueError

If method is unsupported; the input is not a rank-3 square Bloch Hamiltonian; n_occupied is outside the valid range; the momentum space is not two-dimensional and periodic; or its points do not form a unique complete reciprocal quotient.

RuntimeError

For method="fhs", if a neighboring occupied-subspace overlap has determinant magnitude below 1e-14. This indicates a singular link; increasing the momentum-grid resolution may resolve it.

Warns:

Type Description
RuntimeWarning

If the minimum direct gap is no larger than gap_tolerance.

Notes

The FHS value satisfies

\[ C_\mathrm{FHS} = \frac{1}{2\pi} \sum_k \operatorname{Arg}\!\left[ U_x(k)U_y(k+e_x)U_x(k+e_y)^*U_y(k)^* \right], \]

where \(U_i(k)\) is the phase of the determinant of the occupied-subspace overlap between \(k\) and \(k+e_i\). Determinants make the formula invariant under arbitrary unitary changes of occupied-band basis.

nearest_integer is a convenience diagnostic, not proof that the bundle is isolated or the mesh is sufficiently resolved. Inspect direct_gap and, when necessary, repeat the calculation on finer momentum grids.

The flux tensor is intentionally flat for every cell: its symbolic MomentumSpace dimension preserves labels without implying rectangular heatmap adjacency. This is especially important for sheared cells, whose quotient-representative order is not a rectangular Brillouin-zone heatmap.

Examples:

Use the robust finite-grid method:

result = chern_number(hamiltonian, n_occupied=1)
invariant = result["nearest_integer"]
flux = result["berry_flux"]

Request the differential-geometric decomposition:

geometry = chern_number(hamiltonian, n_occupied=1, method="qgt")
metric = geometry["fubini_study_metric"]
curvature = geometry["berry_curvature"]
See Also

quantum_geometric_tensor Gauge-invariant local quantum geometric tensor. fubini_study_metric Metric part of the QGT. berry_curvature Curvature part of the QGT.

Source code in src/qten/topology/chern.py
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
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
631
632
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
def chern_number(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-8,
    *,
    method: Literal["fhs", "qgt"] = "fhs",
) -> FHSResult | QGTResult:
    r"""Compute the first Chern number of an occupied band subspace.

    The ``n_occupied`` lowest-energy eigenstates define an occupied bundle over
    a complete two-dimensional periodic momentum grid. Two numerical methods
    are available:

    - ``method="fhs"`` computes normalized determinant link variables between
      neighboring occupied subspaces and sums their oriented plaquette phases.
      This gauge-invariant Fukui--Hatsugai--Suzuki construction is the default
      and the recommended finite-grid topological invariant.
    - ``method="qgt"`` computes the projector quantum geometric tensor, takes
      \(\Omega_{xy}=2\operatorname{Im}Q_{xy}\), and evaluates
      \(C=(2\pi)^{-1}\sum_k\Omega_{xy}(k)\). It additionally returns all local
      quantum-geometric data.

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``. The first data axis
        enumerates a complete two-dimensional reciprocal quotient; the last
        two axes are square Bloch-Hamiltonian matrices and are aligned onto
        a common Hilbert space.
    n_occupied : int | None, optional
        Number of lowest-energy bands defining the occupied subspace. It must
        lie strictly between zero and the total band count. Defaults to half
        the bands using integer division.
    gap_tolerance : float, optional
        Warning threshold for the minimum direct gap
        \(\min_k[E_{n_\mathrm{occupied}}(k)-
        E_{n_\mathrm{occupied}-1}(k)]\). A gap at or below this value emits a
        `RuntimeWarning`, because the occupied bundle is not
        isolated and its Chern number is not well-defined. Defaults to
        ``1e-8``.
    method : {"fhs", "qgt"}, optional
        Numerical construction. ``"fhs"`` uses discrete determinant link
        variables and is robustly quantized on a suitable finite mesh.
        ``"qgt"`` integrates central-finite-difference curvature and exposes
        local quantum geometry. Defaults to ``"fhs"``.

    Returns
    -------
    dict[str, Any]
        Result mapping. Both methods return:

        - ``"chern"``: raw floating-point Chern value.
        - ``"nearest_integer"``: nearest integer obtained with `numpy.rint`.
        - ``"direct_gap"``: minimum occupied-to-empty direct gap.

        For ``method="fhs"`` the mapping also contains ``"berry_flux"``, the
        oriented plaquette phase in radians as a labeled `Tensor` with dims
        ``(MomentumSpace,)`` and shape ``(N_k,)``. Momentum \(k\) labels the
        plaquette anchored at \(k\), and the order matches the input
        `MomentumSpace`.

        For ``method="qgt"`` the mapping instead contains labeled `Tensor`
        values: ``"quantum_geometric_tensor"`` and
        ``"fubini_study_metric"`` and ``"berry_curvature"`` all have shape
        ``(N_k, 2, 2)``. Each retains the input momentum space as its first
        dimension.

    Raises
    ------
    TypeError
        If the first tensor dimension is not a
        [`MomentumSpace`][qten.symbolics.state_space.MomentumSpace], or either
        matrix dimension is not a `HilbertSpace`.
    ValueError
        If ``method`` is unsupported; the input is not a rank-3 square Bloch
        Hamiltonian; ``n_occupied`` is outside the valid range; the momentum
        space is not two-dimensional and periodic; or its points do not form a
        unique complete reciprocal quotient.
    RuntimeError
        For ``method="fhs"``, if a neighboring occupied-subspace overlap has
        determinant magnitude below ``1e-14``. This indicates a singular link;
        increasing the momentum-grid resolution may resolve it.

    Warns
    -----
    RuntimeWarning
        If the minimum direct gap is no larger than ``gap_tolerance``.

    Notes
    -----
    The FHS value satisfies

    \[
    C_\mathrm{FHS} = \frac{1}{2\pi}
    \sum_k \operatorname{Arg}\!\left[
      U_x(k)U_y(k+e_x)U_x(k+e_y)^*U_y(k)^*
    \right],
    \]

    where \(U_i(k)\) is the phase of the determinant of the occupied-subspace
    overlap between \(k\) and \(k+e_i\). Determinants make the formula
    invariant under arbitrary unitary changes of occupied-band basis.

    ``nearest_integer`` is a convenience diagnostic, not proof that the bundle
    is isolated or the mesh is sufficiently resolved. Inspect ``direct_gap``
    and, when necessary, repeat the calculation on finer momentum grids.

    The flux tensor is intentionally flat for every cell: its symbolic
    `MomentumSpace` dimension preserves labels without implying rectangular
    heatmap adjacency. This is especially important for sheared cells, whose
    quotient-representative order is not a rectangular Brillouin-zone heatmap.

    Examples
    --------
    Use the robust finite-grid method:

    ```python
    result = chern_number(hamiltonian, n_occupied=1)
    invariant = result["nearest_integer"]
    flux = result["berry_flux"]
    ```

    Request the differential-geometric decomposition:

    ```python
    geometry = chern_number(hamiltonian, n_occupied=1, method="qgt")
    metric = geometry["fubini_study_metric"]
    curvature = geometry["berry_curvature"]
    ```

    See Also
    --------
    [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        Gauge-invariant local quantum geometric tensor.
    [`fubini_study_metric`][qten.topology.fubini_study_metric]
        Metric part of the QGT.
    [`berry_curvature`][qten.topology.berry_curvature]
        Curvature part of the QGT.
    """
    if method == "fhs":
        return _discrete_chern_number(bloch_hamiltonian, n_occupied, gap_tolerance)
    if method != "qgt":
        raise ValueError("method must be 'fhs' or 'qgt'.")

    grid = _topology_grid(bloch_hamiltonian, n_occupied, gap_tolerance)
    if grid.momentum_dim != 2:
        raise ValueError("The first Chern number requires a two-dimensional grid.")
    qgt = _quantum_geometric_tensor_from_grid(grid)
    curvature = 2.0 * qgt.imag()
    chern = float(curvature.data[..., 0, 1].sum() / (2.0 * np.pi))
    return {
        "chern": chern,
        "nearest_integer": int(np.rint(chern)),
        "direct_gap": grid.direct_gap,
        "quantum_geometric_tensor": qgt,
        "fubini_study_metric": qgt.real(),
        "berry_curvature": curvature,
    }

fubini_study_metric

fubini_study_metric(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
) -> Tensor

Compute the occupied-subspace Fubini--Study metric on a 1-D, 2-D, or 3-D grid.

This function returns \(g_{ij}(k)=\operatorname{Re}Q_{ij}(k)\), where the QGT is computed by quantum_geometric_tensor.

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace).

required
n_occupied int | None

Number of lowest-energy occupied bands. Defaults to half the bands.

None
gap_tolerance float

Direct-gap warning threshold. Defaults to 1e-8.

1e-08

Returns:

Type Description
Tensor

Real metric with dims (MomentumSpace, IndexSpace(d), IndexSpace(d)) and shape (N_k, d, d). Components are expressed per reciprocal-grid step.

Raises:

Type Description
TypeError

If the first tensor dimension is not a momentum space.

ValueError

If the Hamiltonian, occupied-band selection, or reciprocal grid is invalid. See quantum_geometric_tensor for the complete validation contract.

See Also

quantum_geometric_tensor Complex parent tensor of the metric and curvature. berry_curvature Berry curvature from the imaginary part of the QGT.

Source code in src/qten/topology/chern.py
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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def fubini_study_metric(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-8,
) -> Tensor:
    r"""Compute the occupied-subspace Fubini--Study metric on a 1-D, 2-D,
    or 3-D grid.

    This function returns \(g_{ij}(k)=\operatorname{Re}Q_{ij}(k)\), where the
    QGT is computed by
    [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor].

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``.
    n_occupied : int | None, optional
        Number of lowest-energy occupied bands. Defaults to half the bands.
    gap_tolerance : float, optional
        Direct-gap warning threshold. Defaults to ``1e-8``.

    Returns
    -------
    Tensor
        Real metric with dims ``(MomentumSpace, IndexSpace(d),
        IndexSpace(d))`` and shape ``(N_k, d, d)``. Components are expressed
        per reciprocal-grid step.

    Raises
    ------
    TypeError
        If the first tensor dimension is not a momentum space.
    ValueError
        If the Hamiltonian, occupied-band selection, or reciprocal grid is
        invalid. See
        [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        for the complete validation contract.

    See Also
    --------
    [`quantum_geometric_tensor`][qten.topology.quantum_geometric_tensor]
        Complex parent tensor of the metric and curvature.
    [`berry_curvature`][qten.topology.berry_curvature]
        Berry curvature from the imaginary part of the QGT.
    """
    return quantum_geometric_tensor(bloch_hamiltonian, n_occupied, gap_tolerance).real()

quantum_geometric_tensor

quantum_geometric_tensor(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-08,
) -> Tensor

Compute the occupied-subspace quantum geometric tensor on a 1-D, 2-D, or 3-D grid.

The occupied projector is built from the n_occupied lowest-energy eigenvectors at every momentum. Central differences along every primitive reciprocal-grid direction approximate \(\partial_iP\), after which \(Q_{ij}=\operatorname{Tr}[P(\partial_iP)(\partial_jP)]\) is evaluated.

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace). The final two data axes must be square Hilbert-space matrices at each momentum and are aligned onto a common Hilbert space.

required
n_occupied int | None

Number of lowest-energy bands included in the occupied projector. Defaults to half the Hamiltonian bands using integer division.

None
gap_tolerance float

Minimum acceptable direct gap between bands n_occupied - 1 and n_occupied. A gap at or below this value emits a RuntimeWarning, because the selected bundle is not numerically isolated. Defaults to 1e-8.

1e-08

Returns:

Type Description
Tensor

Complex QGT with dims (MomentumSpace, IndexSpace(d), IndexSpace(d)) and shape (N_k, d, d), where d is the momentum-space dimension. The first dimension is the input Hamiltonian's momentum space, so momentum labels and their ordering are preserved. Components are measured per reciprocal-grid step, not per Cartesian inverse-length unit.

Raises:

Type Description
TypeError

If the first tensor dimension is not a MomentumSpace, or either matrix dimension is not a HilbertSpace.

ValueError

If the tensor is not rank 3, its Hamiltonian blocks are not square, n_occupied is invalid, the momentum space is not 1-D, 2-D, or 3-D, the boundary is not periodic, or momentum points do not form a unique complete reciprocal quotient.

Notes

The projector formulation is invariant under arbitrary momentum-dependent unitary rotations within the occupied subspace. It therefore remains well-defined when occupied bands cross each other, provided the occupied subspace stays separated from the empty bands.

See Also

fubini_study_metric Real part of this tensor. berry_curvature Imaginary antisymmetric part of this tensor. chern_number Brillouin-zone topological invariant.

Source code in src/qten/topology/chern.py
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
def quantum_geometric_tensor(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    gap_tolerance: float = 1e-8,
) -> Tensor:
    r"""Compute the occupied-subspace quantum geometric tensor on a 1-D, 2-D,
    or 3-D grid.

    The occupied projector is built from the ``n_occupied`` lowest-energy
    eigenvectors at every momentum. Central differences along every primitive
    reciprocal-grid direction approximate \(\partial_iP\), after which
    \(Q_{ij}=\operatorname{Tr}[P(\partial_iP)(\partial_jP)]\) is evaluated.

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``. The final two data
        axes must be square Hilbert-space matrices at each momentum and are
        aligned onto a common Hilbert space.
    n_occupied : int | None, optional
        Number of lowest-energy bands included in the occupied projector.
        Defaults to half the Hamiltonian bands using integer division.
    gap_tolerance : float, optional
        Minimum acceptable direct gap between bands ``n_occupied - 1`` and
        ``n_occupied``. A gap at or below this value emits a
        `RuntimeWarning`, because the selected bundle is not
        numerically isolated. Defaults to ``1e-8``.

    Returns
    -------
    Tensor
        Complex QGT with dims ``(MomentumSpace, IndexSpace(d),
        IndexSpace(d))`` and shape ``(N_k, d, d)``, where ``d`` is the
        momentum-space dimension. The first dimension is the input
        Hamiltonian's momentum space, so momentum labels and their ordering
        are preserved. Components are measured per reciprocal-grid step, not
        per Cartesian inverse-length unit.

    Raises
    ------
    TypeError
        If the first tensor dimension is not a
        [`MomentumSpace`][qten.symbolics.state_space.MomentumSpace], or either
        matrix dimension is not a `HilbertSpace`.
    ValueError
        If the tensor is not rank 3, its Hamiltonian blocks are not square,
        ``n_occupied`` is invalid, the momentum space is not 1-D, 2-D, or 3-D,
        the boundary is not periodic, or momentum points do not form a unique
        complete reciprocal quotient.

    Notes
    -----
    The projector formulation is invariant under arbitrary momentum-dependent
    unitary rotations within the occupied subspace. It therefore remains
    well-defined when occupied bands cross each other, provided the occupied
    subspace stays separated from the empty bands.

    See Also
    --------
    [`fubini_study_metric`][qten.topology.fubini_study_metric]
        Real part of this tensor.
    [`berry_curvature`][qten.topology.berry_curvature]
        Imaginary antisymmetric part of this tensor.
    [`chern_number`][qten.topology.chern_number]
        Brillouin-zone topological invariant.
    """
    grid = _topology_grid(bloch_hamiltonian, n_occupied, gap_tolerance)
    return _quantum_geometric_tensor_from_grid(grid)

Z2CombinedResult

Bases: TypedDict

Result of z2_indices(..., method="both").

Both constructions are run. indices follows the Fu--Kane parity values; a mismatch with Wilson emits a RuntimeWarning.

Attributes:

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

Copy of parity["indices"].

method {'both'}

Construction tag for this combined mapping.

parity Z2ParityResult

Full Fu--Kane Z2ParityResult.

wilson Z2WilsonResult

Full hybrid-Wannier Z2WilsonResult.

See Also

z2_indices Public constructor of this mapping.

indices instance-attribute

indices: tuple[int, ...]

method instance-attribute

method: Literal['both']

parity instance-attribute

parity: Z2ParityResult

wilson instance-attribute

wilson: Z2WilsonResult

Z2ParityResult

Bases: TypedDict

Result of z2_indices(..., method="parity").

Fu--Kane indices from inversion eigenvalues at the \(2^d\) TRIM. The runtime object is a plain dict; keys below are required.

Attributes:

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

\(\mathbb{Z}_2\) indices as integers in \(\{0,1\}\). Length 1 in two dimensions, \((\nu,)\). Length 4 in three dimensions, \((\nu_0, \nu_1, \nu_2, \nu_3)\).

method {'parity'}

Construction that produced indices.

parity_products dict[tuple[int, ...], int]

TRIM bit-tuple \(n\) to \(\delta(\Gamma_n)=\pm 1\). Each key has one 0/1 entry per spatial axis, with \(k_j=n_j/2\).

diagnostics dict[tuple[int, ...], Z2ParityTrimDiagnostics]

Per-TRIM Z2ParityTrimDiagnostics with the same keys as parity_products.

direct_gap float

Minimum finite occupied-to-empty gap over the TRIM. nan if none of those gaps are finite.

See Also

z2_indices Public constructor of this mapping.

indices instance-attribute

indices: tuple[int, ...]

method instance-attribute

method: Literal['parity']

parity_products instance-attribute

parity_products: dict[tuple[int, ...], int]

diagnostics instance-attribute

diagnostics: dict[tuple[int, ...], Z2ParityTrimDiagnostics]

direct_gap instance-attribute

direct_gap: float

Z2ParityTrimDiagnostics

Bases: TypedDict

Inversion-parity diagnostics at one time-reversal invariant momentum.

This mapping is one value in Z2ParityResult "diagnostics". TRIM are labeled by bits \(n\in\{0,1\}^d\) with \(k=n/2\).

Attributes:

Name Type Description
delta int

Fu--Kane pair-parity product \(\delta(\Gamma)=\pm 1\). Equal to \((-1)^{N_-/2}\), where \(N_-\) is the number of occupied negative inversion eigenvalues.

parity_eigenvalues Tensor

Occupied inversion eigenvalues \(\xi_n(\Gamma)\) as a labeled Tensor with dims (IndexSpace(n_occupied),) and shape (n_occupied,).

commutator_error float

Relative residual \(\|HI-IH\|/\|H\|\) at this TRIM. Large values mean the supplied or assembled inversion does not commute with \(H(\Gamma)\).

direct_gap float

Occupied-to-empty direct gap at this TRIM. nan if the occupied count leaves no empty band.

delta instance-attribute

delta: int

parity_eigenvalues instance-attribute

parity_eigenvalues: Tensor

commutator_error instance-attribute

commutator_error: float

direct_gap instance-attribute

direct_gap: float

Z2WilsonPlaneResult

Bases: TypedDict

Hybrid-Wannier data on one Wilson-loop plane.

In two dimensions this is one loop orientation over the Brillouin zone. In three dimensions it is one TRIM plane \(k_{\mathrm{normal}}=0\) or \(1/2\).

Attributes:

Name Type Description
z2 int

Plane \(\mathbb{Z}_2\) invariant in \(\{0,1\}\), from the Soluyanov--Vanderbilt largest-gap crossing count of the Wannier centers.

wcc Tensor

Hybrid Wannier charge centers \(\bar x_n(k_\perp)\in[0,1)\) as a labeled Tensor with dims (IndexSpace(n_perp), IndexSpace(n_occupied)) and shape (n_perp, n_occupied). The first axis follows sweep.

gap_pos Tensor

Largest-gap position on the Wannier circle at each sweep sample, as a labeled tensor with dims (IndexSpace(n_perp),).

sweep Tensor

Fractional \(k_\perp\) samples from \(0\) to \(1/2\), labeled by the same IndexSpace(n_perp) as wcc and gap_pos.

min_gap float

Minimum occupied-to-empty direct gap along the Wilson strings on this plane. nan if no finite gap is available.

kramers_resolved bool

Whether Wannier centers at the TRIM-plane endpoints pair into Kramers partners within kramers_tolerance.

z2 instance-attribute

z2: int

wcc instance-attribute

wcc: Tensor

gap_pos instance-attribute

gap_pos: Tensor

sweep instance-attribute

sweep: Tensor

min_gap instance-attribute

min_gap: float

kramers_resolved instance-attribute

kramers_resolved: bool

Z2WilsonResult

Bases: TypedDict

Result of z2_indices(..., method="wilson").

Hybrid-Wannier \(\mathbb{Z}_2\) indices. The runtime object is a plain dict; keys below are required.

Attributes:

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

Same layout as Z2ParityResult.indices: \((\nu,)\) in 2-D or \((\nu_0, \nu_1, \nu_2, \nu_3)\) in 3-D.

method {'wilson'}

Construction that produced indices.

planes dict[tuple[int, float], Z2WilsonPlaneResult]

Plane-resolved hybrid-Wannier data. In 2-D the key is (loop_axis, 0.0) for each loop orientation. In 3-D the key is (normal, trim) with trim in {0.0, 0.5}.

axis_z2 tuple[tuple[int, ...], ...]

Per-axis plane invariants. In 2-D each entry is (ν,) for one loop orientation. In 3-D each entry is (ν(k_j=0), ν(k_j=π)).

min_gap float

Minimum min_gap over planes. nan if none are finite.

See Also

Z2WilsonPlaneResult Value type stored in planes. z2_indices Public constructor of this mapping.

indices instance-attribute

indices: tuple[int, ...]

method instance-attribute

method: Literal['wilson']

planes instance-attribute

planes: dict[tuple[int, float], Z2WilsonPlaneResult]

axis_z2 instance-attribute

axis_z2: tuple[tuple[int, ...], ...]

min_gap instance-attribute

min_gap: float

z2_indices

z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["auto"] = "auto",
    inversion: Tensor | None = None,
    inversion_center: Offset
    | Sequence[float]
    | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-05,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-08,
) -> Z2ParityResult | Z2WilsonResult
z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["parity"],
    inversion: Tensor | None = None,
    inversion_center: Offset
    | Sequence[float]
    | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-05,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-08,
) -> Z2ParityResult
z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["wilson"],
    inversion: Tensor | None = None,
    inversion_center: Offset
    | Sequence[float]
    | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-05,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-08,
) -> Z2WilsonResult
z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["both"],
    inversion: Tensor | None = None,
    inversion_center: Offset
    | Sequence[float]
    | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-05,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-08,
) -> Z2CombinedResult

Compute the 2-D or 3-D \(\mathbb{Z}_2\) indices of an occupied band subspace.

The n_occupied lowest-energy eigenstates, which must form an even number of Kramers pairs, define an occupied bundle over a complete two- or three-dimensional periodic momentum grid. Two numerical methods are available:

  • method="parity" evaluates Fu--Kane inversion eigenvalues at the \(2^d\) TRIM. Two dimensions return \((\nu,)\); three dimensions return \((\nu_0; \nu_1\nu_2\nu_3)\).
  • method="wilson" computes hybrid Wannier charge centers. In 2-D the two loop orientations should agree on \(\nu\). In 3-D the strong index is \(\nu_0=\nu(k_i=0)+\nu(k_i=\pi)\bmod 2\) and the weak indices are the three \(k_i=\pi\) plane invariants. If the three axes disagree on \(\nu_0\), the majority vote is returned.

The Hamiltonian is Fourier-interpolated from the supplied mesh, so TRIM and Wilson strings need not coincide with sampled \(k\)-points. This is the construction used for odd meshes such as \(27^3\) or \(9^2\). The periodic cell must be diagonal in the primitive basis.

Parameters:

Name Type Description Default
bloch_hamiltonian Tensor

Rank-3 Hermitian Tensor with dims (MomentumSpace, HilbertSpace, HilbertSpace), or with a MomentumBlockSpace of diagonal \((k,k)\) blocks whose momenta form a complete 2-D or 3-D reciprocal quotient. The last two axes are square Bloch-Hamiltonian matrices and are aligned onto a common Hilbert space.

required
n_occupied int | None

Number of lowest-energy bands defining the occupied subspace. It must be even and lie strictly between zero and the total band count. Defaults to half the bands using integer division.

None
method ('auto', 'parity', 'wilson', 'both')

Numerical construction. "auto" tries Fu--Kane parity and falls back to Wilson loops if inversion cannot be resolved (RuntimeError). "both" requires parity to succeed. Defaults to "auto".

"auto"
inversion Tensor | None

Optional rank-3 inversion operator with dims (MomentumSpace, HilbertSpace, HilbertSpace), or with a diagonal MomentumBlockSpace of \((k,k)\) blocks. Band axes are aligned onto the Hamiltonian Hilbert space. Each momentum is paired with \(H(k)\) by its label, not by data-axis order, and the labeled points must form the same complete reciprocal quotient. If omitted, spatial inversion is assembled from orbital Offset labels about inversion_center.

None
inversion_center Offset | Sequence[float] | None

Fixed point of spatial inversion, as an Offset or a \(d\)-vector in the Hamiltonian's direct-lattice coordinates. Defaults to the centroid of the unique orbital offsets.

None
n_loop int

Number of Wilson-loop samples around each closed \(k\)-string. Must be at least 8 when Wilson loops are evaluated. Defaults to 32.

32
n_perp int

Number of hybrid-Wannier samples from a TRIM plane's \(k_\perp=0\) edge to \(k_\perp=\pi\). Must be at least 5 when Wilson loops are evaluated. Defaults to 17.

17
parity_tolerance float

Maximum relative \([H,I]\) commutator and inversion-eigenvalue deviation accepted at a TRIM. Defaults to 1e-5.

1e-05
kramers_tolerance float

Maximum Wannier-center separation allowed when pairing Kramers partners on TRIM-plane endpoints. Defaults to 0.08.

0.08
gap_tolerance float

Warning threshold for the minimum sampled occupied-to-empty direct gap. Defaults to 1e-8.

1e-08

Returns:

Type Description
Z2ParityResult or Z2WilsonResult or Z2CombinedResult

Result mapping. Every method returns:

  • "indices": \((\nu,)\) in 2-D or \((\nu_0, \nu_1, \nu_2, \nu_3)\) in 3-D, as integers in \(\{0,1\}\).
  • "method": the construction that produced those indices.

For method="parity" the mapping also contains Fu--Kane "parity_products" at each TRIM, per-TRIM "diagnostics" (including a labeled "parity_eigenvalues" Tensor of shape (n_occupied,)), and "direct_gap".

For method="wilson" it contains hybrid-Wannier "planes" whose "wcc" (shape (n_perp, n_occupied)), "gap_pos", and "sweep" values are labeled Tensor objects, per-axis plane invariants "axis_z2", and "min_gap".

For method="both" it contains both "parity" and "wilson" sub-results; "indices" follows the parity values.

Raises:

Type Description
TypeError

If the Hamiltonian or inversion first dimension is not a MomentumSpace or MomentumBlockSpace, or either Hamiltonian matrix dimension is not a HilbertSpace.

ValueError

If method is unsupported; the input is not a rank-3 square Bloch Hamiltonian; n_occupied is invalid; the momentum space is not two- or three-dimensional and periodic with a diagonal cell; a MomentumBlockSpace contains off-diagonal \((k,k')\) blocks; Hamiltonian or inversion momenta do not form a unique complete reciprocal quotient; inversion band axes do not span the Hamiltonian Hilbert space; or n_loop / n_perp are below the Wilson-loop minima.

RuntimeError

For method="parity" or method="both", if inversion cannot be constructed or is not resolved at a TRIM.

Warns:

Type Description
RuntimeWarning

If the sampled minimum direct gap is no larger than gap_tolerance; if method="auto" falls back from parity to Wilson loops; if the two 2-D Wilson orientations disagree; if the three 3-D Wilson axes disagree on \(\nu_0\) (majority vote is used); if Kramers pairing of Wannier centers is unresolved; if parity and Wilson indices disagree; or if the sampled spectrum is not time-reversal / Kramers consistent.

Notes

In 2-D, Fu--Kane gives \((-1)^\nu=\prod_i\delta(\Gamma_i)\). In 3-D, \((-1)^{\nu_0}=\prod_i\delta(\Gamma_i)\) and \((-1)^{\nu_j}=\prod_{k_j=\pi}\delta(\Gamma_i)\). Wilson indices use the hybrid-Wannier plane invariants described in the module docstring. Both constructions evaluate the Fourier interpolant of the input mesh rather than requiring TRIM or Wilson strings to sit on sampled \(k\)-points.

Examples:

Use Fu--Kane parities when an inversion tensor is available:

result = z2_indices(hamiltonian, n_occupied=2, inversion=inversion, method="parity")
indices = result["indices"]

Fall back to Wilson loops on a system without inversion:

wilson = z2_indices(hamiltonian, n_occupied=2, method="wilson")
See Also

chern_number First Chern number of a 2-D occupied bundle.

Source code in src/qten/topology/z2.py
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
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
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
def z2_indices(
    bloch_hamiltonian: Tensor,
    n_occupied: int | None = None,
    *,
    method: Literal["auto", "parity", "wilson", "both"] = "auto",
    inversion: Tensor | None = None,
    inversion_center: Offset | Sequence[float] | None = None,
    n_loop: int = 32,
    n_perp: int = 17,
    parity_tolerance: float = 1e-5,
    kramers_tolerance: float = 0.08,
    gap_tolerance: float = 1e-8,
) -> Z2ParityResult | Z2WilsonResult | Z2CombinedResult:
    r"""Compute the 2-D or 3-D \(\mathbb{Z}_2\) indices of an occupied band
    subspace.

    The ``n_occupied`` lowest-energy eigenstates, which must form an even
    number of Kramers pairs, define an occupied bundle over a complete
    two- or three-dimensional periodic momentum grid. Two numerical methods
    are available:

    - ``method="parity"`` evaluates Fu--Kane inversion eigenvalues at the
      \(2^d\) TRIM. Two dimensions return \((\nu,)\); three dimensions return
      \((\nu_0; \nu_1\nu_2\nu_3)\).
    - ``method="wilson"`` computes hybrid Wannier charge centers. In 2-D the
      two loop orientations should agree on \(\nu\). In 3-D the strong index
      is \(\nu_0=\nu(k_i=0)+\nu(k_i=\pi)\bmod 2\) and the weak indices are the
      three \(k_i=\pi\) plane invariants. If the three axes disagree on
      \(\nu_0\), the majority vote is returned.

    The Hamiltonian is Fourier-interpolated from the supplied mesh, so TRIM
    and Wilson strings need not coincide with sampled \(k\)-points. This is
    the construction used for odd meshes such as \(27^3\) or \(9^2\). The
    periodic cell must be diagonal in the primitive basis.

    Parameters
    ----------
    bloch_hamiltonian : Tensor
        Rank-3 Hermitian [`Tensor`][qten.linalg.tensors.Tensor] with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``, or with a
        [`MomentumBlockSpace`][qten.symbolics.state_space.MomentumBlockSpace]
        of diagonal \((k,k)\) blocks whose momenta form a complete 2-D or 3-D
        reciprocal quotient. The last two axes are square Bloch-Hamiltonian
        matrices and are aligned onto a common Hilbert space.
    n_occupied : int | None, optional
        Number of lowest-energy bands defining the occupied subspace. It must
        be even and lie strictly between zero and the total band count.
        Defaults to half the bands using integer division.
    method : {"auto", "parity", "wilson", "both"}, optional
        Numerical construction. ``"auto"`` tries Fu--Kane parity and falls
        back to Wilson loops if inversion cannot be resolved
        (`RuntimeError`). ``"both"`` requires parity to succeed. Defaults to
        ``"auto"``.
    inversion : Tensor | None, optional
        Optional rank-3 inversion operator with dims
        ``(MomentumSpace, HilbertSpace, HilbertSpace)``, or with a diagonal
        `MomentumBlockSpace` of \((k,k)\) blocks. Band axes are aligned onto
        the Hamiltonian Hilbert space. Each momentum is paired with
        \(H(k)\) by its label, not by data-axis order, and the labeled
        points must form the same complete reciprocal quotient. If omitted,
        spatial inversion is assembled from orbital
        [`Offset`][qten.geometries.spatials.Offset] labels about
        ``inversion_center``.
    inversion_center : Offset | Sequence[float] | None, optional
        Fixed point of spatial inversion, as an `Offset` or a \(d\)-vector in
        the Hamiltonian's direct-lattice coordinates. Defaults to the centroid
        of the unique orbital offsets.
    n_loop : int, optional
        Number of Wilson-loop samples around each closed \(k\)-string.
        Must be at least 8 when Wilson loops are evaluated. Defaults to 32.
    n_perp : int, optional
        Number of hybrid-Wannier samples from a TRIM plane's \(k_\perp=0\)
        edge to \(k_\perp=\pi\). Must be at least 5 when Wilson loops are
        evaluated. Defaults to 17.
    parity_tolerance : float, optional
        Maximum relative \([H,I]\) commutator and inversion-eigenvalue
        deviation accepted at a TRIM. Defaults to ``1e-5``.
    kramers_tolerance : float, optional
        Maximum Wannier-center separation allowed when pairing Kramers
        partners on TRIM-plane endpoints. Defaults to ``0.08``.
    gap_tolerance : float, optional
        Warning threshold for the minimum sampled occupied-to-empty direct
        gap. Defaults to ``1e-8``.

    Returns
    -------
    Z2ParityResult or Z2WilsonResult or Z2CombinedResult
        Result mapping. Every method returns:

        - ``"indices"``: \((\nu,)\) in 2-D or \((\nu_0, \nu_1, \nu_2, \nu_3)\)
          in 3-D, as integers in \(\{0,1\}\).
        - ``"method"``: the construction that produced those indices.

        For ``method="parity"`` the mapping also contains Fu--Kane
        ``"parity_products"`` at each TRIM, per-TRIM ``"diagnostics"``
        (including a labeled ``"parity_eigenvalues"``
        [`Tensor`][qten.linalg.tensors.Tensor] of shape
        ``(n_occupied,)``), and ``"direct_gap"``.

        For ``method="wilson"`` it contains hybrid-Wannier ``"planes"`` whose
        ``"wcc"`` (shape ``(n_perp, n_occupied)``), ``"gap_pos"``, and
        ``"sweep"`` values are labeled
        [`Tensor`][qten.linalg.tensors.Tensor] objects, per-axis plane
        invariants ``"axis_z2"``, and ``"min_gap"``.

        For ``method="both"`` it contains both ``"parity"`` and ``"wilson"``
        sub-results; ``"indices"`` follows the parity values.

    Raises
    ------
    TypeError
        If the Hamiltonian or inversion first dimension is not a
        `MomentumSpace` or `MomentumBlockSpace`, or either Hamiltonian
        matrix dimension is not a `HilbertSpace`.
    ValueError
        If ``method`` is unsupported; the input is not a rank-3 square Bloch
        Hamiltonian; ``n_occupied`` is invalid; the momentum space is not
        two- or three-dimensional and periodic with a diagonal cell; a
        `MomentumBlockSpace` contains off-diagonal \((k,k')\) blocks;
        Hamiltonian or inversion momenta do not form a unique complete
        reciprocal quotient; inversion band axes do not span the Hamiltonian
        Hilbert space; or ``n_loop`` / ``n_perp`` are below the Wilson-loop
        minima.
    RuntimeError
        For ``method="parity"`` or ``method="both"``, if inversion cannot be
        constructed or is not resolved at a TRIM.

    Warns
    -----
    RuntimeWarning
        If the sampled minimum direct gap is no larger than ``gap_tolerance``;
        if ``method="auto"`` falls back from parity to Wilson loops; if the
        two 2-D Wilson orientations disagree; if the three 3-D Wilson axes
        disagree on \(\nu_0\) (majority vote is used); if Kramers pairing of
        Wannier centers is unresolved; if parity and Wilson indices disagree;
        or if the sampled spectrum is not time-reversal / Kramers consistent.

    Notes
    -----
    In 2-D, Fu--Kane gives \((-1)^\nu=\prod_i\delta(\Gamma_i)\). In 3-D,
    \((-1)^{\nu_0}=\prod_i\delta(\Gamma_i)\) and
    \((-1)^{\nu_j}=\prod_{k_j=\pi}\delta(\Gamma_i)\). Wilson indices use the
    hybrid-Wannier plane invariants described in the module docstring. Both
    constructions evaluate the Fourier interpolant of the input mesh rather
    than requiring TRIM or Wilson strings to sit on sampled \(k\)-points.

    Examples
    --------
    Use Fu--Kane parities when an inversion tensor is available:

    ```python
    result = z2_indices(hamiltonian, n_occupied=2, inversion=inversion, method="parity")
    indices = result["indices"]
    ```

    Fall back to Wilson loops on a system without inversion:

    ```python
    wilson = z2_indices(hamiltonian, n_occupied=2, method="wilson")
    ```

    See Also
    --------
    [`chern_number`][qten.topology.chern_number]
        First Chern number of a 2-D occupied bundle.
    """
    method_name = str(method).lower()
    if method_name not in {"auto", "parity", "wilson", "both"}:
        raise ValueError("method must be 'auto', 'parity', 'wilson', or 'both'.")

    engine = _build_engine(bloch_hamiltonian, n_occupied, inversion, inversion_center)
    engine.check_time_reversal(stacklevel=2)
    parity_result: Z2ParityResult | None = None
    wilson_result: Z2WilsonResult | None = None

    if method_name in {"parity", "both"}:
        parity_result = engine.run_parity(parity_tolerance)
    elif method_name == "auto":
        try:
            parity_result = engine.run_parity(parity_tolerance)
        except RuntimeError as exc:
            warnings.warn(
                f"Parity method unavailable ({exc}). Falling back to Wilson loops.",
                RuntimeWarning,
                stacklevel=2,
            )

    if (
        method_name == "wilson"
        or method_name == "both"
        or (method_name == "auto" and parity_result is None)
    ):
        wilson_result = engine.run_wilson(int(n_loop), int(n_perp), kramers_tolerance)

    if method_name == "both":
        if parity_result is None or wilson_result is None:
            raise RuntimeError("method='both' requires parity and Wilson results.")
        if parity_result["indices"] != wilson_result["indices"]:
            warnings.warn(
                f"Parity {parity_result['indices']} and Wilson "
                f"{wilson_result['indices']} disagree.",
                RuntimeWarning,
                stacklevel=2,
            )
        chosen: Z2ParityResult | Z2WilsonResult | Z2CombinedResult = {
            "indices": parity_result["indices"],
            "method": "both",
            "parity": parity_result,
            "wilson": wilson_result,
        }
    elif parity_result is not None and wilson_result is None:
        chosen = parity_result
    elif wilson_result is not None:
        chosen = wilson_result
    else:
        raise RuntimeError("Z2 calculation produced no result.")

    if chosen["method"] == "parity":
        min_gap = chosen["direct_gap"]
    elif chosen["method"] == "wilson":
        min_gap = chosen["min_gap"]
    else:
        sampled = [chosen["parity"]["direct_gap"], chosen["wilson"]["min_gap"]]
        finite = [gap for gap in sampled if math.isfinite(gap)]
        min_gap = min(finite) if finite else float("nan")
    if math.isfinite(min_gap) and min_gap <= gap_tolerance:
        warnings.warn(
            f"Minimum sampled direct gap is {min_gap:.6e}; the occupied "
            "bundle is not isolated, so its Z2 indices are not well-defined.",
            RuntimeWarning,
            stacklevel=2,
        )
    return chosen