Source code for sim.hamiltonian.terms.hyperfine_n14

# ==============================================================================
#  QUSIM - Quantum Simulator for NV Centers
#  Leon Kaiser, MSQC Goethe University, Frankfurt, Germany
#  https://msqc.cgi-host6.rz.uni-frankfurt.de
#  I.kaiser[at]em.uni-frankfurt.de
#
#  This software is provided for scientific and educational purposes.
#  Free to use, modify, and distribute with attribution.
# ==============================================================================
"""
N14 hyperfine coupling for NV center.

The nitrogen-14 nucleus (I=1) at the vacancy site couples to the
electron spin through hyperfine interaction and has a nuclear
quadrupole moment.

Physics
-------
The N14 hyperfine Hamiltonian consists of two parts:

1. **Hyperfine coupling** (electron-nuclear spin interaction):

   H_hf = A_∥ Sz Iz + A_⊥ (Sx Ix + Sy Iy)

   where A_∥ ≈ -2.14 MHz and A_⊥ ≈ -2.70 MHz for NV centers.

2. **Quadrupole coupling** (nuclear electric quadrupole):

   H_Q = P (Iz² - 2/3)

   where P ≈ -5.0 MHz is the quadrupole parameter.

The total Hamiltonian is:

   H_N14 = A_∥ Sz Iz + A_⊥ (Sx Ix + Sy Iy) + P (Iz² - 2/3)

Matrix Dimension
----------------
The hyperfine coupling acts on the space |ms⟩ ⊗ |mI⟩ which has
dimension 3 × 3 = 9. This is then extended to the full 18-dimensional
space by tensor product with the electronic g/e identity.

Literature Values
-----------------
- A_∥ = -2.14 MHz (parallel hyperfine)
- A_⊥ = -2.70 MHz (perpendicular hyperfine)
- P = -5.0 MHz (quadrupole parameter)

References
----------
[1] Doherty et al., Physics Reports 528, 1-45 (2013)
[2] Smeltzer et al., New J. Phys. 13, 025021 (2011)
"""

import numpy as np
from typing import Optional

from .base import HamiltonianTerm
from ...core.operators import spin1_operators
from ...core.constants import MHZ


