# Copyright © 2024-2025 HQS Quantum Simulations GmbH. All Rights Reserved.
# LICENSE PLACEHOLDER
"""Different solvers to evaluate individual spectral contributions of a NMR Spin Hamiltonian."""
from __future__ import annotations
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
import numpy as np
if TYPE_CHECKING:
from struqture_py.spins import SpinHamiltonianSystem
from hqs_nmr.datatypes import NMRCalculationParameters
from hqs_nmr.hamiltonian_tools import extract_coupling
from hqs_nmr.solver.implementations.Sz_conserved_routines import (
calc_correlator_function_variable_spin_number,
)
from hqs_nmr.solver.implementations.system_tools import (
get_operator_list,
get_operator_sum,
EffectiveSpinSystem,
)
from hqs_nmr.solver.implementations.local_SU2_conserved_routines import (
calc_correlator_function_from_effective_spin_system,
)
[docs]
def nmr_solver(
hamiltonian: SpinHamiltonianSystem,
normalized_gyromagnetic_ratios: np.ndarray,
omegas: np.ndarray,
eta: np.ndarray,
spin_contribution_indices: list[int],
calculation_parameters: NMRCalculationParameters,
**kwargs: dict[str, Any], # noqa: ARG001
) -> np.ndarray:
r"""Calculate a NMR spectrum exploiting Sz conservation given a SpinHamiltonianSystem.
Evaluates a correlator function of the form:
.. math::
\sum_n \frac{\bra{n} I^+_l \ket{m} \bra{m} I^- \ket{n}}{\omega + E_n - E_m + i \eta}
\text{where} I^- = \sum_l I^-_l.
Args:
hamiltonian: Struqture spin Hamiltonian.
normalized_gyromagnetic_ratios: Array of the gyromagnetic factors per site, normalized with
respect to the reference isotope.
omegas: Desired frequencies.
eta: Explicit spin-dependent broadening of the peaks.
spin_contribution_indices: List of indices l of I^+_l for which to evaluate the correlator.
calculation_parameters: Object storing all calculation parameters including solver-specific
settings.
kwargs: Catch all for general interface.
Returns:
An array of the spectral function for the specified spin contributions at each
frequency.
Raises:
ValueError: if number of spins does not match the number
of gyromagnetic factors.
"""
num_spins = hamiltonian.number_spins()
if num_spins != normalized_gyromagnetic_ratios.shape[0]:
raise ValueError("Number of spin sites does not match the number of gyromagnetic factors")
# Obtain the Hamiltonian matrices and shift the shifts by the magnetic shift.
# (this shift is only for numerical stability and cancels later on)
Jz, hJz, hJp = extract_coupling(hamiltonian)
# magnetic shift:
# \sum B_i S_i^z = \sum (B_i - magnetic_shift) S_i^z + magnetic_shift S_i^z
magnetic_shift = Jz.sum() / num_spins
Jz -= magnetic_shift
# Obtain lhs and rhs operators for correlator.
if calculation_parameters.solver_settings.operators_left is None:
site_resolved_operator_list = get_operator_list(num_spins, normalized_gyromagnetic_ratios)
else:
site_resolved_operator_list = calculation_parameters.solver_settings.operators_left
if calculation_parameters.solver_settings.operator_right is None:
operator_sum = get_operator_sum(num_spins, normalized_gyromagnetic_ratios)
else:
operator_sum = calculation_parameters.solver_settings.operator_right
return calc_correlator_function_variable_spin_number(
calculation_parameters.solver_settings.calc_greens_function,
spin_contribution_indices,
site_resolved_operator_list,
operator_sum,
Jz,
hJz,
hJp,
omegas,
eta,
magnetic_shift,
calculation_parameters.solver_settings.threshold_matrix_elements,
calculation_parameters.solver_settings.verbose,
)
[docs]
def nmr_solver_local_su2(
hamiltonian: SpinHamiltonianSystem,
normalized_gyromagnetic_ratios: np.ndarray,
omegas: np.ndarray,
eta: np.ndarray,
spin_contribution_indices: list[int],
calculation_parameters: NMRCalculationParameters,
**kwargs: dict[str, Any], # noqa: ARG001
) -> np.ndarray:
r"""Calculate a NMR spectrum using Sz and local SU2 conservation given a SpinHamiltonianSystem.
Evaluates a correlator function of the form
.. math::
\sum_n \frac{\bra{n} I^+_l \ket{m} \bra{m} I^- \ket{n}}{\omega + E_n - E_m + i \eta}
\text{where} I^- = \sum_l I^-_l.
Args:
hamiltonian: Struqture spin Hamiltonian.
normalized_gyromagnetic_ratios: Array of the gyromagnetic factors per site, normalized with
respect to the reference isotope.
omegas: Desired frequencies.
eta: Explicit spin-dependent broadening of the peaks.
spin_contribution_indices: List of indices for which to evaluate the correlator.
calculation_parameters: Object storing all calculation parameters including solver-specific
settings.
kwargs: Catch all for general interface.
Returns:
An array with the correlator function for the specified spin contributions at each
frequency.
Raises:
ValueError: if number of spins does not match the number of gyromagnetic factors.
"""
num_spins = hamiltonian.number_spins()
if num_spins != normalized_gyromagnetic_ratios.shape[0]:
raise ValueError("Number of spin sites does not match the number of gyromagnetic factors")
# Obtain the Hamiltonian matrices and shift the shifts by the magnetic shift.
# (this shift is only for numerical stability and cancels later on)
Jz, hJz, _ = extract_coupling(hamiltonian)
# magnetic shift:
# \sum B_i S_i^z = \sum (B_i - magnetic_shift) S_i^z + magnetic_shift S_i^z
magnetic_shift = Jz.sum() / num_spins
Jz -= magnetic_shift
# Create effective spin system, encoding the information on the local SU2 symmetry.
effective_spin_system = EffectiveSpinSystem(calculation_parameters.solver_settings, hJz, Jz)
if calculation_parameters.solver_settings.verbose > 0:
print("The following groups have been identified: ")
print(effective_spin_system.group_indices)
return calc_correlator_function_from_effective_spin_system(
spin_contribution_indices,
effective_spin_system,
normalized_gyromagnetic_ratios,
omegas,
eta,
magnetic_shift,
calculation_parameters.solver_settings,
)