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._anisotropy(haeb_evals, reduced=False)[source]#

Calculate anisotropy given eigenvalues sorted with Haeberlen convention

Return type:

FloatOrArray

soprano.nmr.utils._asymmetry(haeb_evals)[source]#

Calculate asymmetry

Return type:

FloatOrArray

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, original_tensor, convention='zyz', passive=False, eps=1e-06)[source]#

Handle edge cases in the Euler angle conventions for degenerate tensors.

Parameters:
  • euler_angles (np.ndarray) – Euler angles in radians

  • eigenvalues (np.ndarray) – Eigenvalues of the tensor

  • original_tensor (np.ndarray) – Original symmetric tensor

  • convention (str, optional) – Euler angle convention. Defaults to “zyz”.

  • eps (float, optional) – Tolerance for degeneracy. Defaults to 1e-6.

  • passive (bool)

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 to False.

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 standard ranges for NMR as defined in: TensorView for MATLAB [6]

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

soprano.nmr.utils._tryallanglestest(euler_angles, pas1, pasv2, arel1, convention, eps=0.001)[source]#

For relative Euler angles from tensor A to B, if B is axially symmetric, we need to try all equivalent Euler angles of the symmetric tensor to find the conventional one.

Go through the 4 equivalent passive angles since we’re using the trick of calculating the A relative angles in the frame of B by calculating the B relative angles in the frame of A with the passive convention.

Credit: function adapted from TensorView for MATLAB: [6]

Parameters:
  • euler_angles (ndarray)

  • pas1 (ndarray)

  • pasv2 (ndarray)

  • arel1 (ndarray)

  • convention (str)

  • eps (float)

Return type:

ndarray