[docs] class HyperfineN14(HamiltonianTerm): """ N14 hyperfine coupling with quadrupole interaction. Parameters ---------- A_parallel : float Parallel hyperfine coupling A_∥ in MHz. Default: -2.14 MHz A_perp : float Perpendicular hyperfine coupling A_⊥ in MHz. Default: -2.70 MHz P_quad : float Quadrupole parameter P in MHz. Default: -5.0 MHz name : str, optional Custom name for the term Attributes ---------- A_parallel : float Parallel coupling in MHz A_perp : float Perpendicular coupling in MHz P_quad : float Quadrupole parameter in MHz Examples -------- >>> from sim import HamiltonianBuilder >>> from sim.hamiltonian.terms import ZFS, HyperfineN14 >>> >>> H = HamiltonianBuilder() >>> H.add(ZFS(D=2.87)) >>> H.add(HyperfineN14()) # Default N14 parameters >>> >>> # Check eigenvalues show hyperfine structure >>> eigvals = H.eigenvalues_mhz() >>> print(f"Hyperfine splitting visible in eigenvalues") Custom parameters: >>> hf = HyperfineN14(A_parallel=-2.2, A_perp=-2.8, P_quad=-4.9) >>> H_matrix = hf.build() >>> print(f"Matrix shape: {H_matrix.shape}") (18, 18) Notes ----- The hyperfine structure splits each ms level into three mI levels. The quadrupole term causes asymmetric splitting of the mI = ±1 levels. The sign convention follows the standard where negative values indicate antiferromagnetic coupling. """
[docs] def __init__( self, A_parallel: float = -2.14, # MHz A_perp: float = -2.70, # MHz P_quad: float = -5.0, # MHz name: Optional[str] = None ): super().__init__(name=name) self.A_parallel = A_parallel self.A_perp = A_perp self.P_quad = P_quad # Cache for the static Hamiltonian self._cache: Optional[np.ndarray] = None
@property def is_time_dependent(self) -> bool: """N14 hyperfine coupling is time-independent.""" return False def _build_9x9(self) -> np.ndarray: """ Build the 9×9 hyperfine Hamiltonian in |ms⟩ ⊗ |mI⟩ space. Returns ------- np.ndarray 9×9 complex Hermitian matrix in rad/s """ # Get spin-1 operators for both electron (S) and nuclear (I) spins S = spin1_operators() I = spin1_operators() # N14 is also spin-1 # Convert parameters from MHz to rad/s A_par = self.A_parallel * MHZ A_perp = self.A_perp * MHZ P = self.P_quad * MHZ # Build hyperfine coupling: A_∥ Sz⊗Iz + A_⊥ (Sx⊗Ix + Sy⊗Iy) # Note: I is Spin1Ops, use .Sx/.Sy/.Sz for nuclear operators H_hf = ( A_par * np.kron(S.Sz, I.Sz) + A_perp * (np.kron(S.Sx, I.Sx) + np.kron(S.Sy, I.Sy)) ) # Build quadrupole term: P (Iz² - 2/3) # This only acts on the nuclear spin I3 = np.eye(3, dtype=np.complex128) Iz_squared = I.Sz @ I.Sz H_quad = P * np.kron(I3, Iz_squared - (2.0/3.0) * I.I) return H_hf + H_quad
[docs] def build(self, t: float = 0.0) -> np.ndarray: """ Build the 18×18 N14 hyperfine Hamiltonian. Parameters ---------- t : float Time in seconds (not used, hyperfine is static) Returns ------- np.ndarray 18×18 complex Hermitian matrix in rad/s Notes ----- The result is cached since the Hamiltonian is time-independent. The 9×9 matrix is extended to 18×18 by acting identically on both ground and excited electronic states. """ if self._cache is None: H_9x9 = self._build_9x9() # Extend to 18×18: I_2 ⊗ H_9x9 # Acts on both g and e manifolds identically I2 = np.eye(2, dtype=np.complex128) self._cache = np.kron(I2, H_9x9) return self._cache.copy()
[docs] def hyperfine_tensor(self) -> np.ndarray: """ Return the 3×3 hyperfine tensor in the NV frame. Returns ------- np.ndarray 3×3 diagonal tensor with [A_⊥, A_⊥, A_∥] in MHz Notes ----- The NV axis is along z, so: - A_xx = A_yy = A_⊥ (perpendicular) - A_zz = A_∥ (parallel) """ return np.diag([self.A_perp, self.A_perp, self.A_parallel])
[docs] def splitting_mhz(self, ms: int = 0) -> dict: """ Calculate the N14 hyperfine splitting for a given ms level. Parameters ---------- ms : int Electron spin projection: +1, 0, or -1 Returns ------- dict Dictionary with keys 'mI_+1', 'mI_0', 'mI_-1' containing the energy shifts in MHz for each nuclear spin state Examples -------- >>> hf = HyperfineN14() >>> split = hf.splitting_mhz(ms=0) >>> print(f"mI=0 shift: {split['mI_0']:.2f} MHz") """ # For ms=0, only quadrupole contributes # For ms=±1, both hyperfine and quadrupole contribute P = self.P_quad A_par = self.A_parallel if ms == 0: # Only quadrupole: P(mI² - 2/3) shifts = { 'mI_+1': P * (1 - 2/3), # P/3 'mI_0': P * (0 - 2/3), # -2P/3 'mI_-1': P * (1 - 2/3), # P/3 } else: # Hyperfine + quadrupole: A_∥·ms·mI + P(mI² - 2/3) shifts = { 'mI_+1': A_par * ms * 1 + P * (1 - 2/3), 'mI_0': A_par * ms * 0 + P * (0 - 2/3), 'mI_-1': A_par * ms * (-1) + P * (1 - 2/3), } return shifts
def __repr__(self) -> str: return ( f"HyperfineN14(A_parallel={self.A_parallel} MHz, " f"A_perp={self.A_perp} MHz, P_quad={self.P_quad} MHz)" )