soprano.nmr.utils#
Utility functions for NMR-related properties
- soprano.nmr.utils._J_constant(Kij, gi, gj)[source]#
J coupling constants for pairs ij, with reduced constant Kij and gyromagnetic ratios gi and gj
- Return type:
FloatOrArray
- soprano.nmr.utils._alpha_zero_euler_angles(R, convention='zyz', eps=1e-09)[source]#
Active ZYZ/ZXZ angles of
Rwithalpha == 0(Rfrom_alpha_zero_gauge()).No range normalisation is applied here: beta is in [0, pi] and gamma in [0, 2 pi).
NMRTensor.euler_anglesnormalises afterwards, as TensorView does, which turnsalpha = 0intopiwhen beta >= pi/2. At gimbal lock (beta = 0 or pi) only a combination of alpha and gamma is defined, and it is all put into gamma.- Parameters:
R (ndarray)
convention (str)
eps (float)
- Return type:
ndarray
- soprano.nmr.utils._alpha_zero_gauge(E, convention='zyz')[source]#
Pick the PAS frame of a tensor with a unique x axis that makes the active alpha zero.
A tensor with eigenvalues
[a, b, b]is symmetric under any rotation about its PAS x axis, soE -> E Rx(phi)describes the same tensor. TensorView fixes this freedom by asking foralpha = 0in the active ZYZ/ZXZ angles (Eq. 70 of Svenningsson & Mueller, Solid State Nucl. Magn. Reson. 123 (2023) 101849). Here the samephiis found directly from the matrix:alphais the azimuth of the third column ofE, sophirotates that column into the xz plane (ZYZ) or the yz plane (ZXZ), on the side with the sign that givesalpha = 0rather thanpi. There are no divisions bysin(gamma) cos(gamma)or square roots, so there are no singular orientations.- Parameters:
E (ndarray)
convention (str)
- Return type:
ndarray
- soprano.nmr.utils._anisotropy(haeb_evals, reduced=False)[source]#
Calculate anisotropy given eigenvalues sorted with Haeberlen convention
- Return type:
FloatOrArray
- soprano.nmr.utils._degeneracy_pattern(eigenvalues, eps=1e-06)[source]#
Classify the eigenvalue pattern of a tensor in its stored (sorted) order.
Returns
(degeneracy, unique_axis):degeneracyis 1 (triaxial), 2 (axial) or 3 (spherical);unique_axisis the index (0, 1, 2 for x, y, z) of the eigenvalue that differs from the other two whendegeneracy == 2, elseNone. Every pair is compared, so[a, b, b]is recognised (a unique axis along x), which comparing everything toeigenvalues[0]would miss.- Parameters:
eps (float)
- soprano.nmr.utils._dip_constant(Rij, gi, gj)[source]#
Dipolar constants for pairs ij, with distances Rij and gyromagnetic ratios gi and gj
- Return type:
FloatOrArray
- soprano.nmr.utils._dip_tensor(d, r, rotation_axis=None)[source]#
Full dipolar tensor given a constant and a connecting vector
- soprano.nmr.utils._ensure_tensor_format(tensors)[source]#
Ensure tensors are in 3x3 format, converting from flattened 1x9 if necessary.
- Parameters:
tensors (np.ndarray) – Tensor array, either (N, 3, 3) or (N, 9)
- Returns:
Tensors in (N, 3, 3) format
- Return type:
np.ndarray
- soprano.nmr.utils._equivalent_euler(euler_angles, passive=False)[source]#
Find the equivalent Euler angles for a given set of Euler angles.
This set should be correct for NMR tensors, according to TensorView for MATLAB: [6]
- Parameters:
euler_angles (ndarray)
passive (bool)
- Return type:
ndarray
- soprano.nmr.utils._equivalent_relative_euler(euler_angles, passive=False)[source]#
Returns a list of 16 equivalent relative Euler angles for a given set of Euler angles that corresponds to the relative orientation of two NMR tensors. See TensorView for MATLAB: [6]
- Parameters:
euler_angles (ndarray)
passive (bool)
- Return type:
ndarray
- soprano.nmr.utils._evals_sort(evals, convention='i', return_indices=False)[source]#
Sort a list of eigenvalue triplets by various conventions
The sorting conventions are: - i: increasing order - d: decreasing order - h: Haeberlen order - n: NQR order (absolute values)
- Parameters:
evals (np.ndarray) – Array of eigenvalue triplets to sort.
convention (str) – The sorting convention to use (‘i’, ‘d’, ‘h’, ‘n’).
return_indices (bool) – Whether to return the sorting indices.
Returns – Union[np.ndarray, tuple[np.ndarray, np.ndarray]]: Sorted eigenvalues, and optionally the sorting indices.
- Return type:
ndarray | tuple[ndarray, ndarray]
- soprano.nmr.utils._evecs_2_quat(evecs)[source]#
Convert a set of eigenvectors to a Quaternion expressing the rotation of the tensor’s PAS with respect to the Cartesian axes
- soprano.nmr.utils._get_tensor_array(s, tensor_tag)[source]#
Get tensor array from structure, ensuring 3x3 format.
- Parameters:
s (Atoms) – The structure
tensor_tag (str) – Name of the tensor array
- Returns:
Tensor array in (N, 3, 3) format
- Return type:
np.ndarray
- Raises:
RuntimeError – If tensor array not found
- soprano.nmr.utils._gradients_to_list(gradients, elements)[source]#
Convert gradients input to a list with one value per element in elements.
Parameters: gradients (Union[float, dict[str, float], list[float]]): The gradients input, which can be a float, a dictionary, or a list. elements (list[str]): The list of elements.
Returns: Optional[list[Optional[float]]]: A list of gradients corresponding to the elements, or None if gradients is None.
Raises: ValueError: If the gradients input is not a float, dictionary, or list, or if the length of the gradients list does not match the length of elements.
- Parameters:
gradients (float | dict[str, float] | list[float])
elements (list[str])
- Return type:
list[float]
- soprano.nmr.utils._handle_euler_edge_cases(euler_angles, eigenvalues, convention='zyz', passive=False, eps=1e-06, angles_are_passive=False)[source]#
Handle edge cases in the Euler angle conventions for degenerate tensors.
Handled here: spherical tensors (all angles zero) and tensors with the unique axis along z (the free angle is set to zero). Tensors with the unique axis along x need the PAS frame to be chosen before the angles are extracted (
_alpha_zero_gauge(), done byNMRTensor.euler_angles); if they arrive here their angles are just normalised.- Parameters:
euler_angles (np.ndarray) – Euler angles in radians
eigenvalues (np.ndarray) – Eigenvalues of the tensor
convention (str, optional) – Euler angle convention. Defaults to “zyz”.
passive (bool, optional) – Whether the angles are passive. Defaults to False.
eps (float, optional) – Tolerance for degeneracy. Defaults to 1e-6.
angles_are_passive (bool, optional) – Whether
euler_anglesare passive angles on entry. Only affects which angle is the free one for a tensor with its unique axis along z: gamma if the angles are active, alpha if they are passive. Defaults to False.
- soprano.nmr.utils._matrix_to_euler(R, convention='zyz', passive=False)[source]#
Convert an NMR tensor eigenframe matrix to NMR-canonical Euler angles (in radians).
NMR-specific function. This function is designed for use with NMR tensor eigenframes, where eigenvector signs are physically arbitrary. It applies
_normalise_euler_angles()after extracting angles from scipy, which restricts β to [0, π/2] by exploiting the sign freedom of eigenvectors. This transformation does not preserve a general rotation matrix: for arbitrary rotations with β > π/2 the returned angles reconstruct a different matrix. Do not use this function for general rotation matrices.SciPy’s upper-case convention strings denote intrinsic rotations; all input convention strings are converted to upper case so the behaviour is always intrinsic.
- Parameters:
R (list[list[float]] | ndarray) – Rotation matrix (3×3), typically the eigenvector matrix of an NMR tensor. Array-like inputs are accepted. If
det(R) < 0, the last column is negated before proceeding. For eigenvector matrices this is valid because eigenvector signs are arbitrary; for general improper rotations it is not the nearest proper rotation in a metric sense.convention (str) – Euler-angle convention, e.g.
"zyz"or"zxz". Converted to upper case internally. Defaults to"zyz".passive (bool) – If
True, return the passive (alias) angles, i.e. those describing the rotation of the coordinate frame rather than the object. Defaults toFalse.
- Returns:
NMR-canonical Euler angles in radians, shape
(3,).- Return type:
np.ndarray
- soprano.nmr.utils._normalise_euler_angles(euler_angles, passive=False, eps=1e-06)[source]#
Normalise Euler angles to the orientation-equivalent set with beta in [0, pi/2).
The four equivalent sets of a tensor (a half turn about each PAS axis) are those of TensorView for MATLAB [6], Eqs. 16-23. The paper allows
0 <= beta <= pi; the member chosen here is the one TensorView’s code reports (MFtoEuler.mandRelativeAngles.m): beta in [0, pi/2), then gamma in [0, pi) for active angles or alpha in [0, pi) for passive ones. The result always describes the same tensor.- Parameters:
euler_angles (np.ndarray) – Euler angles in radians
passive (bool, optional) – Whether the angles are passive rotations. Defaults to False.
eps (float, optional) – Tolerance for degeneracy. Defaults to 1e-6.
- Return type:
ndarray
- soprano.nmr.utils._references_to_list(references, elements)[source]#
Convert references input to a list with one value per element in elements.
Parameters: references (Union[None, dict[str, float], list[float]]): The references input, which can be None, a dictionary, or a list. elements (list[str]): The list of elements.
Returns: Optional[list[Optional[float]]]: A list of references corresponding to the elements, or None if references is None.
Raises: ValueError: If the references input is not None, a dictionary, or a list, or if the length of the references list does not match the length of elements.
- Parameters:
references (None | dict[str, float] | list[float])
elements (list[str])
- Return type:
list[float | None]
- soprano.nmr.utils._skew(evals)[source]#
Calculate skew
\[\kappa = 3 (\sigma_{iso} - \sigma_{22}) / \Omega\]where \(\Omega\) is the span of the tensor.
Note that for chemical shift tensors (\(\delta\)), the sign is reversed.
- Return type:
FloatOrArray
- soprano.nmr.utils._span(evals)[source]#
Calculate span
\[\Omega = \sigma_{33} - \sigma_{11}\]where \(\sigma_{33}\) is the largest, and \(\sigma_{11}\) is the smallest eigenvalue.
- Return type:
FloatOrArray
- soprano.nmr.utils._split_species(species)[source]#
Validate the species string and extract the isotope number and element.
- Parameters:
species (str) – The species string to validate. e.g. ‘13C’ or ‘1H’.
- Returns:
A tuple containing the isotope number (int), and element symbol (str, such as ‘C’ or ‘H’).
- Return type:
tuple
- soprano.nmr.utils._test_euler_rotation(euler_angles, eigenvalues, eigenvecs, convention='zyz', passive=False, eps=1e-06)[source]#
Test that the Euler angles correctly rotate the tensor.
We compare the tensor rotated by the Euler angles to that you get by rotating the tensor with the rotation matrix corresponding to the Euler angles. i.e.
PAS = np.diag(eigenvalues) R = Rotation.from_euler(convention, euler_angles).as_matrix() A_rot = np.dot(R, np.dot(PAS, R.T))
B_rot = np.dot(eigenvecs, np.dot(PAS, eigenvecs.T))
- Parameters:
euler_angles (np.ndarray) – Euler angles in radians
eigenvalues (np.ndarray) – Eigenvalues of the tensor
eigenvecs (np.ndarray) – Eigenvectors of the tensor
convention (str, optional) – Euler angle convention. Defaults to “zyz”.
passive (bool, optional) – Whether the angles are passive rotations. Defaults to False.
eps (float, optional) – Tolerance for degeneracy. Defaults to 1e-6.
- Returns:
True if the Euler angles correctly rotate the tensor. False otherwise.
- Return type:
bool