Source code for dynsight._internal.timesoap.timesoap

"""Compute timeSOAP."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from numpy.typing import NDArray

import numpy as np


[docs] def normalize_soap(soap: NDArray[np.float64]) -> NDArray[np.float64]: """Returns the SOAP spectra normalized to unitary length. Parameters: soap : shape (n_particles, n_frames, n_comp) The SOAP spctra for the trajectory. Returns: np.ndarray of shape (n_particles, n_frames, n_components) The normalized SOAP spectra. Example: .. testsetup:: tsoap1-test import pathlib path = pathlib.Path('source/_static/ex_test_files') .. testcode:: tsoap1-test import numpy as np import MDAnalysis from dynsight.soap import ( saponify_trajectory, normalize_soap, ) univ = MDAnalysis.Universe(path / "trajectory.xyz") cutoff = 2.0 soap = saponify_trajectory(univ, cutoff, soap_respectpbc=False) unitary_soap = normalize_soap(soap) .. testcode:: tsoap1-test :hide: assert np.isclose( np.sum(unitary_soap[0]), 21.987915602216525, atol=1e-6, rtol=1e-3) """ norms = np.linalg.norm(soap, axis=-1) mask = norms > 0.0 norm_soap = np.zeros(soap.shape) norm_soap[mask] = soap[mask] / norms[..., np.newaxis][mask] return norm_soap
[docs] def soap_distance( v_1: NDArray[np.float64], v_2: NDArray[np.float64], ) -> NDArray[np.float64]: r"""Computes the Kernel SOAP distance between two SOAP spectra. The SOAP distance is calculated with: .. math:: d(\vec{v_1},\vec{v_2}) = \sqrt{2-2\frac{\vec{v_1}\cdot\vec{v_2}}{||\vec{v_1}||\cdot||\vec{v_2}||}} This is equivalent to: .. math:: d(\vec{v_1},\vec{v_2})=\sqrt{2-2\hat{v_1}\cdot\hat{v_2}} = \sqrt{\hat{v_1}\cdot\hat{v_1}+\hat{v_2}\cdot\hat{v_2}-2\hat{v_1} \cdot\hat{v_2}} = \\ \sqrt{(\hat{v_1}-\hat{v_2})\cdot(\hat{v_1}-\hat{v_2})} = ||\hat{v_1}-\hat{v_2}|| This represents the Euclidean distance between the versors. Parameters: v_1, v_2 : SOAP spectra. Returns: NDArray[np.float64] : the SOAP distances between the input spectra. Example: .. testsetup:: tsoap2-test import pathlib path = pathlib.Path('source/_static/ex_test_files') .. testcode:: tsoap2-test import numpy as np import MDAnalysis from dynsight.soap import saponify_trajectory, soap_distance univ = MDAnalysis.Universe(path / "trajectory.xyz") cutoff = 2.0 soap = saponify_trajectory(univ, cutoff, soap_respectpbc=False) soap_dist = soap_distance(soap[0][0], soap[0][1]) .. testcode:: tsoap2-test :hide: assert np.isclose(soap_dist, 0.10292206044570047, atol=1e-6, rtol=1e-3) """ norm_v_1 = normalize_soap(v_1) norm_v_2 = normalize_soap(v_2) cos_theta = np.sum(norm_v_1 * norm_v_2, axis=-1) # Takes care of numerical errors cos_theta = np.clip(cos_theta, -1.0, 1.0) tsoap = np.maximum(0, 2 - 2 * cos_theta) return np.sqrt(tsoap)
[docs] def timesoap(soap: NDArray[np.float64], delay: int = 1) -> NDArray[np.float64]: """Performs the 'timeSOAP' analysis on the given SOAP trajectory. timeSOAP was developed by Cristina Caurso. See for reference the paper https://doi.org/10.1063/5.0147025. Parameters: soap: shape (n_particles, n_frames, n_comp) The SOAP spctra for the trajectory. delay: The delay between frames on which timeSOAP is computed. Default is 1. Returns: NDArray[np.float64] Values of timesoap of each particle at each frame. Has shape (n_particles, n_frames - 1). Example: .. testsetup:: tsoap3-test import pathlib path = pathlib.Path('source/_static/ex_test_files') .. testcode:: tsoap3-test import numpy as np import MDAnalysis from dynsight.soap import saponify_trajectory, timesoap univ = MDAnalysis.Universe(path / "trajectory.xyz") cutoff = 2.0 soap = saponify_trajectory(univ, cutoff, soap_respectpbc=False) tsoap = timesoap(soap) .. testcode:: tsoap3-test :hide: assert np.isclose(np.sum(tsoap), 191.3446863900592, atol=1e-6, rtol=1e-3) """ value_error = "delay value outside bounds" if delay < 1 or delay >= soap.shape[1]: raise ValueError(value_error) return soap_distance(soap[:, :-delay, :], soap[:, delay:, :])