From f9d0fc0d5d0b70bed9b1881ac392e51f3b499af1 Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Mon, 3 Jul 2023 12:09:58 -0500 Subject: [PATCH 01/13] Updating isort on __init__ --- .github/workflows/lint.yml | 1 + anisoap/__init__.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 63ac95f..dda8da0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,3 +34,4 @@ jobs: - name: Check imports run: | isort anisoap/*/*py -m 3 --tc --fgw --up -e -l 88 --check + isort anisoap/*py -m 3 --tc --fgw --up -e -l 88 --check diff --git a/anisoap/__init__.py b/anisoap/__init__.py index 4e208b1..46a59fd 100644 --- a/anisoap/__init__.py +++ b/anisoap/__init__.py @@ -1,3 +1,6 @@ -from anisoap import representations, utils +from anisoap import ( + representations, + utils, +) __version__ = "0.0.0" From a1fcf5888cb4a8ed826283af2e67ac8d0f29c462 Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:27:19 -0500 Subject: [PATCH 02/13] Update tests.yml to include coverage (#8) --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index efeaf41..d63c41c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,3 +30,6 @@ jobs: - uses: codecov/codecov-action@v1 with: file: ./tests/coverage.xml + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From ce90903845a0c7b603d54ff65928fafa361e39b2 Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:28:52 -0500 Subject: [PATCH 03/13] Update tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d63c41c..9c5fce9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,4 +32,4 @@ jobs: file: ./tests/coverage.xml - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 - env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 38a5ba09edd80a5f8dbb2fcb3ef852e7ad3dc09d Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:29:30 -0500 Subject: [PATCH 04/13] Update tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9c5fce9..3a00f6e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,4 +32,4 @@ jobs: file: ./tests/coverage.xml - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 - env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From c8dc9b30bbe534188ce54ed698ebb5e4b4bf5af7 Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:33:16 -0500 Subject: [PATCH 05/13] Update tests.yml --- .github/workflows/tests.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3a00f6e..efeaf41 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,6 +30,3 @@ jobs: - uses: codecov/codecov-action@v1 with: file: ./tests/coverage.xml - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From a9ee758f8f04745c70c5d140e3fa3a10aab0a09b Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:52:21 -0500 Subject: [PATCH 06/13] Update README.md --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 39c86af..172013a 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ -# anisoap -A Python Package for Computing the Smooth Overlap of Anisotropic Positions +AniSOAP +======= + + + + + + ## Installation @@ -30,3 +36,11 @@ Please run pytest and check that all tests pass before pushing new changes to th pytest tests/. +Contributors +------------ + +Thanks goes to all people that make AniSOAP possible: + + + + From 9cc1fd0c2d6b117766ad4f51d8e4485d89fbbd2b Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:52:45 -0500 Subject: [PATCH 07/13] Update tests.yml (#9) * Update tests.yml * Update tests.yml --- .github/workflows/tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index efeaf41..a12d1a7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,6 +27,9 @@ jobs: - name: Run tests run: | pytest -v tests/. - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 with: file: ./tests/coverage.xml + env: + name: CODECOV_TOKEN + value: ${{ secrets.CODECOV_TOKEN }} From c552fda82da6802f4821dfdd98ec80d143d480f0 Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:52:51 -0500 Subject: [PATCH 08/13] Adding new tests for EDP (#7) * Adding new tests for EDP * Making requisite changes for equistore compatibility * improved code coverage to 99% by testing for 3 additional cases: show_progress=True, multiple frames, and matrix rotations * pass the linter --------- Co-authored-by: Arthur Lin --- .../ellipsoidal_density_projection.py | 33 ++--- tests/requirements.txt | 2 + tests/test_ellipsoidal_density_projection.py | 126 ++++++++++++++++++ 3 files changed, 145 insertions(+), 16 deletions(-) create mode 100644 tests/test_ellipsoidal_density_projection.py diff --git a/anisoap/representations/ellipsoidal_density_projection.py b/anisoap/representations/ellipsoidal_density_projection.py index 2790700..9685e5e 100644 --- a/anisoap/representations/ellipsoidal_density_projection.py +++ b/anisoap/representations/ellipsoidal_density_projection.py @@ -1,16 +1,8 @@ +import sys import warnings - -import numpy as np - -from anisoap.utils.spherical_to_cartesian import spherical_to_cartesian - -try: - from tqdm import tqdm -except ImportError: - tqdm = lambda i, **kwargs: i - from itertools import product +import numpy as np from equistore.core import ( Labels, TensorBlock, @@ -18,11 +10,13 @@ ) from rascaline import NeighborList from scipy.spatial.transform import Rotation +from tqdm import tqdm import anisoap.representations.radial_basis as radial_basis from anisoap.representations.radial_basis import RadialBasis from anisoap.utils import compute_moments_inefficient_implementation from anisoap.utils.moment_generator import * +from anisoap.utils.spherical_to_cartesian import spherical_to_cartesian def pairwise_ellip_expansion( @@ -76,7 +70,7 @@ def pairwise_ellip_expansion( """ tensorblock_list = [] - keys = np.array(neighbor_list.keys.asarray(), dtype=int) + keys = np.asarray(neighbor_list.keys, dtype=int) keys = [tuple(i) + (l,) for i in keys for l in range(lmax + 1)] num_ns = radial_basis.get_num_radial_functions() @@ -184,7 +178,9 @@ def contract_pairwise_feat(pair_ellip_feat, species): # pair_ellip_feat.keys["angular_channel"] to form the keys of the single particle centered feature ellip_keys.sort() ellip_blocks = [] - property_names = pair_ellip_feat.property_names + ("neighbor_species",) + property_names = pair_ellip_feat.property_names + [ + "neighbor_species", + ] for key in ellip_keys: contract_blocks = [] @@ -194,7 +190,8 @@ def contract_pairwise_feat(pair_ellip_feat, species): # All these lists have as many entries as len(species). for ele in species: - blockidx = pair_ellip_feat.blocks_matching(species_neighbor=ele) + selection = Labels(names=["species_neighbor"], values=np.array([[ele]])) + blockidx = pair_ellip_feat.blocks_matching(selection=selection) # indices of the blocks in pair_ellip_feat with neighbor species = ele sel_blocks = [ pair_ellip_feat.block(i) @@ -218,6 +215,7 @@ def contract_pairwise_feat(pair_ellip_feat, species): pair_block_sample = list( zip(block.samples["structure"], block.samples["first_atom"]) ) + # Takes the structure and first atom index from the current pair_block sample. There might be repeated # entries here because for example (0,0,1) (0,0,2) might be samples of the pair block (the index of the # neighbor atom is changing but for both of these we are keeping (0,0) corresponding to the structure and @@ -237,7 +235,7 @@ def contract_pairwise_feat(pair_ellip_feat, species): sample_idx = [ idx for idx, tup in enumerate(pair_block_sample) - if tup[0] == sample[0] and tup[1] == sample[1] + if tup[0].values[0] == sample[0] and tup[1].values[0] == sample[1] ] # all samples of the pair block that match the current sample # in the example above, for sample = (0,0) we would identify sample_idx = [(0,0,1), (0,0,2)] @@ -370,12 +368,15 @@ def __init__( # Initialize the radial basis class if radial_basis_name not in ["monomial", "gto"]: - raise ValueError( - f"{self.radial_basis} is not an implemented basis" + raise NotImplementedError( + f"{self.radial_basis_name} is not an implemented basis" ". Try 'monomial' or 'gto'" ) if radial_gaussian_width != None and radial_basis_name != "gto": raise ValueError("Gaussian width can only be provided with GTO basis") + elif radial_gaussian_width is None and radial_basis_name == "gto": + raise ValueError("Gaussian width must be provided with GTO basis") + radial_hypers = {} radial_hypers["radial_basis"] = radial_basis_name.lower() # lower case radial_hypers["radial_gaussian_width"] = radial_gaussian_width diff --git a/tests/requirements.txt b/tests/requirements.txt index 9790999..74c415f 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,7 @@ +ase coverage[toml] numpy pytest scipy +tqdm git+https://github.com/Luthaf/rascaline.git diff --git a/tests/test_ellipsoidal_density_projection.py b/tests/test_ellipsoidal_density_projection.py new file mode 100644 index 0000000..262a68e --- /dev/null +++ b/tests/test_ellipsoidal_density_projection.py @@ -0,0 +1,126 @@ +import builtins + +import ase +import numpy as np +import pytest + +from anisoap.representations import EllipsoidalDensityProjection + + +def add_default_params(frame): + frame.arrays["quaternion"] = [[1, 0, 0, 0] for _ in frame] + frame.arrays["c_diameter[1]"] = [1 for _ in frame] + frame.arrays["c_diameter[2]"] = [1 for _ in frame] + frame.arrays["c_diameter[3]"] = [2 for _ in frame] + + return frame + + +TEST_SINGLE_FRAME = add_default_params( + ase.Atoms(symbols=["X"], positions=np.zeros((1, 3)), cell=(10, 10, 10), pbc=False) +) +TEST_QUAT_FRAME = add_default_params( + ase.Atoms( + symbols=["X", "O"], positions=[np.zeros(3), np.ones(3)], cell=(10, 10, 10) + ) +) +TEST_MATRIX_FRAME = TEST_SINGLE_FRAME.copy() +TEST_MATRIX_FRAME.arrays["matrix"] = [np.eye(3)] + +TEST_FRAMES = [ + [TEST_SINGLE_FRAME], + [TEST_QUAT_FRAME], + [TEST_MATRIX_FRAME], + [TEST_SINGLE_FRAME, TEST_QUAT_FRAME, TEST_MATRIX_FRAME], +] + + +DEFAULT_HYPERS = { + "max_angular": 10, + "radial_basis_name": "gto", + "radial_gaussian_width": 5.0, + "cutoff_radius": 1.0, +} + + +class TestEllipsoidalDensityProjection: + """ + Class for testing if the EDP can run as-expected on certain things + """ + + @pytest.mark.parametrize("frames", TEST_FRAMES) + def test_frames(self, frames): + EllipsoidalDensityProjection(**DEFAULT_HYPERS).transform(frames) + + @pytest.mark.parametrize("frames", TEST_FRAMES) + def test_frames_show_progress(self, frames): + EllipsoidalDensityProjection(**DEFAULT_HYPERS).transform( + frames, show_progress=True + ) + + @pytest.mark.parametrize("frames", TEST_FRAMES) + def test_frames_matrix_rotation(self, frames): + EllipsoidalDensityProjection( + rotation_key="matrix", rotation_type="matrix", **DEFAULT_HYPERS + ).transform(frames, show_progress=True) + + +class TestBadInputs: + """ + Class for testing if EDP fails correctly with bad hypers + """ + + test_hypers = [ + [ + {**DEFAULT_HYPERS, "compute_gradients": True}, + NotImplementedError, + "Sorry! Gradients have not yet been implemented", + ], + [ + { + **{k: v for k, v in DEFAULT_HYPERS.items() if k != "radial_basis_name"}, + "radial_basis_name": "nonsense", + }, + NotImplementedError, + "nonsense is not an implemented basis" ". Try 'monomial' or 'gto'", + ], + [ + { + **{k: v for k, v in DEFAULT_HYPERS.items() if k != "radial_basis_name"}, + "radial_basis_name": "monomial", + }, + ValueError, + "Gaussian width can only be provided with GTO basis", + ], + [ + { + **{ + k: v + for k, v in DEFAULT_HYPERS.items() + if k != "radial_gaussian_width" + } + }, + ValueError, + "Gaussian width must be provided with GTO basis", + ], + [ + {**DEFAULT_HYPERS, "rotation_type": "quaternions"}, + ValueError, + "We have only implemented transforming quaternions (`quaternion`) and rotation matrices (`matrix`).", + ], + ] + + @pytest.mark.parametrize("hypers,error_type,expected_message", test_hypers) + def test_hypers(self, hypers, error_type, expected_message): + with pytest.raises(error_type) as cm: + EllipsoidalDensityProjection(**hypers).transform(TEST_SINGLE_FRAME) + assert cm.message == expected_message + + def test_no_rotations(self): + frame = TEST_SINGLE_FRAME.copy() + _ = frame.arrays.pop("quaternion") + with pytest.warns() as cm: + EllipsoidalDensityProjection(**DEFAULT_HYPERS).transform([frame]) + str( + cm + ) == f"Frame 0 does not have rotations stored, this may cause errors down the line." From 2918faf40f4c26aaf7cb77730dbe1b83e7aa2859 Mon Sep 17 00:00:00 2001 From: arthur-lin1027 <35580059+arthur-lin1027@users.noreply.github.com> Date: Tue, 4 Jul 2023 15:21:51 -0500 Subject: [PATCH 09/13] 5 incorporate normalization factors (#6) * Added (and made default behavior) the ability to orthonormalize features that use the GTO basis. * This involves normalizing the features properly, creating an overlap matrix with orthogonal GTOs, and orthogonalizing the features. * Added relevant tests to test new orthonormality functionality * Added a jupyter notebook displaying how Lowdin Orthonormalization works (on a small gto basis set). --------- Co-authored-by: Rose K. Cersonsky <47536110+rosecers@users.noreply.github.com> Co-authored-by: Arthur Lin --- .../ellipsoidal_density_projection.py | 14 +- anisoap/representations/radial_basis.py | 168 ++++++++- .../GTO Orthogonalization Demonstration.ipynb | 351 ++++++++++++++++++ tests/requirements.txt | 1 - tests/test_ellipsoidal_density_projection.py | 21 ++ tests/test_moment_generator.py | 15 +- tests/test_radial_basis.py | 64 +++- tests/test_spherical_to_cartesian.py | 6 +- 8 files changed, 623 insertions(+), 17 deletions(-) create mode 100644 notebooks/GTO Orthogonalization Demonstration.ipynb diff --git a/anisoap/representations/ellipsoidal_density_projection.py b/anisoap/representations/ellipsoidal_density_projection.py index 9685e5e..66f79c8 100644 --- a/anisoap/representations/ellipsoidal_density_projection.py +++ b/anisoap/representations/ellipsoidal_density_projection.py @@ -12,9 +12,7 @@ from scipy.spatial.transform import Rotation from tqdm import tqdm -import anisoap.representations.radial_basis as radial_basis from anisoap.representations.radial_basis import RadialBasis -from anisoap.utils import compute_moments_inefficient_implementation from anisoap.utils.moment_generator import * from anisoap.utils.spherical_to_cartesian import spherical_to_cartesian @@ -400,7 +398,7 @@ def __init__( self.rotation_key = rotation_key - def transform(self, frames, show_progress=False): + def transform(self, frames, show_progress=False, normalize=True): """ Computes the features and (if compute_gradients == True) gradients for all the provided frames. The features and gradients are stored in @@ -411,6 +409,9 @@ def transform(self, frames, show_progress=False): List containing all ase.Atoms structures show_progress : bool Show progress bar for frame analysis + normalize: bool + Whether to perform Lowdin Symmetric Orthonormalization or not. Orthonormalization generally + leads to better performance. Default: True. Returns ------- None, but stores the projection coefficients and (if desired) @@ -488,5 +489,8 @@ def transform(self, frames, show_progress=False): ) features = contract_pairwise_feat(pairwise_ellip_feat, species) - - return features + if normalize: + normalized_features = self.radial_basis.orthonormalize_basis(features) + return normalized_features + else: + return features diff --git a/anisoap/representations/radial_basis.py b/anisoap/representations/radial_basis.py index 8a593dc..6528830 100644 --- a/anisoap/representations/radial_basis.py +++ b/anisoap/representations/radial_basis.py @@ -1,4 +1,90 @@ +import warnings + import numpy as np +import scipy.linalg +from equistore.core import TensorMap +from scipy.special import gamma + + +def inverse_matrix_sqrt(matrix: np.array): + """ + Returns the inverse matrix square root. + The inverse square root of the overlap matrix (or slices of the overlap matrix) yields the + orthonormalization matrix + Args: + matrix: np.array + Symmetric square matrix to find the inverse square root of + + Returns: + inverse_sqrt_matrix: S^{-1/2} + + """ + if not np.allclose(matrix, matrix.conjugate().T): + raise ValueError("Matrix is not hermitian") + eva, eve = np.linalg.eigh(matrix) + + if (eva < 0).any(): + raise ValueError( + "Matrix is not positive semidefinite. Check that a valid gram matrix is passed." + ) + return eve @ np.diag(1 / np.sqrt(eva)) @ eve.T + + +def gto_square_norm(n, sigma): + """ + Compute the square norm of GTOs (inner product of itself over R^3). + An unnormalized GTO of order n is \phi_n = r^n * e^{-r^2/(2*\sigma^2)} + The square norm of the unnormalized GTO has an analytic solution: + <\phi_n | \phi_n> = \int_0^\infty dr r^2 |\phi_n|^2 = 1/2 * \sigma^{2n+3} * \Gamma(n+3/2) + Args: + n: order of the GTO + sigma: width of the GTO + + Returns: + square norm: The square norm of the unnormalized GTO + """ + return 0.5 * sigma ** (2 * n + 3) * gamma(n + 1.5) + + +def gto_prefactor(n, sigma): + """ + Computes the normalization prefactor of an unnormalized GTO. + This prefactor is simply 1/sqrt(square_norm_area). + Scaling a GTO by this prefactor will ensure that the GTO has square norm equal to 1. + Args: + n: order of GTO + sigma: width of GTO + + Returns: + N: normalization constant + + """ + return np.sqrt(1 / gto_square_norm(n, sigma)) + + +def gto_overlap(n, m, sigma_n, sigma_m): + """ + Compute overlap of two *normalized* GTOs + Note that the overlap of two GTOs can be modeled as the square norm of one GTO, with an effective + n and sigma. All we need to do is to calculate those effective parameters, then compute the normalization. + <\phi_n, \phi_m> = \int_0^\infty dr r^2 r^n * e^{-r^2/(2*\sigma_n^2) * r^m * e^{-r^2/(2*\sigma_m^2) + = \int_0^\infty dr r^2 |r^{(n+m)/2} * e^{-r^2/4 * (1/\sigma_n^2 + 1/\sigma_m^2)}|^2 + = \int_0^\infty dr r^2 r^n_{eff} * e^{-r^2/(2*\sigma_{eff}^2) + prefactor. + ---Arguments--- + n: order of the first GTO + m: order of the second GTO + sigma_n: sigma parameter of the first GTO + sigma_m: sigma parameter of the second GTO + + ---Returns--- + S: overlap of the two normalized GTOs + """ + N_n = gto_prefactor(n, sigma_n) + N_m = gto_prefactor(m, sigma_m) + n_eff = (n + m) / 2 + sigma_eff = np.sqrt(2 * sigma_n**2 * sigma_m**2 / (sigma_n**2 + sigma_m**2)) + return N_n * N_m * gto_square_norm(n_eff, sigma_eff) class RadialBasis: @@ -7,9 +93,9 @@ class RadialBasis: This helps to keep a cleaner main code by avoiding if-else clauses related to the radial basis. - TODO: In the long run, this class would precompute quantities like - the normalization factors or orthonormalization matrix for the - radial basis. + Code relating to GTO orthonormalization is heavily inspired by work done in librascal, specifically this + codebase here: https://github.com/lab-cosmo/librascal/blob/8405cbdc0b5c72a5f0b0c93593100dde348bb95f/bindings/rascal/utils/radial_basis.py + """ def __init__(self, radial_basis, max_angular, **hypers): @@ -27,6 +113,12 @@ def __init__(self, radial_basis, max_angular, **hypers): num_n = (max_angular - l) // 2 + 1 self.num_radial_functions.append(num_n) + # As part of the initialization, compute the orthonormalization matrix for GTOs + # If we are using the monomial basis, set self.overlap_matrix equal to None + self.overlap_matrix = None + if self.radial_basis == "gto": + self.overlap_matrix = self.calc_gto_overlap_matrix() + # Get number of radial functions def get_num_radial_functions(self): return self.num_radial_functions @@ -57,3 +149,73 @@ def compute_gaussian_parameters(self, r_ij, lengths, rotation_matrix): center -= 1 / sigma**2 * np.linalg.solve(precision, r_ij) return precision, center + + def calc_gto_overlap_matrix(self): + """ + Computes the overlap matrix for GTOs. + The overlap matrix is a Gram matrix whose entries are the overlap: S_{ij} = \int_0^\infty dr r^2 phi_i phi_j + The overlap has an analytic solution (see above functions). + The overlap matrix is the first step to generating an orthonormal basis set of functions (Lodwin Symmetric + Orthonormalization). The actual orthonormalization matrix cannot be fully precomputed because each tensor + block use a different set of GTOs. Hence, we precompute the full overlap matrix of dim l_max, and while + orthonormalizing each tensor block, we generate the respective orthonormal matrices from slices of the full + overlap matrix. + + Returns: + S: 2D array. The overlap matrix + """ + # Consequence of the floor divide used to compute self.num_radial_functions + max_deg = self.max_angular + 1 + n_grid = np.arange(max_deg) + sigma = self.hypers["radial_gaussian_width"] + sigma_grid = np.ones(max_deg) * sigma + S = gto_overlap( + n_grid[:, np.newaxis], + n_grid[np.newaxis, :], + sigma_grid[:, np.newaxis], + sigma_grid[np.newaxis, :], + ) + return S + + def orthonormalize_basis(self, features: TensorMap): + """ + Apply an in-place orthonormalization on the features, using Lodwin Symmetric Orthonormalization. + Each block in the features TensorMap uses a GTO set of l + 2n, so we must take the appropriate slices of + the overlap matrix to compute the orthonormalization matrix. + An instructive example of Lodwin Symmetric Orthonormalization of a 2-element basis set is found here: + https://booksite.elsevier.com/9780444594365/downloads/16755_10030.pdf + + Parameters: + features: A TensorMap whose blocks' values we wish to orthonormalize. Note that features is modified in place, so a + copy of features must be made before the function if you wish to retain the unnormalized values. + radial_basis: An instance of RadialBasis + + Returns: + normalized_features: features containing values multiplied by proper normalization factors. + """ + # In-place modification. + radial_basis_name = self.radial_basis + if radial_basis_name != "gto": + warnings.warn( + f"Normalization has not been implemented for the {radial_basis_name} basis, and features will not be normalized.", + UserWarning, + ) + return features + for label, block in features.items(): + l = label["angular_channel"] + n_arr = block.properties["n"].values.flatten() + l_2n_arr = l + 2 * n_arr + # normalize all the GTOs by the appropriate prefactor first, since the overlap matrix is in terms of + # normalized GTOs + prefactor_arr = gto_prefactor( + l_2n_arr, self.hypers["radial_gaussian_width"] + ) + block.values[:, :, :] = block.values[:, :, :] * prefactor_arr + + gto_overlap_matrix_slice = self.overlap_matrix[l_2n_arr, :][:, l_2n_arr] + orthonormalization_matrix = inverse_matrix_sqrt(gto_overlap_matrix_slice) + block.values[:, :, :] = np.einsum( + "ijk,kl->ijl", block.values, orthonormalization_matrix + ) + + return features diff --git a/notebooks/GTO Orthogonalization Demonstration.ipynb b/notebooks/GTO Orthogonalization Demonstration.ipynb new file mode 100644 index 0000000..dcd1903 --- /dev/null +++ b/notebooks/GTO Orthogonalization Demonstration.ipynb @@ -0,0 +1,351 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2aaf2214", + "metadata": {}, + "source": [ + "# GTO Orthogonalization Demonstration\n", + "\n", + "The aim of this notebook is to demonstrate Lowdin Symmetric Orthogonalization on a set of Gaussian Type Orbitals (GTOs).\n", + "* A more detailed overview of Lowdin Symmetric Orthogonalization is found here https://booksite.elsevier.com/9780444594365/downloads/16755_10030.pdf\n", + "* An interactive Desmos demonstration with a 2-element GTO set can be found here: https://www.desmos.com/calculator/1rllgc8iwb\n", + "* **Note: In practice, we actually first normalize our GTOs, then orthogonalize, rather than directly orthonormalizing an unnormalized basis. This is much more numerically stable, because the overlap matrix for unnormalized GTOs becomes ill-conditioned at high degrees (You can test this yourself in the notebook below)**\n", + "* TODO: I will also show some benchmarks to show that orthogonalizing the GTO basis yields better results for AniSOAP" + ] + }, + { + "cell_type": "markdown", + "id": "beb27a82", + "metadata": {}, + "source": [ + "# Nonorthogonalized GTOs:\n", + "\n", + "* Unnormalized GTOs of degree n is a monomial of degree n multiplied by a gaussian: $\\phi_n(r) = r^n * e^{-r^2/(2*\\sigma_n^2)}$\n", + "* This unnormalized GTO has a finite square-integral over $\\mathbb{R}^3$: $I_n = \\int_0^\\infty {|\\phi_n(r)|^2*r^2 dr} = \\frac{1}{2}*\\sigma_n^{2n+3}*\\Gamma(\\frac{2n+3}{2})$. \n", + "* Hence the appropriate normalization factor to use is $N_n = 1/\\sqrt{I_n}$\n", + "* The normalized GTOs are thus $\\hat{\\phi}_n(r) = N_n*\\phi_n(r)$ \n", + "* **Note that these GTOs are not yet orthogonal!**\n" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "ef49a95d", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGdCAYAAADAAnMpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABxfUlEQVR4nO3dd3xUZdbA8d+UZNJ7SIE0IEDoSFAJgkgVEF3UFRdXFMvKoovKrgV8baDw6q7I6yKWdRULIKuCootKAGmCSAsiIB1CSQghpPeZ+/5xMwMhbSZTE87385lPhjt37n0yuDuH85znPBpFURSEEEIIIdxE6+4BCCGEEOLKJsGIEEIIIdxKghEhhBBCuJUEI0IIIYRwKwlGhBBCCOFWEowIIYQQwq0kGBFCCCGEW0kwIoQQQgi30rt7ANYwmUycOXOGwMBANBqNu4cjhBBCCCsoikJRURGxsbFotQ3nP1pEMHLmzBni4uLcPQwhhBBCNMPJkydp165dg6+3iGAkMDAQUH+ZoKAgN49GCCGEENYoLCwkLi7O8j3ekBYRjJinZoKCgiQYEUIIIVqYpkospIBVCCGEEG4lwYgQQggh3EqCESGEEEK4VYuoGRFCCE9jNBqpqqpy9zCEcCudToder7e77YYEI0IIYaPi4mJOnTqFoijuHooQbufn50dMTAze3t7NvoYEI0IIYQOj0cipU6fw8/MjMjJSGjGKK5aiKFRWVnLu3DmOHTtGcnJyo43NGiPBiBBC2KCqqgpFUYiMjMTX19fdwxHCrXx9ffHy8uLEiRNUVlbi4+PTrOtIAasQQjSDZESEUDU3G1LrGg4YhxBCCCFEs9kcjGzYsIGxY8cSGxuLRqPhyy+/bPT8ZcuWMXz4cCIjIwkKCqJ///58//33zR2vEEIIIVoZm4ORkpISevXqxfz58606f8OGDQwfPpyVK1eyY8cObrjhBsaOHcuuXbtsHqwQQghxqcGDB/PYY4+5exjCTjYHI6NGjeKll17i1ltvter8efPm8eSTT9KvXz+Sk5OZPXs2ycnJfP311zYPVgghRMuRlZXFhAkT6Ny5M1qt1ilBw7Jly5g1a5bDr9sa7N27l9tuu43ExEQ0Gg3z5s2z6n179uzh+uuvx9fXl7Zt2zJz5kynL2N3ec2IyWSiqKiIsLCwBs+pqKigsLCw1kOodpzdwT93/ZOMnAx3D0UIIRpVUVFBZGQkzzzzDL169XLKPcLCwprcEfZKVVpaSvv27fnf//1foqOjrXpPYWEhw4cPJzY2lm3btvHPf/6Tf/zjH8ydO9epY3V5MPLaa69RUlLCHXfc0eA5c+bMITg42PKIi4tz4Qg915rMNUz6bhLv/vIu93x3DxtObXD3kIS44imKQmlltVsetvxrdfDgwUydOpUnn3ySsLAwoqOjeeGFF5z3wQCJiYn83//9HxMnTiQ4OLjZ11mwYAHJycn4+PgQFRXF7bffbnnt8mmarKwsxowZg6+vL0lJSSxevJjExMRaWQGNRsM777zDTTfdhJ+fHykpKWzZsoXDhw8zePBg/P396d+/P0eOHLG858iRI9xyyy1ERUUREBBAv379WL16tU2fxezZs7nvvvsIDAwkPj6ed999t9mfiTX69evH3//+d+68804MBoNV71m0aBHl5eUsXLiQ7t27c+uttzJjxgzmzp3r1OyIS/uMLFmyhBdeeIGvvvqKNm3aNHje9OnTmTZtmuXPhYWFV3xAUmGs4OWfXkZBIdQQyoWKC8z6aRbf3voteq20ixHCXcqqjHR9zj1F+ftmjsTP2/r//X/44YdMmzaNrVu3smXLFu69914GDBjA8OHD6z1/0aJFPPTQQ41e85133uGuu+6yady22L59O1OnTuXjjz8mLS2NvLw8Nm7c2OD5EydOJDc3l3Xr1uHl5cW0adPIycmpc96sWbOYO3cuc+fO5amnnmLChAm0b9+e6dOnEx8fz3333ccjjzzCt99+C6hdd0ePHs1LL72Ej48PH374IWPHjuXAgQPEx8db9bu89tprzJo1ixkzZvD555/z5z//mUGDBtGlS5d6z589ezazZ89u9JrffvstAwcOtOr+1tiyZQvXX399reBl5MiRTJ8+nePHj5OUlOSwe13KZd9iS5cu5f777+ezzz5j2LBhjZ5rMBisjuKuFKuOr+Jc2Tmi/aNZdvMyblp+E9kl2fxw8geGJ9T/fyRCCHGpnj178vzzzwOQnJzM/PnzWbNmTYPByM0338w111zT6DWjoqIcPs5LZWZm4u/vz0033URgYCAJCQn06dOn3nN/++03Vq9ezbZt20hNTQXgvffeIzk5uc65kyZNsmTon3rqKfr378+zzz7LyJEjAXj00UeZNGmS5fxevXrVmmp66aWXWL58OStWrOCR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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "from scipy.special import gamma\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Visualize a set of 3 normalized GTOs, with random widths and orders\n", + "\n", + "def gto_norm(r, n, sigma_n):\n", + " I_n = 0.5 * sigma_n**(2 * n + 3) * gamma(n + 1.5)\n", + " N_n = 1/np.sqrt(I_n)\n", + " return N_n * r**n * np.exp(-r**2 / (2 * sigma_n**2))\n", + "\n", + "sigma_n_arr = np.array([1, 0.8, 0.5])\n", + "n_arr = np.array([1, 6, 4])\n", + "\n", + "r_grid = np.linspace(0,10,1000)\n", + "\n", + "for i in range(3):\n", + " sigma_n = sigma_n_arr[i]\n", + " n = n_arr[i]\n", + " plt.plot(r_grid, gto_norm(r_grid, n, sigma_n))\n", + "\n", + "plt.legend([f\"n = {n_arr[0]}, sigma_n = {sigma_n_arr[0]}\",\n", + " f\"n = {n_arr[1]}, sigma_n = {sigma_n_arr[1]}\",\n", + " f\"n = {n_arr[2]}, sigma_n = {sigma_n_arr[2]}\"])\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "3f219534", + "metadata": {}, + "source": [ + "Now, we numerically integrate different cases to verify that the GTOs are not yet orthonormal:\n", + "Note that the overlap does have an analytic integral, but to prove a point, we just use (pretty bad trapezoidal) numeric integration" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "6dfa84c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "overlap of identical GTOs 1.0000000000000002\n", + "overlap of nonidentical GTOs 0.742989097543302\n" + ] + } + ], + "source": [ + "import scipy.integrate as integrate\n", + "\n", + "# Note that the integral of a product of the same GTOs (i.e. overlap of same GTOs) have square norm of 1 \n", + "# (i.e. the normalization factor is correct)\n", + "n1 = n2 = n_arr[0]\n", + "sigma_n1 = sigma_n2 = sigma_n_arr[0]\n", + "\n", + "result_identical = np.trapz(gto_norm(r_grid, n1, sigma_n1) * gto_norm(r_grid, n2, sigma_n2) * r_grid**2, r_grid)\n", + "print(\"overlap of identical GTOs\", result_identical)\n", + "\n", + "# But are not yet orthoganol: Here, we are looking at the overlap of two GTOs\n", + "\n", + "n1 = n_arr[0]\n", + "n2 = n_arr[1]\n", + "sigma_n1 = sigma_n_arr[0]\n", + "sigma_n2 = sigma_n_arr[1]\n", + "\n", + "result_nonidentical = np.trapz(gto_norm(r_grid, n1, sigma_n1) * gto_norm(r_grid, n2, sigma_n2) * r_grid**2, r_grid)\n", + "print(\"overlap of nonidentical GTOs\", result_nonidentical)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "8e07236c", + "metadata": {}, + "source": [ + "### Now we apply Lodwin's Symmetric Orthonormalization, which can orthonormalize our basis set, using the following steps\n", + "1. Find the overlap matrix, whose entries are the overlap integral between two *unnormalized* GTOs: $S_{ij} = \\int_0^\\infty \\phi_i \\phi_j r^2 dr$. This matrix is a gram matrix and will hence be hermitian and positive definite\n", + "2. Calculate $S^{-1/2}$, which requires diagonalizing the matrix:\n", + " * $S = MDM^T$\n", + " * $S^{-1/2} = M D^{-1/2} M^T$\n", + " * where $D^{-1/2}$ just takes the recipricol square root of each element in the diagonal\n", + "3. $S^{-1/2}$ is the orthonormalization matrix. Hence, given a basis set $\\underline{\\phi} = [\\phi_1, ..., \\phi_n]$ with corresponding $\\sigma_1, ..., \\sigma_n$, we can generate orthonormal functions $\\underline{\\Phi} = \\Phi_1, ..., \\Phi_n$ with $\\underline{\\Phi} = S^{-1/2}\\underline{\\phi}$\n", + "\n", + "In other words, each new orthonormal basis functions are just linear combinations of our original basis funcitions. \n", + "\n", + "Below, I just pasted a bunch of utility functions below that help us create the overlap and orthonormalization matrices:" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "37cb661d", + "metadata": {}, + "outputs": [], + "source": [ + "def gto(r, n, sigma_n):\n", + " return r**n * np.exp(-r**2 / (2 * sigma_n**2))\n", + "\n", + "def inverse_matrix_sqrt(matrix: np.array):\n", + " \"\"\"\n", + " Returns the inverse matrix square root.\n", + " The inverse square root of the overlap matrix (or slices of the overlap matrix) yields the\n", + " orthonormalization matrix\n", + " Args:\n", + " matrix: np.array\n", + " Symmetric square matrix to find the inverse square root of\n", + "\n", + " Returns:\n", + " inverse_sqrt_matrix: S^{-1/2}\n", + "\n", + " \"\"\"\n", + " if not np.allclose(matrix, matrix.T):\n", + " raise ValueError(\"Matrix is not hermitian\")\n", + " eva, eve = np.linalg.eigh(matrix)\n", + "\n", + " if (eva < 0).all():\n", + " raise ValueError(\"Matrix is not positive semidefinite. Check that a valid gram matrix is passed.\")\n", + " return eve @ np.diag(1/np.sqrt(eva)) @ eve.T\n", + "\n", + "\n", + "def gto_square_norm(n, sigma):\n", + " \"\"\"\n", + " Compute the square norm of GTOs (inner product of itself over R^3).\n", + " A GTO of order n is \\phi_n = r^n * e^{-r^2/(2*\\sigma^2)}\n", + " The square norm of the GTO has an analytic solution:\n", + " <\\phi_n | \\phi_n> = \\int_0^\\infty dr r^2 |\\phi_n|^2 = 1/2 * \\sigma^{2n+3} * \\Gamma(n+3/2)\n", + " Args:\n", + " n: order of the GTO\n", + " sigma: width of the GTO\n", + "\n", + " Returns:\n", + " square norm: The square norm of the GTO\n", + " \"\"\"\n", + " return 0.5 * sigma**(2 * n + 3) * gamma(n + 1.5)\n", + "\n", + "\n", + "def gto_prefactor(n, sigma):\n", + " \"\"\"\n", + " Computes the normalization prefactor of a GTO.\n", + " This prefactor is simply 1/sqrt(square_norm_area).\n", + " Scaling a GTO by this prefactor will ensure that the GTO has square norm equal to 1.\n", + " Args:\n", + " n: order of GTO\n", + " sigma: width of GTO\n", + "\n", + " Returns:\n", + " N: normalization constant\n", + "\n", + " \"\"\"\n", + " return np.sqrt(1 / gto_square_norm(n, sigma))\n", + "\n", + "\n", + "def gto_overlap(n, m, sigma_n, sigma_m):\n", + " \"\"\"\n", + " Compute overlap of two GTOs\n", + " Note that the overlap of two GTOs can be modeled as the square norm of one GTO, with an effective\n", + " n and sigma. All we need to do is to calculate those effective parameters, then compute the normalization.\n", + " <\\phi_n, \\phi_m> = \\int_0^\\infty dr r^2 r^n * e^{-r^2/(2*\\sigma_n^2) * r^m * e^{-r^2/(2*\\sigma_m^2)\n", + " = \\int_0^\\infty dr r^2 |r^{(n+m)/2} * e^{-r^2/4 * (1/\\sigma_n^2 + 1/\\sigma_m^2)}|^2\n", + " = \\int_0^\\infty dr r^2 r^n_{eff} * e^{-r^2/(2*\\sigma_{eff}^2)\n", + " prefactor.\n", + " ---Arguments---\n", + " n: order of the first GTO\n", + " m: order of the second GTO\n", + " sigma_n: sigma parameter of the first GTO\n", + " sigma_m: sigma parameter of the second GTO\n", + "\n", + " ---Returns---\n", + " S: overlap of the two GTOs\n", + " \"\"\"\n", + " n_eff = (n + m) / 2\n", + " sigma_eff = np.sqrt(2 * sigma_n**2 * sigma_m**2 / (sigma_n**2 + sigma_m**2))\n", + " return gto_square_norm(n_eff, sigma_eff)" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "30d945bd", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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RgD+vv60DGsJQGpWap3aICIi4i31KryA83skTJo0iaysLMcjLS3N8Da64rs9thB2cZummJ3caPFcEwbYel++/PkIe4/nlnvtQNYBABLDE6s83z5FWuFFREQamnoVXqraI8FsNtO0acXaDYCAgADCw8PLPeqztXtOAHBp28p/ntrq0jyCQZ1isVrhXyvL977Yi3XtvSuVUXgREZGGql6Fl969e7N06dJyzy1ZsoTk5GRDp615SmFJGRv2nwSgb9tol6/34EBb78sXKUc4cCLP8fyhXNsaMi1Cq97DwhFeshVeRESkYTE0vOTm5pKSkuKYEpaamkpKSopjS/BJkyZx6623Oo6/7777OHDgABMnTmTHjh288847vP322zz66KNGNtNjNh88RVGphZiwANrGuD6Xv3tL255IZRYrb6y0reuSX5LPyUJbQFLPi4hIef379+fhhx/2djPERYaGl40bNzr2SQCYOHEiPXv25JlnngHg6NGjjiADkJSUxMKFC1m1ahXnn38+U6ZM4R//+EejmSa9cf8pwFbv4q7pyX8aYFuE7tPNh0g7me8YMgrzDyPcv+ohNHt4ycjPILc4t8rjREQ8qaioiKeeeorWrVsTEBDAeeedxzvvvOO263/22WdMmTLFbddrbD799FM6d+5MQEAAnTt3Zv78+TWe880333DJJZcQFhbmWJctNdXYX4wNXeelf//+1W7+N3v27ArPXX755WzevNnAVnnPpgO28JLcum6zjCrTq3UTLm0bzXd7Mpm5ei+Dk201NS1Dq+51AQj3Dyc6KJrMgkz2Z++na3RXt7VJRKSubrjhBo4dO8bbb79N27ZtycjIoLS01G3Xd2XX5cZu3bp13HjjjUyZMoXRo0czf/58brjhBr777jsuvvjiSs/Zt28fo0aNYuLEicydO5esrCweeeQRrrnmGrZs2WJYW+tVzUtjZrFY2XzQFl56uTG8APzpzMyj/208xPbj+4Hqh4zsNHQk4gVWKxTneefhxD68/fv358EHH+Txxx8nKiqKuLg4nnvuOePeF2z7Fq1evZqFCxcyaNAgEhMTueiii+jTp49T13njjTdo164dgYGBxMbGct111zle++2w0dGjRxkxYgRBQUEkJSXx4YcfkpiY6FgsFWyzXd966y2uuuoqgoOD6dSpE+vWrWPPnj3079+fkJAQevfuXW635b17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9by080B4OVF4Aqg5vAB0bxnJx/f2JiYsgJ3pOfz3B1s71fMiIvXF999/j9ls5vzzz3frdT/77DOmTJni1ms2Jp9++imdO3cmICCAzp07M3/+/GqP379/PyaTqcJj8eLFhrZT4cVAu84MG3WIM37FxJMFtet5sWsfG8b/7utNyyZBZJyybZ51MPuIYe0TEamtrKwsbr31VkP2FoqKinJp5+vGbN26ddx4443ccsst/PTTT9xyyy3ccMMNrF+/vsZzly1bxtGjRx2PAQMGGNpWhReDWK3WszON6lnPi13rpiF8cl8fWobZFqo7knOUHUezDGmfiNhYrVbyS/K98nBmH97+/fvz4IMP8vjjjxMVFUVcXBzPPfeccW/MOe69917Gjh1L796963T+G2+8Qbt27QgMDCQ2NpbrrrvO8dpvh42OHj3KiBEjCAoKIikpiQ8//JDExERef/11xzEmk4m33nqLq666iuDgYDp16sS6devYs2cP/fv3JyQkhN69e5fbbXnv3r2MGjWK2NhYQkNDufDCC1m2bFmtf4bExESmTp3KHXfcQVhYGAkJCcyaNatO70dtvf766wwePJhJkybRsWNHJk2axMCBA8u9F1Vp2rQpcXFxjoe/v7+hbdUKuwY5nlvE6fwSfExwXjMP9LycqXn57TTpmsRFBDLvjmEM/fwF8ClhzNvLmXv7QMM3kRT5vSooLeDiDy/2yr3Xj11PsF/ttymZM2cOEydOZP369axbt45x48bRt29fBg8eXOnxc+fO5d577632mm+99RY33XRTla+/++677N27lw8++IAXX3yx1m2127hxIw8++CDvv/8+ffr04eTJk6xZs6bK42+99VYyMzMd+xlNnDiRjIyMCsdNmTKF6dOnM336dJ544gnGjh1LmzZtmDRpEgkJCdxxxx1MmDCBRYsWAZCbm8vw4cN58cUXCQwMZM6cOYwcOZJdu3aRkJBQq5/l1VdfZcqUKUyePJlPPvmE+++/n379+tGxY8dKj586dSpTp06t9pqLFi3isssuq/S1devW8cgjj5R7bujQobUKL1dffTWFhYW0a9eORx55pFxgNILCi0F+TbcV6yY2DSHQz9fw+9mHjZoGOhdeAJpHhBEV0JSTRSfIKclk3Ls/8sl9fUiMDnF3M0WkAenevTvPPvssAO3atWPGjBksX768yvBy9dVXc/HF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ePtERKRxUHipA5PJ5NGdpe21NfV5yMhueJvhRAdFk56Xznvb33M8X1xWzNPfP43FamFI6yF0btrZi60UEZGGTOGljuxDR9nF2Ybfy97zUl9nGp0rwDeAib0mAvBGyht8e+hb8kryePzbx9lxcgfh/uFMuniSl1spIiINWf0toKjnPDld2n6PhtDzAnBVm6tYlbaKJQeW8MDyB/A1+VJmLcPsY2Z6/+lEB0V7u4kiItKAqeeljjw5bGS/R32vebEzmUxMu2wa17W/Dh+TD2XWMlqEtmDW4FmObQRERETqytDwcurUKW655RYiIiKIiIjglltu4fTp09WeM27cOEwmU7nHJZdcYmQz68STw0b2npf6uK9RVfx9/Xm297Msv345X43+ioXXLOTCuAu93SwREWkEDB02Gjt2LIcOHWLx4sUA3HPPPdxyyy18+eWX1Z43bNgw3n33Xcf3/v7+RjazTjw5bNRQZhtVJjooWsNEIiLiVoaFlx07drB48WJ++OEHLr7YNlTw73//m969e7Nr1y46dOhQ5bkBAQHExcUZ1TS30GwjERER7zBs2GjdunVEREQ4ggvAJZdcQkREBGvXrq323FWrVhETE0P79u25++67ycjIqPLYoqIisrOzyz08QbONREREvMOw8JKenk5MTEyF52NiYkhPT6/yvCuvvJK5c+eyYsUKXn31VTZs2MCAAQMoKiqq9Php06Y5amoiIiJo1aqV236G6mi2kYiIiHc4HV6ee+65CgW1v31s3LgRsM06+S2r1Vrp83Y33ngjI0aMoGvXrowcOZJFixbx66+/8vXXX1d6/KRJk8jKynI80tLSnP2R6sQrs40aUMGuiIiIUZyueZkwYQJjxoyp9pjExER+/vlnjh07VuG148ePExsbW+v7xcfH07p1a3bv3l3p6wEBAQQEBNT6eu5i73nxxM7SDblgV0RExN2cDi/R0dFER9c8e6R3795kZWXx448/ctFFFwGwfv16srKy6NOnT63vd+LECdLS0oiPj3e2qYay94LYN000kmOqtMKLiIiIcTUvnTp1YtiwYdx999388MMP/PDDD9x9991cddVV5WYadezYkfnzbTsQ5+bm8uijj7Ju3Tr279/PqlWrGDlyJNHR0YwePdqoptZJmN+ZmhctUiciIuJRhi5SN3fuXLp168aQIUMYMmQI3bt35/333y93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Wb16NQ888AA//PADS5cupbS0lCFDhpCXl+ftpjVaGzZsYNasWXTv3t3bTWl0Tp06Rd++ffHz82PRokVs376dV199lcjISG83rVF5+eWXmTlzJjNmzGDHjh288sor/PWvf+Wf//ynt5vW4OXl5dGjRw9mzJhR6euvvPIK06dPZ8aMGWzYsIG4uDgGDx7s2GPQUFap0UUXXWS97777yj3XsWNH65NPPumlFv0+ZGRkWAHr6tWrvd2URiknJ8farl0769KlS62XX3659aGHHvJ2kxqVJ554wnrppZd6uxmN3ogRI6x33HFHueeuueYa68033+ylFjVOgHX+/PmO7y0WizUuLs76l7/8xfFcYWGhNSIiwjpz5kzD26OelxoUFxezadMmhgwZUu75IUOGsHbtWi+16vchKysLgKioKC+3pHF64IEHGDFiBIMGDfJ2UxqlBQsWkJyczPXXX09MTAw9e/bk3//+t7eb1ehceumlLF++nF9//RWAn376ie+++47hw4d7uWWNW2pqKunp6eU+GwMCArj88ss98tnY6DZmdLfMzEzKysqIjY0t93xsbCzp6elealXjZ7VamThxIpdeeildu3b1dnManY8++ojNmzezYcMGbzel0dq3bx9vvvkmEydOZPLkyfz44488+OCDBAQEcOutt3q7eY3GE088QVZWFh07dsTX15eysjJeeukl/vjHP3q7aY2a/fOvss/GAwcOGH5/hZdaMplM5b63Wq0VnhP3mTBhAj///DPfffedt5vS6KSlpfHQQw+xZMkSAgMDvd2cRstisZCcnMzUqVMB6NmzJ9u2bePNN99UeHGjjz/+mA8++IAPP/yQLl26kJKSwsMPP0zz5s257bbbvN28Rs9bn40KLzWIjo7G19e3Qi9LRkZGhcQp7vGnP/2JBQsW8O2339KyZUtvN6fR2bRpExkZGfTq1cvxXFlZGd9++y0zZsygqKgIX19fL7awcYiPj6dz587lnuvUqROffvqpl1rUOD322GM8+eSTjBkzBoBu3bpx4MABpk2bpvBioLi4OMDWAxMfH+943lOfjap5qYG/vz+9evVi6dKl5Z5funQpffr08VKrGier1cqECRP47LPPWLFiBUlJSd5uUqM0cOBAtm7dSkpKiuORnJzMTTfdREpKioKLm/Tt27fCVP9ff/2V1q1be6lFjVN+fj4+PuU/ynx9fTVV2mBJSUnExcWV+2wsLi5m9erVHvlsVM9LLUycOJFbbrmF5ORkevfuzaxZszh48CD33Xeft5vWqDzwwAN8+OGHfPHFF4SFhTl6uyIiIggKCvJy6xqPsLCwCnVEISEhNG3aVPVFbvTII4/Qp08fpk6dyg033MCPP/7IrFmzmDVrlreb1qiMHDmSl156iYSEBLp06cKWLVuYPn06d9xxh7eb1uDl5uayZ88ex/epqamkpKQQFRVFQkICDz/8MFOnTqVdu3a0a9eOqVOnEhwczNixY41vnOHzmRqJf/3rX9bWrVtb/f39rRdccIGm7xoAqPTx7rvvertpjZ6mShvjyy+/tHbt2tUaEBBg7dixo3XWrFneblKjk52dbX3ooYesCQkJ1sDAQGubNm2sTz31lLWoqMjbTWvwVq5cWem/ybfddpvVarVNl3722WetcXFx1oCAAGu/fv2sW7du9UjbTFar1Wp8RBIRERFxD9W8iIiISIOi8CIiIiINisKLiIiINCgKLyIiItKgKLyIiIhIg6LwIiIiIg2KwouIiIg0KAovIiIi0qAovIiIiEiDovAiIiIiDYrCi4iIiDQoCi8iIiLSoPw/DpzqDoqQ5nMAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "overlap_matrix = gto_overlap(n_arr[:, np.newaxis],\n", + " n_arr[np.newaxis, :],\n", + " sigma_n_arr[:, np.newaxis],\n", + " sigma_n_arr[np.newaxis, :])\n", + "orthonormalization_matrix = inverse_matrix_sqrt(overlap_matrix)\n", + "\n", + "# The original nonorthonormalized basis set is (these aren't normalized either!):\n", + "original_gto = np.array([gto(r_grid, n_arr[0], sigma_n_arr[0]),\n", + " gto(r_grid, n_arr[1], sigma_n_arr[1]),\n", + " gto(r_grid, n_arr[2], sigma_n_arr[2])])\n", + "plt.plot(r_grid, original_gto.T)\n", + "plt.legend([f\"n = {n_arr[0]}, sigma_n = {sigma_n_arr[0]}\",\n", + " f\"n = {n_arr[1]}, sigma_n = {sigma_n_arr[1]}\",\n", + " f\"n = {n_arr[2]}, sigma_n = {sigma_n_arr[2]}\"])\n", + "\n", + "plt.show()\n", + "\n", + "# Now, plot orthonormalized basis\n", + "orthonormal_gto = orthonormalization_matrix @ original_gto\n", + "\n", + "plt.plot(r_grid, orthonormal_gto.T)\n", + "plt.legend([f\"n = {n_arr[0]}, sigma_n = {sigma_n_arr[0]}\",\n", + " f\"n = {n_arr[1]}, sigma_n = {sigma_n_arr[1]}\",\n", + " f\"n = {n_arr[2]}, sigma_n = {sigma_n_arr[2]}\"])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "bcb54f34", + "metadata": {}, + "source": [ + "These new functions are orthonormal:" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "6531fee7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "overlap of identical orthonormalized GTOs 0.9999999999999972\n", + "overlap of nonidentical orthonormalized GTOs 5.257868827118135e-16\n" + ] + } + ], + "source": [ + "import scipy.integrate as integrate\n", + "\n", + "# Two identical functions have a overlap of 1:\n", + "\n", + "result_identical = np.trapz(orthonormal_gto[0, :] * orthonormal_gto[0, :] * r_grid**2, r_grid)\n", + "print(\"overlap of identical orthonormalized GTOs\", result_identical)\n", + "\n", + "# Nonidentical functions have an overlap of 0:\n", + "\n", + "result_nonidentical = np.trapz(orthonormal_gto[0, :] * orthonormal_gto[1, :] * r_grid**2, r_grid)\n", + "print(\"overlap of nonidentical orthonormalized GTOs\", result_nonidentical)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21ca4689", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:anisoap] *", + "language": "python", + "name": "conda-env-anisoap-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/requirements.txt b/tests/requirements.txt index 74c415f..efdb65b 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,6 +1,5 @@ ase coverage[toml] -numpy pytest scipy tqdm diff --git a/tests/test_ellipsoidal_density_projection.py b/tests/test_ellipsoidal_density_projection.py index 262a68e..783feb5 100644 --- a/tests/test_ellipsoidal_density_projection.py +++ b/tests/test_ellipsoidal_density_projection.py @@ -1,10 +1,12 @@ import builtins import ase +import equistore import numpy as np import pytest from anisoap.representations import EllipsoidalDensityProjection +from numpy.testing import assert_allclose def add_default_params(frame): @@ -64,6 +66,25 @@ def test_frames_matrix_rotation(self, frames): rotation_key="matrix", rotation_type="matrix", **DEFAULT_HYPERS ).transform(frames, show_progress=True) + @pytest.mark.parametrize("frames", TEST_FRAMES) + def test_frames_normalization_condition(self, frames): + edp = EllipsoidalDensityProjection( + rotation_key="matrix", rotation_type="matrix", **DEFAULT_HYPERS + ) + rep_unnormalized = edp.transform(frames, normalize=False) + rep_normalized_1 = edp.transform(frames, normalize=True) + rep_normalized_2 = edp.radial_basis.orthonormalize_basis(rep_unnormalized) + + # Would do this, but failing GitHub CI for older versions of python (possibly b/c it's + # building an older version of equistore)? + # assert equistore.allclose(rep_normalized_1, rep_normalized_2) + for i in range(len(rep_unnormalized.blocks())): + block_norm_1 = rep_normalized_1.block(i) + block_norm_2 = rep_normalized_2.block(i) + assert_allclose( + block_norm_1.values, block_norm_2.values, rtol=1e-10, atol=1e-10 + ) + class TestBadInputs: """ diff --git a/tests/test_moment_generator.py b/tests/test_moment_generator.py index 10d7fb0..025d281 100644 --- a/tests/test_moment_generator.py +++ b/tests/test_moment_generator.py @@ -1,13 +1,18 @@ import numpy as np import pytest -from scipy.special import gamma, comb from numpy.testing import assert_allclose +from scipy.special import ( + comb, + gamma, +) # Import the different versions of the moment generators -from anisoap.utils import compute_moments_single_variable -from anisoap.utils import compute_moments_inefficient_implementation -from anisoap.utils import compute_moments_diagonal_inefficient_implementation -from anisoap.utils import assert_close +from anisoap.utils import ( + assert_close, + compute_moments_diagonal_inefficient_implementation, + compute_moments_inefficient_implementation, + compute_moments_single_variable, +) class TestMomentsUnivariateGaussian: diff --git a/tests/test_radial_basis.py b/tests/test_radial_basis.py index 0d28ce8..1cc925d 100644 --- a/tests/test_radial_basis.py +++ b/tests/test_radial_basis.py @@ -1,10 +1,10 @@ import numpy as np import pytest from numpy.testing import assert_allclose +from scipy.spatial.transform import Rotation # internal imports from anisoap.representations import RadialBasis, radial_basis -from scipy.spatial.transform import Rotation class TestNumberOfRadialFunctions: @@ -12,6 +12,10 @@ class TestNumberOfRadialFunctions: Test that the number of radial basis functions is correct. """ + def test_notimplemented_basis(self): + with pytest.raises(ValueError): + basis = RadialBasis(radial_basis="nonsense", max_angular=5) + def test_radial_functions_n5(self): basis_gto = RadialBasis(radial_basis="monomial", max_angular=5) num_ns = basis_gto.get_num_radial_functions() @@ -105,3 +109,61 @@ def test_limit_small_sigma(self, sigma, r_ij, lengths, rotation_matrix): atol = 1e-15 / sigma**2 # largest elements of matrix will be 1/sigma^2 assert_allclose(center_gto, center_ref, rtol=1e-10, atol=atol) assert_allclose(prec_gto, prec_ref, rtol=1e-10, atol=atol) + + +class TestGTOUtils: + # Create a list of semipositive definite matrices (spd), seminegative definite matrices (snd), and + # nonsymmetric matrices for testing + + spd_matrices = [] + snd_matrices = [] + nonsym_matrices = [] + for _ in range(100): + dim = np.random.randint(2, 100) + A = np.random.rand(dim, dim) + spd = A @ A.T + spd_matrices.append(spd) + snd_matrices.append(-spd) + nonsym_matrices.append(np.random.random(size=(dim, dim))) + + num_trials = 100 + basis_sizes = np.random.randint(2, 15, num_trials) + + def test_nogto_warning(self): + with pytest.warns(UserWarning): + lmax = 5 + non_gto_basis = RadialBasis("monomial", lmax) + # As a proxy for a tensor map, pass in a numpy array for features + features = np.random.random((5, 5)) + non_normalized_features = non_gto_basis.orthonormalize_basis(features) + assert_allclose(features, non_normalized_features) + + @pytest.mark.parametrize("spd", spd_matrices) + def test_spd_inverse_sqrt_no_exceptions(self, spd): + # Assert that exception is never raised for semipositive definite matrices + try: + radial_basis.inverse_matrix_sqrt(spd) + except ValueError: + assert ( + False + ), f"calling inverse matrix square root on {spd} raised a value error" + + @pytest.mark.parametrize("snd", snd_matrices) + def test_npd_inverse_sqrt_all_exceptions(self, snd): + # Assert that exception is ALWAYS raised for seminegative definite matrices + with pytest.raises(ValueError): + radial_basis.inverse_matrix_sqrt(snd) + + @pytest.mark.parametrize("nonsym", nonsym_matrices) + def test_nonsymmetric_inverse_sqrt_all_exceptions(self, nonsym): + # Assert that exception is ALWAYS raised for nonsymmetric matrices + with pytest.raises(ValueError): + radial_basis.inverse_matrix_sqrt(nonsym) + + @pytest.mark.parametrize("spd", spd_matrices) + def test_spd_inverse_sqrt(self, spd): + dim = np.shape(spd)[0] + inv_sqrt_s = radial_basis.inverse_matrix_sqrt(spd) + assert_allclose( + np.eye(dim), inv_sqrt_s @ inv_sqrt_s @ spd, rtol=1e-6, atol=1e-6 + ) diff --git a/tests/test_spherical_to_cartesian.py b/tests/test_spherical_to_cartesian.py index 8eebb99..e0745c8 100644 --- a/tests/test_spherical_to_cartesian.py +++ b/tests/test_spherical_to_cartesian.py @@ -1,7 +1,9 @@ +from math import factorial + import numpy as np -from numpy.testing import assert_allclose import pytest -from math import factorial +from numpy.testing import assert_allclose + from anisoap.utils import ( TrivariateMonomialIndices, assert_close, From 0a903851d9b5f81a87ca5f0bcc9adbeda2459e47 Mon Sep 17 00:00:00 2001 From: Arthur Lin Date: Tue, 4 Jul 2023 16:11:13 -0500 Subject: [PATCH 10/13] minor changes to accomodate new equistore api --- anisoap/utils/equistore_utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/anisoap/utils/equistore_utils.py b/anisoap/utils/equistore_utils.py index 5b15edb..be366cf 100644 --- a/anisoap/utils/equistore_utils.py +++ b/anisoap/utils/equistore_utils.py @@ -147,7 +147,7 @@ def standardize_keys(descriptor): ) blocks = [] keys = [] - for key, block in descriptor: + for key, block in descriptor.items(): key = tuple(key) if not "order_nu" in key_names: key = (1,) + key @@ -160,7 +160,7 @@ def standardize_keys(descriptor): components=block.components, properties=Labels( property_names, - np.asarray(block.properties.view(dtype=np.int32)).reshape( + np.asarray(block.properties, dtype=np.int32).reshape( -1, len(property_names) ), ), @@ -168,7 +168,7 @@ def standardize_keys(descriptor): ) if not "order_nu" in key_names: - key_names = ("order_nu",) + key_names + key_names = ["order_nu"] + key_names return TensorMap( keys=Labels(names=key_names, values=np.asarray(keys, dtype=np.int32)), @@ -196,8 +196,8 @@ def cg_combine( """ # determines the cutoff in the new features - lmax_a = max(x_a.keys["angular_channel"]) - lmax_b = max(x_b.keys["angular_channel"]) + lmax_a = np.asarray(x_a.keys["angular_channel"], dtype="int32").max() + lmax_b = np.asarray(x_b.keys["angular_channel"], dtype="int32").max() if lcut is None: lcut = lmax_a + lmax_b @@ -253,7 +253,7 @@ def cg_combine( X_grads = {} # loops over sparse blocks of x_a - for index_a, block_a in x_a: + for index_a, block_a in x_a.items(): lam_a = index_a["angular_channel"] order_a = index_a["order_nu"] properties_a = ( @@ -262,7 +262,7 @@ def cg_combine( samples_a = block_a.samples # and x_b - for index_b, block_b in x_b: + for index_b, block_b in x_b.items(): lam_b = index_b["angular_channel"] order_b = index_b["order_nu"] properties_b = block_b.properties From a7c780b6ccb56ebb25b31094fef86dd745aafffe Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Wed, 19 Jul 2023 13:04:51 -0500 Subject: [PATCH 11/13] Updating to be in line with new equistore formatting (#15) --- anisoap/representations/ellipsoidal_density_projection.py | 2 +- anisoap/representations/radial_basis.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/anisoap/representations/ellipsoidal_density_projection.py b/anisoap/representations/ellipsoidal_density_projection.py index 66f79c8..8f408cf 100644 --- a/anisoap/representations/ellipsoidal_density_projection.py +++ b/anisoap/representations/ellipsoidal_density_projection.py @@ -233,7 +233,7 @@ def contract_pairwise_feat(pair_ellip_feat, species): sample_idx = [ idx for idx, tup in enumerate(pair_block_sample) - if tup[0].values[0] == sample[0] and tup[1].values[0] == sample[1] + if tup[0] == sample[0] and tup[1] == sample[1] ] # all samples of the pair block that match the current sample # in the example above, for sample = (0,0) we would identify sample_idx = [(0,0,1), (0,0,2)] diff --git a/anisoap/representations/radial_basis.py b/anisoap/representations/radial_basis.py index 6528830..71addeb 100644 --- a/anisoap/representations/radial_basis.py +++ b/anisoap/representations/radial_basis.py @@ -203,7 +203,7 @@ def orthonormalize_basis(self, features: TensorMap): return features for label, block in features.items(): l = label["angular_channel"] - n_arr = block.properties["n"].values.flatten() + n_arr = block.properties["n"].flatten() l_2n_arr = l + 2 * n_arr # normalize all the GTOs by the appropriate prefactor first, since the overlap matrix is in terms of # normalized GTOs From c43a20f2891210f41f27ce9237ec3f5fd7c7901a Mon Sep 17 00:00:00 2001 From: "Rose K. Cersonsky" <47536110+rosecers@users.noreply.github.com> Date: Thu, 3 Aug 2023 16:10:42 -0500 Subject: [PATCH 12/13] Adding progress bar for sanity sake (#14) --- .../ellipsoidal_density_projection.py | 70 +++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/anisoap/representations/ellipsoidal_density_projection.py b/anisoap/representations/ellipsoidal_density_projection.py index 8f408cf..f251328 100644 --- a/anisoap/representations/ellipsoidal_density_projection.py +++ b/anisoap/representations/ellipsoidal_density_projection.py @@ -10,7 +10,7 @@ ) from rascaline import NeighborList from scipy.spatial.transform import Rotation -from tqdm import tqdm +from tqdm.auto import tqdm from anisoap.representations.radial_basis import RadialBasis from anisoap.utils.moment_generator import * @@ -26,6 +26,7 @@ def pairwise_ellip_expansion( ellipsoid_lengths, sph_to_cart, radial_basis, + show_progress=False, ): """ Function to compute the pairwise expansion by combining the moments and the spherical to Cartesian @@ -57,6 +58,9 @@ def pairwise_ellip_expansion( radial_basis : Instance of the RadialBasis Class anisoap.representations.radial_basis.RadialBasis that has been instantiated appropriately with the cutoff radius, radial basis type. + + show_progress : bool + Show progress bar for frame analysis and feature generation ----------------------------------------------------------- Returns: An Equistore TensorMap with keys (species_1, species_2, l) where ("species_1", "species_2") is key in the @@ -81,7 +85,16 @@ def pairwise_ellip_expansion( species_second_atom=neighbor_species, ) - for isample, nl_sample in enumerate(nl_block.samples): + for isample, nl_sample in enumerate( + tqdm( + nl_block.samples, + disable=(not show_progress), + desc="Iterating samples for ({}, {})".format( + center_species, neighbor_species + ), + leave=False, + ) + ): frame_idx, i, j = ( nl_sample["structure"], nl_sample["first_atom"], @@ -116,7 +129,12 @@ def pairwise_ellip_expansion( np.einsum("mnpqr, pqr->mn", sph_to_cart[l], moments_l) ) - for l in range(lmax + 1): + for l in tqdm( + range(lmax + 1), + disable=(not show_progress), + desc="Accruing lmax", + leave=False, + ): block = TensorBlock( values=np.asarray(values_ldict[l]), samples=nl_block.samples, # as many rows as samples @@ -145,7 +163,7 @@ def pairwise_ellip_expansion( return pairwise_ellip_feat -def contract_pairwise_feat(pair_ellip_feat, species): +def contract_pairwise_feat(pair_ellip_feat, species, show_progress=False): """ Function to sum over the pairwise expansion \sum_{j in a} = -------------------------------------------------------- @@ -158,6 +176,9 @@ def contract_pairwise_feat(pair_ellip_feat, species): species: list of ints List of atomic numbers present across the data frames + show_progress : bool + Show progress bar for frame analysis and feature generation + ----------------------------------------------------------- Returns: An Equistore TensorMap with keys (species, l) "species" takes the value of the atomic numbers present @@ -180,14 +201,21 @@ def contract_pairwise_feat(pair_ellip_feat, species): "neighbor_species", ] - for key in ellip_keys: + for key in tqdm( + ellip_keys, disable=(not show_progress), desc="Iterating tensor block keys" + ): contract_blocks = [] contract_properties = [] contract_samples = [] # these collect the values, properties and samples of the blocks when contracted over neighbor_species. # All these lists have as many entries as len(species). - for ele in species: + for ele in tqdm( + species, + disable=(not show_progress), + desc="Iterating neighbor species", + leave=False, + ): selection = Labels(names=["species_neighbor"], values=np.array([[ele]])) blockidx = pair_ellip_feat.blocks_matching(selection=selection) # indices of the blocks in pair_ellip_feat with neighbor species = ele @@ -229,7 +257,14 @@ def contract_pairwise_feat(pair_ellip_feat, species): block_samples = [] block_values = [] - for isample, sample in enumerate(possible_block_samples): + for isample, sample in enumerate( + tqdm( + possible_block_samples, + disable=(not show_progress), + desc="Finding matching block samples", + leave=False, + ) + ): sample_idx = [ idx for idx, tup in enumerate(pair_block_sample) @@ -275,7 +310,14 @@ def contract_pairwise_feat(pair_ellip_feat, species): # values for each of them as \sum_{j in ele} <|rho_ij> # Thus - all_block_values.shape = (num_final_samples, components_pair, properties_pair, num_species) - for iele, elem_cont_samples in enumerate(contract_samples): + for iele, elem_cont_samples in enumerate( + tqdm( + contract_samples, + disable=(not show_progress), + desc="Contracting features", + leave=False, + ) + ): # This effectively loops over the species of the neighbors # Now we just need to add the contributions to the final samples and values from this species to the right # samples @@ -408,7 +450,7 @@ def transform(self, frames, show_progress=False, normalize=True): frames : ase.Atoms List containing all ase.Atoms structures show_progress : bool - Show progress bar for frame analysis + Show progress bar for frame analysis and feature generation normalize: bool Whether to perform Lowdin Symmetric Orthonormalization or not. Orthonormalization generally leads to better performance. Default: True. @@ -443,10 +485,9 @@ def transform(self, frames, show_progress=False, normalize=True): # Initialize arrays in which to store all features self.feature_gradients = 0 - if show_progress: - frame_generator = tqdm(self.frames) - else: - frame_generator = self.frames + frame_generator = tqdm( + self.frames, disable=(not show_progress), desc="Computing neighborlist" + ) self.frame_to_global_atom_idx = np.zeros((num_frames), int) for n in range(1, num_frames): @@ -486,9 +527,10 @@ def transform(self, frames, show_progress=False, normalize=True): ellipsoid_lengths, self.sph_to_cart, self.radial_basis, + show_progress, ) - features = contract_pairwise_feat(pairwise_ellip_feat, species) + features = contract_pairwise_feat(pairwise_ellip_feat, species, show_progress) if normalize: normalized_features = self.radial_basis.orthonormalize_basis(features) return normalized_features From 3cbe6c3cd6e8ca627085386083b0d32d921bce88 Mon Sep 17 00:00:00 2001 From: arthur-lin1027 <35580059+arthur-lin1027@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:06:40 -0500 Subject: [PATCH 13/13] =?UTF-8?q?added=20warning=20for=20passing=20in=20in?= =?UTF-8?q?teger,=20and=20cast=20any=20int=20arguments=20to=20f=E2=80=A6?= =?UTF-8?q?=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Raise an error when a float is passed for radial_gaussian_width, and added a unit test to ensure error is raised --------- Co-authored-by: Arthur --- anisoap/representations/ellipsoidal_density_projection.py | 5 ++++- tests/test_ellipsoidal_density_projection.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/anisoap/representations/ellipsoidal_density_projection.py b/anisoap/representations/ellipsoidal_density_projection.py index f251328..ad77e9f 100644 --- a/anisoap/representations/ellipsoidal_density_projection.py +++ b/anisoap/representations/ellipsoidal_density_projection.py @@ -416,7 +416,10 @@ def __init__( raise ValueError("Gaussian width can only be provided with GTO basis") elif radial_gaussian_width is None and radial_basis_name == "gto": raise ValueError("Gaussian width must be provided with GTO basis") - + elif type(radial_gaussian_width) == int: + raise ValueError( + "radial_gaussian_width is set as an integer, which could cause overflow errors. Pass in float." + ) radial_hypers = {} radial_hypers["radial_basis"] = radial_basis_name.lower() # lower case radial_hypers["radial_gaussian_width"] = radial_gaussian_width diff --git a/tests/test_ellipsoidal_density_projection.py b/tests/test_ellipsoidal_density_projection.py index 783feb5..2f3adf1 100644 --- a/tests/test_ellipsoidal_density_projection.py +++ b/tests/test_ellipsoidal_density_projection.py @@ -129,6 +129,11 @@ class TestBadInputs: ValueError, "We have only implemented transforming quaternions (`quaternion`) and rotation matrices (`matrix`).", ], + [ + {**DEFAULT_HYPERS, "radial_gaussian_width": 9}, + ValueError, + "radial_gaussian_width is set as an integer, which could cause overflow errors. Pass in float.", + ], ] @pytest.mark.parametrize("hypers,error_type,expected_message", test_hypers)