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": [
+ "