From 19ef895778b451119c603bdedae92719b93c167c Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Fri, 30 Jun 2023 15:08:50 +0200 Subject: [PATCH 01/96] Stub module for CG iterations --- python/rascaline/utils/clebsch_gordan.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 python/rascaline/utils/clebsch_gordan.py diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py new file mode 100644 index 000000000..41a54eb66 --- /dev/null +++ b/python/rascaline/utils/clebsch_gordan.py @@ -0,0 +1,3 @@ +""" +Module for computing Clebsch-gordan iterations with equistore TensorMaps. +""" \ No newline at end of file From e9b4155152a49caad5a65758d2b8516ab3b05afb Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Fri, 30 Jun 2023 15:27:35 +0200 Subject: [PATCH 02/96] Class for calculating CG coeffs --- python/rascaline/utils/clebsch_gordan.py | 142 ++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 41a54eb66..ac93af582 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -1,3 +1,143 @@ """ Module for computing Clebsch-gordan iterations with equistore TensorMaps. -""" \ No newline at end of file +""" +from typing import Sequence +import numpy as np + +import wigners + +from equistore.core import Labels, TensorBlock, TensorMap + + +class ClebschGordanReal: + """ + Class for computing Clebsch-Gordan coefficients for real spherical + harmonics. + """ + def __init__(self, l_max: int): + self.l_max = l_max + self.coeffs = ClebschGordanReal.build_coeff_dict(self.l_max) + + @staticmethod + def build_coeff_dict(l_max: int): + """ + Builds a dictionary of Clebsch-Gordan coefficients for all possible + combination of l1 and l2, up to l_max. + """ + # real-to-complex and complex-to-real transformations as matrices + r2c = {} + c2r = {} + coeff_dict = {} + for L in range(0, l_max + 1): + r2c[L] = _real2complex(L) + c2r[L] = np.conjugate(r2c[L]).T + + for l1 in range(l_max + 1): + for l2 in range(l_max + 1): + for L in range( + max(l1, l2) - min(l1, l2), min(l_max, (l1 + l2)) + 1 + ): + complex_cg = _complex_clebsch_gordan_matrix(l1, l2, L) + + real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( + complex_cg.shape + ) + + real_cg = real_cg.swapaxes(0, 1) + real_cg = (r2c[l2].T @ real_cg.reshape(2 * l2 + 1, -1)).reshape( + real_cg.shape + ) + real_cg = real_cg.swapaxes(0, 1) + + real_cg = real_cg @ c2r[L].T + + if (l1 + l2 + L) % 2 == 0: + rcg = np.real(real_cg) + else: + rcg = np.imag(real_cg) + + new_cg = [] + for M in range(2 * L + 1): + cg_nonzero = np.where(np.abs(rcg[:, :, M]) > 1e-15) + cg_M = np.zeros( + len(cg_nonzero[0]), + dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], + ) + cg_M["m1"] = cg_nonzero[0] + cg_M["m2"] = cg_nonzero[1] + cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], M] + new_cg.append(cg_M) + + coeff_dict[(l1, l2, L)] = new_cg + + return coeff_dict + +def _real2complex(L: int) -> np.ndarray: + """ + Computes a matrix that can be used to convert from real to complex-valued + spherical harmonics(coefficients) of order L. + It's meant to be applied to the left, ``real2complex @ [-L..L]``. + """ + result = np.zeros((2 * L + 1, 2 * L + 1), dtype=np.complex128) + + I_SQRT_2 = 1.0 / np.sqrt(2) + + for m in range(-L, L + 1): + if m < 0: + result[L - m, L + m] = I_SQRT_2 * 1j * (-1) ** m + result[L + m, L + m] = -I_SQRT_2 * 1j + + if m == 0: + result[L, L] = 1.0 + + if m > 0: + result[L + m, L + m] = I_SQRT_2 * (-1) ** m + result[L - m, L + m] = I_SQRT_2 + + return result + + +def _complex_clebsch_gordan_matrix(l1, l2, L): + r"""clebsch-gordan matrix + Computes the Clebsch-Gordan (CG) matrix for + transforming complex-valued spherical harmonics. + The CG matrix is computed as a 3D array of elements + < l1 m1 l2 m2 | L M > + where the first axis loops over m1, the second loops over m2, + and the third one loops over M. The matrix is real. + For example, using the relation: + | l1 l2 L M > = \sum_{m1, m2} | l1 m1 > | l2 m2 > + (https://en.wikipedia.org/wiki/Clebsch–Gordan_coefficients, section + "Formal definition of Clebsch-Gordan coefficients", eq 2) + one can obtain the spherical harmonics L from two sets of + spherical harmonics with l1 and l2 (up to a normalization factor). + E.g.: + Args: + l1: l number for the first set of spherical harmonics + l2: l number for the second set of spherical harmonics + L: l number For the third set of spherical harmonics + Returns: + cg: CG matrix for transforming complex-valued spherical harmonics + >>> from scipy.special import sph_harm + >>> import numpy as np + >>> import wigners + ... + >>> C_112 = _complex_clebsch_gordan_matrix(1, 1, 2) + >>> comp_sph_1 = np.array([ + ... sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1+1) + ... ]) + >>> comp_sph_2 = np.array([sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1+1)]) + >>> # obtain the (unnormalized) spherical harmonics + >>> # with l = 2 by contraction over m1 and m2 + >>> comp_sph_2_u = np.einsum("ijk,i,j->k", C_112, comp_sph_1, comp_sph_2) + >>> # we can check that they differ from the spherical harmonics + >>> # by a constant factor + >>> comp_sph_2 = np.array([sph_harm(m, 2, 0.2, 0.2) for m in range(-2, 2+1)]) + >>> ratio = comp_sph_2 / comp_sph_2_u + >>> np.allclose(ratio[0], ratio) + True + """ + if np.abs(l1 - l2) > L or np.abs(l1 + l2) < L: + return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * L + 1), dtype=np.double) + else: + return wigners.clebsch_gordan_array(l1, l2, L) \ No newline at end of file From 46361f1578148922033010ebc799d13454b4763e Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Fri, 30 Jun 2023 16:53:10 +0200 Subject: [PATCH 03/96] dispatch stub --- python/rascaline/utils/_dispatch_.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 python/rascaline/utils/_dispatch_.py diff --git a/python/rascaline/utils/_dispatch_.py b/python/rascaline/utils/_dispatch_.py new file mode 100644 index 000000000..dd9cd7b1a --- /dev/null +++ b/python/rascaline/utils/_dispatch_.py @@ -0,0 +1,3 @@ +""" +Module containing dispatch functions for numpy/torch operations. +""" \ No newline at end of file From 2faf45ad5c5465d805098d4a31ae534dab9e0138 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Fri, 30 Jun 2023 17:09:18 +0200 Subject: [PATCH 04/96] `_clebsch_gordan_combine` core function --- python/rascaline/utils/clebsch_gordan.py | 61 ++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index ac93af582..92a8a3217 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -9,11 +9,65 @@ from equistore.core import Labels, TensorBlock, TensorMap +def _clebsch_gordan_combine( + arr_1: np.ndarray, + arr_2: np.ndarray, + lamb: int, + cg_cache, +) -> np.ndarray: + """ + Couples arrays corresponding to the irreducible spherical components of 2 + angular channels l1 and l2 using the appropriate Clebsch-Gordan + coefficients. As l1 and l2 can be combined to form multiple lambda channels, + this function returns the coupling to a single specified channel `lambda`. + + `arr_1` has shape (n_i, 2 * l1 + 1, n_p) and `arr_2` has shape (n_i, 2 * l2 + + 1, n_q). n_i is the number of samples, n_p and n_q are the number of + properties in each array. The number of samples in each array must be the + same. + + The ouput array has shape (n_i, 2 * lambda + 1, n_p * n_q), where lambda is + the input parameter `lamb`. + + The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these + must be produced by the ClebschGordanReal class in this module. + """ + # Check the first dimension of the arrays are the same (i.e. same samples) + assert arr_1.shape[0] == arr_2.shape[0] + + # Define useful dimensions + n_i = arr_1.shape[0] # number of samples + n_p = arr_1.shape[2] # number of properties in arr_1 + n_q = arr_2.shape[2] # number of properties in arr_2 + + # Infer l1 and l2 from the len of the lenght of axis 1 of each tensor + l1 = int((arr_1.shape[1] - 1) / 2) + l2 = int((arr_2.shape[1] - 1) / 2) + + # Get the corresponding Clebsch-Gordan coefficients + cg_coeffs = cg_cache.coeffs[(l1, l2, lamb)] + + # Initialise output array + arr_out = np.zeros((n_i, 2 * lamb + 1, n_p * n_q)) + + # Fill in each mu component of the output array in turn + for mu in range(2 * lamb + 1): + # Iterate over the Clebsch-Gordan coefficients for this mu + for m1, m2, cg_coeff in cg_coeffs[mu]: + # Broadcast arrays, multiply together and with CG coeff + out_arr[:, mu, :] = ( + arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeff + ).reshape(n_i, n_p * n_q) + + return arr_out + + class ClebschGordanReal: """ Class for computing Clebsch-Gordan coefficients for real spherical harmonics. """ + def __init__(self, l_max: int): self.l_max = l_max self.coeffs = ClebschGordanReal.build_coeff_dict(self.l_max) @@ -34,9 +88,7 @@ def build_coeff_dict(l_max: int): for l1 in range(l_max + 1): for l2 in range(l_max + 1): - for L in range( - max(l1, l2) - min(l1, l2), min(l_max, (l1 + l2)) + 1 - ): + for L in range(max(l1, l2) - min(l1, l2), min(l_max, (l1 + l2)) + 1): complex_cg = _complex_clebsch_gordan_matrix(l1, l2, L) real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( @@ -72,6 +124,7 @@ def build_coeff_dict(l_max: int): return coeff_dict + def _real2complex(L: int) -> np.ndarray: """ Computes a matrix that can be used to convert from real to complex-valued @@ -140,4 +193,4 @@ def _complex_clebsch_gordan_matrix(l1, l2, L): if np.abs(l1 - l2) > L or np.abs(l1 + l2) < L: return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * L + 1), dtype=np.double) else: - return wigners.clebsch_gordan_array(l1, l2, L) \ No newline at end of file + return wigners.clebsch_gordan_array(l1, l2, L) From 155139d126cd1c204b114e8559eea4d6a4972257 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Fri, 30 Jun 2023 17:09:39 +0200 Subject: [PATCH 05/96] Rough code from Alex to perform dense CG combine --- python/rascaline/utils/clebsch_gordan.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 92a8a3217..05e50a69c 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -194,3 +194,26 @@ def _complex_clebsch_gordan_matrix(l1, l2, L): return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * L + 1), dtype=np.double) else: return wigners.clebsch_gordan_array(l1, l2, L) + + + +# ===== For writing a dense version in the future? ===== + +# def _clebsch_gordan_combine_dense( +# l1_mu_values,#: Array[samples, 2 * l1 + 1, q_properties], # mu values for l1 +# l2_mu_values,#: Array[samples, 2 * l2 + 1, p_properties], # mu values for l2 +# lam: int, +# cg_cache,#: Array[(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] +# ) -> None: #Array[samples, 2 * lam + 1, q_properties * p_properties]: +# """ +# :param l1_mu_values: mu values for l1 +# :param l2_mu_values: mu values for l2 +# :returns lam_mu_values: of shape [samples, (2 * l1 + 1)* (2 * l2 + 1), q_properties, p_properties] +# """ +# # +# #l1l2_mu_values = l1_mu_values[:, None, :] * l2_mu_values[:, :, None, :] +# #return einops.einsum(l1_mu_values, l2_mu_values, cg_cache, "samples l1_mu q_properties, samples l2_mu p_properties, l1_mu l2_mu lam_mu -> samples lam_mu (q_properties p_properties)") + +# # more readable subscript +# #"samples l1_mu q_properties, samples l2_mu p_properties, l1_mu l2_mu lam_mu -> samples lam_mu (q_properties p_properties)" +# return np.einsum("slq, skp, lkL -> sLqp", l1_mu_values, l2_mu_values, cg_cache).reshape(l1_mu_values.shape[0], 2*lam+1, -1) \ No newline at end of file From 62130a2a32429894002586f208146eb5765447a6 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Mon, 3 Jul 2023 14:08:25 +0200 Subject: [PATCH 06/96] add dense version --- python/rascaline/utils/clebsch_gordan.py | 65 ++++++++++++++++-------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 05e50a69c..638fb3d16 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -197,23 +197,48 @@ def _complex_clebsch_gordan_matrix(l1, l2, L): -# ===== For writing a dense version in the future? ===== - -# def _clebsch_gordan_combine_dense( -# l1_mu_values,#: Array[samples, 2 * l1 + 1, q_properties], # mu values for l1 -# l2_mu_values,#: Array[samples, 2 * l2 + 1, p_properties], # mu values for l2 -# lam: int, -# cg_cache,#: Array[(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] -# ) -> None: #Array[samples, 2 * lam + 1, q_properties * p_properties]: -# """ -# :param l1_mu_values: mu values for l1 -# :param l2_mu_values: mu values for l2 -# :returns lam_mu_values: of shape [samples, (2 * l1 + 1)* (2 * l2 + 1), q_properties, p_properties] -# """ -# # -# #l1l2_mu_values = l1_mu_values[:, None, :] * l2_mu_values[:, :, None, :] -# #return einops.einsum(l1_mu_values, l2_mu_values, cg_cache, "samples l1_mu q_properties, samples l2_mu p_properties, l1_mu l2_mu lam_mu -> samples lam_mu (q_properties p_properties)") - -# # more readable subscript -# #"samples l1_mu q_properties, samples l2_mu p_properties, l1_mu l2_mu lam_mu -> samples lam_mu (q_properties p_properties)" -# return np.einsum("slq, skp, lkL -> sLqp", l1_mu_values, l2_mu_values, cg_cache).reshape(l1_mu_values.shape[0], 2*lam+1, -1) \ No newline at end of file +# ===== For writing a dense version in the future ===== + +def _clebsch_gordan_dense(l1_mu_values, l2_mu_values, lam: int, cg_cache): + """ + l1_mu_values,#: Array[samples, 2 * l1 + 1, q_properties], # mu values for l1 + l2_mu_values,#: Array[samples, 2 * l2 + 1, p_properties], # mu values for l2 + lam: int, + cg_cache,#: Array[(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] + ) -> None: #Array[samples, 2 * lam + 1, q_properties * p_properties]: + + :param l1_mu_values: array with the mu values for l1 with shape [samples, 2 * l1 + 1, q_properties] + :param l2_mu_values: array with the mu values for l1 with shape [samples, 2 * l2 + 1, p_properties] + :param lam: int resulting coupled channel + :param cg_cache: array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] + :returns lam_mu_values: array of shape [samples, (2 * l1 + 1)* (2 * l2 + 1), q_properties, p_properties] + + >>> N_SAMPLES = 30 + >>> N_Q_PROPERTIES = 10 + >>> N_P_PROPERTIES = 8 + >>> L1 = 2 + >>> L2 = 3 + >>> LAM = 2 + >>> l1_mu_values = np.random.rand(N_SAMPLES, 2*L1+1, N_Q_PROPERTIES) + >>> l2_mu_values = np.random.rand(N_SAMPLES, 2*L2+1, N_P_PROPERTIES) + >>> cg_cache = np.random.rand(2*L1+1, 2*L2+1, 2*LAM+1) + >>> out1 = _clebsch_gordan_dense(l1_mu_values, l2_mu_values, LAM, cg_cache) + >>> out2 = np.einsum("slq, skp, lkL -> sLqp", l1_mu_values, l2_mu_values, cg_cache).reshape(l1_mu_values.shape[0], 2*LAM+1, -1) + >>> print(np.allclose(out1, out2)) + True + """ + # l1_mu q, samples l2_mu p -> samples l2_mu p l1_mu q + # we broadcast it in this way so we only need to do one swapaxes in the next step + out = l1_mu_values[:, None, None, :, :] * l2_mu_values[:, :, :, None, None] + # samples l2_mu p l1_mu q -> samples q p l1_mu l2_mu + out = out.swapaxes(1,4) + # samples q p l1_mu l2_mu -> samples (q p) (l1_mu l2_mu) + out = out.reshape(-1, l1_mu_values.shape[2]*l2_mu_values.shape[2], l1_mu_values.shape[1]*l2_mu_values.shape[1]) + # l1_mu l2_mu lam_mu -> (l1_mu l2_mu) lam_mu + cg_cache = cg_cache.reshape(-1, 2*lam+1) + # samples (q p) (l1_mu l2_mu), (l1_mu l2_mu) lam_mu -> samples (q p) lam_mu + out = out @ cg_cache.reshape(-1, 2*lam+1) + # samples (q p) lam_mu -> samples lam_mu (q p) + return out.swapaxes(1,2) + + From 5173fe5890fbc96a35baf49315fb762e56ec6dbe Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 3 Jul 2023 15:46:54 +0200 Subject: [PATCH 07/96] option for sparse or not operation in core combine fxn --- python/rascaline/utils/clebsch_gordan.py | 172 +++++++++++++---------- 1 file changed, 96 insertions(+), 76 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 638fb3d16..4acf44047 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -9,59 +9,6 @@ from equistore.core import Labels, TensorBlock, TensorMap -def _clebsch_gordan_combine( - arr_1: np.ndarray, - arr_2: np.ndarray, - lamb: int, - cg_cache, -) -> np.ndarray: - """ - Couples arrays corresponding to the irreducible spherical components of 2 - angular channels l1 and l2 using the appropriate Clebsch-Gordan - coefficients. As l1 and l2 can be combined to form multiple lambda channels, - this function returns the coupling to a single specified channel `lambda`. - - `arr_1` has shape (n_i, 2 * l1 + 1, n_p) and `arr_2` has shape (n_i, 2 * l2 - + 1, n_q). n_i is the number of samples, n_p and n_q are the number of - properties in each array. The number of samples in each array must be the - same. - - The ouput array has shape (n_i, 2 * lambda + 1, n_p * n_q), where lambda is - the input parameter `lamb`. - - The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these - must be produced by the ClebschGordanReal class in this module. - """ - # Check the first dimension of the arrays are the same (i.e. same samples) - assert arr_1.shape[0] == arr_2.shape[0] - - # Define useful dimensions - n_i = arr_1.shape[0] # number of samples - n_p = arr_1.shape[2] # number of properties in arr_1 - n_q = arr_2.shape[2] # number of properties in arr_2 - - # Infer l1 and l2 from the len of the lenght of axis 1 of each tensor - l1 = int((arr_1.shape[1] - 1) / 2) - l2 = int((arr_2.shape[1] - 1) / 2) - - # Get the corresponding Clebsch-Gordan coefficients - cg_coeffs = cg_cache.coeffs[(l1, l2, lamb)] - - # Initialise output array - arr_out = np.zeros((n_i, 2 * lamb + 1, n_p * n_q)) - - # Fill in each mu component of the output array in turn - for mu in range(2 * lamb + 1): - # Iterate over the Clebsch-Gordan coefficients for this mu - for m1, m2, cg_coeff in cg_coeffs[mu]: - # Broadcast arrays, multiply together and with CG coeff - out_arr[:, mu, :] = ( - arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeff - ).reshape(n_i, n_p * n_q) - - return arr_out - - class ClebschGordanReal: """ Class for computing Clebsch-Gordan coefficients for real spherical @@ -196,21 +143,94 @@ def _complex_clebsch_gordan_matrix(l1, l2, L): return wigners.clebsch_gordan_array(l1, l2, L) +def _clebsch_gordan_combine( + arr_1: np.ndarray, + arr_2: np.ndarray, + lamb: int, + cg_cache, + use_sparse: bool = True, +) -> np.ndarray: + """ + Couples arrays corresponding to the irreducible spherical components of 2 + angular channels l1 and l2 using the appropriate Clebsch-Gordan + coefficients. As l1 and l2 can be combined to form multiple lambda channels, + this function returns the coupling to a single specified channel `lambda`. + + `arr_1` has shape (n_i, 2 * l1 + 1, n_p) and `arr_2` has shape (n_i, 2 * l2 + + 1, n_q). n_i is the number of samples, n_p and n_q are the number of + properties in each array. The number of samples in each array must be the + same. + + The ouput array has shape (n_i, 2 * lambda + 1, n_p * n_q), where lambda is + the input parameter `lamb`. + + The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these + must be produced by the ClebschGordanReal class in this module. + + Either performs the operation in a dense or sparse manner, depending on the + value of `sparse`. + """ + # Check the first dimension of the arrays are the same (i.e. same samples) + if use_sparse: + return _clebsch_gordan_combine_sparse(arr_1, arr_2, lamb, cg_cache) + return _clebsch_gordan_dense(arr_1, arr_2, lamb, cg_cache) + + +def _clebsch_gordan_combine_sparse( + arr_1: np.ndarray, + arr_2: np.ndarray, + lamb: int, + cg_cache, +) -> np.ndarray: + assert arr_1.shape[0] == arr_2.shape[0] + + # Define useful dimensions + n_i = arr_1.shape[0] # number of samples + n_p = arr_1.shape[2] # number of properties in arr_1 + n_q = arr_2.shape[2] # number of properties in arr_2 + + # Infer l1 and l2 from the len of the lenght of axis 1 of each tensor + l1 = int((arr_1.shape[1] - 1) / 2) + l2 = int((arr_2.shape[1] - 1) / 2) + + # Get the corresponding Clebsch-Gordan coefficients + cg_coeffs = cg_cache.coeffs[(l1, l2, lamb)] + + # Initialise output array + arr_out = np.zeros((n_i, 2 * lamb + 1, n_p * n_q)) + + # Fill in each mu component of the output array in turn + for mu in range(2 * lamb + 1): + # Iterate over the Clebsch-Gordan coefficients for this mu + for m1, m2, cg_coeff in cg_coeffs[mu]: + # Broadcast arrays, multiply together and with CG coeff + arr_out[:, mu, :] = ( + arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeff + ).reshape(n_i, n_p * n_q) + + return arr_out + # ===== For writing a dense version in the future ===== -def _clebsch_gordan_dense(l1_mu_values, l2_mu_values, lam: int, cg_cache): + +def _clebsch_gordan_combine_dense( + arr_1: np.ndarray, + arr_2: np.ndarray, + lamb: int, + cg_cache, +) -> np.ndarray: """ - l1_mu_values,#: Array[samples, 2 * l1 + 1, q_properties], # mu values for l1 - l2_mu_values,#: Array[samples, 2 * l2 + 1, p_properties], # mu values for l2 - lam: int, - cg_cache,#: Array[(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] - ) -> None: #Array[samples, 2 * lam + 1, q_properties * p_properties]: - - :param l1_mu_values: array with the mu values for l1 with shape [samples, 2 * l1 + 1, q_properties] - :param l2_mu_values: array with the mu values for l1 with shape [samples, 2 * l2 + 1, p_properties] - :param lam: int resulting coupled channel - :param cg_cache: array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] + arr_1,#: Array[samples, 2 * l1 + 1, q_properties], # mu values for l1 + arr_2,#: Array[samples, 2 * l2 + 1, p_properties], # mu values for l2 + lamb: int, + cg_cache,#: Array[(2 * l1 +1) * (2 * l2 +1), (2 * lamb + 1)] + ) -> None: #Array[samples, 2 * lamb + 1, q_properties * p_properties]: + + :param arr_1: array with the mu values for l1 with shape [samples, 2 * l1 + 1, q_properties] + :param arr_2: array with the mu values for l1 with shape [samples, 2 * l2 + 1, p_properties] + :param lamb: int resulting coupled channel + :param cg_cache: array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lamb + 1)] :returns lam_mu_values: array of shape [samples, (2 * l1 + 1)* (2 * l2 + 1), q_properties, p_properties] >>> N_SAMPLES = 30 @@ -219,26 +239,26 @@ def _clebsch_gordan_dense(l1_mu_values, l2_mu_values, lam: int, cg_cache): >>> L1 = 2 >>> L2 = 3 >>> LAM = 2 - >>> l1_mu_values = np.random.rand(N_SAMPLES, 2*L1+1, N_Q_PROPERTIES) - >>> l2_mu_values = np.random.rand(N_SAMPLES, 2*L2+1, N_P_PROPERTIES) + >>> arr_1 = np.random.rand(N_SAMPLES, 2*L1+1, N_Q_PROPERTIES) + >>> arr_2 = np.random.rand(N_SAMPLES, 2*L2+1, N_P_PROPERTIES) >>> cg_cache = np.random.rand(2*L1+1, 2*L2+1, 2*LAM+1) - >>> out1 = _clebsch_gordan_dense(l1_mu_values, l2_mu_values, LAM, cg_cache) - >>> out2 = np.einsum("slq, skp, lkL -> sLqp", l1_mu_values, l2_mu_values, cg_cache).reshape(l1_mu_values.shape[0], 2*LAM+1, -1) + >>> out1 = _clebsch_gordan_dense(arr_1, arr_2, LAM, cg_cache) + >>> out2 = np.einsum("slq, skp, lkL -> sLqp", arr_1, arr_2, cg_cache).reshape(arr_1.shape[0], 2*LAM+1, -1) >>> print(np.allclose(out1, out2)) True """ # l1_mu q, samples l2_mu p -> samples l2_mu p l1_mu q # we broadcast it in this way so we only need to do one swapaxes in the next step - out = l1_mu_values[:, None, None, :, :] * l2_mu_values[:, :, :, None, None] + out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] # samples l2_mu p l1_mu q -> samples q p l1_mu l2_mu - out = out.swapaxes(1,4) + out = out.swapaxes(1, 4) # samples q p l1_mu l2_mu -> samples (q p) (l1_mu l2_mu) - out = out.reshape(-1, l1_mu_values.shape[2]*l2_mu_values.shape[2], l1_mu_values.shape[1]*l2_mu_values.shape[1]) + out = out.reshape( + -1, arr_1.shape[2] * arr_2.shape[2], arr_1.shape[1] * arr_2.shape[1] + ) # l1_mu l2_mu lam_mu -> (l1_mu l2_mu) lam_mu - cg_cache = cg_cache.reshape(-1, 2*lam+1) + cg_cache = cg_cache.reshape(-1, 2 * lamb + 1) # samples (q p) (l1_mu l2_mu), (l1_mu l2_mu) lam_mu -> samples (q p) lam_mu - out = out @ cg_cache.reshape(-1, 2*lam+1) + out = out @ cg_cache.reshape(-1, 2 * lamb + 1) # samples (q p) lam_mu -> samples lam_mu (q p) - return out.swapaxes(1,2) - - + return out.swapaxes(1, 2) From 77647df905e7f62bf7d6bb7bc50113a127706c22 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 3 Jul 2023 17:24:41 +0200 Subject: [PATCH 08/96] First draft code for single center combine --- python/rascaline/utils/clebsch_gordan.py | 137 ++++++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 4acf44047..055e6769a 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -9,6 +9,9 @@ from equistore.core import Labels, TensorBlock, TensorMap +# ===== Class for calculating Clebsch-Gordan coefficients ===== + + class ClebschGordanReal: """ Class for computing Clebsch-Gordan coefficients for real spherical @@ -143,6 +146,98 @@ def _complex_clebsch_gordan_matrix(l1, l2, L): return wigners.clebsch_gordan_array(l1, l2, L) +# ===== Methods for performing CG combinations ===== + + +def _combine_single_center( + tensor_1: TensorMap, + tensor_2: TensorMap, + lambdas: Sequence[int], + cg_cache, + use_sparse: bool = True, +) -> TensorMap: + """ + For 2 TensorMaps with body orders nu and eta respectively, combines their + blocks to form a new TensorMap with body order (nu + eta). + + Assumes + """ + access_keys_1, access_keys_2, combined_keys = _create_combined_keys( + tensor_1.keys, tensor_2.keys, lambdas + ) + combined_blocks = [] + for key_1, key_2, combined_key in zip(access_keys_1, access_keys_2, combined_keys): + # Extract the pair of blocks to combine + block_1, block_2 = tensor_1[key_1], tensor_2[key_2] + + # Extract the desired lambda value + lamb = combined_key["spherical_harmonics"] + + # Combine the blocks into a new TensorBlock + combined_blocks.append( + _combine_single_center_block_pair(block_1, block_2, lamb) + ) + + combined_tensor = TensorMap(combined_keys, combined_blocks) + + # Account for body-order multiplicity + combined_tensor = _apply_body_order_corrections(combined_tensor) + + # Move the l1, l2 keys to the properties + combined_tensor = combined_tensor.keys_to_properties(["l1", "l2"]) + + return combined_tensor + + +def _combine_single_center_block_pair( + block_1: TensorBlock, + block_2: TensorBlock, + lamb: int, + cg_cache, + use_sparse: bool = True, +) -> TensorBlock: + """ + For a given pair of TensorBlocks and desired lambda value, combines the + values arrays and returns in a new TensorBlock. + """ + # Check metadata + if not equistore.equal_metadata(block_1, block_2, check=["samples"]): + raise ValueError( + "TensorBlock pair to combine must have equal samples in the same order" + ) + if block_1.properties.names != block_2.properties.names: + raise ValueError( + "TensorBlock pair to combine must have equal properties in the same order" + ) + + # Do the CG combination - no shape pre-processing required + combined_values = _clebsch_gordan_combine( + block_1.values, block_2.values, lamb, cg_cache + ) + + # Create a TensorBlock + combined_block = TensorBlock( + values=combined_values, + samples=block_1.samples, + components=Labels( + names=["spherical_harmonics_m"], + values=np.arange(-lamb, lamb + 1), + ), + properties=Labels( + names=["n_1", "n_2"], + values=np.array( + [ + [i, j] + for j in block_2.properties.values + for i in block_1.properties.values + ] + ), + ), + ) + + return combined_block + + def _clebsch_gordan_combine( arr_1: np.ndarray, arr_2: np.ndarray, @@ -182,9 +277,13 @@ def _clebsch_gordan_combine_sparse( lamb: int, cg_cache, ) -> np.ndarray: + """ + TODO: docstring + """ + # Samples dimensions must be the same assert arr_1.shape[0] == arr_2.shape[0] - # Define useful dimensions + # Define other useful dimensions n_i = arr_1.shape[0] # number of samples n_p = arr_1.shape[2] # number of properties in arr_1 n_q = arr_2.shape[2] # number of properties in arr_2 @@ -262,3 +361,39 @@ def _clebsch_gordan_combine_dense( out = out @ cg_cache.reshape(-1, 2 * lamb + 1) # samples (q p) lam_mu -> samples lam_mu (q p) return out.swapaxes(1, 2) + + +def _apply_body_order_corrections(tensor: TensorMap) -> TensorMap: + """ + Applies the appropriate prefactors to the block values of the output + TensorMap (i.e. post-CG combination) according to its body order. + """ + return tensor + + +def _create_combined_keys( + keys_1: Labels, keys_2: Labels, lambdas: Sequence[int] +) -> Sequence[Labels]: + """ + Given the keys of 2 TensorMaps and a list of desired lambda values, creates + the correct keys for the TensorMap returned after CG combination. + + The input keys `keys_1` and `keys_2` must contain names + "spherical_harmonics_l" and "species_center". + + Returned is a tuple of 3 Labels objects. The first and second returned + Labels objects correspond to the keys of the input TensorMaps, and the third + Labels object to the keys of the output TensorMap. + """ + # Alex's code here + return access_keys_1, access_keys_2, combined_keys + + # # Don't compute redundant (l1, l2) pairs + # if l1 < l2: + # continue + + # # Compute non-zero lanbda values + # allowed_lambdas = range(np.abs(l1 - l2), abs(l1 + l2) + 1) + # for lamb in allowed_lambdas: + # if lamb not in lambdas: + # continue From 1280535485df81080a99f454888009deed2f1ded Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Mon, 3 Jul 2023 17:35:16 +0200 Subject: [PATCH 09/96] adding _create_combined_keys --- python/rascaline/utils/clebsch_gordan.py | 28 ++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 055e6769a..f01f2b2a7 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -385,8 +385,32 @@ def _create_combined_keys( Labels objects correspond to the keys of the input TensorMaps, and the third Labels object to the keys of the output TensorMap. """ - # Alex's code here - return access_keys_1, access_keys_2, combined_keys + l1s_species_center = keys_1.values[:, keys_1.names.index("species_center")] + l2s_species_center = keys_2.values[:, keys_2.names.index("species_center")] + l1s_spherical_harmonics_l = keys_1.values[:, keys_1.names.index("spherical_harmonics_l")] + l2s_spherical_harmonics_l = keys_2.values[:, keys_2.names.index("spherical_harmonics_l")] + l1_labels = [] + l2_labels = [] + l1l2lam_labels = [] + for species_1, species_2, l1, l2 in zip(l1s_species_center, l2s_species_center, l1s_spherical_harmonics_l, l2s_spherical_harmonics_l): + if species_1 != species_2: + continue + + if l1 < l2: + continue + for lam in lambdas: + if abs(l1-l2) > lam or lam > (l1+l2): + continue + + l1_labels.append({"species_center": species_1, "spherical_harmonics_l": l1}) + l2_labels.append({"species_center": species_1, "spherical_harmonics_l": l2}) + l1l2lam_labels.append({ + "spherical_harmonics_l": lam, + "species_center": species_1, + "l1": l1, + "l2": l2, + }) + return l1_labels, l2_labels, l1l2lam_labels # # Don't compute redundant (l1, l2) pairs # if l1 < l2: From 316fa9314e28011d67da05505b8e0f71638a0cfa Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Mon, 3 Jul 2023 17:42:59 +0200 Subject: [PATCH 10/96] _create_combined_keys now returns 3 labels --- python/rascaline/utils/clebsch_gordan.py | 29 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index f01f2b2a7..ec0bd6132 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -402,15 +402,26 @@ def _create_combined_keys( if abs(l1-l2) > lam or lam > (l1+l2): continue - l1_labels.append({"species_center": species_1, "spherical_harmonics_l": l1}) - l2_labels.append({"species_center": species_1, "spherical_harmonics_l": l2}) - l1l2lam_labels.append({ - "spherical_harmonics_l": lam, - "species_center": species_1, - "l1": l1, - "l2": l2, - }) - return l1_labels, l2_labels, l1l2lam_labels + l1_labels.append([species_1, l1]) + l2_labels.append([species_1, l2]) + l1l2lam_labels.append([ + lam, + species_1, + l1, + l2, + ]) + return (Labels( + names=["spherical_harmonics_l", "species_center"], + values=np.array(l1_labels) + ), + Labels( + names=["spherical_harmonics_l", "species_center"], + values=np.array(l2_labels) + ), + Labels( + names=["spherical_harmonics_l", "species_center", "l1", "l2"], + values=np.array(l1l2lam_labels) + )) # # Don't compute redundant (l1, l2) pairs # if l1 < l2: From b7cbb6460a578a6a4942e827841ece592af8c5b3 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 3 Jul 2023 19:29:35 +0200 Subject: [PATCH 11/96] Single center code and stub for multi center --- python/rascaline/utils/clebsch_gordan.py | 143 +++++++++++++---------- 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index ec0bd6132..c8786ecbb 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -1,14 +1,21 @@ """ Module for computing Clebsch-gordan iterations with equistore TensorMaps. """ +import itertools from typing import Sequence import numpy as np import wigners +import equistore from equistore.core import Labels, TensorBlock, TensorMap +# TODO: +# - [ ] Add support for dense operation +# - [ ] Account for body-order multiplicity + + # ===== Class for calculating Clebsch-Gordan coefficients ===== @@ -149,6 +156,26 @@ def _complex_clebsch_gordan_matrix(l1, l2, L): # ===== Methods for performing CG combinations ===== +def _combine_multi_centers( + tensor_1: TensorMap, + tensor_2: TensorMap, + lambdas: Sequence[int], + cg_cache, + use_sparse: bool = True, +): + """ """ + + +def _combine_multi_centers_block_pair( + block_1: TensorBlock, + block_2: TensorBlock, + lamb: int, + cg_cache, + use_sparse: bool = True, +): + """ """ + + def _combine_single_center( tensor_1: TensorMap, tensor_2: TensorMap, @@ -162,25 +189,40 @@ def _combine_single_center( Assumes """ - access_keys_1, access_keys_2, combined_keys = _create_combined_keys( - tensor_1.keys, tensor_2.keys, lambdas - ) + # Check metadata + if not equistore.equal_metadata(tensor_1, tensor_2, check=["samples"]): + raise ValueError( + "TensorMap pair to combine must have equal samples in the same order" + ) + # Get the correct keys for the combined TensorMap + combined_keys = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas) + + # Iterate over pairs of blocks and combine combined_blocks = [] - for key_1, key_2, combined_key in zip(access_keys_1, access_keys_2, combined_keys): + for combined_key in combined_keys: # Extract the pair of blocks to combine - block_1, block_2 = tensor_1[key_1], tensor_2[key_2] + block_1 = tensor_1.block( + spherical_harmonics_l=combined_key["l1"], + species_center=combined_key["species_center"], + ) + block_2 = tensor_1.block( + spherical_harmonics_l=combined_key["l2"], + species_center=combined_key["species_center"], + ) # Extract the desired lambda value - lamb = combined_key["spherical_harmonics"] + lamb = combined_key["spherical_harmonics_l"] # Combine the blocks into a new TensorBlock combined_blocks.append( - _combine_single_center_block_pair(block_1, block_2, lamb) + _combine_single_center_block_pair( + block_1, block_2, lamb, cg_cache, use_sparse + ) ) - + # Construct our combined TensorMap combined_tensor = TensorMap(combined_keys, combined_blocks) - # Account for body-order multiplicity + # TODO: Account for body-order multiplicity combined_tensor = _apply_body_order_corrections(combined_tensor) # Move the l1, l2 keys to the properties @@ -201,16 +243,13 @@ def _combine_single_center_block_pair( values arrays and returns in a new TensorBlock. """ # Check metadata - if not equistore.equal_metadata(block_1, block_2, check=["samples"]): - raise ValueError( - "TensorBlock pair to combine must have equal samples in the same order" - ) if block_1.properties.names != block_2.properties.names: raise ValueError( "TensorBlock pair to combine must have equal properties in the same order" ) # Do the CG combination - no shape pre-processing required + # print(block_1.values.shape, block_2.values.shape, lamb) combined_values = _clebsch_gordan_combine( block_1.values, block_2.values, lamb, cg_cache ) @@ -219,17 +258,19 @@ def _combine_single_center_block_pair( combined_block = TensorBlock( values=combined_values, samples=block_1.samples, - components=Labels( - names=["spherical_harmonics_m"], - values=np.arange(-lamb, lamb + 1), - ), + components=[ + Labels( + names=["spherical_harmonics_m"], + values=np.arange(-lamb, lamb + 1).reshape(-1, 1), + ), + ], properties=Labels( - names=["n_1", "n_2"], + names=["n1", "n2", "neighbor_1", "neighbor_2"], values=np.array( [ - [i, j] - for j in block_2.properties.values - for i in block_1.properties.values + [n1, n2, neighbor_1, neighbor_2] + for (n2, neighbor_2) in block_2.properties.values + for (n1, neighbor_1) in block_1.properties.values ] ), ), @@ -289,8 +330,8 @@ def _clebsch_gordan_combine_sparse( n_q = arr_2.shape[2] # number of properties in arr_2 # Infer l1 and l2 from the len of the lenght of axis 1 of each tensor - l1 = int((arr_1.shape[1] - 1) / 2) - l2 = int((arr_2.shape[1] - 1) / 2) + l1 = (arr_1.shape[1] - 1) // 2 + l2 = (arr_2.shape[1] - 1) // 2 # Get the corresponding Clebsch-Gordan coefficients cg_coeffs = cg_cache.coeffs[(l1, l2, lamb)] @@ -385,50 +426,24 @@ def _create_combined_keys( Labels objects correspond to the keys of the input TensorMaps, and the third Labels object to the keys of the output TensorMap. """ - l1s_species_center = keys_1.values[:, keys_1.names.index("species_center")] - l2s_species_center = keys_2.values[:, keys_2.names.index("species_center")] - l1s_spherical_harmonics_l = keys_1.values[:, keys_1.names.index("spherical_harmonics_l")] - l2s_spherical_harmonics_l = keys_2.values[:, keys_2.names.index("spherical_harmonics_l")] - l1_labels = [] - l2_labels = [] - l1l2lam_labels = [] - for species_1, species_2, l1, l2 in zip(l1s_species_center, l2s_species_center, l1s_spherical_harmonics_l, l2s_spherical_harmonics_l): - if species_1 != species_2: + # Find the pair product of the keys + combined_vals = set() + for (l1, a), (l2, a2) in itertools.product(keys_1, keys_2): + # Only combine blocks of the same chemical species + if a != a2: continue + # Skip redundant lower triangle of (l1, l2) combinations if l1 < l2: continue - for lam in lambdas: - if abs(l1-l2) > lam or lam > (l1+l2): + + # Only combine to create blocks of desired lambda values + for lam in np.arange(abs(l1 - l2), abs(l1 + l2) + 1): + if lam not in lambdas: continue + combined_vals.add((lam, a, l1, l2)) - l1_labels.append([species_1, l1]) - l2_labels.append([species_1, l2]) - l1l2lam_labels.append([ - lam, - species_1, - l1, - l2, - ]) - return (Labels( - names=["spherical_harmonics_l", "species_center"], - values=np.array(l1_labels) - ), - Labels( - names=["spherical_harmonics_l", "species_center"], - values=np.array(l2_labels) - ), - Labels( - names=["spherical_harmonics_l", "species_center", "l1", "l2"], - values=np.array(l1l2lam_labels) - )) - - # # Don't compute redundant (l1, l2) pairs - # if l1 < l2: - # continue - - # # Compute non-zero lanbda values - # allowed_lambdas = range(np.abs(l1 - l2), abs(l1 + l2) + 1) - # for lamb in allowed_lambdas: - # if lamb not in lambdas: - # continue + return Labels( + names=["spherical_harmonics_l", "species_center", "l1", "l2"], + values=np.array(list(combined_vals)), + ) From c47e369eded9fdc0b7a3165279890def652adf89 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 3 Jul 2023 19:35:33 +0200 Subject: [PATCH 12/96] Temporary notebook for prototyping --- python/rascaline/utils/tmp.ipynb | 103 +++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 python/rascaline/utils/tmp.ipynb diff --git a/python/rascaline/utils/tmp.ipynb b/python/rascaline/utils/tmp.ipynb new file mode 100644 index 000000000..a53df69bb --- /dev/null +++ b/python/rascaline/utils/tmp.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import ase.io\n", + "import numpy as np\n", + "\n", + "import rascaline\n", + "import equistore\n", + "from equistore import Labels, TensorBlock, TensorMap\n", + "\n", + "import clebsch_gordan" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "TensorMap with 10 blocks\n", + "keys: spherical_harmonics_l species_center\n", + " 4 1\n", + " 2 8\n", + " ...\n", + " 4 8\n", + " 3 1" + ] + }, + "execution_count": 125, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "frames = [ase.io.read(\"frame.xyz\")]\n", + "\n", + "lmax = 5\n", + "\n", + "rascal_hypers = {\n", + " \"cutoff\": 3.0, # Angstrom\n", + " \"max_radial\": 6, # Exclusive\n", + " \"max_angular\": 5, # Inclusive\n", + " \"atomic_gaussian_width\": 0.2,\n", + " \"radial_basis\": {\"Gto\": {}},\n", + " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", + " \"center_atom_weight\": 1.0,\n", + "}\n", + "\n", + "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", + "nu1 = calculator.compute(frames)\n", + "nu1 = nu1.keys_to_properties(\"species_neighbor\")\n", + "lambdas = np.arange(lmax)\n", + "\n", + "clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas)\n", + "cg_cache = clebsch_gordan.ClebschGordanReal(l_max=lmax)\n", + "\n", + "lsoap = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache)\n", + "lsoap" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dev", + "language": "python", + "name": "python3" + }, + "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.10.9" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 3252e52fadf8583a8aa33f344f25200b74e22d13 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Fri, 7 Jul 2023 16:43:37 +0200 Subject: [PATCH 13/96] API for combine to nu=2, no inversion yet --- python/rascaline/utils/clebsch_gordan.py | 169 ++++++++++++++++++++--- 1 file changed, 153 insertions(+), 16 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index c8786ecbb..a43838aa2 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -2,18 +2,22 @@ Module for computing Clebsch-gordan iterations with equistore TensorMaps. """ import itertools -from typing import Sequence -import numpy as np +from typing import Optional, Sequence -import wigners +import ase +import numpy as np import equistore from equistore.core import Labels, TensorBlock, TensorMap +import rascaline + +import wigners # TODO: # - [ ] Add support for dense operation # - [ ] Account for body-order multiplicity +# - [ ] Account for nu and sigma combinations! # ===== Class for calculating Clebsch-Gordan coefficients ===== @@ -156,6 +160,9 @@ def _complex_clebsch_gordan_matrix(l1, l2, L): # ===== Methods for performing CG combinations ===== +# ===== Fxns for combining multi center descriptors ===== + + def _combine_multi_centers( tensor_1: TensorMap, tensor_2: TensorMap, @@ -176,6 +183,86 @@ def _combine_multi_centers_block_pair( """ """ +# ===== Fxns for combining single center descriptors ===== + + +def n_body_iteration( + frames: Sequence[ase.Atoms], + rascal_hypers: dict, + nu: int, + lambdas: int, + cg_cache, + use_sparse: bool = True, + intermediate_lambda_max: Optional[int] = None, + selected_samples: Optional[Labels] = None, +) -> TensorMap: + """ + Based on the passed ``rascal_hypers``, generates a rascaline + SphericalExpansion (i.e. nu = 1 body order descriptor) and combines it + iteratively to generate a descriptor of order ``nu``. + + The returned TensorMap will only contain blocks with angular channels of + target order lambda corresponding to those passed in ``lambdas``. + + Passing ``intermediate_lambda_max`` will place a maximum on the angular + order of blocks created by combination at each CG combination step. + """ + # Generate rascaline SphericalExpansion + calculator = rascaline.SphericalExpansion(**rascal_hypers) + nu1 = calculator.compute(frames, selected_samples=selected_samples) + nu1 = nu1.keys_to_properties("species_neighbor") + + # Standardize metadata + nu1 = _add_nu_sigma_to_key_names(nu1) + + return _combine_single_center_to_body_order( + nu1, nu1, lambdas, nu_target=2, cg_cache=cg_cache, use_sparse=use_sparse + ) + + +def _combine_single_center_to_body_order( + tensor_1: TensorMap, + tensor_2: TensorMap, + lambdas: Sequence[int], + nu_target: int, + cg_cache, + use_sparse: bool = True, +) -> TensorMap: + """ + Assumes standradized metadata: + + Key names are: + + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", + "l1", "l2", ...] + + Samples of pairs of blocks corresponding to the same chemical species are + equivalent in the two TensorMaps. Samples names are ["structure", "center"] + + Components names are [["spherical_harmonics_m"],] for each block. + + Property names are ["n1", "n2", ..., "species_neighbor_1", + "species_neighbor_2", ...] for each block. + """ + assert nu_target == 2 # only implemented for nu = 2 + + combined_tensor = _combine_single_center( + tensor_1, tensor_2, lambdas, cg_cache, use_sparse + ) + + # Now we have reached the desired body order: + + # TODO: Account for body-order multiplicity + combined_tensor = _apply_body_order_corrections(combined_tensor) + + # Move the [l1, l2, ...] keys to the properties + combined_tensor = combined_tensor.keys_to_properties( + ["l" + str(i) for i in range(1, len(combined_tensor.keys.names) - 3)] + ) + + return combined_tensor + + def _combine_single_center( tensor_1: TensorMap, tensor_2: TensorMap, @@ -222,12 +309,6 @@ def _combine_single_center( # Construct our combined TensorMap combined_tensor = TensorMap(combined_keys, combined_blocks) - # TODO: Account for body-order multiplicity - combined_tensor = _apply_body_order_corrections(combined_tensor) - - # Move the l1, l2 keys to the properties - combined_tensor = combined_tensor.keys_to_properties(["l1", "l2"]) - return combined_tensor @@ -265,7 +346,7 @@ def _combine_single_center_block_pair( ), ], properties=Labels( - names=["n1", "n2", "neighbor_1", "neighbor_2"], + names=["n1", "n2", "species_neighbor_1", "species_neighbor_2"], values=np.array( [ [n1, n2, neighbor_1, neighbor_2] @@ -279,6 +360,9 @@ def _combine_single_center_block_pair( return combined_block +# ===== Mathematical manipulation fxns + + def _clebsch_gordan_combine( arr_1: np.ndarray, arr_2: np.ndarray, @@ -351,9 +435,6 @@ def _clebsch_gordan_combine_sparse( return arr_out -# ===== For writing a dense version in the future ===== - - def _clebsch_gordan_combine_dense( arr_1: np.ndarray, arr_2: np.ndarray, @@ -412,6 +493,23 @@ def _apply_body_order_corrections(tensor: TensorMap) -> TensorMap: return tensor +# Commented out but left as reference. Not needed as we are just writing an +# end-to-end pipeline for SphericalExpansion -> NICE. +# def _check_nu_combination_valid() -> bool: +# """ """ +# # # Check "order_nu" of each TM to see that it can iteratively add to nu +# # nu1 = np.unique(tensor_1.keys.column("order_nu")) +# # nu2 = np.unique(tensor_2.keys.column("order_nu")) +# # assert len(nu1) != 1 +# # assert len(nu2) != 1 +# # nu1, nu2 = nu1[0], nu2[0] +# # assert _check_nu_combination_valid(nu1, nu2, nu) +# return True + + +# ===== Fxns to manipulate metadata of TensorMaps ===== + + def _create_combined_keys( keys_1: Labels, keys_2: Labels, lambdas: Sequence[int] ) -> Sequence[Labels]: @@ -428,7 +526,9 @@ def _create_combined_keys( """ # Find the pair product of the keys combined_vals = set() - for (l1, a), (l2, a2) in itertools.product(keys_1, keys_2): + for key_1, key_2 in itertools.product(keys_1, keys_2): + nu1, sig1, l1, a = key_1.values[:4] + nu2, sig2, l2, a2 = key_2.values[:4] # Only combine blocks of the same chemical species if a != a2: continue @@ -438,12 +538,49 @@ def _create_combined_keys( continue # Only combine to create blocks of desired lambda values + # TODO: nu and sigma combinations! for lam in np.arange(abs(l1 - l2), abs(l1 + l2) + 1): if lam not in lambdas: continue - combined_vals.add((lam, a, l1, l2)) + combined_vals.add((nu1, sig1, lam, a, l1, l2)) return Labels( - names=["spherical_harmonics_l", "species_center", "l1", "l2"], + names=[ + "order_nu", + "inversion_sigma", + "spherical_harmonics_l", + "species_center", + "l1", + "l2", + ], values=np.array(list(combined_vals)), ) + + +def _add_nu_sigma_to_key_names(tensor: TensorMap) -> TensorMap: + """ + Prepends key names "order_nu" and "inversion_sigma" respectively to the key + names of ``tensor``. + + For instance, if `tensor.keys.names` is ["spherical_harmonics_l", + "species_center"], the returned tensor will have keys with names + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"]. + """ + keys = tensor.keys + prepend_list = [] + if "inversion_sigma" in keys.names: + assert keys.names.index("inversion_sigma") == 1 + else: + prepend_list = [1] + prepend_list + if "order_nu" in keys.names: + assert keys.names.index("order_nu") == 0 + else: + prepend_list = [1] + prepend_list + + new_keys = Labels( + names=["order_nu", "inversion_sigma"] + keys.names, + values=np.array([prepend_list + key_list for key_list in keys.values.tolist()]), + ) + new_blocks = [block.copy() for block in tensor] + + return TensorMap(keys=new_keys, blocks=new_blocks) From e49b7487838aa1028a3b92936661fb0c235ebcc5 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 10 Jul 2023 14:14:51 +0200 Subject: [PATCH 14/96] Lambda-SOAP prototype --- python/rascaline/utils/clebsch_gordan.py | 92 +++++++++++++++++------- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index a43838aa2..94f076bb2 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -17,7 +17,6 @@ # TODO: # - [ ] Add support for dense operation # - [ ] Account for body-order multiplicity -# - [ ] Account for nu and sigma combinations! # ===== Class for calculating Clebsch-Gordan coefficients ===== @@ -272,9 +271,8 @@ def _combine_single_center( ) -> TensorMap: """ For 2 TensorMaps with body orders nu and eta respectively, combines their - blocks to form a new TensorMap with body order (nu + eta). - - Assumes + blocks to form a new TensorMap with body order (nu + eta). Returns blocks + only with angular orders corresponding to those passed in ``lambdas``. """ # Check metadata if not equistore.equal_metadata(tensor_1, tensor_2, check=["samples"]): @@ -515,44 +513,88 @@ def _create_combined_keys( ) -> Sequence[Labels]: """ Given the keys of 2 TensorMaps and a list of desired lambda values, creates - the correct keys for the TensorMap returned after CG combination. + the correct keys for the TensorMap returned after one CG combination. + + The input keys `keys_1` and `keys_2` must follow the key name convention: - The input keys `keys_1` and `keys_2` must contain names - "spherical_harmonics_l" and "species_center". + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", + "l1", "l2", ...] - Returned is a tuple of 3 Labels objects. The first and second returned - Labels objects correspond to the keys of the input TensorMaps, and the third - Labels object to the keys of the output TensorMap. + Returned is a Labels object corresponding to the appropriate keys of the + output TensorMap created by a CG combination step. """ + # Check key names match internal convention + for keys in [keys_1, keys_2]: + assert np.all( + keys.names[:4] + == [ + "order_nu", + "inversion_sigma", + "spherical_harmonics_l", + "species_center", + ] + ) + # Iteratively check the remaining key names are "l1", "l2", ... We will need + # these counters later to name the output keys. + l_counter_1, l_counter_2 = 0, 0 + + # First do for keys_1 + for name in keys_1.names[4:]: + assert name[0] == "l" + l_counter_1 += 1 + assert int(name[1:]) == l_counter_1 + + # Then do for keys_2 + for name in keys_2.names[4:]: + assert name[0] == "l" + l_counter_2 += 1 + assert int(name[1:]) == l_counter_2 + # Find the pair product of the keys combined_vals = set() for key_1, key_2 in itertools.product(keys_1, keys_2): - nu1, sig1, l1, a = key_1.values[:4] - nu2, sig2, l2, a2 = key_2.values[:4] + # Unpack relevant key values + nu1, sig1, lam1, a = key_1.values[:4] + nu2, sig2, lam2, a2 = key_2.values[:4] + # Only combine blocks of the same chemical species if a != a2: continue - # Skip redundant lower triangle of (l1, l2) combinations - if l1 < l2: + # Skip redundant lower triangle of (lam1, lam2) combinations + if lam1 < lam2: continue + # Get the list of previous l values from each key + l_list_1, l_list_2 = key_1.values[4:].tolist(), key_2.values[4:].tolist() + + # Calculate new nu + nu = nu1 + nu2 + # Only combine to create blocks of desired lambda values - # TODO: nu and sigma combinations! - for lam in np.arange(abs(l1 - l2), abs(l1 + l2) + 1): + nonzero_lams = np.arange(abs(lam1 - lam2), abs(lam1 + lam2) + 1) + for lam in nonzero_lams: if lam not in lambdas: continue - combined_vals.add((nu1, sig1, lam, a, l1, l2)) + + # Calculate new sigma + sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) + + # Create a key tuple, ordering the "l1", "l2", etc values + l_list = [lam1] + l_list_1 + [lam2] + l_list_2 + key_tuple = (nu, sig, lam, a, *l_list) + combined_vals.add(key_tuple) + + # Create the output key names + new_names = [ + "order_nu", + "inversion_sigma", + "spherical_harmonics_l", + "species_center", + ] + [f"l{i}" for i in range(1, l_counter_1 + l_counter_2 + 3)] return Labels( - names=[ - "order_nu", - "inversion_sigma", - "spherical_harmonics_l", - "species_center", - "l1", - "l2", - ], + names=new_names, values=np.array(list(combined_vals)), ) From 587ea528c4502ac60f961a54f7d4a208430f2bc4 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 10 Jul 2023 15:01:47 +0200 Subject: [PATCH 15/96] lsoap prototype --- python/rascaline/utils/tmp.ipynb | 49 +++++++------------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/python/rascaline/utils/tmp.ipynb b/python/rascaline/utils/tmp.ipynb index a53df69bb..90f807d43 100644 --- a/python/rascaline/utils/tmp.ipynb +++ b/python/rascaline/utils/tmp.ipynb @@ -2,18 +2,9 @@ "cells": [ { "cell_type": "code", - "execution_count": 52, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -30,50 +21,30 @@ }, { "cell_type": "code", - "execution_count": 125, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TensorMap with 10 blocks\n", - "keys: spherical_harmonics_l species_center\n", - " 4 1\n", - " 2 8\n", - " ...\n", - " 4 8\n", - " 3 1" - ] - }, - "execution_count": 125, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "frames = [ase.io.read(\"frame.xyz\")]\n", "\n", "lmax = 5\n", - "\n", + "lambdas = np.array([0, 2])\n", "rascal_hypers = {\n", " \"cutoff\": 3.0, # Angstrom\n", " \"max_radial\": 6, # Exclusive\n", - " \"max_angular\": 5, # Inclusive\n", + " \"max_angular\": lmax, # Inclusive\n", " \"atomic_gaussian_width\": 0.2,\n", " \"radial_basis\": {\"Gto\": {}},\n", " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", " \"center_atom_weight\": 1.0,\n", "}\n", "\n", - "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", - "nu1 = calculator.compute(frames)\n", - "nu1 = nu1.keys_to_properties(\"species_neighbor\")\n", - "lambdas = np.arange(lmax)\n", - "\n", - "clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas)\n", + "# Precompute CG coefficients\n", "cg_cache = clebsch_gordan.ClebschGordanReal(l_max=lmax)\n", "\n", - "lsoap = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache)\n", + "lsoap = clebsch_gordan.n_body_iteration(\n", + " frames, rascal_hypers=rascal_hypers, nu=2, lambdas=lambdas, cg_cache=cg_cache\n", + ")\n", "lsoap" ] } From 906d34c877e80f6294a8ca6eec3de43f8005fcd6 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 10 Jul 2023 15:01:57 +0200 Subject: [PATCH 16/96] example frame --- python/rascaline/utils/frame.xyz | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 python/rascaline/utils/frame.xyz diff --git a/python/rascaline/utils/frame.xyz b/python/rascaline/utils/frame.xyz new file mode 100644 index 000000000..1fa42c1e3 --- /dev/null +++ b/python/rascaline/utils/frame.xyz @@ -0,0 +1,5 @@ +3 +Properties=species:S:1:pos:R:3 pbc="F F F" +O 0.06633400 0.00000000 0.00370100 +H -0.52638300 -0.76932700 -0.02936600 +H -0.52638300 0.76932700 -0.02936600 From 05e65b959b7fb275702f01658b6a6331ae999a58 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 10 Jul 2023 15:06:41 +0200 Subject: [PATCH 17/96] minor changes --- python/rascaline/utils/clebsch_gordan.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 94f076bb2..869436db4 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -17,6 +17,7 @@ # TODO: # - [ ] Add support for dense operation # - [ ] Account for body-order multiplicity +# - [ ] Gradients? # ===== Class for calculating Clebsch-Gordan coefficients ===== @@ -168,8 +169,9 @@ def _combine_multi_centers( lambdas: Sequence[int], cg_cache, use_sparse: bool = True, -): +) -> TensorMap: """ """ + raise NotImplementedError def _combine_multi_centers_block_pair( @@ -178,14 +180,15 @@ def _combine_multi_centers_block_pair( lamb: int, cg_cache, use_sparse: bool = True, -): +) -> TensorMap: """ """ + raise NotImplementedError # ===== Fxns for combining single center descriptors ===== -def n_body_iteration( +def n_body_iteration_single_center( frames: Sequence[ase.Atoms], rascal_hypers: dict, nu: int, From 44399a0aa1f507bc464136a8b907ffcf096b4bd1 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 10 Jul 2023 17:43:22 +0200 Subject: [PATCH 18/96] Apply corrections for redundant corrections --- python/rascaline/utils/clebsch_gordan.py | 337 ++++++++++++++--------- 1 file changed, 205 insertions(+), 132 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 869436db4..3a027ef39 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -2,7 +2,7 @@ Module for computing Clebsch-gordan iterations with equistore TensorMaps. """ import itertools -from typing import Optional, Sequence +from typing import Optional, Sequence, Tuple import ase import numpy as np @@ -27,30 +27,51 @@ class ClebschGordanReal: """ Class for computing Clebsch-Gordan coefficients for real spherical harmonics. + + Stores the coefficients in a dictionary in the `self.coeffs` attribute, + which is built at initialization. This dictionary has the form: + + { + (l1, l2, lambda): [ + np.ndarray([m1, m2, cg]), + ... + for m1 in range(-l1, l1 + 1), + for m2 in range(-l2, l2 + 1), + ], + ... + for lambda in range(0, l) + } + + where `cg`, i.e. the third value in each array, is the Clebsch-Gordan + coefficient that describes the combination of the `m1` irreducible + component of the `l1` angular channel and the `m2` irreducible component of + the `l2` angular channel into the irreducible tensor of order `lambda`. """ - def __init__(self, l_max: int): - self.l_max = l_max - self.coeffs = ClebschGordanReal.build_coeff_dict(self.l_max) + def __init__(self, lambda_max: int): + self.lambda_max = lambda_max + self.coeffs = ClebschGordanReal.build_coeff_dict(self.lambda_max) @staticmethod - def build_coeff_dict(l_max: int): + def build_coeff_dict(lambda_max: int): """ Builds a dictionary of Clebsch-Gordan coefficients for all possible - combination of l1 and l2, up to l_max. + combination of l1 and l2, up to lambda_max. """ # real-to-complex and complex-to-real transformations as matrices r2c = {} c2r = {} coeff_dict = {} - for L in range(0, l_max + 1): - r2c[L] = _real2complex(L) - c2r[L] = np.conjugate(r2c[L]).T + for lam in range(0, lambda_max + 1): + r2c[lam] = _real2complex(lam) + c2r[lam] = np.conjugate(r2c[lam]).T - for l1 in range(l_max + 1): - for l2 in range(l_max + 1): - for L in range(max(l1, l2) - min(l1, l2), min(l_max, (l1 + l2)) + 1): - complex_cg = _complex_clebsch_gordan_matrix(l1, l2, L) + for l1 in range(lambda_max + 1): + for l2 in range(lambda_max + 1): + for lam in range( + max(l1, l2) - min(l1, l2), min(lambda_max, (l1 + l2)) + 1 + ): + complex_cg = _complex_clebsch_gordan_matrix(l1, l2, lam) real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( complex_cg.shape @@ -62,74 +83,75 @@ def build_coeff_dict(l_max: int): ) real_cg = real_cg.swapaxes(0, 1) - real_cg = real_cg @ c2r[L].T + real_cg = real_cg @ c2r[lam].T - if (l1 + l2 + L) % 2 == 0: + if (l1 + l2 + lam) % 2 == 0: rcg = np.real(real_cg) else: rcg = np.imag(real_cg) new_cg = [] - for M in range(2 * L + 1): - cg_nonzero = np.where(np.abs(rcg[:, :, M]) > 1e-15) + for mu in range(2 * lam + 1): + cg_nonzero = np.where(np.abs(rcg[:, :, mu]) > 1e-15) cg_M = np.zeros( len(cg_nonzero[0]), dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], ) cg_M["m1"] = cg_nonzero[0] cg_M["m2"] = cg_nonzero[1] - cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], M] + cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], mu] new_cg.append(cg_M) - coeff_dict[(l1, l2, L)] = new_cg + coeff_dict[(l1, l2, lam)] = new_cg return coeff_dict -def _real2complex(L: int) -> np.ndarray: +def _real2complex(lam: int) -> np.ndarray: """ Computes a matrix that can be used to convert from real to complex-valued - spherical harmonics(coefficients) of order L. - It's meant to be applied to the left, ``real2complex @ [-L..L]``. + spherical harmonics(coefficients) of order ``lam``. + + It's meant to be applied to the left, ``real2complex @ [-lam, ..., +lam]``. """ - result = np.zeros((2 * L + 1, 2 * L + 1), dtype=np.complex128) + result = np.zeros((2 * lam + 1, 2 * lam + 1), dtype=np.complex128) I_SQRT_2 = 1.0 / np.sqrt(2) - for m in range(-L, L + 1): + for m in range(-lam, lam + 1): if m < 0: - result[L - m, L + m] = I_SQRT_2 * 1j * (-1) ** m - result[L + m, L + m] = -I_SQRT_2 * 1j + result[lam - m, lam + m] = I_SQRT_2 * 1j * (-1) ** m + result[lam + m, lam + m] = -I_SQRT_2 * 1j if m == 0: - result[L, L] = 1.0 + result[lam, lam] = 1.0 if m > 0: - result[L + m, L + m] = I_SQRT_2 * (-1) ** m - result[L - m, L + m] = I_SQRT_2 + result[lam + m, lam + m] = I_SQRT_2 * (-1) ** m + result[lam - m, lam + m] = I_SQRT_2 return result -def _complex_clebsch_gordan_matrix(l1, l2, L): +def _complex_clebsch_gordan_matrix(l1, l2, lam): r"""clebsch-gordan matrix Computes the Clebsch-Gordan (CG) matrix for transforming complex-valued spherical harmonics. The CG matrix is computed as a 3D array of elements - < l1 m1 l2 m2 | L M > + < l1 m1 l2 m2 | lam mu > where the first axis loops over m1, the second loops over m2, - and the third one loops over M. The matrix is real. + and the third one loops over mu. The matrix is real. For example, using the relation: - | l1 l2 L M > = \sum_{m1, m2} | l1 m1 > | l2 m2 > + | l1 l2 lam mu > = \sum_{m1, m2} | l1 m1 > | l2 m2 > (https://en.wikipedia.org/wiki/Clebsch–Gordan_coefficients, section "Formal definition of Clebsch-Gordan coefficients", eq 2) - one can obtain the spherical harmonics L from two sets of + one can obtain the spherical harmonics lam from two sets of spherical harmonics with l1 and l2 (up to a normalization factor). E.g.: Args: l1: l number for the first set of spherical harmonics l2: l number for the second set of spherical harmonics - L: l number For the third set of spherical harmonics + lam: l number For the third set of spherical harmonics Returns: cg: CG matrix for transforming complex-valued spherical harmonics >>> from scipy.special import sph_harm @@ -151,10 +173,10 @@ def _complex_clebsch_gordan_matrix(l1, l2, L): >>> np.allclose(ratio[0], ratio) True """ - if np.abs(l1 - l2) > L or np.abs(l1 + l2) < L: - return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * L + 1), dtype=np.double) + if np.abs(l1 - l2) > lam or np.abs(l1 + l2) < lam: + return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * lam + 1), dtype=np.double) else: - return wigners.clebsch_gordan_array(l1, l2, L) + return wigners.clebsch_gordan_array(l1, l2, lam) # ===== Methods for performing CG combinations ===== @@ -177,7 +199,7 @@ def _combine_multi_centers( def _combine_multi_centers_block_pair( block_1: TensorBlock, block_2: TensorBlock, - lamb: int, + lam: int, cg_cache, use_sparse: bool = True, ) -> TensorMap: @@ -191,9 +213,8 @@ def _combine_multi_centers_block_pair( def n_body_iteration_single_center( frames: Sequence[ase.Atoms], rascal_hypers: dict, - nu: int, - lambdas: int, - cg_cache, + nu_target: int, + lambdas: Sequence[int], use_sparse: bool = True, intermediate_lambda_max: Optional[int] = None, selected_samples: Optional[Labels] = None, @@ -209,50 +230,46 @@ def n_body_iteration_single_center( Passing ``intermediate_lambda_max`` will place a maximum on the angular order of blocks created by combination at each CG combination step. """ - # Generate rascaline SphericalExpansion + if nu_target > 2: # only implemented for nu = 2 + raise NotImplementedError( + "currently CG iterations only implemented for max body order nu=2" + ) + if intermediate_lambda_max is None: + # TODO: figure out a better default? Setting this to the maximum + # possible value by default (as is currently done here) can blow up the + # memory. + intermediate_lambda_max = nu_target * np.max(rascal_hypers["max_angular"]) + else: + # The maximum value intermediate_lambda_max value is dictated by the + # "max_angular" value in the rascal_hypers and the target body order. + # Check the passed value is valid. + if intermediate_lambda_max > nu_target * np.max(rascal_hypers["max_angular"]): + raise ValueError( + "intermediate_lambda_max must be less than nu_target * max_angular" + ) + + # Generate a rascaline SphericalExpansion, for only the selected samples if + # applicable calculator = rascaline.SphericalExpansion(**rascal_hypers) nu1 = calculator.compute(frames, selected_samples=selected_samples) + + # Move the "species_neighbor" key to the properties nu1 = nu1.keys_to_properties("species_neighbor") - # Standardize metadata + # Standardize the key names metadata nu1 = _add_nu_sigma_to_key_names(nu1) - return _combine_single_center_to_body_order( - nu1, nu1, lambdas, nu_target=2, cg_cache=cg_cache, use_sparse=use_sparse - ) - - -def _combine_single_center_to_body_order( - tensor_1: TensorMap, - tensor_2: TensorMap, - lambdas: Sequence[int], - nu_target: int, - cg_cache, - use_sparse: bool = True, -) -> TensorMap: - """ - Assumes standradized metadata: - - Key names are: - - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", - "l1", "l2", ...] - - Samples of pairs of blocks corresponding to the same chemical species are - equivalent in the two TensorMaps. Samples names are ["structure", "center"] - - Components names are [["spherical_harmonics_m"],] for each block. - - Property names are ["n1", "n2", ..., "species_neighbor_1", - "species_neighbor_2", ...] for each block. - """ - assert nu_target == 2 # only implemented for nu = 2 - - combined_tensor = _combine_single_center( - tensor_1, tensor_2, lambdas, cg_cache, use_sparse - ) + # Define the cached CG coefficients - currently only sparse CG matrices implemented + if use_sparse: + cg_cache = ClebschGordanReal(lambda_max=intermediate_lambda_max) + else: + raise NotImplementedError( + "currently CG iterations only implemented for use_sparse=True" + ) - # Now we have reached the desired body order: + # TODO: Combine to the desired body order iteratively. Currently only a + # single CG iteration to body order nu = 2 is implemented. + combined_tensor = _combine_single_center(nu1, nu1, lambdas, cg_cache, use_sparse) # TODO: Account for body-order multiplicity combined_tensor = _apply_body_order_corrections(combined_tensor) @@ -273,52 +290,74 @@ def _combine_single_center( use_sparse: bool = True, ) -> TensorMap: """ - For 2 TensorMaps with body orders nu and eta respectively, combines their - blocks to form a new TensorMap with body order (nu + eta). Returns blocks - only with angular orders corresponding to those passed in ``lambdas``. + For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and eta + respectively, combines their blocks to form a new TensorMap with body order + (nu + eta). Returns blocks only with angular orders corresponding to those + passed in ``lambdas``. + + Assumes the metadata of the two TensorMaps are standaradized as follows. + + Key names are: ["order_nu", "inversion_sigma", "spherical_harmonics_l", + "species_center", "l1", "l2", ...]. + + Samples of pairs of blocks corresponding to the same chemical species are + equivalent in the two TensorMaps. Samples names are ["structure", "center"] + + Components names are [["spherical_harmonics_m"],] for each block. + + Property names are ["n1", "n2", ..., "species_neighbor_1", + "species_neighbor_2", ...] for each block. """ # Check metadata if not equistore.equal_metadata(tensor_1, tensor_2, check=["samples"]): raise ValueError( - "TensorMap pair to combine must have equal samples in the same order" + "TensorMaps `tensor_1` and `tensor_2` to combine must have equivalent keys " + "(order agnostic), and equal samples (same order)" ) + # Get the correct keys for the combined TensorMap - combined_keys = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas) + combined_keys, combination_infos = _create_combined_keys( + tensor_1.keys, tensor_2.keys, lambdas + ) # Iterate over pairs of blocks and combine combined_blocks = [] - for combined_key in combined_keys: - # Extract the pair of blocks to combine + for combined_key, combination_info in zip(combined_keys, combination_infos): + # Extract the pair of blocks to combine. The lambda values of the block + # pair being combined are stored in `combination_info`. block_1 = tensor_1.block( - spherical_harmonics_l=combined_key["l1"], + spherical_harmonics_l=combination_info[0], species_center=combined_key["species_center"], ) - block_2 = tensor_1.block( - spherical_harmonics_l=combined_key["l2"], + block_2 = tensor_2.block( + spherical_harmonics_l=combination_info[1], species_center=combined_key["species_center"], ) - # Extract the desired lambda value - lamb = combined_key["spherical_harmonics_l"] - - # Combine the blocks into a new TensorBlock + # Combine the blocks into a new TensorBlock of the correct lambda order. + # Pass the correction factor accounting for the redundancy of "lx" + # combinations. combined_blocks.append( _combine_single_center_block_pair( - block_1, block_2, lamb, cg_cache, use_sparse + block_1, + block_2, + combined_key["spherical_harmonics_l"], + cg_cache, + use_sparse, + correction_factor=combination_info[2], ) ) - # Construct our combined TensorMap - combined_tensor = TensorMap(combined_keys, combined_blocks) - return combined_tensor + return TensorMap(combined_keys, combined_blocks) def _combine_single_center_block_pair( block_1: TensorBlock, block_2: TensorBlock, - lamb: int, + lam: int, cg_cache, use_sparse: bool = True, + correction_factor: float = 1.0, ) -> TensorBlock: """ For a given pair of TensorBlocks and desired lambda value, combines the @@ -331,19 +370,19 @@ def _combine_single_center_block_pair( ) # Do the CG combination - no shape pre-processing required - # print(block_1.values.shape, block_2.values.shape, lamb) + # print(block_1.values.shape, block_2.values.shape, lam) combined_values = _clebsch_gordan_combine( - block_1.values, block_2.values, lamb, cg_cache + block_1.values, block_2.values, lam, cg_cache ) # Create a TensorBlock combined_block = TensorBlock( - values=combined_values, + values=combined_values * correction_factor, samples=block_1.samples, components=[ Labels( names=["spherical_harmonics_m"], - values=np.arange(-lamb, lamb + 1).reshape(-1, 1), + values=np.arange(-lam, lam + 1).reshape(-1, 1), ), ], properties=Labels( @@ -367,7 +406,7 @@ def _combine_single_center_block_pair( def _clebsch_gordan_combine( arr_1: np.ndarray, arr_2: np.ndarray, - lamb: int, + lam: int, cg_cache, use_sparse: bool = True, ) -> np.ndarray: @@ -383,7 +422,7 @@ def _clebsch_gordan_combine( same. The ouput array has shape (n_i, 2 * lambda + 1, n_p * n_q), where lambda is - the input parameter `lamb`. + the input parameter `lam`. The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these must be produced by the ClebschGordanReal class in this module. @@ -393,14 +432,15 @@ def _clebsch_gordan_combine( """ # Check the first dimension of the arrays are the same (i.e. same samples) if use_sparse: - return _clebsch_gordan_combine_sparse(arr_1, arr_2, lamb, cg_cache) - return _clebsch_gordan_dense(arr_1, arr_2, lamb, cg_cache) + return _clebsch_gordan_combine_sparse(arr_1, arr_2, lam, cg_cache) + raise NotImplementedError + # return _clebsch_gordan_dense(arr_1, arr_2, lam, cg_cache) def _clebsch_gordan_combine_sparse( arr_1: np.ndarray, arr_2: np.ndarray, - lamb: int, + lam: int, cg_cache, ) -> np.ndarray: """ @@ -414,18 +454,18 @@ def _clebsch_gordan_combine_sparse( n_p = arr_1.shape[2] # number of properties in arr_1 n_q = arr_2.shape[2] # number of properties in arr_2 - # Infer l1 and l2 from the len of the lenght of axis 1 of each tensor + # Infer l1 and l2 from the len of the length of axis 1 of each tensor l1 = (arr_1.shape[1] - 1) // 2 l2 = (arr_2.shape[1] - 1) // 2 # Get the corresponding Clebsch-Gordan coefficients - cg_coeffs = cg_cache.coeffs[(l1, l2, lamb)] + cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] # Initialise output array - arr_out = np.zeros((n_i, 2 * lamb + 1, n_p * n_q)) + arr_out = np.zeros((n_i, 2 * lam + 1, n_p * n_q)) # Fill in each mu component of the output array in turn - for mu in range(2 * lamb + 1): + for mu in range(2 * lam + 1): # Iterate over the Clebsch-Gordan coefficients for this mu for m1, m2, cg_coeff in cg_coeffs[mu]: # Broadcast arrays, multiply together and with CG coeff @@ -439,20 +479,20 @@ def _clebsch_gordan_combine_sparse( def _clebsch_gordan_combine_dense( arr_1: np.ndarray, arr_2: np.ndarray, - lamb: int, + lam: int, cg_cache, ) -> np.ndarray: """ arr_1,#: Array[samples, 2 * l1 + 1, q_properties], # mu values for l1 arr_2,#: Array[samples, 2 * l2 + 1, p_properties], # mu values for l2 - lamb: int, - cg_cache,#: Array[(2 * l1 +1) * (2 * l2 +1), (2 * lamb + 1)] - ) -> None: #Array[samples, 2 * lamb + 1, q_properties * p_properties]: + lam: int, + cg_cache,#: Array[(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] + ) -> None: #Array[samples, 2 * lam + 1, q_properties * p_properties]: :param arr_1: array with the mu values for l1 with shape [samples, 2 * l1 + 1, q_properties] :param arr_2: array with the mu values for l1 with shape [samples, 2 * l2 + 1, p_properties] - :param lamb: int resulting coupled channel - :param cg_cache: array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lamb + 1)] + :param lam: int resulting coupled channel + :param cg_cache: array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] :returns lam_mu_values: array of shape [samples, (2 * l1 + 1)* (2 * l2 + 1), q_properties, p_properties] >>> N_SAMPLES = 30 @@ -469,6 +509,7 @@ def _clebsch_gordan_combine_dense( >>> print(np.allclose(out1, out2)) True """ + raise NotImplementedError # l1_mu q, samples l2_mu p -> samples l2_mu p l1_mu q # we broadcast it in this way so we only need to do one swapaxes in the next step out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] @@ -479,9 +520,9 @@ def _clebsch_gordan_combine_dense( -1, arr_1.shape[2] * arr_2.shape[2], arr_1.shape[1] * arr_2.shape[1] ) # l1_mu l2_mu lam_mu -> (l1_mu l2_mu) lam_mu - cg_cache = cg_cache.reshape(-1, 2 * lamb + 1) + cg_cache = cg_cache.reshape(-1, 2 * lam + 1) # samples (q p) (l1_mu l2_mu), (l1_mu l2_mu) lam_mu -> samples (q p) lam_mu - out = out @ cg_cache.reshape(-1, 2 * lamb + 1) + out = out @ cg_cache.reshape(-1, 2 * lam + 1) # samples (q p) lam_mu -> samples lam_mu (q p) return out.swapaxes(1, 2) @@ -513,7 +554,7 @@ def _apply_body_order_corrections(tensor: TensorMap) -> TensorMap: def _create_combined_keys( keys_1: Labels, keys_2: Labels, lambdas: Sequence[int] -) -> Sequence[Labels]: +) -> Tuple[Labels, Sequence[Sequence[int]]]: """ Given the keys of 2 TensorMaps and a list of desired lambda values, creates the correct keys for the TensorMap returned after one CG combination. @@ -523,8 +564,16 @@ def _create_combined_keys( ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", "l1", "l2", ...] - Returned is a Labels object corresponding to the appropriate keys of the - output TensorMap created by a CG combination step. + Returned is a tuple. + + The first element is to the Labels object for the keys of the output + TensorMap created by a CG combination step. + + The second element is a list of list of ints. Each sublist corresponds to + [lam1, lam2, correction_factor terms]. lam1 and lam2 tracks the lambda + values of the blocks that combine to form the block indexed by the + corresponding key. The correction_factor terms are the prefactors that + account for the redundancy in the CG combination. """ # Check key names match internal convention for keys in [keys_1, keys_2]: @@ -554,7 +603,7 @@ def _create_combined_keys( assert int(name[1:]) == l_counter_2 # Find the pair product of the keys - combined_vals = set() + combined_vals = dict() for key_1, key_2 in itertools.product(keys_1, keys_2): # Unpack relevant key values nu1, sig1, lam1, a = key_1.values[:4] @@ -565,11 +614,12 @@ def _create_combined_keys( continue # Skip redundant lower triangle of (lam1, lam2) combinations - if lam1 < lam2: - continue + # if lam1 < lam2: + # continue # Get the list of previous l values from each key - l_list_1, l_list_2 = key_1.values[4:].tolist(), key_2.values[4:].tolist() + l_list_1 = key_1.values[4:].tolist() + l_list_2 = key_2.values[4:].tolist() # Calculate new nu nu = nu1 + nu2 @@ -583,10 +633,25 @@ def _create_combined_keys( # Calculate new sigma sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) - # Create a key tuple, ordering the "l1", "l2", etc values - l_list = [lam1] + l_list_1 + [lam2] + l_list_2 + # Create a list of the "l1", "l2", ... etc values, in ascending + # order such that l1 <= l2 <= l3 < ... + l_list = [lam1] + [lam2] + l_list_1 + l_list_2 + l_list.sort() + + # Define the values for the new key and the lamda values of the + # block pair that created it. key_tuple = (nu, sig, lam, a, *l_list) - combined_vals.add(key_tuple) + + # The number of times this key combination has been made needs to + # been counted for correction later. + if combined_vals.get(key_tuple) is None: + correction_factor = 1 # combination not seen before + else: # combination seen before; increment factor by 1 + correction_factor = combined_vals[key_tuple][2] + 1 + + # Store in a dict such that duplicates are discarded, along with the + # combination info. + combined_vals[key_tuple] = [lam1, lam2, correction_factor] # Create the output key names new_names = [ @@ -596,9 +661,17 @@ def _create_combined_keys( "species_center", ] + [f"l{i}" for i in range(1, l_counter_1 + l_counter_2 + 3)] - return Labels( - names=new_names, - values=np.array(list(combined_vals)), + new_key_vals, combination_infos = [], [] + for new_key_val, combination_info in combined_vals.items(): + new_key_vals.append(new_key_val) + combination_infos.append(combination_info) + + return ( + Labels( + names=new_names, + values=np.array(list(new_key_vals)), + ), + combination_infos, ) From 98f32282ea0f734af7ad0bf5a77b7fd5c0545950 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 10 Jul 2023 17:43:52 +0200 Subject: [PATCH 19/96] import clebsch_gordan --- python/rascaline/utils/__init__.py | 1 + python/rascaline/utils/tmp.ipynb | 48 +++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/python/rascaline/utils/__init__.py b/python/rascaline/utils/__init__.py index de43e8644..0b9eeb24f 100644 --- a/python/rascaline/utils/__init__.py +++ b/python/rascaline/utils/__init__.py @@ -2,6 +2,7 @@ import equistore.core +from . import clebsch_gordan from .power_spectrum import PowerSpectrum diff --git a/python/rascaline/utils/tmp.ipynb b/python/rascaline/utils/tmp.ipynb index 90f807d43..8422b3911 100644 --- a/python/rascaline/utils/tmp.ipynb +++ b/python/rascaline/utils/tmp.ipynb @@ -2,9 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -16,14 +25,32 @@ "import equistore\n", "from equistore import Labels, TensorBlock, TensorMap\n", "\n", - "import clebsch_gordan" + "from rascaline.utils import clebsch_gordan as cg\n", + "# import clebsch_gordan" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "TensorMap with 6 blocks\n", + "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", + " 2 1 0 1\n", + " 2 1 2 1\n", + " ...\n", + " 2 1 2 8\n", + " 2 -1 2 8" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "frames = [ase.io.read(\"frame.xyz\")]\n", "\n", @@ -39,11 +66,12 @@ " \"center_atom_weight\": 1.0,\n", "}\n", "\n", - "# Precompute CG coefficients\n", - "cg_cache = clebsch_gordan.ClebschGordanReal(l_max=lmax)\n", - "\n", - "lsoap = clebsch_gordan.n_body_iteration(\n", - " frames, rascal_hypers=rascal_hypers, nu=2, lambdas=lambdas, cg_cache=cg_cache\n", + "lsoap = cg.n_body_iteration_single_center(\n", + " frames,\n", + " rascal_hypers=rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", + " intermediate_lambda_max=lmax * 2,\n", ")\n", "lsoap" ] From 12d6ba40d02ef4fc10d211f684c478b81a16d18b Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 10 Jul 2023 18:16:30 +0200 Subject: [PATCH 20/96] Allow sparsification with species neighbors not necessarily in `frames` --- python/rascaline/utils/clebsch_gordan.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 3a027ef39..2a8d424fa 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -218,6 +218,7 @@ def n_body_iteration_single_center( use_sparse: bool = True, intermediate_lambda_max: Optional[int] = None, selected_samples: Optional[Labels] = None, + species_neighbors: Optional[Sequence[int]] = None, ) -> TensorMap: """ Based on the passed ``rascal_hypers``, generates a rascaline @@ -253,8 +254,17 @@ def n_body_iteration_single_center( calculator = rascaline.SphericalExpansion(**rascal_hypers) nu1 = calculator.compute(frames, selected_samples=selected_samples) - # Move the "species_neighbor" key to the properties - nu1 = nu1.keys_to_properties("species_neighbor") + # Move the "species_neighbor" key to the properties. If species_neighbors is + # passed as a list of int, sparsity can be created in the properties for + # these species. + if species_neighbors is None: + keys_to_move = "species_neighbor" + else: + keys_to_move = Labels( + names=["species_neighbor"], + values=np.array(species_neighbors).reshape(-1, 1), + ) + nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move) # Standardize the key names metadata nu1 = _add_nu_sigma_to_key_names(nu1) From cdf94edb56f258cde7291ef34a8b983ded781eac Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 10 Jul 2023 18:35:22 +0200 Subject: [PATCH 21/96] Bug in ordering of property columns --- python/rascaline/utils/clebsch_gordan.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 2a8d424fa..09eb39943 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -395,13 +395,15 @@ def _combine_single_center_block_pair( values=np.arange(-lam, lam + 1).reshape(-1, 1), ), ], + # TODO: account for more "species_neighbor_x" and "nx", i.e. for higher + # body order properties=Labels( names=["n1", "n2", "species_neighbor_1", "species_neighbor_2"], values=np.array( [ [n1, n2, neighbor_1, neighbor_2] - for (n2, neighbor_2) in block_2.properties.values - for (n1, neighbor_1) in block_1.properties.values + for (neighbor_2, n2) in block_2.properties.values + for (neighbor_1, n1) in block_1.properties.values ] ), ), From 2bdd7dc69097c176e94aa749d5f218c0498efbe9 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 13 Jul 2023 18:19:10 +0200 Subject: [PATCH 22/96] Multiplicities by tracking intermediate lambda (i.e. "k") --- python/rascaline/utils/clebsch_gordan.py | 393 ++++++++++++++--------- 1 file changed, 249 insertions(+), 144 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 09eb39943..ef228c9cc 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -216,7 +216,7 @@ def n_body_iteration_single_center( nu_target: int, lambdas: Sequence[int], use_sparse: bool = True, - intermediate_lambda_max: Optional[int] = None, + lambda_cut: Optional[int] = None, selected_samples: Optional[Labels] = None, species_neighbors: Optional[Sequence[int]] = None, ) -> TensorMap: @@ -228,31 +228,42 @@ def n_body_iteration_single_center( The returned TensorMap will only contain blocks with angular channels of target order lambda corresponding to those passed in ``lambdas``. - Passing ``intermediate_lambda_max`` will place a maximum on the angular + Passing ``lambda_cut`` will place a maximum on the angular order of blocks created by combination at each CG combination step. + + NOTE: currently only lambda-SOAP (nu_target = 2) is implemented. """ - if nu_target > 2: # only implemented for nu = 2 - raise NotImplementedError( - "currently CG iterations only implemented for max body order nu=2" + # TODO: remove once we can perform higher body orders. + # if nu_target > 2: + # raise NotImplementedError( + # "currently CG iterations only implemented for max body order nu=2" + # ) + + # Set default lambda_cut if not passed + if lambda_cut is None: + # WARNING: the default is the maximum possible angular order for the + # given hypers and target body order. Memory exoplosion possible! + # TODO: better default value here? + lambda_cut = nu_target * np.max(rascal_hypers["max_angular"]) + + # Check `lambda_cut` is valid + if lambda_cut > nu_target * np.max(rascal_hypers["max_angular"]): + raise ValueError( + "`lambda_cut` must be less than `nu_target` * `rascal_hypers['max_angular']`" ) - if intermediate_lambda_max is None: - # TODO: figure out a better default? Setting this to the maximum - # possible value by default (as is currently done here) can blow up the - # memory. - intermediate_lambda_max = nu_target * np.max(rascal_hypers["max_angular"]) + + # Define the cached CG coefficients - currently only sparse CG matrices implemented + if use_sparse: + cg_cache = ClebschGordanReal(lambda_cut) else: - # The maximum value intermediate_lambda_max value is dictated by the - # "max_angular" value in the rascal_hypers and the target body order. - # Check the passed value is valid. - if intermediate_lambda_max > nu_target * np.max(rascal_hypers["max_angular"]): - raise ValueError( - "intermediate_lambda_max must be less than nu_target * max_angular" - ) + raise NotImplementedError( + "currently CG iterations only implemented for use_sparse=True" + ) # Generate a rascaline SphericalExpansion, for only the selected samples if # applicable calculator = rascaline.SphericalExpansion(**rascal_hypers) - nu1 = calculator.compute(frames, selected_samples=selected_samples) + nu1_tensor = calculator.compute(frames, selected_samples=selected_samples) # Move the "species_neighbor" key to the properties. If species_neighbors is # passed as a list of int, sparsity can be created in the properties for @@ -264,29 +275,30 @@ def n_body_iteration_single_center( names=["species_neighbor"], values=np.array(species_neighbors).reshape(-1, 1), ) - nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move) + nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) # Standardize the key names metadata - nu1 = _add_nu_sigma_to_key_names(nu1) - - # Define the cached CG coefficients - currently only sparse CG matrices implemented - if use_sparse: - cg_cache = ClebschGordanReal(lambda_max=intermediate_lambda_max) - else: - raise NotImplementedError( - "currently CG iterations only implemented for use_sparse=True" - ) + nu1_tensor = _add_nu_sigma_to_key_names(nu1_tensor) # TODO: Combine to the desired body order iteratively. Currently only a # single CG iteration to body order nu = 2 is implemented. - combined_tensor = _combine_single_center(nu1, nu1, lambdas, cg_cache, use_sparse) + combined_tensor = nu1_tensor.copy() + for _ in range(nu_target): + combined_tensor = _combine_single_center( + tensor_1=combined_tensor, + tensor_2=nu1_tensor, + lambdas=lambdas, + cg_cache=cg_cache, + use_sparse=use_sparse, + ) # TODO: Account for body-order multiplicity - combined_tensor = _apply_body_order_corrections(combined_tensor) + # combined_tensor = _apply_body_order_corrections(combined_tensor) # Move the [l1, l2, ...] keys to the properties combined_tensor = combined_tensor.keys_to_properties( - ["l" + str(i) for i in range(1, len(combined_tensor.keys.names) - 3)] + [f"l{i}" for i in range(1, nu_target + 1)] + + [f"k{i}" for i in range(2, nu_target)] ) return combined_tensor @@ -300,15 +312,37 @@ def _combine_single_center( use_sparse: bool = True, ) -> TensorMap: """ - For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and eta + For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and 1 respectively, combines their blocks to form a new TensorMap with body order - (nu + eta). Returns blocks only with angular orders corresponding to those + (nu + 1). Returns blocks only with angular orders corresponding to those passed in ``lambdas``. Assumes the metadata of the two TensorMaps are standaradized as follows. - Key names are: ["order_nu", "inversion_sigma", "spherical_harmonics_l", - "species_center", "l1", "l2", ...]. + The keys of `tensor_1` must follow the key name convention: + + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", + "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns + track the l values of the nu=1 blocks that were previously combined. The + "kx" columns tracks the intermediate lambda values of nu > 1 blocks that + haev been combined. + + For instance, a TensorMap of body order nu=4 will have key names + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", + "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of + order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of + order "k2". This was combined with a nu=1 TensorMap with blocks of order + "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was + combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. + + .. math :: + + \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n_{\nu-1} l_{\nu-1} k_{\nu-1} ; + n_{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } + + The keys of `tensor_2` must follow the key name convention: + + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] Samples of pairs of blocks corresponding to the same chemical species are equivalent in the two TensorMaps. Samples names are ["structure", "center"] @@ -318,31 +352,50 @@ def _combine_single_center( Property names are ["n1", "n2", ..., "species_neighbor_1", "species_neighbor_2", ...] for each block. """ - # Check metadata - if not equistore.equal_metadata(tensor_1, tensor_2, check=["samples"]): - raise ValueError( - "TensorMaps `tensor_1` and `tensor_2` to combine must have equivalent keys " - "(order agnostic), and equal samples (same order)" - ) + # TODO: Check all the samples are equivalent + # if not equistore.equal_metadata(tensor_1, tensor_2, check=["samples"]): + # raise ValueError( + # "TensorMaps `tensor_1` and `tensor_2` to combine must have equivalent keys " + # "(order agnostic), and equal samples (same order)" + # ) # Get the correct keys for the combined TensorMap - combined_keys, combination_infos = _create_combined_keys( - tensor_1.keys, tensor_2.keys, lambdas - ) + ( + combined_keys, + keys_1_entries, + keys_2_entries, + multiplicity_list, + ) = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas) # Iterate over pairs of blocks and combine combined_blocks = [] - for combined_key, combination_info in zip(combined_keys, combination_infos): - # Extract the pair of blocks to combine. The lambda values of the block - # pair being combined are stored in `combination_info`. - block_1 = tensor_1.block( - spherical_harmonics_l=combination_info[0], - species_center=combined_key["species_center"], - ) - block_2 = tensor_2.block( - spherical_harmonics_l=combination_info[1], - species_center=combined_key["species_center"], - ) + for combined_key, key_1, key_2, multi in zip( + combined_keys, keys_1_entries, keys_2_entries, multiplicity_list + ): + # # Parse info from the combined key + # nu, sig, lam, a = combined_key.values[:4] + # if nu > 2: + # lam_prev_1 = combined_key[f"k{nu-1}"] + # else: + # lam_prev_1 = combined_key[f"spherical_harmonics_l"] + # lam_prev_2 = combined_key[f"l{nu}"] + + # # Extract the pair of blocks to combine. The lambda values of the block + # # pair being combined are stored in `combination_info`. + # l_list = combined_key.values[4 : 4 + (nu + 1) - 1].tolist() + # k_list = combined_key.values[4 + (nu + 1) : -1].tolist() + # block_1 = tensor_1.block( + # order_nu=nu, + # inversion_sigma=sig, + # spherical_harmonics_l=lam_prev_1, + # species_center=a, + # **{f"l{i + 1}": l for i, l in enumerate(l_list)}, + # **{f"k{i + 2}": k for i, k in enumerate(k_list)}, + # ) + # block_2 = tensor_2.block(spherical_harmonics_l=lam_prev_2, + # species_center=a) + block_1 = tensor_1[key_1] + block_2 = tensor_2[key_2] # Combine the blocks into a new TensorBlock of the correct lambda order. # Pass the correction factor accounting for the redundancy of "lx" @@ -354,7 +407,7 @@ def _combine_single_center( combined_key["spherical_harmonics_l"], cg_cache, use_sparse, - correction_factor=combination_info[2], + correction_factor=multi, ) ) @@ -569,12 +622,35 @@ def _create_combined_keys( ) -> Tuple[Labels, Sequence[Sequence[int]]]: """ Given the keys of 2 TensorMaps and a list of desired lambda values, creates - the correct keys for the TensorMap returned after one CG combination. + the correct keys for the TensorMap returned after one CG combination step. + + Assumes that `keys_1` corresponds to a TensorMap with arbitrary body order, + while `keys_2` corresponds to a TensorMap with body order 1. + + `keys_1` must follow the key name convention: - The input keys `keys_1` and `keys_2` must follow the key name convention: + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", + "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns + track the l values of the nu=1 blocks that were previously combined. The + "kx" columns tracks the intermediate lambda values of nu > 1 blocks that + haev been combined. + For instance, a TensorMap of body order nu=4 will have key names ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", - "l1", "l2", ...] + "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of + order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of + order "k2". This was combined with a nu=1 TensorMap with blocks of order + "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was + combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. + + .. math :: + + \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n_{\nu-1} l_{\nu-1} k_{\nu-1} ; + n_{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } + + `keys_2` must follow the key name convention: + + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] Returned is a tuple. @@ -587,55 +663,58 @@ def _create_combined_keys( corresponding key. The correction_factor terms are the prefactors that account for the redundancy in the CG combination. """ - # Check key names match internal convention - for keys in [keys_1, keys_2]: - assert np.all( - keys.names[:4] - == [ - "order_nu", - "inversion_sigma", - "spherical_harmonics_l", - "species_center", - ] - ) - # Iteratively check the remaining key names are "l1", "l2", ... We will need - # these counters later to name the output keys. - l_counter_1, l_counter_2 = 0, 0 - - # First do for keys_1 - for name in keys_1.names[4:]: - assert name[0] == "l" - l_counter_1 += 1 - assert int(name[1:]) == l_counter_1 - - # Then do for keys_2 - for name in keys_2.names[4:]: - assert name[0] == "l" - l_counter_2 += 1 - assert int(name[1:]) == l_counter_2 - - # Find the pair product of the keys - combined_vals = dict() + # Check that the body order of the second TensorMap is nu = 1 + assert np.all(keys_2.column("order_nu") == 1) + + # Get the body order of the first TensorMap. + nu1 = np.unique(keys_1.column("order_nu"))[0] + assert np.all(keys_1.column("order_nu") == nu1) + + # The second by convention should be nu = 1. + assert np.all(keys_2.column("order_nu") == 1) + + # Define nu value of output TensorMap + nu = nu1 + 1 + + # If nu = 1, the key names don't yet have any "lx" columns + if nu1 == 1: + l_list_names = [] + new_l_list_names = ["l1", "l2"] + else: + l_list_names = [f"l{l}" for l in range(1, nu1 + 1)] + new_l_list_names = l_list_names + [f"l{nu}"] + + # Check key names + assert np.all( + keys_1.names + == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + + l_list_names + + [f"k{k}" for k in range(2, nu1)] + ) + assert np.all( + keys_2.names + == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + ) + + # Define key names of output Labels (i.e. for combined TensorMap) + new_names = ( + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + + new_l_list_names + + [f"k{k}" for k in range(2, nu)] + ) + + new_key_values = [] + keys_1_entries = [] + keys_2_entries = [] for key_1, key_2 in itertools.product(keys_1, keys_2): # Unpack relevant key values - nu1, sig1, lam1, a = key_1.values[:4] - nu2, sig2, lam2, a2 = key_2.values[:4] + sig1, lam1, a = key_1.values[1:4] + sig2, lam2, a2 = key_2.values[1:4] # Only combine blocks of the same chemical species if a != a2: continue - # Skip redundant lower triangle of (lam1, lam2) combinations - # if lam1 < lam2: - # continue - - # Get the list of previous l values from each key - l_list_1 = key_1.values[4:].tolist() - l_list_2 = key_2.values[4:].tolist() - - # Calculate new nu - nu = nu1 + nu2 - # Only combine to create blocks of desired lambda values nonzero_lams = np.arange(abs(lam1 - lam2), abs(lam1 + lam2) + 1) for lam in nonzero_lams: @@ -645,58 +724,84 @@ def _create_combined_keys( # Calculate new sigma sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) - # Create a list of the "l1", "l2", ... etc values, in ascending - # order such that l1 <= l2 <= l3 < ... - l_list = [lam1] + [lam2] + l_list_1 + l_list_2 - l_list.sort() - - # Define the values for the new key and the lamda values of the - # block pair that created it. - key_tuple = (nu, sig, lam, a, *l_list) - - # The number of times this key combination has been made needs to - # been counted for correction later. - if combined_vals.get(key_tuple) is None: - correction_factor = 1 # combination not seen before - else: # combination seen before; increment factor by 1 - correction_factor = combined_vals[key_tuple][2] + 1 - - # Store in a dict such that duplicates are discarded, along with the - # combination info. - combined_vals[key_tuple] = [lam1, lam2, correction_factor] - - # Create the output key names - new_names = [ - "order_nu", - "inversion_sigma", - "spherical_harmonics_l", - "species_center", - ] + [f"l{i}" for i in range(1, l_counter_1 + l_counter_2 + 3)] - - new_key_vals, combination_infos = [], [] - for new_key_val, combination_info in combined_vals.items(): - new_key_vals.append(new_key_val) - combination_infos.append(combination_info) - - return ( - Labels( - names=new_names, - values=np.array(list(new_key_vals)), - ), - combination_infos, + # Extract the l and k lists from keys_1 + l_list = key_1.values[4 : 4 + (nu1 + 1)].tolist() + k_list = key_1.values[4 + nu1 :].tolist() + + # Build the new keys values. l{nu} is `lam2`` (i.e. + # "spherical_harmonics_l" of the key from `keys_2`. k{nu-1} is + # `lam1` (i.e. "spherical_harmonics_l" of the key from `keys_1`). + new_vals = [nu, sig, lam, a] + l_list + [lam2] + k_list + [lam1] + new_key_values.append(new_vals) + keys_1_entries.append(key_1) + keys_2_entries.append(key_2) + + # Define new keys as the full product of keys_1 and keys_2 + combined_keys = Labels(names=new_names, values=np.array(new_key_values)) + + # Now account for multiplicty + key_idxs_to_keep = [] + mult_dict = {} + for key_idx, key in enumerate(combined_keys): + # Get the important key values. This is all of the keys, excpet the k + # list + key_vals_slice = key.values[: 4 + (nu + 1)].tolist() + first_part, l_list = key_vals_slice[:4], key_vals_slice[4:] + + # Sort the l list + l_list_sorted = sorted(l_list) + key_slice_sorted = tuple(first_part + l_list_sorted) + + # Compare the sliced key with the one recreated when the l list is + # sorted. If they are identical, this is the key of the block that we + # want to compute a CG combination for. + key_slice_tuple = tuple(first_part + l_list) + key_slice_sorted_tuple = tuple(first_part + l_list_sorted) + if np.all(key_slice_tuple == key_slice_sorted_tuple): + key_idxs_to_keep.append(key_idx) + + # Now count the multiplicity of each sorted l_list + if mult_dict.get(key_slice_sorted_tuple) is None: + mult_dict[key_slice_sorted_tuple] = 1 + else: + mult_dict[key_slice_sorted_tuple] += 1 + + # Build a reduced Labels object for the combined keys, with redundancies removed + combined_keys_red = Labels( + names=new_names, + values=np.array([combined_keys[idx].values for idx in key_idxs_to_keep]), ) + # Create a of LabelsEntry objects that correspond to the original keys in + # `keys_1` and `keys_2` that combined to form the combined key + keys_1_entries_red = [keys_1_entries[idx] for idx in key_idxs_to_keep] + keys_2_entries_red = [keys_2_entries[idx] for idx in key_idxs_to_keep] + + # Define the multiplicity of each key + mult_list = [ + mult_dict[tuple(combined_keys[idx].values[: 4 + (nu + 1)].tolist())] + for idx in key_idxs_to_keep + ] + + return combined_keys_red, keys_1_entries_red, keys_2_entries_red, mult_list + def _add_nu_sigma_to_key_names(tensor: TensorMap) -> TensorMap: """ Prepends key names "order_nu" and "inversion_sigma" respectively to the key - names of ``tensor``. + names of ``tensor``. This function should only be used on a nu=1 + SphericalExpansion. - For instance, if `tensor.keys.names` is ["spherical_harmonics_l", - "species_center"], the returned tensor will have keys with names - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"]. + For instance, for a tensor with `tensor.keys.names` as + ["spherical_harmonics_l", "species_center"], the returned tensor will have + keys with names ["order_nu", "inversion_sigma", "spherical_harmonics_l", + "species_center"]. """ keys = tensor.keys + if keys.names != ["spherical_harmonics_l", "species_center"]: + raise ValueError( + "this function is only intended to be used on nu=1 SphericalExpansion" + ) prepend_list = [] if "inversion_sigma" in keys.names: assert keys.names.index("inversion_sigma") == 1 From fe65eb795efea3d02f7b9699b3e0c0e8f8e2bf2b Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 13 Jul 2023 18:19:40 +0200 Subject: [PATCH 23/96] more prototyping --- python/rascaline/utils/tmp.ipynb | 73 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/python/rascaline/utils/tmp.ipynb b/python/rascaline/utils/tmp.ipynb index 8422b3911..20c2aaabb 100644 --- a/python/rascaline/utils/tmp.ipynb +++ b/python/rascaline/utils/tmp.ipynb @@ -2,18 +2,9 @@ "cells": [ { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -25,32 +16,15 @@ "import equistore\n", "from equistore import Labels, TensorBlock, TensorMap\n", "\n", - "from rascaline.utils import clebsch_gordan as cg\n", - "# import clebsch_gordan" + "# from rascaline.utils import clebsch_gordan\n", + "import clebsch_gordan" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TensorMap with 6 blocks\n", - "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", - " 2 1 0 1\n", - " 2 1 2 1\n", - " ...\n", - " 2 1 2 8\n", - " 2 -1 2 8" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "frames = [ase.io.read(\"frame.xyz\")]\n", "\n", @@ -66,14 +40,41 @@ " \"center_atom_weight\": 1.0,\n", "}\n", "\n", - "lsoap = cg.n_body_iteration_single_center(\n", + "n_body = clebsch_gordan.n_body_iteration_single_center(\n", " frames,\n", " rascal_hypers=rascal_hypers,\n", - " nu_target=2,\n", + " nu_target=3,\n", " lambdas=lambdas,\n", - " intermediate_lambda_max=lmax * 2,\n", + " lambda_cut=lmax * 2,\n", + " species_neighbors=[1, 8, 6],\n", ")\n", - "lsoap" + "n_body" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "selected_samples = None\n", + "species_neighbors = [1, 8, 6]\n", + "\n", + "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", + "nu1 = calculator.compute(frames, selected_samples=selected_samples)\n", + "\n", + "# Move the \"species_neighbor\" key to the properties. If species_neighbors is\n", + "# passed as a list of int, sparsity can be created in the properties for\n", + "# these species.\n", + "if species_neighbors is None:\n", + " keys_to_move = \"species_neighbor\"\n", + "else:\n", + " keys_to_move = Labels(\n", + " names=[\"species_neighbor\"],\n", + " values=np.array(species_neighbors).reshape(-1, 1),\n", + " )\n", + "nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move)\n", + "nu1 = clebsch_gordan._add_nu_sigma_to_key_names(nu1)" ] } ], From c6d536b9313213209ed7af4ca5e794f416817005 Mon Sep 17 00:00:00 2001 From: DivyaSuman14 Date: Tue, 18 Jul 2023 14:32:44 +0200 Subject: [PATCH 24/96] fix property names for iterations --- python/rascaline/utils/clebsch_gordan.py | 28 +++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index ef228c9cc..7aca0bac1 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -283,7 +283,7 @@ def n_body_iteration_single_center( # TODO: Combine to the desired body order iteratively. Currently only a # single CG iteration to body order nu = 2 is implemented. combined_tensor = nu1_tensor.copy() - for _ in range(nu_target): + for _ in range(1, nu_target): combined_tensor = _combine_single_center( tensor_1=combined_tensor, tensor_2=nu1_tensor, @@ -427,16 +427,24 @@ def _combine_single_center_block_pair( values arrays and returns in a new TensorBlock. """ # Check metadata - if block_1.properties.names != block_2.properties.names: - raise ValueError( - "TensorBlock pair to combine must have equal properties in the same order" - ) + #if block_1.properties.names != block_2.properties.names: + # raise ValueError( + # "TensorBlock pair to combine must have equal properties in the same order" + # ) # Do the CG combination - no shape pre-processing required # print(block_1.values.shape, block_2.values.shape, lam) combined_values = _clebsch_gordan_combine( block_1.values, block_2.values, lam, cg_cache ) + #TODO: Add comments + combined_nu = int(len(block_1.properties.names)/2 + 1) + n_names = [f"n_{i}" for i in range(1, combined_nu+1)] + neighbor_names = [f"species_neighbor_{i}" for i in range(1, combined_nu+1)] + prop_names = [item for i in zip(neighbor_names,n_names) for item in i] + prop_vals = np.array([np.concatenate((b2, b1)) + for b2 in block_2.properties.values + for b1 in block_1.properties.values]) # Create a TensorBlock combined_block = TensorBlock( @@ -451,14 +459,8 @@ def _combine_single_center_block_pair( # TODO: account for more "species_neighbor_x" and "nx", i.e. for higher # body order properties=Labels( - names=["n1", "n2", "species_neighbor_1", "species_neighbor_2"], - values=np.array( - [ - [n1, n2, neighbor_1, neighbor_2] - for (neighbor_2, n2) in block_2.properties.values - for (neighbor_1, n1) in block_1.properties.values - ] - ), + names=prop_names, + values=prop_vals, ), ) From eae57245b7d321b2cfd6b82bd4570420963c142e Mon Sep 17 00:00:00 2001 From: DivyaSuman14 Date: Fri, 21 Jul 2023 14:34:25 +0200 Subject: [PATCH 25/96] fixed bug in property values indexing --- python/rascaline/utils/clebsch_gordan.py | 32 +++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 7aca0bac1..4ba3b78a1 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -427,24 +427,21 @@ def _combine_single_center_block_pair( values arrays and returns in a new TensorBlock. """ # Check metadata - #if block_1.properties.names != block_2.properties.names: + # if block_1.properties.names != block_2.properties.names: # raise ValueError( # "TensorBlock pair to combine must have equal properties in the same order" # ) # Do the CG combination - no shape pre-processing required - # print(block_1.values.shape, block_2.values.shape, lam) combined_values = _clebsch_gordan_combine( block_1.values, block_2.values, lam, cg_cache ) - #TODO: Add comments - combined_nu = int(len(block_1.properties.names)/2 + 1) - n_names = [f"n_{i}" for i in range(1, combined_nu+1)] - neighbor_names = [f"species_neighbor_{i}" for i in range(1, combined_nu+1)] - prop_names = [item for i in zip(neighbor_names,n_names) for item in i] - prop_vals = np.array([np.concatenate((b2, b1)) - for b2 in block_2.properties.values - for b1 in block_1.properties.values]) + + # + combined_nu = int(len(block_1.properties.names) / 2 + 1) + n_names = [f"n_{i}" for i in range(1, combined_nu + 1)] + neighbor_names = [f"species_neighbor_{i}" for i in range(1, combined_nu + 1)] + prop_names = [item for i in zip(neighbor_names, n_names) for item in i] # Create a TensorBlock combined_block = TensorBlock( @@ -460,7 +457,13 @@ def _combine_single_center_block_pair( # body order properties=Labels( names=prop_names, - values=prop_vals, + values=np.array( + [ + np.concatenate((b2, b1)) + for b2 in block_2.properties.values + for b1 in block_1.properties.values + ] + ), ), ) @@ -635,7 +638,7 @@ def _create_combined_keys( "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns track the l values of the nu=1 blocks that were previously combined. The "kx" columns tracks the intermediate lambda values of nu > 1 blocks that - haev been combined. + have been combined. For instance, a TensorMap of body order nu=4 will have key names ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", @@ -727,7 +730,7 @@ def _create_combined_keys( sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) # Extract the l and k lists from keys_1 - l_list = key_1.values[4 : 4 + (nu1 + 1)].tolist() + l_list = key_1.values[4 : 4 + nu1].tolist() k_list = key_1.values[4 + nu1 :].tolist() # Build the new keys values. l{nu} is `lam2`` (i.e. @@ -738,6 +741,8 @@ def _create_combined_keys( keys_1_entries.append(key_1) keys_2_entries.append(key_2) + # print(new_names) + # print(new_key_values) # Define new keys as the full product of keys_1 and keys_2 combined_keys = Labels(names=new_names, values=np.array(new_key_values)) @@ -752,7 +757,6 @@ def _create_combined_keys( # Sort the l list l_list_sorted = sorted(l_list) - key_slice_sorted = tuple(first_part + l_list_sorted) # Compare the sliced key with the one recreated when the l list is # sorted. If they are identical, this is the key of the block that we From 947c60d33232412a817a2eee16f370c7d3f6525e Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Sun, 23 Jul 2023 16:26:16 +0200 Subject: [PATCH 26/96] integretinog dense version into clebsch gordan * computation of dense clebsch gordan matrices * passing use_sparse argumentation through whole computation * adding test file that compares output of sparse and dense --- python/rascaline/utils/clebsch_gordan.py | 104 ++++++++------- python/tests/utils/test_clebsch_gordan.py | 155 ++++++++++++++++++++++ rascaline-c-api/include/rascaline.h | 9 +- 3 files changed, 213 insertions(+), 55 deletions(-) create mode 100644 python/tests/utils/test_clebsch_gordan.py diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 4ba3b78a1..13803d977 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -48,12 +48,17 @@ class ClebschGordanReal: the `l2` angular channel into the irreducible tensor of order `lambda`. """ - def __init__(self, lambda_max: int): - self.lambda_max = lambda_max - self.coeffs = ClebschGordanReal.build_coeff_dict(self.lambda_max) + def __init__(self, lambda_max: int, sparse: bool = True): + self._lambda_max = lambda_max + self._sparse = sparse + self._coeffs = ClebschGordanReal.build_coeff_dict(self._lambda_max, self._sparse) + + @property + def coeffs(self): + return self._coeffs @staticmethod - def build_coeff_dict(lambda_max: int): + def build_coeff_dict(lambda_max: int, sparse: bool): """ Builds a dictionary of Clebsch-Gordan coefficients for all possible combination of l1 and l2, up to lambda_max. @@ -90,18 +95,20 @@ def build_coeff_dict(lambda_max: int): else: rcg = np.imag(real_cg) - new_cg = [] - for mu in range(2 * lam + 1): - cg_nonzero = np.where(np.abs(rcg[:, :, mu]) > 1e-15) - cg_M = np.zeros( - len(cg_nonzero[0]), - dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], - ) - cg_M["m1"] = cg_nonzero[0] - cg_M["m2"] = cg_nonzero[1] - cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], mu] - new_cg.append(cg_M) - + if sparse: + new_cg = [] + for mu in range(2 * lam + 1): + cg_nonzero = np.where(np.abs(rcg[:, :, mu]) > 1e-15) + cg_M = np.zeros( + len(cg_nonzero[0]), + dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], + ) + cg_M["m1"] = cg_nonzero[0] + cg_M["m2"] = cg_nonzero[1] + cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], mu] + new_cg.append(cg_M) + else: + new_cg = rcg coeff_dict[(l1, l2, lam)] = new_cg return coeff_dict @@ -227,7 +234,7 @@ def n_body_iteration_single_center( The returned TensorMap will only contain blocks with angular channels of target order lambda corresponding to those passed in ``lambdas``. - +k_angular Passing ``lambda_cut`` will place a maximum on the angular order of blocks created by combination at each CG combination step. @@ -253,12 +260,7 @@ def n_body_iteration_single_center( ) # Define the cached CG coefficients - currently only sparse CG matrices implemented - if use_sparse: - cg_cache = ClebschGordanReal(lambda_cut) - else: - raise NotImplementedError( - "currently CG iterations only implemented for use_sparse=True" - ) + cg_cache = ClebschGordanReal(lambda_cut, use_sparse) # Generate a rascaline SphericalExpansion, for only the selected samples if # applicable @@ -434,7 +436,7 @@ def _combine_single_center_block_pair( # Do the CG combination - no shape pre-processing required combined_values = _clebsch_gordan_combine( - block_1.values, block_2.values, lam, cg_cache + block_1.values, block_2.values, lam, cg_cache, use_sparse ) # @@ -503,7 +505,7 @@ def _clebsch_gordan_combine( # Check the first dimension of the arrays are the same (i.e. same samples) if use_sparse: return _clebsch_gordan_combine_sparse(arr_1, arr_2, lam, cg_cache) - raise NotImplementedError + return _clebsch_gordan_combine_dense(arr_1, arr_2, lam, cg_cache) # return _clebsch_gordan_dense(arr_1, arr_2, lam, cg_cache) @@ -539,7 +541,7 @@ def _clebsch_gordan_combine_sparse( # Iterate over the Clebsch-Gordan coefficients for this mu for m1, m2, cg_coeff in cg_coeffs[mu]: # Broadcast arrays, multiply together and with CG coeff - arr_out[:, mu, :] = ( + arr_out[:, mu, :] += ( arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeff ).reshape(n_i, n_p * n_q) @@ -547,23 +549,19 @@ def _clebsch_gordan_combine_sparse( def _clebsch_gordan_combine_dense( - arr_1: np.ndarray, - arr_2: np.ndarray, + arr_1, + arr_2, lam: int, cg_cache, -) -> np.ndarray: +): """ - arr_1,#: Array[samples, 2 * l1 + 1, q_properties], # mu values for l1 - arr_2,#: Array[samples, 2 * l2 + 1, p_properties], # mu values for l2 - lam: int, - cg_cache,#: Array[(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] - ) -> None: #Array[samples, 2 * lam + 1, q_properties * p_properties]: - :param arr_1: array with the mu values for l1 with shape [samples, 2 * l1 + 1, q_properties] - :param arr_2: array with the mu values for l1 with shape [samples, 2 * l2 + 1, p_properties] + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + 1, n_p_properties] :param lam: int resulting coupled channel :param cg_cache: array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] - :returns lam_mu_values: array of shape [samples, (2 * l1 + 1)* (2 * l2 + 1), q_properties, p_properties] + + :returns lam_mu_values: array of shape [samples, (2*lam+1), q_properties*p_properties] >>> N_SAMPLES = 30 >>> N_Q_PROPERTIES = 10 @@ -573,27 +571,37 @@ def _clebsch_gordan_combine_dense( >>> LAM = 2 >>> arr_1 = np.random.rand(N_SAMPLES, 2*L1+1, N_Q_PROPERTIES) >>> arr_2 = np.random.rand(N_SAMPLES, 2*L2+1, N_P_PROPERTIES) - >>> cg_cache = np.random.rand(2*L1+1, 2*L2+1, 2*LAM+1) + >>> cg_cache = {(L1, L2, LAM): np.random.rand(2*L1+1, 2*L2+1, 2*LAM+1)} >>> out1 = _clebsch_gordan_dense(arr_1, arr_2, LAM, cg_cache) - >>> out2 = np.einsum("slq, skp, lkL -> sLqp", arr_1, arr_2, cg_cache).reshape(arr_1.shape[0], 2*LAM+1, -1) + >>> # (samples l1_m q_features) (samples l2_m p_features), + >>> # (l1_m l2_m lambda_mu) + >>> # --> (samples, lambda_mu q_features p_features) + >>> # in einsum l1_m is l, l2_m is k, lambda_mu is L + >>> out2 = np.einsum("slq, skp, lkL -> sLqp", arr_1, arr_2, cg_cache[(L1, L2, LAM)]) + >>> # --> (samples lambda_mu (q_features p_features)) + >>> out2 = out2.reshape(arr_1.shape[0], 2*LAM+1, -1) >>> print(np.allclose(out1, out2)) True """ - raise NotImplementedError - # l1_mu q, samples l2_mu p -> samples l2_mu p l1_mu q + # Infer l1 and l2 from the len of the length of axis 1 of each tensor + l1 = (arr_1.shape[1] - 1) // 2 + l2 = (arr_2.shape[1] - 1) // 2 + cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] + + # (samples None None l1_mu q) * (samples l2_mu p None None) -> (samples l2_mu p l1_mu q) # we broadcast it in this way so we only need to do one swapaxes in the next step out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] - # samples l2_mu p l1_mu q -> samples q p l1_mu l2_mu + # (samples l2_mu p l1_mu q) -> (samples q p l1_mu l2_mu) out = out.swapaxes(1, 4) - # samples q p l1_mu l2_mu -> samples (q p) (l1_mu l2_mu) + # samples (q p l1_mu l2_mu) -> (samples (q p) (l1_mu l2_mu)) out = out.reshape( -1, arr_1.shape[2] * arr_2.shape[2], arr_1.shape[1] * arr_2.shape[1] ) - # l1_mu l2_mu lam_mu -> (l1_mu l2_mu) lam_mu - cg_cache = cg_cache.reshape(-1, 2 * lam + 1) - # samples (q p) (l1_mu l2_mu), (l1_mu l2_mu) lam_mu -> samples (q p) lam_mu - out = out @ cg_cache.reshape(-1, 2 * lam + 1) - # samples (q p) lam_mu -> samples lam_mu (q p) + # (l1_mu l2_mu lam_mu) -> ((l1_mu l2_mu) lam_mu) + cg_coeffs = cg_coeffs.reshape(-1, 2 * lam + 1) + # (samples (q p) (l1_mu l2_mu)) @ ((l1_mu l2_mu) lam_mu) -> samples (q p) lam_mu + out = out @ cg_coeffs + # (samples (q p) lam_mu) -> (samples lam_mu (q p)) return out.swapaxes(1, 2) diff --git a/python/tests/utils/test_clebsch_gordan.py b/python/tests/utils/test_clebsch_gordan.py new file mode 100644 index 000000000..774d38453 --- /dev/null +++ b/python/tests/utils/test_clebsch_gordan.py @@ -0,0 +1,155 @@ +import numpy as np + +import ase.io +import numpy as np + +import rascaline +import equistore +from equistore import Labels, TensorBlock, TensorMap +import equistore.operations + +from rascaline.utils import clebsch_gordan + +def test_clebsch_gordan_combine_dense_sparse_agree(): + N_SAMPLES = 30 + N_Q_PROPERTIES = 10 + N_P_PROPERTIES = 8 + L1 = 2 + L2 = 1 + LAM = 2 + arr_1 = np.random.rand(N_SAMPLES, 2*L1+1, N_Q_PROPERTIES) + arr_2 = np.random.rand(N_SAMPLES, 2*L2+1, N_P_PROPERTIES) + + cg_cache_sparse = clebsch_gordan.ClebschGordanReal(lambda_max=LAM, sparse=True) + out_sparse = clebsch_gordan._clebsch_gordan_combine_sparse(arr_1, arr_2, LAM, cg_cache_sparse) + + cg_cache_dense = clebsch_gordan.ClebschGordanReal(lambda_max=LAM, sparse=False) + out_dense = clebsch_gordan._clebsch_gordan_combine_dense(arr_1, arr_2, LAM, cg_cache_dense) + + assert np.allclose(out_sparse, out_dense) + + +def test_n_body_iteration_single_center_dense_sparse_agree(): + lmax = 5 + lambdas = np.array([0, 2]) + rascal_hypers = { + "cutoff": 3.0, # Angstrom + "max_radial": 6, # Exclusive + "max_angular": lmax, # Inclusive + "atomic_gaussian_width": 0.2, + "radial_basis": {"Gto": {}}, + "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + "center_atom_weight": 1.0, + } + + frames = [ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], + [-0.526383, 0.769327, -0.029366], + [ 0.066334, 0.000000, 0.003701]])] + + n_body_sparse = clebsch_gordan.n_body_iteration_single_center( + frames, + rascal_hypers=rascal_hypers, + nu_target=3, + lambdas=lambdas, + lambda_cut=lmax * 2, + species_neighbors=[1, 8, 6], + use_sparse=True + ) + + n_body_dense = clebsch_gordan.n_body_iteration_single_center( + frames, + rascal_hypers=rascal_hypers, + nu_target=3, + lambdas=lambdas, + lambda_cut=lmax * 2, + species_neighbors=[1, 8, 6], + use_sparse=False + ) + + assert equistore.operations.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) + +# old test that become decprecated through prototyping on API +#def test_soap_kernel(): +# """ +# Tests if we get the same result computing SOAP from spherical expansion coefficients using GC utils +# as when using SoapPowerSpectrum. +# +# """ +# frames = ase.Atoms('HO', +# positions=[[0., 0., 0.], [1., 1., 1.]], +# pbc=[False, False, False]) +# +# +# rascal_hypers = { +# "cutoff": 3.0, # Angstrom +# "max_radial": 2, # Exclusive +# "max_angular": 3, # Inclusive +# "atomic_gaussian_width": 0.2, +# "radial_basis": {"Gto": {}}, +# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, +# "center_atom_weight": 1.0, +# } +# +# calculator = rascaline.SphericalExpansion(**rascal_hypers) +# nu1 = calculator.compute(frames) +# nu1 = nu1.keys_to_properties("species_neighbor") +# +# lmax = 1 +# lambdas = np.arange(lmax) +# +# clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) +# cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) +# soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) +# soap_cg = soap_cg.keys_to_properties(["spherical_harmonics_l"]).keys_to_samples(["species_center"]) +# n_samples = len(soap_cg[0].samples) +# kernel_cg = np.zeros((n_samples, n_samples)) +# for key, block in soap_cg.items(): +# kernel_cg += block.values.squeeze() @ block.values.squeeze().T +# +# calculator = rascaline.SoapPowerSpectrum(**rascal_hypers) +# nu2 = calculator.compute(frames) +# soap_rascaline = nu2.keys_to_properties(["species_neighbor_1", "species_neighbor_2"]).keys_to_samples(["species_center"]) +# kernel_rascaline = np.zeros((n_samples, n_samples)) +# for key, block in soap_rascaline.items(): +# kernel_rascaline += block.values.squeeze() @ block.values.squeeze().T +# +# # worries me a bit that the rtol is shit, might be missing multiplicity? +# assert np.allclose(kernel_cg, kernel_rascaline, atol=1e-9, rtol=1e-1) +# +#def test_soap_zeros(): +# """ +# Tests if the l1!=l2 values are zero when computing the 3-body invariant (SOAP) +# """ +# frames = ase.Atoms('HO', +# positions=[[0., 0., 0.], [1., 1., 1.]], +# pbc=[False, False, False]) +# +# +# rascal_hypers = { +# "cutoff": 3.0, # Angstrom +# "max_radial": 2, # Exclusive +# "max_angular": 3, # Inclusive +# "atomic_gaussian_width": 0.2, +# "radial_basis": {"Gto": {}}, +# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, +# "center_atom_weight": 1.0, +# } +# +# calculator = rascaline.SphericalExpansion(**rascal_hypers) +# nu1 = calculator.compute(frames) +# nu1 = nu1.keys_to_properties("species_neighbor") +# +# lmax = 1 +# lambdas = np.arange(lmax) +# +# clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) +# cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) +# soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) +# soap_cg.keys_to_properties("spherical_harmonics_l") +# sliced_blocks = [] +# for key, block in soap_cg.items(): +# idx = block.properties.values[:, block.properties.names.index("l1")] != block.properties.values[:, block.properties.names.index("l2")] +# sliced_block = equistore.slice_block(block, "properties", Labels(names=block.properties.names, values=block.properties.values[idx])) +# sliced_blocks.append(sliced_block) +# +# assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) diff --git a/rascaline-c-api/include/rascaline.h b/rascaline-c-api/include/rascaline.h index 2f351d1e9..d3646ce20 100644 --- a/rascaline-c-api/include/rascaline.h +++ b/rascaline-c-api/include/rascaline.h @@ -210,9 +210,7 @@ typedef struct rascal_system_t { * cutoff passed in the last call to `compute_neighbors`. This function is * only valid to call after a call to `compute_neighbors`. */ - rascal_status_t (*pairs)(const void *user_data, - const struct rascal_pair_t **pairs, - uintptr_t *count); + rascal_status_t (*pairs)(const void *user_data, const struct rascal_pair_t **pairs, uintptr_t *count); /** * This function should set `*pairs` to a pointer to the first element of a * contiguous array containing all pairs in this system containing the atom @@ -224,10 +222,7 @@ typedef struct rascal_system_t { * included both in the return of `pairs_containing(i)` and * `pairs_containing(j)`. */ - rascal_status_t (*pairs_containing)(const void *user_data, - uintptr_t center, - const struct rascal_pair_t **pairs, - uintptr_t *count); + rascal_status_t (*pairs_containing)(const void *user_data, uintptr_t center, const struct rascal_pair_t **pairs, uintptr_t *count); } rascal_system_t; /** From deb0627feb92a23b30d072099b73597b1922b7bc Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Sun, 23 Jul 2023 16:51:56 +0200 Subject: [PATCH 27/96] simplify sparse cg coeffs format --- python/rascaline/utils/clebsch_gordan.py | 35 ++++++++---------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/python/rascaline/utils/clebsch_gordan.py b/python/rascaline/utils/clebsch_gordan.py index 13803d977..5fee005e3 100644 --- a/python/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/utils/clebsch_gordan.py @@ -91,25 +91,16 @@ def build_coeff_dict(lambda_max: int, sparse: bool): real_cg = real_cg @ c2r[lam].T if (l1 + l2 + lam) % 2 == 0: - rcg = np.real(real_cg) + cg_l1l2lam = np.real(real_cg) else: - rcg = np.imag(real_cg) + cg_l1l2lam = np.imag(real_cg) if sparse: - new_cg = [] - for mu in range(2 * lam + 1): - cg_nonzero = np.where(np.abs(rcg[:, :, mu]) > 1e-15) - cg_M = np.zeros( - len(cg_nonzero[0]), - dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], - ) - cg_M["m1"] = cg_nonzero[0] - cg_M["m2"] = cg_nonzero[1] - cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], mu] - new_cg.append(cg_M) - else: - new_cg = rcg - coeff_dict[(l1, l2, lam)] = new_cg + # if sparse we make a dictionary out of the matrix + nonzeros_cg_coeffs_idx = np.where(np.abs(cg_l1l2lam) > 1e-15) + cg_l1l2lam = {(m1, m2, mu): cg_l1l2lam[m1, m2, mu] + for m1, m2, mu in zip(*nonzeros_cg_coeffs_idx)} + coeff_dict[(l1, l2, lam)] = cg_l1l2lam return coeff_dict @@ -537,13 +528,11 @@ def _clebsch_gordan_combine_sparse( arr_out = np.zeros((n_i, 2 * lam + 1, n_p * n_q)) # Fill in each mu component of the output array in turn - for mu in range(2 * lam + 1): - # Iterate over the Clebsch-Gordan coefficients for this mu - for m1, m2, cg_coeff in cg_coeffs[mu]: - # Broadcast arrays, multiply together and with CG coeff - arr_out[:, mu, :] += ( - arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeff - ).reshape(n_i, n_p * n_q) + for m1, m2, mu in cg_coeffs.keys(): + # Broadcast arrays, multiply together and with CG coeff + arr_out[:, mu, :] += ( + arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeffs[(m1, m2, mu)] + ).reshape(n_i, n_p * n_q) return arr_out From f763c4b4b2e18d687df93c8b515b9ccd197031e7 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 26 Jul 2023 10:50:43 +0200 Subject: [PATCH 28/96] New example data --- .../utils/combined_magres_spherical.xyz | 1584 +++++++++++++++++ 1 file changed, 1584 insertions(+) create mode 100644 python/rascaline/rascaline/utils/combined_magres_spherical.xyz diff --git a/python/rascaline/rascaline/utils/combined_magres_spherical.xyz b/python/rascaline/rascaline/utils/combined_magres_spherical.xyz new file mode 100644 index 000000000..8272cb8e0 --- /dev/null +++ b/python/rascaline/rascaline/utils/combined_magres_spherical.xyz @@ -0,0 +1,1584 @@ +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.01060000 -0.00000000 0.00000000 -0.00000000 -0.01060000 -0.00000000 0.00000000 -0.00000000 0.02120000 -0.00000000 0.00000000 0.00000000 0.02596459 0.00000000 0.00000000 +Li 0.00000000 0.00000000 0.00000000 -0.00910000 -0.00000000 0.00000000 -0.00000000 -0.00910000 0.00000000 0.00000000 0.00000000 0.01820000 -0.00000000 0.00000000 0.00000000 0.02229036 0.00000000 0.00000000 +Li 2.95500000 1.70600000 6.63400000 0.00740000 -0.00000000 0.00000000 -0.00000000 0.00740000 -0.00000000 0.00000000 -0.00000000 -0.01470000 -0.00005774 0.00000000 0.00000000 -0.01804457 0.00000000 0.00000000 +Li 2.95500000 1.70600000 3.01600000 0.00710000 -0.00000000 0.00000000 -0.00000000 0.00710000 -0.00000000 0.00000000 -0.00000000 -0.01410000 -0.00005774 0.00000000 0.00000000 -0.01730973 0.00000000 0.00000000 +Li 0.00000000 3.41200000 11.45900000 -0.00080000 0.00000000 0.00000000 0.00000000 -0.00080000 -0.00000000 0.00000000 -0.00000000 0.00160000 -0.00000000 0.00000000 0.00000000 0.00195959 0.00000000 0.00000000 +Li 0.00000000 3.41200000 7.84000000 -0.00030000 -0.00000000 0.00000000 -0.00000000 -0.00030000 -0.00000000 0.00000000 -0.00000000 0.00050000 0.00005774 0.00000000 0.00000000 0.00065320 0.00000000 0.00000000 +Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 0.00000000 -0.00000000 0.00670000 -0.00000000 0.00000000 -0.00000000 -0.01330000 -0.00005774 0.00000000 0.00000000 -0.01632993 0.00000000 0.00000000 +Li 2.95500000 1.70600000 12.06200000 0.00590000 -0.00000000 0.00000000 -0.00000000 0.00590000 -0.00000000 0.00000000 -0.00000000 -0.01170000 -0.00005774 0.00000000 0.00000000 -0.01437034 0.00000000 0.00000000 +O 0.00000000 0.00000000 3.80700000 0.06860000 -0.00000000 0.00000000 -0.00000000 0.06860000 -0.00000000 0.00000000 -0.00000000 -0.13710000 -0.00005774 0.00000000 0.00000000 -0.16795335 0.00000000 0.00000000 +O 0.00000000 0.00000000 10.66800000 0.05090000 -0.00000000 0.00000000 -0.00000000 0.05090000 0.00000000 0.00000000 0.00000000 -0.10170000 -0.00005774 0.00000000 0.00000000 -0.12459738 0.00000000 0.00000000 +O 2.95500000 1.70600000 8.63200000 0.05550000 0.00000000 -0.00000000 0.00000000 0.05550000 -0.00000000 -0.00000000 -0.00000000 -0.11100000 0.00000000 0.00000000 0.00000000 -0.13594668 0.00000000 0.00000000 +O 2.95500000 1.70600000 1.01800000 0.09550000 -0.00000000 0.00000000 -0.00000000 0.09550000 0.00000000 0.00000000 0.00000000 -0.19100000 0.00000000 0.00000000 0.00000000 -0.23392627 0.00000000 0.00000000 +O 0.00000000 3.41200000 13.45700000 -0.00340000 0.00000000 -0.00000000 0.00000000 -0.00340000 -0.00000000 -0.00000000 -0.00000000 0.00680000 0.00000000 0.00000000 0.00000000 0.00832827 0.00000000 0.00000000 +O 0.00000000 3.41200000 5.84300000 0.00840000 0.00000000 -0.00000000 0.00000000 0.00840000 0.00000000 -0.00000000 0.00000000 -0.01670000 -0.00005774 0.00000000 0.00000000 -0.02049406 0.00000000 0.00000000 +O -1.32400000 4.17600000 1.14400000 0.06020000 -0.04410000 0.06170000 -0.04410000 0.00930000 -0.03560000 0.06170000 -0.03560000 -0.06950000 0.00000000 -0.06236682 -0.05034600 -0.08511977 0.08725698 0.03599174 +O 0.00000000 1.88300000 1.14400000 -0.01610000 0.00000000 -0.00000000 0.00000000 0.08560000 0.07120000 -0.00000000 0.07120000 -0.06950000 0.00000000 0.00000000 0.10069201 -0.08511977 0.00000000 -0.07191276 +O 1.32400000 4.17600000 1.14400000 0.06020000 0.04410000 -0.06170000 0.04410000 0.00930000 -0.03560000 -0.06170000 -0.03560000 -0.06950000 0.00000000 0.06236682 -0.05034600 -0.08511977 -0.08725698 0.03599174 +O 4.27800000 0.94200000 13.33100000 -0.10950000 0.04920000 0.02020000 0.04920000 -0.05270000 -0.01170000 0.02020000 -0.01170000 0.16220000 -0.00000000 0.06957931 -0.01654630 0.19865362 0.02856711 -0.04016367 +O 1.63100000 0.94200000 13.33100000 -0.10950000 -0.04920000 -0.02020000 -0.04920000 -0.05270000 -0.01170000 -0.02020000 -0.01170000 0.16220000 -0.00000000 -0.06957931 -0.01654630 0.19865362 -0.02856711 -0.04016367 +O 2.95500000 3.23400000 13.33100000 -0.02430000 0.00000000 -0.00000000 0.00000000 -0.13790000 0.02340000 -0.00000000 0.02340000 0.16220000 -0.00000000 0.00000000 0.03309260 0.19865362 0.00000000 0.08032733 +O 4.58600000 0.76400000 5.96800000 -0.11800000 0.10490000 -0.07110000 0.10490000 0.00310000 0.04100000 -0.07110000 0.04100000 0.11490000 -0.00000000 0.14835100 0.05798276 0.14072319 -0.10055058 -0.08563063 +O 2.95500000 3.58900000 5.96800000 0.06370000 0.00000000 0.00000000 0.00000000 -0.17860000 -0.08210000 0.00000000 -0.08210000 0.11490000 -0.00000000 0.00000000 -0.11610693 0.14072319 0.00000000 0.17133197 +O 1.32400000 0.76400000 5.96800000 -0.11800000 -0.10490000 0.07110000 -0.10490000 0.00310000 0.04100000 0.07110000 0.04100000 0.11490000 -0.00000000 -0.14835100 0.05798276 0.14072319 0.10055058 -0.08563063 +O 1.32400000 2.64800000 3.68100000 -0.00510000 0.02280000 0.00150000 0.02280000 0.02120000 -0.00090000 0.00150000 -0.00090000 -0.01610000 0.00000000 0.03224407 -0.00127279 -0.01971839 0.00212132 -0.01859691 +O -1.32400000 2.64800000 3.68100000 -0.00510000 -0.02280000 -0.00150000 -0.02280000 0.02120000 -0.00090000 -0.00150000 -0.00090000 -0.01610000 0.00000000 -0.03224407 -0.00127279 -0.01971839 -0.00212132 -0.01859691 +O 0.00000000 4.94000000 3.68100000 0.03440000 -0.00000000 0.00000000 -0.00000000 -0.01830000 0.00170000 0.00000000 0.00170000 -0.01610000 0.00000000 0.00000000 0.00240416 -0.01971839 0.00000000 0.03726453 +O 1.63100000 2.47000000 10.79300000 0.01150000 0.00820000 0.02680000 0.00820000 0.02090000 -0.01550000 0.02680000 -0.01550000 -0.03240000 0.00000000 0.01159655 -0.02192031 -0.03968173 0.03790092 -0.00664680 +O 2.95500000 0.17700000 10.79300000 0.02560000 0.00000000 0.00000000 0.00000000 0.00680000 0.03100000 0.00000000 0.03100000 -0.03240000 0.00000000 0.00000000 0.04384062 -0.03968173 0.00000000 0.01329361 +O 4.27800000 2.47000000 10.79300000 0.01150000 -0.00820000 -0.02680000 -0.00820000 0.02090000 -0.01550000 -0.02680000 -0.01550000 -0.03240000 0.00000000 -0.01159655 -0.02192031 -0.03968173 -0.03790092 -0.00664680 +O -1.63100000 4.35300000 8.50600000 -0.01090000 0.04610000 0.01230000 0.04610000 0.04240000 -0.00710000 0.01230000 -0.00710000 -0.03150000 0.00000000 0.06519525 -0.01004092 -0.03857946 0.01739483 -0.03768879 +O 1.63100000 4.35300000 8.50600000 -0.01090000 -0.04610000 -0.01230000 -0.04610000 0.04240000 -0.00710000 -0.01230000 -0.00710000 -0.03150000 0.00000000 -0.06519525 -0.01004092 -0.03857946 -0.01739483 -0.03768879 +O 0.00000000 1.52800000 8.50600000 0.06900000 0.00000000 -0.00000000 0.00000000 -0.03750000 0.01420000 -0.00000000 0.01420000 -0.03150000 0.00000000 0.00000000 0.02008183 -0.03857946 0.00000000 0.07530687 +Ti 0.00000000 3.41200000 2.41200000 0.03660000 0.00000000 -0.00000000 0.00000000 0.03660000 -0.00000000 -0.00000000 -0.00000000 -0.07330000 0.00005774 0.00000000 0.00000000 -0.08973297 0.00000000 0.00000000 +Ti 2.95500000 0.00000000 0.00000000 0.08900000 -0.00000000 0.00000000 -0.00000000 -0.13050000 0.13930000 0.00000000 0.13930000 0.04150000 0.00000000 0.00000000 0.19699995 0.05082691 0.00000000 0.15520994 +Ti -1.47700000 2.55900000 0.00000000 -0.07560000 -0.09510000 -0.12060000 -0.09510000 0.03420000 -0.06960000 -0.12060000 -0.06960000 0.04150000 -0.00005774 -0.13449171 -0.09842926 0.05078609 -0.17055416 -0.07764032 +Ti 1.47700000 2.55900000 0.00000000 -0.07560000 0.09510000 0.12060000 0.09510000 0.03420000 -0.06960000 0.12060000 -0.06960000 0.04150000 -0.00005774 0.13449171 -0.09842926 0.05078609 0.17055416 -0.07764032 +Ti 0.00000000 1.70600000 4.82500000 0.05950000 -0.00000000 -0.00000000 -0.00000000 -0.10090000 0.15820000 -0.00000000 0.15820000 0.04130000 0.00005774 0.00000000 0.22372859 0.05062279 0.00000000 0.11341993 +Ti 1.47700000 4.26500000 4.82500000 -0.06080000 -0.06950000 -0.13700000 -0.06950000 0.01940000 -0.07910000 -0.13700000 -0.07910000 0.04130000 0.00005774 -0.09828784 -0.11186429 0.05062279 -0.19374726 -0.05670996 +Ti -1.47700000 4.26500000 4.82500000 -0.06080000 0.06950000 0.13700000 0.06950000 0.01940000 -0.07910000 0.13700000 -0.07910000 0.04130000 0.00005774 0.09828784 -0.11186429 0.05062279 0.19374726 -0.05670996 +Ti 2.95500000 3.41200000 9.65000000 0.10390000 -0.00000000 -0.00000000 -0.00000000 -0.14050000 0.21220000 -0.00000000 0.21220000 0.03660000 0.00000000 0.00000000 0.30009612 0.04482566 0.00000000 0.17281690 +Ti 1.47700000 0.85300000 9.65000000 -0.07940000 -0.10580000 -0.18380000 -0.10580000 0.04280000 -0.10610000 -0.18380000 -0.10610000 0.03660000 -0.00000000 -0.14962379 -0.15004806 0.04482566 -0.25993245 -0.08640845 +Ti 4.43200000 0.85300000 9.65000000 -0.07940000 0.10580000 0.18380000 0.10580000 0.04280000 -0.10610000 0.18380000 -0.10610000 0.03660000 -0.00000000 0.14962379 -0.15004806 0.04482566 0.25993245 -0.08640845 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00660000 0.00030000 0.00000000 0.00030000 0.00630000 0.00000000 0.00000000 0.00000000 -0.01290000 0.00000000 0.00042426 0.00000000 -0.01579921 0.00000000 0.00021213 +Li 1.47700000 4.26500000 12.06200000 0.00000000 0.00130000 -0.00740000 0.00130000 -0.00150000 -0.00430000 -0.00740000 -0.00430000 0.00140000 0.00005774 0.00183848 -0.00608112 0.00175547 -0.01046518 0.00106066 +Li 2.95500000 1.70600000 6.63400000 0.00750000 0.00040000 0.00010000 0.00040000 0.00700000 0.00000000 0.00010000 0.00000000 -0.01450000 0.00000000 0.00056569 0.00000000 -0.01775880 0.00014142 0.00035355 +Li 2.95500000 1.70600000 3.01600000 0.00850000 0.00040000 0.00010000 0.00040000 0.00810000 0.00000000 0.00010000 0.00000000 -0.01660000 0.00000000 0.00056569 0.00000000 -0.02033076 0.00014142 0.00028284 +Li 0.00000000 3.41200000 9.65000000 -0.00910000 0.00050000 0.00160000 0.00050000 -0.00980000 0.00090000 0.00160000 0.00090000 0.01890000 -0.00000000 0.00070711 0.00127279 0.02314768 0.00226274 0.00049497 +Li 0.00000000 3.41200000 7.84000000 -0.01750000 0.00030000 -0.00000000 0.00030000 -0.01790000 -0.00000000 -0.00000000 -0.00000000 0.03540000 -0.00000000 0.00042426 0.00000000 0.04335597 0.00000000 0.00028284 +Li 0.00000000 0.00000000 7.23700000 0.00670000 0.00030000 -0.00000000 0.00030000 0.00630000 -0.00000000 -0.00000000 -0.00000000 -0.01310000 0.00005774 0.00042426 0.00000000 -0.01600333 0.00000000 0.00028284 +Li 2.95500000 1.70600000 12.06200000 0.00550000 -0.00300000 0.00050000 -0.00300000 0.00890000 0.00030000 0.00050000 0.00030000 -0.01440000 0.00000000 -0.00424264 0.00042426 -0.01763633 0.00070711 -0.00240416 +O 0.00000000 0.00000000 3.80700000 0.08360000 0.00430000 -0.00030000 0.00430000 0.07860000 -0.00020000 -0.00030000 -0.00020000 -0.16230000 0.00005774 0.00608112 -0.00028284 -0.19873527 -0.00042426 0.00353553 +O 0.00000000 0.00000000 10.66800000 0.09890000 0.06040000 -0.06360000 0.06040000 0.02920000 -0.03670000 -0.06360000 -0.03670000 -0.12820000 0.00005774 0.08541850 -0.05190164 -0.15697147 -0.08994398 0.04928534 +O 2.95500000 1.70600000 8.63200000 0.07480000 0.01010000 -0.01700000 0.01010000 0.06310000 -0.00980000 -0.01700000 -0.00980000 -0.13790000 0.00000000 0.01428356 -0.01385929 -0.16889232 -0.02404163 0.00827315 +O 2.95500000 1.70600000 1.01800000 0.09670000 0.00780000 -0.01710000 0.00780000 0.08780000 -0.00990000 -0.01710000 -0.00990000 -0.18450000 0.00000000 0.01103087 -0.01400071 -0.22596543 -0.02418305 0.00629325 +O 0.00000000 3.41200000 13.45700000 0.03630000 0.05780000 -0.05250000 0.05780000 -0.03030000 -0.03030000 -0.05250000 -0.03030000 -0.00600000 0.00000000 0.08174154 -0.04285067 -0.00734847 -0.07424621 0.04709331 +O 0.00000000 3.41200000 5.84300000 0.01680000 0.00480000 -0.00010000 0.00480000 0.01120000 -0.00010000 -0.00010000 -0.00010000 -0.02800000 0.00000000 0.00678823 -0.00014142 -0.03429286 -0.00014142 0.00395980 +O -1.32400000 4.17600000 1.14400000 -0.01830000 0.05160000 0.01590000 0.05160000 0.04180000 -0.00110000 0.01590000 -0.00110000 -0.02350000 0.00000000 0.07297342 -0.00155563 -0.02878150 0.02248600 -0.04249712 +O 0.00000000 1.88300000 1.14400000 0.07140000 -0.00020000 0.00700000 -0.00020000 -0.04790000 0.01430000 0.00700000 0.01430000 -0.02350000 -0.00000000 -0.00028284 0.02022325 -0.02878150 0.00989949 0.08435784 +O 1.32400000 4.17600000 1.14400000 -0.01320000 -0.04120000 -0.02350000 -0.04120000 0.03430000 -0.01360000 -0.02350000 -0.01360000 -0.02110000 0.00000000 -0.05826560 -0.01923330 -0.02584212 -0.03323402 -0.03358757 +O 4.27800000 0.94200000 13.33100000 -0.18730000 0.11740000 -0.05150000 0.11740000 0.03410000 0.07910000 -0.05150000 0.07910000 0.15320000 -0.00000000 0.16602867 0.11186429 0.18763091 -0.07283200 -0.15655344 +O 1.63100000 0.94200000 13.33100000 -0.14190000 -0.07890000 0.05220000 -0.07890000 -0.05080000 0.03020000 0.05220000 0.03020000 0.19280000 -0.00005774 -0.11158145 0.04270925 0.23608999 0.07382195 -0.06441743 +O 2.95500000 3.23400000 13.33100000 0.08040000 -0.03710000 0.04280000 -0.03710000 -0.23360000 -0.08420000 0.04280000 -0.08420000 0.15320000 0.00000000 -0.05246732 -0.11907678 0.18763091 0.06052834 0.22203153 +O 4.58600000 0.76400000 5.96800000 -0.11970000 0.11720000 -0.06980000 0.11720000 0.00350000 0.04070000 -0.06980000 0.04070000 0.11620000 -0.00000000 0.16574583 0.05755849 0.14231535 -0.09871211 -0.08711556 +O 2.95500000 3.58900000 5.96800000 0.07420000 0.00520000 0.00030000 0.00520000 -0.19040000 -0.08080000 0.00030000 -0.08080000 0.11620000 -0.00000000 0.00735391 -0.11426846 0.14231535 0.00042426 0.18710045 +O 1.32400000 0.76400000 5.96800000 -0.12000000 -0.10760000 0.06980000 -0.10760000 0.00420000 0.04030000 0.06980000 0.04030000 0.11570000 0.00005774 -0.15216938 0.05699281 0.14174381 0.09871211 -0.08782266 +O 1.32400000 2.64800000 3.68100000 -0.00200000 0.04430000 -0.00390000 0.04430000 0.03870000 0.00390000 -0.00390000 0.00390000 -0.03670000 0.00000000 0.06264966 0.00551543 -0.04494814 -0.00551543 -0.02877925 +O -1.32400000 2.64800000 3.68100000 -0.00160000 -0.03490000 0.00400000 -0.03490000 0.03870000 0.00230000 0.00400000 0.00230000 -0.03710000 0.00000000 -0.04935605 0.00325269 -0.04543803 0.00565685 -0.02849640 +O 0.00000000 4.94000000 3.68100000 0.06690000 0.00460000 0.00140000 0.00460000 -0.03020000 -0.00530000 0.00140000 -0.00530000 -0.03670000 0.00000000 0.00650538 -0.00749533 -0.04494814 0.00197990 0.06866007 +O 1.63100000 2.47000000 10.79300000 -0.08200000 0.06530000 -0.01360000 0.06530000 0.04530000 0.07950000 -0.01360000 0.07950000 0.03680000 -0.00005774 0.09234815 0.11242998 0.04502979 -0.01923330 -0.09001469 +O 2.95500000 0.17700000 10.79300000 0.07000000 -0.02240000 0.06200000 -0.02240000 -0.10680000 -0.05150000 0.06200000 -0.05150000 0.03680000 -0.00000000 -0.03167838 -0.07283200 0.04507061 0.08768124 0.12501648 +O 4.27800000 2.47000000 10.79300000 0.00300000 0.02490000 0.00700000 0.02490000 -0.02570000 0.00400000 0.00700000 0.00400000 0.02270000 -0.00000000 0.03521392 0.00565685 0.02780171 0.00989949 0.02029396 +O -1.63100000 4.35300000 8.50600000 -0.03770000 0.06420000 -0.03530000 0.06420000 0.02700000 0.02680000 -0.03530000 0.02680000 0.01070000 0.00000000 0.09079251 0.03790092 0.01310477 -0.04992174 -0.04574981 +O 1.63100000 4.35300000 8.50600000 -0.02940000 -0.04120000 0.02650000 -0.04120000 0.01820000 0.01530000 0.02650000 0.01530000 0.01110000 0.00005774 -0.05826560 0.02163747 0.01363549 0.03747666 -0.03365828 +O 0.00000000 1.52800000 8.50600000 0.06640000 0.00410000 0.00550000 0.00410000 -0.07710000 -0.04390000 0.00550000 -0.04390000 0.01070000 -0.00000000 0.00579828 -0.06208398 0.01310477 0.00777817 0.10146982 +Ti 0.00000000 3.41200000 2.41200000 0.10910000 -0.00130000 0.00100000 -0.00130000 0.11060000 0.00060000 0.00100000 0.00060000 -0.21970000 0.00000000 -0.00183848 0.00084853 -0.26907645 0.00141421 -0.00106066 +Ti 2.95500000 0.00000000 0.00000000 0.08160000 0.01180000 -0.02140000 0.01180000 -0.15940000 0.18350000 -0.02140000 0.18350000 0.07790000 -0.00005774 0.01668772 0.25950819 0.09536680 -0.03026417 0.17041273 +Ti -1.47700000 2.55900000 0.00000000 -0.09630000 -0.10100000 -0.15360000 -0.10100000 0.02030000 -0.08870000 -0.15360000 -0.08870000 0.07600000 -0.00000000 -0.14283557 -0.12544074 0.09308061 -0.21722320 -0.08244865 +Ti 1.47700000 2.55900000 0.00000000 -0.08890000 0.11030000 0.14820000 0.11030000 0.01110000 -0.11030000 0.14820000 -0.11030000 0.07790000 -0.00005774 0.15598776 -0.15598776 0.09536680 0.20958645 -0.07071068 +Ti 0.00000000 1.70600000 4.82500000 0.06990000 0.00010000 -0.00010000 0.00010000 -0.11370000 0.17310000 -0.00010000 0.17310000 0.04380000 -0.00000000 0.00014142 0.24480037 0.05364383 -0.00014142 0.12982481 +Ti 1.47700000 4.26500000 4.82500000 -0.06780000 -0.07940000 -0.14970000 -0.07940000 0.02390000 -0.08640000 -0.14970000 -0.08640000 0.04400000 -0.00005774 -0.11228856 -0.12218805 0.05384795 -0.21170777 -0.06484169 +Ti -1.47700000 4.26500000 4.82500000 -0.06770000 0.07960000 0.14990000 0.07960000 0.02390000 -0.08660000 0.14990000 -0.08660000 0.04380000 -0.00000000 0.11257140 -0.12247089 0.05364383 0.21199061 -0.06477098 +Ti 2.95500000 3.41200000 9.65000000 0.11450000 0.01220000 -0.02040000 0.01220000 -0.14490000 0.23840000 -0.02040000 0.23840000 0.03050000 -0.00005774 0.01725341 0.33714851 0.03731389 -0.02884996 0.18342350 +Ti 1.47700000 0.85300000 9.65000000 -0.07660000 -0.10820000 -0.19470000 -0.10820000 0.04830000 -0.11240000 -0.19470000 -0.11240000 0.02830000 0.00000000 -0.15301791 -0.15895760 0.03466028 -0.27534738 -0.08831764 +Ti 4.43200000 0.85300000 9.65000000 -0.06950000 0.11840000 0.19620000 0.11840000 0.03900000 -0.13690000 0.19620000 -0.13690000 0.03050000 0.00000000 0.16744289 -0.19360584 0.03735472 0.27746870 -0.07672109 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00740000 0.00000000 0.00000000 0.00000000 0.00850000 0.00000000 0.00000000 0.00000000 -0.01590000 0.00000000 0.00000000 0.00000000 -0.01947344 0.00000000 -0.00077782 +Li 1.47700000 4.26500000 12.06200000 0.00600000 0.00190000 -0.00720000 0.00190000 -0.00460000 -0.00540000 -0.00720000 -0.00540000 -0.00150000 0.00005774 0.00268701 -0.00763675 -0.00179629 -0.01018234 0.00749533 +Li 2.95500000 1.70600000 6.63400000 0.00530000 0.00000000 0.00000000 0.00000000 0.00610000 0.00010000 0.00000000 0.00010000 -0.01140000 0.00000000 0.00000000 0.00014142 -0.01396209 0.00000000 -0.00056569 +Li 2.95500000 1.70600000 3.01600000 0.00600000 -0.00000000 -0.00000000 -0.00000000 0.00690000 0.00010000 -0.00000000 0.00010000 -0.01290000 0.00000000 0.00000000 0.00014142 -0.01579921 0.00000000 -0.00063640 +Li -1.47700000 4.26500000 12.06200000 0.00600000 -0.00190000 0.00720000 -0.00190000 -0.00460000 -0.00540000 0.00720000 -0.00540000 -0.00150000 0.00005774 -0.00268701 -0.00763675 -0.00179629 0.01018234 0.00749533 +Li 0.00000000 3.41200000 9.65000000 0.00780000 0.00000000 0.00000000 0.00000000 0.00880000 0.00220000 0.00000000 0.00220000 -0.01660000 0.00000000 0.00000000 0.00311127 -0.02033076 0.00000000 -0.00070711 +Li 0.00000000 0.00000000 7.23700000 0.00570000 0.00000000 0.00000000 0.00000000 0.00670000 -0.00000000 0.00000000 -0.00000000 -0.01240000 0.00000000 0.00000000 0.00000000 -0.01518684 0.00000000 -0.00070711 +Li 2.95500000 1.70600000 12.06200000 0.00390000 -0.00000000 -0.00000000 -0.00000000 0.01280000 0.00120000 -0.00000000 0.00120000 -0.01670000 0.00000000 0.00000000 0.00169706 -0.02045324 0.00000000 -0.00629325 +O 0.00000000 0.00000000 3.80700000 0.07960000 -0.00000000 0.00000000 -0.00000000 0.08960000 -0.00030000 0.00000000 -0.00030000 -0.16920000 0.00000000 0.00000000 -0.00042426 -0.20722683 0.00000000 -0.00707107 +O 0.00000000 0.00000000 10.66800000 0.14890000 0.00000000 0.00000000 0.00000000 0.03330000 -0.09430000 0.00000000 -0.09430000 -0.18230000 0.00005774 0.00000000 -0.13336034 -0.22323017 0.00000000 0.08174154 +O 2.95500000 1.70600000 8.63200000 0.04910000 0.00000000 0.00000000 0.00000000 0.05130000 -0.02170000 0.00000000 -0.02170000 -0.10040000 0.00000000 0.00000000 -0.03068843 -0.12296439 0.00000000 -0.00155563 +O 2.95500000 1.70600000 1.01800000 0.08440000 -0.00000000 0.00000000 -0.00000000 0.08590000 -0.01910000 0.00000000 -0.01910000 -0.17030000 0.00000000 0.00000000 -0.02701148 -0.20857405 0.00000000 -0.00106066 +O 0.00000000 3.41200000 13.45700000 0.08090000 0.00000000 0.00000000 0.00000000 -0.02490000 -0.07170000 0.00000000 -0.07170000 -0.05590000 -0.00005774 0.00000000 -0.10139911 -0.06850406 0.00000000 0.07481190 +O 0.00000000 3.41200000 5.84300000 -0.01310000 0.00000000 -0.00000000 0.00000000 -0.00210000 -0.00000000 -0.00000000 -0.00000000 0.01530000 -0.00005774 0.00000000 0.00000000 0.01869777 0.00000000 -0.00777817 +O -1.32400000 4.17600000 1.14400000 -0.02340000 0.04110000 -0.00050000 0.04110000 0.04640000 0.00920000 -0.00050000 0.00920000 -0.02300000 0.00000000 0.05812418 0.01301076 -0.02816913 -0.00070711 -0.04935605 +O 0.00000000 1.88300000 1.14400000 0.06480000 0.00000000 -0.00000000 0.00000000 -0.03670000 -0.02000000 -0.00000000 -0.02000000 -0.02810000 0.00000000 0.00000000 -0.02828427 -0.03441533 0.00000000 0.07177134 +O 1.32400000 4.17600000 1.14400000 -0.02340000 -0.04110000 0.00050000 -0.04110000 0.04640000 0.00920000 0.00050000 0.00920000 -0.02300000 0.00000000 -0.05812418 0.01301076 -0.02816913 0.00070711 -0.04935605 +O 4.27800000 0.94200000 13.33100000 -0.17710000 0.11200000 -0.06810000 0.11200000 0.07050000 0.09160000 -0.06810000 0.09160000 0.10650000 0.00005774 0.15839192 0.12954196 0.13047615 -0.09630794 -0.17507964 +O 1.63100000 0.94200000 13.33100000 -0.17710000 -0.11200000 0.06810000 -0.11200000 0.07050000 0.09160000 0.06810000 0.09160000 0.10650000 0.00005774 -0.15839192 0.12954196 0.13047615 0.09630794 -0.17507964 +O 2.95500000 3.23400000 13.33100000 0.15530000 0.00000000 -0.00000000 0.00000000 -0.23220000 -0.14120000 -0.00000000 -0.14120000 0.07690000 -0.00000000 0.00000000 -0.19968696 0.09418288 0.00000000 0.27400388 +O 4.58600000 0.76400000 5.96800000 -0.13620000 0.09300000 -0.05040000 0.09300000 -0.01660000 0.02950000 -0.05040000 0.02950000 0.15280000 -0.00000000 0.13152186 0.04171930 0.18714102 -0.07127636 -0.08456997 +O 2.95500000 3.58900000 5.96800000 0.02620000 0.00000000 -0.00000000 0.00000000 -0.17780000 -0.05840000 -0.00000000 -0.05840000 0.15160000 -0.00000000 0.00000000 -0.08259007 0.18567132 0.00000000 0.14424978 +O 1.32400000 0.76400000 5.96800000 -0.13620000 -0.09300000 0.05040000 -0.09300000 -0.01660000 0.02950000 0.05040000 0.02950000 0.15280000 -0.00000000 -0.13152186 0.04171930 0.18714102 0.07127636 -0.08456997 +O 1.32400000 2.64800000 3.68100000 -0.01330000 0.03860000 0.01720000 0.03860000 0.04330000 -0.00830000 0.01720000 -0.00830000 -0.03000000 0.00000000 0.05458864 -0.01173797 -0.03674235 0.02432447 -0.04002224 +O -1.32400000 2.64800000 3.68100000 -0.01330000 -0.03860000 -0.01720000 -0.03860000 0.04330000 -0.00830000 -0.01720000 -0.00830000 -0.03000000 0.00000000 -0.05458864 -0.01173797 -0.03674235 -0.02432447 -0.04002224 +O 0.00000000 4.94000000 3.68100000 0.05580000 -0.00000000 0.00000000 -0.00000000 -0.02490000 0.01850000 0.00000000 0.01850000 -0.03090000 0.00000000 0.00000000 0.02616295 -0.03784462 0.00000000 0.05706352 +O 1.63100000 2.47000000 10.79300000 -0.09660000 0.07700000 -0.02320000 0.07700000 0.09600000 0.08860000 -0.02320000 0.08860000 0.00060000 -0.00000000 0.10889444 0.12529932 0.00073485 -0.03280975 -0.13618877 +O 2.95500000 0.17700000 10.79300000 0.14310000 0.00000000 -0.00000000 0.00000000 -0.15400000 -0.12210000 -0.00000000 -0.12210000 0.01100000 -0.00005774 0.00000000 -0.17267548 0.01343137 0.00000000 0.21008142 +O 4.27800000 2.47000000 10.79300000 -0.09660000 -0.07700000 0.02320000 -0.07700000 0.09600000 0.08860000 0.02320000 0.08860000 0.00060000 -0.00000000 -0.10889444 0.12529932 0.00073485 0.03280975 -0.13618877 +O -1.63100000 4.35300000 8.50600000 -0.00240000 0.01250000 -0.01810000 0.01250000 -0.00260000 0.01610000 -0.01810000 0.01610000 0.00490000 0.00005774 0.01767767 0.02276884 0.00604207 -0.02559727 0.00014142 +O 1.63100000 4.35300000 8.50600000 -0.00240000 -0.01250000 0.01810000 -0.01250000 -0.00260000 0.01610000 0.01810000 0.01610000 0.00490000 0.00005774 -0.01767767 0.02276884 0.00604207 0.02559727 0.00014142 +O 0.00000000 1.52800000 8.50600000 0.00630000 0.00000000 -0.00000000 0.00000000 -0.01950000 -0.02540000 -0.00000000 -0.02540000 0.01310000 0.00005774 0.00000000 -0.03592102 0.01608498 0.00000000 0.01824335 +Ti 0.00000000 3.41200000 2.41200000 0.09630000 -0.00000000 -0.00000000 -0.00000000 0.09510000 0.00250000 -0.00000000 0.00250000 -0.19140000 0.00000000 0.00000000 0.00353553 -0.23441617 0.00000000 0.00084853 +Ti 2.95500000 0.00000000 0.00000000 0.06460000 0.00000000 0.00000000 0.00000000 -0.11790000 0.19880000 0.00000000 0.19880000 0.05330000 0.00000000 0.00000000 0.28114566 0.06527890 0.00000000 0.12904699 +Ti -1.47700000 2.55900000 0.00000000 -0.06130000 -0.07840000 -0.15010000 -0.07840000 0.00830000 -0.10970000 -0.15010000 -0.10970000 0.05300000 -0.00000000 -0.11087434 -0.15513923 0.06491148 -0.21227346 -0.04921463 +Ti 1.47700000 2.55900000 0.00000000 -0.06130000 0.07840000 0.15010000 0.07840000 0.00830000 -0.10970000 0.15010000 -0.10970000 0.05300000 -0.00000000 0.11087434 -0.15513923 0.06491148 0.21227346 -0.04921463 +Ti 0.00000000 1.70600000 4.82500000 0.09280000 -0.00000000 -0.00000000 -0.00000000 -0.14630000 0.15500000 -0.00000000 0.15500000 0.05340000 0.00005774 0.00000000 0.21920310 0.06544220 0.00000000 0.16906923 +Ti 1.47700000 4.26500000 4.82500000 -0.08660000 -0.10350000 -0.13400000 -0.10350000 0.03290000 -0.07740000 -0.13400000 -0.07740000 0.05360000 0.00005774 -0.14637110 -0.10946013 0.06568715 -0.18950462 -0.08449926 +Ti -1.47700000 4.26500000 4.82500000 -0.08660000 0.10350000 0.13400000 0.10350000 0.03290000 -0.07740000 0.13400000 -0.07740000 0.05360000 0.00005774 0.14637110 -0.10946013 0.06568715 0.18950462 -0.08449926 +Ti 2.95500000 3.41200000 9.65000000 0.11190000 0.00000000 -0.00000000 0.00000000 -0.14660000 0.27080000 -0.00000000 0.27080000 0.03470000 0.00000000 0.00000000 0.38296903 0.04249865 0.00000000 0.18278710 +Ti 1.47700000 0.85300000 9.65000000 -0.06880000 -0.11260000 -0.21090000 -0.11260000 0.03300000 -0.14660000 -0.21090000 -0.14660000 0.03580000 -0.00000000 -0.15924045 -0.20732371 0.04384587 -0.29825764 -0.07198347 +Ti 4.43200000 0.85300000 9.65000000 -0.06880000 0.11260000 0.21090000 0.11260000 0.03300000 -0.14660000 0.21090000 -0.14660000 0.03580000 -0.00000000 0.15924045 -0.20732371 0.04384587 0.29825764 -0.07198347 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00690000 0.00000000 -0.00000000 0.00000000 0.00690000 -0.00000000 -0.00000000 -0.00000000 -0.01380000 0.00000000 0.00000000 0.00000000 -0.01690148 0.00000000 0.00000000 +Li 0.00000000 0.00000000 12.66500000 0.00710000 -0.00000000 -0.00000000 -0.00000000 0.00710000 -0.00000000 -0.00000000 -0.00000000 -0.01420000 0.00000000 0.00000000 0.00000000 -0.01739138 0.00000000 0.00000000 +Li 2.95500000 1.70600000 6.63400000 0.00710000 0.00000000 0.00000000 0.00000000 0.00710000 -0.00000000 0.00000000 -0.00000000 -0.01420000 0.00000000 0.00000000 0.00000000 -0.01739138 0.00000000 0.00000000 +Li 2.95500000 1.70600000 3.01600000 0.00690000 0.00000000 -0.00000000 0.00000000 0.00690000 -0.00000000 -0.00000000 -0.00000000 -0.01380000 0.00000000 0.00000000 0.00000000 -0.01690148 0.00000000 0.00000000 +Li 0.00000000 3.41200000 11.45900000 -0.00000000 -0.00000000 0.00000000 -0.00000000 -0.00000000 -0.00000000 0.00000000 -0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 +Li 0.00000000 3.41200000 7.84000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 +Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 -0.00000000 -0.00000000 0.00670000 -0.00000000 -0.00000000 -0.00000000 -0.01340000 0.00000000 0.00000000 0.00000000 -0.01641158 0.00000000 0.00000000 +Li 2.95500000 1.70600000 12.06200000 0.00670000 0.00000000 0.00000000 0.00000000 0.00670000 -0.00000000 0.00000000 -0.00000000 -0.01340000 0.00000000 0.00000000 0.00000000 -0.01641158 0.00000000 0.00000000 +O 0.00000000 0.00000000 3.80700000 0.07680000 0.00000000 0.00000000 0.00000000 0.07680000 -0.00000000 0.00000000 -0.00000000 -0.15350000 -0.00005774 0.00000000 0.00000000 -0.18803916 0.00000000 0.00000000 +O 0.00000000 0.00000000 10.66800000 0.04940000 -0.00000000 -0.00000000 -0.00000000 0.04940000 0.00000000 -0.00000000 0.00000000 -0.09880000 0.00000000 0.00000000 0.00000000 -0.12100479 0.00000000 0.00000000 +O 2.95500000 1.70600000 8.63200000 0.04940000 -0.00000000 -0.00000000 -0.00000000 0.04940000 -0.00000000 -0.00000000 -0.00000000 -0.09880000 0.00000000 0.00000000 0.00000000 -0.12100479 0.00000000 0.00000000 +O 2.95500000 1.70600000 1.01800000 0.07680000 -0.00000000 0.00000000 -0.00000000 0.07680000 0.00000000 0.00000000 0.00000000 -0.15350000 -0.00005774 0.00000000 0.00000000 -0.18803916 0.00000000 0.00000000 +O 0.00000000 3.41200000 13.45700000 0.01120000 0.00000000 0.00000000 0.00000000 0.01120000 0.00000000 0.00000000 0.00000000 -0.02250000 0.00005774 0.00000000 0.00000000 -0.02751593 0.00000000 0.00000000 +O 0.00000000 3.41200000 5.84300000 0.01120000 -0.00000000 0.00000000 -0.00000000 0.01120000 0.00000000 0.00000000 0.00000000 -0.02250000 0.00005774 0.00000000 0.00000000 -0.02751593 0.00000000 0.00000000 +O -1.32400000 4.17600000 1.14400000 -0.00680000 0.03280000 -0.00350000 0.03280000 0.03110000 0.00200000 -0.00350000 0.00200000 -0.02430000 0.00000000 0.04638620 0.00282843 -0.02976130 -0.00494975 -0.02679935 +O 0.00000000 1.88300000 1.14400000 0.05010000 0.00000000 0.00000000 0.00000000 -0.02580000 -0.00400000 0.00000000 -0.00400000 -0.02430000 0.00000000 0.00000000 -0.00565685 -0.02976130 0.00000000 0.05366940 +O 1.32400000 4.17600000 1.14400000 -0.00680000 -0.03280000 0.00350000 -0.03280000 0.03110000 0.00200000 0.00350000 0.00200000 -0.02430000 0.00000000 -0.04638620 0.00282843 -0.02976130 0.00494975 -0.02679935 +O 4.27800000 0.94200000 13.33100000 -0.12020000 0.10760000 -0.06910000 0.10760000 0.00400000 0.03990000 -0.06910000 0.03990000 0.11630000 -0.00005774 0.15216938 0.05642712 0.14239700 -0.09772216 -0.08782266 +O 1.63100000 0.94200000 13.33100000 -0.12020000 -0.10760000 0.06910000 -0.10760000 0.00400000 0.03990000 0.06910000 0.03990000 0.11630000 -0.00005774 -0.15216938 0.05642712 0.14239700 0.09772216 -0.08782266 +O 2.95500000 3.23400000 13.33100000 0.06610000 -0.00000000 -0.00000000 -0.00000000 -0.18240000 -0.07970000 -0.00000000 -0.07970000 0.11630000 -0.00000000 0.00000000 -0.11271282 0.14243783 0.00000000 0.17571604 +O 4.58600000 0.76400000 5.96800000 -0.12020000 0.10760000 -0.06910000 0.10760000 0.00400000 0.03990000 -0.06910000 0.03990000 0.11630000 -0.00005774 0.15216938 0.05642712 0.14239700 -0.09772216 -0.08782266 +O 2.95500000 3.58900000 5.96800000 0.06610000 0.00000000 0.00000000 0.00000000 -0.18240000 -0.07970000 0.00000000 -0.07970000 0.11630000 -0.00000000 0.00000000 -0.11271282 0.14243783 0.00000000 0.17571604 +O 1.32400000 0.76400000 5.96800000 -0.12020000 -0.10760000 0.06910000 -0.10760000 0.00400000 0.03990000 0.06910000 0.03990000 0.11630000 -0.00005774 -0.15216938 0.05642712 0.14239700 0.09772216 -0.08782266 +O 1.32400000 2.64800000 3.68100000 -0.00680000 0.03280000 -0.00350000 0.03280000 0.03110000 0.00200000 -0.00350000 0.00200000 -0.02430000 0.00000000 0.04638620 0.00282843 -0.02976130 -0.00494975 -0.02679935 +O -1.32400000 2.64800000 3.68100000 -0.00680000 -0.03280000 0.00350000 -0.03280000 0.03110000 0.00200000 0.00350000 0.00200000 -0.02430000 0.00000000 -0.04638620 0.00282843 -0.02976130 0.00494975 -0.02679935 +O 0.00000000 4.94000000 3.68100000 0.05010000 -0.00000000 -0.00000000 -0.00000000 -0.02580000 -0.00400000 -0.00000000 -0.00400000 -0.02430000 0.00000000 0.00000000 -0.00565685 -0.02976130 0.00000000 0.05366940 +O 1.63100000 2.47000000 10.79300000 -0.03620000 0.06170000 -0.03450000 0.06170000 0.03510000 0.01990000 -0.03450000 0.01990000 0.00100000 0.00005774 0.08725698 0.02814285 0.00126557 -0.04879037 -0.05041671 +O 2.95500000 0.17700000 10.79300000 0.07080000 0.00000000 0.00000000 0.00000000 -0.07180000 -0.03990000 0.00000000 -0.03990000 0.00100000 0.00000000 0.00000000 -0.05642712 0.00122474 0.00000000 0.10083343 +O 4.27800000 2.47000000 10.79300000 -0.03620000 -0.06170000 0.03450000 -0.06170000 0.03510000 0.01990000 0.03450000 0.01990000 0.00100000 0.00005774 -0.08725698 0.02814285 0.00126557 0.04879037 -0.05041671 +O -1.63100000 4.35300000 8.50600000 -0.03610000 0.06170000 -0.03450000 0.06170000 0.03510000 0.01990000 -0.03450000 0.01990000 0.00100000 0.00000000 0.08725698 0.02814285 0.00122474 -0.04879037 -0.05034600 +O 1.63100000 4.35300000 8.50600000 -0.03610000 -0.06170000 0.03450000 -0.06170000 0.03510000 0.01990000 0.03450000 0.01990000 0.00100000 0.00000000 -0.08725698 0.02814285 0.00122474 0.04879037 -0.05034600 +O 0.00000000 1.52800000 8.50600000 0.07080000 0.00000000 -0.00000000 0.00000000 -0.07180000 -0.03990000 -0.00000000 -0.03990000 0.00100000 0.00000000 0.00000000 -0.05642712 0.00122474 0.00000000 0.10083343 +Ti 0.00000000 3.41200000 2.41200000 0.07430000 -0.00000000 -0.00000000 -0.00000000 0.07430000 -0.00000000 -0.00000000 -0.00000000 -0.14850000 -0.00005774 0.00000000 0.00000000 -0.18191544 0.00000000 0.00000000 +Ti 2.95500000 0.00000000 0.00000000 0.06450000 -0.00000000 -0.00000000 -0.00000000 -0.10710000 0.16310000 -0.00000000 0.16310000 0.04260000 0.00000000 0.00000000 0.23065823 0.05217413 0.00000000 0.12133952 +Ti -1.47700000 2.55900000 0.00000000 -0.06420000 -0.07430000 -0.14130000 -0.07430000 0.02160000 -0.08160000 -0.14130000 -0.08160000 0.04260000 -0.00000000 -0.10507607 -0.11539983 0.05217413 -0.19982838 -0.06066976 +Ti 1.47700000 2.55900000 0.00000000 -0.06420000 0.07430000 0.14130000 0.07430000 0.02160000 -0.08160000 0.14130000 -0.08160000 0.04260000 -0.00000000 0.10507607 -0.11539983 0.05217413 0.19982838 -0.06066976 +Ti 0.00000000 1.70600000 4.82500000 0.06450000 0.00000000 -0.00000000 0.00000000 -0.10710000 0.16310000 -0.00000000 0.16310000 0.04260000 0.00000000 0.00000000 0.23065823 0.05217413 0.00000000 0.12133952 +Ti 1.47700000 4.26500000 4.82500000 -0.06420000 -0.07430000 -0.14130000 -0.07430000 0.02160000 -0.08160000 -0.14130000 -0.08160000 0.04260000 -0.00000000 -0.10507607 -0.11539983 0.05217413 -0.19982838 -0.06066976 +Ti -1.47700000 4.26500000 4.82500000 -0.06420000 0.07430000 0.14130000 0.07430000 0.02160000 -0.08160000 0.14130000 -0.08160000 0.04260000 -0.00000000 0.10507607 -0.11539983 0.05217413 0.19982838 -0.06066976 +Ti 2.95500000 3.41200000 9.65000000 0.08050000 -0.00000000 -0.00000000 -0.00000000 -0.11570000 0.24470000 -0.00000000 0.24470000 0.03520000 -0.00000000 0.00000000 0.34605806 0.04311102 0.00000000 0.13873435 +Ti 1.47700000 0.85300000 9.65000000 -0.06660000 -0.08500000 -0.21190000 -0.08500000 0.03150000 -0.12230000 -0.21190000 -0.12230000 0.03520000 -0.00005774 -0.12020815 -0.17295832 0.04307019 -0.29967185 -0.06936718 +Ti 4.43200000 0.85300000 9.65000000 -0.06660000 0.08500000 0.21190000 0.08500000 0.03150000 -0.12230000 0.21190000 -0.12230000 0.03520000 -0.00005774 0.12020815 -0.17295832 0.04307019 0.29967185 -0.06936718 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00210000 -0.00000000 0.00000000 -0.00000000 0.00430000 -0.00140000 0.00000000 -0.00140000 -0.00220000 0.00000000 0.00000000 -0.00197990 -0.00269444 0.00000000 -0.00452548 +Li 0.00000000 0.00000000 12.66500000 -0.00330000 -0.00000000 0.00000000 -0.00000000 0.00340000 -0.00140000 0.00000000 -0.00140000 -0.00010000 -0.00000000 0.00000000 -0.00197990 -0.00012247 0.00000000 -0.00473762 +Li 2.95500000 1.70600000 6.63400000 0.00240000 -0.00000000 0.00000000 -0.00000000 0.00330000 0.00000000 0.00000000 0.00000000 -0.00570000 0.00000000 0.00000000 0.00000000 -0.00698105 0.00000000 -0.00063640 +Li 2.95500000 1.70600000 3.01600000 0.01200000 -0.00000000 -0.00000000 -0.00000000 0.01140000 0.00030000 -0.00000000 0.00030000 -0.02340000 0.00000000 0.00000000 0.00042426 -0.02865903 0.00000000 0.00042426 +Li 0.00000000 3.41200000 9.65000000 -0.00810000 -0.00000000 0.00000000 -0.00000000 -0.00700000 0.00010000 0.00000000 0.00010000 0.01510000 -0.00000000 0.00000000 0.00014142 0.01849365 0.00000000 -0.00077782 +Li 0.00000000 3.41200000 7.84000000 -0.01400000 -0.00000000 -0.00000000 -0.00000000 -0.01320000 0.00000000 -0.00000000 0.00000000 0.02720000 0.00000000 0.00000000 0.00000000 0.03331306 0.00000000 -0.00056569 +Li 0.00000000 0.00000000 7.23700000 0.00620000 0.00000000 -0.00000000 0.00000000 0.00720000 0.00000000 -0.00000000 0.00000000 -0.01340000 0.00000000 0.00000000 0.00000000 -0.01641158 0.00000000 -0.00070711 +Li 2.95500000 0.00000000 0.00000000 0.00650000 0.00000000 0.00000000 0.00000000 0.00510000 -0.00280000 0.00000000 -0.00280000 -0.01160000 0.00000000 0.00000000 -0.00395980 -0.01420704 0.00000000 0.00098995 +O 0.00000000 0.00000000 3.80700000 0.07340000 -0.00000000 0.00000000 -0.00000000 0.08600000 -0.00470000 0.00000000 -0.00470000 -0.15940000 0.00000000 0.00000000 -0.00664680 -0.19522433 0.00000000 -0.00890955 +O 0.00000000 0.00000000 10.66800000 0.05990000 -0.00000000 0.00000000 -0.00000000 0.07290000 -0.00380000 0.00000000 -0.00380000 -0.13280000 0.00000000 0.00000000 -0.00537401 -0.16264612 0.00000000 -0.00919239 +O 2.95500000 1.70600000 8.63200000 0.05050000 -0.00000000 0.00000000 -0.00000000 0.06030000 -0.00120000 0.00000000 -0.00120000 -0.11080000 0.00000000 0.00000000 -0.00169706 -0.13570173 0.00000000 -0.00692965 +O 2.95500000 1.70600000 1.01800000 0.09010000 -0.00000000 -0.00000000 -0.00000000 -0.03430000 -0.14300000 -0.00000000 -0.14300000 -0.05580000 0.00000000 0.00000000 -0.20223254 -0.06834076 0.00000000 0.08796408 +O 0.00000000 3.41200000 13.45700000 -0.00420000 0.00000000 -0.00000000 0.00000000 0.03730000 -0.06830000 -0.00000000 -0.06830000 -0.03310000 0.00000000 0.00000000 -0.09659079 -0.04053906 0.00000000 -0.02934493 +O 0.00000000 3.41200000 5.84300000 0.02240000 -0.00000000 -0.00000000 -0.00000000 0.03260000 -0.00140000 -0.00000000 -0.00140000 -0.05500000 0.00000000 0.00000000 -0.00197990 -0.06736097 0.00000000 -0.00721249 +O -1.32400000 4.17600000 1.14400000 -0.16030000 -0.08110000 0.15750000 -0.08110000 0.09900000 0.00840000 0.15750000 0.00840000 0.06130000 -0.00000000 -0.11469272 0.01187939 0.07507686 0.22273864 -0.18335279 +O 0.00000000 1.88300000 1.14400000 0.02040000 0.00000000 -0.00000000 0.00000000 -0.08120000 0.09030000 -0.00000000 0.09030000 0.06080000 -0.00000000 0.00000000 0.12770348 0.07446449 0.00000000 0.07184205 +O 1.32400000 4.17600000 1.14400000 -0.16030000 0.08110000 -0.15750000 0.08110000 0.09900000 0.00840000 -0.15750000 0.00840000 0.06130000 -0.00000000 0.11469272 0.01187939 0.07507686 -0.22273864 -0.18335279 +O 4.27800000 0.94200000 13.33100000 -0.14440000 -0.07620000 0.17550000 -0.07620000 0.09840000 -0.01030000 0.17550000 -0.01030000 0.04600000 -0.00000000 -0.10776307 -0.01456640 0.05633826 0.24819448 -0.17168553 +O 1.63100000 0.94200000 13.33100000 -0.14440000 0.07620000 -0.17550000 0.07620000 0.09840000 -0.01030000 -0.17550000 -0.01030000 0.04600000 -0.00000000 0.10776307 -0.01456640 0.05633826 -0.24819448 -0.17168553 +O 2.95500000 3.23400000 13.33100000 -0.01530000 -0.00000000 -0.00000000 -0.00000000 -0.06080000 0.09630000 -0.00000000 0.09630000 0.07600000 0.00005774 0.00000000 0.13618877 0.09312144 0.00000000 0.03217336 +O 4.58600000 0.76400000 5.96800000 -0.12540000 0.12990000 -0.09240000 0.12990000 0.03500000 0.05860000 -0.09240000 0.05860000 0.09040000 -0.00000000 0.18370634 0.08287291 0.11071694 -0.13067333 -0.11341993 +O 2.95500000 3.58900000 5.96800000 0.09770000 -0.00000000 0.00000000 -0.00000000 -0.18990000 -0.10810000 0.00000000 -0.10810000 0.09220000 0.00000000 0.00000000 -0.15287649 0.11292148 0.00000000 0.20336391 +O 1.32400000 0.76400000 5.96800000 -0.12540000 -0.12990000 0.09240000 -0.12990000 0.03500000 0.05860000 0.09240000 0.05860000 0.09040000 -0.00000000 -0.18370634 0.08287291 0.11071694 0.13067333 -0.11341993 +O 1.32400000 2.64800000 3.68100000 -0.02630000 0.04940000 -0.01910000 0.04940000 0.05690000 0.03720000 -0.01910000 0.03720000 -0.03060000 0.00000000 0.06986215 0.05260874 -0.03747719 -0.02701148 -0.05883128 +O -1.32400000 2.64800000 3.68100000 -0.02630000 -0.04940000 0.01910000 -0.04940000 0.05690000 0.03720000 0.01910000 0.03720000 -0.03060000 0.00000000 -0.06986215 0.05260874 -0.03747719 0.02701148 -0.05883128 +O 0.00000000 4.94000000 3.68100000 0.06780000 0.00000000 -0.00000000 0.00000000 -0.02870000 -0.02640000 -0.00000000 -0.02640000 -0.03910000 0.00000000 0.00000000 -0.03733524 -0.04788752 0.00000000 0.06823580 +O 1.63100000 2.47000000 10.79300000 -0.02620000 0.02030000 0.05700000 0.02030000 0.02350000 -0.01210000 0.05700000 -0.01210000 0.00270000 -0.00000000 0.02870854 -0.01711198 0.00330681 0.08061017 -0.03514321 +O 2.95500000 0.17700000 10.79300000 0.01790000 0.00000000 0.00000000 0.00000000 -0.01340000 0.05900000 0.00000000 0.05900000 -0.00440000 -0.00005774 0.00000000 0.08343860 -0.00542970 0.00000000 0.02213244 +O 4.27800000 2.47000000 10.79300000 -0.02620000 -0.02030000 -0.05700000 -0.02030000 0.02350000 -0.01210000 -0.05700000 -0.01210000 0.00270000 -0.00000000 -0.02870854 -0.01711198 0.00330681 -0.08061017 -0.03514321 +O -1.63100000 4.35300000 8.50600000 -0.04310000 0.04460000 0.00120000 0.04460000 0.01780000 0.00460000 0.00120000 0.00460000 0.02530000 -0.00000000 0.06307392 0.00650538 0.03098605 0.00169706 -0.04306280 +O 1.63100000 4.35300000 8.50600000 -0.04310000 -0.04460000 -0.00120000 -0.04460000 0.01780000 0.00460000 -0.00120000 0.00460000 0.02530000 -0.00000000 -0.06307392 0.00650538 0.03098605 -0.00169706 -0.04306280 +O 0.00000000 1.52800000 8.50600000 0.03080000 -0.00000000 -0.00000000 -0.00000000 -0.05850000 0.00050000 -0.00000000 0.00050000 0.02770000 0.00000000 0.00000000 0.00070711 0.03392543 0.00000000 0.06314464 +Ti 2.95500000 1.70600000 12.06200000 0.07310000 -0.00000000 -0.00000000 -0.00000000 0.16220000 0.07700000 -0.00000000 0.07700000 -0.23520000 -0.00005774 0.00000000 0.10889444 -0.28810082 0.00000000 -0.06300321 +Ti 0.00000000 3.41200000 2.41200000 0.07490000 -0.00000000 -0.00000000 -0.00000000 0.17470000 0.08310000 -0.00000000 0.08310000 -0.24960000 0.00000000 0.00000000 0.11752115 -0.30569632 0.00000000 -0.07056926 +Ti -1.47700000 2.55900000 0.00000000 -0.10490000 -0.26660000 0.06490000 -0.26660000 -0.02130000 -0.05100000 0.06490000 -0.05100000 0.12620000 -0.00000000 -0.37702934 -0.07212489 0.15456280 0.09178246 -0.05911413 +Ti 1.47700000 2.55900000 0.00000000 -0.10490000 0.26660000 -0.06490000 0.26660000 -0.02130000 -0.05100000 -0.06490000 -0.05100000 0.12620000 -0.00000000 0.37702934 -0.07212489 0.15456280 -0.09178246 -0.05911413 +Ti 0.00000000 1.70600000 4.82500000 0.07960000 0.00000000 -0.00000000 0.00000000 -0.12150000 0.21180000 -0.00000000 0.21180000 0.04190000 -0.00000000 0.00000000 0.29953043 0.05131681 0.00000000 0.14219917 +Ti 1.47700000 4.26500000 4.82500000 -0.07460000 -0.08760000 -0.18150000 -0.08760000 0.03070000 -0.10310000 -0.18150000 -0.10310000 0.04390000 -0.00000000 -0.12388511 -0.14580542 0.05376630 -0.25667976 -0.07445834 +Ti -1.47700000 4.26500000 4.82500000 -0.07460000 0.08760000 0.18150000 0.08760000 0.03070000 -0.10310000 0.18150000 -0.10310000 0.04390000 -0.00000000 0.12388511 -0.14580542 0.05376630 0.25667976 -0.07445834 +Ti 2.95500000 3.41200000 9.65000000 0.10160000 -0.00000000 -0.00000000 -0.00000000 -0.13590000 0.18660000 -0.00000000 0.18660000 0.03430000 -0.00000000 0.00000000 0.26389225 0.04200875 0.00000000 0.16793786 +Ti 1.47700000 0.85300000 9.65000000 -0.07960000 -0.10460000 -0.15990000 -0.10460000 0.04400000 -0.09060000 -0.15990000 -0.09060000 0.03560000 0.00000000 -0.14792674 -0.12812775 0.04360092 -0.22613275 -0.08739840 +Ti 4.43200000 0.85300000 9.65000000 -0.07960000 0.10460000 0.15990000 0.10460000 0.04400000 -0.09060000 0.15990000 -0.09060000 0.03560000 0.00000000 0.14792674 -0.12812775 0.04360092 0.22613275 -0.08739840 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00150000 0.00010000 0.00000000 0.00010000 0.00440000 -0.00160000 0.00000000 -0.00160000 -0.00290000 0.00000000 0.00014142 -0.00226274 -0.00355176 0.00000000 -0.00417193 +Li 0.00000000 0.00000000 12.66500000 0.01570000 -0.02150000 -0.01490000 -0.02150000 -0.00140000 0.00680000 -0.01490000 0.00680000 -0.01430000 0.00000000 -0.03040559 0.00961665 -0.01751385 -0.02107178 0.01209153 +Li 2.95500000 1.70600000 6.63400000 0.00330000 -0.00000000 -0.00010000 -0.00000000 0.00330000 0.00000000 -0.00010000 0.00000000 -0.00660000 0.00000000 0.00000000 0.00000000 -0.00808332 -0.00014142 0.00000000 +Li 2.95500000 1.70600000 3.01600000 0.01130000 -0.00000000 -0.00010000 -0.00000000 0.00970000 0.00040000 -0.00010000 0.00040000 -0.02090000 -0.00005774 0.00000000 0.00056569 -0.02563799 -0.00014142 0.00113137 +Li -1.47700000 4.26500000 12.06200000 0.01820000 -0.01350000 -0.01480000 -0.01350000 -0.00110000 0.00330000 -0.01480000 0.00330000 -0.01700000 -0.00005774 -0.01909188 0.00466690 -0.02086149 -0.02093036 0.01364716 +Li 0.00000000 3.41200000 7.84000000 0.00290000 0.00010000 0.00000000 0.00010000 0.00300000 -0.00000000 0.00000000 -0.00000000 -0.00600000 0.00005774 0.00014142 0.00000000 -0.00730764 0.00000000 -0.00007071 +Li 0.00000000 0.00000000 7.23700000 0.00680000 -0.00000000 0.00010000 -0.00000000 0.00680000 -0.00000000 0.00010000 -0.00000000 -0.01360000 0.00000000 0.00000000 0.00000000 -0.01665653 0.00014142 0.00000000 +Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00010000 0.00180000 -0.00010000 0.00390000 -0.00140000 0.00180000 -0.00140000 -0.01010000 0.00000000 -0.00014142 -0.00197990 -0.01236992 0.00254558 0.00162635 +O 0.00000000 0.00000000 3.80700000 0.07760000 -0.00100000 0.00040000 -0.00100000 0.07950000 -0.00590000 0.00040000 -0.00590000 -0.15710000 0.00000000 -0.00141421 -0.00834386 -0.19240742 0.00056569 -0.00134350 +O 0.00000000 0.00000000 10.66800000 0.08950000 -0.06110000 0.09280000 -0.06110000 0.02450000 -0.06170000 0.09280000 -0.06170000 -0.11390000 -0.00005774 -0.08640845 -0.08725698 -0.13953927 0.13123902 0.04596194 +O 2.95500000 1.70600000 8.63200000 0.03490000 -0.00420000 0.01840000 -0.00420000 0.02940000 -0.01130000 0.01840000 -0.01130000 -0.06430000 0.00000000 -0.00593970 -0.01598061 -0.07875110 0.02602153 0.00388909 +O 2.95500000 1.70600000 1.01800000 0.10120000 -0.00660000 0.01170000 -0.00660000 -0.05480000 -0.19300000 0.01170000 -0.19300000 -0.04640000 0.00000000 -0.00933381 -0.27294322 -0.05682816 0.01654630 0.11030866 +O 0.00000000 3.41200000 13.45700000 0.15350000 -0.04620000 0.07060000 -0.04620000 -0.07130000 -0.21210000 0.07060000 -0.21210000 -0.08220000 0.00000000 -0.06533667 -0.29995470 -0.10067403 0.09984348 0.15895760 +O 0.00000000 3.41200000 5.84300000 0.02600000 -0.00070000 0.00040000 -0.00070000 0.02460000 -0.00120000 0.00040000 -0.00120000 -0.05060000 0.00000000 -0.00098995 -0.00169706 -0.06197209 0.00056569 0.00098995 +O -1.32400000 4.17600000 1.14400000 -0.15110000 -0.08930000 0.17430000 -0.08930000 0.09870000 0.00820000 0.17430000 0.00820000 0.05240000 0.00000000 -0.12628927 0.01159655 0.06417663 0.24649742 -0.17663527 +O 0.00000000 1.88300000 1.14400000 0.03560000 0.00450000 -0.01120000 0.00450000 -0.10140000 0.06960000 -0.01120000 0.06960000 0.06580000 0.00000000 0.00636396 0.09842926 0.08058821 -0.01583919 0.09687363 +O 1.32400000 4.17600000 1.14400000 -0.15600000 0.08700000 -0.17600000 0.08700000 0.11140000 0.02210000 -0.17600000 0.02210000 0.04460000 -0.00000000 0.12303658 0.03125412 0.05462362 -0.24890159 -0.18908035 +O 4.27800000 0.94200000 13.33100000 -0.13820000 -0.09870000 0.16650000 -0.09870000 0.11900000 0.01000000 0.16650000 0.01000000 0.01920000 -0.00000000 -0.13958288 0.01414214 0.02351510 0.23546656 -0.18186786 +O 1.63100000 0.94200000 13.33100000 -0.20680000 0.14230000 -0.16900000 0.14230000 0.15000000 0.13900000 -0.16900000 0.13900000 0.05680000 0.00000000 0.20124259 0.19657569 0.06956551 -0.23900209 -0.25229570 +O 2.95500000 3.23400000 13.33100000 0.02580000 0.08040000 -0.09120000 0.08040000 -0.09170000 0.00020000 -0.09120000 0.00020000 0.06590000 0.00000000 0.11370277 0.00028284 0.08071069 -0.12897628 0.08308505 +O 4.58600000 0.76400000 5.96800000 -0.12510000 0.12520000 -0.08390000 0.12520000 0.01770000 0.05390000 -0.08390000 0.05390000 0.10750000 -0.00005774 0.17705954 0.07622611 0.13161925 -0.11865252 -0.10097485 +O 2.95500000 3.58900000 5.96800000 0.09000000 -0.00240000 -0.00030000 -0.00240000 -0.20120000 -0.09860000 -0.00030000 -0.09860000 0.11120000 -0.00000000 -0.00339411 -0.13944146 0.13619163 -0.00042426 0.20590949 +O 1.32400000 0.76400000 5.96800000 -0.12480000 -0.12830000 0.08400000 -0.12830000 0.01600000 0.05420000 0.08400000 0.05420000 0.10880000 0.00000000 -0.18144360 0.07665038 0.13325224 0.11879394 -0.09956063 +O 1.32400000 2.64800000 3.68100000 -0.02060000 0.04810000 -0.00900000 0.04810000 0.04920000 0.03530000 -0.00900000 0.03530000 -0.02860000 0.00000000 0.06802367 0.04992174 -0.03502770 -0.01272792 -0.04935605 +O -1.32400000 2.64800000 3.68100000 -0.02050000 -0.05120000 0.00880000 -0.05120000 0.04780000 0.03610000 0.00880000 0.03610000 -0.02730000 0.00000000 -0.07240773 0.05105311 -0.03343553 0.01244508 -0.04829539 +O 0.00000000 4.94000000 3.68100000 0.07010000 -0.00210000 -0.00090000 -0.00210000 -0.03630000 -0.01600000 -0.00090000 -0.01600000 -0.03380000 0.00000000 -0.00296985 -0.02262742 -0.04139638 -0.00127279 0.07523616 +O 1.63100000 2.47000000 10.79300000 -0.04470000 0.07660000 -0.00610000 0.07660000 0.05610000 0.03610000 -0.00610000 0.03610000 -0.01140000 -0.00000000 0.10832876 0.05105311 -0.01396209 -0.00862670 -0.07127636 +O 2.95500000 0.17700000 10.79300000 0.12860000 0.05390000 -0.07390000 0.05390000 -0.11990000 -0.02820000 -0.07390000 -0.02820000 -0.00870000 0.00000000 0.07622611 -0.03988082 -0.01065528 -0.10451038 0.17571604 +O 4.27800000 2.47000000 10.79300000 -0.11910000 -0.08780000 -0.01730000 -0.08780000 0.12000000 0.10920000 -0.01730000 0.10920000 -0.00090000 0.00000000 -0.12416795 0.15443212 -0.00110227 -0.02446589 -0.16906923 +O -1.63100000 4.35300000 8.50600000 -0.13860000 0.13510000 -0.09340000 0.13510000 0.01630000 0.05930000 -0.09340000 0.05930000 0.12230000 -0.00000000 0.19106025 0.08386286 0.14978630 -0.13208755 -0.10953084 +O 1.63100000 4.35300000 8.50600000 -0.14140000 -0.14550000 0.11350000 -0.14550000 0.02860000 0.08550000 0.11350000 0.08550000 0.11290000 -0.00005774 -0.20576807 0.12091526 0.13823287 0.16051324 -0.12020815 +O 0.00000000 1.52800000 8.50600000 0.11020000 0.00140000 -0.01270000 0.00140000 -0.22490000 -0.13980000 -0.01270000 -0.13980000 0.11460000 0.00005774 0.00197990 -0.19770706 0.14039659 -0.01796051 0.23695148 +Ti 2.95500000 1.70600000 12.06200000 0.07090000 0.03340000 -0.03960000 0.03340000 0.19730000 0.10880000 -0.03960000 0.10880000 -0.26820000 0.00000000 0.04723473 0.15386644 -0.32847657 -0.05600286 -0.08937830 +Ti 0.00000000 3.41200000 2.41200000 0.07860000 0.00040000 -0.00170000 0.00040000 0.17360000 0.08570000 -0.00170000 0.08570000 -0.25220000 0.00000000 0.00056569 0.12119810 -0.30888066 -0.00240416 -0.06717514 +Ti -1.47700000 2.55900000 0.00000000 -0.07580000 -0.27240000 0.06130000 -0.27240000 -0.04190000 -0.09500000 0.06130000 -0.09500000 0.11770000 0.00000000 -0.38523177 -0.13435029 0.14415247 0.08669129 -0.02397092 +Ti 1.47700000 2.55900000 0.00000000 -0.08610000 0.26890000 -0.06970000 0.26890000 -0.02210000 -0.06920000 -0.06970000 -0.06920000 0.10820000 -0.00000000 0.38028203 -0.09786358 0.13251740 -0.09857069 -0.04525483 +Ti 0.00000000 1.70600000 4.82500000 0.08060000 -0.00010000 0.00010000 -0.00010000 -0.12570000 0.20710000 0.00010000 0.20710000 0.04510000 -0.00000000 -0.00014142 0.29288363 0.05523599 0.00014142 0.14587613 +Ti 1.47700000 4.26500000 4.82500000 -0.07670000 -0.08910000 -0.17730000 -0.08910000 0.03020000 -0.10010000 -0.17730000 -0.10010000 0.04650000 0.00000000 -0.12600643 -0.14156278 0.05695064 -0.25074006 -0.07558971 +Ti -1.47700000 4.26500000 4.82500000 -0.07680000 0.08910000 0.17730000 0.08910000 0.03030000 -0.10000000 0.17730000 -0.10000000 0.04650000 -0.00000000 0.12600643 -0.14142136 0.05695064 0.25074006 -0.07573114 +Ti 2.95500000 3.41200000 9.65000000 0.06620000 -0.01150000 0.02180000 -0.01150000 -0.12100000 0.24240000 0.02180000 0.24240000 0.05490000 -0.00005774 -0.01626346 0.34280537 0.06719767 0.03082986 0.13237039 +Ti 1.47700000 0.85300000 9.65000000 -0.06690000 -0.08580000 -0.19690000 -0.08580000 0.01040000 -0.13570000 -0.19690000 -0.13570000 0.05650000 -0.00000000 -0.12133952 -0.19190878 0.06919809 -0.27845865 -0.05465935 +Ti 4.43200000 0.85300000 9.65000000 -0.07220000 0.07660000 0.19320000 0.07660000 0.02140000 -0.10830000 0.19320000 -0.10830000 0.05080000 -0.00000000 0.10832876 -0.15315933 0.06221704 0.27322606 -0.06618519 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00160000 -0.00000000 0.00000000 -0.00000000 0.00510000 -0.00160000 0.00000000 -0.00160000 -0.00350000 0.00000000 0.00000000 -0.00226274 -0.00428661 0.00000000 -0.00473762 +Li 0.00000000 0.00000000 12.66500000 -0.02020000 -0.00000000 -0.00000000 -0.00000000 0.03470000 -0.01900000 -0.00000000 -0.01900000 -0.01450000 0.00000000 0.00000000 -0.02687006 -0.01775880 0.00000000 -0.03882016 +Li 2.95500000 1.70600000 6.63400000 0.00320000 0.00000000 0.00000000 0.00000000 0.00410000 -0.00010000 0.00000000 -0.00010000 -0.00720000 -0.00005774 0.00000000 -0.00014142 -0.00885899 0.00000000 -0.00063640 +Li 2.95500000 1.70600000 3.01600000 0.01120000 0.00000000 -0.00000000 0.00000000 0.01060000 0.00020000 -0.00000000 0.00020000 -0.02180000 0.00000000 0.00000000 0.00028284 -0.02669944 0.00000000 0.00042426 +Li 0.00000000 1.70600000 12.06200000 -0.01050000 0.00000000 0.00000000 0.00000000 0.02680000 -0.01210000 0.00000000 -0.01210000 -0.01630000 0.00000000 0.00000000 -0.01711198 -0.01996334 0.00000000 -0.02637508 +Li 0.00000000 3.41200000 7.84000000 0.00300000 0.00000000 -0.00000000 0.00000000 0.00370000 0.00000000 -0.00000000 0.00000000 -0.00670000 0.00000000 0.00000000 0.00000000 -0.00820579 0.00000000 -0.00049497 +Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 0.00000000 -0.00000000 0.00760000 0.00010000 0.00000000 0.00010000 -0.01430000 0.00000000 0.00000000 0.00014142 -0.01751385 0.00000000 -0.00063640 +Li 2.95500000 0.00000000 0.00000000 0.00630000 -0.00000000 0.00000000 -0.00000000 0.00590000 -0.00310000 0.00000000 -0.00310000 -0.01220000 0.00000000 0.00000000 -0.00438406 -0.01494189 0.00000000 0.00028284 +O 0.00000000 0.00000000 3.80700000 0.07570000 0.00000000 0.00000000 0.00000000 0.08870000 -0.00570000 0.00000000 -0.00570000 -0.16450000 0.00005774 0.00000000 -0.00806102 -0.20142971 0.00000000 -0.00919239 +O 0.00000000 0.00000000 10.66800000 -0.01270000 -0.00000000 0.00000000 -0.00000000 0.13640000 0.09810000 0.00000000 0.09810000 -0.12380000 0.00005774 0.00000000 0.13873435 -0.15158259 0.00000000 -0.10542962 +O 2.95500000 1.70600000 8.63200000 0.02690000 0.00000000 -0.00000000 0.00000000 0.04500000 0.01980000 -0.00000000 0.01980000 -0.07190000 0.00000000 0.00000000 0.02800143 -0.08805916 0.00000000 -0.01279863 +O 2.95500000 1.70600000 1.01800000 0.09700000 -0.00000000 0.00000000 -0.00000000 -0.04250000 -0.15470000 0.00000000 -0.15470000 -0.05460000 0.00005774 0.00000000 -0.21877884 -0.06683025 0.00000000 0.09864140 +O 0.00000000 3.41200000 13.45700000 0.01950000 0.00000000 -0.00000000 0.00000000 0.06930000 -0.08480000 -0.00000000 -0.08480000 -0.08880000 0.00000000 0.00000000 -0.11992531 -0.10875734 0.00000000 -0.03521392 +O 0.00000000 3.41200000 5.84300000 0.02410000 -0.00000000 -0.00000000 -0.00000000 0.03440000 -0.00070000 -0.00000000 -0.00070000 -0.05850000 0.00000000 0.00000000 -0.00098995 -0.07164757 0.00000000 -0.00728320 +O -1.32400000 4.17600000 1.14400000 -0.15290000 -0.08570000 0.16690000 -0.08570000 0.11610000 0.01150000 0.16690000 0.01150000 0.03680000 0.00000000 -0.12119810 0.01626346 0.04507061 0.23603224 -0.19021172 +O 0.00000000 1.88300000 1.14400000 0.02310000 0.00000000 -0.00000000 0.00000000 -0.08550000 0.08450000 -0.00000000 0.08450000 0.06240000 0.00000000 0.00000000 0.11950105 0.07642408 0.00000000 0.07679180 +O 1.32400000 4.17600000 1.14400000 -0.15290000 0.08570000 -0.16690000 0.08570000 0.11610000 0.01150000 -0.16690000 0.01150000 0.03680000 0.00000000 0.12119810 0.01626346 0.04507061 -0.23603224 -0.19021172 +O 4.27800000 0.94200000 13.33100000 -0.06170000 -0.03790000 0.04600000 -0.03790000 0.02140000 -0.04050000 0.04600000 -0.04050000 0.04030000 -0.00000000 -0.05359869 -0.05727565 0.04935722 0.06505382 -0.05876057 +O 1.63100000 0.94200000 13.33100000 -0.06170000 0.03790000 -0.04600000 0.03790000 0.02140000 -0.04050000 -0.04600000 -0.04050000 0.04030000 -0.00000000 0.05359869 -0.05727565 0.04935722 -0.06505382 -0.05876057 +O 2.95500000 3.23400000 13.33100000 0.01890000 -0.00000000 -0.00000000 -0.00000000 -0.05740000 0.06460000 -0.00000000 0.06460000 0.03850000 0.00000000 0.00000000 0.09135820 0.04715268 0.00000000 0.05395225 +O 4.58600000 0.76400000 5.96800000 -0.12710000 0.12500000 -0.08440000 0.12500000 0.02750000 0.05370000 -0.08440000 0.05370000 0.09960000 -0.00000000 0.17677670 0.07594327 0.12198459 -0.11935962 -0.10931871 +O 2.95500000 3.58900000 5.96800000 0.08760000 0.00000000 -0.00000000 0.00000000 -0.18860000 -0.09830000 -0.00000000 -0.09830000 0.10100000 -0.00000000 0.00000000 -0.13901719 0.12369923 0.00000000 0.19530289 +O 1.32400000 0.76400000 5.96800000 -0.12710000 -0.12500000 0.08440000 -0.12500000 0.02750000 0.05370000 0.08440000 0.05370000 0.09960000 -0.00000000 -0.17677670 0.07594327 0.12198459 0.11935962 -0.10931871 +O 1.32400000 2.64800000 3.68100000 -0.02230000 0.04870000 -0.01010000 0.04870000 0.05870000 0.03520000 -0.01010000 0.03520000 -0.03640000 0.00000000 0.06887220 0.04978032 -0.04458071 -0.01428356 -0.05727565 +O -1.32400000 2.64800000 3.68100000 -0.02230000 -0.04870000 0.01010000 -0.04870000 0.05870000 0.03520000 0.01010000 0.03520000 -0.03640000 0.00000000 -0.06887220 0.04978032 -0.04458071 0.01428356 -0.05727565 +O 0.00000000 4.94000000 3.68100000 0.06720000 -0.00000000 -0.00000000 -0.00000000 -0.02460000 -0.01560000 0.00000000 -0.01560000 -0.04260000 0.00000000 0.00000000 -0.02206173 -0.05217413 0.00000000 0.06491240 +O 1.63100000 2.47000000 10.79300000 -0.02010000 0.13610000 -0.05550000 0.13610000 0.03380000 -0.01960000 -0.05550000 -0.01960000 -0.01370000 0.00000000 0.19247447 -0.02771859 -0.01677900 -0.07848885 -0.03811306 +O 2.95500000 0.17700000 10.79300000 0.08860000 0.00000000 0.00000000 0.00000000 -0.06250000 -0.01210000 0.00000000 -0.01210000 -0.02610000 0.00000000 0.00000000 -0.01711198 -0.03196584 0.00000000 0.10684383 +O 4.27800000 2.47000000 10.79300000 -0.02010000 -0.13610000 0.05550000 -0.13610000 0.03380000 -0.01960000 0.05550000 -0.01960000 -0.01370000 0.00000000 -0.19247447 -0.02771859 -0.01677900 0.07848885 -0.03811306 +O -1.63100000 4.35300000 8.50600000 -0.13670000 0.14550000 -0.12540000 0.14550000 0.03300000 0.06310000 -0.12540000 0.06310000 0.10380000 -0.00005774 0.20576807 0.08923688 0.12708769 -0.17734238 -0.11999602 +O 1.63100000 4.35300000 8.50600000 -0.13670000 -0.14550000 0.12540000 -0.14550000 0.03300000 0.06310000 0.12540000 0.06310000 0.10380000 -0.00005774 -0.20576807 0.08923688 0.12708769 0.17734238 -0.11999602 +O 0.00000000 1.52800000 8.50600000 0.09180000 -0.00000000 -0.00000000 -0.00000000 -0.20800000 -0.10870000 -0.00000000 -0.10870000 0.11620000 -0.00000000 0.00000000 -0.15372501 0.14231535 0.00000000 0.21199061 +Ti 2.95500000 1.70600000 12.06200000 0.12860000 -0.00000000 -0.00000000 -0.00000000 0.13840000 0.03960000 -0.00000000 0.03960000 -0.26690000 -0.00005774 0.00000000 0.05600286 -0.32692523 0.00000000 -0.00692965 +Ti 0.00000000 3.41200000 2.41200000 0.07980000 0.00000000 0.00000000 0.00000000 0.17150000 0.08680000 0.00000000 0.08680000 -0.25130000 0.00000000 0.00000000 0.12275374 -0.30777839 0.00000000 -0.06484169 +Ti -1.47700000 2.55900000 0.00000000 -0.09850000 -0.25810000 0.04340000 -0.25810000 -0.01780000 -0.05560000 0.04340000 -0.05560000 0.11630000 -0.00000000 -0.36500852 -0.07863027 0.14243783 0.06137687 -0.05706352 +Ti 1.47700000 2.55900000 0.00000000 -0.09850000 0.25810000 -0.04340000 0.25810000 -0.01780000 -0.05560000 -0.04340000 -0.05560000 0.11630000 -0.00000000 0.36500852 -0.07863027 0.14243783 -0.06137687 -0.05706352 +Ti 0.00000000 1.70600000 4.82500000 0.08060000 0.00000000 -0.00000000 0.00000000 -0.12570000 0.20720000 -0.00000000 0.20720000 0.04520000 -0.00005774 0.00000000 0.29302505 0.05531764 0.00000000 0.14587613 +Ti 1.47700000 4.26500000 4.82500000 -0.07680000 -0.08900000 -0.17750000 -0.08900000 0.03040000 -0.10000000 -0.17750000 -0.10000000 0.04640000 -0.00000000 -0.12586501 -0.14142136 0.05682816 -0.25102291 -0.07580185 +Ti -1.47700000 4.26500000 4.82500000 -0.07680000 0.08900000 0.17750000 0.08900000 0.03040000 -0.10000000 0.17750000 -0.10000000 0.04640000 -0.00000000 0.12586501 -0.14142136 0.05682816 0.25102291 -0.07580185 +Ti 2.95500000 3.41200000 9.65000000 0.06500000 -0.00000000 -0.00000000 -0.00000000 -0.11490000 0.22520000 -0.00000000 0.22520000 0.04990000 -0.00000000 0.00000000 0.31848089 0.06111477 0.00000000 0.12720851 +Ti 1.47700000 0.85300000 9.65000000 -0.08680000 -0.07510000 -0.21740000 -0.07510000 0.02970000 -0.09830000 -0.21740000 -0.09830000 0.05710000 0.00000000 -0.10620744 -0.13901719 0.06993293 -0.30745003 -0.08237794 +Ti 4.43200000 0.85300000 9.65000000 -0.08680000 0.07510000 0.21740000 0.07510000 0.02970000 -0.09830000 0.21740000 -0.09830000 0.05710000 0.00000000 0.10620744 -0.13901719 0.06993293 0.30745003 -0.08237794 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 0.00000000 -0.01820000 -0.00000000 -0.00000000 -0.00000000 -0.00030000 0.00580000 -0.00000000 0.00580000 0.01850000 0.00000000 0.00000000 0.00820244 0.02265778 0.00000000 -0.01265721 +Li 0.00000000 0.00000000 12.66500000 -0.01940000 -0.00000000 0.00000000 -0.00000000 -0.01220000 -0.00230000 0.00000000 -0.00230000 0.03160000 -0.00000000 0.00000000 -0.00325269 0.03870194 0.00000000 -0.00509117 +Li 2.95500000 1.70600000 6.63400000 0.00090000 -0.00000000 -0.00000000 -0.00000000 0.00180000 -0.00000000 -0.00000000 -0.00000000 -0.00270000 0.00000000 0.00000000 0.00000000 -0.00330681 0.00000000 -0.00063640 +Li 2.95500000 1.70600000 3.01600000 0.00910000 0.00000000 -0.00000000 0.00000000 0.00850000 0.00020000 -0.00000000 0.00020000 -0.01760000 0.00000000 0.00000000 0.00028284 -0.02155551 0.00000000 0.00042426 +Li 0.00000000 3.41200000 11.45900000 0.01190000 -0.00000000 0.00000000 -0.00000000 0.01120000 0.00040000 0.00000000 0.00040000 -0.02310000 0.00000000 0.00000000 0.00056569 -0.02829161 0.00000000 0.00049497 +Li 0.00000000 3.41200000 7.84000000 0.00440000 0.00000000 -0.00000000 0.00000000 0.00520000 0.00000000 -0.00000000 0.00000000 -0.00960000 0.00000000 0.00000000 0.00000000 -0.01175755 0.00000000 -0.00056569 +Li 0.00000000 0.00000000 7.23700000 0.00660000 -0.00000000 0.00000000 -0.00000000 0.00760000 0.00000000 0.00000000 0.00000000 -0.01420000 0.00000000 0.00000000 0.00000000 -0.01739138 0.00000000 -0.00070711 +Li 2.95500000 0.00000000 0.00000000 0.00970000 -0.00000000 -0.00000000 -0.00000000 0.00350000 -0.00200000 -0.00000000 -0.00200000 -0.01320000 0.00000000 0.00000000 -0.00282843 -0.01616663 0.00000000 0.00438406 +O 0.00000000 0.00000000 3.80700000 0.07740000 0.00000000 -0.00000000 0.00000000 0.08880000 -0.00390000 -0.00000000 -0.00390000 -0.16620000 0.00000000 0.00000000 -0.00551543 -0.20355260 0.00000000 -0.00806102 +O 0.00000000 0.00000000 10.66800000 0.07310000 -0.00000000 0.00000000 -0.00000000 0.08660000 -0.00610000 0.00000000 -0.00610000 -0.15970000 0.00000000 0.00000000 -0.00862670 -0.19559176 0.00000000 -0.00954594 +O 2.95500000 1.70600000 8.63200000 0.02200000 0.00000000 -0.00000000 0.00000000 0.03230000 -0.00080000 -0.00000000 -0.00080000 -0.05430000 0.00000000 0.00000000 -0.00113137 -0.06650365 0.00000000 -0.00728320 +O 2.95500000 1.70600000 1.01800000 0.06970000 -0.00000000 -0.00000000 -0.00000000 -0.03640000 -0.19220000 -0.00000000 -0.19220000 -0.03330000 0.00000000 0.00000000 -0.27181185 -0.04078400 0.00000000 0.07502403 +O 0.00000000 3.41200000 13.45700000 0.13080000 0.00000000 -0.00000000 0.00000000 -0.03120000 -0.16810000 -0.00000000 -0.16810000 -0.09970000 0.00005774 0.00000000 -0.23772930 -0.12206624 0.00000000 0.11455130 +O 0.00000000 3.41200000 5.84300000 0.02490000 -0.00000000 0.00000000 -0.00000000 0.03510000 -0.00070000 0.00000000 -0.00070000 -0.06000000 0.00000000 0.00000000 -0.00098995 -0.07348469 0.00000000 -0.00721249 +O -1.32400000 4.17600000 1.14400000 -0.13130000 -0.10430000 0.26110000 -0.10430000 0.09150000 -0.01350000 0.26110000 -0.01350000 0.03980000 -0.00000000 -0.14750247 -0.01909188 0.04874485 0.36925116 -0.15754339 +O 0.00000000 1.88300000 1.14400000 0.00580000 0.00000000 0.00000000 0.00000000 -0.09340000 0.16570000 0.00000000 0.16570000 0.08760000 -0.00000000 0.00000000 0.23433519 0.10728765 0.00000000 0.07014499 +O 1.32400000 4.17600000 1.14400000 -0.13130000 0.10430000 -0.26110000 0.10430000 0.09150000 -0.01350000 -0.26110000 -0.01350000 0.03980000 -0.00000000 0.14750247 -0.01909188 0.04874485 -0.36925116 -0.15754339 +O 4.27800000 0.94200000 13.33100000 -0.06810000 -0.15090000 0.22230000 -0.15090000 0.07060000 -0.01650000 0.22230000 -0.01650000 -0.00250000 -0.00000000 -0.21340483 -0.02333452 -0.00306186 0.31437967 -0.09807571 +O 1.63100000 0.94200000 13.33100000 -0.06810000 0.15090000 -0.22230000 0.15090000 0.07060000 -0.01650000 -0.22230000 -0.01650000 -0.00250000 -0.00000000 0.21340483 -0.02333452 -0.00306186 -0.31437967 -0.09807571 +O 2.95500000 3.23400000 13.33100000 -0.04150000 0.00000000 -0.00000000 0.00000000 0.03000000 0.14460000 -0.00000000 0.14460000 0.01150000 -0.00000000 0.00000000 0.20449528 0.01408457 0.00000000 -0.05055813 +O 4.58600000 0.76400000 5.96800000 -0.12530000 0.13210000 -0.06880000 0.13210000 0.03790000 0.04460000 -0.06880000 0.04460000 0.08740000 -0.00000000 0.18681761 0.06307392 0.10704270 -0.09729789 -0.11539983 +O 2.95500000 3.58900000 5.96800000 0.10240000 0.00000000 0.00000000 0.00000000 -0.19110000 -0.08020000 0.00000000 -0.08020000 0.08870000 -0.00000000 0.00000000 -0.11341993 0.10863487 0.00000000 0.20753584 +O 1.32400000 0.76400000 5.96800000 -0.12530000 -0.13210000 0.06880000 -0.13210000 0.03790000 0.04460000 0.06880000 0.04460000 0.08740000 -0.00000000 -0.18681761 0.06307392 0.10704270 0.09729789 -0.11539983 +O 1.32400000 2.64800000 3.68100000 -0.01770000 0.02610000 0.00820000 0.02610000 0.03420000 0.02560000 0.00820000 0.02560000 -0.01660000 0.00005774 0.03691097 0.03620387 -0.02028994 0.01159655 -0.03669884 +O -1.32400000 2.64800000 3.68100000 -0.01770000 -0.02610000 -0.00820000 -0.02610000 0.03420000 0.02560000 -0.00820000 0.02560000 -0.01660000 0.00005774 -0.03691097 0.03620387 -0.02028994 -0.01159655 -0.03669884 +O 0.00000000 4.94000000 3.68100000 0.03000000 -0.00000000 -0.00000000 -0.00000000 -0.00710000 0.00410000 -0.00000000 0.00410000 -0.02300000 0.00005774 0.00000000 0.00579828 -0.02812831 0.00000000 0.02623366 +O 1.63100000 2.47000000 10.79300000 -0.01770000 0.04210000 0.00400000 0.04210000 0.05840000 0.02750000 0.00400000 0.02750000 -0.04060000 -0.00005774 0.05953839 0.03889087 -0.04976547 0.00565685 -0.05381083 +O 2.95500000 0.17700000 10.79300000 0.06200000 0.00000000 0.00000000 0.00000000 -0.01510000 0.00220000 0.00000000 0.00220000 -0.04680000 -0.00005774 0.00000000 0.00311127 -0.05735888 0.00000000 0.05451793 +O 4.27800000 2.47000000 10.79300000 -0.01770000 -0.04210000 -0.00400000 -0.04210000 0.05840000 0.02750000 -0.00400000 0.02750000 -0.04060000 -0.00005774 -0.05953839 0.03889087 -0.04976547 -0.00565685 -0.05381083 +O -1.63100000 4.35300000 8.50600000 -0.12880000 0.12060000 -0.07740000 0.12060000 0.02110000 0.04980000 -0.07740000 0.04980000 0.10780000 -0.00005774 0.17055416 0.07042784 0.13198667 -0.10946013 -0.10599531 +O 1.63100000 4.35300000 8.50600000 -0.12880000 -0.12060000 0.07740000 -0.12060000 0.02110000 0.04980000 0.07740000 0.04980000 0.10780000 -0.00005774 -0.17055416 0.07042784 0.13198667 0.10946013 -0.10599531 +O 0.00000000 1.52800000 8.50600000 0.07870000 0.00000000 -0.00000000 0.00000000 -0.18750000 -0.09080000 -0.00000000 -0.09080000 0.10880000 -0.00000000 0.00000000 -0.12841059 0.13325224 0.00000000 0.18823183 +Ti 2.95500000 1.70600000 12.06200000 0.07080000 0.00000000 0.00000000 0.00000000 0.17150000 0.08560000 0.00000000 0.08560000 -0.24220000 -0.00005774 0.00000000 0.12105668 -0.29667403 0.00000000 -0.07120565 +Ti 0.00000000 3.41200000 2.41200000 0.08800000 -0.00000000 -0.00000000 -0.00000000 0.17150000 0.08990000 -0.00000000 0.08990000 -0.25960000 0.00005774 0.00000000 0.12713780 -0.31790294 0.00000000 -0.05904342 +Ti -1.47700000 2.55900000 0.00000000 -0.09210000 -0.27610000 0.05540000 -0.27610000 -0.02810000 -0.08060000 0.05540000 -0.08060000 0.12010000 0.00005774 -0.39046436 -0.11398561 0.14713268 0.07834743 -0.04525483 +Ti 1.47700000 2.55900000 0.00000000 -0.09210000 0.27610000 -0.05540000 0.27610000 -0.02810000 -0.08060000 -0.05540000 -0.08060000 0.12010000 0.00005774 0.39046436 -0.11398561 0.14713268 -0.07834743 -0.04525483 +Ti 0.00000000 1.70600000 4.82500000 0.10300000 0.00000000 -0.00000000 0.00000000 -0.14820000 0.18560000 -0.00000000 0.18560000 0.04510000 0.00005774 0.00000000 0.26247804 0.05527682 0.00000000 0.17762522 +Ti 1.47700000 4.26500000 4.82500000 -0.08800000 -0.10820000 -0.15860000 -0.10820000 0.04130000 -0.08910000 -0.15860000 -0.08910000 0.04670000 -0.00000000 -0.15301791 -0.12600643 0.05719559 -0.22429427 -0.09142891 +Ti -1.47700000 4.26500000 4.82500000 -0.08800000 0.10820000 0.15860000 0.10820000 0.04130000 -0.08910000 0.15860000 -0.08910000 0.04670000 -0.00000000 0.15301791 -0.12600643 0.05719559 0.22429427 -0.09142891 +Ti 2.95500000 3.41200000 9.65000000 0.07980000 -0.00000000 -0.00000000 -0.00000000 -0.12660000 0.20230000 -0.00000000 0.20230000 0.04670000 0.00005774 0.00000000 0.28609540 0.05723641 0.00000000 0.14594684 +Ti 1.47700000 0.85300000 9.65000000 -0.07740000 -0.08860000 -0.17320000 -0.08860000 0.02940000 -0.09730000 -0.17320000 -0.09730000 0.04800000 -0.00000000 -0.12529932 -0.13760298 0.05878775 -0.24494179 -0.07551900 +Ti 4.43200000 0.85300000 9.65000000 -0.07740000 0.08860000 0.17320000 0.08860000 0.02940000 -0.09730000 0.17320000 -0.09730000 0.04800000 -0.00000000 0.12529932 -0.13760298 0.05878775 0.24494179 -0.07551900 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 4.43200000 0.85300000 2.41200000 -0.00170000 0.00330000 0.00140000 0.00330000 0.00880000 -0.00410000 0.00140000 -0.00410000 -0.00710000 0.00000000 0.00466690 -0.00579828 -0.00869569 0.00197990 -0.00742462 +Li 0.00000000 0.00000000 12.66500000 -0.00110000 0.00010000 -0.00000000 0.00010000 0.00490000 -0.00160000 -0.00000000 -0.00160000 -0.00380000 0.00000000 0.00014142 -0.00226274 -0.00465403 0.00000000 -0.00424264 +Li 2.95500000 1.70600000 6.63400000 0.00330000 0.00010000 0.00000000 0.00010000 0.00320000 0.00000000 0.00000000 0.00000000 -0.00650000 0.00000000 0.00014142 0.00000000 -0.00796084 0.00000000 0.00007071 +Li 2.95500000 3.41200000 2.41200000 0.00620000 -0.00380000 0.00130000 -0.00380000 -0.00090000 0.00480000 0.00130000 0.00480000 -0.00520000 -0.00005774 -0.00537401 0.00678823 -0.00640950 0.00183848 0.00502046 +Li 0.00000000 3.41200000 11.45900000 0.01150000 -0.00000000 -0.00010000 -0.00000000 0.00990000 0.00030000 -0.00010000 0.00030000 -0.02140000 0.00000000 0.00000000 0.00042426 -0.02620954 -0.00014142 0.00113137 +Li 0.00000000 3.41200000 7.84000000 0.00280000 -0.00000000 -0.00010000 -0.00000000 0.00280000 -0.00000000 -0.00010000 -0.00000000 -0.00570000 0.00005774 0.00000000 0.00000000 -0.00694022 -0.00014142 0.00000000 +Li 0.00000000 0.00000000 7.23700000 0.00690000 -0.00000000 0.00010000 -0.00000000 0.00700000 0.00010000 0.00010000 0.00010000 -0.01390000 0.00000000 0.00000000 0.00014142 -0.01702395 0.00014142 -0.00007071 +Li 2.95500000 0.00000000 0.00000000 0.00480000 0.00010000 0.00190000 0.00010000 0.00520000 -0.00120000 0.00190000 -0.00120000 -0.01000000 0.00000000 0.00014142 -0.00169706 -0.01224745 0.00268701 -0.00028284 +O 0.00000000 0.00000000 3.80700000 0.06740000 -0.05370000 0.07380000 -0.05370000 0.12790000 0.03570000 0.07380000 0.03570000 -0.19530000 0.00000000 -0.07594327 0.05048742 -0.23919267 0.10436896 -0.04277996 +O 0.00000000 0.00000000 10.66800000 0.07710000 -0.00100000 0.00030000 -0.00100000 0.07900000 -0.00580000 0.00030000 -0.00580000 -0.15600000 -0.00005774 -0.00141421 -0.00820244 -0.19110102 0.00042426 -0.00134350 +O 2.95500000 1.70600000 8.63200000 0.02680000 -0.00070000 0.00040000 -0.00070000 0.02540000 -0.00090000 0.00040000 -0.00090000 -0.05220000 0.00000000 -0.00098995 -0.00127279 -0.06393168 0.00056569 0.00098995 +O 2.95500000 1.70600000 1.01800000 0.07230000 -0.04120000 0.08290000 -0.04120000 -0.01950000 -0.10860000 0.08290000 -0.10860000 -0.05280000 0.00000000 -0.05826560 -0.15358359 -0.06466653 0.11723830 0.06491240 +O 0.00000000 3.41200000 13.45700000 0.10720000 -0.00760000 0.01140000 -0.00760000 -0.05420000 -0.16730000 0.01140000 -0.16730000 -0.05300000 0.00000000 -0.01074802 -0.23659793 -0.06491148 0.01612203 0.11412703 +O 0.00000000 3.41200000 5.84300000 0.03140000 -0.00400000 0.01840000 -0.00400000 0.03340000 0.00920000 0.01840000 0.00920000 -0.06480000 0.00000000 -0.00565685 0.01301076 -0.07936347 0.02602153 -0.00141421 +O -1.32400000 4.17600000 1.14400000 -0.14150000 0.00430000 0.08480000 0.00430000 0.08770000 -0.07240000 0.08480000 -0.07240000 0.05380000 -0.00000000 0.00608112 -0.10238906 0.06589127 0.11992531 -0.16206887 +O 0.00000000 1.88300000 1.14400000 0.08180000 0.05950000 -0.08130000 0.05950000 -0.17040000 0.05530000 -0.08130000 0.05530000 0.08860000 -0.00000000 0.08414571 0.07820601 0.10851240 -0.11497556 0.17833233 +O 1.32400000 4.17600000 1.14400000 -0.19500000 0.01440000 -0.09820000 0.01440000 0.09270000 0.03620000 -0.09820000 0.03620000 0.10230000 -0.00000000 0.02036468 0.05119453 0.12529140 -0.13887577 -0.20343462 +O 4.27800000 0.94200000 13.33100000 -0.15590000 -0.07680000 0.15530000 -0.07680000 0.10290000 0.00890000 0.15530000 0.00890000 0.05300000 0.00000000 -0.10861160 0.01258650 0.06491148 0.21962737 -0.18299923 +O 1.63100000 0.94200000 13.33100000 -0.15960000 0.07440000 -0.15610000 0.07440000 0.11470000 0.02150000 -0.15610000 0.02150000 0.04490000 -0.00000000 0.10521749 0.03040559 0.05499104 -0.22075874 -0.19395939 +O 2.95500000 3.23400000 13.33100000 0.03060000 0.00440000 -0.01090000 0.00440000 -0.10600000 0.06250000 -0.01090000 0.06250000 0.07550000 -0.00005774 0.00622254 0.08838835 0.09242741 -0.01541493 0.09659079 +O 4.58600000 0.76400000 5.96800000 -0.13430000 0.14090000 -0.09840000 0.14090000 0.01760000 0.04780000 -0.09840000 0.04780000 0.11670000 0.00000000 0.19926269 0.06759941 0.14292773 -0.13915861 -0.10740952 +O 2.95500000 3.58900000 5.96800000 0.10110000 0.00150000 -0.01260000 0.00150000 -0.22210000 -0.10770000 -0.01260000 -0.10770000 0.12100000 -0.00000000 0.00212132 -0.15231080 0.14819413 -0.01781909 0.22853691 +O 1.32400000 0.76400000 5.96800000 -0.13740000 -0.15240000 0.11860000 -0.15240000 0.03100000 0.07390000 0.11860000 0.07390000 0.10640000 -0.00000000 -0.21552615 0.10451038 0.13031285 0.16772573 -0.11907678 +O 1.32400000 2.64800000 3.68100000 -0.00830000 0.12910000 -0.06370000 0.12910000 0.02490000 -0.01250000 -0.06370000 -0.01250000 -0.01660000 0.00000000 0.18257497 -0.01767767 -0.02033076 -0.09008540 -0.02347595 +O -1.32400000 2.64800000 3.68100000 -0.07640000 -0.13110000 0.05420000 -0.13110000 0.08230000 0.06450000 0.05420000 0.06450000 -0.00580000 -0.00005774 -0.18540340 0.09121677 -0.00714435 0.07665038 -0.11221785 +O 0.00000000 4.94000000 3.68100000 0.12530000 0.05210000 -0.07030000 0.05210000 -0.10340000 -0.04050000 -0.07030000 -0.04050000 -0.02190000 0.00000000 0.07368053 -0.05727565 -0.02682191 -0.09941921 0.16171532 +O 1.63100000 2.47000000 10.79300000 -0.02110000 0.04940000 -0.01370000 0.04940000 0.04890000 0.03610000 -0.01370000 0.03610000 -0.02790000 0.00005774 0.06986215 0.05105311 -0.03412956 -0.01937473 -0.04949747 +O 2.95500000 0.17700000 10.79300000 0.07030000 -0.00210000 -0.00080000 -0.00210000 -0.03770000 -0.01970000 -0.00080000 -0.01970000 -0.03260000 0.00000000 -0.00296985 -0.02786001 -0.03992668 -0.00113137 0.07636753 +O 4.27800000 2.47000000 10.79300000 -0.02090000 -0.05250000 0.01350000 -0.05250000 0.04760000 0.03700000 0.01350000 0.03700000 -0.02670000 0.00000000 -0.07424621 0.05232590 -0.03270069 0.01909188 -0.04843681 +O -1.63100000 4.35300000 8.50600000 -0.12470000 0.12670000 -0.08680000 0.12670000 0.01970000 0.05500000 -0.08680000 0.05500000 0.10490000 0.00005774 0.17918086 0.07778175 0.12851656 -0.12275374 -0.10210622 +O 1.63100000 4.35300000 8.50600000 -0.12430000 -0.12980000 0.08690000 -0.12980000 0.01810000 0.05540000 0.08690000 0.05540000 0.10620000 -0.00000000 -0.18356492 0.07834743 0.13006791 0.12289516 -0.10069201 +O 0.00000000 1.52800000 8.50600000 0.09300000 -0.00240000 -0.00030000 -0.00240000 -0.20140000 -0.10140000 -0.00030000 -0.10140000 0.10830000 0.00005774 -0.00339411 -0.14340126 0.13268069 -0.00042426 0.20817224 +Ti 2.95500000 1.70600000 12.06200000 0.08000000 0.00030000 -0.00150000 0.00030000 0.16950000 0.08490000 -0.00150000 0.08490000 -0.24950000 0.00000000 0.00042426 0.12006673 -0.30557385 -0.00212132 -0.06328606 +Ti 0.00000000 3.41200000 2.41200000 0.11950000 0.03460000 -0.03870000 0.03460000 0.17170000 0.05850000 -0.03870000 0.05850000 -0.29120000 0.00000000 0.04893179 0.08273149 -0.35664571 -0.05473006 -0.03691097 +Ti -1.47700000 2.55900000 0.00000000 -0.08310000 -0.24440000 0.01980000 -0.24440000 -0.04630000 -0.09160000 0.01980000 -0.09160000 0.12940000 -0.00000000 -0.34563379 -0.12954196 0.15848199 0.02800143 -0.02602153 +Ti 1.47700000 2.55900000 0.00000000 -0.08920000 0.24090000 -0.02530000 0.24090000 -0.03040000 -0.06870000 -0.02530000 -0.06870000 0.11960000 -0.00000000 0.34068405 -0.09715647 0.14647949 -0.03577960 -0.04157788 +Ti 0.00000000 1.70600000 4.82500000 0.07510000 -0.01160000 0.02350000 -0.01160000 -0.12510000 0.22120000 0.02350000 0.22120000 0.05000000 -0.00000000 -0.01640488 0.31282404 0.06123724 0.03323402 0.14156278 +Ti 1.47700000 4.26500000 4.82500000 -0.08040000 -0.08840000 -0.20570000 -0.08840000 0.02510000 -0.11660000 -0.20570000 -0.11660000 0.05530000 -0.00000000 -0.12501648 -0.16489730 0.06772839 -0.29090373 -0.07459977 +Ti -1.47700000 4.26500000 4.82500000 -0.08770000 0.08100000 0.20080000 0.08100000 0.03660000 -0.08650000 0.20080000 -0.08650000 0.05110000 0.00000000 0.11455130 -0.12232947 0.06258446 0.28397408 -0.08789337 +Ti 2.95500000 3.41200000 9.65000000 0.08050000 -0.00010000 0.00020000 -0.00010000 -0.12530000 0.20860000 0.00020000 0.20860000 0.04480000 -0.00000000 -0.00014142 0.29500495 0.05486857 0.00028284 0.14552258 +Ti 1.47700000 0.85300000 9.65000000 -0.07630000 -0.08890000 -0.17890000 -0.08890000 0.03050000 -0.10080000 -0.17890000 -0.10080000 0.04580000 0.00000000 -0.12572359 -0.14255273 0.05609332 -0.25300281 -0.07551900 +Ti 4.43200000 0.85300000 9.65000000 -0.07640000 0.08890000 0.17880000 0.08890000 0.03060000 -0.10070000 0.17880000 -0.10070000 0.04580000 -0.00000000 0.12572359 -0.14241131 0.05609332 0.25286138 -0.07566043 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 2.95500000 3.41200000 2.41200000 -0.01120000 -0.00000000 -0.00000000 -0.00000000 0.02590000 -0.01400000 -0.00000000 -0.01400000 -0.01470000 0.00000000 0.00000000 -0.01979899 -0.01800375 0.00000000 -0.02623366 +Li 0.00000000 0.00000000 12.66500000 -0.00130000 -0.00000000 0.00000000 -0.00000000 0.00560000 -0.00160000 0.00000000 -0.00160000 -0.00430000 0.00000000 0.00000000 -0.00226274 -0.00526640 0.00000000 -0.00487904 +Li 2.95500000 1.70600000 6.63400000 0.00310000 0.00000000 -0.00000000 0.00000000 0.00370000 0.00000000 -0.00000000 0.00000000 -0.00680000 0.00000000 0.00000000 0.00000000 -0.00832827 0.00000000 -0.00042426 +Li 2.95500000 1.70600000 3.01600000 -0.00650000 0.00000000 -0.00000000 0.00000000 0.04160000 -0.01560000 -0.00000000 -0.01560000 -0.03510000 0.00000000 0.00000000 -0.02206173 -0.04298854 0.00000000 -0.03401184 +Li 0.00000000 3.41200000 11.45900000 0.01160000 0.00000000 0.00000000 0.00000000 0.01090000 0.00030000 0.00000000 0.00030000 -0.02260000 0.00005774 0.00000000 0.00042426 -0.02763841 0.00000000 0.00049497 +Li 0.00000000 3.41200000 7.84000000 0.00220000 0.00000000 -0.00000000 0.00000000 0.00310000 -0.00010000 -0.00000000 -0.00010000 -0.00540000 0.00005774 0.00000000 -0.00014142 -0.00657280 0.00000000 -0.00063640 +Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 0.00000000 -0.00000000 0.00770000 0.00010000 0.00000000 0.00010000 -0.01450000 0.00005774 0.00000000 0.00014142 -0.01771798 0.00000000 -0.00070711 +Li 2.95500000 0.00000000 0.00000000 0.00410000 0.00000000 0.00000000 0.00000000 0.00730000 -0.00130000 0.00000000 -0.00130000 -0.01150000 0.00005774 0.00000000 -0.00183848 -0.01404374 0.00000000 -0.00226274 +O 0.00000000 0.00000000 3.80700000 0.03500000 0.00000000 0.00000000 0.00000000 0.17450000 0.06990000 0.00000000 0.06990000 -0.20940000 -0.00005774 0.00000000 0.09885353 -0.25650240 0.00000000 -0.09864140 +O 0.00000000 0.00000000 10.66800000 0.07440000 -0.00000000 0.00000000 -0.00000000 0.08740000 -0.00550000 0.00000000 -0.00550000 -0.16180000 0.00000000 0.00000000 -0.00777817 -0.19816372 0.00000000 -0.00919239 +O 2.95500000 1.70600000 8.63200000 0.02500000 0.00000000 -0.00000000 0.00000000 0.03530000 -0.00070000 -0.00000000 -0.00070000 -0.06030000 0.00000000 0.00000000 -0.00098995 -0.07385212 0.00000000 -0.00728320 +O 2.95500000 1.70600000 1.01800000 -0.01250000 -0.00000000 0.00000000 -0.00000000 -0.00720000 -0.02950000 0.00000000 -0.02950000 0.01970000 -0.00000000 0.00000000 -0.04171930 0.02412747 0.00000000 -0.00374767 +O 0.00000000 3.41200000 13.45700000 0.09910000 0.00000000 0.00000000 0.00000000 -0.04470000 -0.14770000 0.00000000 -0.14770000 -0.05430000 -0.00005774 0.00000000 -0.20887934 -0.06654447 0.00000000 0.10168196 +O 0.00000000 3.41200000 5.84300000 0.02090000 -0.00000000 -0.00000000 -0.00000000 0.03630000 0.02030000 -0.00000000 0.02030000 -0.05720000 0.00000000 0.00000000 0.02870854 -0.07005541 0.00000000 -0.01088944 +O -1.32400000 4.17600000 1.14400000 -0.15560000 0.01660000 0.08370000 0.01660000 0.07930000 -0.07330000 0.08370000 -0.07330000 0.07620000 0.00005774 0.02347595 -0.10366185 0.09336638 0.11836968 -0.16609938 +O 0.00000000 1.88300000 1.14400000 0.05870000 0.00000000 0.00000000 0.00000000 -0.13410000 0.09210000 0.00000000 0.09210000 0.07540000 -0.00000000 0.00000000 0.13024907 0.09234576 0.00000000 0.13633019 +O 1.32400000 4.17600000 1.14400000 -0.15560000 -0.01660000 -0.08370000 -0.01660000 0.07930000 -0.07330000 -0.08370000 -0.07330000 0.07620000 0.00005774 -0.02347595 -0.10366185 0.09336638 -0.11836968 -0.16609938 +O 4.27800000 0.94200000 13.33100000 -0.15780000 -0.07150000 0.14790000 -0.07150000 0.11720000 0.01180000 0.14790000 0.01180000 0.04060000 0.00000000 -0.10111627 0.01668772 0.04972464 0.20916219 -0.19445436 +O 1.63100000 0.94200000 13.33100000 -0.15780000 0.07150000 -0.14790000 0.07150000 0.11720000 0.01180000 -0.14790000 0.01180000 0.04060000 0.00000000 0.10111627 0.01668772 0.04972464 -0.20916219 -0.19445436 +O 2.95500000 3.23400000 13.33100000 0.01830000 -0.00000000 -0.00000000 -0.00000000 -0.08970000 0.06250000 -0.00000000 0.06250000 0.07140000 -0.00000000 0.00000000 0.08838835 0.08744678 0.00000000 0.07636753 +O 4.58600000 0.76400000 5.96800000 -0.12320000 0.13310000 -0.09280000 0.13310000 0.03180000 0.04390000 -0.09280000 0.04390000 0.09140000 -0.00000000 0.18823183 0.06208398 0.11194168 -0.13123902 -0.10960155 +O 2.95500000 3.58900000 5.96800000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.18480000 -0.06910000 -0.00000000 -0.06910000 0.10430000 -0.00000000 0.00000000 -0.09772216 0.12774089 0.00000000 0.18759543 +O 1.32400000 0.76400000 5.96800000 -0.12320000 -0.13310000 0.09280000 -0.13310000 0.03180000 0.04390000 0.09280000 0.04390000 0.09140000 -0.00000000 -0.18823183 0.06208398 0.11194168 0.13123902 -0.10960155 +O 1.32400000 2.64800000 3.68100000 0.04760000 0.09260000 -0.09950000 0.09260000 0.00300000 -0.00540000 -0.09950000 -0.00540000 -0.05060000 0.00000000 0.13095618 -0.00763675 -0.06197209 -0.14071425 0.03153696 +O -1.32400000 2.64800000 3.68100000 0.04760000 -0.09260000 0.09950000 -0.09260000 0.00300000 -0.00540000 0.09950000 -0.00540000 -0.05060000 0.00000000 -0.13095618 -0.00763675 -0.06197209 0.14071425 0.03153696 +O 0.00000000 4.94000000 3.68100000 0.06080000 -0.00000000 -0.00000000 -0.00000000 -0.00850000 -0.02880000 -0.00000000 -0.02880000 -0.05230000 0.00000000 0.00000000 -0.04072935 -0.06405416 0.00000000 0.04900250 +O 1.63100000 2.47000000 10.79300000 -0.02380000 0.05020000 -0.01720000 0.05020000 0.05800000 0.03700000 -0.01720000 0.03700000 -0.03420000 0.00000000 0.07099352 0.05232590 -0.04188627 -0.02432447 -0.05784133 +O 2.95500000 0.17700000 10.79300000 0.06760000 0.00000000 0.00000000 0.00000000 -0.02710000 -0.02290000 0.00000000 -0.02290000 -0.04050000 0.00000000 0.00000000 -0.03238549 -0.04960217 0.00000000 0.06696301 +O 4.27800000 2.47000000 10.79300000 -0.02380000 -0.05020000 0.01720000 -0.05020000 0.05800000 0.03700000 0.01720000 0.03700000 -0.03420000 0.00000000 -0.07099352 0.05232590 -0.04188627 0.02432447 -0.05784133 +O -1.63100000 4.35300000 8.50600000 -0.12630000 0.12770000 -0.08970000 0.12770000 0.03150000 0.05650000 -0.08970000 0.05650000 0.09480000 0.00000000 0.18059507 0.07990307 0.11610581 -0.12685496 -0.11158145 +O 1.63100000 4.35300000 8.50600000 -0.12630000 -0.12770000 0.08970000 -0.12770000 0.03150000 0.05650000 0.08970000 0.05650000 0.09480000 0.00000000 -0.18059507 0.07990307 0.11610581 0.12685496 -0.11158145 +O 0.00000000 1.52800000 8.50600000 0.09320000 0.00000000 -0.00000000 0.00000000 -0.18890000 -0.10420000 -0.00000000 -0.10420000 0.09580000 -0.00005774 0.00000000 -0.14736105 0.11728973 0.00000000 0.19947482 +Ti 2.95500000 1.70600000 12.06200000 0.07920000 0.00000000 0.00000000 0.00000000 0.16670000 0.08370000 0.00000000 0.08370000 -0.24600000 0.00005774 0.00000000 0.11836968 -0.30124641 0.00000000 -0.06187184 +Ti 0.00000000 3.41200000 2.41200000 0.12850000 0.00000000 -0.00000000 0.00000000 0.14690000 0.03330000 -0.00000000 0.03330000 -0.27540000 0.00000000 0.00000000 0.04709331 -0.33729474 0.00000000 -0.01301076 +Ti -1.47700000 2.55900000 0.00000000 -0.08610000 -0.22300000 0.00750000 -0.22300000 -0.04420000 -0.07580000 0.00750000 -0.07580000 0.13030000 -0.00000000 -0.31536962 -0.10719739 0.15958426 0.01060660 -0.02962777 +Ti 1.47700000 2.55900000 0.00000000 -0.08610000 0.22300000 -0.00750000 0.22300000 -0.04420000 -0.07580000 -0.00750000 -0.07580000 0.13030000 -0.00000000 0.31536962 -0.10719739 0.15958426 -0.01060660 -0.02962777 +Ti 0.00000000 1.70600000 4.82500000 0.08990000 0.00000000 -0.00000000 0.00000000 -0.13000000 0.18400000 -0.00000000 0.18400000 0.04010000 -0.00000000 0.00000000 0.26021530 0.04911227 0.00000000 0.15549278 +Ti 1.47700000 4.26500000 4.82500000 -0.09210000 -0.09010000 -0.18750000 -0.09010000 0.04690000 -0.07960000 -0.18750000 -0.07960000 0.04520000 0.00000000 -0.12742064 -0.11257140 0.05535847 -0.26516504 -0.09828784 +Ti -1.47700000 4.26500000 4.82500000 -0.09210000 0.09010000 0.18750000 0.09010000 0.04690000 -0.07960000 0.18750000 -0.07960000 0.04520000 0.00000000 0.12742064 -0.11257140 0.05535847 0.26516504 -0.09828784 +Ti 2.95500000 3.41200000 9.65000000 0.08040000 -0.00000000 -0.00000000 -0.00000000 -0.12460000 0.21000000 -0.00000000 0.21000000 0.04410000 0.00005774 0.00000000 0.29698485 0.05405207 0.00000000 0.14495689 +Ti 1.47700000 0.85300000 9.65000000 -0.07600000 -0.08870000 -0.18020000 -0.08870000 0.03090000 -0.10140000 -0.18020000 -0.10140000 0.04500000 0.00005774 -0.12544074 -0.14340126 0.05515434 -0.25484128 -0.07558971 +Ti 4.43200000 0.85300000 9.65000000 -0.07600000 0.08870000 0.18020000 0.08870000 0.03090000 -0.10140000 0.18020000 -0.10140000 0.04500000 0.00005774 0.12544074 -0.14340126 0.05515434 0.25484128 -0.07558971 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 1.47700000 0.85300000 2.41200000 -0.00170000 -0.00340000 -0.00130000 -0.00340000 0.00880000 -0.00410000 -0.00130000 -0.00410000 -0.00710000 0.00000000 -0.00480833 -0.00579828 -0.00869569 -0.00183848 -0.00742462 +Li 0.00000000 0.00000000 12.66500000 -0.00110000 -0.00000000 -0.00000000 -0.00000000 0.00490000 -0.00160000 -0.00000000 -0.00160000 -0.00380000 0.00000000 0.00000000 -0.00226274 -0.00465403 0.00000000 -0.00424264 +Li 2.95500000 1.70600000 6.63400000 0.00330000 -0.00010000 -0.00000000 -0.00010000 0.00320000 0.00000000 -0.00000000 0.00000000 -0.00650000 0.00000000 -0.00014142 0.00000000 -0.00796084 0.00000000 0.00007071 +Li 2.95500000 3.41200000 2.41200000 0.00620000 0.00380000 -0.00130000 0.00380000 -0.00100000 0.00480000 -0.00130000 0.00480000 -0.00520000 0.00000000 0.00537401 0.00678823 -0.00636867 -0.00183848 0.00509117 +Li 0.00000000 3.41200000 11.45900000 0.01150000 0.00000000 0.00010000 0.00000000 0.00990000 0.00030000 0.00010000 0.00030000 -0.02140000 0.00000000 0.00000000 0.00042426 -0.02620954 0.00014142 0.00113137 +Li 0.00000000 3.41200000 7.84000000 0.00280000 0.00000000 0.00010000 0.00000000 0.00280000 -0.00000000 0.00010000 -0.00000000 -0.00570000 0.00005774 0.00000000 0.00000000 -0.00694022 0.00014142 0.00000000 +Li 0.00000000 0.00000000 7.23700000 0.00690000 0.00000000 -0.00010000 0.00000000 0.00700000 0.00010000 -0.00010000 0.00010000 -0.01390000 0.00000000 0.00000000 0.00014142 -0.01702395 -0.00014142 -0.00007071 +Li 2.95500000 0.00000000 0.00000000 0.00480000 -0.00020000 -0.00190000 -0.00020000 0.00520000 -0.00120000 -0.00190000 -0.00120000 -0.01000000 0.00000000 -0.00028284 -0.00169706 -0.01224745 -0.00268701 -0.00028284 +O 0.00000000 0.00000000 3.80700000 0.06740000 0.05180000 -0.07360000 0.05180000 0.12780000 0.03580000 -0.07360000 0.03580000 -0.19520000 0.00000000 0.07325626 0.05062885 -0.23907020 -0.10408612 -0.04270925 +O 0.00000000 0.00000000 10.66800000 0.07710000 -0.00090000 -0.00010000 -0.00090000 0.07900000 -0.00580000 -0.00010000 -0.00580000 -0.15600000 -0.00005774 -0.00127279 -0.00820244 -0.19110102 -0.00014142 -0.00134350 +O 2.95500000 1.70600000 8.63200000 0.02680000 -0.00070000 -0.00010000 -0.00070000 0.02540000 -0.00090000 -0.00010000 -0.00090000 -0.05220000 0.00000000 -0.00098995 -0.00127279 -0.06393168 -0.00014142 0.00098995 +O 2.95500000 1.70600000 1.01800000 0.07230000 0.03990000 -0.08260000 0.03990000 -0.01980000 -0.10850000 -0.08260000 -0.10850000 -0.05260000 0.00005774 0.05642712 -0.15344217 -0.06438076 -0.11681404 0.06512453 +O 0.00000000 3.41200000 13.45700000 0.10720000 0.00630000 -0.01090000 0.00630000 -0.05420000 -0.16730000 -0.01090000 -0.16730000 -0.05300000 0.00000000 0.00890955 -0.23659793 -0.06491148 -0.01541493 0.11412703 +O 0.00000000 3.41200000 5.84300000 0.03140000 0.00260000 -0.01810000 0.00260000 0.03340000 0.00920000 -0.01810000 0.00920000 -0.06480000 0.00000000 0.00367696 0.01301076 -0.07936347 -0.02559727 -0.00141421 +O -1.32400000 4.17600000 1.14400000 -0.19550000 -0.01730000 0.09820000 -0.01730000 0.09470000 0.03600000 0.09820000 0.03600000 0.10080000 -0.00000000 -0.02446589 0.05091169 0.12345428 0.13887577 -0.20520239 +O 0.00000000 1.88300000 1.14400000 0.08180000 -0.06350000 0.08120000 -0.06350000 -0.17010000 0.05510000 0.08120000 0.05510000 0.08830000 -0.00000000 -0.08980256 0.07792317 0.10814497 0.11483414 0.17812020 +O 1.32400000 4.17600000 1.14400000 -0.14100000 -0.00720000 -0.08450000 -0.00720000 0.08570000 -0.07220000 -0.08450000 -0.07220000 0.05530000 -0.00000000 -0.01018234 -0.10210622 0.06772839 -0.11950105 -0.16030111 +O 4.27800000 0.94200000 13.33100000 -0.16000000 -0.07740000 0.15620000 -0.07740000 0.11690000 0.02130000 0.15620000 0.02130000 0.04310000 -0.00000000 -0.10946013 0.03012275 0.05278650 0.22090016 -0.19579787 +O 1.63100000 0.94200000 13.33100000 -0.15550000 0.07370000 -0.15510000 0.07370000 0.10080000 0.00910000 -0.15510000 0.00910000 0.05470000 0.00000000 0.10422754 0.01286934 0.06699354 -0.21934452 -0.18123147 +O 2.95500000 3.23400000 13.33100000 0.03060000 -0.00840000 0.01080000 -0.00840000 -0.10610000 0.06250000 0.01080000 0.06250000 0.07550000 0.00000000 -0.01187939 0.08838835 0.09246824 0.01527351 0.09666150 +O 4.58600000 0.76400000 5.96800000 -0.13790000 0.14930000 -0.11860000 0.14930000 0.03270000 0.07410000 -0.11860000 0.07410000 0.10530000 -0.00005774 0.21114208 0.10479322 0.12892481 -0.16772573 -0.12063242 +O 2.95500000 3.58900000 5.96800000 0.10110000 -0.00650000 0.01280000 -0.00650000 -0.22210000 -0.10780000 0.01280000 -0.10780000 0.12100000 -0.00000000 -0.00919239 -0.15245222 0.14819413 0.01810193 0.22853691 +O 1.32400000 0.76400000 5.96800000 -0.13380000 -0.14400000 0.09840000 -0.14400000 0.01590000 0.04770000 0.09840000 0.04770000 0.11790000 -0.00000000 -0.20364675 0.06745799 0.14439742 0.13915861 -0.10585389 +O 1.32400000 2.64800000 3.68100000 -0.07680000 0.12820000 -0.05410000 0.12820000 0.08370000 0.06450000 -0.05410000 0.06450000 -0.00690000 -0.00000000 0.18130218 0.09121677 -0.00845074 -0.07650895 -0.11349064 +O -1.32400000 2.64800000 3.68100000 -0.00800000 -0.13210000 0.06390000 -0.13210000 0.02350000 -0.01250000 0.06390000 -0.01250000 -0.01560000 0.00005774 -0.18681761 -0.01767767 -0.01906520 0.09036825 -0.02227386 +O 0.00000000 4.94000000 3.68100000 0.12530000 -0.05690000 0.07040000 -0.05690000 -0.10310000 -0.04060000 0.07040000 -0.04060000 -0.02210000 -0.00005774 -0.08046875 -0.05741707 -0.02710769 0.09956063 0.16150319 +O 1.63100000 2.47000000 10.79300000 -0.02120000 0.04950000 -0.01340000 0.04950000 0.04920000 0.03690000 -0.01340000 0.03690000 -0.02800000 0.00000000 0.07000357 0.05218448 -0.03429286 -0.01895046 -0.04978032 +O 2.95500000 0.17700000 10.79300000 0.07030000 -0.00270000 0.00090000 -0.00270000 -0.03770000 -0.01970000 0.00090000 -0.01970000 -0.03260000 0.00000000 -0.00381838 -0.02786001 -0.03992668 0.00127279 0.07636753 +O 4.27800000 2.47000000 10.79300000 -0.02070000 -0.05250000 0.01380000 -0.05250000 0.04730000 0.03620000 0.01380000 0.03620000 -0.02660000 0.00000000 -0.07424621 0.05119453 -0.03257821 0.01951615 -0.04808326 +O -1.63100000 4.35300000 8.50600000 -0.12480000 0.12670000 -0.08690000 0.12670000 0.01980000 0.05550000 -0.08690000 0.05550000 0.10500000 -0.00000000 0.17918086 0.07848885 0.12859821 -0.12289516 -0.10224764 +O 1.63100000 4.35300000 8.50600000 -0.12420000 -0.12980000 0.08680000 -0.12980000 0.01800000 0.05490000 0.08680000 0.05490000 0.10610000 0.00005774 -0.18356492 0.07764032 0.12998626 0.12275374 -0.10055058 +O 0.00000000 1.52800000 8.50600000 0.09300000 -0.00250000 0.00050000 -0.00250000 -0.20140000 -0.10140000 0.00050000 -0.10140000 0.10830000 0.00005774 -0.00353553 -0.14340126 0.13268069 0.00070711 0.20817224 +Ti 2.95500000 1.70600000 12.06200000 0.08000000 -0.00040000 0.00160000 -0.00040000 0.16950000 0.08490000 0.00160000 0.08490000 -0.24950000 0.00000000 -0.00056569 0.12006673 -0.30557385 0.00226274 -0.06328606 +Ti 0.00000000 3.41200000 2.41200000 0.11950000 -0.03470000 0.03880000 -0.03470000 0.17180000 0.05850000 0.03880000 0.05850000 -0.29130000 0.00000000 -0.04907321 0.08273149 -0.35676818 0.05487149 -0.03698168 +Ti -1.47700000 2.55900000 0.00000000 -0.08920000 -0.24100000 0.02540000 -0.24100000 -0.03040000 -0.06870000 0.02540000 -0.06870000 0.11960000 -0.00000000 -0.34082547 -0.09715647 0.14647949 0.03592102 -0.04157788 +Ti 1.47700000 2.55900000 0.00000000 -0.08310000 0.24440000 -0.01970000 0.24440000 -0.04630000 -0.09160000 -0.01970000 -0.09160000 0.12940000 -0.00000000 0.34563379 -0.12954196 0.15848199 -0.02786001 -0.02602153 +Ti 0.00000000 1.70600000 4.82500000 0.07510000 0.01150000 -0.02350000 0.01150000 -0.12510000 0.22120000 -0.02350000 0.22120000 0.05000000 -0.00000000 0.01626346 0.31282404 0.06123724 -0.03323402 0.14156278 +Ti 1.47700000 4.26500000 4.82500000 -0.08770000 -0.08110000 -0.20070000 -0.08110000 0.03650000 -0.08650000 -0.20070000 -0.08650000 0.05110000 0.00005774 -0.11469272 -0.12232947 0.06262529 -0.28383266 -0.08782266 +Ti -1.47700000 4.26500000 4.82500000 -0.08040000 0.08840000 0.20580000 0.08840000 0.02520000 -0.11660000 0.20580000 -0.11660000 0.05520000 -0.00000000 0.12501648 -0.16489730 0.06760592 0.29104515 -0.07467048 +Ti 2.95500000 3.41200000 9.65000000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.12530000 0.20860000 -0.00000000 0.20860000 0.04480000 -0.00000000 0.00000000 0.29500495 0.05486857 0.00000000 0.14552258 +Ti 1.47700000 0.85300000 9.65000000 -0.07640000 -0.08900000 -0.17870000 -0.08900000 0.03060000 -0.10060000 -0.17870000 -0.10060000 0.04590000 -0.00005774 -0.12586501 -0.14226988 0.05617496 -0.25271996 -0.07566043 +Ti 4.43200000 0.85300000 9.65000000 -0.07630000 0.08890000 0.17900000 0.08890000 0.03060000 -0.10080000 0.17900000 -0.10080000 0.04580000 -0.00005774 0.12572359 -0.14255273 0.05605249 0.25314423 -0.07558971 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.01940000 -0.00000000 -0.00000000 -0.00000000 -0.01220000 -0.00230000 -0.00000000 -0.00230000 0.03160000 -0.00000000 0.00000000 -0.00325269 0.03870194 0.00000000 -0.00509117 +Li 0.00000000 0.00000000 0.00000000 -0.01820000 0.00000000 -0.00000000 0.00000000 -0.00030000 0.00580000 -0.00000000 0.00580000 0.01850000 0.00000000 0.00000000 0.00820244 0.02265778 0.00000000 -0.01265721 +Li 2.95500000 1.70600000 6.63400000 0.00440000 0.00000000 0.00000000 0.00000000 0.00520000 0.00000000 0.00000000 0.00000000 -0.00960000 0.00000000 0.00000000 0.00000000 -0.01175755 0.00000000 -0.00056569 +Li 2.95500000 1.70600000 3.01600000 0.01190000 0.00000000 -0.00000000 0.00000000 0.01120000 0.00040000 -0.00000000 0.00040000 -0.02310000 0.00000000 0.00000000 0.00056569 -0.02829161 0.00000000 0.00049497 +Li 0.00000000 3.41200000 11.45900000 0.00910000 -0.00000000 0.00000000 -0.00000000 0.00850000 0.00020000 0.00000000 0.00020000 -0.01760000 0.00000000 0.00000000 0.00028284 -0.02155551 0.00000000 0.00042426 +Li 0.00000000 3.41200000 7.84000000 0.00090000 -0.00000000 -0.00000000 -0.00000000 0.00180000 -0.00000000 -0.00000000 -0.00000000 -0.00270000 0.00000000 0.00000000 0.00000000 -0.00330681 0.00000000 -0.00063640 +Li 0.00000000 0.00000000 7.23700000 0.00660000 -0.00000000 0.00000000 -0.00000000 0.00760000 0.00000000 0.00000000 0.00000000 -0.01420000 0.00000000 0.00000000 0.00000000 -0.01739138 0.00000000 -0.00070711 +Li 2.95500000 0.00000000 0.00000000 0.00970000 0.00000000 -0.00000000 0.00000000 0.00350000 -0.00200000 -0.00000000 -0.00200000 -0.01320000 0.00000000 0.00000000 -0.00282843 -0.01616663 0.00000000 0.00438406 +O 0.00000000 0.00000000 3.80700000 0.07310000 0.00000000 0.00000000 0.00000000 0.08660000 -0.00610000 0.00000000 -0.00610000 -0.15970000 0.00000000 0.00000000 -0.00862670 -0.19559176 0.00000000 -0.00954594 +O 0.00000000 0.00000000 10.66800000 0.07740000 -0.00000000 0.00000000 -0.00000000 0.08880000 -0.00390000 0.00000000 -0.00390000 -0.16620000 0.00000000 0.00000000 -0.00551543 -0.20355260 0.00000000 -0.00806102 +O 2.95500000 1.70600000 8.63200000 0.02490000 -0.00000000 -0.00000000 -0.00000000 0.03510000 -0.00070000 -0.00000000 -0.00070000 -0.06000000 0.00000000 0.00000000 -0.00098995 -0.07348469 0.00000000 -0.00721249 +O 2.95500000 1.70600000 1.01800000 0.13080000 -0.00000000 0.00000000 -0.00000000 -0.03120000 -0.16810000 0.00000000 -0.16810000 -0.09970000 0.00005774 0.00000000 -0.23772930 -0.12206624 0.00000000 0.11455130 +O 0.00000000 3.41200000 13.45700000 0.06970000 0.00000000 -0.00000000 0.00000000 -0.03640000 -0.19220000 -0.00000000 -0.19220000 -0.03330000 0.00000000 0.00000000 -0.27181185 -0.04078400 0.00000000 0.07502403 +O 0.00000000 3.41200000 5.84300000 0.02200000 -0.00000000 -0.00000000 -0.00000000 0.03230000 -0.00080000 -0.00000000 -0.00080000 -0.05430000 0.00000000 0.00000000 -0.00113137 -0.06650365 0.00000000 -0.00728320 +O -1.32400000 4.17600000 1.14400000 -0.06810000 -0.15090000 0.22230000 -0.15090000 0.07060000 -0.01650000 0.22230000 -0.01650000 -0.00250000 -0.00000000 -0.21340483 -0.02333452 -0.00306186 0.31437967 -0.09807571 +O 0.00000000 1.88300000 1.14400000 -0.04150000 -0.00000000 0.00000000 -0.00000000 0.03000000 0.14460000 0.00000000 0.14460000 0.01150000 -0.00000000 0.00000000 0.20449528 0.01408457 0.00000000 -0.05055813 +O 1.32400000 4.17600000 1.14400000 -0.06810000 0.15090000 -0.22230000 0.15090000 0.07060000 -0.01650000 -0.22230000 -0.01650000 -0.00250000 -0.00000000 0.21340483 -0.02333452 -0.00306186 -0.31437967 -0.09807571 +O 4.27800000 0.94200000 13.33100000 -0.13130000 -0.10430000 0.26110000 -0.10430000 0.09150000 -0.01350000 0.26110000 -0.01350000 0.03980000 -0.00000000 -0.14750247 -0.01909188 0.04874485 0.36925116 -0.15754339 +O 1.63100000 0.94200000 13.33100000 -0.13130000 0.10430000 -0.26110000 0.10430000 0.09150000 -0.01350000 -0.26110000 -0.01350000 0.03980000 -0.00000000 0.14750247 -0.01909188 0.04874485 -0.36925116 -0.15754339 +O 2.95500000 3.23400000 13.33100000 0.00580000 -0.00000000 -0.00000000 -0.00000000 -0.09340000 0.16570000 -0.00000000 0.16570000 0.08760000 -0.00000000 0.00000000 0.23433519 0.10728765 0.00000000 0.07014499 +O 4.58600000 0.76400000 5.96800000 -0.12880000 0.12060000 -0.07740000 0.12060000 0.02110000 0.04980000 -0.07740000 0.04980000 0.10780000 -0.00005774 0.17055416 0.07042784 0.13198667 -0.10946013 -0.10599531 +O 2.95500000 3.58900000 5.96800000 0.07870000 0.00000000 0.00000000 0.00000000 -0.18750000 -0.09080000 0.00000000 -0.09080000 0.10880000 -0.00000000 0.00000000 -0.12841059 0.13325224 0.00000000 0.18823183 +O 1.32400000 0.76400000 5.96800000 -0.12880000 -0.12060000 0.07740000 -0.12060000 0.02110000 0.04980000 0.07740000 0.04980000 0.10780000 -0.00005774 -0.17055416 0.07042784 0.13198667 0.10946013 -0.10599531 +O 1.32400000 2.64800000 3.68100000 -0.01770000 0.04210000 0.00400000 0.04210000 0.05840000 0.02750000 0.00400000 0.02750000 -0.04060000 -0.00005774 0.05953839 0.03889087 -0.04976547 0.00565685 -0.05381083 +O -1.32400000 2.64800000 3.68100000 -0.01770000 -0.04210000 -0.00400000 -0.04210000 0.05840000 0.02750000 -0.00400000 0.02750000 -0.04060000 -0.00005774 -0.05953839 0.03889087 -0.04976547 -0.00565685 -0.05381083 +O 0.00000000 4.94000000 3.68100000 0.06200000 -0.00000000 0.00000000 -0.00000000 -0.01510000 0.00220000 0.00000000 0.00220000 -0.04680000 -0.00005774 0.00000000 0.00311127 -0.05735888 0.00000000 0.05451793 +O 1.63100000 2.47000000 10.79300000 -0.01770000 0.02610000 0.00820000 0.02610000 0.03420000 0.02560000 0.00820000 0.02560000 -0.01660000 0.00005774 0.03691097 0.03620387 -0.02028994 0.01159655 -0.03669884 +O 2.95500000 0.17700000 10.79300000 0.03000000 0.00000000 -0.00000000 0.00000000 -0.00710000 0.00410000 -0.00000000 0.00410000 -0.02300000 0.00005774 0.00000000 0.00579828 -0.02812831 0.00000000 0.02623366 +O 4.27800000 2.47000000 10.79300000 -0.01770000 -0.02610000 -0.00820000 -0.02610000 0.03420000 0.02560000 -0.00820000 0.02560000 -0.01660000 0.00005774 -0.03691097 0.03620387 -0.02028994 -0.01159655 -0.03669884 +O -1.63100000 4.35300000 8.50600000 -0.12530000 0.13210000 -0.06880000 0.13210000 0.03790000 0.04460000 -0.06880000 0.04460000 0.08740000 -0.00000000 0.18681761 0.06307392 0.10704270 -0.09729789 -0.11539983 +O 1.63100000 4.35300000 8.50600000 -0.12530000 -0.13210000 0.06880000 -0.13210000 0.03790000 0.04460000 0.06880000 0.04460000 0.08740000 -0.00000000 -0.18681761 0.06307392 0.10704270 0.09729789 -0.11539983 +O 0.00000000 1.52800000 8.50600000 0.10240000 0.00000000 -0.00000000 0.00000000 -0.19110000 -0.08020000 -0.00000000 -0.08020000 0.08870000 -0.00000000 0.00000000 -0.11341993 0.10863487 0.00000000 0.20753584 +Ti 2.95500000 1.70600000 12.06200000 0.08800000 -0.00000000 0.00000000 -0.00000000 0.17150000 0.08990000 0.00000000 0.08990000 -0.25960000 0.00005774 0.00000000 0.12713780 -0.31790294 0.00000000 -0.05904342 +Ti 0.00000000 3.41200000 2.41200000 0.07080000 -0.00000000 -0.00000000 -0.00000000 0.17150000 0.08560000 -0.00000000 0.08560000 -0.24220000 -0.00005774 0.00000000 0.12105668 -0.29667403 0.00000000 -0.07120565 +Ti -1.47700000 2.55900000 0.00000000 -0.09210000 -0.27610000 0.05540000 -0.27610000 -0.02810000 -0.08060000 0.05540000 -0.08060000 0.12010000 0.00005774 -0.39046436 -0.11398561 0.14713268 0.07834743 -0.04525483 +Ti 1.47700000 2.55900000 0.00000000 -0.09210000 0.27610000 -0.05540000 0.27610000 -0.02810000 -0.08060000 -0.05540000 -0.08060000 0.12010000 0.00005774 0.39046436 -0.11398561 0.14713268 -0.07834743 -0.04525483 +Ti 0.00000000 1.70600000 4.82500000 0.07980000 -0.00000000 0.00000000 -0.00000000 -0.12660000 0.20230000 0.00000000 0.20230000 0.04670000 0.00005774 0.00000000 0.28609540 0.05723641 0.00000000 0.14594684 +Ti 1.47700000 4.26500000 4.82500000 -0.07740000 -0.08860000 -0.17320000 -0.08860000 0.02940000 -0.09730000 -0.17320000 -0.09730000 0.04800000 -0.00000000 -0.12529932 -0.13760298 0.05878775 -0.24494179 -0.07551900 +Ti -1.47700000 4.26500000 4.82500000 -0.07740000 0.08860000 0.17320000 0.08860000 0.02940000 -0.09730000 0.17320000 -0.09730000 0.04800000 -0.00000000 0.12529932 -0.13760298 0.05878775 0.24494179 -0.07551900 +Ti 2.95500000 3.41200000 9.65000000 0.10300000 -0.00000000 -0.00000000 -0.00000000 -0.14820000 0.18560000 -0.00000000 0.18560000 0.04510000 0.00005774 0.00000000 0.26247804 0.05527682 0.00000000 0.17762522 +Ti 1.47700000 0.85300000 9.65000000 -0.08800000 -0.10820000 -0.15860000 -0.10820000 0.04130000 -0.08910000 -0.15860000 -0.08910000 0.04670000 -0.00000000 -0.15301791 -0.12600643 0.05719559 -0.22429427 -0.09142891 +Ti 4.43200000 0.85300000 9.65000000 -0.08800000 0.10820000 0.15860000 0.10820000 0.04130000 -0.08910000 0.15860000 -0.08910000 0.04670000 -0.00000000 0.15301791 -0.12600643 0.05719559 0.22429427 -0.09142891 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00130000 -0.00000000 0.00000000 -0.00000000 0.00560000 -0.00160000 0.00000000 -0.00160000 -0.00430000 0.00000000 0.00000000 -0.00226274 -0.00526640 0.00000000 -0.00487904 +Li 0.00000000 1.70600000 12.06200000 -0.01120000 0.00000000 0.00000000 0.00000000 0.02590000 -0.01400000 0.00000000 -0.01400000 -0.01470000 0.00000000 0.00000000 -0.01979899 -0.01800375 0.00000000 -0.02623366 +Li 2.95500000 1.70600000 6.63400000 0.00220000 0.00000000 -0.00000000 0.00000000 0.00310000 -0.00010000 -0.00000000 -0.00010000 -0.00540000 0.00005774 0.00000000 -0.00014142 -0.00657280 0.00000000 -0.00063640 +Li 2.95500000 1.70600000 3.01600000 0.01160000 0.00000000 -0.00000000 0.00000000 0.01090000 0.00030000 -0.00000000 0.00030000 -0.02260000 0.00005774 0.00000000 0.00042426 -0.02763841 0.00000000 0.00049497 +Li 0.00000000 3.41200000 11.45900000 -0.00650000 -0.00000000 0.00000000 -0.00000000 0.04160000 -0.01560000 0.00000000 -0.01560000 -0.03510000 0.00000000 0.00000000 -0.02206173 -0.04298854 0.00000000 -0.03401184 +Li 0.00000000 3.41200000 7.84000000 0.00310000 -0.00000000 -0.00000000 -0.00000000 0.00370000 0.00000000 -0.00000000 0.00000000 -0.00680000 0.00000000 0.00000000 0.00000000 -0.00832827 0.00000000 -0.00042426 +Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 -0.00000000 -0.00000000 0.00770000 0.00010000 -0.00000000 0.00010000 -0.01450000 0.00005774 0.00000000 0.00014142 -0.01771798 0.00000000 -0.00070711 +Li 2.95500000 0.00000000 0.00000000 0.00410000 0.00000000 -0.00000000 0.00000000 0.00730000 -0.00130000 -0.00000000 -0.00130000 -0.01150000 0.00005774 0.00000000 -0.00183848 -0.01404374 0.00000000 -0.00226274 +O 0.00000000 0.00000000 3.80700000 0.07440000 -0.00000000 -0.00000000 -0.00000000 0.08740000 -0.00550000 -0.00000000 -0.00550000 -0.16180000 0.00000000 0.00000000 -0.00777817 -0.19816372 0.00000000 -0.00919239 +O 0.00000000 0.00000000 10.66800000 0.03500000 -0.00000000 0.00000000 -0.00000000 0.17450000 0.06990000 0.00000000 0.06990000 -0.20940000 -0.00005774 0.00000000 0.09885353 -0.25650240 0.00000000 -0.09864140 +O 2.95500000 1.70600000 8.63200000 0.02090000 -0.00000000 -0.00000000 -0.00000000 0.03630000 0.02030000 -0.00000000 0.02030000 -0.05720000 0.00000000 0.00000000 0.02870854 -0.07005541 0.00000000 -0.01088944 +O 2.95500000 1.70600000 1.01800000 0.09910000 -0.00000000 0.00000000 -0.00000000 -0.04470000 -0.14770000 0.00000000 -0.14770000 -0.05430000 -0.00005774 0.00000000 -0.20887934 -0.06654447 0.00000000 0.10168196 +O 0.00000000 3.41200000 13.45700000 -0.01250000 0.00000000 -0.00000000 0.00000000 -0.00720000 -0.02950000 -0.00000000 -0.02950000 0.01970000 -0.00000000 0.00000000 -0.04171930 0.02412747 0.00000000 -0.00374767 +O 0.00000000 3.41200000 5.84300000 0.02500000 0.00000000 -0.00000000 0.00000000 0.03530000 -0.00070000 -0.00000000 -0.00070000 -0.06030000 0.00000000 0.00000000 -0.00098995 -0.07385212 0.00000000 -0.00728320 +O -1.32400000 4.17600000 1.14400000 -0.15780000 -0.07150000 0.14790000 -0.07150000 0.11720000 0.01180000 0.14790000 0.01180000 0.04060000 0.00000000 -0.10111627 0.01668772 0.04972464 0.20916219 -0.19445436 +O 0.00000000 1.88300000 1.14400000 0.01830000 0.00000000 0.00000000 0.00000000 -0.08970000 0.06250000 0.00000000 0.06250000 0.07140000 -0.00000000 0.00000000 0.08838835 0.08744678 0.00000000 0.07636753 +O 1.32400000 4.17600000 1.14400000 -0.15780000 0.07150000 -0.14790000 0.07150000 0.11720000 0.01180000 -0.14790000 0.01180000 0.04060000 0.00000000 0.10111627 0.01668772 0.04972464 -0.20916219 -0.19445436 +O 4.27800000 0.94200000 13.33100000 -0.15560000 0.01660000 0.08370000 0.01660000 0.07930000 -0.07330000 0.08370000 -0.07330000 0.07620000 0.00005774 0.02347595 -0.10366185 0.09336638 0.11836968 -0.16609938 +O 1.63100000 0.94200000 13.33100000 -0.15560000 -0.01660000 -0.08370000 -0.01660000 0.07930000 -0.07330000 -0.08370000 -0.07330000 0.07620000 0.00005774 -0.02347595 -0.10366185 0.09336638 -0.11836968 -0.16609938 +O 2.95500000 3.23400000 13.33100000 0.05870000 0.00000000 -0.00000000 0.00000000 -0.13410000 0.09210000 -0.00000000 0.09210000 0.07540000 -0.00000000 0.00000000 0.13024907 0.09234576 0.00000000 0.13633019 +O 4.58600000 0.76400000 5.96800000 -0.12630000 0.12770000 -0.08970000 0.12770000 0.03150000 0.05650000 -0.08970000 0.05650000 0.09480000 0.00000000 0.18059507 0.07990307 0.11610581 -0.12685496 -0.11158145 +O 2.95500000 3.58900000 5.96800000 0.09320000 -0.00000000 0.00000000 -0.00000000 -0.18890000 -0.10420000 0.00000000 -0.10420000 0.09580000 -0.00005774 0.00000000 -0.14736105 0.11728973 0.00000000 0.19947482 +O 1.32400000 0.76400000 5.96800000 -0.12630000 -0.12770000 0.08970000 -0.12770000 0.03150000 0.05650000 0.08970000 0.05650000 0.09480000 0.00000000 -0.18059507 0.07990307 0.11610581 0.12685496 -0.11158145 +O 1.32400000 2.64800000 3.68100000 -0.02380000 0.05020000 -0.01720000 0.05020000 0.05800000 0.03700000 -0.01720000 0.03700000 -0.03420000 0.00000000 0.07099352 0.05232590 -0.04188627 -0.02432447 -0.05784133 +O -1.32400000 2.64800000 3.68100000 -0.02380000 -0.05020000 0.01720000 -0.05020000 0.05800000 0.03700000 0.01720000 0.03700000 -0.03420000 0.00000000 -0.07099352 0.05232590 -0.04188627 0.02432447 -0.05784133 +O 0.00000000 4.94000000 3.68100000 0.06760000 -0.00000000 -0.00000000 -0.00000000 -0.02710000 -0.02290000 0.00000000 -0.02290000 -0.04050000 0.00000000 0.00000000 -0.03238549 -0.04960217 0.00000000 0.06696301 +O 1.63100000 2.47000000 10.79300000 0.04760000 0.09260000 -0.09950000 0.09260000 0.00300000 -0.00540000 -0.09950000 -0.00540000 -0.05060000 0.00000000 0.13095618 -0.00763675 -0.06197209 -0.14071425 0.03153696 +O 2.95500000 0.17700000 10.79300000 0.06080000 0.00000000 0.00000000 0.00000000 -0.00850000 -0.02880000 0.00000000 -0.02880000 -0.05230000 0.00000000 0.00000000 -0.04072935 -0.06405416 0.00000000 0.04900250 +O 4.27800000 2.47000000 10.79300000 0.04760000 -0.09260000 0.09950000 -0.09260000 0.00300000 -0.00540000 0.09950000 -0.00540000 -0.05060000 0.00000000 -0.13095618 -0.00763675 -0.06197209 0.14071425 0.03153696 +O -1.63100000 4.35300000 8.50600000 -0.12320000 0.13310000 -0.09280000 0.13310000 0.03180000 0.04390000 -0.09280000 0.04390000 0.09140000 -0.00000000 0.18823183 0.06208398 0.11194168 -0.13123902 -0.10960155 +O 1.63100000 4.35300000 8.50600000 -0.12320000 -0.13310000 0.09280000 -0.13310000 0.03180000 0.04390000 0.09280000 0.04390000 0.09140000 -0.00000000 -0.18823183 0.06208398 0.11194168 0.13123902 -0.10960155 +O 0.00000000 1.52800000 8.50600000 0.08050000 0.00000000 -0.00000000 -0.00000000 -0.18480000 -0.06910000 -0.00000000 -0.06910000 0.10430000 -0.00000000 0.00000000 -0.09772216 0.12774089 0.00000000 0.18759543 +Ti 2.95500000 1.70600000 12.06200000 0.12850000 0.00000000 0.00000000 0.00000000 0.14690000 0.03330000 0.00000000 0.03330000 -0.27540000 0.00000000 0.00000000 0.04709331 -0.33729474 0.00000000 -0.01301076 +Ti 0.00000000 3.41200000 2.41200000 0.07920000 0.00000000 -0.00000000 0.00000000 0.16670000 0.08370000 -0.00000000 0.08370000 -0.24600000 0.00005774 0.00000000 0.11836968 -0.30124641 0.00000000 -0.06187184 +Ti -1.47700000 2.55900000 0.00000000 -0.08610000 -0.22300000 0.00750000 -0.22300000 -0.04420000 -0.07580000 0.00750000 -0.07580000 0.13030000 -0.00000000 -0.31536962 -0.10719739 0.15958426 0.01060660 -0.02962777 +Ti 1.47700000 2.55900000 0.00000000 -0.08610000 0.22300000 -0.00750000 0.22300000 -0.04420000 -0.07580000 -0.00750000 -0.07580000 0.13030000 -0.00000000 0.31536962 -0.10719739 0.15958426 -0.01060660 -0.02962777 +Ti 0.00000000 1.70600000 4.82500000 0.08040000 0.00000000 0.00000000 0.00000000 -0.12460000 0.21000000 0.00000000 0.21000000 0.04410000 0.00005774 0.00000000 0.29698485 0.05405207 0.00000000 0.14495689 +Ti 1.47700000 4.26500000 4.82500000 -0.07600000 -0.08870000 -0.18020000 -0.08870000 0.03090000 -0.10140000 -0.18020000 -0.10140000 0.04500000 0.00005774 -0.12544074 -0.14340126 0.05515434 -0.25484128 -0.07558971 +Ti -1.47700000 4.26500000 4.82500000 -0.07600000 0.08870000 0.18020000 0.08870000 0.03090000 -0.10140000 0.18020000 -0.10140000 0.04500000 0.00005774 0.12544074 -0.14340126 0.05515434 0.25484128 -0.07558971 +Ti 2.95500000 3.41200000 9.65000000 0.08990000 0.00000000 -0.00000000 0.00000000 -0.13000000 0.18400000 -0.00000000 0.18400000 0.04010000 -0.00000000 0.00000000 0.26021530 0.04911227 0.00000000 0.15549278 +Ti 1.47700000 0.85300000 9.65000000 -0.09210000 -0.09010000 -0.18750000 -0.09010000 0.04690000 -0.07960000 -0.18750000 -0.07960000 0.04520000 0.00000000 -0.12742064 -0.11257140 0.05535847 -0.26516504 -0.09828784 +Ti 4.43200000 0.85300000 9.65000000 -0.09210000 0.09010000 0.18750000 0.09010000 0.04690000 -0.07960000 0.18750000 -0.07960000 0.04520000 0.00000000 0.12742064 -0.11257140 0.05535847 0.26516504 -0.09828784 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00110000 0.00010000 -0.00000000 0.00010000 0.00490000 -0.00160000 -0.00000000 -0.00160000 -0.00380000 0.00000000 0.00014142 -0.00226274 -0.00465403 0.00000000 -0.00424264 +Li -1.47700000 4.26500000 12.06200000 -0.00170000 0.00330000 0.00140000 0.00330000 0.00880000 -0.00410000 0.00140000 -0.00410000 -0.00710000 0.00000000 0.00466690 -0.00579828 -0.00869569 0.00197990 -0.00742462 +Li 2.95500000 1.70600000 6.63400000 0.00280000 -0.00000000 -0.00010000 -0.00000000 0.00280000 -0.00000000 -0.00010000 -0.00000000 -0.00570000 0.00005774 0.00000000 0.00000000 -0.00694022 -0.00014142 0.00000000 +Li 2.95500000 1.70600000 3.01600000 0.01150000 -0.00000000 -0.00010000 -0.00000000 0.00990000 0.00030000 -0.00010000 0.00030000 -0.02140000 0.00000000 0.00000000 0.00042426 -0.02620954 -0.00014142 0.00113137 +Li 0.00000000 1.70600000 12.06200000 0.00620000 -0.00380000 0.00130000 -0.00380000 -0.00090000 0.00480000 0.00130000 0.00480000 -0.00520000 -0.00005774 -0.00537401 0.00678823 -0.00640950 0.00183848 0.00502046 +Li 0.00000000 3.41200000 7.84000000 0.00330000 0.00010000 0.00000000 0.00010000 0.00320000 0.00000000 0.00000000 0.00000000 -0.00650000 0.00000000 0.00014142 0.00000000 -0.00796084 0.00000000 0.00007071 +Li 0.00000000 0.00000000 7.23700000 0.00690000 -0.00000000 0.00010000 -0.00000000 0.00700000 0.00010000 0.00010000 0.00010000 -0.01390000 0.00000000 0.00000000 0.00014142 -0.01702395 0.00014142 -0.00007071 +Li 2.95500000 0.00000000 0.00000000 0.00480000 0.00010000 0.00190000 0.00010000 0.00520000 -0.00120000 0.00190000 -0.00120000 -0.01000000 0.00000000 0.00014142 -0.00169706 -0.01224745 0.00268701 -0.00028284 +O 0.00000000 0.00000000 3.80700000 0.07710000 -0.00100000 0.00030000 -0.00100000 0.07900000 -0.00580000 0.00030000 -0.00580000 -0.15600000 -0.00005774 -0.00141421 -0.00820244 -0.19110102 0.00042426 -0.00134350 +O 0.00000000 0.00000000 10.66800000 0.06740000 -0.05370000 0.07380000 -0.05370000 0.12790000 0.03570000 0.07380000 0.03570000 -0.19530000 0.00000000 -0.07594327 0.05048742 -0.23919267 0.10436896 -0.04277996 +O 2.95500000 1.70600000 8.63200000 0.03140000 -0.00400000 0.01840000 -0.00400000 0.03340000 0.00920000 0.01840000 0.00920000 -0.06480000 0.00000000 -0.00565685 0.01301076 -0.07936347 0.02602153 -0.00141421 +O 2.95500000 1.70600000 1.01800000 0.10720000 -0.00760000 0.01140000 -0.00760000 -0.05420000 -0.16730000 0.01140000 -0.16730000 -0.05300000 0.00000000 -0.01074802 -0.23659793 -0.06491148 0.01612203 0.11412703 +O 0.00000000 3.41200000 13.45700000 0.07230000 -0.04120000 0.08290000 -0.04120000 -0.01950000 -0.10860000 0.08290000 -0.10860000 -0.05280000 0.00000000 -0.05826560 -0.15358359 -0.06466653 0.11723830 0.06491240 +O 0.00000000 3.41200000 5.84300000 0.02680000 -0.00070000 0.00040000 -0.00070000 0.02540000 -0.00090000 0.00040000 -0.00090000 -0.05220000 0.00000000 -0.00098995 -0.00127279 -0.06393168 0.00056569 0.00098995 +O -1.32400000 4.17600000 1.14400000 -0.15590000 -0.07680000 0.15530000 -0.07680000 0.10290000 0.00890000 0.15530000 0.00890000 0.05300000 0.00000000 -0.10861160 0.01258650 0.06491148 0.21962737 -0.18299923 +O 0.00000000 1.88300000 1.14400000 0.03060000 0.00440000 -0.01090000 0.00440000 -0.10600000 0.06250000 -0.01090000 0.06250000 0.07550000 -0.00005774 0.00622254 0.08838835 0.09242741 -0.01541493 0.09659079 +O 1.32400000 4.17600000 1.14400000 -0.15960000 0.07440000 -0.15610000 0.07440000 0.11470000 0.02150000 -0.15610000 0.02150000 0.04490000 -0.00000000 0.10521749 0.03040559 0.05499104 -0.22075874 -0.19395939 +O 4.27800000 0.94200000 13.33100000 -0.14150000 0.00430000 0.08480000 0.00430000 0.08770000 -0.07240000 0.08480000 -0.07240000 0.05380000 -0.00000000 0.00608112 -0.10238906 0.06589127 0.11992531 -0.16206887 +O 1.63100000 0.94200000 13.33100000 -0.19500000 0.01440000 -0.09820000 0.01440000 0.09270000 0.03620000 -0.09820000 0.03620000 0.10230000 -0.00000000 0.02036468 0.05119453 0.12529140 -0.13887577 -0.20343462 +O 2.95500000 3.23400000 13.33100000 0.08180000 0.05950000 -0.08130000 0.05950000 -0.17040000 0.05530000 -0.08130000 0.05530000 0.08860000 -0.00000000 0.08414571 0.07820601 0.10851240 -0.11497556 0.17833233 +O 4.58600000 0.76400000 5.96800000 -0.12470000 0.12670000 -0.08680000 0.12670000 0.01970000 0.05500000 -0.08680000 0.05500000 0.10490000 0.00005774 0.17918086 0.07778175 0.12851656 -0.12275374 -0.10210622 +O 2.95500000 3.58900000 5.96800000 0.09300000 -0.00240000 -0.00030000 -0.00240000 -0.20140000 -0.10140000 -0.00030000 -0.10140000 0.10830000 0.00005774 -0.00339411 -0.14340126 0.13268069 -0.00042426 0.20817224 +O 1.32400000 0.76400000 5.96800000 -0.12430000 -0.12980000 0.08690000 -0.12980000 0.01810000 0.05540000 0.08690000 0.05540000 0.10620000 -0.00000000 -0.18356492 0.07834743 0.13006791 0.12289516 -0.10069201 +O 1.32400000 2.64800000 3.68100000 -0.02110000 0.04940000 -0.01370000 0.04940000 0.04890000 0.03610000 -0.01370000 0.03610000 -0.02790000 0.00005774 0.06986215 0.05105311 -0.03412956 -0.01937473 -0.04949747 +O -1.32400000 2.64800000 3.68100000 -0.02090000 -0.05250000 0.01350000 -0.05250000 0.04760000 0.03700000 0.01350000 0.03700000 -0.02670000 0.00000000 -0.07424621 0.05232590 -0.03270069 0.01909188 -0.04843681 +O 0.00000000 4.94000000 3.68100000 0.07030000 -0.00210000 -0.00080000 -0.00210000 -0.03770000 -0.01970000 -0.00080000 -0.01970000 -0.03260000 0.00000000 -0.00296985 -0.02786001 -0.03992668 -0.00113137 0.07636753 +O 1.63100000 2.47000000 10.79300000 -0.00830000 0.12910000 -0.06370000 0.12910000 0.02490000 -0.01250000 -0.06370000 -0.01250000 -0.01660000 0.00000000 0.18257497 -0.01767767 -0.02033076 -0.09008540 -0.02347595 +O 2.95500000 0.17700000 10.79300000 0.12530000 0.05210000 -0.07030000 0.05210000 -0.10340000 -0.04050000 -0.07030000 -0.04050000 -0.02190000 0.00000000 0.07368053 -0.05727565 -0.02682191 -0.09941921 0.16171532 +O 4.27800000 2.47000000 10.79300000 -0.07640000 -0.13110000 0.05420000 -0.13110000 0.08230000 0.06450000 0.05420000 0.06450000 -0.00580000 -0.00005774 -0.18540340 0.09121677 -0.00714435 0.07665038 -0.11221785 +O -1.63100000 4.35300000 8.50600000 -0.13430000 0.14090000 -0.09840000 0.14090000 0.01760000 0.04780000 -0.09840000 0.04780000 0.11670000 0.00000000 0.19926269 0.06759941 0.14292773 -0.13915861 -0.10740952 +O 1.63100000 4.35300000 8.50600000 -0.13740000 -0.15240000 0.11860000 -0.15240000 0.03100000 0.07390000 0.11860000 0.07390000 0.10640000 -0.00000000 -0.21552615 0.10451038 0.13031285 0.16772573 -0.11907678 +O 0.00000000 1.52800000 8.50600000 0.10110000 0.00150000 -0.01260000 0.00150000 -0.22210000 -0.10770000 -0.01260000 -0.10770000 0.12100000 -0.00000000 0.00212132 -0.15231080 0.14819413 -0.01781909 0.22853691 +Ti 2.95500000 1.70600000 12.06200000 0.11950000 0.03460000 -0.03870000 0.03460000 0.17170000 0.05850000 -0.03870000 0.05850000 -0.29120000 0.00000000 0.04893179 0.08273149 -0.35664571 -0.05473006 -0.03691097 +Ti 0.00000000 3.41200000 2.41200000 0.08000000 0.00030000 -0.00150000 0.00030000 0.16950000 0.08490000 -0.00150000 0.08490000 -0.24950000 0.00000000 0.00042426 0.12006673 -0.30557385 -0.00212132 -0.06328606 +Ti -1.47700000 2.55900000 0.00000000 -0.08310000 -0.24440000 0.01980000 -0.24440000 -0.04630000 -0.09160000 0.01980000 -0.09160000 0.12940000 -0.00000000 -0.34563379 -0.12954196 0.15848199 0.02800143 -0.02602153 +Ti 1.47700000 2.55900000 0.00000000 -0.08920000 0.24090000 -0.02530000 0.24090000 -0.03040000 -0.06870000 -0.02530000 -0.06870000 0.11960000 -0.00000000 0.34068405 -0.09715647 0.14647949 -0.03577960 -0.04157788 +Ti 0.00000000 1.70600000 4.82500000 0.08050000 -0.00010000 0.00020000 -0.00010000 -0.12530000 0.20860000 0.00020000 0.20860000 0.04480000 -0.00000000 -0.00014142 0.29500495 0.05486857 0.00028284 0.14552258 +Ti 1.47700000 4.26500000 4.82500000 -0.07630000 -0.08890000 -0.17890000 -0.08890000 0.03050000 -0.10080000 -0.17890000 -0.10080000 0.04580000 0.00000000 -0.12572359 -0.14255273 0.05609332 -0.25300281 -0.07551900 +Ti -1.47700000 4.26500000 4.82500000 -0.07640000 0.08890000 0.17880000 0.08890000 0.03060000 -0.10070000 0.17880000 -0.10070000 0.04580000 -0.00000000 0.12572359 -0.14241131 0.05609332 0.25286138 -0.07566043 +Ti 2.95500000 3.41200000 9.65000000 0.07510000 -0.01160000 0.02350000 -0.01160000 -0.12510000 0.22120000 0.02350000 0.22120000 0.05000000 -0.00000000 -0.01640488 0.31282404 0.06123724 0.03323402 0.14156278 +Ti 1.47700000 0.85300000 9.65000000 -0.08040000 -0.08840000 -0.20570000 -0.08840000 0.02510000 -0.11660000 -0.20570000 -0.11660000 0.05530000 -0.00000000 -0.12501648 -0.16489730 0.06772839 -0.29090373 -0.07459977 +Ti 4.43200000 0.85300000 9.65000000 -0.08770000 0.08100000 0.20080000 0.08100000 0.03660000 -0.08650000 0.20080000 -0.08650000 0.05110000 0.00000000 0.11455130 -0.12232947 0.06258446 0.28397408 -0.08789337 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00110000 -0.00000000 -0.00000000 -0.00000000 0.00490000 -0.00160000 -0.00000000 -0.00160000 -0.00380000 0.00000000 0.00000000 -0.00226274 -0.00465403 0.00000000 -0.00424264 +Li 1.47700000 4.26500000 12.06200000 -0.00170000 -0.00340000 -0.00130000 -0.00340000 0.00880000 -0.00410000 -0.00130000 -0.00410000 -0.00710000 0.00000000 -0.00480833 -0.00579828 -0.00869569 -0.00183848 -0.00742462 +Li 2.95500000 1.70600000 6.63400000 0.00280000 0.00000000 0.00010000 0.00000000 0.00280000 -0.00000000 0.00010000 -0.00000000 -0.00570000 0.00005774 0.00000000 0.00000000 -0.00694022 0.00014142 0.00000000 +Li 2.95500000 1.70600000 3.01600000 0.01150000 0.00000000 0.00010000 0.00000000 0.00990000 0.00030000 0.00010000 0.00030000 -0.02140000 0.00000000 0.00000000 0.00042426 -0.02620954 0.00014142 0.00113137 +Li 0.00000000 1.70600000 12.06200000 0.00620000 0.00380000 -0.00130000 0.00380000 -0.00100000 0.00480000 -0.00130000 0.00480000 -0.00520000 0.00000000 0.00537401 0.00678823 -0.00636867 -0.00183848 0.00509117 +Li 0.00000000 3.41200000 7.84000000 0.00330000 -0.00010000 -0.00000000 -0.00010000 0.00320000 0.00000000 -0.00000000 0.00000000 -0.00650000 0.00000000 -0.00014142 0.00000000 -0.00796084 0.00000000 0.00007071 +Li 0.00000000 0.00000000 7.23700000 0.00690000 0.00000000 -0.00010000 0.00000000 0.00700000 0.00010000 -0.00010000 0.00010000 -0.01390000 0.00000000 0.00000000 0.00014142 -0.01702395 -0.00014142 -0.00007071 +Li 2.95500000 0.00000000 0.00000000 0.00480000 -0.00020000 -0.00190000 -0.00020000 0.00520000 -0.00120000 -0.00190000 -0.00120000 -0.01000000 0.00000000 -0.00028284 -0.00169706 -0.01224745 -0.00268701 -0.00028284 +O 0.00000000 0.00000000 3.80700000 0.07710000 -0.00090000 -0.00010000 -0.00090000 0.07900000 -0.00580000 -0.00010000 -0.00580000 -0.15600000 -0.00005774 -0.00127279 -0.00820244 -0.19110102 -0.00014142 -0.00134350 +O 0.00000000 0.00000000 10.66800000 0.06740000 0.05180000 -0.07360000 0.05180000 0.12780000 0.03580000 -0.07360000 0.03580000 -0.19520000 0.00000000 0.07325626 0.05062885 -0.23907020 -0.10408612 -0.04270925 +O 2.95500000 1.70600000 8.63200000 0.03140000 0.00260000 -0.01810000 0.00260000 0.03340000 0.00920000 -0.01810000 0.00920000 -0.06480000 0.00000000 0.00367696 0.01301076 -0.07936347 -0.02559727 -0.00141421 +O 2.95500000 1.70600000 1.01800000 0.10720000 0.00630000 -0.01090000 0.00630000 -0.05420000 -0.16730000 -0.01090000 -0.16730000 -0.05300000 0.00000000 0.00890955 -0.23659793 -0.06491148 -0.01541493 0.11412703 +O 0.00000000 3.41200000 13.45700000 0.07230000 0.03990000 -0.08260000 0.03990000 -0.01980000 -0.10850000 -0.08260000 -0.10850000 -0.05260000 0.00005774 0.05642712 -0.15344217 -0.06438076 -0.11681404 0.06512453 +O 0.00000000 3.41200000 5.84300000 0.02680000 -0.00070000 -0.00010000 -0.00070000 0.02540000 -0.00090000 -0.00010000 -0.00090000 -0.05220000 0.00000000 -0.00098995 -0.00127279 -0.06393168 -0.00014142 0.00098995 +O -1.32400000 4.17600000 1.14400000 -0.16000000 -0.07740000 0.15620000 -0.07740000 0.11690000 0.02130000 0.15620000 0.02130000 0.04310000 -0.00000000 -0.10946013 0.03012275 0.05278650 0.22090016 -0.19579787 +O 0.00000000 1.88300000 1.14400000 0.03060000 -0.00840000 0.01080000 -0.00840000 -0.10610000 0.06250000 0.01080000 0.06250000 0.07550000 0.00000000 -0.01187939 0.08838835 0.09246824 0.01527351 0.09666150 +O 1.32400000 4.17600000 1.14400000 -0.15550000 0.07370000 -0.15510000 0.07370000 0.10080000 0.00910000 -0.15510000 0.00910000 0.05470000 0.00000000 0.10422754 0.01286934 0.06699354 -0.21934452 -0.18123147 +O 4.27800000 0.94200000 13.33100000 -0.19550000 -0.01730000 0.09820000 -0.01730000 0.09470000 0.03600000 0.09820000 0.03600000 0.10080000 -0.00000000 -0.02446589 0.05091169 0.12345428 0.13887577 -0.20520239 +O 1.63100000 0.94200000 13.33100000 -0.14100000 -0.00720000 -0.08450000 -0.00720000 0.08570000 -0.07220000 -0.08450000 -0.07220000 0.05530000 -0.00000000 -0.01018234 -0.10210622 0.06772839 -0.11950105 -0.16030111 +O 2.95500000 3.23400000 13.33100000 0.08180000 -0.06350000 0.08120000 -0.06350000 -0.17010000 0.05510000 0.08120000 0.05510000 0.08830000 -0.00000000 -0.08980256 0.07792317 0.10814497 0.11483414 0.17812020 +O 4.58600000 0.76400000 5.96800000 -0.12480000 0.12670000 -0.08690000 0.12670000 0.01980000 0.05550000 -0.08690000 0.05550000 0.10500000 -0.00000000 0.17918086 0.07848885 0.12859821 -0.12289516 -0.10224764 +O 2.95500000 3.58900000 5.96800000 0.09300000 -0.00250000 0.00050000 -0.00250000 -0.20140000 -0.10140000 0.00050000 -0.10140000 0.10830000 0.00005774 -0.00353553 -0.14340126 0.13268069 0.00070711 0.20817224 +O 1.32400000 0.76400000 5.96800000 -0.12420000 -0.12980000 0.08680000 -0.12980000 0.01800000 0.05490000 0.08680000 0.05490000 0.10610000 0.00005774 -0.18356492 0.07764032 0.12998626 0.12275374 -0.10055058 +O 1.32400000 2.64800000 3.68100000 -0.02120000 0.04950000 -0.01340000 0.04950000 0.04920000 0.03690000 -0.01340000 0.03690000 -0.02800000 0.00000000 0.07000357 0.05218448 -0.03429286 -0.01895046 -0.04978032 +O -1.32400000 2.64800000 3.68100000 -0.02070000 -0.05250000 0.01380000 -0.05250000 0.04730000 0.03620000 0.01380000 0.03620000 -0.02660000 0.00000000 -0.07424621 0.05119453 -0.03257821 0.01951615 -0.04808326 +O 0.00000000 4.94000000 3.68100000 0.07030000 -0.00270000 0.00090000 -0.00270000 -0.03770000 -0.01970000 0.00090000 -0.01970000 -0.03260000 0.00000000 -0.00381838 -0.02786001 -0.03992668 0.00127279 0.07636753 +O 1.63100000 2.47000000 10.79300000 -0.07680000 0.12820000 -0.05410000 0.12820000 0.08370000 0.06450000 -0.05410000 0.06450000 -0.00690000 -0.00000000 0.18130218 0.09121677 -0.00845074 -0.07650895 -0.11349064 +O 2.95500000 0.17700000 10.79300000 0.12530000 -0.05690000 0.07040000 -0.05690000 -0.10310000 -0.04060000 0.07040000 -0.04060000 -0.02210000 -0.00005774 -0.08046875 -0.05741707 -0.02710769 0.09956063 0.16150319 +O 4.27800000 2.47000000 10.79300000 -0.00800000 -0.13210000 0.06390000 -0.13210000 0.02350000 -0.01250000 0.06390000 -0.01250000 -0.01560000 0.00005774 -0.18681761 -0.01767767 -0.01906520 0.09036825 -0.02227386 +O -1.63100000 4.35300000 8.50600000 -0.13790000 0.14930000 -0.11860000 0.14930000 0.03270000 0.07410000 -0.11860000 0.07410000 0.10530000 -0.00005774 0.21114208 0.10479322 0.12892481 -0.16772573 -0.12063242 +O 1.63100000 4.35300000 8.50600000 -0.13380000 -0.14400000 0.09840000 -0.14400000 0.01590000 0.04770000 0.09840000 0.04770000 0.11790000 -0.00000000 -0.20364675 0.06745799 0.14439742 0.13915861 -0.10585389 +O 0.00000000 1.52800000 8.50600000 0.10110000 -0.00650000 0.01280000 -0.00650000 -0.22210000 -0.10780000 0.01280000 -0.10780000 0.12100000 -0.00000000 -0.00919239 -0.15245222 0.14819413 0.01810193 0.22853691 +Ti 2.95500000 1.70600000 12.06200000 0.11950000 -0.03470000 0.03880000 -0.03470000 0.17180000 0.05850000 0.03880000 0.05850000 -0.29130000 0.00000000 -0.04907321 0.08273149 -0.35676818 0.05487149 -0.03698168 +Ti 0.00000000 3.41200000 2.41200000 0.08000000 -0.00040000 0.00160000 -0.00040000 0.16950000 0.08490000 0.00160000 0.08490000 -0.24950000 0.00000000 -0.00056569 0.12006673 -0.30557385 0.00226274 -0.06328606 +Ti -1.47700000 2.55900000 0.00000000 -0.08920000 -0.24100000 0.02540000 -0.24100000 -0.03040000 -0.06870000 0.02540000 -0.06870000 0.11960000 -0.00000000 -0.34082547 -0.09715647 0.14647949 0.03592102 -0.04157788 +Ti 1.47700000 2.55900000 0.00000000 -0.08310000 0.24440000 -0.01970000 0.24440000 -0.04630000 -0.09160000 -0.01970000 -0.09160000 0.12940000 -0.00000000 0.34563379 -0.12954196 0.15848199 -0.02786001 -0.02602153 +Ti 0.00000000 1.70600000 4.82500000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.12530000 0.20860000 -0.00000000 0.20860000 0.04480000 -0.00000000 0.00000000 0.29500495 0.05486857 0.00000000 0.14552258 +Ti 1.47700000 4.26500000 4.82500000 -0.07640000 -0.08900000 -0.17870000 -0.08900000 0.03060000 -0.10060000 -0.17870000 -0.10060000 0.04590000 -0.00005774 -0.12586501 -0.14226988 0.05617496 -0.25271996 -0.07566043 +Ti -1.47700000 4.26500000 4.82500000 -0.07630000 0.08890000 0.17900000 0.08890000 0.03060000 -0.10080000 0.17900000 -0.10080000 0.04580000 -0.00005774 0.12572359 -0.14255273 0.05605249 0.25314423 -0.07558971 +Ti 2.95500000 3.41200000 9.65000000 0.07510000 0.01150000 -0.02350000 0.01150000 -0.12510000 0.22120000 -0.02350000 0.22120000 0.05000000 -0.00000000 0.01626346 0.31282404 0.06123724 -0.03323402 0.14156278 +Ti 1.47700000 0.85300000 9.65000000 -0.08770000 -0.08110000 -0.20070000 -0.08110000 0.03650000 -0.08650000 -0.20070000 -0.08650000 0.05110000 0.00005774 -0.11469272 -0.12232947 0.06262529 -0.28383266 -0.08782266 +Ti 4.43200000 0.85300000 9.65000000 -0.08040000 0.08840000 0.20580000 0.08840000 0.02520000 -0.11660000 0.20580000 -0.11660000 0.05520000 -0.00000000 0.12501648 -0.16489730 0.06760592 0.29104515 -0.07467048 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00150000 0.00000000 -0.00000000 0.00000000 0.00510000 -0.00140000 -0.00000000 -0.00140000 -0.00360000 0.00000000 0.00000000 -0.00197990 -0.00440908 0.00000000 -0.00466690 +Li 0.00000000 0.00000000 12.66500000 -0.00070000 0.00000000 -0.00000000 0.00000000 0.00640000 -0.00180000 -0.00000000 -0.00180000 -0.00570000 0.00000000 0.00000000 -0.00254558 -0.00698105 0.00000000 -0.00502046 +Li 2.95500000 1.70600000 4.82500000 -0.00900000 0.00000000 -0.00000000 0.00000000 -0.00790000 0.00010000 -0.00000000 0.00010000 0.01690000 -0.00000000 0.00000000 0.00014142 0.02069819 0.00000000 -0.00077782 +Li 2.95500000 1.70600000 3.01600000 -0.00540000 0.00000000 -0.00000000 0.00000000 -0.00610000 0.00050000 -0.00000000 0.00050000 0.01150000 -0.00000000 0.00000000 0.00070711 0.01408457 0.00000000 0.00049497 +Li 0.00000000 3.41200000 11.45900000 0.01000000 -0.00000000 -0.00000000 -0.00000000 0.00940000 0.00030000 -0.00000000 0.00030000 -0.01940000 0.00000000 0.00000000 0.00042426 -0.02376005 0.00000000 0.00042426 +Li 0.00000000 3.41200000 7.84000000 0.00020000 0.00000000 -0.00000000 0.00000000 0.00110000 0.00000000 -0.00000000 0.00000000 -0.00130000 0.00000000 0.00000000 0.00000000 -0.00159217 0.00000000 -0.00063640 +Li 0.00000000 0.00000000 7.23700000 0.00590000 -0.00000000 0.00000000 -0.00000000 0.00690000 0.00000000 0.00000000 0.00000000 -0.01280000 0.00000000 0.00000000 0.00000000 -0.01567673 0.00000000 -0.00070711 +Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 0.00000000 -0.00000000 0.00570000 -0.00170000 0.00000000 -0.00170000 -0.01150000 0.00000000 0.00000000 -0.00240416 -0.01408457 0.00000000 0.00007071 +O 0.00000000 0.00000000 3.80700000 0.10130000 0.00000000 -0.00000000 0.00000000 0.11440000 -0.00590000 -0.00000000 -0.00590000 -0.21570000 0.00000000 0.00000000 -0.00834386 -0.26417747 0.00000000 -0.00926310 +O 0.00000000 0.00000000 10.66800000 0.07770000 0.00000000 0.00000000 0.00000000 0.09110000 -0.00660000 0.00000000 -0.00660000 -0.16880000 0.00000000 0.00000000 -0.00933381 -0.20673693 0.00000000 -0.00947523 +O 2.95500000 1.70600000 8.63200000 0.01540000 0.00000000 -0.00000000 0.00000000 0.02560000 -0.00100000 -0.00000000 -0.00100000 -0.04100000 0.00000000 0.00000000 -0.00141421 -0.05021454 0.00000000 -0.00721249 +O 2.95500000 1.70600000 1.01800000 0.10070000 -0.00000000 0.00000000 -0.00000000 -0.05640000 -0.17440000 0.00000000 -0.17440000 -0.04430000 0.00000000 0.00000000 -0.24663885 -0.05425620 0.00000000 0.11108648 +O 0.00000000 3.41200000 13.45700000 0.08340000 0.00000000 -0.00000000 0.00000000 -0.04510000 -0.17380000 -0.00000000 -0.17380000 -0.03830000 0.00000000 0.00000000 -0.24579032 -0.04690773 0.00000000 0.09086322 +O 0.00000000 3.41200000 5.84300000 0.01210000 0.00000000 -0.00000000 0.00000000 0.02250000 -0.00110000 -0.00000000 -0.00110000 -0.03460000 0.00000000 0.00000000 -0.00155563 -0.04237617 0.00000000 -0.00735391 +O -1.32400000 4.17600000 1.14400000 -0.15480000 -0.08910000 0.16710000 -0.08910000 0.10510000 0.00240000 0.16710000 0.00240000 0.04970000 -0.00000000 -0.12600643 0.00339411 0.06086982 0.23631509 -0.18377705 +O 0.00000000 1.88300000 1.14400000 0.00980000 0.00000000 -0.00000000 0.00000000 -0.07390000 0.08330000 -0.00000000 0.08330000 0.06410000 -0.00000000 0.00000000 0.11780399 0.07850615 0.00000000 0.05918484 +O 1.32400000 4.17600000 1.14400000 -0.15480000 0.08910000 -0.16710000 0.08910000 0.10510000 0.00240000 -0.16710000 0.00240000 0.04970000 -0.00000000 0.12600643 0.00339411 0.06086982 -0.23631509 -0.18377705 +O 4.27800000 0.94200000 13.33100000 -0.14670000 -0.08680000 0.17350000 -0.08680000 0.11800000 0.02060000 0.17350000 0.02060000 0.02870000 0.00000000 -0.12275374 0.02913280 0.03515018 0.24536605 -0.18717116 +O 1.63100000 0.94200000 13.33100000 -0.14670000 0.08680000 -0.17350000 0.08680000 0.11800000 0.02060000 -0.17350000 0.02060000 0.02870000 0.00000000 0.12275374 0.02913280 0.03515018 -0.24536605 -0.18717116 +O 2.95500000 3.23400000 13.33100000 0.03010000 0.00000000 -0.00000000 0.00000000 -0.08910000 0.06180000 -0.00000000 0.06180000 0.05900000 0.00000000 0.00000000 0.08739840 0.07225995 0.00000000 0.08428713 +O 4.58600000 0.76400000 5.96800000 -0.12280000 0.06940000 0.01710000 0.06940000 -0.03300000 -0.00380000 0.01710000 -0.00380000 0.15580000 -0.00000000 0.09814642 -0.00537401 0.19081525 0.02418305 -0.06349819 +O 2.95500000 3.58900000 5.96800000 -0.00600000 0.00000000 0.00000000 0.00000000 -0.15180000 0.01830000 0.00000000 0.01830000 0.15780000 -0.00000000 0.00000000 0.02588011 0.19326474 0.00000000 0.10309617 +O 1.32400000 0.76400000 5.96800000 -0.12280000 -0.06940000 -0.01710000 -0.06940000 -0.03300000 -0.00380000 -0.01710000 -0.00380000 0.15580000 -0.00000000 -0.09814642 -0.00537401 0.19081525 -0.02418305 -0.06349819 +O 1.32400000 2.64800000 3.68100000 0.04520000 -0.02420000 0.06790000 -0.02420000 0.04390000 -0.01120000 0.06790000 -0.01120000 -0.08900000 -0.00005774 -0.03422397 -0.01583919 -0.10904312 0.09602510 0.00091924 +O -1.32400000 2.64800000 3.68100000 0.04520000 0.02420000 -0.06790000 0.02420000 0.04390000 -0.01120000 -0.06790000 -0.01120000 -0.08900000 -0.00005774 0.03422397 -0.01583919 -0.10904312 -0.09602510 0.00091924 +O 0.00000000 4.94000000 3.68100000 0.01120000 -0.00000000 -0.00000000 -0.00000000 0.08430000 0.07320000 -0.00000000 0.07320000 -0.09540000 -0.00005774 0.00000000 0.10352043 -0.11688149 0.00000000 -0.05168951 +O 1.63100000 2.47000000 10.79300000 -0.01890000 0.04830000 0.01380000 0.04830000 0.06090000 0.02540000 0.01380000 0.02540000 -0.04190000 -0.00005774 0.06830652 0.03592102 -0.05135763 0.01951615 -0.05642712 +O 2.95500000 0.17700000 10.79300000 0.06880000 0.00000000 0.00000000 0.00000000 -0.02120000 0.01080000 0.00000000 0.01080000 -0.04770000 0.00005774 0.00000000 0.01527351 -0.05837951 0.00000000 0.06363961 +O 4.27800000 2.47000000 10.79300000 -0.01890000 -0.04830000 -0.01380000 -0.04830000 0.06090000 0.02540000 -0.01380000 0.02540000 -0.04190000 -0.00005774 -0.06830652 0.03592102 -0.05135763 -0.01951615 -0.05642712 +O -1.63100000 4.35300000 8.50600000 -0.12720000 0.10680000 -0.07420000 0.10680000 0.00650000 0.04830000 -0.07420000 0.04830000 0.12070000 -0.00000000 0.15103801 0.06830652 0.14782671 -0.10493465 -0.09454018 +O 1.63100000 4.35300000 8.50600000 -0.12720000 -0.10680000 0.07420000 -0.10680000 0.00650000 0.04830000 0.07420000 0.04830000 0.12070000 -0.00000000 -0.15103801 0.06830652 0.14782671 0.10493465 -0.09454018 +O 0.00000000 1.52800000 8.50600000 0.05600000 0.00000000 -0.00000000 0.00000000 -0.17810000 -0.08700000 -0.00000000 -0.08700000 0.12210000 0.00000000 0.00000000 -0.12303658 0.14954135 0.00000000 0.16553370 +Ti 2.95500000 1.70600000 12.06200000 0.08370000 -0.00000000 -0.00000000 -0.00000000 0.16840000 0.08770000 -0.00000000 0.08770000 -0.25210000 0.00000000 0.00000000 0.12402653 -0.30875818 0.00000000 -0.05989194 +Ti 0.00000000 3.41200000 2.41200000 0.07030000 0.00000000 -0.00000000 0.00000000 0.17070000 0.08150000 -0.00000000 0.08150000 -0.24100000 0.00000000 0.00000000 0.11525841 -0.29516351 0.00000000 -0.07099352 +Ti -1.47700000 2.55900000 0.00000000 -0.08030000 -0.24840000 0.04920000 -0.24840000 -0.03860000 -0.07620000 0.04920000 -0.07620000 0.11890000 -0.00000000 -0.35129065 -0.10776307 0.14562217 0.06957931 -0.02948635 +Ti 1.47700000 2.55900000 0.00000000 -0.08030000 0.24840000 -0.04920000 0.24840000 -0.03860000 -0.07620000 -0.04920000 -0.07620000 0.11890000 -0.00000000 0.35129065 -0.10776307 0.14562217 -0.06957931 -0.02948635 +Ti 0.00000000 1.70600000 4.82500000 0.11030000 0.00000000 -0.00000000 0.00000000 -0.16040000 0.18740000 -0.00000000 0.18740000 0.05020000 -0.00005774 0.00000000 0.26502362 0.06144137 0.00000000 0.19141381 +Ti 1.47700000 4.26500000 4.82500000 -0.09530000 -0.11730000 -0.16050000 -0.11730000 0.04440000 -0.09030000 -0.16050000 -0.09030000 0.05090000 -0.00000000 -0.16588725 -0.12770348 0.06233951 -0.22698128 -0.09878282 +Ti -1.47700000 4.26500000 4.82500000 -0.09530000 0.11730000 0.16050000 0.11730000 0.04440000 -0.09030000 0.16050000 -0.09030000 0.05090000 -0.00000000 0.16588725 -0.12770348 0.06233951 0.22698128 -0.09878282 +Ti 2.95500000 3.41200000 9.65000000 0.10290000 0.00000000 0.00000000 0.00000000 -0.14830000 0.18380000 0.00000000 0.18380000 0.04540000 -0.00000000 0.00000000 0.25993245 0.05560342 0.00000000 0.17762522 +Ti 1.47700000 0.85300000 9.65000000 -0.08800000 -0.10810000 -0.15700000 -0.10810000 0.04130000 -0.08770000 -0.15700000 -0.08770000 0.04670000 -0.00000000 -0.15287649 -0.12402653 0.05719559 -0.22203153 -0.09142891 +Ti 4.43200000 0.85300000 9.65000000 -0.08800000 0.10810000 0.15700000 0.10810000 0.04130000 -0.08770000 0.15700000 -0.08770000 0.04670000 -0.00000000 0.15287649 -0.12402653 0.05719559 0.22203153 -0.09142891 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00100000 0.00000000 0.00010000 0.00000000 0.00510000 -0.00160000 0.00010000 -0.00160000 -0.00410000 0.00000000 0.00000000 -0.00226274 -0.00502145 0.00014142 -0.00431335 +Li 0.00000000 0.00000000 12.66500000 -0.00220000 0.00000000 0.00010000 0.00000000 0.00370000 -0.00140000 0.00010000 -0.00140000 -0.00150000 0.00000000 0.00000000 -0.00197990 -0.00183712 0.00014142 -0.00417193 +Li -1.47700000 2.55900000 7.23700000 0.01910000 0.02290000 0.00490000 0.02290000 -0.00730000 0.00280000 0.00490000 0.00280000 -0.01190000 0.00005774 0.03238549 0.00395980 -0.01453364 0.00692965 0.01866762 +Li 2.95500000 1.70600000 3.01600000 0.01130000 -0.00010000 -0.00000000 -0.00010000 0.00990000 0.00030000 -0.00000000 0.00030000 -0.02120000 0.00000000 -0.00014142 0.00042426 -0.02596459 0.00000000 0.00098995 +Li 0.00000000 3.41200000 11.45900000 0.01200000 -0.00010000 -0.00000000 -0.00010000 0.01050000 0.00040000 -0.00000000 0.00040000 -0.02250000 0.00000000 -0.00014142 0.00056569 -0.02755676 0.00000000 0.00106066 +Li 0.00000000 3.41200000 7.84000000 0.02180000 0.02130000 0.01540000 0.02130000 -0.00270000 0.00890000 0.01540000 0.00890000 -0.01910000 0.00000000 0.03012275 0.01258650 -0.02339263 0.02177889 0.01732412 +Li 0.00000000 0.00000000 7.23700000 0.00560000 -0.00340000 0.00100000 -0.00340000 0.00970000 0.00060000 0.00100000 0.00060000 -0.01520000 -0.00005774 -0.00480833 0.00084853 -0.01865695 0.00141421 -0.00289914 +Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00010000 0.00000000 -0.00010000 0.00480000 -0.00180000 0.00000000 -0.00180000 -0.01070000 0.00005774 -0.00014142 -0.00254558 -0.01306395 0.00000000 0.00070711 +O 0.00000000 0.00000000 3.80700000 0.08850000 0.00280000 -0.02010000 0.00280000 0.08610000 -0.01770000 -0.02010000 -0.01770000 -0.17470000 0.00005774 0.00395980 -0.02503158 -0.21392210 -0.02842569 0.00169706 +O 0.00000000 0.00000000 10.66800000 0.07620000 0.00170000 -0.02040000 0.00170000 0.07480000 -0.01690000 -0.02040000 -0.01690000 -0.15100000 0.00000000 0.00240416 -0.02390021 -0.18493648 -0.02884996 0.00098995 +O 2.95500000 1.70600000 8.63200000 0.07920000 0.05510000 -0.05980000 0.05510000 0.01330000 -0.03590000 -0.05980000 -0.03590000 -0.09250000 0.00000000 0.07792317 -0.05077027 -0.11328890 -0.08456997 0.04659834 +O 2.95500000 1.70600000 1.01800000 0.08930000 -0.00080000 -0.00030000 -0.00080000 -0.05200000 -0.17110000 -0.00030000 -0.17110000 -0.03730000 0.00000000 -0.00113137 -0.24197194 -0.04568298 -0.00042426 0.09991419 +O 0.00000000 3.41200000 13.45700000 0.10390000 -0.00080000 -0.00030000 -0.00080000 -0.05930000 -0.17410000 -0.00030000 -0.17410000 -0.04470000 0.00005774 -0.00113137 -0.24621458 -0.05470527 -0.00042426 0.11539983 +O 0.00000000 3.41200000 5.84300000 0.03910000 0.05970000 -0.09210000 0.05970000 -0.03180000 -0.05410000 -0.09210000 -0.05410000 -0.00730000 0.00000000 0.08442855 -0.07650895 -0.00894064 -0.13024907 0.05013387 +O -1.32400000 4.17600000 1.14400000 -0.14600000 -0.08430000 0.16840000 -0.08430000 0.10670000 0.01880000 0.16840000 0.01880000 0.03930000 -0.00000000 -0.11921820 0.02658721 0.04813247 0.23815356 -0.17868588 +O 0.00000000 1.88300000 1.14400000 0.03050000 -0.00200000 0.00120000 -0.00200000 -0.09940000 0.06380000 0.00120000 0.06380000 0.06890000 -0.00000000 -0.00282843 0.09022683 0.08438492 0.00169706 0.09185317 +O 1.32400000 4.17600000 1.14400000 -0.14520000 0.08110000 -0.16810000 0.08110000 0.10460000 0.01670000 -0.16810000 0.01670000 0.04060000 0.00000000 0.11469272 0.02361737 0.04972464 -0.23772930 -0.17663527 +O 4.27800000 0.94200000 13.33100000 -0.15690000 -0.08220000 0.16420000 -0.08220000 0.10050000 0.00810000 0.16420000 0.00810000 0.05640000 0.00000000 -0.11624835 0.01145513 0.06907561 0.23221387 -0.18200929 +O 1.63100000 0.94200000 13.33100000 -0.15610000 0.07920000 -0.16420000 0.07920000 0.09880000 0.00550000 -0.16420000 0.00550000 0.05730000 -0.00000000 0.11200571 0.00777817 0.07017788 -0.23221387 -0.18024152 +O 2.95500000 3.23400000 13.33100000 0.01950000 -0.00190000 0.00130000 -0.00190000 -0.09380000 0.07380000 0.00130000 0.07380000 0.07420000 0.00005774 -0.00268701 0.10436896 0.09091689 0.00183848 0.08011520 +O 4.58600000 0.76400000 5.96800000 -0.20330000 0.15720000 -0.08510000 0.15720000 0.09410000 0.12260000 -0.08510000 0.12260000 0.10920000 -0.00000000 0.22231437 0.17338258 0.13374214 -0.12034957 -0.21029356 +O 2.95500000 3.58900000 5.96800000 0.15590000 -0.05330000 0.05950000 -0.05330000 -0.26820000 -0.13380000 0.05950000 -0.13380000 0.11230000 -0.00000000 -0.07537758 -0.18922177 0.13753885 0.08414571 0.29988399 +O 1.32400000 0.76400000 5.96800000 -0.14190000 -0.13630000 0.08880000 -0.13630000 0.00760000 0.05680000 0.08880000 0.05680000 0.13430000 -0.00000000 -0.19275731 0.08032733 0.16448324 0.12558216 -0.10571246 +O 1.32400000 2.64800000 3.68100000 -0.03400000 0.05510000 -0.02150000 0.05510000 0.05500000 0.05470000 -0.02150000 0.05470000 -0.02100000 0.00000000 0.07792317 0.07735748 -0.02571964 -0.03040559 -0.06293250 +O -1.32400000 2.64800000 3.68100000 -0.02670000 -0.05490000 0.00860000 -0.05490000 0.04430000 0.03660000 0.00860000 0.03660000 -0.01750000 -0.00005774 -0.07764032 0.05176022 -0.02147386 0.01216224 -0.05020458 +O 0.00000000 4.94000000 3.68100000 0.07620000 -0.00820000 0.01000000 -0.00820000 -0.05070000 -0.03480000 0.01000000 -0.03480000 -0.02550000 0.00000000 -0.01159655 -0.04921463 -0.03123099 0.01414214 0.08973185 +O 1.63100000 2.47000000 10.79300000 -0.03020000 0.05260000 -0.01170000 0.05260000 0.05780000 0.04570000 -0.01170000 0.04570000 -0.02760000 0.00000000 0.07438763 0.06462956 -0.03380296 -0.01654630 -0.06222540 +O 2.95500000 0.17700000 10.79300000 0.07900000 -0.00800000 0.01120000 -0.00800000 -0.04550000 -0.02420000 0.01120000 -0.02420000 -0.03350000 0.00000000 -0.01131371 -0.03422397 -0.04102895 0.01583919 0.08803479 +O 4.27800000 2.47000000 10.79300000 -0.02180000 -0.05040000 -0.00140000 -0.05040000 0.04640000 0.02610000 -0.00140000 0.02610000 -0.02460000 0.00000000 -0.07127636 0.03691097 -0.03012872 -0.00197990 -0.04822468 +O -1.63100000 4.35300000 8.50600000 -0.17170000 0.08870000 -0.09590000 0.08870000 0.10600000 0.13540000 -0.09590000 0.13540000 0.06570000 0.00000000 0.12544074 0.19148452 0.08046574 -0.13562308 -0.19636355 +O 1.63100000 4.35300000 8.50600000 -0.10870000 -0.10600000 0.07390000 -0.10600000 0.00570000 0.04820000 0.07390000 0.04820000 0.10300000 0.00000000 -0.14990664 0.06816509 0.12614872 0.10451038 -0.08089302 +O 0.00000000 1.52800000 8.50600000 0.11290000 -0.07910000 0.06510000 -0.07910000 -0.18220000 -0.14970000 0.06510000 -0.14970000 0.06930000 -0.00000000 -0.11186429 -0.21170777 0.08487482 0.09206530 0.20866721 +Ti 2.95500000 1.70600000 12.06200000 0.07560000 0.00040000 0.00120000 0.00040000 0.17060000 0.08300000 0.00120000 0.08300000 -0.24610000 -0.00005774 0.00056569 0.11737973 -0.30145054 0.00169706 -0.06717514 +Ti 0.00000000 3.41200000 2.41200000 0.08150000 -0.00000000 0.00100000 -0.00000000 0.16970000 0.08700000 0.00100000 0.08700000 -0.25130000 0.00005774 0.00000000 0.12303658 -0.30773756 0.00141421 -0.06236682 +Ti -1.47700000 2.55900000 0.00000000 -0.08120000 -0.24910000 0.04890000 -0.24910000 -0.03760000 -0.07580000 0.04890000 -0.07580000 0.11880000 -0.00000000 -0.35228060 -0.10719739 0.14549969 0.06915504 -0.03082986 +Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24900000 -0.04850000 0.24900000 -0.03790000 -0.07600000 -0.04850000 -0.07600000 0.11890000 -0.00000000 0.35213918 -0.10748023 0.14562217 -0.06858936 -0.03047630 +Ti 0.00000000 1.70600000 4.82500000 0.06870000 0.01320000 -0.02310000 0.01320000 -0.12830000 0.24740000 -0.02310000 0.24740000 0.05960000 0.00000000 0.01866762 0.34987644 0.07299479 -0.03266833 0.13930004 +Ti 1.47700000 4.26500000 4.82500000 -0.07910000 -0.08130000 -0.20410000 -0.08130000 0.01890000 -0.11540000 -0.20410000 -0.11540000 0.06020000 -0.00000000 -0.11497556 -0.16320025 0.07372964 -0.28864099 -0.06929646 +Ti -1.47700000 4.26500000 4.82500000 -0.07010000 0.09130000 0.20060000 0.09130000 0.00920000 -0.13970000 0.20060000 -0.13970000 0.06100000 -0.00005774 0.12911770 -0.19756563 0.07466861 0.28369124 -0.05607357 +Ti 2.95500000 3.41200000 9.65000000 0.08850000 0.01500000 -0.02280000 0.01500000 -0.12990000 0.20370000 -0.02280000 0.20370000 0.04150000 -0.00005774 0.02121320 0.28807530 0.05078609 -0.03224407 0.15443212 +Ti 1.47700000 0.85300000 9.65000000 -0.07760000 -0.09500000 -0.15970000 -0.09500000 0.03590000 -0.09020000 -0.15970000 -0.09020000 0.04170000 -0.00000000 -0.13435029 -0.12756206 0.05107186 -0.22584991 -0.08025662 +Ti 4.43200000 0.85300000 9.65000000 -0.06510000 0.10240000 0.16340000 0.10240000 0.02250000 -0.11830000 0.16340000 -0.11830000 0.04250000 0.00005774 0.14481547 -0.16730146 0.05209248 0.23108250 -0.06194255 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00100000 -0.00000000 -0.00010000 -0.00000000 0.00510000 -0.00160000 -0.00010000 -0.00160000 -0.00410000 0.00000000 0.00000000 -0.00226274 -0.00502145 -0.00014142 -0.00431335 +Li 0.00000000 0.00000000 12.66500000 -0.00220000 -0.00000000 -0.00010000 -0.00000000 0.00370000 -0.00140000 -0.00010000 -0.00140000 -0.00150000 0.00000000 0.00000000 -0.00197990 -0.00183712 -0.00014142 -0.00417193 +Li 1.47700000 2.55900000 7.23700000 0.01920000 -0.02290000 -0.00480000 -0.02290000 -0.00730000 0.00280000 -0.00480000 0.00280000 -0.01190000 0.00000000 -0.03238549 0.00395980 -0.01457446 -0.00678823 0.01873833 +Li 2.95500000 1.70600000 3.01600000 0.01130000 0.00010000 0.00000000 0.00010000 0.00990000 0.00030000 0.00000000 0.00030000 -0.02120000 0.00000000 0.00014142 0.00042426 -0.02596459 0.00000000 0.00098995 +Li 0.00000000 3.41200000 11.45900000 0.01200000 0.00010000 0.00000000 0.00010000 0.01050000 0.00040000 0.00000000 0.00040000 -0.02250000 0.00000000 0.00014142 0.00056569 -0.02755676 0.00000000 0.00106066 +Li 0.00000000 3.41200000 7.84000000 0.02180000 -0.02130000 -0.01540000 -0.02130000 -0.00270000 0.00890000 -0.01540000 0.00890000 -0.01910000 0.00000000 -0.03012275 0.01258650 -0.02339263 -0.02177889 0.01732412 +Li 0.00000000 0.00000000 7.23700000 0.00560000 0.00340000 -0.00100000 0.00340000 0.00960000 0.00060000 -0.00100000 0.00060000 -0.01520000 0.00000000 0.00480833 0.00084853 -0.01861612 -0.00141421 -0.00282843 +Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 -0.00000000 -0.00000000 0.00480000 -0.00180000 -0.00000000 -0.00180000 -0.01070000 0.00005774 0.00000000 -0.00254558 -0.01306395 0.00000000 0.00070711 +O 0.00000000 0.00000000 3.80700000 0.08860000 -0.00480000 0.02040000 -0.00480000 0.08610000 -0.01770000 0.02040000 -0.01770000 -0.17470000 0.00000000 -0.00678823 -0.02503158 -0.21396293 0.02884996 0.00176777 +O 0.00000000 0.00000000 10.66800000 0.07620000 -0.00360000 0.02070000 -0.00360000 0.07480000 -0.01690000 0.02070000 -0.01690000 -0.15100000 0.00000000 -0.00509117 -0.02390021 -0.18493648 0.02927422 0.00098995 +O 2.95500000 1.70600000 8.63200000 0.07920000 -0.05650000 0.06010000 -0.05650000 0.01340000 -0.03600000 0.06010000 -0.03600000 -0.09260000 0.00000000 -0.07990307 -0.05091169 -0.11341138 0.08499424 0.04652763 +O 2.95500000 1.70600000 1.01800000 0.08930000 -0.00050000 0.00080000 -0.00050000 -0.05200000 -0.17110000 0.00080000 -0.17110000 -0.03730000 0.00000000 -0.00070711 -0.24197194 -0.04568298 0.00113137 0.09991419 +O 0.00000000 3.41200000 13.45700000 0.10390000 -0.00050000 0.00080000 -0.00050000 -0.05930000 -0.17410000 0.00080000 -0.17410000 -0.04470000 0.00005774 -0.00070711 -0.24621458 -0.05470527 0.00113137 0.11539983 +O 0.00000000 3.41200000 5.84300000 0.03910000 -0.06090000 0.09240000 -0.06090000 -0.03170000 -0.05420000 0.09240000 -0.05420000 -0.00740000 0.00000000 -0.08612561 -0.07665038 -0.00906311 0.13067333 0.05006316 +O -1.32400000 4.17600000 1.14400000 -0.14560000 -0.08420000 0.16820000 -0.08420000 0.10680000 0.01640000 0.16820000 0.01640000 0.03880000 -0.00000000 -0.11907678 0.02319310 0.04752010 0.23787072 -0.17847375 +O 0.00000000 1.88300000 1.14400000 0.03050000 -0.00190000 -0.00130000 -0.00190000 -0.09940000 0.06380000 -0.00130000 0.06380000 0.06890000 -0.00000000 -0.00268701 0.09022683 0.08438492 -0.00183848 0.09185317 +O 1.32400000 4.17600000 1.14400000 -0.14560000 0.08130000 -0.16830000 0.08130000 0.10450000 0.01900000 -0.16830000 0.01900000 0.04110000 0.00000000 0.11497556 0.02687006 0.05033701 -0.23801214 -0.17684741 +O 4.27800000 0.94200000 13.33100000 -0.15650000 -0.08220000 0.16440000 -0.08220000 0.10090000 0.00520000 0.16440000 0.00520000 0.05560000 -0.00000000 -0.11624835 0.00735391 0.06809581 0.23249671 -0.18200929 +O 1.63100000 0.94200000 13.33100000 -0.15650000 0.07920000 -0.16410000 0.07920000 0.09830000 0.00830000 -0.16410000 0.00830000 0.05820000 -0.00000000 0.11200571 0.01173797 0.07128015 -0.23207245 -0.18017081 +O 2.95500000 3.23400000 13.33100000 0.01950000 -0.00210000 -0.00150000 -0.00210000 -0.09380000 0.07380000 -0.00150000 0.07380000 0.07420000 0.00005774 -0.00296985 0.10436896 0.09091689 -0.00212132 0.08011520 +O 4.58600000 0.76400000 5.96800000 -0.14250000 0.13320000 -0.08880000 0.13320000 0.00940000 0.05690000 -0.08880000 0.05690000 0.13310000 -0.00000000 0.18837325 0.08046875 0.16301354 -0.12558216 -0.10740952 +O 2.95500000 3.58900000 5.96800000 0.15580000 0.04850000 -0.05920000 0.04850000 -0.26840000 -0.13370000 -0.05920000 -0.13370000 0.11260000 0.00000000 0.06858936 -0.18908035 0.13790627 -0.08372144 0.29995470 +O 1.32400000 0.76400000 5.96800000 -0.20270000 -0.16030000 0.08500000 -0.16030000 0.09240000 0.12250000 0.08500000 0.12250000 0.11040000 -0.00005774 -0.22669843 0.17324116 0.13517101 0.12020815 -0.20866721 +O 1.32400000 2.64800000 3.68100000 -0.02710000 0.05180000 -0.00850000 0.05180000 0.04580000 0.03660000 -0.00850000 0.03660000 -0.01880000 0.00005774 0.07325626 0.05176022 -0.02298438 -0.01202082 -0.05154808 +O -1.32400000 2.64800000 3.68100000 -0.03370000 -0.05810000 0.02150000 -0.05810000 0.05350000 0.05470000 0.02150000 0.05470000 -0.01980000 0.00000000 -0.08216581 0.07735748 -0.02424995 0.03040559 -0.06165971 +O 0.00000000 4.94000000 3.68100000 0.07620000 0.00340000 -0.00990000 0.00340000 -0.05070000 -0.03480000 -0.00990000 -0.03480000 -0.02550000 0.00000000 0.00480833 -0.04921463 -0.03123099 -0.01400071 0.08973185 +O 1.63100000 2.47000000 10.79300000 -0.02220000 0.04730000 0.00150000 0.04730000 0.04800000 0.02610000 0.00150000 0.02610000 -0.02590000 0.00005774 0.06689230 0.03691097 -0.03168007 0.00212132 -0.04963890 +O 2.95500000 0.17700000 10.79300000 0.07900000 0.00320000 -0.01110000 0.00320000 -0.04550000 -0.02420000 -0.01110000 -0.02420000 -0.03350000 0.00000000 0.00452548 -0.03422397 -0.04102895 -0.01569777 0.08803479 +O 4.27800000 2.47000000 10.79300000 -0.02980000 -0.05570000 0.01170000 -0.05570000 0.05630000 0.04570000 0.01170000 0.04570000 -0.02640000 -0.00005774 -0.07877170 0.06462956 -0.03237409 0.01654630 -0.06088189 +O -1.63100000 4.35300000 8.50600000 -0.10920000 0.10280000 -0.07390000 0.10280000 0.00750000 0.04830000 -0.07390000 0.04830000 0.10160000 0.00005774 0.14538115 0.06830652 0.12447490 -0.10451038 -0.08251936 +O 1.63100000 4.35300000 8.50600000 -0.17120000 -0.09180000 0.09580000 -0.09180000 0.10430000 0.13530000 0.09580000 0.13530000 0.06690000 -0.00000000 -0.12982481 0.19134309 0.08193543 0.13548166 -0.19480792 +O 0.00000000 1.52800000 8.50600000 0.11280000 0.07430000 -0.06480000 0.07430000 -0.18230000 -0.14960000 -0.06480000 -0.14960000 0.06950000 -0.00000000 0.10507607 -0.21156635 0.08511977 -0.09164104 0.20866721 +Ti 2.95500000 1.70600000 12.06200000 0.07560000 -0.00050000 -0.00110000 -0.00050000 0.17060000 0.08300000 -0.00110000 0.08300000 -0.24610000 -0.00005774 -0.00070711 0.11737973 -0.30145054 -0.00155563 -0.06717514 +Ti 0.00000000 3.41200000 2.41200000 0.08150000 -0.00010000 -0.00090000 -0.00010000 0.16970000 0.08700000 -0.00090000 0.08700000 -0.25130000 0.00005774 -0.00014142 0.12303658 -0.30773756 -0.00127279 -0.06236682 +Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24910000 0.04860000 -0.24910000 -0.03780000 -0.07610000 0.04860000 -0.07610000 0.11880000 0.00000000 -0.35228060 -0.10762165 0.14549969 0.06873078 -0.03054701 +Ti 1.47700000 2.55900000 0.00000000 -0.08120000 0.24900000 -0.04870000 0.24900000 -0.03770000 -0.07580000 -0.04870000 -0.07580000 0.11890000 -0.00000000 0.35213918 -0.10719739 0.14562217 -0.06887220 -0.03075914 +Ti 0.00000000 1.70600000 4.82500000 0.06870000 -0.01340000 0.02320000 -0.01340000 -0.12820000 0.24740000 0.02320000 0.24740000 0.05960000 -0.00005774 -0.01895046 0.34987644 0.07295397 0.03280975 0.13922933 +Ti 1.47700000 4.26500000 4.82500000 -0.07010000 -0.09140000 -0.20050000 -0.09140000 0.00910000 -0.13970000 -0.20050000 -0.13970000 0.06100000 -0.00000000 -0.12925912 -0.19756563 0.07470944 -0.28354982 -0.05600286 +Ti -1.47700000 4.26500000 4.82500000 -0.07910000 0.08120000 0.20420000 0.08120000 0.01890000 -0.11540000 0.20420000 -0.11540000 0.06020000 -0.00000000 0.11483414 -0.16320025 0.07372964 0.28878241 -0.06929646 +Ti 2.95500000 3.41200000 9.65000000 0.08850000 -0.01510000 0.02290000 -0.01510000 -0.12990000 0.20370000 0.02290000 0.20370000 0.04140000 -0.00000000 -0.02135462 0.28807530 0.05070444 0.03238549 0.15443212 +Ti 1.47700000 0.85300000 9.65000000 -0.06510000 -0.10240000 -0.16330000 -0.10240000 0.02250000 -0.11830000 -0.16330000 -0.11830000 0.04260000 0.00000000 -0.14481547 -0.16730146 0.05217413 -0.23094107 -0.06194255 +Ti 4.43200000 0.85300000 9.65000000 -0.07760000 0.09490000 0.15980000 0.09490000 0.03600000 -0.09020000 0.15980000 -0.09020000 0.04170000 -0.00005774 0.13420887 -0.12756206 0.05103104 0.22599133 -0.08032733 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00110000 0.00000000 0.00000000 0.00000000 0.00590000 -0.00180000 0.00000000 -0.00180000 -0.00480000 0.00000000 0.00000000 -0.00254558 -0.00587878 0.00000000 -0.00494975 +Li 0.00000000 0.00000000 12.66500000 -0.00230000 0.00000000 0.00000000 0.00000000 0.00440000 -0.00150000 0.00000000 -0.00150000 -0.00210000 0.00000000 0.00000000 -0.00212132 -0.00257196 0.00000000 -0.00473762 +Li 2.95500000 0.00000000 7.23700000 -0.02060000 0.00000000 0.00000000 0.00000000 0.03310000 -0.00560000 0.00000000 -0.00560000 -0.01250000 0.00000000 0.00000000 -0.00791960 -0.01530931 0.00000000 -0.03797163 +Li 2.95500000 1.70600000 3.01600000 0.01140000 0.00000000 0.00000000 0.00000000 0.01050000 0.00030000 0.00000000 0.00030000 -0.02190000 0.00000000 0.00000000 0.00042426 -0.02682191 0.00000000 0.00063640 +Li 0.00000000 3.41200000 11.45900000 0.01200000 -0.00000000 0.00000000 -0.00000000 0.01110000 0.00040000 0.00000000 0.00040000 -0.02320000 0.00005774 0.00000000 0.00056569 -0.02837326 0.00000000 0.00063640 +Li 0.00000000 3.41200000 7.84000000 -0.01510000 -0.00000000 -0.00000000 -0.00000000 0.03490000 -0.01780000 -0.00000000 -0.01780000 -0.01980000 0.00000000 0.00000000 -0.02517300 -0.02424995 0.00000000 -0.03535534 +Li 0.00000000 0.00000000 7.23700000 0.01140000 -0.00000000 0.00000000 -0.00000000 0.00450000 -0.00110000 0.00000000 -0.00110000 -0.01590000 0.00000000 0.00000000 -0.00155563 -0.01947344 0.00000000 0.00487904 +Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 -0.00000000 -0.00000000 0.00570000 -0.00180000 -0.00000000 -0.00180000 -0.01140000 -0.00005774 0.00000000 -0.00254558 -0.01400292 0.00000000 0.00007071 +O 0.00000000 0.00000000 3.80700000 0.07980000 -0.00000000 -0.00000000 -0.00000000 0.10180000 0.01710000 -0.00000000 0.01710000 -0.18170000 0.00005774 0.00000000 0.02418305 -0.22249532 0.00000000 -0.01555635 +O 0.00000000 0.00000000 10.66800000 0.06940000 0.00000000 0.00000000 0.00000000 0.08860000 0.01840000 0.00000000 0.01840000 -0.15800000 0.00000000 0.00000000 0.02602153 -0.19350969 0.00000000 -0.01357645 +O 2.95500000 1.70600000 8.63200000 -0.01930000 -0.00000000 0.00000000 -0.00000000 0.11900000 0.06790000 0.00000000 0.06790000 -0.09970000 0.00000000 0.00000000 0.09602510 -0.12210706 0.00000000 -0.09779287 +O 2.95500000 1.70600000 1.01800000 0.08700000 -0.00000000 0.00000000 -0.00000000 -0.04170000 -0.16980000 0.00000000 -0.16980000 -0.04530000 0.00000000 0.00000000 -0.24013346 -0.05548094 0.00000000 0.09100464 +O 0.00000000 3.41200000 13.45700000 0.10180000 0.00000000 0.00000000 0.00000000 -0.04890000 -0.17300000 0.00000000 -0.17300000 -0.05290000 0.00000000 0.00000000 -0.24465895 -0.06478900 0.00000000 0.10656099 +O 0.00000000 3.41200000 5.84300000 -0.06730000 -0.00000000 -0.00000000 -0.00000000 0.08220000 0.10550000 -0.00000000 0.10550000 -0.01490000 0.00000000 0.00000000 0.14919953 -0.01824870 0.00000000 -0.10571246 +O -1.32400000 4.17600000 1.14400000 -0.14790000 -0.08290000 0.16630000 -0.08290000 0.11540000 0.01520000 0.16630000 0.01520000 0.03250000 -0.00000000 -0.11723830 0.02149605 0.03980421 0.23518372 -0.18618122 +O 0.00000000 1.88300000 1.14400000 0.02820000 -0.00000000 0.00000000 -0.00000000 -0.08740000 0.06430000 0.00000000 0.06430000 0.05920000 -0.00000000 0.00000000 0.09093393 0.07250490 0.00000000 0.08174154 +O 1.32400000 4.17600000 1.14400000 -0.14790000 0.08290000 -0.16630000 0.08290000 0.11540000 0.01520000 -0.16630000 0.01520000 0.03250000 -0.00000000 0.11723830 0.02149605 0.03980421 -0.23518372 -0.18618122 +O 4.27800000 0.94200000 13.33100000 -0.15930000 -0.08110000 0.16180000 -0.08110000 0.10950000 0.00390000 0.16180000 0.00390000 0.04980000 -0.00000000 -0.11469272 0.00551543 0.06099229 0.22881975 -0.19007030 +O 1.63100000 0.94200000 13.33100000 -0.15930000 0.08110000 -0.16180000 0.08110000 0.10950000 0.00390000 -0.16180000 0.00390000 0.04980000 -0.00000000 0.11469272 0.00551543 0.06099229 -0.22881975 -0.19007030 +O 2.95500000 3.23400000 13.33100000 0.01710000 0.00000000 -0.00000000 0.00000000 -0.08150000 0.07440000 -0.00000000 0.07440000 0.06450000 -0.00005774 0.00000000 0.10521749 0.07895522 0.00000000 0.06972073 +O 4.58600000 0.76400000 5.96800000 -0.11740000 0.20700000 -0.14470000 0.20700000 0.01590000 0.02000000 -0.14470000 0.02000000 0.10150000 -0.00000000 0.29274221 0.02828427 0.12431160 -0.20463670 -0.09425733 +O 2.95500000 3.58900000 5.96800000 0.08370000 0.00000000 0.00000000 0.00000000 -0.21000000 -0.10370000 0.00000000 -0.10370000 0.12630000 -0.00000000 0.00000000 -0.14665395 0.15468528 0.00000000 0.20767726 +O 1.32400000 0.76400000 5.96800000 -0.11740000 -0.20700000 0.14470000 -0.20700000 0.01590000 0.02000000 0.14470000 0.02000000 0.10150000 -0.00000000 -0.29274221 0.02828427 0.12431160 0.20463670 -0.09425733 +O 1.32400000 2.64800000 3.68100000 -0.02630000 0.06110000 -0.03130000 0.06110000 0.05450000 0.03760000 -0.03130000 0.03760000 -0.02820000 0.00000000 0.08640845 0.05317443 -0.03453781 -0.04426488 -0.05713423 +O -1.32400000 2.64800000 3.68100000 -0.02630000 -0.06110000 0.03130000 -0.06110000 0.05450000 0.03760000 0.03130000 0.03760000 -0.02820000 0.00000000 -0.08640845 0.05317443 -0.03453781 0.04426488 -0.05713423 +O 0.00000000 4.94000000 3.68100000 0.06570000 -0.00000000 -0.00000000 -0.00000000 -0.03300000 -0.01450000 -0.00000000 -0.01450000 -0.03270000 0.00000000 0.00000000 -0.02050610 -0.04004916 0.00000000 0.06979144 +O 1.63100000 2.47000000 10.79300000 -0.02300000 0.05840000 -0.02260000 0.05840000 0.05770000 0.02640000 -0.02260000 0.02640000 -0.03470000 0.00000000 0.08259007 0.03733524 -0.04249865 -0.03196123 -0.05706352 +O 2.95500000 0.17700000 10.79300000 0.06660000 0.00000000 0.00000000 0.00000000 -0.02540000 -0.00310000 0.00000000 -0.00310000 -0.04120000 0.00000000 0.00000000 -0.00438406 -0.05045949 0.00000000 0.06505382 +O 4.27800000 2.47000000 10.79300000 -0.02300000 -0.05840000 0.02260000 -0.05840000 0.05770000 0.02640000 0.02260000 0.02640000 -0.03470000 0.00000000 -0.08259007 0.03733524 -0.04249865 0.03196123 -0.05706352 +O -1.63100000 4.35300000 8.50600000 -0.04120000 0.16480000 -0.16110000 0.16480000 -0.01680000 0.02310000 -0.16110000 0.02310000 0.05800000 -0.00000000 0.23306240 0.03266833 0.07103520 -0.22782980 -0.01725341 +O 1.63100000 4.35300000 8.50600000 -0.04120000 -0.16480000 0.16110000 -0.16480000 -0.01680000 0.02310000 0.16110000 0.02310000 0.05800000 -0.00000000 -0.23306240 0.03266833 0.07103520 0.22782980 -0.01725341 +O 0.00000000 1.52800000 8.50600000 0.06400000 0.00000000 -0.00000000 0.00000000 -0.15960000 -0.08680000 -0.00000000 -0.08680000 0.09570000 -0.00005774 0.00000000 -0.12275374 0.11716726 0.00000000 0.15810908 +Ti 2.95500000 1.70600000 12.06200000 0.07420000 -0.00000000 -0.00000000 -0.00000000 0.17140000 0.08100000 -0.00000000 0.08100000 -0.24570000 0.00005774 0.00000000 0.11455130 -0.30087899 0.00000000 -0.06873078 +Ti 0.00000000 3.41200000 2.41200000 0.08100000 -0.00000000 0.00000000 -0.00000000 0.16990000 0.08550000 0.00000000 0.08550000 -0.25100000 0.00005774 0.00000000 0.12091526 -0.30737014 0.00000000 -0.06286179 +Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24890000 0.04850000 -0.24890000 -0.03780000 -0.07620000 0.04850000 -0.07620000 0.11880000 0.00000000 -0.35199776 -0.10776307 0.14549969 0.06858936 -0.03054701 +Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24890000 -0.04850000 0.24890000 -0.03780000 -0.07620000 -0.04850000 -0.07620000 0.11880000 0.00000000 0.35199776 -0.10776307 0.14549969 -0.06858936 -0.03054701 +Ti 0.00000000 1.70600000 4.82500000 0.06480000 0.00000000 -0.00000000 0.00000000 -0.12360000 0.23790000 -0.00000000 0.23790000 0.05870000 0.00005774 0.00000000 0.33644141 0.07193335 0.00000000 0.13321892 +Ti 1.47700000 4.26500000 4.82500000 -0.09310000 -0.07800000 -0.22360000 -0.07800000 0.03210000 -0.09990000 -0.22360000 -0.09990000 0.06100000 0.00000000 -0.11030866 -0.14127993 0.07470944 -0.31621815 -0.08852977 +Ti -1.47700000 4.26500000 4.82500000 -0.09310000 0.07800000 0.22360000 0.07800000 0.03210000 -0.09990000 0.22360000 -0.09990000 0.06100000 0.00000000 0.11030866 -0.14127993 0.07470944 0.31621815 -0.08852977 +Ti 2.95500000 3.41200000 9.65000000 0.08870000 -0.00000000 -0.00000000 -0.00000000 -0.12920000 0.18630000 -0.00000000 0.18630000 0.04050000 -0.00000000 0.00000000 0.26346799 0.04960217 0.00000000 0.15407857 +Ti 1.47700000 0.85300000 9.65000000 -0.09120000 -0.08730000 -0.18610000 -0.08730000 0.04860000 -0.07900000 -0.18610000 -0.07900000 0.04260000 0.00000000 -0.12346084 -0.11172287 0.05217413 -0.26318514 -0.09885353 +Ti 4.43200000 0.85300000 9.65000000 -0.09120000 0.08730000 0.18610000 0.08730000 0.04860000 -0.07900000 0.18610000 -0.07900000 0.04260000 0.00000000 0.12346084 -0.11172287 0.05217413 0.26318514 -0.09885353 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00330000 -0.00000000 0.00000000 -0.00000000 0.00340000 -0.00140000 0.00000000 -0.00140000 -0.00010000 -0.00000000 0.00000000 -0.00197990 -0.00012247 0.00000000 -0.00473762 +Li 0.00000000 0.00000000 12.66500000 -0.00210000 0.00000000 0.00000000 0.00000000 0.00430000 -0.00140000 0.00000000 -0.00140000 -0.00220000 0.00000000 0.00000000 -0.00197990 -0.00269444 0.00000000 -0.00452548 +Li 2.95500000 1.70600000 6.63400000 -0.01400000 0.00000000 0.00000000 0.00000000 -0.01320000 0.00000000 0.00000000 0.00000000 0.02720000 0.00000000 0.00000000 0.00000000 0.03331306 0.00000000 -0.00056569 +Li 2.95500000 1.70600000 4.82500000 -0.00810000 0.00000000 0.00000000 0.00000000 -0.00700000 0.00010000 0.00000000 0.00010000 0.01510000 -0.00000000 0.00000000 0.00014142 0.01849365 0.00000000 -0.00077782 +Li 0.00000000 3.41200000 11.45900000 0.01200000 0.00000000 0.00000000 0.00000000 0.01140000 0.00030000 0.00000000 0.00030000 -0.02340000 0.00000000 0.00000000 0.00042426 -0.02865903 0.00000000 0.00042426 +Li 0.00000000 3.41200000 7.84000000 0.00240000 0.00000000 -0.00000000 0.00000000 0.00330000 0.00000000 -0.00000000 0.00000000 -0.00570000 0.00000000 0.00000000 0.00000000 -0.00698105 0.00000000 -0.00063640 +Li 0.00000000 0.00000000 7.23700000 0.00620000 0.00000000 -0.00000000 0.00000000 0.00720000 0.00000000 -0.00000000 0.00000000 -0.01340000 0.00000000 0.00000000 0.00000000 -0.01641158 0.00000000 -0.00070711 +Li 2.95500000 0.00000000 0.00000000 0.00650000 -0.00000000 0.00000000 -0.00000000 0.00510000 -0.00280000 0.00000000 -0.00280000 -0.01160000 0.00000000 0.00000000 -0.00395980 -0.01420704 0.00000000 0.00098995 +O 0.00000000 0.00000000 3.80700000 0.05990000 -0.00000000 -0.00000000 -0.00000000 0.07290000 -0.00380000 -0.00000000 -0.00380000 -0.13280000 0.00000000 0.00000000 -0.00537401 -0.16264612 0.00000000 -0.00919239 +O 0.00000000 0.00000000 10.66800000 0.07340000 0.00000000 0.00000000 0.00000000 0.08600000 -0.00470000 0.00000000 -0.00470000 -0.15940000 0.00000000 0.00000000 -0.00664680 -0.19522433 0.00000000 -0.00890955 +O 2.95500000 1.70600000 8.63200000 0.02240000 0.00000000 -0.00000000 0.00000000 0.03260000 -0.00140000 -0.00000000 -0.00140000 -0.05500000 0.00000000 0.00000000 -0.00197990 -0.06736097 0.00000000 -0.00721249 +O 2.95500000 1.70600000 1.01800000 -0.00420000 -0.00000000 -0.00000000 -0.00000000 0.03730000 -0.06830000 -0.00000000 -0.06830000 -0.03310000 0.00000000 0.00000000 -0.09659079 -0.04053906 0.00000000 -0.02934493 +O 0.00000000 3.41200000 13.45700000 0.09010000 0.00000000 -0.00000000 0.00000000 -0.03430000 -0.14300000 -0.00000000 -0.14300000 -0.05580000 0.00000000 0.00000000 -0.20223254 -0.06834076 0.00000000 0.08796408 +O 0.00000000 3.41200000 5.84300000 0.05050000 0.00000000 -0.00000000 0.00000000 0.06030000 -0.00120000 -0.00000000 -0.00120000 -0.11080000 0.00000000 0.00000000 -0.00169706 -0.13570173 0.00000000 -0.00692965 +O -1.32400000 4.17600000 1.14400000 -0.14440000 -0.07620000 0.17550000 -0.07620000 0.09840000 -0.01030000 0.17550000 -0.01030000 0.04600000 -0.00000000 -0.10776307 -0.01456640 0.05633826 0.24819448 -0.17168553 +O 0.00000000 1.88300000 1.14400000 -0.01530000 0.00000000 0.00000000 0.00000000 -0.06080000 0.09630000 0.00000000 0.09630000 0.07600000 0.00005774 0.00000000 0.13618877 0.09312144 0.00000000 0.03217336 +O 1.32400000 4.17600000 1.14400000 -0.14440000 0.07620000 -0.17550000 0.07620000 0.09840000 -0.01030000 -0.17550000 -0.01030000 0.04600000 -0.00000000 0.10776307 -0.01456640 0.05633826 -0.24819448 -0.17168553 +O 4.27800000 0.94200000 13.33100000 -0.16030000 -0.08110000 0.15750000 -0.08110000 0.09900000 0.00840000 0.15750000 0.00840000 0.06130000 -0.00000000 -0.11469272 0.01187939 0.07507686 0.22273864 -0.18335279 +O 1.63100000 0.94200000 13.33100000 -0.16030000 0.08110000 -0.15750000 0.08110000 0.09900000 0.00840000 -0.15750000 0.00840000 0.06130000 -0.00000000 0.11469272 0.01187939 0.07507686 -0.22273864 -0.18335279 +O 2.95500000 3.23400000 13.33100000 0.02040000 -0.00000000 -0.00000000 -0.00000000 -0.08120000 0.09030000 -0.00000000 0.09030000 0.06080000 -0.00000000 0.00000000 0.12770348 0.07446449 0.00000000 0.07184205 +O 4.58600000 0.76400000 5.96800000 -0.04310000 0.04460000 0.00120000 0.04460000 0.01780000 0.00460000 0.00120000 0.00460000 0.02530000 -0.00000000 0.06307392 0.00650538 0.03098605 0.00169706 -0.04306280 +O 2.95500000 3.58900000 5.96800000 0.03080000 0.00000000 0.00000000 0.00000000 -0.05850000 0.00050000 0.00000000 0.00050000 0.02770000 0.00000000 0.00000000 0.00070711 0.03392543 0.00000000 0.06314464 +O 1.32400000 0.76400000 5.96800000 -0.04310000 -0.04460000 -0.00120000 -0.04460000 0.01780000 0.00460000 -0.00120000 0.00460000 0.02530000 -0.00000000 -0.06307392 0.00650538 0.03098605 -0.00169706 -0.04306280 +O 1.32400000 2.64800000 3.68100000 -0.02620000 0.02030000 0.05700000 0.02030000 0.02350000 -0.01210000 0.05700000 -0.01210000 0.00270000 -0.00000000 0.02870854 -0.01711198 0.00330681 0.08061017 -0.03514321 +O -1.32400000 2.64800000 3.68100000 -0.02620000 -0.02030000 -0.05700000 -0.02030000 0.02350000 -0.01210000 -0.05700000 -0.01210000 0.00270000 -0.00000000 -0.02870854 -0.01711198 0.00330681 -0.08061017 -0.03514321 +O 0.00000000 4.94000000 3.68100000 0.01790000 -0.00000000 -0.00000000 -0.00000000 -0.01340000 0.05900000 -0.00000000 0.05900000 -0.00440000 -0.00005774 0.00000000 0.08343860 -0.00542970 0.00000000 0.02213244 +O 1.63100000 2.47000000 10.79300000 -0.02630000 0.04940000 -0.01910000 0.04940000 0.05690000 0.03720000 -0.01910000 0.03720000 -0.03060000 0.00000000 0.06986215 0.05260874 -0.03747719 -0.02701148 -0.05883128 +O 2.95500000 0.17700000 10.79300000 0.06780000 0.00000000 0.00000000 0.00000000 -0.02870000 -0.02640000 0.00000000 -0.02640000 -0.03910000 0.00000000 0.00000000 -0.03733524 -0.04788752 0.00000000 0.06823580 +O 4.27800000 2.47000000 10.79300000 -0.02630000 -0.04940000 0.01910000 -0.04940000 0.05690000 0.03720000 0.01910000 0.03720000 -0.03060000 0.00000000 -0.06986215 0.05260874 -0.03747719 0.02701148 -0.05883128 +O -1.63100000 4.35300000 8.50600000 -0.12540000 0.12990000 -0.09240000 0.12990000 0.03500000 0.05860000 -0.09240000 0.05860000 0.09040000 -0.00000000 0.18370634 0.08287291 0.11071694 -0.13067333 -0.11341993 +O 1.63100000 4.35300000 8.50600000 -0.12540000 -0.12990000 0.09240000 -0.12990000 0.03500000 0.05860000 0.09240000 0.05860000 0.09040000 -0.00000000 -0.18370634 0.08287291 0.11071694 0.13067333 -0.11341993 +O 0.00000000 1.52800000 8.50600000 0.09770000 0.00000000 -0.00000000 0.00000000 -0.18990000 -0.10810000 -0.00000000 -0.10810000 0.09220000 0.00000000 0.00000000 -0.15287649 0.11292148 0.00000000 0.20336391 +Ti 2.95500000 1.70600000 12.06200000 0.07490000 -0.00000000 -0.00000000 -0.00000000 0.17470000 0.08310000 -0.00000000 0.08310000 -0.24960000 0.00000000 0.00000000 0.11752115 -0.30569632 0.00000000 -0.07056926 +Ti 0.00000000 3.41200000 2.41200000 0.07310000 0.00000000 -0.00000000 0.00000000 0.16220000 0.07700000 -0.00000000 0.07700000 -0.23520000 -0.00005774 0.00000000 0.10889444 -0.28810082 0.00000000 -0.06300321 +Ti -1.47700000 2.55900000 0.00000000 -0.10490000 -0.26660000 0.06490000 -0.26660000 -0.02130000 -0.05100000 0.06490000 -0.05100000 0.12620000 -0.00000000 -0.37702934 -0.07212489 0.15456280 0.09178246 -0.05911413 +Ti 1.47700000 2.55900000 0.00000000 -0.10490000 0.26660000 -0.06490000 0.26660000 -0.02130000 -0.05100000 -0.06490000 -0.05100000 0.12620000 -0.00000000 0.37702934 -0.07212489 0.15456280 -0.09178246 -0.05911413 +Ti 0.00000000 1.70600000 4.82500000 0.10160000 -0.00000000 -0.00000000 -0.00000000 -0.13590000 0.18660000 -0.00000000 0.18660000 0.03430000 -0.00000000 0.00000000 0.26389225 0.04200875 0.00000000 0.16793786 +Ti 1.47700000 4.26500000 4.82500000 -0.07960000 -0.10460000 -0.15990000 -0.10460000 0.04400000 -0.09060000 -0.15990000 -0.09060000 0.03560000 0.00000000 -0.14792674 -0.12812775 0.04360092 -0.22613275 -0.08739840 +Ti -1.47700000 4.26500000 4.82500000 -0.07960000 0.10460000 0.15990000 0.10460000 0.04400000 -0.09060000 0.15990000 -0.09060000 0.03560000 0.00000000 0.14792674 -0.12812775 0.04360092 0.22613275 -0.08739840 +Ti 2.95500000 3.41200000 9.65000000 0.07960000 -0.00000000 0.00000000 -0.00000000 -0.12150000 0.21180000 0.00000000 0.21180000 0.04190000 -0.00000000 0.00000000 0.29953043 0.05131681 0.00000000 0.14219917 +Ti 1.47700000 0.85300000 9.65000000 -0.07460000 -0.08760000 -0.18150000 -0.08760000 0.03070000 -0.10310000 -0.18150000 -0.10310000 0.04390000 -0.00000000 -0.12388511 -0.14580542 0.05376630 -0.25667976 -0.07445834 +Ti 4.43200000 0.85300000 9.65000000 -0.07460000 0.08760000 0.18150000 0.08760000 0.03070000 -0.10310000 0.18150000 -0.10310000 0.04390000 -0.00000000 0.12388511 -0.14580542 0.05376630 0.25667976 -0.07445834 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.01570000 -0.02150000 -0.01490000 -0.02150000 -0.00140000 0.00680000 -0.01490000 0.00680000 -0.01430000 0.00000000 -0.03040559 0.00961665 -0.01751385 -0.02107178 0.01209153 +Li 0.00000000 0.00000000 12.66500000 -0.00150000 0.00010000 0.00000000 0.00010000 0.00440000 -0.00160000 0.00000000 -0.00160000 -0.00290000 0.00000000 0.00014142 -0.00226274 -0.00355176 0.00000000 -0.00417193 +Li 2.95500000 1.70600000 6.63400000 0.00290000 0.00010000 0.00000000 0.00010000 0.00300000 -0.00000000 0.00000000 -0.00000000 -0.00600000 0.00005774 0.00014142 0.00000000 -0.00730764 0.00000000 -0.00007071 +Li 4.43200000 0.85300000 2.41200000 0.01820000 -0.01350000 -0.01480000 -0.01350000 -0.00110000 0.00330000 -0.01480000 0.00330000 -0.01700000 -0.00005774 -0.01909188 0.00466690 -0.02086149 -0.02093036 0.01364716 +Li 0.00000000 3.41200000 11.45900000 0.01130000 -0.00000000 -0.00010000 -0.00000000 0.00970000 0.00040000 -0.00010000 0.00040000 -0.02090000 -0.00005774 0.00000000 0.00056569 -0.02563799 -0.00014142 0.00113137 +Li 0.00000000 3.41200000 7.84000000 0.00330000 -0.00000000 -0.00010000 -0.00000000 0.00330000 0.00000000 -0.00010000 0.00000000 -0.00660000 0.00000000 0.00000000 0.00000000 -0.00808332 -0.00014142 0.00000000 +Li 0.00000000 0.00000000 7.23700000 0.00680000 -0.00000000 0.00010000 -0.00000000 0.00680000 -0.00000000 0.00010000 -0.00000000 -0.01360000 0.00000000 0.00000000 0.00000000 -0.01665653 0.00014142 0.00000000 +Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00010000 0.00180000 -0.00010000 0.00390000 -0.00140000 0.00180000 -0.00140000 -0.01010000 0.00000000 -0.00014142 -0.00197990 -0.01236992 0.00254558 0.00162635 +O 0.00000000 0.00000000 3.80700000 0.08950000 -0.06110000 0.09280000 -0.06110000 0.02450000 -0.06170000 0.09280000 -0.06170000 -0.11390000 -0.00005774 -0.08640845 -0.08725698 -0.13953927 0.13123902 0.04596194 +O 0.00000000 0.00000000 10.66800000 0.07760000 -0.00100000 0.00040000 -0.00100000 0.07950000 -0.00590000 0.00040000 -0.00590000 -0.15710000 0.00000000 -0.00141421 -0.00834386 -0.19240742 0.00056569 -0.00134350 +O 2.95500000 1.70600000 8.63200000 0.02600000 -0.00070000 0.00040000 -0.00070000 0.02460000 -0.00120000 0.00040000 -0.00120000 -0.05060000 0.00000000 -0.00098995 -0.00169706 -0.06197209 0.00056569 0.00098995 +O 2.95500000 1.70600000 1.01800000 0.15350000 -0.04620000 0.07060000 -0.04620000 -0.07130000 -0.21210000 0.07060000 -0.21210000 -0.08220000 0.00000000 -0.06533667 -0.29995470 -0.10067403 0.09984348 0.15895760 +O 0.00000000 3.41200000 13.45700000 0.10120000 -0.00660000 0.01170000 -0.00660000 -0.05480000 -0.19300000 0.01170000 -0.19300000 -0.04640000 0.00000000 -0.00933381 -0.27294322 -0.05682816 0.01654630 0.11030866 +O 0.00000000 3.41200000 5.84300000 0.03490000 -0.00420000 0.01840000 -0.00420000 0.02940000 -0.01130000 0.01840000 -0.01130000 -0.06430000 0.00000000 -0.00593970 -0.01598061 -0.07875110 0.02602153 0.00388909 +O -1.32400000 4.17600000 1.14400000 -0.13820000 -0.09870000 0.16650000 -0.09870000 0.11900000 0.01000000 0.16650000 0.01000000 0.01920000 -0.00000000 -0.13958288 0.01414214 0.02351510 0.23546656 -0.18186786 +O 0.00000000 1.88300000 1.14400000 0.02580000 0.08040000 -0.09120000 0.08040000 -0.09170000 0.00020000 -0.09120000 0.00020000 0.06590000 0.00000000 0.11370277 0.00028284 0.08071069 -0.12897628 0.08308505 +O 1.32400000 4.17600000 1.14400000 -0.20680000 0.14230000 -0.16900000 0.14230000 0.15000000 0.13900000 -0.16900000 0.13900000 0.05680000 0.00000000 0.20124259 0.19657569 0.06956551 -0.23900209 -0.25229570 +O 4.27800000 0.94200000 13.33100000 -0.15110000 -0.08930000 0.17430000 -0.08930000 0.09870000 0.00820000 0.17430000 0.00820000 0.05240000 0.00000000 -0.12628927 0.01159655 0.06417663 0.24649742 -0.17663527 +O 1.63100000 0.94200000 13.33100000 -0.15600000 0.08700000 -0.17600000 0.08700000 0.11140000 0.02210000 -0.17600000 0.02210000 0.04460000 -0.00000000 0.12303658 0.03125412 0.05462362 -0.24890159 -0.18908035 +O 2.95500000 3.23400000 13.33100000 0.03560000 0.00450000 -0.01120000 0.00450000 -0.10140000 0.06960000 -0.01120000 0.06960000 0.06580000 0.00000000 0.00636396 0.09842926 0.08058821 -0.01583919 0.09687363 +O 4.58600000 0.76400000 5.96800000 -0.13860000 0.13510000 -0.09340000 0.13510000 0.01630000 0.05930000 -0.09340000 0.05930000 0.12230000 -0.00000000 0.19106025 0.08386286 0.14978630 -0.13208755 -0.10953084 +O 2.95500000 3.58900000 5.96800000 0.11020000 0.00140000 -0.01270000 0.00140000 -0.22490000 -0.13980000 -0.01270000 -0.13980000 0.11460000 0.00005774 0.00197990 -0.19770706 0.14039659 -0.01796051 0.23695148 +O 1.32400000 0.76400000 5.96800000 -0.14140000 -0.14550000 0.11350000 -0.14550000 0.02860000 0.08550000 0.11350000 0.08550000 0.11290000 -0.00005774 -0.20576807 0.12091526 0.13823287 0.16051324 -0.12020815 +O 1.32400000 2.64800000 3.68100000 -0.04470000 0.07660000 -0.00610000 0.07660000 0.05610000 0.03610000 -0.00610000 0.03610000 -0.01140000 -0.00000000 0.10832876 0.05105311 -0.01396209 -0.00862670 -0.07127636 +O -1.32400000 2.64800000 3.68100000 -0.11910000 -0.08780000 -0.01730000 -0.08780000 0.12000000 0.10920000 -0.01730000 0.10920000 -0.00090000 0.00000000 -0.12416795 0.15443212 -0.00110227 -0.02446589 -0.16906923 +O 0.00000000 4.94000000 3.68100000 0.12860000 0.05390000 -0.07390000 0.05390000 -0.11990000 -0.02820000 -0.07390000 -0.02820000 -0.00870000 0.00000000 0.07622611 -0.03988082 -0.01065528 -0.10451038 0.17571604 +O 1.63100000 2.47000000 10.79300000 -0.02060000 0.04810000 -0.00900000 0.04810000 0.04920000 0.03530000 -0.00900000 0.03530000 -0.02860000 0.00000000 0.06802367 0.04992174 -0.03502770 -0.01272792 -0.04935605 +O 2.95500000 0.17700000 10.79300000 0.07010000 -0.00210000 -0.00090000 -0.00210000 -0.03630000 -0.01600000 -0.00090000 -0.01600000 -0.03380000 0.00000000 -0.00296985 -0.02262742 -0.04139638 -0.00127279 0.07523616 +O 4.27800000 2.47000000 10.79300000 -0.02050000 -0.05120000 0.00880000 -0.05120000 0.04780000 0.03610000 0.00880000 0.03610000 -0.02730000 0.00000000 -0.07240773 0.05105311 -0.03343553 0.01244508 -0.04829539 +O -1.63100000 4.35300000 8.50600000 -0.12510000 0.12520000 -0.08390000 0.12520000 0.01770000 0.05390000 -0.08390000 0.05390000 0.10750000 -0.00005774 0.17705954 0.07622611 0.13161925 -0.11865252 -0.10097485 +O 1.63100000 4.35300000 8.50600000 -0.12480000 -0.12830000 0.08400000 -0.12830000 0.01600000 0.05420000 0.08400000 0.05420000 0.10880000 0.00000000 -0.18144360 0.07665038 0.13325224 0.11879394 -0.09956063 +O 0.00000000 1.52800000 8.50600000 0.09000000 -0.00240000 -0.00030000 -0.00240000 -0.20120000 -0.09860000 -0.00030000 -0.09860000 0.11120000 -0.00000000 -0.00339411 -0.13944146 0.13619163 -0.00042426 0.20590949 +Ti 2.95500000 1.70600000 12.06200000 0.07860000 0.00040000 -0.00170000 0.00040000 0.17360000 0.08570000 -0.00170000 0.08570000 -0.25220000 0.00000000 0.00056569 0.12119810 -0.30888066 -0.00240416 -0.06717514 +Ti 0.00000000 3.41200000 2.41200000 0.07090000 0.03340000 -0.03960000 0.03340000 0.19730000 0.10880000 -0.03960000 0.10880000 -0.26820000 0.00000000 0.04723473 0.15386644 -0.32847657 -0.05600286 -0.08937830 +Ti -1.47700000 2.55900000 0.00000000 -0.07580000 -0.27240000 0.06130000 -0.27240000 -0.04190000 -0.09500000 0.06130000 -0.09500000 0.11770000 0.00000000 -0.38523177 -0.13435029 0.14415247 0.08669129 -0.02397092 +Ti 1.47700000 2.55900000 0.00000000 -0.08610000 0.26890000 -0.06970000 0.26890000 -0.02210000 -0.06920000 -0.06970000 -0.06920000 0.10820000 -0.00000000 0.38028203 -0.09786358 0.13251740 -0.09857069 -0.04525483 +Ti 0.00000000 1.70600000 4.82500000 0.06620000 -0.01150000 0.02180000 -0.01150000 -0.12100000 0.24240000 0.02180000 0.24240000 0.05490000 -0.00005774 -0.01626346 0.34280537 0.06719767 0.03082986 0.13237039 +Ti 1.47700000 4.26500000 4.82500000 -0.06690000 -0.08580000 -0.19690000 -0.08580000 0.01040000 -0.13570000 -0.19690000 -0.13570000 0.05650000 -0.00000000 -0.12133952 -0.19190878 0.06919809 -0.27845865 -0.05465935 +Ti -1.47700000 4.26500000 4.82500000 -0.07220000 0.07660000 0.19320000 0.07660000 0.02140000 -0.10830000 0.19320000 -0.10830000 0.05080000 -0.00000000 0.10832876 -0.15315933 0.06221704 0.27322606 -0.06618519 +Ti 2.95500000 3.41200000 9.65000000 0.08060000 -0.00010000 0.00010000 -0.00010000 -0.12570000 0.20710000 0.00010000 0.20710000 0.04510000 -0.00000000 -0.00014142 0.29288363 0.05523599 0.00014142 0.14587613 +Ti 1.47700000 0.85300000 9.65000000 -0.07670000 -0.08910000 -0.17730000 -0.08910000 0.03020000 -0.10010000 -0.17730000 -0.10010000 0.04650000 0.00000000 -0.12600643 -0.14156278 0.05695064 -0.25074006 -0.07558971 +Ti 4.43200000 0.85300000 9.65000000 -0.07680000 0.08910000 0.17730000 0.08910000 0.03030000 -0.10000000 0.17730000 -0.10000000 0.04650000 -0.00000000 0.12600643 -0.14142136 0.05695064 0.25074006 -0.07573114 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.02020000 -0.00000000 0.00000000 -0.00000000 0.03470000 -0.01900000 0.00000000 -0.01900000 -0.01450000 0.00000000 0.00000000 -0.02687006 -0.01775880 0.00000000 -0.03882016 +Li 0.00000000 0.00000000 12.66500000 -0.00160000 0.00000000 0.00000000 0.00000000 0.00510000 -0.00160000 0.00000000 -0.00160000 -0.00350000 0.00000000 0.00000000 -0.00226274 -0.00428661 0.00000000 -0.00473762 +Li 2.95500000 1.70600000 6.63400000 0.00300000 0.00000000 0.00000000 0.00000000 0.00370000 0.00000000 0.00000000 0.00000000 -0.00670000 0.00000000 0.00000000 0.00000000 -0.00820579 0.00000000 -0.00049497 +Li 2.95500000 3.41200000 2.41200000 -0.01050000 0.00000000 0.00000000 0.00000000 0.02680000 -0.01210000 0.00000000 -0.01210000 -0.01630000 0.00000000 0.00000000 -0.01711198 -0.01996334 0.00000000 -0.02637508 +Li 0.00000000 3.41200000 11.45900000 0.01120000 0.00000000 -0.00000000 0.00000000 0.01060000 0.00020000 -0.00000000 0.00020000 -0.02180000 0.00000000 0.00000000 0.00028284 -0.02669944 0.00000000 0.00042426 +Li 0.00000000 3.41200000 7.84000000 0.00320000 -0.00000000 -0.00000000 -0.00000000 0.00410000 -0.00010000 -0.00000000 -0.00010000 -0.00720000 -0.00005774 0.00000000 -0.00014142 -0.00885899 0.00000000 -0.00063640 +Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 -0.00000000 -0.00000000 0.00760000 0.00010000 -0.00000000 0.00010000 -0.01430000 0.00000000 0.00000000 0.00014142 -0.01751385 0.00000000 -0.00063640 +Li 2.95500000 0.00000000 0.00000000 0.00630000 -0.00000000 0.00000000 -0.00000000 0.00590000 -0.00310000 0.00000000 -0.00310000 -0.01220000 0.00000000 0.00000000 -0.00438406 -0.01494189 0.00000000 0.00028284 +O 0.00000000 0.00000000 3.80700000 -0.01270000 0.00000000 0.00000000 0.00000000 0.13640000 0.09810000 0.00000000 0.09810000 -0.12380000 0.00005774 0.00000000 0.13873435 -0.15158259 0.00000000 -0.10542962 +O 0.00000000 0.00000000 10.66800000 0.07570000 0.00000000 -0.00000000 0.00000000 0.08870000 -0.00570000 -0.00000000 -0.00570000 -0.16450000 0.00005774 0.00000000 -0.00806102 -0.20142971 0.00000000 -0.00919239 +O 2.95500000 1.70600000 8.63200000 0.02410000 0.00000000 -0.00000000 0.00000000 0.03440000 -0.00070000 -0.00000000 -0.00070000 -0.05850000 0.00000000 0.00000000 -0.00098995 -0.07164757 0.00000000 -0.00728320 +O 2.95500000 1.70600000 1.01800000 0.01950000 -0.00000000 0.00000000 -0.00000000 0.06930000 -0.08480000 0.00000000 -0.08480000 -0.08880000 0.00000000 0.00000000 -0.11992531 -0.10875734 0.00000000 -0.03521392 +O 0.00000000 3.41200000 13.45700000 0.09700000 0.00000000 -0.00000000 0.00000000 -0.04250000 -0.15470000 -0.00000000 -0.15470000 -0.05460000 0.00005774 0.00000000 -0.21877884 -0.06683025 0.00000000 0.09864140 +O 0.00000000 3.41200000 5.84300000 0.02690000 0.00000000 0.00000000 0.00000000 0.04500000 0.01980000 0.00000000 0.01980000 -0.07190000 0.00000000 0.00000000 0.02800143 -0.08805916 0.00000000 -0.01279863 +O -1.32400000 4.17600000 1.14400000 -0.06170000 -0.03790000 0.04600000 -0.03790000 0.02140000 -0.04050000 0.04600000 -0.04050000 0.04030000 -0.00000000 -0.05359869 -0.05727565 0.04935722 0.06505382 -0.05876057 +O 0.00000000 1.88300000 1.14400000 0.01890000 0.00000000 0.00000000 0.00000000 -0.05740000 0.06460000 0.00000000 0.06460000 0.03850000 0.00000000 0.00000000 0.09135820 0.04715268 0.00000000 0.05395225 +O 1.32400000 4.17600000 1.14400000 -0.06170000 0.03790000 -0.04600000 0.03790000 0.02140000 -0.04050000 -0.04600000 -0.04050000 0.04030000 -0.00000000 0.05359869 -0.05727565 0.04935722 -0.06505382 -0.05876057 +O 4.27800000 0.94200000 13.33100000 -0.15290000 -0.08570000 0.16690000 -0.08570000 0.11610000 0.01150000 0.16690000 0.01150000 0.03680000 0.00000000 -0.12119810 0.01626346 0.04507061 0.23603224 -0.19021172 +O 1.63100000 0.94200000 13.33100000 -0.15290000 0.08570000 -0.16690000 0.08570000 0.11610000 0.01150000 -0.16690000 0.01150000 0.03680000 0.00000000 0.12119810 0.01626346 0.04507061 -0.23603224 -0.19021172 +O 2.95500000 3.23400000 13.33100000 0.02310000 -0.00000000 -0.00000000 -0.00000000 -0.08550000 0.08450000 -0.00000000 0.08450000 0.06240000 0.00000000 0.00000000 0.11950105 0.07642408 0.00000000 0.07679180 +O 4.58600000 0.76400000 5.96800000 -0.13670000 0.14550000 -0.12540000 0.14550000 0.03300000 0.06310000 -0.12540000 0.06310000 0.10380000 -0.00005774 0.20576807 0.08923688 0.12708769 -0.17734238 -0.11999602 +O 2.95500000 3.58900000 5.96800000 0.09180000 0.00000000 0.00000000 0.00000000 -0.20800000 -0.10870000 0.00000000 -0.10870000 0.11620000 -0.00000000 0.00000000 -0.15372501 0.14231535 0.00000000 0.21199061 +O 1.32400000 0.76400000 5.96800000 -0.13670000 -0.14550000 0.12540000 -0.14550000 0.03300000 0.06310000 0.12540000 0.06310000 0.10380000 -0.00005774 -0.20576807 0.08923688 0.12708769 0.17734238 -0.11999602 +O 1.32400000 2.64800000 3.68100000 -0.02010000 0.13610000 -0.05550000 0.13610000 0.03380000 -0.01960000 -0.05550000 -0.01960000 -0.01370000 0.00000000 0.19247447 -0.02771859 -0.01677900 -0.07848885 -0.03811306 +O -1.32400000 2.64800000 3.68100000 -0.02010000 -0.13610000 0.05550000 -0.13610000 0.03380000 -0.01960000 0.05550000 -0.01960000 -0.01370000 0.00000000 -0.19247447 -0.02771859 -0.01677900 0.07848885 -0.03811306 +O 0.00000000 4.94000000 3.68100000 0.08860000 -0.00000000 -0.00000000 -0.00000000 -0.06250000 -0.01210000 -0.00000000 -0.01210000 -0.02610000 0.00000000 0.00000000 -0.01711198 -0.03196584 0.00000000 0.10684383 +O 1.63100000 2.47000000 10.79300000 -0.02230000 0.04870000 -0.01010000 0.04870000 0.05870000 0.03520000 -0.01010000 0.03520000 -0.03640000 0.00000000 0.06887220 0.04978032 -0.04458071 -0.01428356 -0.05727565 +O 2.95500000 0.17700000 10.79300000 0.06720000 -0.00000000 0.00000000 -0.00000000 -0.02460000 -0.01560000 0.00000000 -0.01560000 -0.04260000 0.00000000 0.00000000 -0.02206173 -0.05217413 0.00000000 0.06491240 +O 4.27800000 2.47000000 10.79300000 -0.02230000 -0.04870000 0.01010000 -0.04870000 0.05870000 0.03520000 0.01010000 0.03520000 -0.03640000 0.00000000 -0.06887220 0.04978032 -0.04458071 0.01428356 -0.05727565 +O -1.63100000 4.35300000 8.50600000 -0.12710000 0.12500000 -0.08440000 0.12500000 0.02750000 0.05370000 -0.08440000 0.05370000 0.09960000 -0.00000000 0.17677670 0.07594327 0.12198459 -0.11935962 -0.10931871 +O 1.63100000 4.35300000 8.50600000 -0.12710000 -0.12500000 0.08440000 -0.12500000 0.02750000 0.05370000 0.08440000 0.05370000 0.09960000 -0.00000000 -0.17677670 0.07594327 0.12198459 0.11935962 -0.10931871 +O 0.00000000 1.52800000 8.50600000 0.08760000 0.00000000 -0.00000000 0.00000000 -0.18860000 -0.09830000 -0.00000000 -0.09830000 0.10100000 -0.00000000 0.00000000 -0.13901719 0.12369923 0.00000000 0.19530289 +Ti 2.95500000 1.70600000 12.06200000 0.07980000 0.00000000 0.00000000 0.00000000 0.17150000 0.08680000 0.00000000 0.08680000 -0.25130000 0.00000000 0.00000000 0.12275374 -0.30777839 0.00000000 -0.06484169 +Ti 0.00000000 3.41200000 2.41200000 0.12860000 -0.00000000 -0.00000000 -0.00000000 0.13840000 0.03960000 -0.00000000 0.03960000 -0.26690000 -0.00005774 0.00000000 0.05600286 -0.32692523 0.00000000 -0.00692965 +Ti -1.47700000 2.55900000 0.00000000 -0.09850000 -0.25810000 0.04340000 -0.25810000 -0.01780000 -0.05560000 0.04340000 -0.05560000 0.11630000 -0.00000000 -0.36500852 -0.07863027 0.14243783 0.06137687 -0.05706352 +Ti 1.47700000 2.55900000 0.00000000 -0.09850000 0.25810000 -0.04340000 0.25810000 -0.01780000 -0.05560000 -0.04340000 -0.05560000 0.11630000 -0.00000000 0.36500852 -0.07863027 0.14243783 -0.06137687 -0.05706352 +Ti 0.00000000 1.70600000 4.82500000 0.06500000 0.00000000 -0.00000000 0.00000000 -0.11490000 0.22520000 -0.00000000 0.22520000 0.04990000 -0.00000000 0.00000000 0.31848089 0.06111477 0.00000000 0.12720851 +Ti 1.47700000 4.26500000 4.82500000 -0.08680000 -0.07510000 -0.21740000 -0.07510000 0.02970000 -0.09830000 -0.21740000 -0.09830000 0.05710000 0.00000000 -0.10620744 -0.13901719 0.06993293 -0.30745003 -0.08237794 +Ti -1.47700000 4.26500000 4.82500000 -0.08680000 0.07510000 0.21740000 0.07510000 0.02970000 -0.09830000 0.21740000 -0.09830000 0.05710000 0.00000000 0.10620744 -0.13901719 0.06993293 0.30745003 -0.08237794 +Ti 2.95500000 3.41200000 9.65000000 0.08060000 -0.00000000 0.00000000 -0.00000000 -0.12570000 0.20720000 0.00000000 0.20720000 0.04520000 -0.00005774 0.00000000 0.29302505 0.05531764 0.00000000 0.14587613 +Ti 1.47700000 0.85300000 9.65000000 -0.07680000 -0.08900000 -0.17750000 -0.08900000 0.03040000 -0.10000000 -0.17750000 -0.10000000 0.04640000 -0.00000000 -0.12586501 -0.14142136 0.05682816 -0.25102291 -0.07580185 +Ti 4.43200000 0.85300000 9.65000000 -0.07680000 0.08900000 0.17750000 0.08900000 0.03040000 -0.10000000 0.17750000 -0.10000000 0.04640000 -0.00000000 0.12586501 -0.14142136 0.05682816 0.25102291 -0.07580185 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.01570000 0.02150000 0.01490000 0.02150000 -0.00140000 0.00680000 0.01490000 0.00680000 -0.01430000 0.00000000 0.03040559 0.00961665 -0.01751385 0.02107178 0.01209153 +Li 0.00000000 0.00000000 12.66500000 -0.00150000 -0.00010000 -0.00000000 -0.00010000 0.00440000 -0.00160000 -0.00000000 -0.00160000 -0.00290000 0.00000000 -0.00014142 -0.00226274 -0.00355176 0.00000000 -0.00417193 +Li 2.95500000 1.70600000 6.63400000 0.00290000 -0.00010000 -0.00000000 -0.00010000 0.00300000 -0.00000000 -0.00000000 -0.00000000 -0.00600000 0.00005774 -0.00014142 0.00000000 -0.00730764 0.00000000 -0.00007071 +Li 1.47700000 0.85300000 2.41200000 0.01810000 0.01350000 0.01490000 0.01350000 -0.00110000 0.00330000 0.01490000 0.00330000 -0.01710000 0.00005774 0.01909188 0.00466690 -0.02090231 0.02107178 0.01357645 +Li 0.00000000 3.41200000 11.45900000 0.01130000 0.00000000 0.00010000 0.00000000 0.00970000 0.00040000 0.00010000 0.00040000 -0.02090000 -0.00005774 0.00000000 0.00056569 -0.02563799 0.00014142 0.00113137 +Li 0.00000000 3.41200000 7.84000000 0.00330000 0.00000000 0.00010000 0.00000000 0.00330000 0.00000000 0.00010000 0.00000000 -0.00660000 0.00000000 0.00000000 0.00000000 -0.00808332 0.00014142 0.00000000 +Li 0.00000000 0.00000000 7.23700000 0.00680000 -0.00000000 -0.00010000 -0.00000000 0.00680000 -0.00000000 -0.00010000 -0.00000000 -0.01360000 0.00000000 0.00000000 0.00000000 -0.01665653 -0.00014142 0.00000000 +Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00000000 -0.00180000 -0.00000000 0.00390000 -0.00140000 -0.00180000 -0.00140000 -0.01010000 0.00000000 0.00000000 -0.00197990 -0.01236992 -0.00254558 0.00162635 +O 0.00000000 0.00000000 3.80700000 0.08950000 0.05940000 -0.09250000 0.05940000 0.02430000 -0.06160000 -0.09250000 -0.06160000 -0.11380000 0.00000000 0.08400429 -0.08711556 -0.13937597 -0.13081475 0.04610336 +O 0.00000000 0.00000000 10.66800000 0.07760000 -0.00090000 -0.00010000 -0.00090000 0.07950000 -0.00590000 -0.00010000 -0.00590000 -0.15710000 0.00000000 -0.00127279 -0.00834386 -0.19240742 -0.00014142 -0.00134350 +O 2.95500000 1.70600000 8.63200000 0.02600000 -0.00070000 -0.00010000 -0.00070000 0.02460000 -0.00110000 -0.00010000 -0.00110000 -0.05060000 0.00000000 -0.00098995 -0.00155563 -0.06197209 -0.00014142 0.00098995 +O 2.95500000 1.70600000 1.01800000 0.15360000 0.04470000 -0.07010000 0.04470000 -0.07160000 -0.21210000 -0.07010000 -0.21210000 -0.08200000 0.00000000 0.06321535 -0.29995470 -0.10042908 -0.09913637 0.15924045 +O 0.00000000 3.41200000 13.45700000 0.10120000 0.00520000 -0.01120000 0.00520000 -0.05480000 -0.19300000 -0.01120000 -0.19300000 -0.04640000 0.00000000 0.00735391 -0.27294322 -0.05682816 -0.01583919 0.11030866 +O 0.00000000 3.41200000 5.84300000 0.03490000 0.00280000 -0.01810000 0.00280000 0.02940000 -0.01130000 -0.01810000 -0.01130000 -0.06430000 0.00000000 0.00395980 -0.01598061 -0.07875110 -0.02559727 0.00388909 +O -1.32400000 4.17600000 1.14400000 -0.20720000 -0.14530000 0.16900000 -0.14530000 0.15220000 0.13880000 0.16900000 0.13880000 0.05500000 -0.00000000 -0.20548523 0.19629284 0.06736097 0.23900209 -0.25413418 +O 0.00000000 1.88300000 1.14400000 0.02580000 -0.08420000 0.09110000 -0.08420000 -0.09150000 0.00010000 0.09110000 0.00010000 0.06570000 -0.00000000 -0.11907678 0.00014142 0.08046574 0.12883486 0.08294363 +O 1.32400000 4.17600000 1.14400000 -0.13780000 0.09550000 -0.16630000 0.09550000 0.11670000 0.01030000 -0.16630000 0.01030000 0.02110000 -0.00000000 0.13505740 0.01456640 0.02584212 -0.23518372 -0.17995868 +O 4.27800000 0.94200000 13.33100000 -0.15640000 -0.09000000 0.17610000 -0.09000000 0.11360000 0.02190000 0.17610000 0.02190000 0.04280000 0.00000000 -0.12727922 0.03097128 0.05241908 0.24904301 -0.19091883 +O 1.63100000 0.94200000 13.33100000 -0.15070000 0.08630000 -0.17420000 0.08630000 0.09650000 0.00850000 -0.17420000 0.00850000 0.05420000 0.00000000 0.12204663 0.01202082 0.06638117 -0.24635600 -0.17479680 +O 2.95500000 3.23400000 13.33100000 0.03560000 -0.00850000 0.01110000 -0.00850000 -0.10140000 0.06960000 0.01110000 0.06960000 0.06580000 0.00000000 -0.01202082 0.09842926 0.08058821 0.01569777 0.09687363 +O 4.58600000 0.76400000 5.96800000 -0.14190000 0.14240000 -0.11360000 0.14240000 0.03020000 0.08570000 -0.11360000 0.08570000 0.11170000 -0.00000000 0.20138401 0.12119810 0.13680400 -0.16065466 -0.12169308 +O 2.95500000 3.58900000 5.96800000 0.11020000 -0.00640000 0.01300000 -0.00640000 -0.22490000 -0.13980000 0.01300000 -0.13980000 0.11460000 0.00005774 -0.00905097 -0.19770706 0.14039659 0.01838478 0.23695148 +O 1.32400000 0.76400000 5.96800000 -0.13810000 -0.13820000 0.09340000 -0.13820000 0.01460000 0.05920000 0.09340000 0.05920000 0.12350000 -0.00000000 -0.19544431 0.08372144 0.15125599 0.13208755 -0.10797521 +O 1.32400000 2.64800000 3.68100000 -0.11950000 0.08470000 0.01730000 0.08470000 0.12160000 0.10920000 0.01730000 0.10920000 -0.00210000 -0.00000000 0.11978389 0.15443212 -0.00257196 0.02446589 -0.17048344 +O -1.32400000 2.64800000 3.68100000 -0.04430000 -0.07970000 0.00620000 -0.07970000 0.05460000 0.03620000 0.00620000 0.03620000 -0.01030000 -0.00000000 -0.11271282 0.05119453 -0.01261487 0.00876812 -0.06993286 +O 0.00000000 4.94000000 3.68100000 0.12860000 -0.05840000 0.07390000 -0.05840000 -0.11960000 -0.02830000 0.07390000 -0.02830000 -0.00900000 0.00000000 -0.08259007 -0.04002224 -0.01102270 0.10451038 0.17550390 +O 1.63100000 2.47000000 10.79300000 -0.02080000 0.04810000 -0.00870000 0.04810000 0.04940000 0.03610000 -0.00870000 0.03610000 -0.02860000 0.00000000 0.06802367 0.05105311 -0.03502770 -0.01230366 -0.04963890 +O 2.95500000 0.17700000 10.79300000 0.07010000 -0.00270000 0.00100000 -0.00270000 -0.03630000 -0.01600000 0.00100000 -0.01600000 -0.03380000 0.00000000 -0.00381838 -0.02262742 -0.04139638 0.00141421 0.07523616 +O 4.27800000 2.47000000 10.79300000 -0.02030000 -0.05120000 0.00910000 -0.05120000 0.04760000 0.03530000 0.00910000 0.03530000 -0.02730000 0.00000000 -0.07240773 0.04992174 -0.03343553 0.01286934 -0.04801255 +O -1.63100000 4.35300000 8.50600000 -0.12520000 0.12520000 -0.08400000 0.12520000 0.01770000 0.05430000 -0.08400000 0.05430000 0.10760000 -0.00005774 0.17705954 0.07679180 0.13174172 -0.11879394 -0.10104556 +O 1.63100000 4.35300000 8.50600000 -0.12470000 -0.12830000 0.08390000 -0.12830000 0.01600000 0.05380000 0.08390000 0.05380000 0.10870000 0.00000000 -0.18144360 0.07608469 0.13312977 0.11865252 -0.09948992 +O 0.00000000 1.52800000 8.50600000 0.09000000 -0.00250000 0.00050000 -0.00250000 -0.20120000 -0.09860000 0.00050000 -0.09860000 0.11120000 -0.00000000 -0.00353553 -0.13944146 0.13619163 0.00070711 0.20590949 +Ti 2.95500000 1.70600000 12.06200000 0.07860000 -0.00050000 0.00180000 -0.00050000 0.17360000 0.08570000 0.00180000 0.08570000 -0.25220000 0.00000000 -0.00070711 0.12119810 -0.30888066 0.00254558 -0.06717514 +Ti 0.00000000 3.41200000 2.41200000 0.07090000 -0.03350000 0.03970000 -0.03350000 0.19740000 0.10880000 0.03970000 0.10880000 -0.26830000 0.00000000 -0.04737615 0.15386644 -0.32859905 0.05614428 -0.08944901 +Ti -1.47700000 2.55900000 0.00000000 -0.08610000 -0.26900000 0.06980000 -0.26900000 -0.02210000 -0.06920000 0.06980000 -0.06920000 0.10820000 -0.00000000 -0.38042345 -0.09786358 0.13251740 0.09871211 -0.04525483 +Ti 1.47700000 2.55900000 0.00000000 -0.07580000 0.27230000 -0.06120000 0.27230000 -0.04190000 -0.09490000 -0.06120000 -0.09490000 0.11770000 0.00000000 0.38509035 -0.13420887 0.14415247 -0.08654987 -0.02397092 +Ti 0.00000000 1.70600000 4.82500000 0.06620000 0.01140000 -0.02170000 0.01140000 -0.12110000 0.24240000 -0.02170000 0.24240000 0.05490000 0.00000000 0.01612203 0.34280537 0.06723849 -0.03068843 0.13244110 +Ti 1.47700000 4.26500000 4.82500000 -0.07220000 -0.07670000 -0.19310000 -0.07670000 0.02130000 -0.10830000 -0.19310000 -0.10830000 0.05090000 -0.00000000 -0.10847018 -0.15315933 0.06233951 -0.27308464 -0.06611448 +Ti -1.47700000 4.26500000 4.82500000 -0.06700000 0.08570000 0.19700000 0.08570000 0.01050000 -0.13570000 0.19700000 -0.13570000 0.05650000 -0.00000000 0.12119810 -0.19190878 0.06919809 0.27860007 -0.05480078 +Ti 2.95500000 3.41200000 9.65000000 0.08060000 0.00000000 -0.00000000 0.00000000 -0.12570000 0.20710000 -0.00000000 0.20710000 0.04510000 -0.00000000 0.00000000 0.29288363 0.05523599 0.00000000 0.14587613 +Ti 1.47700000 0.85300000 9.65000000 -0.07680000 -0.08910000 -0.17720000 -0.08910000 0.03030000 -0.10000000 -0.17720000 -0.10000000 0.04650000 -0.00000000 -0.12600643 -0.14142136 0.05695064 -0.25059864 -0.07573114 +Ti 4.43200000 0.85300000 9.65000000 -0.07670000 0.08900000 0.17740000 0.08900000 0.03020000 -0.10010000 0.17740000 -0.10010000 0.04640000 0.00005774 0.12586501 -0.14156278 0.05686899 0.25088149 -0.07558971 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00150000 -0.00010000 -0.00000000 -0.00010000 0.00440000 -0.00160000 -0.00000000 -0.00160000 -0.00290000 0.00000000 -0.00014142 -0.00226274 -0.00355176 0.00000000 -0.00417193 +Li 0.00000000 0.00000000 12.66500000 0.01570000 0.02150000 0.01490000 0.02150000 -0.00140000 0.00680000 0.01490000 0.00680000 -0.01430000 0.00000000 0.03040559 0.00961665 -0.01751385 0.02107178 0.01209153 +Li 2.95500000 1.70600000 6.63400000 0.00330000 0.00000000 0.00010000 0.00000000 0.00330000 0.00000000 0.00010000 0.00000000 -0.00660000 0.00000000 0.00000000 0.00000000 -0.00808332 0.00014142 0.00000000 +Li 2.95500000 1.70600000 3.01600000 0.01130000 0.00000000 0.00010000 0.00000000 0.00970000 0.00040000 0.00010000 0.00040000 -0.02090000 -0.00005774 0.00000000 0.00056569 -0.02563799 0.00014142 0.00113137 +Li 1.47700000 4.26500000 12.06200000 0.01810000 0.01350000 0.01490000 0.01350000 -0.00110000 0.00330000 0.01490000 0.00330000 -0.01710000 0.00005774 0.01909188 0.00466690 -0.02090231 0.02107178 0.01357645 +Li 0.00000000 3.41200000 7.84000000 0.00290000 -0.00010000 -0.00000000 -0.00010000 0.00300000 -0.00000000 -0.00000000 -0.00000000 -0.00600000 0.00005774 -0.00014142 0.00000000 -0.00730764 0.00000000 -0.00007071 +Li 0.00000000 0.00000000 7.23700000 0.00680000 -0.00000000 -0.00010000 -0.00000000 0.00680000 -0.00000000 -0.00010000 -0.00000000 -0.01360000 0.00000000 0.00000000 0.00000000 -0.01665653 -0.00014142 0.00000000 +Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00000000 -0.00180000 -0.00000000 0.00390000 -0.00140000 -0.00180000 -0.00140000 -0.01010000 0.00000000 0.00000000 -0.00197990 -0.01236992 -0.00254558 0.00162635 +O 0.00000000 0.00000000 3.80700000 0.07760000 -0.00090000 -0.00010000 -0.00090000 0.07950000 -0.00590000 -0.00010000 -0.00590000 -0.15710000 0.00000000 -0.00127279 -0.00834386 -0.19240742 -0.00014142 -0.00134350 +O 0.00000000 0.00000000 10.66800000 0.08950000 0.05940000 -0.09250000 0.05940000 0.02430000 -0.06160000 -0.09250000 -0.06160000 -0.11380000 0.00000000 0.08400429 -0.08711556 -0.13937597 -0.13081475 0.04610336 +O 2.95500000 1.70600000 8.63200000 0.03490000 0.00280000 -0.01810000 0.00280000 0.02940000 -0.01130000 -0.01810000 -0.01130000 -0.06430000 0.00000000 0.00395980 -0.01598061 -0.07875110 -0.02559727 0.00388909 +O 2.95500000 1.70600000 1.01800000 0.10120000 0.00520000 -0.01120000 0.00520000 -0.05480000 -0.19300000 -0.01120000 -0.19300000 -0.04640000 0.00000000 0.00735391 -0.27294322 -0.05682816 -0.01583919 0.11030866 +O 0.00000000 3.41200000 13.45700000 0.15360000 0.04470000 -0.07010000 0.04470000 -0.07160000 -0.21210000 -0.07010000 -0.21210000 -0.08200000 0.00000000 0.06321535 -0.29995470 -0.10042908 -0.09913637 0.15924045 +O 0.00000000 3.41200000 5.84300000 0.02600000 -0.00070000 -0.00010000 -0.00070000 0.02460000 -0.00110000 -0.00010000 -0.00110000 -0.05060000 0.00000000 -0.00098995 -0.00155563 -0.06197209 -0.00014142 0.00098995 +O -1.32400000 4.17600000 1.14400000 -0.15640000 -0.09000000 0.17610000 -0.09000000 0.11360000 0.02190000 0.17610000 0.02190000 0.04280000 0.00000000 -0.12727922 0.03097128 0.05241908 0.24904301 -0.19091883 +O 0.00000000 1.88300000 1.14400000 0.03560000 -0.00850000 0.01110000 -0.00850000 -0.10140000 0.06960000 0.01110000 0.06960000 0.06580000 0.00000000 -0.01202082 0.09842926 0.08058821 0.01569777 0.09687363 +O 1.32400000 4.17600000 1.14400000 -0.15070000 0.08630000 -0.17420000 0.08630000 0.09650000 0.00850000 -0.17420000 0.00850000 0.05420000 0.00000000 0.12204663 0.01202082 0.06638117 -0.24635600 -0.17479680 +O 4.27800000 0.94200000 13.33100000 -0.20720000 -0.14530000 0.16900000 -0.14530000 0.15220000 0.13880000 0.16900000 0.13880000 0.05500000 -0.00000000 -0.20548523 0.19629284 0.06736097 0.23900209 -0.25413418 +O 1.63100000 0.94200000 13.33100000 -0.13780000 0.09550000 -0.16630000 0.09550000 0.11670000 0.01030000 -0.16630000 0.01030000 0.02110000 -0.00000000 0.13505740 0.01456640 0.02584212 -0.23518372 -0.17995868 +O 2.95500000 3.23400000 13.33100000 0.02580000 -0.08420000 0.09110000 -0.08420000 -0.09150000 0.00010000 0.09110000 0.00010000 0.06570000 -0.00000000 -0.11907678 0.00014142 0.08046574 0.12883486 0.08294363 +O 4.58600000 0.76400000 5.96800000 -0.12520000 0.12520000 -0.08400000 0.12520000 0.01770000 0.05430000 -0.08400000 0.05430000 0.10760000 -0.00005774 0.17705954 0.07679180 0.13174172 -0.11879394 -0.10104556 +O 2.95500000 3.58900000 5.96800000 0.09000000 -0.00250000 0.00050000 -0.00250000 -0.20120000 -0.09860000 0.00050000 -0.09860000 0.11120000 -0.00000000 -0.00353553 -0.13944146 0.13619163 0.00070711 0.20590949 +O 1.32400000 0.76400000 5.96800000 -0.12470000 -0.12830000 0.08390000 -0.12830000 0.01600000 0.05380000 0.08390000 0.05380000 0.10870000 0.00000000 -0.18144360 0.07608469 0.13312977 0.11865252 -0.09948992 +O 1.32400000 2.64800000 3.68100000 -0.02080000 0.04810000 -0.00870000 0.04810000 0.04940000 0.03610000 -0.00870000 0.03610000 -0.02860000 0.00000000 0.06802367 0.05105311 -0.03502770 -0.01230366 -0.04963890 +O -1.32400000 2.64800000 3.68100000 -0.02030000 -0.05120000 0.00910000 -0.05120000 0.04760000 0.03530000 0.00910000 0.03530000 -0.02730000 0.00000000 -0.07240773 0.04992174 -0.03343553 0.01286934 -0.04801255 +O 0.00000000 4.94000000 3.68100000 0.07010000 -0.00270000 0.00100000 -0.00270000 -0.03630000 -0.01600000 0.00100000 -0.01600000 -0.03380000 0.00000000 -0.00381838 -0.02262742 -0.04139638 0.00141421 0.07523616 +O 1.63100000 2.47000000 10.79300000 -0.11950000 0.08470000 0.01730000 0.08470000 0.12160000 0.10920000 0.01730000 0.10920000 -0.00210000 -0.00000000 0.11978389 0.15443212 -0.00257196 0.02446589 -0.17048344 +O 2.95500000 0.17700000 10.79300000 0.12860000 -0.05840000 0.07390000 -0.05840000 -0.11960000 -0.02830000 0.07390000 -0.02830000 -0.00900000 0.00000000 -0.08259007 -0.04002224 -0.01102270 0.10451038 0.17550390 +O 4.27800000 2.47000000 10.79300000 -0.04430000 -0.07970000 0.00620000 -0.07970000 0.05460000 0.03620000 0.00620000 0.03620000 -0.01030000 -0.00000000 -0.11271282 0.05119453 -0.01261487 0.00876812 -0.06993286 +O -1.63100000 4.35300000 8.50600000 -0.14190000 0.14240000 -0.11360000 0.14240000 0.03020000 0.08570000 -0.11360000 0.08570000 0.11170000 -0.00000000 0.20138401 0.12119810 0.13680400 -0.16065466 -0.12169308 +O 1.63100000 4.35300000 8.50600000 -0.13810000 -0.13820000 0.09340000 -0.13820000 0.01460000 0.05920000 0.09340000 0.05920000 0.12350000 -0.00000000 -0.19544431 0.08372144 0.15125599 0.13208755 -0.10797521 +O 0.00000000 1.52800000 8.50600000 0.11020000 -0.00640000 0.01300000 -0.00640000 -0.22490000 -0.13980000 0.01300000 -0.13980000 0.11460000 0.00005774 -0.00905097 -0.19770706 0.14039659 0.01838478 0.23695148 +Ti 2.95500000 1.70600000 12.06200000 0.07090000 -0.03350000 0.03970000 -0.03350000 0.19740000 0.10880000 0.03970000 0.10880000 -0.26830000 0.00000000 -0.04737615 0.15386644 -0.32859905 0.05614428 -0.08944901 +Ti 0.00000000 3.41200000 2.41200000 0.07860000 -0.00050000 0.00180000 -0.00050000 0.17360000 0.08570000 0.00180000 0.08570000 -0.25220000 0.00000000 -0.00070711 0.12119810 -0.30888066 0.00254558 -0.06717514 +Ti -1.47700000 2.55900000 0.00000000 -0.08610000 -0.26900000 0.06980000 -0.26900000 -0.02210000 -0.06920000 0.06980000 -0.06920000 0.10820000 -0.00000000 -0.38042345 -0.09786358 0.13251740 0.09871211 -0.04525483 +Ti 1.47700000 2.55900000 0.00000000 -0.07580000 0.27230000 -0.06120000 0.27230000 -0.04190000 -0.09490000 -0.06120000 -0.09490000 0.11770000 0.00000000 0.38509035 -0.13420887 0.14415247 -0.08654987 -0.02397092 +Ti 0.00000000 1.70600000 4.82500000 0.08060000 0.00000000 -0.00000000 0.00000000 -0.12570000 0.20710000 -0.00000000 0.20710000 0.04510000 -0.00000000 0.00000000 0.29288363 0.05523599 0.00000000 0.14587613 +Ti 1.47700000 4.26500000 4.82500000 -0.07680000 -0.08910000 -0.17720000 -0.08910000 0.03030000 -0.10000000 -0.17720000 -0.10000000 0.04650000 -0.00000000 -0.12600643 -0.14142136 0.05695064 -0.25059864 -0.07573114 +Ti -1.47700000 4.26500000 4.82500000 -0.07670000 0.08900000 0.17740000 0.08900000 0.03020000 -0.10010000 0.17740000 -0.10010000 0.04640000 0.00005774 0.12586501 -0.14156278 0.05686899 0.25088149 -0.07558971 +Ti 2.95500000 3.41200000 9.65000000 0.06620000 0.01140000 -0.02170000 0.01140000 -0.12110000 0.24240000 -0.02170000 0.24240000 0.05490000 0.00000000 0.01612203 0.34280537 0.06723849 -0.03068843 0.13244110 +Ti 1.47700000 0.85300000 9.65000000 -0.07220000 -0.07670000 -0.19310000 -0.07670000 0.02130000 -0.10830000 -0.19310000 -0.10830000 0.05090000 -0.00000000 -0.10847018 -0.15315933 0.06233951 -0.27308464 -0.06611448 +Ti 4.43200000 0.85300000 9.65000000 -0.06700000 0.08570000 0.19700000 0.08570000 0.01050000 -0.13570000 0.19700000 -0.13570000 0.05650000 -0.00000000 0.12119810 -0.19190878 0.06919809 0.27860007 -0.05480078 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00070000 -0.00000000 -0.00000000 -0.00000000 0.00640000 -0.00180000 -0.00000000 -0.00180000 -0.00570000 0.00000000 0.00000000 -0.00254558 -0.00698105 0.00000000 -0.00502046 +Li 0.00000000 0.00000000 12.66500000 -0.00150000 -0.00000000 -0.00000000 -0.00000000 0.00510000 -0.00140000 -0.00000000 -0.00140000 -0.00360000 0.00000000 0.00000000 -0.00197990 -0.00440908 0.00000000 -0.00466690 +Li 2.95500000 1.70600000 6.63400000 0.00020000 -0.00000000 0.00000000 -0.00000000 0.00110000 0.00000000 0.00000000 0.00000000 -0.00130000 0.00000000 0.00000000 0.00000000 -0.00159217 0.00000000 -0.00063640 +Li 2.95500000 1.70600000 3.01600000 0.01000000 -0.00000000 -0.00000000 -0.00000000 0.00940000 0.00030000 -0.00000000 0.00030000 -0.01940000 0.00000000 0.00000000 0.00042426 -0.02376005 0.00000000 0.00042426 +Li 0.00000000 3.41200000 11.45900000 -0.00540000 -0.00000000 0.00000000 -0.00000000 -0.00610000 0.00050000 0.00000000 0.00050000 0.01150000 -0.00000000 0.00000000 0.00070711 0.01408457 0.00000000 0.00049497 +Li 0.00000000 3.41200000 9.65000000 -0.00900000 -0.00000000 0.00000000 -0.00000000 -0.00790000 0.00010000 0.00000000 0.00010000 0.01690000 -0.00000000 0.00000000 0.00014142 0.02069819 0.00000000 -0.00077782 +Li 0.00000000 0.00000000 7.23700000 0.00590000 -0.00000000 -0.00000000 -0.00000000 0.00690000 0.00000000 -0.00000000 0.00000000 -0.01280000 0.00000000 0.00000000 0.00000000 -0.01567673 0.00000000 -0.00070711 +Li 2.95500000 0.00000000 0.00000000 0.00580000 0.00000000 -0.00000000 0.00000000 0.00570000 -0.00170000 -0.00000000 -0.00170000 -0.01150000 0.00000000 0.00000000 -0.00240416 -0.01408457 0.00000000 0.00007071 +O 0.00000000 0.00000000 3.80700000 0.07770000 -0.00000000 -0.00000000 -0.00000000 0.09110000 -0.00660000 -0.00000000 -0.00660000 -0.16880000 0.00000000 0.00000000 -0.00933381 -0.20673693 0.00000000 -0.00947523 +O 0.00000000 0.00000000 10.66800000 0.10130000 -0.00000000 0.00000000 -0.00000000 0.11440000 -0.00590000 0.00000000 -0.00590000 -0.21570000 0.00000000 0.00000000 -0.00834386 -0.26417747 0.00000000 -0.00926310 +O 2.95500000 1.70600000 8.63200000 0.01210000 0.00000000 -0.00000000 0.00000000 0.02250000 -0.00110000 -0.00000000 -0.00110000 -0.03460000 0.00000000 0.00000000 -0.00155563 -0.04237617 0.00000000 -0.00735391 +O 2.95500000 1.70600000 1.01800000 0.08340000 -0.00000000 -0.00000000 -0.00000000 -0.04510000 -0.17380000 -0.00000000 -0.17380000 -0.03830000 0.00000000 0.00000000 -0.24579032 -0.04690773 0.00000000 0.09086322 +O 0.00000000 3.41200000 13.45700000 0.10070000 0.00000000 -0.00000000 0.00000000 -0.05640000 -0.17440000 -0.00000000 -0.17440000 -0.04430000 0.00000000 0.00000000 -0.24663885 -0.05425620 0.00000000 0.11108648 +O 0.00000000 3.41200000 5.84300000 0.01540000 -0.00000000 0.00000000 -0.00000000 0.02560000 -0.00100000 0.00000000 -0.00100000 -0.04100000 0.00000000 0.00000000 -0.00141421 -0.05021454 0.00000000 -0.00721249 +O -1.32400000 4.17600000 1.14400000 -0.14670000 -0.08680000 0.17350000 -0.08680000 0.11800000 0.02060000 0.17350000 0.02060000 0.02870000 0.00000000 -0.12275374 0.02913280 0.03515018 0.24536605 -0.18717116 +O 0.00000000 1.88300000 1.14400000 0.03010000 -0.00000000 0.00000000 -0.00000000 -0.08910000 0.06180000 0.00000000 0.06180000 0.05900000 0.00000000 0.00000000 0.08739840 0.07225995 0.00000000 0.08428713 +O 1.32400000 4.17600000 1.14400000 -0.14670000 0.08680000 -0.17350000 0.08680000 0.11800000 0.02060000 -0.17350000 0.02060000 0.02870000 0.00000000 0.12275374 0.02913280 0.03515018 -0.24536605 -0.18717116 +O 4.27800000 0.94200000 13.33100000 -0.15480000 -0.08910000 0.16710000 -0.08910000 0.10510000 0.00240000 0.16710000 0.00240000 0.04970000 -0.00000000 -0.12600643 0.00339411 0.06086982 0.23631509 -0.18377705 +O 1.63100000 0.94200000 13.33100000 -0.15480000 0.08910000 -0.16710000 0.08910000 0.10510000 0.00240000 -0.16710000 0.00240000 0.04970000 -0.00000000 0.12600643 0.00339411 0.06086982 -0.23631509 -0.18377705 +O 2.95500000 3.23400000 13.33100000 0.00980000 0.00000000 -0.00000000 0.00000000 -0.07390000 0.08330000 -0.00000000 0.08330000 0.06410000 -0.00000000 0.00000000 0.11780399 0.07850615 0.00000000 0.05918484 +O 4.58600000 0.76400000 5.96800000 -0.12720000 0.10680000 -0.07420000 0.10680000 0.00650000 0.04830000 -0.07420000 0.04830000 0.12070000 -0.00000000 0.15103801 0.06830652 0.14782671 -0.10493465 -0.09454018 +O 2.95500000 3.58900000 5.96800000 0.05600000 0.00000000 0.00000000 0.00000000 -0.17810000 -0.08700000 0.00000000 -0.08700000 0.12210000 0.00000000 0.00000000 -0.12303658 0.14954135 0.00000000 0.16553370 +O 1.32400000 0.76400000 5.96800000 -0.12720000 -0.10680000 0.07420000 -0.10680000 0.00650000 0.04830000 0.07420000 0.04830000 0.12070000 -0.00000000 -0.15103801 0.06830652 0.14782671 0.10493465 -0.09454018 +O 1.32400000 2.64800000 3.68100000 -0.01890000 0.04830000 0.01380000 0.04830000 0.06090000 0.02540000 0.01380000 0.02540000 -0.04190000 -0.00005774 0.06830652 0.03592102 -0.05135763 0.01951615 -0.05642712 +O -1.32400000 2.64800000 3.68100000 -0.01890000 -0.04830000 -0.01380000 -0.04830000 0.06090000 0.02540000 -0.01380000 0.02540000 -0.04190000 -0.00005774 -0.06830652 0.03592102 -0.05135763 -0.01951615 -0.05642712 +O 0.00000000 4.94000000 3.68100000 0.06880000 -0.00000000 0.00000000 -0.00000000 -0.02120000 0.01080000 0.00000000 0.01080000 -0.04770000 0.00005774 0.00000000 0.01527351 -0.05837951 0.00000000 0.06363961 +O 1.63100000 2.47000000 10.79300000 0.04520000 -0.02420000 0.06790000 -0.02420000 0.04390000 -0.01120000 0.06790000 -0.01120000 -0.08900000 -0.00005774 -0.03422397 -0.01583919 -0.10904312 0.09602510 0.00091924 +O 2.95500000 0.17700000 10.79300000 0.01120000 0.00000000 0.00000000 0.00000000 0.08430000 0.07320000 0.00000000 0.07320000 -0.09540000 -0.00005774 0.00000000 0.10352043 -0.11688149 0.00000000 -0.05168951 +O 4.27800000 2.47000000 10.79300000 0.04520000 0.02420000 -0.06790000 0.02420000 0.04390000 -0.01120000 -0.06790000 -0.01120000 -0.08900000 -0.00005774 0.03422397 -0.01583919 -0.10904312 -0.09602510 0.00091924 +O -1.63100000 4.35300000 8.50600000 -0.12280000 0.06940000 0.01710000 0.06940000 -0.03300000 -0.00380000 0.01710000 -0.00380000 0.15580000 -0.00000000 0.09814642 -0.00537401 0.19081525 0.02418305 -0.06349819 +O 1.63100000 4.35300000 8.50600000 -0.12280000 -0.06940000 -0.01710000 -0.06940000 -0.03300000 -0.00380000 -0.01710000 -0.00380000 0.15580000 -0.00000000 -0.09814642 -0.00537401 0.19081525 -0.02418305 -0.06349819 +O 0.00000000 1.52800000 8.50600000 -0.00600000 0.00000000 -0.00000000 0.00000000 -0.15180000 0.01830000 -0.00000000 0.01830000 0.15780000 -0.00000000 0.00000000 0.02588011 0.19326474 0.00000000 0.10309617 +Ti 2.95500000 1.70600000 12.06200000 0.07030000 -0.00000000 0.00000000 -0.00000000 0.17070000 0.08150000 0.00000000 0.08150000 -0.24100000 0.00000000 0.00000000 0.11525841 -0.29516351 0.00000000 -0.07099352 +Ti 0.00000000 3.41200000 2.41200000 0.08370000 0.00000000 -0.00000000 0.00000000 0.16840000 0.08770000 -0.00000000 0.08770000 -0.25210000 0.00000000 0.00000000 0.12402653 -0.30875818 0.00000000 -0.05989194 +Ti -1.47700000 2.55900000 0.00000000 -0.08030000 -0.24840000 0.04920000 -0.24840000 -0.03860000 -0.07620000 0.04920000 -0.07620000 0.11890000 -0.00000000 -0.35129065 -0.10776307 0.14562217 0.06957931 -0.02948635 +Ti 1.47700000 2.55900000 0.00000000 -0.08030000 0.24840000 -0.04920000 0.24840000 -0.03860000 -0.07620000 -0.04920000 -0.07620000 0.11890000 -0.00000000 0.35129065 -0.10776307 0.14562217 -0.06957931 -0.02948635 +Ti 0.00000000 1.70600000 4.82500000 0.10290000 -0.00000000 -0.00000000 0.00000000 -0.14830000 0.18380000 -0.00000000 0.18380000 0.04540000 -0.00000000 0.00000000 0.25993245 0.05560342 0.00000000 0.17762522 +Ti 1.47700000 4.26500000 4.82500000 -0.08800000 -0.10810000 -0.15700000 -0.10810000 0.04130000 -0.08770000 -0.15700000 -0.08770000 0.04670000 -0.00000000 -0.15287649 -0.12402653 0.05719559 -0.22203153 -0.09142891 +Ti -1.47700000 4.26500000 4.82500000 -0.08800000 0.10810000 0.15700000 0.10810000 0.04130000 -0.08770000 0.15700000 -0.08770000 0.04670000 -0.00000000 0.15287649 -0.12402653 0.05719559 0.22203153 -0.09142891 +Ti 2.95500000 3.41200000 9.65000000 0.11030000 -0.00000000 -0.00000000 -0.00000000 -0.16040000 0.18740000 -0.00000000 0.18740000 0.05020000 -0.00005774 0.00000000 0.26502362 0.06144137 0.00000000 0.19141381 +Ti 1.47700000 0.85300000 9.65000000 -0.09530000 -0.11730000 -0.16050000 -0.11730000 0.04440000 -0.09030000 -0.16050000 -0.09030000 0.05090000 -0.00000000 -0.16588725 -0.12770348 0.06233951 -0.22698128 -0.09878282 +Ti 4.43200000 0.85300000 9.65000000 -0.09530000 0.11730000 0.16050000 0.11730000 0.04440000 -0.09030000 0.16050000 -0.09030000 0.05090000 -0.00000000 0.16588725 -0.12770348 0.06233951 0.22698128 -0.09878282 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00230000 -0.00000000 -0.00000000 -0.00000000 0.00450000 -0.00150000 -0.00000000 -0.00150000 -0.00210000 -0.00005774 0.00000000 -0.00212132 -0.00261279 0.00000000 -0.00480833 +Li 0.00000000 0.00000000 12.66500000 -0.00110000 -0.00000000 -0.00000000 -0.00000000 0.00590000 -0.00180000 -0.00000000 -0.00180000 -0.00480000 0.00000000 0.00000000 -0.00254558 -0.00587878 0.00000000 -0.00494975 +Li 2.95500000 1.70600000 6.63400000 -0.01510000 -0.00000000 0.00000000 -0.00000000 0.03490000 -0.01780000 0.00000000 -0.01780000 -0.01980000 0.00000000 0.00000000 -0.02517300 -0.02424995 0.00000000 -0.03535534 +Li 2.95500000 1.70600000 3.01600000 0.01200000 -0.00000000 -0.00000000 -0.00000000 0.01110000 0.00040000 -0.00000000 0.00040000 -0.02320000 0.00005774 0.00000000 0.00056569 -0.02837326 0.00000000 0.00063640 +Li 0.00000000 3.41200000 11.45900000 0.01140000 -0.00000000 0.00000000 -0.00000000 0.01050000 0.00030000 0.00000000 0.00030000 -0.02190000 0.00000000 0.00000000 0.00042426 -0.02682191 0.00000000 0.00063640 +Li 2.95500000 0.00000000 7.23700000 -0.02060000 0.00000000 0.00000000 0.00000000 0.03310000 -0.00560000 0.00000000 -0.00560000 -0.01250000 0.00000000 0.00000000 -0.00791960 -0.01530931 0.00000000 -0.03797163 +Li 0.00000000 0.00000000 7.23700000 0.01140000 -0.00000000 0.00000000 -0.00000000 0.00450000 -0.00110000 0.00000000 -0.00110000 -0.01590000 0.00000000 0.00000000 -0.00155563 -0.01947344 0.00000000 0.00487904 +Li 2.95500000 0.00000000 0.00000000 0.00580000 0.00000000 -0.00000000 0.00000000 0.00570000 -0.00180000 -0.00000000 -0.00180000 -0.01140000 -0.00005774 0.00000000 -0.00254558 -0.01400292 0.00000000 0.00007071 +O 0.00000000 0.00000000 3.80700000 0.06940000 -0.00000000 -0.00000000 -0.00000000 0.08860000 0.01840000 -0.00000000 0.01840000 -0.15800000 0.00000000 0.00000000 0.02602153 -0.19350969 0.00000000 -0.01357645 +O 0.00000000 0.00000000 10.66800000 0.07980000 -0.00000000 0.00000000 -0.00000000 0.10180000 0.01710000 0.00000000 0.01710000 -0.18170000 0.00005774 0.00000000 0.02418305 -0.22249532 0.00000000 -0.01555635 +O 2.95500000 1.70600000 8.63200000 -0.06730000 0.00000000 0.00000000 0.00000000 0.08220000 0.10550000 0.00000000 0.10550000 -0.01490000 0.00000000 0.00000000 0.14919953 -0.01824870 0.00000000 -0.10571246 +O 2.95500000 1.70600000 1.01800000 0.10180000 -0.00000000 -0.00000000 -0.00000000 -0.04890000 -0.17300000 -0.00000000 -0.17300000 -0.05290000 0.00000000 0.00000000 -0.24465895 -0.06478900 0.00000000 0.10656099 +O 0.00000000 3.41200000 13.45700000 0.08700000 0.00000000 0.00000000 0.00000000 -0.04170000 -0.16980000 0.00000000 -0.16980000 -0.04530000 0.00000000 0.00000000 -0.24013346 -0.05548094 0.00000000 0.09100464 +O 0.00000000 3.41200000 5.84300000 -0.01930000 0.00000000 -0.00000000 0.00000000 0.11900000 0.06790000 -0.00000000 0.06790000 -0.09970000 0.00000000 0.00000000 0.09602510 -0.12210706 0.00000000 -0.09779287 +O -1.32400000 4.17600000 1.14400000 -0.15930000 -0.08110000 0.16180000 -0.08110000 0.10950000 0.00390000 0.16180000 0.00390000 0.04980000 -0.00000000 -0.11469272 0.00551543 0.06099229 0.22881975 -0.19007030 +O 0.00000000 1.88300000 1.14400000 0.01710000 0.00000000 0.00000000 0.00000000 -0.08150000 0.07440000 0.00000000 0.07440000 0.06450000 -0.00005774 0.00000000 0.10521749 0.07895522 0.00000000 0.06972073 +O 1.32400000 4.17600000 1.14400000 -0.15930000 0.08110000 -0.16180000 0.08110000 0.10950000 0.00390000 -0.16180000 0.00390000 0.04980000 -0.00000000 0.11469272 0.00551543 0.06099229 -0.22881975 -0.19007030 +O 4.27800000 0.94200000 13.33100000 -0.14790000 -0.08290000 0.16630000 -0.08290000 0.11540000 0.01520000 0.16630000 0.01520000 0.03250000 -0.00000000 -0.11723830 0.02149605 0.03980421 0.23518372 -0.18618122 +O 1.63100000 0.94200000 13.33100000 -0.14790000 0.08290000 -0.16630000 0.08290000 0.11540000 0.01520000 -0.16630000 0.01520000 0.03250000 -0.00000000 0.11723830 0.02149605 0.03980421 -0.23518372 -0.18618122 +O 2.95500000 3.23400000 13.33100000 0.02820000 -0.00000000 -0.00000000 -0.00000000 -0.08740000 0.06430000 -0.00000000 0.06430000 0.05920000 -0.00000000 0.00000000 0.09093393 0.07250490 0.00000000 0.08174154 +O 4.58600000 0.76400000 5.96800000 -0.04120000 0.16480000 -0.16110000 0.16480000 -0.01680000 0.02310000 -0.16110000 0.02310000 0.05800000 -0.00000000 0.23306240 0.03266833 0.07103520 -0.22782980 -0.01725341 +O 2.95500000 3.58900000 5.96800000 0.06400000 -0.00000000 0.00000000 -0.00000000 -0.15960000 -0.08680000 0.00000000 -0.08680000 0.09570000 -0.00005774 0.00000000 -0.12275374 0.11716726 0.00000000 0.15810908 +O 1.32400000 0.76400000 5.96800000 -0.04120000 -0.16480000 0.16110000 -0.16480000 -0.01680000 0.02310000 0.16110000 0.02310000 0.05800000 -0.00000000 -0.23306240 0.03266833 0.07103520 0.22782980 -0.01725341 +O 1.32400000 2.64800000 3.68100000 -0.02300000 0.05840000 -0.02260000 0.05840000 0.05770000 0.02640000 -0.02260000 0.02640000 -0.03470000 0.00000000 0.08259007 0.03733524 -0.04249865 -0.03196123 -0.05706352 +O -1.32400000 2.64800000 3.68100000 -0.02300000 -0.05840000 0.02260000 -0.05840000 0.05770000 0.02640000 0.02260000 0.02640000 -0.03470000 0.00000000 -0.08259007 0.03733524 -0.04249865 0.03196123 -0.05706352 +O 0.00000000 4.94000000 3.68100000 0.06660000 -0.00000000 -0.00000000 -0.00000000 -0.02540000 -0.00310000 -0.00000000 -0.00310000 -0.04120000 0.00000000 0.00000000 -0.00438406 -0.05045949 0.00000000 0.06505382 +O 1.63100000 2.47000000 10.79300000 -0.02630000 0.06110000 -0.03130000 0.06110000 0.05450000 0.03760000 -0.03130000 0.03760000 -0.02820000 0.00000000 0.08640845 0.05317443 -0.03453781 -0.04426488 -0.05713423 +O 2.95500000 0.17700000 10.79300000 0.06570000 0.00000000 0.00000000 0.00000000 -0.03300000 -0.01450000 0.00000000 -0.01450000 -0.03270000 0.00000000 0.00000000 -0.02050610 -0.04004916 0.00000000 0.06979144 +O 4.27800000 2.47000000 10.79300000 -0.02630000 -0.06110000 0.03130000 -0.06110000 0.05450000 0.03760000 0.03130000 0.03760000 -0.02820000 0.00000000 -0.08640845 0.05317443 -0.03453781 0.04426488 -0.05713423 +O -1.63100000 4.35300000 8.50600000 -0.11740000 0.20700000 -0.14470000 0.20700000 0.01590000 0.02000000 -0.14470000 0.02000000 0.10150000 -0.00000000 0.29274221 0.02828427 0.12431160 -0.20463670 -0.09425733 +O 1.63100000 4.35300000 8.50600000 -0.11740000 -0.20700000 0.14470000 -0.20700000 0.01590000 0.02000000 0.14470000 0.02000000 0.10150000 -0.00000000 -0.29274221 0.02828427 0.12431160 0.20463670 -0.09425733 +O 0.00000000 1.52800000 8.50600000 0.08370000 -0.00000000 -0.00000000 -0.00000000 -0.21000000 -0.10370000 -0.00000000 -0.10370000 0.12630000 -0.00000000 0.00000000 -0.14665395 0.15468528 0.00000000 0.20767726 +Ti 2.95500000 1.70600000 12.06200000 0.08100000 0.00000000 -0.00000000 0.00000000 0.16990000 0.08550000 -0.00000000 0.08550000 -0.25100000 0.00005774 0.00000000 0.12091526 -0.30737014 0.00000000 -0.06286179 +Ti 0.00000000 3.41200000 2.41200000 0.07420000 -0.00000000 0.00000000 -0.00000000 0.17140000 0.08100000 0.00000000 0.08100000 -0.24570000 0.00005774 0.00000000 0.11455130 -0.30087899 0.00000000 -0.06873078 +Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24890000 0.04850000 -0.24890000 -0.03780000 -0.07620000 0.04850000 -0.07620000 0.11880000 0.00000000 -0.35199776 -0.10776307 0.14549969 0.06858936 -0.03054701 +Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24890000 -0.04850000 0.24890000 -0.03780000 -0.07620000 -0.04850000 -0.07620000 0.11880000 0.00000000 0.35199776 -0.10776307 0.14549969 -0.06858936 -0.03054701 +Ti 0.00000000 1.70600000 4.82500000 0.08870000 0.00000000 0.00000000 0.00000000 -0.12920000 0.18630000 0.00000000 0.18630000 0.04050000 -0.00000000 0.00000000 0.26346799 0.04960217 0.00000000 0.15407857 +Ti 1.47700000 4.26500000 4.82500000 -0.09120000 -0.08730000 -0.18610000 -0.08730000 0.04860000 -0.07900000 -0.18610000 -0.07900000 0.04260000 0.00000000 -0.12346084 -0.11172287 0.05217413 -0.26318514 -0.09885353 +Ti -1.47700000 4.26500000 4.82500000 -0.09120000 0.08730000 0.18610000 0.08730000 0.04860000 -0.07900000 0.18610000 -0.07900000 0.04260000 0.00000000 0.12346084 -0.11172287 0.05217413 0.26318514 -0.09885353 +Ti 2.95500000 3.41200000 9.65000000 0.06480000 0.00000000 -0.00000000 0.00000000 -0.12360000 0.23790000 -0.00000000 0.23790000 0.05870000 0.00005774 0.00000000 0.33644141 0.07193335 0.00000000 0.13321892 +Ti 1.47700000 0.85300000 9.65000000 -0.09310000 -0.07800000 -0.22360000 -0.07800000 0.03210000 -0.09990000 -0.22360000 -0.09990000 0.06100000 0.00000000 -0.11030866 -0.14127993 0.07470944 -0.31621815 -0.08852977 +Ti 4.43200000 0.85300000 9.65000000 -0.09310000 0.07800000 0.22360000 0.07800000 0.03210000 -0.09990000 0.22360000 -0.09990000 0.06100000 0.00000000 0.11030866 -0.14127993 0.07470944 0.31621815 -0.08852977 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00220000 -0.00000000 -0.00010000 -0.00000000 0.00370000 -0.00140000 -0.00010000 -0.00140000 -0.00150000 0.00000000 0.00000000 -0.00197990 -0.00183712 -0.00014142 -0.00417193 +Li 0.00000000 0.00000000 12.66500000 -0.00100000 -0.00000000 -0.00010000 -0.00000000 0.00510000 -0.00160000 -0.00010000 -0.00160000 -0.00410000 0.00000000 0.00000000 -0.00226274 -0.00502145 -0.00014142 -0.00431335 +Li 2.95500000 1.70600000 6.63400000 0.02180000 -0.02130000 -0.01540000 -0.02130000 -0.00270000 0.00890000 -0.01540000 0.00890000 -0.01910000 0.00000000 -0.03012275 0.01258650 -0.02339263 -0.02177889 0.01732412 +Li 2.95500000 1.70600000 3.01600000 0.01200000 0.00010000 0.00000000 0.00010000 0.01050000 0.00040000 0.00000000 0.00040000 -0.02250000 0.00000000 0.00014142 0.00056569 -0.02755676 0.00000000 0.00106066 +Li 0.00000000 3.41200000 11.45900000 0.01130000 0.00010000 0.00000000 0.00010000 0.00990000 0.00030000 0.00000000 0.00030000 -0.02120000 0.00000000 0.00014142 0.00042426 -0.02596459 0.00000000 0.00098995 +Li 1.47700000 2.55900000 7.23700000 0.01920000 -0.02290000 -0.00480000 -0.02290000 -0.00730000 0.00280000 -0.00480000 0.00280000 -0.01190000 0.00000000 -0.03238549 0.00395980 -0.01457446 -0.00678823 0.01873833 +Li 0.00000000 0.00000000 7.23700000 0.00560000 0.00340000 -0.00100000 0.00340000 0.00960000 0.00060000 -0.00100000 0.00060000 -0.01520000 0.00000000 0.00480833 0.00084853 -0.01861612 -0.00141421 -0.00282843 +Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 -0.00000000 -0.00000000 0.00480000 -0.00180000 -0.00000000 -0.00180000 -0.01070000 0.00005774 0.00000000 -0.00254558 -0.01306395 0.00000000 0.00070711 +O 0.00000000 0.00000000 3.80700000 0.07620000 -0.00360000 0.02070000 -0.00360000 0.07480000 -0.01690000 0.02070000 -0.01690000 -0.15100000 0.00000000 -0.00509117 -0.02390021 -0.18493648 0.02927422 0.00098995 +O 0.00000000 0.00000000 10.66800000 0.08860000 -0.00480000 0.02040000 -0.00480000 0.08610000 -0.01770000 0.02040000 -0.01770000 -0.17470000 0.00000000 -0.00678823 -0.02503158 -0.21396293 0.02884996 0.00176777 +O 2.95500000 1.70600000 8.63200000 0.03910000 -0.06090000 0.09240000 -0.06090000 -0.03170000 -0.05420000 0.09240000 -0.05420000 -0.00740000 0.00000000 -0.08612561 -0.07665038 -0.00906311 0.13067333 0.05006316 +O 2.95500000 1.70600000 1.01800000 0.10390000 -0.00050000 0.00080000 -0.00050000 -0.05930000 -0.17410000 0.00080000 -0.17410000 -0.04470000 0.00005774 -0.00070711 -0.24621458 -0.05470527 0.00113137 0.11539983 +O 0.00000000 3.41200000 13.45700000 0.08930000 -0.00050000 0.00080000 -0.00050000 -0.05200000 -0.17110000 0.00080000 -0.17110000 -0.03730000 0.00000000 -0.00070711 -0.24197194 -0.04568298 0.00113137 0.09991419 +O 0.00000000 3.41200000 5.84300000 0.07920000 -0.05650000 0.06010000 -0.05650000 0.01340000 -0.03600000 0.06010000 -0.03600000 -0.09260000 0.00000000 -0.07990307 -0.05091169 -0.11341138 0.08499424 0.04652763 +O -1.32400000 4.17600000 1.14400000 -0.15650000 -0.08220000 0.16440000 -0.08220000 0.10090000 0.00520000 0.16440000 0.00520000 0.05560000 -0.00000000 -0.11624835 0.00735391 0.06809581 0.23249671 -0.18200929 +O 0.00000000 1.88300000 1.14400000 0.01950000 -0.00210000 -0.00150000 -0.00210000 -0.09380000 0.07380000 -0.00150000 0.07380000 0.07420000 0.00005774 -0.00296985 0.10436896 0.09091689 -0.00212132 0.08011520 +O 1.32400000 4.17600000 1.14400000 -0.15650000 0.07920000 -0.16410000 0.07920000 0.09830000 0.00830000 -0.16410000 0.00830000 0.05820000 -0.00000000 0.11200571 0.01173797 0.07128015 -0.23207245 -0.18017081 +O 4.27800000 0.94200000 13.33100000 -0.14560000 -0.08420000 0.16820000 -0.08420000 0.10680000 0.01640000 0.16820000 0.01640000 0.03880000 -0.00000000 -0.11907678 0.02319310 0.04752010 0.23787072 -0.17847375 +O 1.63100000 0.94200000 13.33100000 -0.14560000 0.08130000 -0.16830000 0.08130000 0.10450000 0.01900000 -0.16830000 0.01900000 0.04110000 0.00000000 0.11497556 0.02687006 0.05033701 -0.23801214 -0.17684741 +O 2.95500000 3.23400000 13.33100000 0.03050000 -0.00190000 -0.00130000 -0.00190000 -0.09940000 0.06380000 -0.00130000 0.06380000 0.06890000 -0.00000000 -0.00268701 0.09022683 0.08438492 -0.00183848 0.09185317 +O 4.58600000 0.76400000 5.96800000 -0.10920000 0.10280000 -0.07390000 0.10280000 0.00750000 0.04830000 -0.07390000 0.04830000 0.10160000 0.00005774 0.14538115 0.06830652 0.12447490 -0.10451038 -0.08251936 +O 2.95500000 3.58900000 5.96800000 0.11280000 0.07430000 -0.06480000 0.07430000 -0.18230000 -0.14960000 -0.06480000 -0.14960000 0.06950000 -0.00000000 0.10507607 -0.21156635 0.08511977 -0.09164104 0.20866721 +O 1.32400000 0.76400000 5.96800000 -0.17120000 -0.09180000 0.09580000 -0.09180000 0.10430000 0.13530000 0.09580000 0.13530000 0.06690000 -0.00000000 -0.12982481 0.19134309 0.08193543 0.13548166 -0.19480792 +O 1.32400000 2.64800000 3.68100000 -0.02220000 0.04730000 0.00150000 0.04730000 0.04800000 0.02610000 0.00150000 0.02610000 -0.02590000 0.00005774 0.06689230 0.03691097 -0.03168007 0.00212132 -0.04963890 +O -1.32400000 2.64800000 3.68100000 -0.02980000 -0.05570000 0.01170000 -0.05570000 0.05630000 0.04570000 0.01170000 0.04570000 -0.02640000 -0.00005774 -0.07877170 0.06462956 -0.03237409 0.01654630 -0.06088189 +O 0.00000000 4.94000000 3.68100000 0.07900000 0.00320000 -0.01110000 0.00320000 -0.04550000 -0.02420000 -0.01110000 -0.02420000 -0.03350000 0.00000000 0.00452548 -0.03422397 -0.04102895 -0.01569777 0.08803479 +O 1.63100000 2.47000000 10.79300000 -0.02710000 0.05180000 -0.00850000 0.05180000 0.04580000 0.03660000 -0.00850000 0.03660000 -0.01880000 0.00005774 0.07325626 0.05176022 -0.02298438 -0.01202082 -0.05154808 +O 2.95500000 0.17700000 10.79300000 0.07620000 0.00340000 -0.00990000 0.00340000 -0.05070000 -0.03480000 -0.00990000 -0.03480000 -0.02550000 0.00000000 0.00480833 -0.04921463 -0.03123099 -0.01400071 0.08973185 +O 4.27800000 2.47000000 10.79300000 -0.03370000 -0.05810000 0.02150000 -0.05810000 0.05350000 0.05470000 0.02150000 0.05470000 -0.01980000 0.00000000 -0.08216581 0.07735748 -0.02424995 0.03040559 -0.06165971 +O -1.63100000 4.35300000 8.50600000 -0.14250000 0.13320000 -0.08880000 0.13320000 0.00940000 0.05690000 -0.08880000 0.05690000 0.13310000 -0.00000000 0.18837325 0.08046875 0.16301354 -0.12558216 -0.10740952 +O 1.63100000 4.35300000 8.50600000 -0.20270000 -0.16030000 0.08500000 -0.16030000 0.09240000 0.12250000 0.08500000 0.12250000 0.11040000 -0.00005774 -0.22669843 0.17324116 0.13517101 0.12020815 -0.20866721 +O 0.00000000 1.52800000 8.50600000 0.15580000 0.04850000 -0.05920000 0.04850000 -0.26840000 -0.13370000 -0.05920000 -0.13370000 0.11260000 0.00000000 0.06858936 -0.18908035 0.13790627 -0.08372144 0.29995470 +Ti 2.95500000 1.70600000 12.06200000 0.08150000 -0.00010000 -0.00090000 -0.00010000 0.16970000 0.08700000 -0.00090000 0.08700000 -0.25130000 0.00005774 -0.00014142 0.12303658 -0.30773756 -0.00127279 -0.06236682 +Ti 0.00000000 3.41200000 2.41200000 0.07560000 -0.00050000 -0.00110000 -0.00050000 0.17060000 0.08300000 -0.00110000 0.08300000 -0.24610000 -0.00005774 -0.00070711 0.11737973 -0.30145054 -0.00155563 -0.06717514 +Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24910000 0.04860000 -0.24910000 -0.03780000 -0.07610000 0.04860000 -0.07610000 0.11880000 0.00000000 -0.35228060 -0.10762165 0.14549969 0.06873078 -0.03054701 +Ti 1.47700000 2.55900000 0.00000000 -0.08120000 0.24900000 -0.04870000 0.24900000 -0.03770000 -0.07580000 -0.04870000 -0.07580000 0.11890000 -0.00000000 0.35213918 -0.10719739 0.14562217 -0.06887220 -0.03075914 +Ti 0.00000000 1.70600000 4.82500000 0.08850000 -0.01510000 0.02290000 -0.01510000 -0.12990000 0.20370000 0.02290000 0.20370000 0.04140000 -0.00000000 -0.02135462 0.28807530 0.05070444 0.03238549 0.15443212 +Ti 1.47700000 4.26500000 4.82500000 -0.06510000 -0.10240000 -0.16330000 -0.10240000 0.02250000 -0.11830000 -0.16330000 -0.11830000 0.04260000 0.00000000 -0.14481547 -0.16730146 0.05217413 -0.23094107 -0.06194255 +Ti -1.47700000 4.26500000 4.82500000 -0.07760000 0.09490000 0.15980000 0.09490000 0.03600000 -0.09020000 0.15980000 -0.09020000 0.04170000 -0.00005774 0.13420887 -0.12756206 0.05103104 0.22599133 -0.08032733 +Ti 2.95500000 3.41200000 9.65000000 0.06870000 -0.01340000 0.02320000 -0.01340000 -0.12820000 0.24740000 0.02320000 0.24740000 0.05960000 -0.00005774 -0.01895046 0.34987644 0.07295397 0.03280975 0.13922933 +Ti 1.47700000 0.85300000 9.65000000 -0.07010000 -0.09140000 -0.20050000 -0.09140000 0.00910000 -0.13970000 -0.20050000 -0.13970000 0.06100000 -0.00000000 -0.12925912 -0.19756563 0.07470944 -0.28354982 -0.05600286 +Ti 4.43200000 0.85300000 9.65000000 -0.07910000 0.08120000 0.20420000 0.08120000 0.01890000 -0.11540000 0.20420000 -0.11540000 0.06020000 -0.00000000 0.11483414 -0.16320025 0.07372964 0.28878241 -0.06929646 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00220000 0.00000000 0.00010000 0.00000000 0.00370000 -0.00140000 0.00010000 -0.00140000 -0.00150000 0.00000000 0.00000000 -0.00197990 -0.00183712 0.00014142 -0.00417193 +Li 0.00000000 0.00000000 12.66500000 -0.00100000 0.00000000 0.00010000 0.00000000 0.00510000 -0.00160000 0.00010000 -0.00160000 -0.00410000 0.00000000 0.00000000 -0.00226274 -0.00502145 0.00014142 -0.00431335 +Li 2.95500000 1.70600000 6.63400000 0.02180000 0.02130000 0.01540000 0.02130000 -0.00270000 0.00890000 0.01540000 0.00890000 -0.01910000 0.00000000 0.03012275 0.01258650 -0.02339263 0.02177889 0.01732412 +Li 2.95500000 1.70600000 3.01600000 0.01200000 -0.00010000 -0.00000000 -0.00010000 0.01050000 0.00040000 -0.00000000 0.00040000 -0.02250000 0.00000000 -0.00014142 0.00056569 -0.02755676 0.00000000 0.00106066 +Li 0.00000000 3.41200000 11.45900000 0.01130000 -0.00010000 -0.00000000 -0.00010000 0.00990000 0.00030000 -0.00000000 0.00030000 -0.02120000 0.00000000 -0.00014142 0.00042426 -0.02596459 0.00000000 0.00098995 +Li -1.47700000 2.55900000 7.23700000 0.01910000 0.02290000 0.00490000 0.02290000 -0.00730000 0.00280000 0.00490000 0.00280000 -0.01190000 0.00005774 0.03238549 0.00395980 -0.01453364 0.00692965 0.01866762 +Li 0.00000000 0.00000000 7.23700000 0.00560000 -0.00340000 0.00100000 -0.00340000 0.00970000 0.00060000 0.00100000 0.00060000 -0.01520000 -0.00005774 -0.00480833 0.00084853 -0.01865695 0.00141421 -0.00289914 +Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00010000 0.00000000 -0.00010000 0.00480000 -0.00180000 0.00000000 -0.00180000 -0.01070000 0.00005774 -0.00014142 -0.00254558 -0.01306395 0.00000000 0.00070711 +O 0.00000000 0.00000000 3.80700000 0.07620000 0.00170000 -0.02040000 0.00170000 0.07480000 -0.01690000 -0.02040000 -0.01690000 -0.15100000 0.00000000 0.00240416 -0.02390021 -0.18493648 -0.02884996 0.00098995 +O 0.00000000 0.00000000 10.66800000 0.08850000 0.00280000 -0.02010000 0.00280000 0.08610000 -0.01770000 -0.02010000 -0.01770000 -0.17470000 0.00005774 0.00395980 -0.02503158 -0.21392210 -0.02842569 0.00169706 +O 2.95500000 1.70600000 8.63200000 0.03910000 0.05970000 -0.09210000 0.05970000 -0.03180000 -0.05410000 -0.09210000 -0.05410000 -0.00730000 0.00000000 0.08442855 -0.07650895 -0.00894064 -0.13024907 0.05013387 +O 2.95500000 1.70600000 1.01800000 0.10390000 -0.00080000 -0.00030000 -0.00080000 -0.05930000 -0.17410000 -0.00030000 -0.17410000 -0.04470000 0.00005774 -0.00113137 -0.24621458 -0.05470527 -0.00042426 0.11539983 +O 0.00000000 3.41200000 13.45700000 0.08930000 -0.00080000 -0.00030000 -0.00080000 -0.05200000 -0.17110000 -0.00030000 -0.17110000 -0.03730000 0.00000000 -0.00113137 -0.24197194 -0.04568298 -0.00042426 0.09991419 +O 0.00000000 3.41200000 5.84300000 0.07920000 0.05510000 -0.05980000 0.05510000 0.01330000 -0.03590000 -0.05980000 -0.03590000 -0.09250000 0.00000000 0.07792317 -0.05077027 -0.11328890 -0.08456997 0.04659834 +O -1.32400000 4.17600000 1.14400000 -0.15690000 -0.08220000 0.16420000 -0.08220000 0.10050000 0.00810000 0.16420000 0.00810000 0.05640000 0.00000000 -0.11624835 0.01145513 0.06907561 0.23221387 -0.18200929 +O 0.00000000 1.88300000 1.14400000 0.01950000 -0.00190000 0.00130000 -0.00190000 -0.09380000 0.07380000 0.00130000 0.07380000 0.07420000 0.00005774 -0.00268701 0.10436896 0.09091689 0.00183848 0.08011520 +O 1.32400000 4.17600000 1.14400000 -0.15610000 0.07920000 -0.16420000 0.07920000 0.09880000 0.00550000 -0.16420000 0.00550000 0.05730000 -0.00000000 0.11200571 0.00777817 0.07017788 -0.23221387 -0.18024152 +O 4.27800000 0.94200000 13.33100000 -0.14600000 -0.08430000 0.16840000 -0.08430000 0.10670000 0.01880000 0.16840000 0.01880000 0.03930000 -0.00000000 -0.11921820 0.02658721 0.04813247 0.23815356 -0.17868588 +O 1.63100000 0.94200000 13.33100000 -0.14520000 0.08110000 -0.16810000 0.08110000 0.10460000 0.01670000 -0.16810000 0.01670000 0.04060000 0.00000000 0.11469272 0.02361737 0.04972464 -0.23772930 -0.17663527 +O 2.95500000 3.23400000 13.33100000 0.03050000 -0.00200000 0.00120000 -0.00200000 -0.09940000 0.06380000 0.00120000 0.06380000 0.06890000 -0.00000000 -0.00282843 0.09022683 0.08438492 0.00169706 0.09185317 +O 4.58600000 0.76400000 5.96800000 -0.17170000 0.08870000 -0.09590000 0.08870000 0.10600000 0.13540000 -0.09590000 0.13540000 0.06570000 0.00000000 0.12544074 0.19148452 0.08046574 -0.13562308 -0.19636355 +O 2.95500000 3.58900000 5.96800000 0.11290000 -0.07910000 0.06510000 -0.07910000 -0.18220000 -0.14970000 0.06510000 -0.14970000 0.06930000 -0.00000000 -0.11186429 -0.21170777 0.08487482 0.09206530 0.20866721 +O 1.32400000 0.76400000 5.96800000 -0.10870000 -0.10600000 0.07390000 -0.10600000 0.00570000 0.04820000 0.07390000 0.04820000 0.10300000 0.00000000 -0.14990664 0.06816509 0.12614872 0.10451038 -0.08089302 +O 1.32400000 2.64800000 3.68100000 -0.03020000 0.05260000 -0.01170000 0.05260000 0.05780000 0.04570000 -0.01170000 0.04570000 -0.02760000 0.00000000 0.07438763 0.06462956 -0.03380296 -0.01654630 -0.06222540 +O -1.32400000 2.64800000 3.68100000 -0.02180000 -0.05040000 -0.00140000 -0.05040000 0.04640000 0.02610000 -0.00140000 0.02610000 -0.02460000 0.00000000 -0.07127636 0.03691097 -0.03012872 -0.00197990 -0.04822468 +O 0.00000000 4.94000000 3.68100000 0.07900000 -0.00800000 0.01120000 -0.00800000 -0.04550000 -0.02420000 0.01120000 -0.02420000 -0.03350000 0.00000000 -0.01131371 -0.03422397 -0.04102895 0.01583919 0.08803479 +O 1.63100000 2.47000000 10.79300000 -0.03400000 0.05510000 -0.02150000 0.05510000 0.05500000 0.05470000 -0.02150000 0.05470000 -0.02100000 0.00000000 0.07792317 0.07735748 -0.02571964 -0.03040559 -0.06293250 +O 2.95500000 0.17700000 10.79300000 0.07620000 -0.00820000 0.01000000 -0.00820000 -0.05070000 -0.03480000 0.01000000 -0.03480000 -0.02550000 0.00000000 -0.01159655 -0.04921463 -0.03123099 0.01414214 0.08973185 +O 4.27800000 2.47000000 10.79300000 -0.02670000 -0.05490000 0.00860000 -0.05490000 0.04430000 0.03660000 0.00860000 0.03660000 -0.01750000 -0.00005774 -0.07764032 0.05176022 -0.02147386 0.01216224 -0.05020458 +O -1.63100000 4.35300000 8.50600000 -0.20330000 0.15720000 -0.08510000 0.15720000 0.09410000 0.12260000 -0.08510000 0.12260000 0.10920000 -0.00000000 0.22231437 0.17338258 0.13374214 -0.12034957 -0.21029356 +O 1.63100000 4.35300000 8.50600000 -0.14190000 -0.13630000 0.08880000 -0.13630000 0.00760000 0.05680000 0.08880000 0.05680000 0.13430000 -0.00000000 -0.19275731 0.08032733 0.16448324 0.12558216 -0.10571246 +O 0.00000000 1.52800000 8.50600000 0.15590000 -0.05330000 0.05950000 -0.05330000 -0.26820000 -0.13380000 0.05950000 -0.13380000 0.11230000 -0.00000000 -0.07537758 -0.18922177 0.13753885 0.08414571 0.29988399 +Ti 2.95500000 1.70600000 12.06200000 0.08150000 -0.00000000 0.00100000 -0.00000000 0.16970000 0.08700000 0.00100000 0.08700000 -0.25130000 0.00005774 0.00000000 0.12303658 -0.30773756 0.00141421 -0.06236682 +Ti 0.00000000 3.41200000 2.41200000 0.07560000 0.00040000 0.00120000 0.00040000 0.17060000 0.08300000 0.00120000 0.08300000 -0.24610000 -0.00005774 0.00056569 0.11737973 -0.30145054 0.00169706 -0.06717514 +Ti -1.47700000 2.55900000 0.00000000 -0.08120000 -0.24910000 0.04890000 -0.24910000 -0.03760000 -0.07580000 0.04890000 -0.07580000 0.11880000 -0.00000000 -0.35228060 -0.10719739 0.14549969 0.06915504 -0.03082986 +Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24900000 -0.04850000 0.24900000 -0.03790000 -0.07600000 -0.04850000 -0.07600000 0.11890000 -0.00000000 0.35213918 -0.10748023 0.14562217 -0.06858936 -0.03047630 +Ti 0.00000000 1.70600000 4.82500000 0.08850000 0.01500000 -0.02280000 0.01500000 -0.12990000 0.20370000 -0.02280000 0.20370000 0.04150000 -0.00005774 0.02121320 0.28807530 0.05078609 -0.03224407 0.15443212 +Ti 1.47700000 4.26500000 4.82500000 -0.07760000 -0.09500000 -0.15970000 -0.09500000 0.03590000 -0.09020000 -0.15970000 -0.09020000 0.04170000 -0.00000000 -0.13435029 -0.12756206 0.05107186 -0.22584991 -0.08025662 +Ti -1.47700000 4.26500000 4.82500000 -0.06510000 0.10240000 0.16340000 0.10240000 0.02250000 -0.11830000 0.16340000 -0.11830000 0.04250000 0.00005774 0.14481547 -0.16730146 0.05209248 0.23108250 -0.06194255 +Ti 2.95500000 3.41200000 9.65000000 0.06870000 0.01320000 -0.02310000 0.01320000 -0.12830000 0.24740000 -0.02310000 0.24740000 0.05960000 0.00000000 0.01866762 0.34987644 0.07299479 -0.03266833 0.13930004 +Ti 1.47700000 0.85300000 9.65000000 -0.07910000 -0.08130000 -0.20410000 -0.08130000 0.01890000 -0.11540000 -0.20410000 -0.11540000 0.06020000 -0.00000000 -0.11497556 -0.16320025 0.07372964 -0.28864099 -0.06929646 +Ti 4.43200000 0.85300000 9.65000000 -0.07010000 0.09130000 0.20060000 0.09130000 0.00920000 -0.13970000 0.20060000 -0.13970000 0.06100000 -0.00005774 0.12911770 -0.19756563 0.07466861 0.28369124 -0.05607357 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 -0.00180000 0.00000000 -0.00000000 0.00000000 0.00510000 -0.00160000 -0.00000000 -0.00160000 -0.00340000 0.00005774 0.00000000 -0.00226274 -0.00412331 0.00000000 -0.00487904 +Li 0.00000000 0.00000000 12.66500000 -0.00180000 0.00000000 -0.00000000 0.00000000 0.00510000 -0.00160000 -0.00000000 -0.00160000 -0.00340000 0.00005774 0.00000000 -0.00226274 -0.00412331 0.00000000 -0.00487904 +Li 2.95500000 1.70600000 6.63400000 0.00270000 0.00000000 -0.00000000 0.00000000 0.00360000 0.00000000 -0.00000000 0.00000000 -0.00630000 0.00000000 0.00000000 0.00000000 -0.00771589 0.00000000 -0.00063640 +Li 2.95500000 1.70600000 3.01600000 0.01140000 0.00000000 -0.00000000 0.00000000 0.01070000 0.00040000 -0.00000000 0.00040000 -0.02220000 0.00005774 0.00000000 0.00056569 -0.02714851 0.00000000 0.00049497 +Li 0.00000000 3.41200000 11.45900000 0.01140000 -0.00000000 0.00000000 -0.00000000 0.01070000 0.00040000 0.00000000 0.00040000 -0.02220000 0.00005774 0.00000000 0.00056569 -0.02714851 0.00000000 0.00049497 +Li 0.00000000 3.41200000 7.84000000 0.00270000 -0.00000000 0.00000000 -0.00000000 0.00360000 0.00000000 0.00000000 0.00000000 -0.00630000 0.00000000 0.00000000 0.00000000 -0.00771589 0.00000000 -0.00063640 +Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 0.00000000 -0.00000000 0.00760000 0.00000000 0.00000000 0.00000000 -0.01430000 0.00000000 0.00000000 0.00000000 -0.01751385 0.00000000 -0.00063640 +Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 0.00000000 -0.00000000 0.00560000 -0.00180000 0.00000000 -0.00180000 -0.01140000 0.00000000 0.00000000 -0.00254558 -0.01396209 0.00000000 0.00014142 +O 0.00000000 0.00000000 3.80700000 0.07490000 0.00000000 -0.00000000 0.00000000 0.08800000 -0.00560000 -0.00000000 -0.00560000 -0.16290000 0.00000000 0.00000000 -0.00791960 -0.19951094 0.00000000 -0.00926310 +O 0.00000000 0.00000000 10.66800000 0.07490000 -0.00000000 -0.00000000 -0.00000000 0.08800000 -0.00560000 -0.00000000 -0.00560000 -0.16290000 0.00000000 0.00000000 -0.00791960 -0.19951094 0.00000000 -0.00926310 +O 2.95500000 1.70600000 8.63200000 0.02430000 0.00000000 -0.00000000 0.00000000 0.03460000 -0.00100000 -0.00000000 -0.00100000 -0.05890000 0.00000000 0.00000000 -0.00141421 -0.07213747 0.00000000 -0.00728320 +O 2.95500000 1.70600000 1.01800000 0.09540000 -0.00000000 0.00000000 -0.00000000 -0.04850000 -0.17550000 0.00000000 -0.17550000 -0.04690000 0.00000000 0.00000000 -0.24819448 -0.05744053 0.00000000 0.10175267 +O 0.00000000 3.41200000 13.45700000 0.09540000 0.00000000 0.00000000 0.00000000 -0.04850000 -0.17550000 0.00000000 -0.17550000 -0.04690000 0.00000000 0.00000000 -0.24819448 -0.05744053 0.00000000 0.10175267 +O 0.00000000 3.41200000 5.84300000 0.02430000 -0.00000000 -0.00000000 -0.00000000 0.03460000 -0.00100000 -0.00000000 -0.00100000 -0.05890000 0.00000000 0.00000000 -0.00141421 -0.07213747 0.00000000 -0.00728320 +O -1.32400000 4.17600000 1.14400000 -0.15480000 -0.08500000 0.16800000 -0.08500000 0.11370000 0.01190000 0.16800000 0.01190000 0.04110000 0.00000000 -0.12020815 0.01682914 0.05033701 0.23758788 -0.18985817 +O 0.00000000 1.88300000 1.14400000 0.02220000 -0.00000000 0.00000000 -0.00000000 -0.08430000 0.06970000 0.00000000 0.06970000 0.06200000 0.00005774 0.00000000 0.09857069 0.07597501 0.00000000 0.07530687 +O 1.32400000 4.17600000 1.14400000 -0.15480000 0.08500000 -0.16800000 0.08500000 0.11370000 0.01190000 -0.16800000 0.01190000 0.04110000 0.00000000 0.12020815 0.01682914 0.05033701 -0.23758788 -0.18985817 +O 4.27800000 0.94200000 13.33100000 -0.15480000 -0.08500000 0.16800000 -0.08500000 0.11370000 0.01190000 0.16800000 0.01190000 0.04110000 0.00000000 -0.12020815 0.01682914 0.05033701 0.23758788 -0.18985817 +O 1.63100000 0.94200000 13.33100000 -0.15480000 0.08500000 -0.16800000 0.08500000 0.11370000 0.01190000 -0.16800000 0.01190000 0.04110000 0.00000000 0.12020815 0.01682914 0.05033701 -0.23758788 -0.18985817 +O 2.95500000 3.23400000 13.33100000 0.02220000 -0.00000000 -0.00000000 -0.00000000 -0.08430000 0.06970000 -0.00000000 0.06970000 0.06200000 0.00005774 0.00000000 0.09857069 0.07597501 0.00000000 0.07530687 +O 4.58600000 0.76400000 5.96800000 -0.12660000 0.12620000 -0.08690000 0.12620000 0.02960000 0.05540000 -0.08690000 0.05540000 0.09690000 0.00005774 0.17847375 0.07834743 0.11871860 -0.12289516 -0.11045008 +O 2.95500000 3.58900000 5.96800000 0.09040000 0.00000000 0.00000000 0.00000000 -0.18860000 -0.10150000 0.00000000 -0.10150000 0.09830000 -0.00005774 0.00000000 -0.14354268 0.12035160 0.00000000 0.19728279 +O 1.32400000 0.76400000 5.96800000 -0.12660000 -0.12620000 0.08690000 -0.12620000 0.02960000 0.05540000 0.08690000 0.05540000 0.09690000 0.00005774 -0.17847375 0.07834743 0.11871860 0.12289516 -0.11045008 +O 1.32400000 2.64800000 3.68100000 -0.02330000 0.04890000 -0.01270000 0.04890000 0.05840000 0.03630000 -0.01270000 0.03630000 -0.03510000 0.00000000 0.06915504 0.05133595 -0.04298854 -0.01796051 -0.05777062 +O -1.32400000 2.64800000 3.68100000 -0.02330000 -0.04890000 0.01270000 -0.04890000 0.05840000 0.03630000 0.01270000 0.03630000 -0.03510000 0.00000000 -0.06915504 0.05133595 -0.04298854 0.01796051 -0.05777062 +O 0.00000000 4.94000000 3.68100000 0.06750000 0.00000000 0.00000000 0.00000000 -0.02570000 -0.01930000 0.00000000 -0.01930000 -0.04180000 0.00000000 0.00000000 -0.02729432 -0.05119434 0.00000000 0.06590235 +O 1.63100000 2.47000000 10.79300000 -0.02330000 0.04890000 -0.01270000 0.04890000 0.05840000 0.03630000 -0.01270000 0.03630000 -0.03510000 0.00000000 0.06915504 0.05133595 -0.04298854 -0.01796051 -0.05777062 +O 2.95500000 0.17700000 10.79300000 0.06750000 0.00000000 0.00000000 0.00000000 -0.02570000 -0.01930000 0.00000000 -0.01930000 -0.04180000 0.00000000 0.00000000 -0.02729432 -0.05119434 0.00000000 0.06590235 +O 4.27800000 2.47000000 10.79300000 -0.02330000 -0.04890000 0.01270000 -0.04890000 0.05840000 0.03630000 0.01270000 0.03630000 -0.03510000 0.00000000 -0.06915504 0.05133595 -0.04298854 0.01796051 -0.05777062 +O -1.63100000 4.35300000 8.50600000 -0.12660000 0.12620000 -0.08690000 0.12620000 0.02960000 0.05540000 -0.08690000 0.05540000 0.09690000 0.00005774 0.17847375 0.07834743 0.11871860 -0.12289516 -0.11045008 +O 1.63100000 4.35300000 8.50600000 -0.12660000 -0.12620000 0.08690000 -0.12620000 0.02960000 0.05540000 0.08690000 0.05540000 0.09690000 0.00005774 -0.17847375 0.07834743 0.11871860 0.12289516 -0.11045008 +O 0.00000000 1.52800000 8.50600000 0.09040000 0.00000000 0.00000000 0.00000000 -0.18860000 -0.10150000 0.00000000 -0.10150000 0.09830000 -0.00005774 0.00000000 -0.14354268 0.12035160 0.00000000 0.19728279 +Ti 2.95500000 1.70600000 12.06200000 0.07770000 0.00000000 0.00000000 0.00000000 0.17090000 0.08470000 0.00000000 0.08470000 -0.24860000 0.00000000 0.00000000 0.11978389 -0.30447158 0.00000000 -0.06590235 +Ti 0.00000000 3.41200000 2.41200000 0.07770000 0.00000000 0.00000000 0.00000000 0.17090000 0.08470000 0.00000000 0.08470000 -0.24860000 0.00000000 0.00000000 0.11978389 -0.30447158 0.00000000 -0.06590235 +Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24960000 0.04950000 -0.24960000 -0.03770000 -0.07610000 0.04950000 -0.07610000 0.11870000 -0.00000000 -0.35298771 -0.10762165 0.14537722 0.07000357 -0.03061772 +Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24960000 -0.04950000 0.24960000 -0.03770000 -0.07610000 -0.04950000 -0.07610000 0.11870000 -0.00000000 0.35298771 -0.10762165 0.14537722 -0.07000357 -0.03061772 +Ti 0.00000000 1.70600000 4.82500000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.12500000 0.20870000 -0.00000000 0.20870000 0.04450000 -0.00000000 0.00000000 0.29514637 0.05450115 0.00000000 0.14531044 +Ti 1.47700000 4.26500000 4.82500000 -0.07630000 -0.08890000 -0.17880000 -0.08890000 0.03070000 -0.10080000 -0.17880000 -0.10080000 0.04570000 -0.00005774 -0.12572359 -0.14255273 0.05593002 -0.25286138 -0.07566043 +Ti -1.47700000 4.26500000 4.82500000 -0.07630000 0.08890000 0.17880000 0.08890000 0.03070000 -0.10080000 0.17880000 -0.10080000 0.04570000 -0.00005774 0.12572359 -0.14255273 0.05593002 0.25286138 -0.07566043 +Ti 2.95500000 3.41200000 9.65000000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.12500000 0.20870000 -0.00000000 0.20870000 0.04450000 -0.00000000 0.00000000 0.29514637 0.05450115 0.00000000 0.14531044 +Ti 1.47700000 0.85300000 9.65000000 -0.07630000 -0.08890000 -0.17880000 -0.08890000 0.03070000 -0.10080000 -0.17880000 -0.10080000 0.04570000 -0.00005774 -0.12572359 -0.14255273 0.05593002 -0.25286138 -0.07566043 +Ti 4.43200000 0.85300000 9.65000000 -0.07630000 0.08890000 0.17880000 0.08890000 0.03070000 -0.10080000 0.17880000 -0.10080000 0.04570000 -0.00005774 0.12572359 -0.14255273 0.05593002 0.25286138 -0.07566043 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00310000 0.00000000 -0.00000000 0.00000000 0.00790000 -0.00170000 -0.00000000 -0.00170000 -0.01100000 0.00000000 0.00000000 -0.00240416 -0.01347219 0.00000000 -0.00339411 +Li 0.00000000 0.00000000 12.66500000 0.01400000 0.02150000 0.01500000 0.02150000 -0.00340000 0.00730000 0.01500000 0.00730000 -0.01050000 -0.00005774 0.03040559 0.01032376 -0.01290065 0.02121320 0.01230366 +Li 2.95500000 1.70600000 6.63400000 -0.00270000 0.00000000 0.00000000 0.00000000 0.00290000 -0.00120000 0.00000000 -0.00120000 -0.00020000 0.00000000 0.00000000 -0.00169706 -0.00024495 0.00000000 -0.00395980 +Li 2.95500000 1.70600000 3.01600000 0.00310000 -0.00000000 0.00000000 -0.00000000 0.00790000 -0.00140000 0.00000000 -0.00140000 -0.01100000 0.00000000 0.00000000 -0.00197990 -0.01347219 0.00000000 -0.00339411 +Li 1.47700000 4.26500000 12.06200000 0.01790000 0.01380000 0.01480000 0.01380000 -0.00140000 0.00350000 0.01480000 0.00350000 -0.01660000 0.00005774 0.01951615 0.00494975 -0.02028994 0.02093036 0.01364716 +Li 0.00000000 3.41200000 7.84000000 0.01280000 -0.00000000 -0.00000000 -0.00000000 0.01130000 0.00020000 -0.00000000 0.00020000 -0.02410000 0.00000000 0.00000000 0.00028284 -0.02951635 0.00000000 0.00106066 +Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00010000 -0.00170000 -0.00010000 0.00440000 -0.00110000 -0.00170000 -0.00110000 -0.01020000 0.00000000 -0.00014142 -0.00155563 -0.01249240 -0.00240416 0.00098995 +Li 0.00000000 1.70600000 4.82500000 0.00530000 -0.00000000 -0.00000000 -0.00000000 0.00550000 -0.00120000 -0.00000000 -0.00120000 -0.01080000 0.00000000 0.00000000 -0.00169706 -0.01322724 0.00000000 -0.00014142 +O 0.00000000 0.00000000 3.80700000 0.01290000 -0.00100000 0.00010000 -0.00100000 0.08350000 -0.09190000 0.00010000 -0.09190000 -0.09640000 0.00000000 -0.00141421 -0.12996623 -0.11806541 0.00014142 -0.04992174 +O 0.00000000 0.00000000 10.66800000 0.05410000 0.05790000 -0.08260000 0.05790000 -0.00820000 -0.05550000 -0.08260000 -0.05550000 -0.04590000 0.00000000 0.08188297 -0.07848885 -0.05621579 -0.11681404 0.04405275 +O 2.95500000 1.70600000 8.63200000 0.04640000 0.00240000 -0.01470000 0.00240000 0.04570000 -0.01390000 -0.01470000 -0.01390000 -0.09210000 0.00000000 0.00339411 -0.01965757 -0.11279900 -0.02078894 0.00049497 +O 2.95500000 1.70600000 1.01800000 0.02730000 0.00170000 -0.00940000 0.00170000 0.04350000 -0.13940000 -0.00940000 -0.13940000 -0.07080000 0.00000000 0.00240416 -0.19714137 -0.08671194 -0.01329361 -0.01145513 +O 0.00000000 3.41200000 13.45700000 0.12550000 0.05150000 -0.06490000 0.05150000 -0.08690000 -0.11060000 -0.06490000 -0.11060000 -0.03860000 0.00000000 0.07283200 -0.15641202 -0.04727515 -0.09178246 0.15018948 +O 0.00000000 3.41200000 5.84300000 0.10130000 -0.00060000 0.00010000 -0.00060000 -0.06370000 -0.10070000 0.00010000 -0.10070000 -0.03760000 0.00000000 -0.00084853 -0.14241131 -0.04605041 0.00014142 0.11667262 +O -1.32400000 4.17600000 1.14400000 -0.11050000 -0.02080000 0.11100000 -0.02080000 0.06620000 -0.00910000 0.11100000 -0.00910000 0.04430000 -0.00000000 -0.02941564 -0.01286934 0.05425620 0.15697771 -0.12494577 +O 0.00000000 1.88300000 1.14400000 0.05090000 -0.00780000 0.01220000 -0.00780000 -0.09630000 0.05210000 0.01220000 0.05210000 0.04550000 -0.00005774 -0.01103087 0.07368053 0.05568507 0.01725341 0.10408612 +O 1.32400000 4.17600000 1.14400000 -0.10340000 0.00740000 -0.09530000 0.00740000 0.04600000 -0.03080000 -0.09530000 -0.03080000 0.05740000 0.00000000 0.01046518 -0.04355778 0.07030036 -0.13477455 -0.10564175 +O 4.27800000 0.94200000 13.33100000 -0.20790000 -0.13370000 0.14990000 -0.13370000 0.12960000 0.12800000 0.14990000 0.12800000 0.07840000 -0.00005774 -0.18908035 0.18101934 0.09597917 0.21199061 -0.23864854 +O 1.63100000 0.94200000 13.33100000 -0.13540000 0.07670000 -0.14220000 0.07670000 0.09700000 -0.00990000 -0.14220000 -0.00990000 0.03840000 -0.00000000 0.10847018 -0.01400071 0.04703020 -0.20110117 -0.16433162 +O 2.95500000 3.23400000 13.33100000 0.00530000 -0.08560000 0.09800000 -0.08560000 -0.08320000 0.00740000 0.09800000 0.00740000 0.07790000 -0.00000000 -0.12105668 0.01046518 0.09540763 0.13859293 0.06257895 +O 4.58600000 0.76400000 5.96800000 -0.14380000 -0.06370000 0.13620000 -0.06370000 0.08500000 -0.00250000 0.13620000 -0.00250000 0.05880000 -0.00000000 -0.09008540 -0.00353553 0.07201500 0.19261589 -0.16178603 +O 2.95500000 3.58900000 5.96800000 0.01110000 -0.00170000 0.00090000 -0.00170000 -0.08660000 0.07450000 0.00090000 0.07450000 0.07550000 -0.00000000 -0.00240416 0.10535891 0.09246824 0.00127279 0.06908433 +O 1.32400000 0.76400000 5.96800000 -0.14320000 0.06230000 -0.13680000 0.06230000 0.08180000 -0.00450000 -0.13680000 -0.00450000 0.06140000 -0.00000000 0.08810550 -0.00636396 0.07519934 -0.19346442 -0.15909903 +O 1.32400000 2.64800000 3.68100000 -0.06350000 0.02090000 0.06970000 0.02090000 0.04850000 -0.03540000 0.06970000 -0.03540000 0.01500000 -0.00000000 0.02955706 -0.05006316 0.01837117 0.09857069 -0.07919596 +O -1.32400000 2.64800000 3.68100000 -0.06120000 -0.02430000 -0.06810000 -0.02430000 0.04760000 -0.03510000 -0.06810000 -0.03510000 0.01360000 -0.00000000 -0.03436539 -0.04963890 0.01665653 -0.09630794 -0.07693322 +O 0.00000000 4.94000000 3.68100000 0.04230000 -0.00240000 0.00060000 -0.00240000 -0.08570000 0.05210000 0.00060000 0.05210000 0.04350000 -0.00005774 -0.00339411 0.07368053 0.05323558 0.00084853 0.09050967 +O 1.63100000 2.47000000 10.79300000 -0.09930000 0.06520000 0.02250000 0.06520000 0.11520000 0.09030000 0.02250000 0.09030000 -0.01580000 -0.00005774 0.09220672 0.12770348 -0.01939179 0.03181981 -0.15167440 +O 2.95500000 0.17700000 10.79300000 0.11220000 -0.05640000 0.06450000 -0.05640000 -0.09180000 -0.01760000 0.06450000 -0.01760000 -0.02040000 0.00000000 -0.07976164 -0.02489016 -0.02498480 0.09121677 0.14424978 +O 4.27800000 2.47000000 10.79300000 -0.02890000 -0.05730000 -0.00150000 -0.05730000 0.04420000 0.02630000 -0.00150000 0.02630000 -0.01530000 0.00000000 -0.08103444 0.03719382 -0.01873860 -0.00212132 -0.05168951 +O -1.63100000 4.35300000 8.50600000 -0.01900000 0.04010000 -0.01280000 0.04010000 0.05020000 0.04300000 -0.01280000 0.04300000 -0.03120000 0.00000000 0.05670996 0.06081118 -0.03821204 -0.01810193 -0.04893179 +O 1.63100000 4.35300000 8.50600000 -0.01850000 -0.04050000 0.00130000 -0.04050000 0.03710000 0.02570000 0.00130000 0.02570000 -0.01860000 0.00000000 -0.05727565 0.03634529 -0.02278025 0.00183848 -0.03931514 +O 0.00000000 1.52800000 8.50600000 0.06410000 -0.00660000 0.00960000 -0.00660000 -0.02940000 -0.02540000 0.00960000 -0.02540000 -0.03470000 0.00000000 -0.00933381 -0.03592102 -0.04249865 0.01357645 0.06611448 +Ti 0.00000000 0.00000000 7.23700000 0.06780000 0.00150000 -0.00000000 0.00150000 0.15920000 0.07530000 -0.00000000 0.07530000 -0.22700000 0.00000000 0.00212132 0.10649028 -0.27801709 0.00000000 -0.06462956 +Ti 2.95500000 1.70600000 12.06200000 0.05360000 -0.03080000 0.03670000 -0.03080000 0.18270000 0.09760000 0.03670000 0.09760000 -0.23630000 0.00000000 -0.04355778 0.13802724 -0.28940721 0.05190164 -0.09128749 +Ti 0.00000000 3.41200000 2.41200000 0.08260000 0.00050000 0.00130000 0.00050000 0.18010000 0.14750000 0.00130000 0.14750000 -0.26270000 0.00000000 0.00070711 0.20859650 -0.32174048 0.00183848 -0.06894291 +Ti -1.47700000 2.55900000 0.00000000 -0.08400000 -0.25970000 0.05200000 -0.25970000 -0.02190000 -0.06980000 0.05200000 -0.06980000 0.10590000 -0.00000000 -0.36727126 -0.09871211 0.12970048 0.07353911 -0.04391133 +Ti 1.47700000 2.55900000 0.00000000 -0.07720000 0.26230000 -0.04390000 0.26230000 -0.03870000 -0.09270000 -0.04390000 -0.09270000 0.11590000 -0.00000000 0.37094822 -0.13109760 0.14194793 -0.06208398 -0.02722361 +Ti 1.47700000 4.26500000 4.82500000 -0.08010000 -0.23730000 0.02550000 -0.23730000 -0.03380000 -0.07790000 0.02550000 -0.07790000 0.11390000 -0.00000000 -0.33559288 -0.11016724 0.13949844 0.03606245 -0.03273904 +Ti -1.47700000 4.26500000 4.82500000 -0.07970000 0.23730000 -0.02520000 0.23730000 -0.03410000 -0.07830000 -0.02520000 -0.07830000 0.11370000 0.00005774 0.33559288 -0.11073292 0.13929432 -0.03563818 -0.03224407 +Ti 2.95500000 3.41200000 9.65000000 0.04960000 0.00570000 -0.01350000 0.00570000 -0.07060000 0.12930000 -0.01350000 0.12930000 0.02100000 -0.00000000 0.00806102 0.18285781 0.02571964 -0.01909188 0.08499424 +Ti 1.47700000 0.85300000 9.65000000 -0.03140000 -0.04410000 -0.09220000 -0.04410000 0.01960000 -0.04920000 -0.09220000 -0.04920000 0.01180000 -0.00000000 -0.06236682 -0.06957931 0.01445199 -0.13039049 -0.03606245 +Ti 4.43200000 0.85300000 9.65000000 -0.03570000 0.05690000 0.10500000 0.05690000 0.01760000 -0.07260000 0.10500000 -0.07260000 0.01810000 -0.00000000 0.08046875 -0.10267190 0.02216788 0.14849242 -0.03768879 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00310000 0.00000000 0.00000000 0.00000000 0.00880000 -0.00150000 0.00000000 -0.00150000 -0.01190000 0.00000000 0.00000000 -0.00212132 -0.01457446 0.00000000 -0.00403051 +Li 0.00000000 0.00000000 12.66500000 -0.00280000 -0.00000000 -0.00000000 -0.00000000 0.00370000 -0.00130000 -0.00000000 -0.00130000 -0.00090000 -0.00000000 0.00000000 -0.00183848 -0.00110227 0.00000000 -0.00459619 +Li 2.95500000 1.70600000 6.63400000 -0.02190000 0.00000000 0.00000000 0.00000000 0.03250000 -0.01870000 0.00000000 -0.01870000 -0.01060000 0.00000000 0.00000000 -0.02644579 -0.01298230 0.00000000 -0.03846661 +Li 2.95500000 1.70600000 3.01600000 0.00290000 0.00000000 -0.00000000 0.00000000 0.00860000 -0.00180000 -0.00000000 -0.00180000 -0.01140000 -0.00005774 0.00000000 -0.00254558 -0.01400292 0.00000000 -0.00403051 +Li 0.00000000 3.41200000 11.45900000 0.01280000 -0.00000000 0.00000000 -0.00000000 0.01210000 0.00030000 0.00000000 0.00030000 -0.02490000 0.00000000 0.00000000 0.00042426 -0.03049615 0.00000000 0.00049497 +Li 2.95500000 0.00000000 7.23700000 -0.01100000 0.00000000 -0.00000000 0.00000000 0.02650000 -0.01250000 -0.00000000 -0.01250000 -0.01550000 0.00000000 0.00000000 -0.01767767 -0.01898355 0.00000000 -0.02651650 +Li 2.95500000 0.00000000 0.00000000 0.00520000 0.00000000 -0.00000000 0.00000000 0.00630000 -0.00120000 -0.00000000 -0.00120000 -0.01160000 0.00005774 0.00000000 -0.00169706 -0.01416622 0.00000000 -0.00077782 +Li 0.00000000 1.70600000 4.82500000 0.00610000 0.00000000 -0.00000000 0.00000000 0.00620000 -0.00290000 -0.00000000 -0.00290000 -0.01230000 0.00000000 0.00000000 -0.00410122 -0.01506436 0.00000000 -0.00007071 +O 0.00000000 0.00000000 3.80700000 0.01950000 0.00000000 -0.00000000 0.00000000 0.07830000 -0.10570000 -0.00000000 -0.10570000 -0.09790000 0.00005774 0.00000000 -0.14948237 -0.11986170 0.00000000 -0.04157788 +O 0.00000000 0.00000000 10.66800000 0.03910000 0.00000000 0.00000000 0.00000000 0.06110000 0.01180000 0.00000000 0.01180000 -0.10020000 0.00000000 0.00000000 0.01668772 -0.12271944 0.00000000 -0.01555635 +O 2.95500000 1.70600000 8.63200000 -0.04560000 0.00000000 -0.00000000 0.00000000 0.10160000 0.08690000 -0.00000000 0.08690000 -0.05600000 0.00000000 0.00000000 0.12289516 -0.06858571 0.00000000 -0.10408612 +O 2.95500000 1.70600000 1.01800000 0.00850000 -0.00000000 -0.00000000 -0.00000000 0.09960000 -0.08750000 -0.00000000 -0.08750000 -0.10810000 0.00000000 0.00000000 -0.12374369 -0.13239492 0.00000000 -0.06441743 +O 0.00000000 3.41200000 13.45700000 0.10620000 0.00000000 -0.00000000 0.00000000 -0.05480000 -0.10700000 -0.00000000 -0.10700000 -0.05130000 -0.00005774 0.00000000 -0.15132085 -0.06287024 0.00000000 0.11384419 +O 0.00000000 3.41200000 5.84300000 0.03550000 0.00000000 0.00000000 0.00000000 0.03200000 -0.03660000 0.00000000 -0.03660000 -0.06760000 0.00005774 0.00000000 -0.05176022 -0.08275193 0.00000000 0.00247487 +O -1.32400000 4.17600000 1.14400000 -0.05450000 0.02570000 0.06670000 0.02570000 0.05970000 -0.03630000 0.06670000 -0.03630000 -0.00520000 0.00000000 0.03634529 -0.05133595 -0.00636867 0.09432804 -0.08075159 +O 0.00000000 1.88300000 1.14400000 0.03970000 0.00000000 0.00000000 0.00000000 -0.07270000 0.05250000 0.00000000 0.05250000 0.03300000 -0.00000000 0.00000000 0.07424621 0.04041658 0.00000000 0.07947880 +O 1.32400000 4.17600000 1.14400000 -0.05450000 -0.02570000 -0.06670000 -0.02570000 0.05970000 -0.03630000 -0.06670000 -0.03630000 -0.00520000 0.00000000 -0.03634529 -0.05133595 -0.00636867 -0.09432804 -0.08075159 +O 4.27800000 0.94200000 13.33100000 -0.14680000 -0.06280000 0.13300000 -0.06280000 0.09420000 -0.00650000 0.13300000 -0.00650000 0.05250000 0.00005774 -0.08881261 -0.00919239 0.06433993 0.18809040 -0.17041273 +O 1.63100000 0.94200000 13.33100000 -0.14680000 0.06280000 -0.13300000 0.06280000 0.09420000 -0.00650000 -0.13300000 -0.00650000 0.05250000 0.00005774 0.08881261 -0.00919239 0.06433993 -0.18809040 -0.17041273 +O 2.95500000 3.23400000 13.33100000 0.00750000 0.00000000 -0.00000000 0.00000000 -0.07390000 0.07530000 -0.00000000 0.07530000 0.06640000 -0.00000000 0.00000000 0.10649028 0.08132306 0.00000000 0.05755849 +O 4.58600000 0.76400000 5.96800000 -0.06860000 -0.03510000 0.02520000 -0.03510000 -0.00090000 -0.05950000 0.02520000 -0.05950000 0.06940000 0.00005774 -0.04963890 -0.08414571 0.08503812 0.03563818 -0.04787113 +O 2.95500000 3.58900000 5.96800000 0.00080000 0.00000000 0.00000000 0.00000000 -0.04680000 0.07720000 0.00000000 0.07720000 0.04600000 -0.00000000 0.00000000 0.10917729 0.05633826 0.00000000 0.03365828 +O 1.32400000 0.76400000 5.96800000 -0.06860000 0.03510000 -0.02520000 0.03510000 -0.00090000 -0.05950000 -0.02520000 -0.05950000 0.06940000 0.00005774 0.04963890 -0.08414571 0.08503812 -0.03563818 -0.04787113 +O 1.32400000 2.64800000 3.68100000 -0.08590000 -0.00070000 0.09600000 -0.00070000 0.08080000 -0.03120000 0.09600000 -0.03120000 0.00510000 0.00000000 -0.00098995 -0.04412346 0.00624620 0.13576450 -0.11787470 +O -1.32400000 2.64800000 3.68100000 -0.08590000 0.00070000 -0.09600000 0.00070000 0.08080000 -0.03120000 -0.09600000 -0.03120000 0.00510000 0.00000000 0.00098995 -0.04412346 0.00624620 -0.13576450 -0.11787470 +O 0.00000000 4.94000000 3.68100000 0.03660000 0.00000000 -0.00000000 0.00000000 -0.07600000 0.06850000 -0.00000000 0.06850000 0.03940000 -0.00000000 0.00000000 0.09687363 0.04825495 0.00000000 0.07962022 +O 1.63100000 2.47000000 10.79300000 -0.01430000 0.04470000 -0.02180000 0.04470000 0.05240000 0.02650000 -0.02180000 0.02650000 -0.03800000 -0.00005774 0.06321535 0.03747666 -0.04658113 -0.03082986 -0.04716402 +O 2.95500000 0.17700000 10.79300000 0.05130000 0.00000000 -0.00000000 0.00000000 -0.01870000 -0.00630000 -0.00000000 -0.00630000 -0.03260000 0.00000000 0.00000000 -0.00890955 -0.03992668 0.00000000 0.04949747 +O 4.27800000 2.47000000 10.79300000 -0.01430000 -0.04470000 0.02180000 -0.04470000 0.05240000 0.02650000 0.02180000 0.02650000 -0.03800000 -0.00005774 -0.06321535 0.03747666 -0.04658113 0.03082986 -0.04716402 +O -1.63100000 4.35300000 8.50600000 -0.00420000 0.11510000 -0.04090000 0.11510000 0.03080000 -0.02190000 -0.04090000 -0.02190000 -0.02670000 0.00005774 0.16277598 -0.03097128 -0.03265986 -0.05784133 -0.02474874 +O 1.63100000 4.35300000 8.50600000 -0.00420000 -0.11510000 0.04090000 -0.11510000 0.03080000 -0.02190000 0.04090000 -0.02190000 -0.02670000 0.00005774 -0.16277598 -0.03097128 -0.03265986 0.05784133 -0.02474874 +O 0.00000000 1.52800000 8.50600000 0.06580000 0.00000000 -0.00000000 0.00000000 -0.03730000 -0.00500000 -0.00000000 -0.00500000 -0.02860000 0.00005774 0.00000000 -0.00707107 -0.03498688 0.00000000 0.07290271 +Ti 0.00000000 0.00000000 7.23700000 0.10810000 -0.00000000 0.00000000 -0.00000000 0.12690000 0.03290000 0.00000000 0.03290000 -0.23510000 0.00005774 0.00000000 0.04652763 -0.28789669 0.00000000 -0.01329361 +Ti 2.95500000 1.70600000 12.06200000 0.06510000 -0.00000000 -0.00000000 -0.00000000 0.16170000 0.07520000 -0.00000000 0.07520000 -0.22680000 0.00000000 0.00000000 0.10634886 -0.27777214 0.00000000 -0.06830652 +Ti 0.00000000 3.41200000 2.41200000 0.08170000 -0.00000000 -0.00000000 -0.00000000 0.18050000 0.14900000 -0.00000000 0.14900000 -0.26210000 -0.00005774 0.00000000 0.21071782 -0.32104646 0.00000000 -0.06986215 +Ti -1.47700000 2.55900000 0.00000000 -0.07980000 -0.23690000 0.02470000 -0.23690000 -0.03380000 -0.07830000 0.02470000 -0.07830000 0.11360000 -0.00000000 -0.33502719 -0.11073292 0.13913102 0.03493107 -0.03252691 +Ti 1.47700000 2.55900000 0.00000000 -0.07980000 0.23690000 -0.02470000 0.23690000 -0.03380000 -0.07830000 -0.02470000 -0.07830000 0.11360000 -0.00000000 0.33502719 -0.11073292 0.13913102 -0.03493107 -0.03252691 +Ti 1.47700000 4.26500000 4.82500000 -0.09770000 -0.24880000 0.02780000 -0.24880000 -0.01690000 -0.05670000 0.02780000 -0.05670000 0.11460000 -0.00000000 -0.35185633 -0.08018591 0.14035576 0.03931514 -0.05713423 +Ti -1.47700000 4.26500000 4.82500000 -0.09770000 0.24880000 -0.02780000 0.24880000 -0.01690000 -0.05670000 -0.02780000 -0.05670000 0.11460000 -0.00000000 0.35185633 -0.08018591 0.14035576 -0.03931514 -0.05713423 +Ti 2.95500000 3.41200000 9.65000000 0.04160000 -0.00000000 -0.00000000 -0.00000000 -0.05780000 0.10750000 -0.00000000 0.10750000 0.01620000 0.00000000 0.00000000 0.15202796 0.01984087 0.00000000 0.07028641 +Ti 1.47700000 0.85300000 9.65000000 -0.04620000 -0.05260000 -0.11850000 -0.05260000 0.02760000 -0.04900000 -0.11850000 -0.04900000 0.01860000 -0.00000000 -0.07438763 -0.06929646 0.02278025 -0.16758431 -0.05218448 +Ti 4.43200000 0.85300000 9.65000000 -0.04620000 0.05260000 0.11850000 0.05260000 0.02760000 -0.04900000 0.11850000 -0.04900000 0.01860000 -0.00000000 0.07438763 -0.06929646 0.02278025 0.16758431 -0.05218448 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00310000 -0.00000000 -0.00000000 -0.00000000 0.00880000 -0.00150000 -0.00000000 -0.00150000 -0.01190000 0.00000000 0.00000000 -0.00212132 -0.01457446 0.00000000 -0.00403051 +Li 0.00000000 0.00000000 12.66500000 -0.00290000 0.00000000 0.00000000 0.00000000 0.00350000 -0.00120000 0.00000000 -0.00120000 -0.00060000 0.00000000 0.00000000 -0.00169706 -0.00073485 0.00000000 -0.00452548 +Li 2.95500000 1.70600000 6.63400000 -0.00290000 0.00000000 0.00000000 0.00000000 0.00350000 -0.00120000 0.00000000 -0.00120000 -0.00060000 0.00000000 0.00000000 -0.00169706 -0.00073485 0.00000000 -0.00452548 +Li 2.95500000 1.70600000 3.01600000 0.00310000 0.00000000 0.00000000 0.00000000 0.00880000 -0.00150000 0.00000000 -0.00150000 -0.01190000 0.00000000 0.00000000 -0.00212132 -0.01457446 0.00000000 -0.00403051 +Li 0.00000000 3.41200000 11.45900000 0.01220000 -0.00000000 0.00000000 -0.00000000 0.01150000 0.00030000 0.00000000 0.00030000 -0.02380000 0.00005774 0.00000000 0.00042426 -0.02910810 0.00000000 0.00049497 +Li 0.00000000 3.41200000 7.84000000 0.01220000 0.00000000 -0.00000000 0.00000000 0.01150000 0.00030000 -0.00000000 0.00030000 -0.02380000 0.00005774 0.00000000 0.00042426 -0.02910810 0.00000000 0.00049497 +Li 2.95500000 0.00000000 0.00000000 0.00530000 0.00000000 0.00000000 0.00000000 0.00630000 -0.00130000 0.00000000 -0.00130000 -0.01160000 0.00000000 0.00000000 -0.00183848 -0.01420704 0.00000000 -0.00070711 +Li 0.00000000 1.70600000 4.82500000 0.00530000 0.00000000 0.00000000 0.00000000 0.00630000 -0.00130000 0.00000000 -0.00130000 -0.01160000 0.00000000 0.00000000 -0.00183848 -0.01420704 0.00000000 -0.00070711 +O 0.00000000 0.00000000 3.80700000 0.00830000 0.00000000 -0.00000000 0.00000000 0.09410000 -0.08890000 -0.00000000 -0.08890000 -0.10240000 0.00000000 0.00000000 -0.12572359 -0.12541387 0.00000000 -0.06066976 +O 0.00000000 0.00000000 10.66800000 0.03070000 0.00000000 -0.00000000 0.00000000 0.04570000 -0.00560000 -0.00000000 -0.00560000 -0.07640000 0.00000000 0.00000000 -0.00791960 -0.09357051 0.00000000 -0.01060660 +O 2.95500000 1.70600000 8.63200000 0.03070000 0.00000000 -0.00000000 0.00000000 0.04570000 -0.00560000 -0.00000000 -0.00560000 -0.07640000 0.00000000 0.00000000 -0.00791960 -0.09357051 0.00000000 -0.01060660 +O 2.95500000 1.70600000 1.01800000 0.00830000 -0.00000000 0.00000000 -0.00000000 0.09410000 -0.08890000 0.00000000 -0.08890000 -0.10240000 0.00000000 0.00000000 -0.12572359 -0.12541387 0.00000000 -0.06066976 +O 0.00000000 3.41200000 13.45700000 0.10670000 0.00000000 0.00000000 0.00000000 -0.05710000 -0.11030000 0.00000000 -0.11030000 -0.04950000 -0.00005774 0.00000000 -0.15598776 -0.06066570 0.00000000 0.11582409 +O 0.00000000 3.41200000 5.84300000 0.10670000 -0.00000000 -0.00000000 -0.00000000 -0.05710000 -0.11030000 -0.00000000 -0.11030000 -0.04950000 -0.00005774 0.00000000 -0.15598776 -0.06066570 0.00000000 0.11582409 +O -1.32400000 4.17600000 1.14400000 -0.05530000 0.02420000 0.06820000 0.02420000 0.06170000 -0.03630000 0.06820000 -0.03630000 -0.00640000 0.00000000 0.03422397 -0.05133595 -0.00783837 0.09644936 -0.08273149 +O 0.00000000 1.88300000 1.14400000 0.04020000 0.00000000 -0.00000000 0.00000000 -0.07340000 0.05210000 -0.00000000 0.05210000 0.03320000 0.00000000 0.00000000 0.07368053 0.04066153 0.00000000 0.08032733 +O 1.32400000 4.17600000 1.14400000 -0.05530000 -0.02420000 -0.06820000 -0.02420000 0.06170000 -0.03630000 -0.06820000 -0.03630000 -0.00640000 0.00000000 -0.03422397 -0.05133595 -0.00783837 -0.09644936 -0.08273149 +O 4.27800000 0.94200000 13.33100000 -0.14680000 -0.06560000 0.13680000 -0.06560000 0.09190000 -0.00540000 0.13680000 -0.00540000 0.05490000 0.00000000 -0.09277241 -0.00763675 0.06723849 0.19346442 -0.16878639 +O 1.63100000 0.94200000 13.33100000 -0.14680000 0.06560000 -0.13680000 0.06560000 0.09190000 -0.00540000 -0.13680000 -0.00540000 0.05490000 0.00000000 0.09277241 -0.00763675 0.06723849 -0.19346442 -0.16878639 +O 2.95500000 3.23400000 13.33100000 0.00580000 0.00000000 -0.00000000 0.00000000 -0.07310000 0.07650000 -0.00000000 0.07650000 0.06730000 -0.00000000 0.00000000 0.10818734 0.08242533 0.00000000 0.05579073 +O 4.58600000 0.76400000 5.96800000 -0.14680000 -0.06560000 0.13680000 -0.06560000 0.09190000 -0.00540000 0.13680000 -0.00540000 0.05490000 0.00000000 -0.09277241 -0.00763675 0.06723849 0.19346442 -0.16878639 +O 2.95500000 3.58900000 5.96800000 0.00580000 0.00000000 0.00000000 0.00000000 -0.07310000 0.07650000 0.00000000 0.07650000 0.06730000 -0.00000000 0.00000000 0.10818734 0.08242533 0.00000000 0.05579073 +O 1.32400000 0.76400000 5.96800000 -0.14680000 0.06560000 -0.13680000 0.06560000 0.09190000 -0.00540000 -0.13680000 -0.00540000 0.05490000 0.00000000 0.09277241 -0.00763675 0.06723849 -0.19346442 -0.16878639 +O 1.32400000 2.64800000 3.68100000 -0.05530000 0.02420000 0.06820000 0.02420000 0.06170000 -0.03630000 0.06820000 -0.03630000 -0.00640000 0.00000000 0.03422397 -0.05133595 -0.00783837 0.09644936 -0.08273149 +O -1.32400000 2.64800000 3.68100000 -0.05530000 -0.02420000 -0.06820000 -0.02420000 0.06170000 -0.03630000 -0.06820000 -0.03630000 -0.00640000 0.00000000 -0.03422397 -0.05133595 -0.00783837 -0.09644936 -0.08273149 +O 0.00000000 4.94000000 3.68100000 0.04020000 -0.00000000 0.00000000 -0.00000000 -0.07340000 0.05210000 0.00000000 0.05210000 0.03320000 0.00000000 0.00000000 0.07368053 0.04066153 0.00000000 0.08032733 +O 1.63100000 2.47000000 10.79300000 -0.00900000 0.03090000 0.00480000 0.03090000 0.05160000 0.02200000 0.00480000 0.02200000 -0.04270000 0.00005774 0.04369920 0.03111270 -0.05225578 0.00678823 -0.04285067 +O 2.95500000 0.17700000 10.79300000 0.05040000 0.00000000 -0.00000000 0.00000000 -0.00300000 -0.00000000 -0.00000000 -0.00000000 -0.04740000 0.00000000 0.00000000 0.00000000 -0.05805291 0.00000000 0.03775950 +O 4.27800000 2.47000000 10.79300000 -0.00900000 -0.03090000 -0.00480000 -0.03090000 0.05160000 0.02200000 -0.00480000 0.02200000 -0.04270000 0.00005774 -0.04369920 0.03111270 -0.05225578 -0.00678823 -0.04285067 +O -1.63100000 4.35300000 8.50600000 -0.00900000 0.03090000 0.00480000 0.03090000 0.05160000 0.02200000 0.00480000 0.02200000 -0.04270000 0.00005774 0.04369920 0.03111270 -0.05225578 0.00678823 -0.04285067 +O 1.63100000 4.35300000 8.50600000 -0.00900000 -0.03090000 -0.00480000 -0.03090000 0.05160000 0.02200000 -0.00480000 0.02200000 -0.04270000 0.00005774 -0.04369920 0.03111270 -0.05225578 -0.00678823 -0.04285067 +O 0.00000000 1.52800000 8.50600000 0.05040000 0.00000000 -0.00000000 0.00000000 -0.00300000 -0.00000000 -0.00000000 -0.00000000 -0.04740000 0.00000000 0.00000000 0.00000000 -0.05805291 0.00000000 0.03775950 +Ti 0.00000000 0.00000000 7.23700000 0.06510000 0.00000000 -0.00000000 0.00000000 0.15840000 0.07460000 -0.00000000 0.07460000 -0.22350000 0.00000000 0.00000000 0.10550033 -0.27373048 0.00000000 -0.06597306 +Ti 2.95500000 1.70600000 12.06200000 0.06510000 0.00000000 -0.00000000 0.00000000 0.15840000 0.07460000 -0.00000000 0.07460000 -0.22350000 0.00000000 0.00000000 0.10550033 -0.27373048 0.00000000 -0.06597306 +Ti 0.00000000 3.41200000 2.41200000 0.08290000 0.00000000 0.00000000 0.00000000 0.17950000 0.14420000 0.00000000 0.14420000 -0.26240000 0.00000000 0.00000000 0.20392960 -0.32137305 0.00000000 -0.06830652 +Ti -1.47700000 2.55900000 0.00000000 -0.08020000 -0.23740000 0.02580000 -0.23740000 -0.03380000 -0.07750000 0.02580000 -0.07750000 0.11400000 -0.00000000 -0.33573430 -0.10960155 0.13962092 0.03648671 -0.03280975 +Ti 1.47700000 2.55900000 0.00000000 -0.08020000 0.23740000 -0.02580000 0.23740000 -0.03380000 -0.07750000 -0.02580000 -0.07750000 0.11400000 -0.00000000 0.33573430 -0.10960155 0.13962092 -0.03648671 -0.03280975 +Ti 1.47700000 4.26500000 4.82500000 -0.08020000 -0.23740000 0.02580000 -0.23740000 -0.03380000 -0.07750000 0.02580000 -0.07750000 0.11400000 -0.00000000 -0.33573430 -0.10960155 0.13962092 0.03648671 -0.03280975 +Ti -1.47700000 4.26500000 4.82500000 -0.08020000 0.23740000 -0.02580000 0.23740000 -0.03380000 -0.07750000 -0.02580000 -0.07750000 0.11400000 -0.00000000 0.33573430 -0.10960155 0.13962092 -0.03648671 -0.03280975 +Ti 2.95500000 3.41200000 9.65000000 0.05450000 -0.00000000 0.00000000 -0.00000000 -0.06160000 0.08400000 0.00000000 0.08400000 0.00710000 -0.00000000 0.00000000 0.11879394 0.00869569 0.00000000 0.08209510 +Ti 1.47700000 0.85300000 9.65000000 -0.03160000 -0.05280000 -0.07250000 -0.05280000 0.02880000 -0.03840000 -0.07250000 -0.03840000 0.00280000 0.00000000 -0.07467048 -0.05430580 0.00342929 -0.10253048 -0.04270925 +Ti 4.43200000 0.85300000 9.65000000 -0.03160000 0.05280000 0.07250000 0.05280000 0.02880000 -0.03840000 0.07250000 -0.03840000 0.00280000 0.00000000 0.07467048 -0.05430580 0.00342929 0.10253048 -0.04270925 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00270000 -0.00030000 0.00010000 -0.00030000 0.00940000 -0.00130000 0.00010000 -0.00130000 -0.01200000 -0.00005774 -0.00042426 -0.00183848 -0.01473776 0.00014142 -0.00473762 +Li -1.47700000 4.26500000 12.06200000 -0.00200000 0.00320000 0.00120000 0.00320000 0.00840000 -0.00390000 0.00120000 -0.00390000 -0.00640000 0.00000000 0.00452548 -0.00551543 -0.00783837 0.00169706 -0.00735391 +Li 2.95500000 1.70600000 6.63400000 0.00140000 0.00240000 0.00100000 0.00240000 -0.00140000 0.00060000 0.00100000 0.00060000 -0.00010000 0.00005774 0.00339411 0.00084853 -0.00008165 0.00141421 0.00197990 +Li 2.95500000 1.70600000 3.01600000 0.00750000 0.00270000 0.00110000 0.00270000 0.00360000 0.00050000 0.00110000 0.00050000 -0.01110000 0.00000000 0.00381838 0.00070711 -0.01359467 0.00155563 0.00275772 +Li 0.00000000 1.70600000 12.06200000 0.00590000 -0.00370000 0.00120000 -0.00370000 -0.00140000 0.00420000 0.00120000 0.00420000 -0.00450000 0.00000000 -0.00523259 0.00593970 -0.00551135 0.00169706 0.00516188 +Li 0.00000000 3.41200000 7.84000000 0.01170000 -0.00060000 -0.00030000 -0.00060000 0.01250000 -0.00010000 -0.00030000 -0.00010000 -0.02410000 -0.00005774 -0.00084853 -0.00014142 -0.02955718 -0.00042426 -0.00056569 +Li 2.95500000 0.00000000 0.00000000 0.00440000 0.00010000 0.00170000 0.00010000 0.00600000 -0.00060000 0.00170000 -0.00060000 -0.01040000 0.00000000 0.00014142 -0.00084853 -0.01273735 0.00240416 -0.00113137 +Li 1.47700000 4.26500000 4.82500000 0.00580000 0.00010000 0.00100000 0.00010000 0.00540000 0.00050000 0.00100000 0.00050000 -0.01120000 0.00000000 0.00014142 0.00070711 -0.01371714 0.00141421 0.00028284 +O 0.00000000 0.00000000 3.80700000 0.07480000 0.03110000 0.05890000 0.03110000 0.02670000 0.02920000 0.05890000 0.02920000 -0.10160000 0.00005774 0.04398204 0.04129504 -0.12439325 0.08329718 0.03401184 +O 0.00000000 0.00000000 10.66800000 0.02310000 -0.05230000 0.06580000 -0.05230000 0.08270000 0.03220000 0.06580000 0.03220000 -0.10580000 0.00000000 -0.07396337 0.04553768 -0.12957801 0.09305525 -0.04214356 +O 2.95500000 1.70600000 8.63200000 0.04230000 -0.00250000 0.01790000 -0.00250000 0.04310000 0.00900000 0.01790000 0.00900000 -0.08540000 0.00000000 -0.00353553 0.01272792 -0.10459321 0.02531442 -0.00056569 +O 2.95500000 1.70600000 1.01800000 0.01170000 -0.00980000 0.01240000 -0.00980000 0.09940000 -0.05860000 0.01240000 -0.05860000 -0.11110000 0.00000000 -0.01385929 -0.08287291 -0.13606916 0.01753625 -0.06201326 +O 0.00000000 3.41200000 13.45700000 0.10610000 -0.03360000 0.08920000 -0.03360000 -0.04100000 -0.08590000 0.08920000 -0.08590000 -0.06510000 0.00000000 -0.04751758 -0.12148095 -0.07973089 0.12614785 0.10401541 +O 0.00000000 3.41200000 5.84300000 -0.01980000 -0.07610000 0.11340000 -0.07610000 0.08740000 0.05320000 0.11340000 0.05320000 -0.06750000 -0.00005774 -0.10762165 0.07523616 -0.08271110 0.16037182 -0.07580185 +O -1.32400000 4.17600000 1.14400000 0.01000000 0.05420000 0.10170000 0.05420000 0.05060000 -0.02570000 0.10170000 -0.02570000 -0.06060000 0.00000000 0.07665038 -0.03634529 -0.07421954 0.14382552 -0.02870854 +O 0.00000000 1.88300000 1.14400000 0.05310000 0.00700000 -0.02870000 0.00700000 -0.10730000 0.04020000 -0.02870000 0.04020000 0.05430000 -0.00005774 0.00989949 0.05685139 0.06646282 -0.04058793 0.11341993 +O 1.32400000 4.17600000 1.14400000 0.00680000 -0.03560000 -0.04840000 -0.03560000 0.05340000 -0.02080000 -0.04840000 -0.02080000 -0.06020000 0.00000000 -0.05034600 -0.02941564 -0.07372964 -0.06844794 -0.03295118 +O 4.27800000 0.94200000 13.33100000 -0.14710000 0.01090000 0.06000000 0.01090000 0.07590000 -0.07510000 0.06000000 -0.07510000 0.07120000 0.00000000 0.01541493 -0.10620744 0.08720183 0.08485281 -0.15768481 +O 1.63100000 0.94200000 13.33100000 -0.19640000 0.01330000 -0.07630000 0.01330000 0.07010000 0.02920000 -0.07630000 0.02920000 0.12640000 -0.00005774 0.01880904 0.04129504 0.15476693 -0.10790449 -0.18844396 +O 2.95500000 3.23400000 13.33100000 0.06150000 0.05880000 -0.08680000 0.05880000 -0.15200000 0.06380000 -0.08680000 0.06380000 0.09050000 -0.00000000 0.08315576 0.09022683 0.11083941 -0.12275374 0.15096730 +O 4.58600000 0.76400000 5.96800000 0.07910000 0.07170000 0.06760000 0.07170000 -0.13540000 -0.11900000 0.06760000 -0.11900000 0.05630000 -0.00000000 0.10139911 -0.16829141 0.06895314 0.09560084 0.15167440 +O 2.95500000 3.58900000 5.96800000 -0.03010000 0.12810000 -0.06720000 0.12810000 -0.03350000 0.11650000 -0.06720000 0.11650000 0.06360000 -0.00000000 0.18116076 0.16475588 0.07789377 -0.09503515 0.00240416 +O 1.32400000 0.76400000 5.96800000 -0.05910000 -0.04110000 -0.06370000 -0.04110000 -0.01440000 -0.03620000 -0.06370000 -0.03620000 0.07350000 -0.00000000 -0.05812418 -0.05119453 0.09001875 -0.09008540 -0.03160767 +O 1.32400000 2.64800000 3.68100000 -0.00170000 0.02990000 0.07300000 0.02990000 0.05150000 -0.07420000 0.07300000 -0.07420000 -0.04980000 0.00000000 0.04228499 -0.10493465 -0.06099229 0.10323759 -0.03761808 +O -1.32400000 2.64800000 3.68100000 -0.06310000 -0.06180000 -0.05000000 -0.06180000 0.01430000 -0.00780000 -0.05000000 -0.00780000 0.04880000 -0.00000000 -0.08739840 -0.01103087 0.05976755 -0.07071068 -0.05473006 +O 0.00000000 4.94000000 3.68100000 0.05730000 -0.00580000 0.00670000 -0.00580000 -0.01180000 0.05650000 0.00670000 0.05650000 -0.04560000 0.00005774 -0.00820244 0.07990307 -0.05580754 0.00947523 0.04886108 +O 1.63100000 2.47000000 10.79300000 0.00650000 0.10470000 -0.04540000 0.10470000 0.01820000 -0.02390000 -0.04540000 -0.02390000 -0.02470000 0.00000000 0.14806816 -0.03379970 -0.03025120 -0.06420530 -0.00827315 +O 2.95500000 0.17700000 10.79300000 0.10550000 0.05000000 -0.06390000 0.05000000 -0.07570000 -0.02390000 -0.06390000 -0.02390000 -0.02980000 0.00000000 0.07071068 -0.03379970 -0.03649740 -0.09036825 0.12812775 +O 4.27800000 2.47000000 10.79300000 -0.05790000 -0.10890000 0.03540000 -0.10890000 0.07720000 0.04600000 0.03540000 0.04600000 -0.01930000 0.00000000 -0.15400786 0.06505382 -0.02363758 0.05006316 -0.09553013 +O -1.63100000 4.35300000 8.50600000 -0.00250000 0.04830000 -0.02330000 0.04830000 0.02850000 -0.01500000 -0.02330000 -0.01500000 -0.02600000 0.00000000 0.06830652 -0.02121320 -0.03184337 -0.03295118 -0.02192031 +O 1.63100000 4.35300000 8.50600000 -0.00150000 -0.04600000 0.02000000 -0.04600000 0.04520000 0.01480000 0.02000000 0.01480000 -0.04360000 -0.00005774 -0.06505382 0.02093036 -0.05343970 0.02828427 -0.03302189 +O 0.00000000 1.52800000 8.50600000 0.06210000 0.00810000 -0.02740000 0.00810000 -0.03970000 -0.01180000 -0.02740000 -0.01180000 -0.02240000 -0.00000000 0.01145513 -0.01668772 -0.02743429 -0.03874945 0.07198347 +Ti 0.00000000 0.00000000 7.23700000 0.13410000 0.03780000 -0.06390000 0.03780000 0.09090000 -0.03630000 -0.06390000 -0.03630000 -0.22500000 0.00000000 0.05345727 -0.05133595 -0.27556760 -0.09036825 0.03054701 +Ti 2.95500000 1.70600000 12.06200000 0.10520000 0.03170000 -0.03560000 0.03170000 0.15980000 0.04860000 -0.03560000 0.04860000 -0.26500000 0.00000000 0.04483057 0.06873078 -0.32455739 -0.05034600 -0.03860803 +Ti 0.00000000 3.41200000 2.41200000 0.11420000 0.02140000 -0.06520000 0.02140000 0.14040000 0.03840000 -0.06520000 0.03840000 -0.25460000 0.00000000 0.03026417 0.05430580 -0.31182004 -0.09220672 -0.01852620 +Ti -1.47700000 2.55900000 0.00000000 -0.08410000 -0.23290000 0.00060000 -0.23290000 -0.03710000 -0.08900000 0.00060000 -0.08900000 0.12130000 -0.00005774 -0.32937034 -0.12586501 0.14852073 0.00084853 -0.03323402 +Ti 1.47700000 2.55900000 0.00000000 -0.08310000 0.23130000 -0.00750000 0.23130000 -0.03100000 -0.07290000 -0.00750000 -0.07290000 0.11410000 -0.00000000 0.32710760 -0.10309617 0.13974339 -0.01060660 -0.03684026 +Ti 0.00000000 1.70600000 4.82500000 0.16080000 -0.09730000 0.08090000 -0.09730000 -0.27130000 0.01860000 0.08090000 0.01860000 0.11050000 0.00000000 -0.13760298 0.02630437 0.13533431 0.11440988 0.30554084 +Ti -1.47700000 4.26500000 4.82500000 -0.25100000 0.13630000 0.05780000 0.13630000 0.13840000 0.06320000 0.05780000 0.06320000 0.11260000 -0.00000000 0.19275731 0.08937830 0.13790627 0.08174154 -0.27534738 +Ti 2.95500000 3.41200000 9.65000000 0.05160000 -0.00480000 0.01240000 -0.00480000 -0.06320000 0.09580000 0.01240000 0.09580000 0.01150000 0.00005774 -0.00678823 0.13548166 0.01412539 0.01753625 0.08117586 +Ti 1.47700000 0.85300000 9.65000000 -0.04110000 -0.05810000 -0.10470000 -0.05810000 0.02520000 -0.05920000 -0.10470000 -0.05920000 0.01590000 -0.00000000 -0.08216581 -0.08372144 0.01947344 -0.14806816 -0.04688118 +Ti 4.43200000 0.85300000 9.65000000 -0.03800000 0.04880000 0.08820000 0.04880000 0.02870000 -0.03490000 0.08820000 -0.03490000 0.00930000 -0.00000000 0.06901362 -0.04935605 0.01139013 0.12473364 -0.04716402 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00200000 -0.00030000 0.00010000 -0.00030000 0.00860000 -0.00150000 0.00010000 -0.00150000 -0.01050000 -0.00005774 -0.00042426 -0.00212132 -0.01290065 0.00014142 -0.00466690 +Li 0.00000000 0.00000000 12.66500000 -0.02180000 0.00000000 -0.00000000 0.00000000 0.03170000 -0.01880000 -0.00000000 -0.01880000 -0.00980000 -0.00005774 0.00000000 -0.02658721 -0.01204332 0.00000000 -0.03783021 +Li 2.95500000 1.70600000 6.63400000 0.00150000 0.00240000 0.00110000 0.00240000 -0.00130000 0.00060000 0.00110000 0.00060000 -0.00020000 -0.00000000 0.00339411 0.00084853 -0.00024495 0.00155563 0.00197990 +Li 2.95500000 1.70600000 3.01600000 0.00750000 0.00270000 0.00110000 0.00270000 0.00360000 0.00060000 0.00110000 0.00060000 -0.01110000 0.00000000 0.00381838 0.00084853 -0.01359467 0.00155563 0.00275772 +Li 0.00000000 1.70600000 12.06200000 -0.01080000 -0.00000000 0.00000000 -0.00000000 0.02560000 -0.01260000 0.00000000 -0.01260000 -0.01480000 0.00000000 0.00000000 -0.01781909 -0.01812622 0.00000000 -0.02573869 +Li 0.00000000 3.41200000 7.84000000 0.01190000 -0.00070000 -0.00030000 -0.00070000 0.01260000 -0.00010000 -0.00030000 -0.00010000 -0.02440000 -0.00005774 -0.00098995 -0.00014142 -0.02992460 -0.00042426 -0.00049497 +Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00010000 0.00000000 -0.00010000 0.00560000 -0.00280000 0.00000000 -0.00280000 -0.01180000 0.00000000 -0.00014142 -0.00395980 -0.01445199 0.00000000 0.00042426 +Li 1.47700000 4.26500000 4.82500000 0.00580000 0.00010000 0.00100000 0.00010000 0.00540000 0.00050000 0.00100000 0.00050000 -0.01120000 0.00000000 0.00014142 0.00070711 -0.01371714 0.00141421 0.00028284 +O 0.00000000 0.00000000 3.80700000 0.07290000 0.02920000 0.06110000 0.02920000 0.02700000 0.02950000 0.06110000 0.02950000 -0.09990000 0.00000000 0.04129504 0.04171930 -0.12235201 0.08640845 0.03245620 +O 0.00000000 0.00000000 10.66800000 -0.04310000 -0.00080000 0.00100000 -0.00080000 0.09220000 0.08830000 0.00100000 0.08830000 -0.04900000 -0.00005774 -0.00113137 0.12487506 -0.06005332 0.00141421 -0.09567155 +O 2.95500000 1.70600000 8.63200000 0.04430000 0.00070000 0.00400000 0.00070000 0.04920000 0.01840000 0.00400000 0.01840000 -0.09350000 0.00000000 0.00098995 0.02602153 -0.11451365 0.00565685 -0.00346482 +O 2.95500000 1.70600000 1.01800000 0.02470000 -0.00710000 0.00540000 -0.00710000 0.06700000 -0.08650000 0.00540000 -0.08650000 -0.09170000 0.00000000 -0.01004092 -0.12232947 -0.11230910 0.00763675 -0.02991062 +O 0.00000000 3.41200000 13.45700000 0.05590000 0.00460000 0.01180000 0.00460000 0.03210000 -0.04850000 0.01180000 -0.04850000 -0.08790000 -0.00005774 0.00650538 -0.06858936 -0.10769590 0.01668772 0.01682914 +O 0.00000000 3.41200000 5.84300000 -0.01980000 -0.07520000 0.11240000 -0.07520000 0.08580000 0.05240000 0.11240000 0.05240000 -0.06590000 -0.00005774 -0.10634886 0.07410479 -0.08075151 0.15895760 -0.07467048 +O -1.32400000 4.17600000 1.14400000 -0.02590000 0.00700000 0.12870000 0.00700000 0.05180000 -0.02290000 0.12870000 -0.02290000 -0.02600000 0.00005774 0.00989949 -0.03238549 -0.03180254 0.18200929 -0.05494220 +O 0.00000000 1.88300000 1.14400000 0.04570000 0.00200000 -0.01700000 0.00200000 -0.10400000 0.06610000 -0.01700000 0.06610000 0.05830000 -0.00000000 0.00282843 0.09347952 0.07140263 -0.02404163 0.10585389 +O 1.32400000 4.17600000 1.14400000 -0.02310000 -0.00280000 -0.07820000 -0.00280000 0.04980000 -0.02730000 -0.07820000 -0.02730000 -0.02670000 0.00000000 -0.00395980 -0.03860803 -0.03270069 -0.11059150 -0.05154808 +O 4.27800000 0.94200000 13.33100000 -0.06830000 -0.03250000 0.02530000 -0.03250000 -0.00840000 -0.05630000 0.02530000 -0.05630000 0.07670000 -0.00000000 -0.04596194 -0.07962022 0.09393793 0.03577960 -0.04235570 +O 1.63100000 0.94200000 13.33100000 -0.06750000 0.03460000 -0.02420000 0.03460000 -0.01400000 -0.05770000 -0.02420000 -0.05770000 0.08150000 -0.00000000 0.04893179 -0.08160012 0.09981671 -0.03422397 -0.03783021 +O 2.95500000 3.23400000 13.33100000 0.00220000 -0.00120000 -0.00030000 -0.00120000 -0.05650000 0.07680000 -0.00030000 0.07680000 0.05430000 -0.00000000 -0.00169706 0.10861160 0.06650365 -0.00042426 0.04150717 +O 4.58600000 0.76400000 5.96800000 0.07890000 0.07400000 0.06630000 0.07400000 -0.13430000 -0.11930000 0.06630000 -0.11930000 0.05540000 0.00000000 0.10465180 -0.16871568 0.06785087 0.09376236 0.15075517 +O 2.95500000 3.58900000 5.96800000 -0.03000000 0.12910000 -0.06570000 0.12910000 -0.03400000 0.11700000 -0.06570000 0.11700000 0.06400000 -0.00000000 0.18257497 0.16546299 0.07838367 -0.09291383 0.00282843 +O 1.32400000 0.76400000 5.96800000 -0.05970000 -0.04130000 -0.06360000 -0.04130000 -0.01390000 -0.03720000 -0.06360000 -0.03720000 0.07360000 -0.00000000 -0.05840702 -0.05260874 0.09014122 -0.08994398 -0.03238549 +O 1.32400000 2.64800000 3.68100000 -0.00040000 0.03370000 0.07220000 0.03370000 0.04150000 -0.07770000 0.07220000 -0.07770000 -0.04110000 0.00000000 0.04765900 -0.10988439 -0.05033701 0.10210622 -0.02962777 +O -1.32400000 2.64800000 3.68100000 -0.06350000 -0.06180000 -0.05100000 -0.06180000 0.01380000 -0.00790000 -0.05100000 -0.00790000 0.04980000 -0.00005774 -0.08739840 -0.01117229 0.06095147 -0.07212489 -0.05465935 +O 0.00000000 4.94000000 3.68100000 0.05190000 -0.00130000 0.00260000 -0.00130000 -0.00920000 0.05740000 0.00260000 0.05740000 -0.04270000 0.00000000 -0.00183848 0.08117586 -0.05229661 0.00367696 0.04320422 +O 1.63100000 2.47000000 10.79300000 -0.00330000 0.11530000 -0.04360000 0.11530000 0.02160000 -0.02630000 -0.04360000 -0.02630000 -0.01830000 0.00000000 0.16305882 -0.03719382 -0.02241283 -0.06165971 -0.01760696 +O 2.95500000 0.17700000 10.79300000 0.07040000 -0.00270000 -0.00270000 -0.00270000 -0.04980000 -0.00700000 -0.00270000 -0.00700000 -0.02060000 -0.00000000 -0.00381838 -0.00989949 -0.02522974 -0.00381838 0.08499424 +O 4.27800000 2.47000000 10.79300000 -0.00310000 -0.11760000 0.04150000 -0.11760000 0.01870000 -0.02480000 0.04150000 -0.02480000 -0.01550000 -0.00005774 -0.16631151 -0.03507250 -0.01902437 0.05868986 -0.01541493 +O -1.63100000 4.35300000 8.50600000 -0.00200000 0.05080000 -0.04040000 0.05080000 0.03210000 -0.00550000 -0.04040000 -0.00550000 -0.03010000 0.00000000 0.07184205 -0.00777817 -0.03686482 -0.05713423 -0.02411234 +O 1.63100000 4.35300000 8.50600000 -0.00040000 -0.04530000 0.02610000 -0.04530000 0.03600000 0.00730000 0.02610000 0.00730000 -0.03560000 0.00000000 -0.06406387 0.01032376 -0.04360092 0.03691097 -0.02573869 +O 0.00000000 1.52800000 8.50600000 0.05690000 0.00420000 -0.01840000 0.00420000 -0.04120000 -0.01270000 -0.01840000 -0.01270000 -0.01570000 0.00000000 0.00593970 -0.01796051 -0.01922849 -0.02602153 0.06936718 +Ti 0.00000000 0.00000000 7.23700000 0.13420000 0.03960000 -0.06450000 0.03960000 0.09240000 -0.03680000 -0.06450000 -0.03680000 -0.22650000 -0.00005774 0.05600286 -0.05204306 -0.27744554 -0.09121677 0.02955706 +Ti 2.95500000 1.70600000 12.06200000 0.10850000 0.00030000 -0.00050000 0.00030000 0.12590000 0.03190000 -0.00050000 0.03190000 -0.23440000 0.00000000 0.00042426 0.04511341 -0.28708020 -0.00070711 -0.01230366 +Ti 0.00000000 3.41200000 2.41200000 0.11270000 0.02270000 -0.06460000 0.02270000 0.14070000 0.04300000 -0.06460000 0.04300000 -0.25340000 0.00000000 0.03210265 0.06081118 -0.31035035 -0.09135820 -0.01979899 +Ti -1.47700000 2.55900000 0.00000000 -0.09650000 -0.24850000 0.02760000 -0.24850000 -0.01640000 -0.05810000 0.02760000 -0.05810000 0.11290000 -0.00000000 -0.35143207 -0.08216581 0.13827370 0.03903229 -0.05663925 +Ti 1.47700000 2.55900000 0.00000000 -0.09490000 0.24890000 -0.02880000 0.24890000 -0.01940000 -0.06050000 -0.02880000 -0.06050000 0.11430000 -0.00000000 0.35199776 -0.08555992 0.13998834 -0.04072935 -0.05338656 +Ti 0.00000000 1.70600000 4.82500000 0.16090000 -0.09700000 0.08070000 -0.09700000 -0.27170000 0.01780000 0.08070000 0.01780000 0.11070000 0.00005774 -0.13717872 0.02517300 0.13562008 0.11412703 0.30589439 +Ti -1.47700000 4.26500000 4.82500000 -0.25130000 0.13630000 0.05780000 0.13630000 0.13850000 0.06360000 0.05780000 0.06360000 0.11280000 0.00000000 0.19275731 0.08994398 0.13815122 0.08174154 -0.27563022 +Ti 2.95500000 3.41200000 9.65000000 0.04450000 -0.00010000 -0.00110000 -0.00010000 -0.05980000 0.10800000 -0.00110000 0.10800000 0.01530000 0.00000000 -0.00014142 0.15273506 0.01873860 -0.00155563 0.07375124 +Ti 1.47700000 0.85300000 9.65000000 -0.04660000 -0.05160000 -0.11980000 -0.05160000 0.02580000 -0.05140000 -0.11980000 -0.05140000 0.02090000 -0.00005774 -0.07297342 -0.07269058 0.02555634 -0.16942278 -0.05119453 +Ti 4.43200000 0.85300000 9.65000000 -0.04680000 0.05340000 0.11840000 0.05340000 0.02730000 -0.05200000 0.11840000 -0.05200000 0.01940000 0.00005774 0.07551900 -0.07353911 0.02380088 0.16744289 -0.05239661 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00220000 -0.00040000 0.00010000 -0.00040000 0.00890000 -0.00130000 0.00010000 -0.00130000 -0.01110000 0.00000000 -0.00056569 -0.00183848 -0.01359467 0.00014142 -0.00473762 +Li 0.00000000 0.00000000 12.66500000 -0.00270000 0.00000000 0.00000000 0.00000000 0.00290000 -0.00120000 0.00000000 -0.00120000 -0.00020000 0.00000000 0.00000000 -0.00169706 -0.00024495 0.00000000 -0.00395980 +Li 2.95500000 1.70600000 6.63400000 0.01830000 0.02320000 0.01620000 0.02320000 -0.00840000 0.00940000 0.01620000 0.00940000 -0.00990000 0.00000000 0.03280975 0.01329361 -0.01212497 0.02291026 0.01887975 +Li 2.95500000 1.70600000 3.01600000 0.00720000 0.00270000 0.00130000 0.00270000 0.00330000 0.00070000 0.00130000 0.00070000 -0.01050000 0.00000000 0.00381838 0.00098995 -0.01285982 0.00183848 0.00275772 +Li 0.00000000 3.41200000 11.45900000 0.01300000 -0.00000000 -0.00010000 -0.00000000 0.01150000 0.00030000 -0.00010000 0.00030000 -0.02440000 -0.00005774 0.00000000 0.00042426 -0.02992460 -0.00014142 0.00106066 +Li -1.47700000 2.55900000 7.23700000 0.01650000 0.01570000 0.01090000 0.01570000 -0.00170000 0.00630000 0.01090000 0.00630000 -0.01480000 0.00000000 0.02220315 0.00890955 -0.01812622 0.01541493 0.01286934 +Li 2.95500000 0.00000000 0.00000000 0.00540000 -0.00010000 0.00000000 -0.00010000 0.00580000 -0.00110000 0.00000000 -0.00110000 -0.01120000 0.00000000 -0.00014142 -0.00155563 -0.01371714 0.00000000 -0.00028284 +Li 1.47700000 4.26500000 4.82500000 0.00590000 -0.00030000 0.00240000 -0.00030000 0.00590000 0.00140000 0.00240000 0.00140000 -0.01180000 0.00000000 -0.00042426 0.00197990 -0.01445199 0.00339411 0.00000000 +O 0.00000000 0.00000000 3.80700000 0.06250000 0.01470000 0.07770000 0.01470000 0.02910000 0.03860000 0.07770000 0.03860000 -0.09170000 0.00005774 0.02078894 0.05458864 -0.11226828 0.10988439 0.02361737 +O 0.00000000 0.00000000 10.66800000 0.04740000 0.00240000 -0.01390000 0.00240000 0.04610000 -0.01270000 -0.01390000 -0.01270000 -0.09350000 0.00000000 0.00339411 -0.01796051 -0.11451365 -0.01965757 0.00091924 +O 2.95500000 1.70600000 8.63200000 0.05900000 0.05820000 -0.07600000 0.05820000 -0.01000000 -0.04500000 -0.07600000 -0.04500000 -0.04900000 0.00000000 0.08230723 -0.06363961 -0.06001250 -0.10748023 0.04879037 +O 2.95500000 1.70600000 1.01800000 0.01310000 -0.00530000 0.00500000 -0.00530000 0.08680000 -0.06760000 0.00500000 -0.06760000 -0.09990000 0.00000000 -0.00749533 -0.09560084 -0.12235201 0.00707107 -0.05211377 +O 0.00000000 3.41200000 13.45700000 0.12450000 0.00820000 0.01080000 0.00820000 -0.05850000 -0.12360000 0.01080000 -0.12360000 -0.06590000 -0.00005774 0.01159655 -0.17479680 -0.08075151 0.01527351 0.12940054 +O 0.00000000 3.41200000 5.84300000 0.03400000 -0.00810000 0.04790000 -0.00810000 0.05390000 0.01400000 0.04790000 0.01400000 -0.08790000 0.00000000 -0.01145513 0.01979899 -0.10765507 0.06774083 -0.01407142 +O -1.32400000 4.17600000 1.14400000 0.00190000 0.03500000 0.10340000 0.03500000 0.03920000 -0.02380000 0.10340000 -0.02380000 -0.04110000 0.00000000 0.04949747 -0.03365828 -0.05033701 0.14622968 -0.02637508 +O 0.00000000 1.88300000 1.14400000 0.04790000 0.00260000 -0.01870000 0.00260000 -0.09770000 0.04820000 -0.01870000 0.04820000 0.04970000 0.00005774 0.00367696 0.06816509 0.06091064 -0.02644579 0.10295475 +O 1.32400000 4.17600000 1.14400000 0.00720000 -0.02720000 -0.04830000 -0.02720000 0.03550000 -0.03100000 -0.04830000 -0.03100000 -0.04270000 0.00000000 -0.03846661 -0.04384062 -0.05229661 -0.06830652 -0.02001112 +O 4.27800000 0.94200000 13.33100000 -0.14510000 -0.05530000 0.13650000 -0.05530000 0.08960000 0.00230000 0.13650000 0.00230000 0.05550000 0.00000000 -0.07820601 0.00325269 0.06797334 0.19304015 -0.16595796 +O 1.63100000 0.94200000 13.33100000 -0.14490000 0.06280000 -0.13410000 0.06280000 0.08090000 -0.00160000 -0.13410000 -0.00160000 0.06400000 -0.00000000 0.08881261 -0.00226274 0.07838367 -0.18964604 -0.15966471 +O 2.95500000 3.23400000 13.33100000 0.01030000 -0.00080000 0.00050000 -0.00080000 -0.08400000 0.07370000 0.00050000 0.07370000 0.07360000 0.00005774 -0.00113137 0.10422754 0.09018205 0.00070711 0.06668017 +O 4.58600000 0.76400000 5.96800000 0.00480000 0.00970000 0.06140000 0.00970000 -0.08140000 0.00620000 0.06140000 0.00620000 0.07660000 -0.00000000 0.01371787 0.00876812 0.09381546 0.08683271 0.06095260 +O 2.95500000 3.58900000 5.96800000 -0.05740000 0.04050000 0.03780000 0.04050000 -0.02410000 0.04980000 0.03780000 0.04980000 0.08150000 -0.00000000 0.05727565 0.07042784 0.09981671 0.05345727 -0.02354666 +O 1.32400000 0.76400000 5.96800000 -0.04080000 -0.02600000 -0.06660000 -0.02600000 -0.01360000 -0.03810000 -0.06660000 -0.03810000 0.05430000 0.00005774 -0.03676955 -0.05388154 0.06654447 -0.09418662 -0.01923330 +O 1.32400000 2.64800000 3.68100000 0.02640000 0.03720000 0.08410000 0.03720000 -0.00040000 -0.10000000 0.08410000 -0.10000000 -0.02600000 0.00000000 0.05260874 -0.14142136 -0.03184337 0.11893536 0.01895046 +O -1.32400000 2.64800000 3.68100000 -0.06830000 -0.06380000 -0.06570000 -0.06380000 0.01000000 -0.01830000 -0.06570000 -0.01830000 0.05830000 0.00000000 -0.09022683 -0.02588011 0.07140263 -0.09291383 -0.05536646 +O 0.00000000 4.94000000 3.68100000 0.03390000 0.03020000 -0.01550000 0.03020000 -0.00720000 0.08140000 -0.01550000 0.08140000 -0.02670000 0.00000000 0.04270925 0.11511698 -0.03270069 -0.02192031 0.02906209 +O 1.63100000 2.47000000 10.79300000 -0.02050000 0.04010000 -0.01540000 0.04010000 0.05050000 0.03780000 -0.01540000 0.03780000 -0.03000000 -0.00000000 0.05670996 0.05345727 -0.03674235 -0.02177889 -0.05020458 +O 2.95500000 0.17700000 10.79300000 0.06610000 -0.00690000 0.00680000 -0.00690000 -0.03060000 -0.02630000 0.00680000 -0.02630000 -0.03550000 -0.00000000 -0.00975807 -0.03719382 -0.04347844 0.00961665 0.06837723 +O 4.27800000 2.47000000 10.79300000 -0.02030000 -0.04040000 0.00180000 -0.04040000 0.03600000 0.02230000 0.00180000 0.02230000 -0.01560000 -0.00005774 -0.05713423 0.03153696 -0.01914684 0.00254558 -0.03981011 +O -1.63100000 4.35300000 8.50600000 -0.08450000 0.06850000 0.00100000 0.06850000 0.10290000 0.05100000 0.00100000 0.05100000 -0.01840000 0.00000000 0.09687363 0.07212489 -0.02253531 0.00141421 -0.13251181 +O 1.63100000 4.35300000 8.50600000 -0.01730000 -0.05340000 0.00470000 -0.05340000 0.03800000 0.00580000 0.00470000 0.00580000 -0.02070000 0.00000000 -0.07551900 0.00820244 -0.02535222 0.00664680 -0.03910300 +O 0.00000000 1.52800000 8.50600000 0.11500000 -0.04930000 0.04230000 -0.04930000 -0.09940000 -0.02360000 0.04230000 -0.02360000 -0.01560000 -0.00000000 -0.06972073 -0.03337544 -0.01910602 0.05982123 0.15160369 +Ti 0.00000000 0.00000000 7.23700000 0.12130000 0.00770000 -0.02780000 0.00770000 0.11310000 -0.01550000 -0.02780000 -0.01550000 -0.23440000 0.00000000 0.01088944 -0.02192031 -0.28708020 -0.03931514 0.00579828 +Ti 2.95500000 1.70600000 12.06200000 0.06850000 0.00170000 -0.00040000 0.00170000 0.15800000 0.07420000 -0.00040000 0.07420000 -0.22650000 0.00000000 0.00240416 0.10493465 -0.27740471 -0.00056569 -0.06328606 +Ti 0.00000000 3.41200000 2.41200000 0.11410000 0.02350000 -0.06950000 0.02350000 0.13930000 0.03440000 -0.06950000 0.03440000 -0.25340000 0.00000000 0.03323402 0.04864895 -0.31035035 -0.09828784 -0.01781909 +Ti -1.47700000 2.55900000 0.00000000 -0.07950000 -0.23590000 0.02490000 -0.23590000 -0.03130000 -0.07880000 0.02490000 -0.07880000 0.11080000 0.00000000 -0.33361298 -0.11144003 0.13570173 0.03521392 -0.03408255 +Ti 1.47700000 2.55900000 0.00000000 -0.07700000 0.23690000 -0.02620000 0.23690000 -0.03580000 -0.08190000 -0.02620000 -0.08190000 0.11280000 -0.00000000 0.33502719 -0.11582409 0.13815122 -0.03705240 -0.02913280 +Ti 0.00000000 1.70600000 4.82500000 0.17880000 -0.08960000 0.06410000 -0.08960000 -0.29170000 0.00520000 0.06410000 0.00520000 0.11290000 0.00000000 -0.12671354 0.00735391 0.13827370 0.09065109 0.33269374 +Ti -1.47700000 4.26500000 4.82500000 -0.25380000 0.15710000 0.03800000 0.15710000 0.13950000 0.05520000 0.03800000 0.05520000 0.11430000 0.00000000 0.22217295 0.07806459 0.13998834 0.05374012 -0.27810510 +Ti 2.95500000 3.41200000 9.65000000 0.05240000 0.00560000 -0.01530000 0.00560000 -0.07320000 0.12950000 -0.01530000 0.12950000 0.02090000 -0.00005774 0.00791960 0.18314066 0.02555634 -0.02163747 0.08881261 +Ti 1.47700000 0.85300000 9.65000000 -0.03370000 -0.04520000 -0.09410000 -0.04520000 0.01840000 -0.05300000 -0.09410000 -0.05300000 0.01530000 0.00000000 -0.06392245 -0.07495332 0.01873860 -0.13307750 -0.03684026 +Ti 4.43200000 0.85300000 9.65000000 -0.03740000 0.05880000 0.10420000 0.05880000 0.01800000 -0.07660000 0.10420000 -0.07660000 0.01940000 0.00000000 0.08315576 -0.10832876 0.02376005 0.14736105 -0.03917372 +42 +Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" +Li 0.00000000 0.00000000 1.80900000 0.00220000 -0.00030000 0.00010000 -0.00030000 0.00880000 -0.00130000 0.00010000 -0.00130000 -0.01100000 0.00000000 -0.00042426 -0.00183848 -0.01347219 0.00014142 -0.00466690 +Li 0.00000000 0.00000000 12.66500000 -0.00280000 0.00000000 -0.00000000 0.00000000 0.00270000 -0.00120000 -0.00000000 -0.00120000 0.00010000 -0.00000000 0.00000000 -0.00169706 0.00012247 0.00000000 -0.00388909 +Li 2.95500000 1.70600000 6.63400000 0.00130000 0.00240000 0.00110000 0.00240000 -0.00140000 0.00060000 0.00110000 0.00060000 0.00010000 -0.00000000 0.00339411 0.00084853 0.00012247 0.00155563 0.00190919 +Li 2.95500000 1.70600000 3.01600000 0.00750000 0.00270000 0.00120000 0.00270000 0.00360000 0.00060000 0.00120000 0.00060000 -0.01100000 -0.00005774 0.00381838 0.00084853 -0.01351302 0.00169706 0.00275772 +Li 0.00000000 3.41200000 11.45900000 0.01240000 0.00000000 -0.00000000 0.00000000 0.01090000 0.00030000 -0.00000000 0.00030000 -0.02330000 0.00000000 0.00000000 0.00042426 -0.02853656 0.00000000 0.00106066 +Li 0.00000000 3.41200000 7.84000000 0.01120000 -0.00070000 -0.00030000 -0.00070000 0.01210000 -0.00010000 -0.00030000 -0.00010000 -0.02330000 0.00000000 -0.00098995 -0.00014142 -0.02853656 -0.00042426 -0.00063640 +Li 2.95500000 0.00000000 0.00000000 0.00540000 -0.00010000 0.00000000 -0.00010000 0.00580000 -0.00110000 0.00000000 -0.00110000 -0.01110000 -0.00005774 -0.00014142 -0.00155563 -0.01363549 0.00000000 -0.00028284 +Li 1.47700000 4.26500000 4.82500000 0.00580000 0.00010000 0.00100000 0.00010000 0.00540000 0.00060000 0.00100000 0.00060000 -0.01110000 -0.00005774 0.00014142 0.00084853 -0.01363549 0.00141421 0.00028284 +O 0.00000000 0.00000000 3.80700000 0.06960000 0.02760000 0.06340000 0.02760000 0.02540000 0.03140000 0.06340000 0.03140000 -0.09500000 0.00000000 0.03903229 0.04440631 -0.11635076 0.08966114 0.03125412 +O 0.00000000 0.00000000 10.66800000 0.03320000 -0.00070000 0.00110000 -0.00070000 0.03590000 -0.00410000 0.00110000 -0.00410000 -0.06910000 0.00000000 -0.00098995 -0.00579828 -0.08462987 0.00155563 -0.00190919 +O 2.95500000 1.70600000 8.63200000 0.03580000 0.00080000 0.00410000 0.00080000 0.03330000 0.00110000 0.00410000 0.00110000 -0.06910000 0.00000000 0.00113137 0.00155563 -0.08462987 0.00579828 0.00176777 +O 2.95500000 1.70600000 1.01800000 0.01260000 -0.00540000 0.00450000 -0.00540000 0.08240000 -0.07060000 0.00450000 -0.07060000 -0.09500000 0.00000000 -0.00763675 -0.09984348 -0.11635076 0.00636396 -0.04935605 +O 0.00000000 3.41200000 13.45700000 0.12430000 0.00800000 0.01070000 0.00800000 -0.06140000 -0.12620000 0.01070000 -0.12620000 -0.06280000 -0.00005774 0.01131371 -0.17847375 -0.07695480 0.01513209 0.13130973 +O 0.00000000 3.41200000 5.84300000 -0.02190000 -0.07640000 0.11460000 -0.07640000 0.08480000 0.05380000 0.11460000 0.05380000 -0.06280000 -0.00005774 -0.10804592 0.07608469 -0.07695480 0.16206887 -0.07544829 +O -1.32400000 4.17600000 1.14400000 0.00090000 0.03300000 0.10590000 0.03300000 0.04060000 -0.02200000 0.10590000 -0.02200000 -0.04150000 0.00000000 0.04666905 -0.03111270 -0.05082691 0.14976522 -0.02807214 +O 0.00000000 1.88300000 1.14400000 0.04800000 0.00270000 -0.01790000 0.00270000 -0.09840000 0.04810000 -0.01790000 0.04810000 0.05040000 -0.00000000 0.00381838 0.06802367 0.06172714 -0.02531442 0.10352043 +O 1.32400000 4.17600000 1.14400000 0.00290000 -0.02460000 -0.05270000 -0.02460000 0.03600000 -0.02930000 -0.05270000 -0.02930000 -0.03900000 0.00005774 -0.03478965 -0.04143646 -0.04772423 -0.07452905 -0.02340523 +O 4.27800000 0.94200000 13.33100000 -0.14650000 -0.05980000 0.13880000 -0.05980000 0.08730000 -0.00050000 0.13880000 -0.00050000 0.05920000 -0.00000000 -0.08456997 -0.00070711 0.07250490 0.19629284 -0.16532157 +O 1.63100000 0.94200000 13.33100000 -0.14660000 0.06540000 -0.13620000 0.06540000 0.07990000 -0.00180000 -0.13620000 -0.00180000 0.06660000 0.00005774 0.09248957 -0.00254558 0.08160883 -0.19261589 -0.16015969 +O 2.95500000 3.23400000 13.33100000 0.00750000 -0.00120000 -0.00050000 -0.00120000 -0.08250000 0.07590000 -0.00050000 0.07590000 0.07500000 0.00000000 -0.00169706 0.10733881 0.09185587 -0.00070711 0.06363961 +O 4.58600000 0.76400000 5.96800000 0.08070000 0.07130000 0.06980000 0.07130000 -0.13990000 -0.12000000 0.06980000 -0.12000000 0.05920000 -0.00000000 0.10083343 -0.16970563 0.07250490 0.09871211 0.15598776 +O 2.95500000 3.58900000 5.96800000 -0.03330000 0.13080000 -0.06650000 0.13080000 -0.03330000 0.11880000 -0.06650000 0.11880000 0.06660000 -0.00000000 0.18497913 0.16800857 0.08156801 -0.09404520 0.00000000 +O 1.32400000 0.76400000 5.96800000 -0.05890000 -0.03960000 -0.06600000 -0.03960000 -0.01600000 -0.03760000 -0.06600000 -0.03760000 0.07500000 -0.00005774 -0.05600286 -0.05317443 0.09181504 -0.09333810 -0.03033488 +O 1.32400000 2.64800000 3.68100000 0.00210000 0.03370000 0.07200000 0.03370000 0.03940000 -0.08070000 0.07200000 -0.08070000 -0.04150000 0.00000000 0.04765900 -0.11412703 -0.05082691 0.10182338 -0.02637508 +O -1.32400000 2.64800000 3.68100000 -0.06420000 -0.06210000 -0.05060000 -0.06210000 0.01370000 -0.00860000 -0.05060000 -0.00860000 0.05040000 0.00005774 -0.08782266 -0.01216224 0.06176797 -0.07155921 -0.05508362 +O 0.00000000 4.94000000 3.68100000 0.04900000 0.00210000 -0.00100000 0.00210000 -0.01000000 0.06030000 -0.00100000 0.06030000 -0.03900000 0.00000000 0.00296985 0.08527708 -0.04776505 -0.00141421 0.04171930 +O 1.63100000 2.47000000 10.79300000 -0.00840000 0.03020000 0.00200000 0.03020000 0.04270000 0.01720000 0.00200000 0.01720000 -0.03430000 0.00000000 0.04270925 0.02432447 -0.04200875 0.00282843 -0.03613316 +O 2.95500000 0.17700000 10.79300000 0.05480000 -0.00280000 -0.00260000 -0.00280000 -0.01500000 -0.00150000 -0.00260000 -0.00150000 -0.03990000 0.00005774 -0.00395980 -0.00212132 -0.04882650 -0.00367696 0.04935605 +O 4.27800000 2.47000000 10.79300000 -0.00820000 -0.03260000 -0.00420000 -0.03260000 0.03930000 0.01890000 -0.00420000 0.01890000 -0.03110000 0.00000000 -0.04610336 0.02672864 -0.03808957 -0.00593970 -0.03358757 +O -1.63100000 4.35300000 8.50600000 0.00370000 0.03720000 -0.01390000 0.03720000 0.03060000 -0.01030000 -0.01390000 -0.01030000 -0.03430000 0.00000000 0.05260874 -0.01456640 -0.04200875 -0.01965757 -0.01902117 +O 1.63100000 4.35300000 8.50600000 0.00490000 -0.03160000 -0.00000000 -0.03160000 0.03490000 0.00300000 -0.00000000 0.00300000 -0.03990000 0.00005774 -0.04468915 0.00424264 -0.04882650 0.00000000 -0.02121320 +O 0.00000000 1.52800000 8.50600000 0.05570000 0.00420000 -0.01850000 0.00420000 -0.02460000 -0.00580000 -0.01850000 -0.00580000 -0.03110000 0.00000000 0.00593970 -0.00820244 -0.03808957 -0.02616295 0.05678067 +Ti 0.00000000 0.00000000 7.23700000 0.13420000 0.03990000 -0.06390000 0.03990000 0.08870000 -0.03650000 -0.06390000 -0.03650000 -0.22290000 0.00000000 0.05642712 -0.05161880 -0.27299563 -0.09036825 0.03217336 +Ti 2.95500000 1.70600000 12.06200000 0.06550000 0.00020000 -0.00030000 0.00020000 0.15730000 0.07350000 -0.00030000 0.07350000 -0.22290000 0.00005774 0.00028284 0.10394470 -0.27295481 -0.00042426 -0.06491240 +Ti 0.00000000 3.41200000 2.41200000 0.11380000 0.02260000 -0.06520000 0.02260000 0.13990000 0.03760000 -0.06520000 0.03760000 -0.25360000 -0.00005774 0.03196123 0.05317443 -0.31063612 -0.09220672 -0.01845549 +Ti -1.47700000 2.55900000 0.00000000 -0.07950000 -0.23640000 0.02580000 -0.23640000 -0.03160000 -0.07830000 0.02580000 -0.07830000 0.11110000 -0.00000000 -0.33432009 -0.11073292 0.13606916 0.03648671 -0.03387041 +Ti 1.47700000 2.55900000 0.00000000 -0.07730000 0.23740000 -0.02720000 0.23740000 -0.03590000 -0.08120000 -0.02720000 -0.08120000 0.11320000 -0.00000000 0.33573430 -0.11483414 0.13864112 -0.03846661 -0.02927422 +Ti 0.00000000 1.70600000 4.82500000 0.16120000 -0.09750000 0.08070000 -0.09750000 -0.27230000 0.01680000 0.08070000 0.01680000 0.11110000 -0.00000000 -0.13788582 0.02375879 0.13606916 0.11412703 0.30653079 +Ti -1.47700000 4.26500000 4.82500000 -0.25180000 0.13660000 0.05670000 0.13660000 0.13870000 0.06410000 0.05670000 0.06410000 0.11320000 -0.00005774 0.19318157 0.09065109 0.13860029 0.08018591 -0.27612520 +Ti 2.95500000 3.41200000 9.65000000 0.05700000 -0.00020000 -0.00130000 -0.00020000 -0.06260000 0.08400000 -0.00130000 0.08400000 0.00560000 0.00000000 -0.00028284 0.11879394 0.00685857 -0.00183848 0.08456997 +Ti 1.47700000 0.85300000 9.65000000 -0.03250000 -0.05190000 -0.07340000 -0.05190000 0.02690000 -0.04090000 -0.07340000 -0.04090000 0.00560000 -0.00000000 -0.07339768 -0.05784133 0.00685857 -0.10380328 -0.04200214 +Ti 4.43200000 0.85300000 9.65000000 -0.03240000 0.05320000 0.07190000 0.05320000 0.02900000 -0.04150000 0.07190000 -0.04150000 0.00340000 -0.00000000 0.07523616 -0.05868986 0.00416413 0.10168196 -0.04341636 From f7c585d96f3eeff5718865c4eedbab877fc2371e Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 26 Jul 2023 10:56:39 +0200 Subject: [PATCH 29/96] Some cleaning up --- .../rascaline/utils/clebsch_gordan.py | 217 ++++++++---------- 1 file changed, 90 insertions(+), 127 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index 5fee005e3..2159d8c40 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -51,7 +51,9 @@ class ClebschGordanReal: def __init__(self, lambda_max: int, sparse: bool = True): self._lambda_max = lambda_max self._sparse = sparse - self._coeffs = ClebschGordanReal.build_coeff_dict(self._lambda_max, self._sparse) + self._coeffs = ClebschGordanReal.build_coeff_dict( + self._lambda_max, self._sparse + ) @property def coeffs(self): @@ -93,13 +95,15 @@ def build_coeff_dict(lambda_max: int, sparse: bool): if (l1 + l2 + lam) % 2 == 0: cg_l1l2lam = np.real(real_cg) else: - cg_l1l2lam = np.imag(real_cg) + cg_l1l2lam = np.imag(real_cg) if sparse: # if sparse we make a dictionary out of the matrix nonzeros_cg_coeffs_idx = np.where(np.abs(cg_l1l2lam) > 1e-15) - cg_l1l2lam = {(m1, m2, mu): cg_l1l2lam[m1, m2, mu] - for m1, m2, mu in zip(*nonzeros_cg_coeffs_idx)} + cg_l1l2lam = { + (m1, m2, mu): cg_l1l2lam[m1, m2, mu] + for m1, m2, mu in zip(*nonzeros_cg_coeffs_idx) + } coeff_dict[(l1, l2, lam)] = cg_l1l2lam return coeff_dict @@ -225,18 +229,10 @@ def n_body_iteration_single_center( The returned TensorMap will only contain blocks with angular channels of target order lambda corresponding to those passed in ``lambdas``. -k_angular - Passing ``lambda_cut`` will place a maximum on the angular - order of blocks created by combination at each CG combination step. - NOTE: currently only lambda-SOAP (nu_target = 2) is implemented. + Passing ``lambda_cut`` will place a maximum value on the angular + order of blocks created by combination at each CG combination step. """ - # TODO: remove once we can perform higher body orders. - # if nu_target > 2: - # raise NotImplementedError( - # "currently CG iterations only implemented for max body order nu=2" - # ) - # Set default lambda_cut if not passed if lambda_cut is None: # WARNING: the default is the maximum possible angular order for the @@ -270,12 +266,21 @@ def n_body_iteration_single_center( ) nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) - # Standardize the key names metadata - nu1_tensor = _add_nu_sigma_to_key_names(nu1_tensor) + # # Standardize the key names metadata + # nu1_tensor = _add_nu_sigma_to_key_names(nu1_tensor) + + # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 + nu1_tensor = equistore.insert_dimension( + nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 + ) + nu1_tensor = equistore.insert_dimension( + nu1_tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 + ) - # TODO: Combine to the desired body order iteratively. Currently only a - # single CG iteration to body order nu = 2 is implemented. + # Create a copy of the nu = 1 TensorMap to combine with itself combined_tensor = nu1_tensor.copy() + + # Iteratively combine untilt the target body order is reached for _ in range(1, nu_target): combined_tensor = _combine_single_center( tensor_1=combined_tensor, @@ -285,14 +290,16 @@ def n_body_iteration_single_center( use_sparse=use_sparse, ) - # TODO: Account for body-order multiplicity + # TODO: Account for body-order multiplicity and normalize block values # combined_tensor = _apply_body_order_corrections(combined_tensor) + # combined_tensor = _normalize_blocks(combined_tensor) # Move the [l1, l2, ...] keys to the properties - combined_tensor = combined_tensor.keys_to_properties( - [f"l{i}" for i in range(1, nu_target + 1)] - + [f"k{i}" for i in range(2, nu_target)] - ) + if nu_target > 1: + combined_tensor = combined_tensor.keys_to_properties( + [f"l{i}" for i in range(1, nu_target + 1)] + + [f"k{i}" for i in range(2, nu_target)] + ) return combined_tensor @@ -330,8 +337,8 @@ def _combine_single_center( .. math :: - \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n_{\nu-1} l_{\nu-1} k_{\nu-1} ; - n_{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } + \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n{\nu-1} l_{\nu-1} k_{\nu-1} ; + n{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } The keys of `tensor_2` must follow the key name convention: @@ -345,12 +352,11 @@ def _combine_single_center( Property names are ["n1", "n2", ..., "species_neighbor_1", "species_neighbor_2", ...] for each block. """ - # TODO: Check all the samples are equivalent - # if not equistore.equal_metadata(tensor_1, tensor_2, check=["samples"]): - # raise ValueError( - # "TensorMaps `tensor_1` and `tensor_2` to combine must have equivalent keys " - # "(order agnostic), and equal samples (same order)" - # ) + + # TODO: what metadata can we check for equivalence here? + # - Keys are not the same in general, as we accumulate "lx" and "kx" + # dimensions + # Get the correct keys for the combined TensorMap ( @@ -365,28 +371,7 @@ def _combine_single_center( for combined_key, key_1, key_2, multi in zip( combined_keys, keys_1_entries, keys_2_entries, multiplicity_list ): - # # Parse info from the combined key - # nu, sig, lam, a = combined_key.values[:4] - # if nu > 2: - # lam_prev_1 = combined_key[f"k{nu-1}"] - # else: - # lam_prev_1 = combined_key[f"spherical_harmonics_l"] - # lam_prev_2 = combined_key[f"l{nu}"] - - # # Extract the pair of blocks to combine. The lambda values of the block - # # pair being combined are stored in `combination_info`. - # l_list = combined_key.values[4 : 4 + (nu + 1) - 1].tolist() - # k_list = combined_key.values[4 + (nu + 1) : -1].tolist() - # block_1 = tensor_1.block( - # order_nu=nu, - # inversion_sigma=sig, - # spherical_harmonics_l=lam_prev_1, - # species_center=a, - # **{f"l{i + 1}": l for i, l in enumerate(l_list)}, - # **{f"k{i + 2}": k for i, k in enumerate(k_list)}, - # ) - # block_2 = tensor_2.block(spherical_harmonics_l=lam_prev_2, - # species_center=a) + # Retrieve the blocks block_1 = tensor_1[key_1] block_2 = tensor_2[key_2] @@ -419,20 +404,18 @@ def _combine_single_center_block_pair( For a given pair of TensorBlocks and desired lambda value, combines the values arrays and returns in a new TensorBlock. """ - # Check metadata - # if block_1.properties.names != block_2.properties.names: - # raise ValueError( - # "TensorBlock pair to combine must have equal properties in the same order" - # ) - # Do the CG combination - no shape pre-processing required + # Do the CG combination - single center so no shape pre-processing required combined_values = _clebsch_gordan_combine( block_1.values, block_2.values, lam, cg_cache, use_sparse ) - # - combined_nu = int(len(block_1.properties.names) / 2 + 1) - n_names = [f"n_{i}" for i in range(1, combined_nu + 1)] + # Infer the new nu value: block 1's properties are nu pairs of + # "species_neighbor_x" and "nx". + combined_nu = int((len(block_1.properties.names) / 2) + 1) + + # Define the new property names for "nx" and "species_neighbor_x" + n_names = [f"n{i}" for i in range(1, combined_nu + 1)] neighbor_names = [f"species_neighbor_{i}" for i in range(1, combined_nu + 1)] prop_names = [item for i in zip(neighbor_names, n_names) for item in i] @@ -446,8 +429,6 @@ def _combine_single_center_block_pair( values=np.arange(-lam, lam + 1).reshape(-1, 1), ), ], - # TODO: account for more "species_neighbor_x" and "nx", i.e. for higher - # body order properties=Labels( names=prop_names, values=np.array( @@ -507,7 +488,20 @@ def _clebsch_gordan_combine_sparse( cg_cache, ) -> np.ndarray: """ - TODO: docstring + TODO: finish docstring. + + Performs a Clebsch-Gordan combination step on 2 arrays using sparse + operations. + + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + + 1, n_p_properties] + :param lam: int value of the resulting coupled channel + :param cg_cache: sparse dictionary with keys (m1, m2, mu) and array values + being sparse blocks of shape + + :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] """ # Samples dimensions must be the same assert arr_1.shape[0] == arr_2.shape[0] @@ -538,19 +532,26 @@ def _clebsch_gordan_combine_sparse( def _clebsch_gordan_combine_dense( - arr_1, - arr_2, + arr_1: np.ndarray, + arr_2: np.ndarray, lam: int, cg_cache, -): +) -> np.ndarray: """ + Performs a Clebsch-Gordan combination step on 2 arrays using a dense + operation. + + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + + 1, n_p_properties] + :param lam: int value of the resulting coupled channel + :param cg_cache: dense array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + + 1)] - :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + 1, n_q_properties] - :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + 1, n_p_properties] - :param lam: int resulting coupled channel - :param cg_cache: array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)] + :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] - :returns lam_mu_values: array of shape [samples, (2*lam+1), q_properties*p_properties] + TODO: do we need this example here? Could it be moved to a test? >>> N_SAMPLES = 30 >>> N_Q_PROPERTIES = 10 @@ -579,19 +580,24 @@ def _clebsch_gordan_combine_dense( # (samples None None l1_mu q) * (samples l2_mu p None None) -> (samples l2_mu p l1_mu q) # we broadcast it in this way so we only need to do one swapaxes in the next step - out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] + arr_out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] + # (samples l2_mu p l1_mu q) -> (samples q p l1_mu l2_mu) - out = out.swapaxes(1, 4) + arr_out = arr_out.swapaxes(1, 4) + # samples (q p l1_mu l2_mu) -> (samples (q p) (l1_mu l2_mu)) - out = out.reshape( + arr_out = arr_out.reshape( -1, arr_1.shape[2] * arr_2.shape[2], arr_1.shape[1] * arr_2.shape[1] ) + # (l1_mu l2_mu lam_mu) -> ((l1_mu l2_mu) lam_mu) cg_coeffs = cg_coeffs.reshape(-1, 2 * lam + 1) + # (samples (q p) (l1_mu l2_mu)) @ ((l1_mu l2_mu) lam_mu) -> samples (q p) lam_mu - out = out @ cg_coeffs + arr_out = arr_out @ cg_coeffs + # (samples (q p) lam_mu) -> (samples lam_mu (q p)) - return out.swapaxes(1, 2) + return arr_out.swapaxes(1, 2) def _apply_body_order_corrections(tensor: TensorMap) -> TensorMap: @@ -602,18 +608,12 @@ def _apply_body_order_corrections(tensor: TensorMap) -> TensorMap: return tensor -# Commented out but left as reference. Not needed as we are just writing an -# end-to-end pipeline for SphericalExpansion -> NICE. -# def _check_nu_combination_valid() -> bool: -# """ """ -# # # Check "order_nu" of each TM to see that it can iteratively add to nu -# # nu1 = np.unique(tensor_1.keys.column("order_nu")) -# # nu2 = np.unique(tensor_2.keys.column("order_nu")) -# # assert len(nu1) != 1 -# # assert len(nu2) != 1 -# # nu1, nu2 = nu1[0], nu2[0] -# # assert _check_nu_combination_valid(nu1, nu2, nu) -# return True +def _normalize_blocks(tensor: TensorMap) -> TensorMap: + """ + Applies corrections to the block values based on their 'leaf' l-values, such + that the norm is preserved. + """ + return tensor # ===== Fxns to manipulate metadata of TensorMaps ===== @@ -738,8 +738,6 @@ def _create_combined_keys( keys_1_entries.append(key_1) keys_2_entries.append(key_2) - # print(new_names) - # print(new_key_values) # Define new keys as the full product of keys_1 and keys_2 combined_keys = Labels(names=new_names, values=np.array(new_key_values)) @@ -787,38 +785,3 @@ def _create_combined_keys( ] return combined_keys_red, keys_1_entries_red, keys_2_entries_red, mult_list - - -def _add_nu_sigma_to_key_names(tensor: TensorMap) -> TensorMap: - """ - Prepends key names "order_nu" and "inversion_sigma" respectively to the key - names of ``tensor``. This function should only be used on a nu=1 - SphericalExpansion. - - For instance, for a tensor with `tensor.keys.names` as - ["spherical_harmonics_l", "species_center"], the returned tensor will have - keys with names ["order_nu", "inversion_sigma", "spherical_harmonics_l", - "species_center"]. - """ - keys = tensor.keys - if keys.names != ["spherical_harmonics_l", "species_center"]: - raise ValueError( - "this function is only intended to be used on nu=1 SphericalExpansion" - ) - prepend_list = [] - if "inversion_sigma" in keys.names: - assert keys.names.index("inversion_sigma") == 1 - else: - prepend_list = [1] + prepend_list - if "order_nu" in keys.names: - assert keys.names.index("order_nu") == 0 - else: - prepend_list = [1] + prepend_list - - new_keys = Labels( - names=["order_nu", "inversion_sigma"] + keys.names, - values=np.array([prepend_list + key_list for key_list in keys.values.tolist()]), - ) - new_blocks = [block.copy() for block in tensor] - - return TensorMap(keys=new_keys, blocks=new_blocks) From ba5b77e0fc1b3694090f22e944863ae497a23c25 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 26 Jul 2023 10:58:45 +0200 Subject: [PATCH 30/96] Example `nu_target=3` iteration with new test system --- python/rascaline/rascaline/utils/tmp.ipynb | 185 ++++++++++++++++++--- 1 file changed, 161 insertions(+), 24 deletions(-) diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index 20c2aaabb..fc45b06c3 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -20,20 +20,159 @@ "import clebsch_gordan" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test system 1" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "frames = ase.io.read(\"combined_magres_spherical.xyz\", \":\")\n", + "\n", + "# Define hyperparameters for generating the rascaline SphericalExpansion\n", + "rascal_hypers = {\n", + " \"cutoff\": 3.0, # Angstrom\n", + " \"max_radial\": 6, # Exclusive\n", + " \"max_angular\": 5, # Inclusive\n", + " \"atomic_gaussian_width\": 0.2,\n", + " \"radial_basis\": {\"Gto\": {}},\n", + " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", + " \"center_atom_weight\": 1.0,\n", + "}\n", + "\n", + "# Define target lambda channels\n", + "lambdas = np.array([0, 2])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "22.5 s ± 925 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "use_sparse = False\n", + "\n", + "tensor_dense = clebsch_gordan.n_body_iteration_single_center(\n", + " frames,\n", + " rascal_hypers=rascal_hypers,\n", + " nu_target=3,\n", + " lambdas=lambdas,\n", + " use_sparse=use_sparse,\n", + ")\n", + "tensor_dense\n", + "\n", + "# timeit comes out at about 22 seconds for nu_target = 3" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12.9 s ± 1.92 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "use_sparse = True\n", + "\n", + "tensor_sparse = clebsch_gordan.n_body_iteration_single_center(\n", + " frames,\n", + " rascal_hypers=rascal_hypers,\n", + " nu_target=3,\n", + " lambdas=lambdas,\n", + " use_sparse=use_sparse,\n", + ")\n", + "tensor_sparse\n", + "\n", + "# timeit comes out at about 13 seconds for nu_target = 3" + ] + }, + { + "cell_type": "code", + "execution_count": 7, "metadata": {}, "outputs": [], + "source": [ + "tensor_sparse = clebsch_gordan.n_body_iteration_single_center(\n", + " frames,\n", + " rascal_hypers=rascal_hypers,\n", + " nu_target=3,\n", + " lambdas=lambdas,\n", + " use_sparse=True,\n", + ")\n", + "\n", + "tensor_dense = clebsch_gordan.n_body_iteration_single_center(\n", + " frames,\n", + " rascal_hypers=rascal_hypers,\n", + " nu_target=3,\n", + " lambdas=lambdas,\n", + " use_sparse=False,\n", + ")\n", + "\n", + "assert equistore.allclose(tensor_dense, tensor_sparse)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test system 2" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "TensorMap with 8 blocks\n", + "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", + " 3 1 0 1\n", + " 3 -1 2 1\n", + " ...\n", + " 3 1 2 8\n", + " 3 -1 0 8" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "frames = [ase.io.read(\"frame.xyz\")]\n", "\n", - "lmax = 5\n", "lambdas = np.array([0, 2])\n", + "\n", "rascal_hypers = {\n", " \"cutoff\": 3.0, # Angstrom\n", " \"max_radial\": 6, # Exclusive\n", - " \"max_angular\": lmax, # Inclusive\n", + " \"max_angular\": 5, # Inclusive\n", " \"atomic_gaussian_width\": 0.2,\n", " \"radial_basis\": {\"Gto\": {}},\n", " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", @@ -45,8 +184,6 @@ " rascal_hypers=rascal_hypers,\n", " nu_target=3,\n", " lambdas=lambdas,\n", - " lambda_cut=lmax * 2,\n", - " species_neighbors=[1, 8, 6],\n", ")\n", "n_body" ] @@ -57,24 +194,24 @@ "metadata": {}, "outputs": [], "source": [ - "selected_samples = None\n", - "species_neighbors = [1, 8, 6]\n", - "\n", - "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", - "nu1 = calculator.compute(frames, selected_samples=selected_samples)\n", - "\n", - "# Move the \"species_neighbor\" key to the properties. If species_neighbors is\n", - "# passed as a list of int, sparsity can be created in the properties for\n", - "# these species.\n", - "if species_neighbors is None:\n", - " keys_to_move = \"species_neighbor\"\n", - "else:\n", - " keys_to_move = Labels(\n", - " names=[\"species_neighbor\"],\n", - " values=np.array(species_neighbors).reshape(-1, 1),\n", - " )\n", - "nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move)\n", - "nu1 = clebsch_gordan._add_nu_sigma_to_key_names(nu1)" + "# selected_samples = None\n", + "# species_neighbors = [1, 8, 6]\n", + "\n", + "# calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", + "# nu1 = calculator.compute(frames, selected_samples=selected_samples)\n", + "\n", + "# # Move the \"species_neighbor\" key to the properties. If species_neighbors is\n", + "# # passed as a list of int, sparsity can be created in the properties for\n", + "# # these species.\n", + "# if species_neighbors is None:\n", + "# keys_to_move = \"species_neighbor\"\n", + "# else:\n", + "# keys_to_move = Labels(\n", + "# names=[\"species_neighbor\"],\n", + "# values=np.array(species_neighbors).reshape(-1, 1),\n", + "# )\n", + "# nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move)\n", + "# nu1 = clebsch_gordan._add_nu_sigma_to_key_names(nu1)" ] } ], From 39ec6373bcc66cd9b1aa91c4e68d114e102eac0f Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 31 Jul 2023 18:11:18 +0200 Subject: [PATCH 31/96] Old CG code for reference --- python/rascaline/rascaline/utils/spherical.py | 1100 +++++++++++++++++ 1 file changed, 1100 insertions(+) create mode 100644 python/rascaline/rascaline/utils/spherical.py diff --git a/python/rascaline/rascaline/utils/spherical.py b/python/rascaline/rascaline/utils/spherical.py new file mode 100644 index 000000000..b195fa30e --- /dev/null +++ b/python/rascaline/rascaline/utils/spherical.py @@ -0,0 +1,1100 @@ +""" +Classes and functions to aid converting to and from, and operating within, the +spherical basis. Contains classes WignerDReal and ClebschGordanReal, as well as +functions to perform Clebsch-Gordan iterations. Code mostly taken from +librascal: + +github.com/lab-cosmo/librascal/blob/master/bindings/rascal/utils/cg_utils.py +""" +from copy import deepcopy +from itertools import product +import re +from typing import Optional, Tuple, Sequence + +import ase +import numpy as np +from scipy.spatial.transform import Rotation +import torch +import wigners + +import equistore +from equistore import Labels, TensorBlock, TensorMap +import rascaline + + +# ===== WignerDReal class for describing rotations + + +class WignerDReal: + """ + A helper class to compute Wigner D matrices given the Euler angles of a rotation, + and apply them to spherical harmonics (or coefficients). Built to function with + real-valued coefficients. + """ + + def __init__(self, lmax, alpha, beta, gamma): + """ + Initialize the WignerDReal class. + lmax: int + maximum angular momentum channel for which the Wigner D matrices are + computed + alpha, beta, gamma: float + Euler angles, in radians + """ + self._lmax = lmax + + self._rotation = cartesian_rotation(alpha, beta, gamma) + + r2c_mats = {} + c2r_mats = {} + for L in range(0, self._lmax + 1): + r2c_mats[L] = np.hstack( + [_r2c(np.eye(2 * L + 1)[i])[:, np.newaxis] for i in range(2 * L + 1)] + ) + c2r_mats[L] = np.conjugate(r2c_mats[L]).T + self._wddict = {} + for L in range(0, self._lmax + 1): + wig = _wigner_d(L, alpha, beta, gamma) + self._wddict[L] = np.real(c2r_mats[L] @ np.conjugate(wig) @ r2c_mats[L]) + + def rotate(self, rho): + """ + Rotates a vector of 2l+1 spherical harmonics (coefficients) according to the + rotation defined in the initialization. + rho: array + List of 2l+1 coefficients + Returns: + -------- + (2l+1) array containing the coefficients for the rotated structure + """ + + L = (rho.shape[-1] - 1) // 2 + return rho @ self._wddict[L].T + + def rotate_frame(self, frame, in_place=False): + """ + Utility function to also rotate a structure, given as an Atoms frame. + NB: it will rotate positions and cell, and no other array. + frame: ase.Atoms + An atomic structure in ASE format, that will be modified in place + in_frame: bool + Whether the frame should be copied or processed in place (defaults to False) + Returns: + ------- + The rotated frame. + """ + + if is_ase_Atoms(frame): + if in_place: + frame = frame.copy() + frame.positions = frame.positions @ self._rotation.T + frame.cell = frame.cell @ self._rotation.T + else: + if in_place: + frame = deepcopy(frame) + frame["positions"] = self._rotation @ frame["positions"] + frame["cell"] = self._rotation @ frame["cell"] + return frame + + def rotate_coeff_vector( + self, + frame: ase.Atoms, + coeffs: np.ndarray, + lmax: dict, + nmax: dict, + ) -> np.ndarray: + """ + Rotates the irreducible spherical components (ISCs) of basis set + coefficients in the spherical basis passed in as a flat vector. + + Given the basis set definition specified by ``lmax`` and ``nmax``, the + assumed ordering of basis function coefficients follows the following + hierarchy, which can be read as nested loops over the various indices. + Be mindful that some indices range are from 0 to x (exclusive) and + others from 0 to x + 1 (exclusive). The ranges reported below are + ordered. + + 1. Loop over atoms (index ``i``, of chemical species ``a``) in the + structure. ``i`` takes values 0 to N (** exclusive **), where N is the + number of atoms in the structure. + + 2. Loop over spherical harmonics channel (index ``l``) for each atom. + ``l`` takes values from 0 to ``lmax[a] + 1`` (** exclusive **), where + ``a`` is the chemical species of atom ``i``, given by the chemical + symbol at the ``i``th position of ``symbol_list``. + + 3. Loop over radial channel (index ``n``) for each atom ``i`` and + spherical harmonics channel ``l`` combination. ``n`` takes values from 0 + to ``nmax[(a, l)]`` (** exclusive **). + + 4. Loop over spherical harmonics component (index ``m``) for each atom. + ``m`` takes values from ``-l`` to ``l`` (** inclusive **). + + :param frame: the atomic structure in ASE format for which the + coefficients are defined. + :param coeffs: the coefficients in the spherical basis, as a flat + vector. + :param lmax: dict containing the maximum spherical harmonics (l) value + for each atom type. + :param nmax: dict containing the maximum radial channel (n) value for + each combination of atom type and l. + + :return: the rotated coefficients in the spherical basis, as a flat + vector with the same order as the input vector. + """ + # Initialize empty vector for storing the rotated ISCs + rot_vect = np.empty_like(coeffs) + + # Iterate over atomic species of the atoms in the frame + curr_idx = 0 + for symbol in frame.get_chemical_symbols(): + # Get the basis set lmax value for this species + sym_lmax = lmax[symbol] + for l in range(sym_lmax + 1): + # Get the number of radial functions for this species and l value + sym_l_nmax = nmax[(symbol, l)] + # Get the Wigner D Matrix for this l value + wig_mat = self._wddict[l].T + for n in range(sym_l_nmax): + # Retrieve the irreducible spherical component + isc = coeffs[curr_idx : curr_idx + (2 * l + 1)] + # Rotate the ISC and store + rot_isc = isc @ wig_mat + rot_vect[curr_idx : curr_idx + (2 * l + 1)][:] = rot_isc[:] + # Update the start index for the next ISC + curr_idx += 2 * l + 1 + + return rot_vect + + def rotate_tensorblock(self, l: int, block: TensorBlock) -> TensorBlock: + """ + Rotates a TensorBlock ``block``, represented in the spherical basis, + according to the Wigner D Real matrices for the given ``l`` value. + Assumes the components of the block are [("spherical_harmonics_m",),]. + """ + + # Get the Wigner matrix for this l value + wig = self._wddict[l].T + + # Copy the block + block_rotated = block.copy() + vals = block_rotated.values + + # Perform the rotation, either with numpy or torch, by taking the + # tensordot product of the irreducible spherical components. Modify in-place the + # values of the copied TensorBlock + if isinstance(vals, torch.Tensor): + wig = torch.tensor(wig) + block_rotated.values[:] = torch.tensordot( + vals.swapaxes(1, 2), wig, dims=1 + ).swapaxes(1, 2) + elif isinstance(block.values, np.ndarray): + block_rotated.values[:] = np.tensordot( + vals.swapaxes(1, 2), wig, axes=1 + ).swapaxes(1, 2) + else: + raise TypeError("TensorBlock values must be a numpy array or torch tensor.") + + return block_rotated + + def rotate_tensormap(self, tensor: TensorMap) -> TensorMap: + """ + Rotates a TensorMap usign Wigner D Matrices. Assumes the tensor keys has + a name "spherical_harmonics_l" that indicates the l value, and that each + block has exactly one component axis, named by + ("spherical_harmonics_m",). + """ + # Retrieve the key and the position of the l value in the key names + keys = tensor.keys + idx_l_value = keys.names.index("spherical_harmonics_l") + + # Iterate over the blocks and rotate + rotated_blocks = [] + for key in keys: + # Retrieve the l value + l = key[idx_l_value] + + # Rotate the block and store + rotated_blocks.append(self.rotate_tensorblock(l, tensor[key])) + + return TensorMap(keys, rotated_blocks) + + +# ===== helper functions for WignerDReal + + +def rotate_ase_frame(frame) -> Tuple[ase.Atoms, Tuple[float, float, float]]: + """ + Make a copy of the input ``frame``. Randomly rotates its xyz and cell + coordinates and returns the new frame, and euler angles alpha, beta, and + gamma. + """ + # Randomly generate euler angles between 0 and pi + alpha, beta, gamma = np.random.uniform(size=(3)) * np.pi + # Build cartesian rotation matrix + R = cartesian_rotation(alpha, beta, gamma) + # Copy the frame + rotated_frame = frame.copy() + # Rotate its positions and cell + rotated_frame.positions = rotated_frame.positions @ R.T + rotated_frame.cell = rotated_frame.cell @ R.T + + return rotated_frame, (alpha, beta, gamma) + + +def _wigner_d(l, alpha, beta, gamma): + """Computes a Wigner D matrix + D^l_{mm'}(alpha, beta, gamma) + from sympy and converts it to numerical values. + (alpha, beta, gamma) are Euler angles (radians, ZYZ convention) and l the irrep. + """ + try: + from sympy.physics.wigner import wigner_d + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Calculation of Wigner D matrices requires a sympy installation" + ) + return np.complex128(wigner_d(l, alpha, beta, gamma)) + + +def cartesian_rotation(alpha, beta, gamma): + """A Cartesian rotation matrix in the appropriate convention + (ZYZ, implicit rotations) to be consistent with the common Wigner D definition. + (alpha, beta, gamma) are Euler angles (radians).""" + return Rotation.from_euler("ZYZ", [alpha, beta, gamma]).as_matrix() + + +def is_ase_Atoms(frame): + is_ase = True + if not hasattr(frame, "get_cell"): + is_ase = False + if not hasattr(frame, "get_positions"): + is_ase = False + if not hasattr(frame, "get_atomic_numbers"): + is_ase = False + if not hasattr(frame, "get_pbc"): + is_ase = False + return + + +def check_equivariance( + unrotated: TensorMap, + rotated: TensorMap, + lmax: int, + alpha: float, + beta: float, + gamma: float, + rtol: Optional[float] = 1e-15, + atol: Optional[float] = 1e-15, + n_checks_per_block: Optional[int] = None, +) -> bool: + """ + Checks equivariance by comparing the expansion coefficients of the + structural representations of an unrotated and rotated structure, rotating + the component vectors of the unrotated structure using a Wigner D-Matrix + constructed using parameters ``lmax``, ``alpha``, ``beta``, ``gamma``. + + If ``n_checks_per_block`` is passed (i.e. not None, the default), only this + number of (sample, property) combinations are checked per block. Otherwise, + all component vectors in every block are checked. + + :param unrotated: a TensorMap of the coefficients in the spherical basis for + the unrotated structure. + :param rotated: a TensorMap of the coefficients in the spherical basis for + the rotated structure. + :param lmax: the maximum l value for which the spherical basis is expanded. + :param alpha: the first Euler angle for the rotation between the unrotated + and rotated structure. + :param beta: the second Euler angle for the rotation between the unrotated + and rotated structure. + :param gamma: the third Euler angle for the rotation between the unrotated + and rotated structure. + :param rtol: the relative tolerance for the check. Default 1e-15. + :param atol: the absolute tolerance for the check. Default 1e-15. + :param n_checks_per_block: the number of comparisons between rotated and + unrotated structures to perform per block of the input TensorMaps. + + :return bool: True if the rotated and unrotated structures are exact rotated + forms of eachother in the spherical basis, within the defined + tolerances. False otherwise. + """ + equivariant = True + + # Check that the metadata is equivalent + equistore.equal_metadata(unrotated, rotated) + + # Build Wigner D-Matrices + wigner_d_matrices = WignerDReal(lmax, alpha, beta, gamma) + + # Check each block in turn + for key in rotated.keys: + # Access the blocks to compare + unr_block = unrotated[key] + rot_block = rotated[key] + + # Define the number of samples and properties + n_samples = len(unr_block.samples) + n_props = len(unr_block.properties) + + # If ``n_checks_per_block`` is passed, define a subset of samples and + # properties to check + samps_props = list(product(range(n_samples), range(n_props))) + samps_props = ( + samps_props + if n_checks_per_block is None + else samps_props[:n_checks_per_block] + ) + for sample_i, property_i in samps_props: + # Get the component vectors, each of length (2 \lambda + 1) + try: + unr_comp_vect = ( + unr_block.values[sample_i, ..., property_i].detach().numpy() + ) + rot_comp_vect = ( + rot_block.values[sample_i, ..., property_i].detach().numpy() + ) + except AttributeError: + unr_comp_vect = unr_block.values[sample_i, ..., property_i] + rot_comp_vect = rot_block.values[sample_i, ..., property_i] + + # Rotate the unrotated components vector with a wigner D-matrix + unr_comp_vect_rot = wigner_d_matrices.rotate(unr_comp_vect) + + # Check for exact (within a strict tolerance) equivalence + if not np.allclose(unr_comp_vect_rot, rot_comp_vect, rtol=rtol, atol=atol): + print( + f"block {key}, sample {unr_block.samples[sample_i]}," + + f" property {unr_block.properties[property_i]}, vectors" + + f" not equivariant: {unr_comp_vect_rot}, {rot_comp_vect}" + ) + equivariant = False + return equivariant + + +# ===== CleschGordanReal class + + +class ClebschGordanReal: + def __init__(self, l_max): + self._l_max = l_max + self._cg = {} + + # real-to-complex and complex-to-real transformations as matrices + r2c = {} + c2r = {} + for L in range(0, self._l_max + 1): + r2c[L] = _real2complex(L) + c2r[L] = np.conjugate(r2c[L]).T + + for l1 in range(self._l_max + 1): + for l2 in range(self._l_max + 1): + for L in range( + max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1 + ): + complex_cg = _complex_clebsch_gordan_matrix(l1, l2, L) + + real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( + complex_cg.shape + ) + + real_cg = real_cg.swapaxes(0, 1) + real_cg = (r2c[l2].T @ real_cg.reshape(2 * l2 + 1, -1)).reshape( + real_cg.shape + ) + real_cg = real_cg.swapaxes(0, 1) + + real_cg = real_cg @ c2r[L].T + + if (l1 + l2 + L) % 2 == 0: + rcg = np.real(real_cg) + else: + rcg = np.imag(real_cg) + + new_cg = [] + for M in range(2 * L + 1): + cg_nonzero = np.where(np.abs(rcg[:, :, M]) > 1e-15) + cg_M = np.zeros( + len(cg_nonzero[0]), + dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], + ) + cg_M["m1"] = cg_nonzero[0] + cg_M["m2"] = cg_nonzero[1] + cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], M] + new_cg.append(cg_M) + + self._cg[(l1, l2, L)] = new_cg + + def combine(self, rho1, rho2, L): + # automatically infer l1 and l2 from the size of the coefficients vectors + l1 = (rho1.shape[1] - 1) // 2 + l2 = (rho2.shape[1] - 1) // 2 + if L > self._l_max or l1 > self._l_max or l2 > self._l_max: + raise ValueError("Requested CG entry has not been precomputed") + + n_items = rho1.shape[0] + n_features = rho1.shape[2] + if rho1.shape[0] != rho2.shape[0] or rho1.shape[2] != rho2.shape[2]: + raise IndexError("Cannot combine differently-shaped feature blocks") + + rho = np.zeros((n_items, 2 * L + 1, n_features)) + if (l1, l2, L) in self._cg: + for M in range(2 * L + 1): + for m1, m2, cg in self._cg[(l1, l2, L)][M]: + rho[:, M] += rho1[:, m1, :] * rho2[:, m2, :] * cg + + return rho + + def combine_einsum(self, rho1, rho2, L, combination_string): + # automatically infer l1 and l2 from the size of the coefficients vectors + l1 = (rho1.shape[1] - 1) // 2 + l2 = (rho2.shape[1] - 1) // 2 + if L > self._l_max or l1 > self._l_max or l2 > self._l_max: + raise ValueError( + "Requested CG entry ", (l1, l2, L), " has not been precomputed" + ) + + n_items = rho1.shape[0] + if rho1.shape[0] != rho2.shape[0]: + raise IndexError( + "Cannot combine feature blocks with different number of items" + ) + + # infers the shape of the output using the einsum internals + features = np.einsum(combination_string, rho1[:, 0, ...], rho2[:, 0, ...]).shape + rho = np.zeros((n_items, 2 * L + 1) + features[1:]) + + if (l1, l2, L) in self._cg: + for M in range(2 * L + 1): + for m1, m2, cg in self._cg[(l1, l2, L)][M]: + rho[:, M, ...] += np.einsum( + combination_string, rho1[:, m1, ...], rho2[:, m2, ...] * cg + ) + + return rho + + def couple(self, decoupled, iterate=0): + """ + Goes from an uncoupled product basis to a coupled basis. A + (2l1+1)x(2l2+1) matrix transforming like the outer product of Y^m1_l1 + Y^m2_l2 can be rewritten as a list of coupled vectors, each transforming + like a Y^L irrep. + + The process can be iterated: a D dimensional array that is the product + of D Y^m_l can be turned into a set of multiple terms transforming as a + single Y^M_L. + + decoupled: array or dict + (...)x(2l1+1)x(2l2+1) array containing coefficients that transform + like products of Y^l1 and Y^l2 harmonics. can also be called on a + array of higher dimensionality, in which case the result will + contain matrices of entries. If the further index also correspond to + spherical harmonics, the process can be iterated, and couple() can + be called onto its output, in which case the decoupling is applied + to each entry. + + iterate: int + calls couple iteratively the given number of times. equivalent to + couple(couple(... couple(decoupled))) + + Returns: + -------- + A dictionary tracking the nature of the coupled objects. When called one + time, it returns a dictionary containing (l1, l2) [the coefficients of + the parent Ylm] which in turns is a dictionary of coupled terms, in the + form L:(...)x(2L+1)x(...) array. When called multiple times, it applies + the coupling to each term, and keeps track of the additional l terms, so + that e.g. when called with iterate=1 the return dictionary contains + terms of the form (l3,l4,l1,l2) : { L: array } + + + Note that this coupling scheme is different from the NICE-coupling where + angular momenta are coupled from left to right as (((l1 l2) l3) l4)... ) + Thus results may differ when combining more than two angular channels. + """ + + coupled = {} + + # when called on a matrix, turns it into a dict form to which we can + # apply the generic algorithm + if not isinstance(decoupled, dict): + l2 = (decoupled.shape[-1] - 1) // 2 + decoupled = {(): {l2: decoupled}} + + # runs over the tuple of (partly) decoupled terms + for ltuple, lcomponents in decoupled.items(): + # each is a list of L terms + for lc in lcomponents.keys(): + # this is the actual matrix-valued coupled term, + # of shape (..., 2l1+1, 2l2+1), transforming as Y^m1_l1 Y^m2_l2 + dec_term = lcomponents[lc] + l1 = (dec_term.shape[-2] - 1) // 2 + l2 = (dec_term.shape[-1] - 1) // 2 + + # there is a certain redundance: the L value is also the last entry + # in ltuple + if lc != l2: + raise ValueError( + "Inconsistent shape for coupled angular momentum block." + ) + + # in the new coupled term, prepend (l1,l2) to the existing label + coupled[(l1, l2) + ltuple] = {} + for L in range( + max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1 + ): + Lterm = np.zeros(shape=dec_term.shape[:-2] + (2 * L + 1,)) + for M in range(2 * L + 1): + for m1, m2, cg in self._cg[(l1, l2, L)][M]: + Lterm[..., M] += dec_term[..., m1, m2] * cg + coupled[(l1, l2) + ltuple][L] = Lterm + + # repeat if required + if iterate > 0: + coupled = self.couple(coupled, iterate - 1) + return coupled + + def decouple(self, coupled, iterate=0): + """ + Undoes the transformation enacted by couple. + """ + + decoupled = {} + # applies the decoupling to each entry in the dictionary + for ltuple, lcomponents in coupled.items(): + # the initial pair in the key indicates the decoupled terms that generated + # the L entries + l1, l2 = ltuple[:2] + + # shape of the coupled matrix (last entry is the 2L+1 M terms) + shape = next(iter(lcomponents.values())).shape[:-1] + + dec_term = np.zeros( + shape + + ( + 2 * l1 + 1, + 2 * l2 + 1, + ) + ) + for L in range(max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1): + # supports missing L components, e.g. if they are zero because of symmetry + if not L in lcomponents: + continue + for M in range(2 * L + 1): + for m1, m2, cg in self._cg[(l1, l2, L)][M]: + dec_term[..., m1, m2] += cg * lcomponents[L][..., M] + # stores the result with a key that drops the l's we have just decoupled + if not ltuple[2:] in decoupled: + decoupled[ltuple[2:]] = {} + decoupled[ltuple[2:]][l2] = dec_term + + # rinse, repeat + if iterate > 0: + decoupled = self.decouple(decoupled, iterate - 1) + + # if we got a fully decoupled state, just return an array + if ltuple[2:] == (): + decoupled = next(iter(decoupled[()].values())) + return decoupled + + +# ===== helper functions for ClebschGordanReal + + +def _real2complex(L): + """ + Computes a matrix that can be used to convert from real to complex-valued + spherical harmonics(coefficients) of order L. + + It's meant to be applied to the left, ``real2complex @ [-L..L]``. + """ + result = np.zeros((2 * L + 1, 2 * L + 1), dtype=np.complex128) + + I_SQRT_2 = 1.0 / np.sqrt(2) + + for m in range(-L, L + 1): + if m < 0: + result[L - m, L + m] = I_SQRT_2 * 1j * (-1) ** m + result[L + m, L + m] = -I_SQRT_2 * 1j + + if m == 0: + result[L, L] = 1.0 + + if m > 0: + result[L + m, L + m] = I_SQRT_2 * (-1) ** m + result[L - m, L + m] = I_SQRT_2 + + return result + + +I_SQRT_2 = 1.0 / np.sqrt(2) +SQRT_2 = np.sqrt(2) + + +def _r2c(sp): + """Real to complex SPH. Assumes a block with 2l+1 reals corresponding + to real SPH with m indices from -l to +l""" + + l = (len(sp) - 1) // 2 # infers l from the vector size + rc = np.zeros(len(sp), dtype=np.complex128) + rc[l] = sp[l] + for m in range(1, l + 1): + rc[l + m] = (sp[l + m] + 1j * sp[l - m]) * I_SQRT_2 * (-1) ** m + rc[l - m] = (sp[l + m] - 1j * sp[l - m]) * I_SQRT_2 + return rc + + +def _complex_clebsch_gordan_matrix(l1, l2, L): + if np.abs(l1 - l2) > L or np.abs(l1 + l2) < L: + return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * L + 1), dtype=np.double) + else: + return wigners.clebsch_gordan_array(l1, l2, L) + + +# ======== Fxns used to perform CG iterations + + +def acdc_standardize_keys(descriptor): + """ + Standardize the naming scheme of density expansion coefficient blocks + (nu=1) + """ + + key_names = descriptor.keys.names + if not "spherical_harmonics_l" in key_names: + raise ValueError( + "Descriptor missing spherical harmonics channel key `spherical_harmonics_l`" + ) + blocks = [] + keys = [] + for key, block in descriptor.items(): + key = tuple(key) + if not "inversion_sigma" in key_names: + key = (1,) + key + if not "order_nu" in key_names: + key = (1,) + key + keys.append(key) + property_names = _remove_suffix(block.properties.names, "_1") + blocks.append( + TensorBlock( + values=block.values, + samples=block.samples, + components=block.components, + properties=Labels(property_names, block.properties.values), + ) + ) + + if not "inversion_sigma" in key_names: + key_names = ["inversion_sigma"] + key_names + if not "order_nu" in key_names: + key_names = ["order_nu"] + key_names + + return TensorMap( + keys=Labels(names=key_names, values=np.asarray(keys, dtype=np.int32)), + blocks=blocks, + ) + + +def cg_combine( + x_a, + x_b, + feature_names=None, + clebsch_gordan=None, + lcut=None, + other_keys_match=None, +): + """ + Performs a CG product of two sets of equivariants. Only requirement is that + sparse indices are labeled as ("inversion_sigma", "spherical_harmonics_l", + "order_nu"). The automatically-determined naming of output features can be + overridden by giving a list of "feature_names". By defaults, all other key + labels are combined in an "outer product" mode, i.e. if there is a key-side + neighbor_species in both x_a and x_b, the returned keys will have two + neighbor_species labels, corresponding to the parent features. By providing + a list `other_keys_match` of keys that should match, these are not + outer-producted, but combined together. for instance, passing `["species + center"]` means that the keys with the same species center will be combined + together, but yield a single key with the same species_center in the + results. + """ + + # determines the cutoff in the new features + lmax_a = max(x_a.keys["spherical_harmonics_l"]) + lmax_b = max(x_b.keys["spherical_harmonics_l"]) + if lcut is None: + lcut = lmax_a + lmax_b + + # creates a CG object, if needed + if clebsch_gordan is None: + clebsch_gordan = ClebschGordanReal(lcut) + + other_keys_a = tuple( + name + for name in x_a.keys.names + if name not in ["spherical_harmonics_l", "order_nu", "inversion_sigma"] + ) + other_keys_b = tuple( + name + for name in x_b.keys.names + if name not in ["spherical_harmonics_l", "order_nu", "inversion_sigma"] + ) + + if other_keys_match is None: + OTHER_KEYS = [k + "_a" for k in other_keys_a] + [k + "_b" for k in other_keys_b] + else: + OTHER_KEYS = ( + other_keys_match + + [ + k + ("_a" if k in other_keys_b else "") + for k in other_keys_a + if k not in other_keys_match + ] + + [ + k + ("_b" if k in other_keys_a else "") + for k in other_keys_b + if k not in other_keys_match + ] + ) + + # we assume grad components are all the same + if x_a.block(0).has_gradient("positions"): + grad_components = x_a.block(0).gradient("positions").components + else: + grad_components = None + + # automatic generation of the output features names + # "x1 x2 x3 ; x1 x2 -> x1_a x2_a x3_a k_nu x1_b x2_b l_nu" + if feature_names is None: + NU = x_a.keys[0]["order_nu"] + x_b.keys[0]["order_nu"] + feature_names = ( + tuple(n + "_a" for n in x_a.block(0).properties.names) + + ("k_" + str(NU),) + + tuple(n + "_b" for n in x_b.block(0).properties.names) + + ("l_" + str(NU),) + ) + + X_idx = {} + X_blocks = {} + X_samples = {} + X_grad_samples = {} + X_grads = {} + + # loops over sparse blocks of x_a + for index_a, block_a in x_a.items(): + lam_a = index_a["spherical_harmonics_l"] + sigma_a = index_a["inversion_sigma"] + order_a = index_a["order_nu"] + properties_a = ( + block_a.properties + ) # pre-extract this block as accessing a c property has a non-zero cost + samples_a = block_a.samples + + # and x_b + for index_b, block_b in x_b.items(): + lam_b = index_b["spherical_harmonics_l"] + sigma_b = index_b["inversion_sigma"] + order_b = index_b["order_nu"] + properties_b = block_b.properties + samples_b = block_b.samples + + if other_keys_match is None: + OTHERS = tuple(index_a[name] for name in other_keys_a) + tuple( + index_b[name] for name in other_keys_b + ) + else: + OTHERS = tuple( + index_a[k] for k in other_keys_match if index_a[k] == index_b[k] + ) + # skip combinations without matching key + if len(OTHERS) < len(other_keys_match): + continue + # adds non-matching keys to build outer product + OTHERS = OTHERS + tuple( + index_a[k] for k in other_keys_a if k not in other_keys_match + ) + OTHERS = OTHERS + tuple( + index_b[k] for k in other_keys_b if k not in other_keys_match + ) + + if "neighbor" in samples_b.names and "neighbor" not in samples_a.names: + # we hard-code a combination method where b can be a pair descriptor. this needs some work to be general and robust + # note also that this assumes that structure, center are ordered in the same way in the centred and neighbor descriptors + neighbor_slice = [] + smp_a, smp_b = 0, 0 + while smp_b < samples_b.shape[0]: + if samples_b[smp_b][["structure", "center"]] != samples_a[smp_a]: + smp_a += 1 + neighbor_slice.append(smp_a) + smp_b += 1 + neighbor_slice = np.asarray(neighbor_slice) + else: + neighbor_slice = slice(None) + + # determines the properties that are in the select list + sel_feats = [] + sel_idx = [] + sel_feats = ( + np.indices((len(properties_a), len(properties_b))).reshape(2, -1).T + ) + + prop_ids_a = [] + prop_ids_b = [] + for n_a, f_a in enumerate(properties_a): + prop_ids_a.append(tuple(f_a) + (lam_a,)) + for n_b, f_b in enumerate(properties_b): + prop_ids_b.append(tuple(f_b) + (lam_b,)) + prop_ids_a = np.asarray(prop_ids_a) + prop_ids_b = np.asarray(prop_ids_b) + sel_idx = np.hstack( + [prop_ids_a[sel_feats[:, 0]], prop_ids_b[sel_feats[:, 1]]] + ) + if len(sel_feats) == 0: + continue + # loops over all permissible output blocks. note that blocks will + # be filled from different la, lb + for L in range(np.abs(lam_a - lam_b), 1 + min(lam_a + lam_b, lcut)): + # determines parity of the block + S = sigma_a * sigma_b * (-1) ** (lam_a + lam_b + L) + NU = order_a + order_b + KEY = ( + NU, + S, + L, + ) + OTHERS + if not KEY in X_idx: + X_idx[KEY] = [] + X_blocks[KEY] = [] + X_samples[KEY] = block_b.samples + if grad_components is not None: + X_grads[KEY] = [] + X_grad_samples[KEY] = block_b.gradient("positions").samples + + # builds all products in one go + one_shot_blocks = clebsch_gordan.combine_einsum( + block_a.values[neighbor_slice][:, :, sel_feats[:, 0]], + block_b.values[:, :, sel_feats[:, 1]], + L, + combination_string="iq,iq->iq", + ) + # do gradients, if they are present... + if grad_components is not None: + grad_a = block_a.gradient("positions") + grad_b = block_b.gradient("positions") + grad_a_data = np.swapaxes(grad_a.data, 1, 2) + grad_b_data = np.swapaxes(grad_b.data, 1, 2) + one_shot_grads = clebsch_gordan.combine_einsum( + block_a.values[grad_a.samples["sample"]][ + neighbor_slice, :, sel_feats[:, 0] + ], + grad_b_data[..., sel_feats[:, 1]], + L=L, + combination_string="iq,iaq->iaq", + ) + clebsch_gordan.combine_einsum( + block_b.values[grad_b.samples["sample"]][:, :, sel_feats[:, 1]], + grad_a_data[neighbor_slice, ..., sel_feats[:, 0]], + L=L, + combination_string="iq,iaq->iaq", + ) + + # now loop over the selected features to build the blocks + + X_idx[KEY].append(sel_idx) + X_blocks[KEY].append(one_shot_blocks) + if grad_components is not None: + X_grads[KEY].append(one_shot_grads) + + # turns data into sparse storage format (and dumps any empty block in the + # process) + nz_idx = [] + nz_blk = [] + for KEY in X_blocks: + L = KEY[2] + # create blocks + if len(X_blocks[KEY]) == 0: + continue # skips empty blocks + nz_idx.append(KEY) + block_data = np.concatenate(X_blocks[KEY], axis=-1) + sph_components = Labels( + ["spherical_harmonics_m"], + np.asarray(range(-L, L + 1), dtype=np.int32).reshape(-1, 1), + ) + newblock = TensorBlock( + # feature index must be last + values=block_data, + samples=X_samples[KEY], + components=[sph_components], + properties=Labels( + feature_names, np.asarray(np.vstack(X_idx[KEY]), dtype=np.int32) + ), + ) + if grad_components is not None: + grad_data = np.swapaxes(np.concatenate(X_grads[KEY], axis=-1), 2, 1) + newblock.add_gradient( + "positions", + data=grad_data, + samples=X_grad_samples[KEY], + components=[grad_components[0], sph_components], + ) + nz_blk.append(newblock) + X = TensorMap( + Labels( + ["order_nu", "inversion_sigma", "spherical_harmonics_l"] + OTHER_KEYS, + np.asarray(nz_idx, dtype=np.int32), + ), + nz_blk, + ) + return X + + +def cg_increment( + x_nu, + x_1, + clebsch_gordan=None, + lcut=None, + other_keys_match=None, +): + """Specialized version of the CG product to perform iterations with nu=1 features""" + + nu = x_nu.keys["order_nu"][0] + + feature_roots = _remove_suffix(x_1.block(0).properties.names) + + if nu == 1: + feature_names = ( + [root + "_1" for root in feature_roots] + + ["l_1"] + + [root + "_2" for root in feature_roots] + + ["l_2"] + ) + else: + feature_names = ( + [x_nu.block(0).properties.names] + + ["k_" + str(nu + 1)] + + [root + "_" + str(nu + 1) for root in feature_roots] + + ["l_" + str(nu + 1)] + ) + + return cg_combine( + x_nu, + x_1, + feature_names=feature_names, + clebsch_gordan=clebsch_gordan, + lcut=lcut, + other_keys_match=other_keys_match, + ) + + +def _remove_suffix(names, new_suffix=""): + suffix = re.compile("_[0-9]?$") + rname = [] + for name in names: + match = suffix.search(name) + if match is None: + rname.append(name + new_suffix) + else: + rname.append(name[: match.start()] + new_suffix) + return rname + + + +def lambda_soap_vector( + frames: list, + rascal_hypers: dict, + lambda_cut: Optional[int] = None, + selected_samples: Optional[Labels] = None, + neighbor_species: Optional[Sequence[int]] = None, + even_parity_only: bool = False, +) -> TensorMap: + """ + Takes a list of frames of ASE loaded frames and a dict of Rascaline + hyperparameters and generates a lambda-SOAP (i.e. nu=2) representation. + + Passing a subset of samples in `selected_samples` can be used to, for + instance, only calculate the features for a subset of the strutcures passed + in `frames`. For instance: `selected_samples = Labels(names=["structure"], + values[4, 5, 6])` will only calculate the lambda-features for structures + indexed by 4, 5, 6. + + :param frames: a list of structures generated by the ase.io function. + :param rascal_hypers: a dict of hyperparameters used to calculate the atom + density correlation calculated with rascaline SphericalExpansion + :param lambda_cut: an int of the maximum lambda value to compute + combinations for. If none, the 'max_angular' value in `rascal_hypers` + will be used instead. + :param selected_samples: a Labels object that defines which samples, as a + subset of the total samples in `frames` (i.e. atomic environments or + structures) to perform the calculation on. + :param neighbor_species: a list of int that correspond to the atomic charges + of all the neighbour species that you want to be in your properties (or + features) dimension. This list may contain charges for atoms that don't + appear in ``frames``, but are included anyway so that the one can + enforce consistent properties dimension size with other lambda-feature + vectors. + :param even_parity_only: a bool that determines whether to only include the + key/block pairs with even parity under rotation, i.e. sigma = +1. + Defaults to false, where both parities are included. + :param save_dir: a str of the absolute path to the directory where the + TensorMap of the calculated lambda-SOAP representation and pickled + ``rascal_hypers`` dict should be written. If none, the TensorMap will not be + saved. + + :return: a TensorMap of the lambda-SOAP feature vector for the selected + samples of the input frames. + """ + # Generate Rascaline spherical expansion + calculator = rascaline.SphericalExpansion(**rascal_hypers) + if lambda_cut is None: + lambda_cut = 2 * rascal_hypers["max_angular"] + else: + if lambda_cut > 2 * rascal_hypers["max_angular"]: + raise ValueError( + "As this function generates 2-body features (nu=2), " + "`lambda_cut` must be <= 2 x rascal_hypers['max_angular'] " + f"`rascal_hypers`. Received {lambda_cut}." + ) + # Pre-calculate ClebschGordan coefficients + cg = ClebschGordanReal(l_max=lambda_cut) + + # Generate descriptor via Spherical Expansion + nu1 = calculator.compute(frames, selected_samples=selected_samples) + + # nu=1 features + nu1 = acdc_standardize_keys(nu1) + + # Move "species_neighbor" sparse keys to properties with enforced atom + # charges if ``neighbor_species`` is specified. This is required as the CG + # iteration code currently does not handle neighbour species padding + # automatically. + keys_to_move = "species_neighbor" + if neighbor_species is not None: + keys_to_move = Labels( + names=(keys_to_move,), + values=np.array(neighbor_species).reshape(-1, 1), + ) + nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move) + + # Combined nu=1 features to generate nu=2 features. lambda-SOAP is defined + # as just the nu=2 features. + lsoap = cg_increment( + nu1, + nu1, + clebsch_gordan=cg, + lcut=lambda_cut, + other_keys_match=["species_center"], + ) + + # Clean the lambda-SOAP TensorMap. Drop the order_nu key name as this is by + # definition 2 for all keys. + lsoap = equistore.remove_dimension(lsoap, axis="keys", name="order_nu") + + # Drop all odd parity keys/blocks + if even_parity_only: + keys_to_drop = Labels( + names=lsoap.keys.names, + values=lsoap.keys.values[lsoap.keys.column("inversion_sigma") == -1], + ) + lsoap = equistore.drop_blocks(lsoap, keys=keys_to_drop) + + # Drop the inversion_sigma key name as this is now +1 for all blocks + lsoap = equistore.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + + return lsoap \ No newline at end of file From dd65be2ed54b7099d9fac79d64276c297d124859 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 31 Jul 2023 18:11:44 +0200 Subject: [PATCH 32/96] Test for equivariance --- python/rascaline/rascaline/utils/tmp.ipynb | 151 +++++++++++++++------ 1 file changed, 110 insertions(+), 41 deletions(-) diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index fc45b06c3..2ed7fe778 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -17,14 +17,8 @@ "from equistore import Labels, TensorBlock, TensorMap\n", "\n", "# from rascaline.utils import clebsch_gordan\n", - "import clebsch_gordan" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Test system 1" + "import clebsch_gordan\n", + "import spherical" ] }, { @@ -47,22 +41,122 @@ "}\n", "\n", "# Define target lambda channels\n", - "lambdas = np.array([0, 2])" + "lambdas = np.array([0, 1, 2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Equivariance Test" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "22.5 s ± 925 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "Random rotation angles (rad): 2.772363661827398 1.8039589759258468 1.2427261014900488\n" ] } ], + "source": [ + "# Pick a test frame\n", + "frame = frames[0]\n", + "\n", + "# Randomly rigidly rotate the frame\n", + "frame_rot, (a, b, c) = spherical.rotate_ase_frame(frame)\n", + "print(\"Random rotation angles (rad):\", a, b, c)\n", + "\n", + "# Generate lambda-SOAP for both frames\n", + "lsoap = clebsch_gordan.n_body_iteration_single_center(\n", + " [frame],\n", + " rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", + ")\n", + "\n", + "lsoap_rot = clebsch_gordan.n_body_iteration_single_center(\n", + " [frame_rot],\n", + " rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", + ")\n", + "\n", + "# Build a Wigner-D Matrix from the random rotation angles\n", + "wig = spherical.WignerDReal(rascal_hypers[\"max_angular\"], a, b, c)\n", + "\n", + "# Rotate the lambda-SOAP descriptor of the unrotated frame\n", + "lsoap_unrot_rot = wig.rotate_tensormap(lsoap)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "assert equistore.equal_metadata(lsoap_unrot_rot, lsoap_rot)\n", + "assert equistore.allclose(lsoap_unrot_rot, lsoap_rot)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Normalization Tests" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lsoap_new0 = clebsch_gordan.n_body_iteration_single_center(\n", + " frames,\n", + " rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", + ")\n", + "lsoap_new0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lsoap_old0 = spherical.lambda_soap_vector(\n", + " frames,\n", + " rascal_hypers,\n", + ")\n", + "# Drop all odd parity keys/blocks\n", + "keys_to_drop = Labels(\n", + " names=lsoap_old0.keys.names,\n", + " values=lsoap_old0.keys.values[[v not in lambdas for v in lsoap_old0.keys.column(\"spherical_harmonics_l\")]],\n", + ")\n", + "lsoap_old0 = equistore.drop_blocks(lsoap_old0, keys=keys_to_drop)\n", + "lsoap_old0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test system 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "%%timeit\n", "\n", @@ -82,17 +176,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "12.9 s ± 1.92 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "\n", @@ -112,7 +198,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -144,26 +230,9 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TensorMap with 8 blocks\n", - "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", - " 3 1 0 1\n", - " 3 -1 2 1\n", - " ...\n", - " 3 1 2 8\n", - " 3 -1 0 8" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "frames = [ase.io.read(\"frame.xyz\")]\n", "\n", From ede6e4c86a5ea9aa2dc8201a591002f138122983 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 31 Jul 2023 19:39:32 +0200 Subject: [PATCH 33/96] Change correction factor to sqrt of multiplicity. Easier testing! --- python/rascaline/rascaline/utils/clebsch_gordan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index 2159d8c40..e9baae78d 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -385,7 +385,7 @@ def _combine_single_center( combined_key["spherical_harmonics_l"], cg_cache, use_sparse, - correction_factor=multi, + correction_factor=np.sqrt(multi), ) ) From 61b1b5fdb5c22ea2518a1d03093e9b879ad692f1 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 31 Jul 2023 20:25:23 +0200 Subject: [PATCH 34/96] Fxns to invert tensormap --- python/rascaline/rascaline/utils/spherical.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/python/rascaline/rascaline/utils/spherical.py b/python/rascaline/rascaline/utils/spherical.py index b195fa30e..37c097240 100644 --- a/python/rascaline/rascaline/utils/spherical.py +++ b/python/rascaline/rascaline/utils/spherical.py @@ -242,6 +242,43 @@ def rotate_ase_frame(frame) -> Tuple[ase.Atoms, Tuple[float, float, float]]: return rotated_frame, (alpha, beta, gamma) +def invert_ase_frame(frame) -> ase.Atoms: + """ + Make a copy of the input ``frame``. Randomly rotates its xyz and cell + coordinates and returns the new frame, and euler angles alpha, beta, and + gamma. + """ + # Copy the frame + inverted_frame = frame.copy() + # Invert the positions and cell + inverted_frame.positions = -1 * inverted_frame.positions @ R.T + inverted_frame.cell = -1 * inverted_frame.cell @ R.T + + return inverted_frame + + +def invert_tensormap(tensor: TensorMap) -> TensorMap: + """ + Takes a TensorMap in the spherical basis and perform a parity inversion. + This amounts to applying a factor of (-1) to all odd parity blocks, i.e. + those with key values of "order_nu" equal to -1. + """ + new_blocks = [] + for key, block in tensor.items(): + if key["inversion_sigma"] == -1: + new_block = TensorBlock( + values=block.values * 1, + samples=block.samples, + components=block.components, + properties=block.properties, + ) + else: + new_block = block.copy() + new_blocks.append(new_block) + + return TensorMap(tensor.keys, new_blocks) + + def _wigner_d(l, alpha, beta, gamma): """Computes a Wigner D matrix D^l_{mm'}(alpha, beta, gamma) @@ -995,7 +1032,6 @@ def _remove_suffix(names, new_suffix=""): return rname - def lambda_soap_vector( frames: list, rascal_hypers: dict, @@ -1097,4 +1133,4 @@ def lambda_soap_vector( # Drop the inversion_sigma key name as this is now +1 for all blocks lsoap = equistore.remove_dimension(lsoap, axis="keys", name="inversion_sigma") - return lsoap \ No newline at end of file + return lsoap From e669d519406aef81ec4257de21e43ae686d03cb3 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 31 Jul 2023 20:25:41 +0200 Subject: [PATCH 35/96] Attempt at O(3) equivariance test --- python/rascaline/rascaline/utils/tmp.ipynb | 349 ++++++++++++++++++++- 1 file changed, 335 insertions(+), 14 deletions(-) diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index 2ed7fe778..6fb9dbaea 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -2,9 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -15,6 +24,7 @@ "import rascaline\n", "import equistore\n", "from equistore import Labels, TensorBlock, TensorMap\n", + "import chemiscope\n", "\n", "# from rascaline.utils import clebsch_gordan\n", "import clebsch_gordan\n", @@ -23,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -44,23 +54,57 @@ "lambdas = np.array([0, 1, 2])" ] }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniforge3/envs/dev/lib/python3.10/site-packages/chemiscope/input.py:195: UserWarning: chemiscope behavior changed to no longer include properties from the structure objects. Use `chemiscope.extract_properties` to also visualize these properties ([efg_L2, efg, efg_L0])\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fc16544574e848cc91ff5119d343d6a8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "StructureWidget(value='{\"meta\": {\"name\": \" \"}, \"structures\": [{\"size\": 42, \"names\": [\"Li\", \"Li\", \"Li\", \"Li\", \"…" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chemiscope.show([frames[0], frame_inv], mode=\"structure\")" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Equivariance Test" + "# Equivariance Test - SO(3)" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Random rotation angles (rad): 2.772363661827398 1.8039589759258468 1.2427261014900488\n" + "Random rotation angles (rad): 2.20723878026074 2.7663510356084258 2.130637286978451\n", + "SO(3)EQUIVARIANT!\n" ] } ], @@ -71,6 +115,7 @@ "# Randomly rigidly rotate the frame\n", "frame_rot, (a, b, c) = spherical.rotate_ase_frame(frame)\n", "print(\"Random rotation angles (rad):\", a, b, c)\n", + "assert not np.all(frame.positions == frame_rot.positions)\n", "\n", "# Generate lambda-SOAP for both frames\n", "lsoap = clebsch_gordan.n_body_iteration_single_center(\n", @@ -91,17 +136,257 @@ "wig = spherical.WignerDReal(rascal_hypers[\"max_angular\"], a, b, c)\n", "\n", "# Rotate the lambda-SOAP descriptor of the unrotated frame\n", - "lsoap_unrot_rot = wig.rotate_tensormap(lsoap)" + "lsoap_unrot_rot = wig.rotate_tensormap(lsoap)\n", + "\n", + "# Check for equivariance!\n", + "assert equistore.equal_metadata(lsoap_unrot_rot, lsoap_rot)\n", + "assert equistore.allclose(lsoap_unrot_rot, lsoap_rot)\n", + "print(\"SO(3) EQUIVARIANT!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Equivariance Test - O(3)" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=3) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=3) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=3) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=3) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=3) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=8) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=8) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=8) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=8) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=8) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=22) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=22) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=22) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=22) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22) False\n" + ] + } + ], + "source": [ + "# Pick a test frame\n", + "frame = frames[0]\n", + "\n", + "# Invert the positions\n", + "frame_inv = frames[0].copy()\n", + "frame_inv.positions = -1 * frame_inv.positions\n", + "assert not np.all(frame.positions == frame_inv.positions)\n", + "\n", + "# Generate lambda-SOAP for both frames\n", + "lsoap = clebsch_gordan.n_body_iteration_single_center(\n", + " [frame],\n", + " rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", + ")\n", + "\n", + "lsoap_inv = clebsch_gordan.n_body_iteration_single_center(\n", + " [frame_inv],\n", + " rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", + ")\n", + "\n", + "# Invert the TensorMaps\n", + "lsoap_uninv_inv = spherical.invert_tensormap(lsoap)\n", + "\n", + "# Check for equivariance!\n", + "assert equistore.equal_metadata(lsoap_uninv_inv, lsoap_inv)\n", + "for key in lsoap_inv.keys:\n", + " close = equistore.allclose_block(lsoap_uninv_inv[key], lsoap_inv[key])\n", + " print(key, close)\n", + " # if not close:\n", + " # break\n", + "# equistore.allclose_raise(lsoap_uninv_inv, lsoap_inv)\n", + "# print(\"INVERSION EQUIVARIANT!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0., 0., 0., 0., 0.])" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lsoap_uninv_inv.block(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22).values[0, ..., 0]" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", + " 1.74112929e-08, -2.46996381e-08, -4.00459763e-08],\n", + " [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", + " -5.43871155e-10, 7.70527867e-10, 1.25186725e-09],\n", + " [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", + " -2.09929680e-09, 2.97451559e-09, 4.83177337e-09],\n", + " [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", + " -4.21231684e-08, 5.97620466e-08, 9.68773874e-08],\n", + " [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", + " 1.05008555e-09, -1.48888188e-09, -2.41593216e-09]],\n", + "\n", + " [[ 5.77641685e-09, -1.10520104e-08, 3.05784221e-08, ...,\n", + " 3.31438002e-06, -4.69593733e-06, -7.62865268e-06],\n", + " [ 3.52113221e-11, -6.64820657e-11, 1.88645904e-10, ...,\n", + " -1.00210410e-07, 1.41982231e-07, 2.30651638e-07],\n", + " [ 8.62442322e-11, -1.62836678e-10, 4.62056526e-10, ...,\n", + " 1.00771627e-08, -1.42656815e-08, -2.32029603e-08],\n", + " [ 4.08445606e-09, -7.81478413e-09, 2.16217466e-08, ...,\n", + " -3.06243227e-06, 4.33897545e-06, 7.04874201e-06],\n", + " [ 4.97931307e-11, -9.40137997e-11, 2.66768459e-10, ...,\n", + " 3.03024493e-08, -4.29163996e-08, -6.97570326e-08]],\n", + "\n", + " [[-2.89809123e-09, 5.54467175e-09, -1.53421415e-08, ...,\n", + " -1.61405131e-06, 2.28690439e-06, 3.71499190e-06],\n", + " [ 3.54809005e-09, -6.78830461e-09, 1.87830411e-08, ...,\n", + " -2.70760855e-06, 3.83627574e-06, 6.23202632e-06],\n", + " [ 2.21766379e-12, -4.18714896e-12, 1.18812009e-11, ...,\n", + " -1.51589225e-08, 2.14453501e-08, 3.49169128e-08],\n", + " [-2.04953663e-09, 3.92120421e-09, -1.08499971e-08, ...,\n", + " 1.41054804e-06, -1.99853556e-06, -3.24661975e-06],\n", + " [ 5.01708159e-09, -9.59882032e-09, 2.65596549e-08, ...,\n", + " 2.88240218e-06, -4.08389902e-06, -6.63437092e-06]],\n", + "\n", + " ...,\n", + "\n", + " [[-1.26014906e-12, 2.41066958e-12, -6.67175035e-12, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", + " [-2.82617530e-13, 5.40648274e-13, -1.49629424e-12, ...,\n", + " -1.38259301e-07, 1.95891472e-07, 3.18228335e-07],\n", + " [-6.92603198e-13, 1.32495229e-12, -3.66692797e-12, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", + " [ 1.78213011e-12, -3.40922101e-12, 9.43533433e-12, ...,\n", + " -5.53732602e-09, 7.84573990e-09, 1.27449485e-08],\n", + " [-3.99616116e-13, 7.64467080e-13, -2.11573290e-12, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]],\n", + "\n", + " [[ 1.37624274e-12, -2.63296916e-12, 7.28586172e-12, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", + " [ 2.37340102e-12, -4.54068977e-12, 1.25648419e-11, ...,\n", + " -2.89341666e-08, 4.09949781e-08, 6.65972135e-08],\n", + " [-9.77191959e-13, 1.86952205e-12, -5.17327765e-12, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", + " [-9.09707010e-13, 1.74041275e-12, -4.81601064e-12, ...,\n", + " -2.59960994e-08, 3.68308839e-08, 5.98359139e-08],\n", + " [-1.25523198e-12, 2.40145645e-12, -6.64522803e-12, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]],\n", + "\n", + " [[ 1.07215234e-13, -2.05099578e-13, 5.67650670e-13, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", + " [-5.66542786e-13, 1.08377966e-12, -2.99955867e-12, ...,\n", + " -4.05338380e-08, 5.74301234e-08, 9.32957217e-08],\n", + " [-2.97565786e-13, 5.69234583e-13, -1.57546096e-12, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", + " [-4.67474364e-13, 8.94264680e-13, -2.47504131e-12, ...,\n", + " 7.52065039e-08, -1.06555678e-07, -1.73101109e-07],\n", + " [ 5.29475372e-13, -1.01287082e-12, 2.80330530e-12, ...,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]]])" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "diff = equistore.subtract(lsoap_uninv_inv, lsoap_inv)\n", + "diff.block(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22).values" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "assert equistore.equal_metadata(lsoap_unrot_rot, lsoap_rot)\n", - "assert equistore.allclose(lsoap_unrot_rot, lsoap_rot)" + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=3) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=3) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=3) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=3) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=3) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=8) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=8) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=8) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=8) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=8) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=22) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=22) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=22) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=22) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22) True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=3) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=3) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=3) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=3) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=3) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=8) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=8) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=8) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=8) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=8) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=22) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=22) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=22) True\n", + "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=22) False\n", + "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22) False" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lsoap_inv.keys[0][\"inversion_sigma\"]" ] }, { @@ -113,28 +398,64 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "TensorMap with 15 blocks\n", + "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", + " 2 1 0 3\n", + " 2 1 1 3\n", + " ...\n", + " 2 1 2 22\n", + " 2 -1 2 22" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "lsoap_new0 = clebsch_gordan.n_body_iteration_single_center(\n", " frames,\n", " rascal_hypers,\n", " nu_target=2,\n", " lambdas=lambdas,\n", + " lambda_cut=5,\n", ")\n", "lsoap_new0" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "TensorMap with 15 blocks\n", + "keys: inversion_sigma spherical_harmonics_l species_center\n", + " 1 0 3\n", + " 1 1 3\n", + " ...\n", + " -1 1 22\n", + " -1 2 22" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "lsoap_old0 = spherical.lambda_soap_vector(\n", " frames,\n", " rascal_hypers,\n", + " lambda_cut=5,\n", ")\n", "# Drop all odd parity keys/blocks\n", "keys_to_drop = Labels(\n", From 3954cfd83507b57b095e28250361ba711df559e1 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 2 Aug 2023 14:40:01 +0200 Subject: [PATCH 36/96] Correctly account for phase convention in O(3) transformation --- python/rascaline/rascaline/utils/spherical.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/python/rascaline/rascaline/utils/spherical.py b/python/rascaline/rascaline/utils/spherical.py index 37c097240..f5b59096b 100644 --- a/python/rascaline/rascaline/utils/spherical.py +++ b/python/rascaline/rascaline/utils/spherical.py @@ -265,15 +265,20 @@ def invert_tensormap(tensor: TensorMap) -> TensorMap: """ new_blocks = [] for key, block in tensor.items(): + multiplier = 1 + if key["inversion_sigma"] == -1: - new_block = TensorBlock( - values=block.values * 1, - samples=block.samples, - components=block.components, - properties=block.properties, - ) - else: - new_block = block.copy() + multiplier *= -1 + + if key["spherical_harmonics_l"] % 2 == 1: + multiplier *= -1 + + new_block = TensorBlock( + values=block.values * multiplier, + samples=block.samples, + components=block.components, + properties=block.properties, + ) new_blocks.append(new_block) return TensorMap(tensor.keys, new_blocks) From 610f41500afb81a34d0c734368a36d6fa45b69ca Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 2 Aug 2023 15:16:14 +0200 Subject: [PATCH 37/96] Notebook updated with equivariance checks --- python/rascaline/rascaline/utils/tmp.ipynb | 385 ++++++--------------- 1 file changed, 105 insertions(+), 280 deletions(-) diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index 6fb9dbaea..9bd9fb4f6 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -2,18 +2,9 @@ "cells": [ { "cell_type": "code", - "execution_count": 19, + "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -33,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -50,65 +41,34 @@ " \"center_atom_weight\": 1.0,\n", "}\n", "\n", - "# Define target lambda channels\n", - "lambdas = np.array([0, 1, 2])" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/opt/miniforge3/envs/dev/lib/python3.10/site-packages/chemiscope/input.py:195: UserWarning: chemiscope behavior changed to no longer include properties from the structure objects. Use `chemiscope.extract_properties` to also visualize these properties ([efg_L2, efg, efg_L0])\n", - " warnings.warn(\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "fc16544574e848cc91ff5119d343d6a8", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "StructureWidget(value='{\"meta\": {\"name\": \" \"}, \"structures\": [{\"size\": 42, \"names\": [\"Li\", \"Li\", \"Li\", \"Li\", \"…" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "chemiscope.show([frames[0], frame_inv], mode=\"structure\")" + "# chemiscope.show(frames, mode=\"structure\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Equivariance Test - SO(3)" + "# Equivariance Test - SO(3) for $\\nu=3$" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Random rotation angles (rad): 2.20723878026074 2.7663510356084258 2.130637286978451\n", - "SO(3)EQUIVARIANT!\n" + "Random rotation angles (rad): 1.4521811579492145 2.2229088215428923 1.1400953735068622\n", + "SO(3) EQUIVARIANT!\n" ] } ], "source": [ + "# Define target lambda channels\n", + "lambdas = np.array([0, 1, 2])\n", + "\n", "# Pick a test frame\n", "frame = frames[0]\n", "\n", @@ -117,18 +77,18 @@ "print(\"Random rotation angles (rad):\", a, b, c)\n", "assert not np.all(frame.positions == frame_rot.positions)\n", "\n", - "# Generate lambda-SOAP for both frames\n", - "lsoap = clebsch_gordan.n_body_iteration_single_center(\n", + "# Generate nu=3 descriptor for both frames\n", + "nu3 = clebsch_gordan.n_body_iteration_single_center(\n", " [frame],\n", " rascal_hypers,\n", - " nu_target=2,\n", + " nu_target=3,\n", " lambdas=lambdas,\n", ")\n", "\n", - "lsoap_rot = clebsch_gordan.n_body_iteration_single_center(\n", + "nu3_rot = clebsch_gordan.n_body_iteration_single_center(\n", " [frame_rot],\n", " rascal_hypers,\n", - " nu_target=2,\n", + " nu_target=3,\n", " lambdas=lambdas,\n", ")\n", "\n", @@ -136,56 +96,50 @@ "wig = spherical.WignerDReal(rascal_hypers[\"max_angular\"], a, b, c)\n", "\n", "# Rotate the lambda-SOAP descriptor of the unrotated frame\n", - "lsoap_unrot_rot = wig.rotate_tensormap(lsoap)\n", + "nu3_unrot_rot = wig.rotate_tensormap(nu3)\n", "\n", "# Check for equivariance!\n", - "assert equistore.equal_metadata(lsoap_unrot_rot, lsoap_rot)\n", - "assert equistore.allclose(lsoap_unrot_rot, lsoap_rot)\n", - "print(\"SO(3) EQUIVARIANT!\")" + "assert equistore.equal_metadata(nu3_unrot_rot, nu3_rot)\n", + "assert equistore.allclose(nu3_unrot_rot, nu3_rot)\n", + "print(\"SO(3) EQUIVARIANT!\")\n", + "\n", + "# chemiscope.show([frames[0], frame_rot], mode=\"structure\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Equivariance Test - O(3)" + "# Equivariance Test - O(3) on $\\nu=2$ (i.e. $\\lambda$-SOAP)" ] }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=3) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=3) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=3) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=3) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=3) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=8) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=8) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=8) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=8) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=8) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=22) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=22) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=22) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=22) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22) False\n" + "Random rotation angles (rad): 1.5925083910906 0.10309657050925311 1.435700350750785\n", + "O(3) EQUIVARIANT!\n" ] } ], "source": [ + "# Define target lambda channels\n", + "lambdas = np.array([0, 1, 2, 3, 4, 5])\n", + "\n", "# Pick a test frame\n", "frame = frames[0]\n", "\n", "# Invert the positions\n", - "frame_inv = frames[0].copy()\n", - "frame_inv.positions = -1 * frame_inv.positions\n", - "assert not np.all(frame.positions == frame_inv.positions)\n", + "frame_o3 = frame.copy()\n", + "frame_o3.positions = -1 * frame_o3.positions\n", + "frame_o3, (a, b, c) = spherical.rotate_ase_frame(frame_o3)\n", + "print(\"Random rotation angles (rad):\", a, b, c)\n", + "assert not np.all(frame.positions == frame_o3.positions)\n", "\n", "# Generate lambda-SOAP for both frames\n", "lsoap = clebsch_gordan.n_body_iteration_single_center(\n", @@ -195,205 +149,78 @@ " lambdas=lambdas,\n", ")\n", "\n", - "lsoap_inv = clebsch_gordan.n_body_iteration_single_center(\n", - " [frame_inv],\n", + "lsoap_o3 = clebsch_gordan.n_body_iteration_single_center(\n", + " [frame_o3],\n", " rascal_hypers,\n", " nu_target=2,\n", " lambdas=lambdas,\n", ")\n", "\n", - "# Invert the TensorMaps\n", - "lsoap_uninv_inv = spherical.invert_tensormap(lsoap)\n", + "# Build a Wigner-D Matrix from the random rotation angles\n", + "wig = spherical.WignerDReal(rascal_hypers[\"max_angular\"], a, b, c)\n", + "\n", + "# Invert the TensorMap\n", + "lsoap_transformed = spherical.invert_tensormap(lsoap)\n", + "lsoap_transformed = wig.rotate_tensormap(lsoap_transformed)\n", "\n", "# Check for equivariance!\n", - "assert equistore.equal_metadata(lsoap_uninv_inv, lsoap_inv)\n", - "for key in lsoap_inv.keys:\n", - " close = equistore.allclose_block(lsoap_uninv_inv[key], lsoap_inv[key])\n", - " print(key, close)\n", - " # if not close:\n", - " # break\n", - "# equistore.allclose_raise(lsoap_uninv_inv, lsoap_inv)\n", - "# print(\"INVERSION EQUIVARIANT!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([0., 0., 0., 0., 0.])" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "lsoap_uninv_inv.block(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22).values[0, ..., 0]" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", - " 1.74112929e-08, -2.46996381e-08, -4.00459763e-08],\n", - " [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", - " -5.43871155e-10, 7.70527867e-10, 1.25186725e-09],\n", - " [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", - " -2.09929680e-09, 2.97451559e-09, 4.83177337e-09],\n", - " [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", - " -4.21231684e-08, 5.97620466e-08, 9.68773874e-08],\n", - " [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,\n", - " 1.05008555e-09, -1.48888188e-09, -2.41593216e-09]],\n", - "\n", - " [[ 5.77641685e-09, -1.10520104e-08, 3.05784221e-08, ...,\n", - " 3.31438002e-06, -4.69593733e-06, -7.62865268e-06],\n", - " [ 3.52113221e-11, -6.64820657e-11, 1.88645904e-10, ...,\n", - " -1.00210410e-07, 1.41982231e-07, 2.30651638e-07],\n", - " [ 8.62442322e-11, -1.62836678e-10, 4.62056526e-10, ...,\n", - " 1.00771627e-08, -1.42656815e-08, -2.32029603e-08],\n", - " [ 4.08445606e-09, -7.81478413e-09, 2.16217466e-08, ...,\n", - " -3.06243227e-06, 4.33897545e-06, 7.04874201e-06],\n", - " [ 4.97931307e-11, -9.40137997e-11, 2.66768459e-10, ...,\n", - " 3.03024493e-08, -4.29163996e-08, -6.97570326e-08]],\n", - "\n", - " [[-2.89809123e-09, 5.54467175e-09, -1.53421415e-08, ...,\n", - " -1.61405131e-06, 2.28690439e-06, 3.71499190e-06],\n", - " [ 3.54809005e-09, -6.78830461e-09, 1.87830411e-08, ...,\n", - " -2.70760855e-06, 3.83627574e-06, 6.23202632e-06],\n", - " [ 2.21766379e-12, -4.18714896e-12, 1.18812009e-11, ...,\n", - " -1.51589225e-08, 2.14453501e-08, 3.49169128e-08],\n", - " [-2.04953663e-09, 3.92120421e-09, -1.08499971e-08, ...,\n", - " 1.41054804e-06, -1.99853556e-06, -3.24661975e-06],\n", - " [ 5.01708159e-09, -9.59882032e-09, 2.65596549e-08, ...,\n", - " 2.88240218e-06, -4.08389902e-06, -6.63437092e-06]],\n", - "\n", - " ...,\n", - "\n", - " [[-1.26014906e-12, 2.41066958e-12, -6.67175035e-12, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", - " [-2.82617530e-13, 5.40648274e-13, -1.49629424e-12, ...,\n", - " -1.38259301e-07, 1.95891472e-07, 3.18228335e-07],\n", - " [-6.92603198e-13, 1.32495229e-12, -3.66692797e-12, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", - " [ 1.78213011e-12, -3.40922101e-12, 9.43533433e-12, ...,\n", - " -5.53732602e-09, 7.84573990e-09, 1.27449485e-08],\n", - " [-3.99616116e-13, 7.64467080e-13, -2.11573290e-12, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]],\n", - "\n", - " [[ 1.37624274e-12, -2.63296916e-12, 7.28586172e-12, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", - " [ 2.37340102e-12, -4.54068977e-12, 1.25648419e-11, ...,\n", - " -2.89341666e-08, 4.09949781e-08, 6.65972135e-08],\n", - " [-9.77191959e-13, 1.86952205e-12, -5.17327765e-12, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", - " [-9.09707010e-13, 1.74041275e-12, -4.81601064e-12, ...,\n", - " -2.59960994e-08, 3.68308839e-08, 5.98359139e-08],\n", - " [-1.25523198e-12, 2.40145645e-12, -6.64522803e-12, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]],\n", - "\n", - " [[ 1.07215234e-13, -2.05099578e-13, 5.67650670e-13, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", - " [-5.66542786e-13, 1.08377966e-12, -2.99955867e-12, ...,\n", - " -4.05338380e-08, 5.74301234e-08, 9.32957217e-08],\n", - " [-2.97565786e-13, 5.69234583e-13, -1.57546096e-12, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],\n", - " [-4.67474364e-13, 8.94264680e-13, -2.47504131e-12, ...,\n", - " 7.52065039e-08, -1.06555678e-07, -1.73101109e-07],\n", - " [ 5.29475372e-13, -1.01287082e-12, 2.80330530e-12, ...,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]]])" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "diff = equistore.subtract(lsoap_uninv_inv, lsoap_inv)\n", - "diff.block(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22).values" + "assert equistore.equal_metadata(lsoap_transformed, lsoap_o3)\n", + "assert equistore.allclose(lsoap_transformed, lsoap_o3)\n", + "print(\"O(3) EQUIVARIANT!\")\n", + "\n", + "# chemiscope.show([frame, frame_o3], mode=\"structure\")" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=3) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=3) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=3) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=3) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=3) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=8) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=8) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=8) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=8) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=8) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=22) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=22) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=22) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=22) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22) True" + "# Other Tests" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=3) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=3) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=3) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=3) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=3) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=8) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=8) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=8) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=8) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=8) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=0, species_center=22) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=1, species_center=22) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=1, species_center=22) True\n", - "LabelsEntry(order_nu=2, inversion_sigma=1, spherical_harmonics_l=2, species_center=22) False\n", - "LabelsEntry(order_nu=2, inversion_sigma=-1, spherical_harmonics_l=2, species_center=22) False" + "# Define target lambda channels\n", + "lambdas = np.array([0, 1, 2, 3, 4, 5])" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1" + "TensorMap with 33 blocks\n", + "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", + " 2 1 0 3\n", + " 2 1 1 3\n", + " ...\n", + " 2 1 5 22\n", + " 2 -1 5 22" ] }, - "execution_count": 28, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "lsoap_inv.keys[0][\"inversion_sigma\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Normalization Tests" + "# Dense\n", + "lsoap_new0 = clebsch_gordan.n_body_iteration_single_center(\n", + " frames,\n", + " rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", + " lambda_cut=5,\n", + " use_sparse=False,\n", + ")\n", + "lsoap_new0" ] }, { @@ -404,13 +231,13 @@ { "data": { "text/plain": [ - "TensorMap with 15 blocks\n", + "TensorMap with 33 blocks\n", "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", " 2 1 0 3\n", " 2 1 1 3\n", " ...\n", - " 2 1 2 22\n", - " 2 -1 2 22" + " 2 1 5 22\n", + " 2 -1 5 22" ] }, "execution_count": 7, @@ -419,14 +246,16 @@ } ], "source": [ - "lsoap_new0 = clebsch_gordan.n_body_iteration_single_center(\n", + "# Sparse\n", + "lsoap_new1 = clebsch_gordan.n_body_iteration_single_center(\n", " frames,\n", " rascal_hypers,\n", " nu_target=2,\n", " lambdas=lambdas,\n", " lambda_cut=5,\n", + " use_sparse=True,\n", ")\n", - "lsoap_new0" + "lsoap_new1" ] }, { @@ -437,16 +266,37 @@ { "data": { "text/plain": [ - "TensorMap with 15 blocks\n", + "True" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Check sparse == dense\n", + "equistore.allclose(lsoap_new0, lsoap_new1)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "TensorMap with 33 blocks\n", "keys: inversion_sigma spherical_harmonics_l species_center\n", " 1 0 3\n", " 1 1 3\n", " ...\n", - " -1 1 22\n", - " -1 2 22" + " -1 4 22\n", + " -1 5 22" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -457,7 +307,8 @@ " rascal_hypers,\n", " lambda_cut=5,\n", ")\n", - "# Drop all odd parity keys/blocks\n", + "# Using the old implementation, blocks must be dropped to retain only the\n", + "# desired target lambdas\n", "keys_to_drop = Labels(\n", " names=lsoap_old0.keys.names,\n", " values=lsoap_old0.keys.values[[v not in lambdas for v in lsoap_old0.keys.column(\"spherical_harmonics_l\")]],\n", @@ -577,32 +428,6 @@ ")\n", "n_body" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# selected_samples = None\n", - "# species_neighbors = [1, 8, 6]\n", - "\n", - "# calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", - "# nu1 = calculator.compute(frames, selected_samples=selected_samples)\n", - "\n", - "# # Move the \"species_neighbor\" key to the properties. If species_neighbors is\n", - "# # passed as a list of int, sparsity can be created in the properties for\n", - "# # these species.\n", - "# if species_neighbors is None:\n", - "# keys_to_move = \"species_neighbor\"\n", - "# else:\n", - "# keys_to_move = Labels(\n", - "# names=[\"species_neighbor\"],\n", - "# values=np.array(species_neighbors).reshape(-1, 1),\n", - "# )\n", - "# nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move)\n", - "# nu1 = clebsch_gordan._add_nu_sigma_to_key_names(nu1)" - ] } ], "metadata": { From 13e5e187a6d5c8e052819bcc4eb0a3c8032d3858 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 2 Aug 2023 15:17:53 +0200 Subject: [PATCH 38/96] small tidy --- .../rascaline/utils/clebsch_gordan.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index e9baae78d..9a7a1046a 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -353,12 +353,7 @@ def _combine_single_center( "species_neighbor_2", ...] for each block. """ - # TODO: what metadata can we check for equivalence here? - # - Keys are not the same in general, as we accumulate "lx" and "kx" - # dimensions - - - # Get the correct keys for the combined TensorMap + # Get the correct keys for the combined output TensorMap ( combined_keys, keys_1_entries, @@ -478,7 +473,6 @@ def _clebsch_gordan_combine( if use_sparse: return _clebsch_gordan_combine_sparse(arr_1, arr_2, lam, cg_cache) return _clebsch_gordan_combine_dense(arr_1, arr_2, lam, cg_cache) - # return _clebsch_gordan_dense(arr_1, arr_2, lam, cg_cache) def _clebsch_gordan_combine_sparse( @@ -665,19 +659,18 @@ def _create_combined_keys( corresponding key. The correction_factor terms are the prefactors that account for the redundancy in the CG combination. """ - # Check that the body order of the second TensorMap is nu = 1 - assert np.all(keys_2.column("order_nu") == 1) - # Get the body order of the first TensorMap. nu1 = np.unique(keys_1.column("order_nu"))[0] + + # Define nu value of output TensorMap + nu = nu1 + 1 + + # Check the body order of the first TensorMap. assert np.all(keys_1.column("order_nu") == nu1) # The second by convention should be nu = 1. assert np.all(keys_2.column("order_nu") == 1) - # Define nu value of output TensorMap - nu = nu1 + 1 - # If nu = 1, the key names don't yet have any "lx" columns if nu1 == 1: l_list_names = [] From e535b93fb6d3444e366891fc87fabb4da891256d Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 2 Aug 2023 16:33:17 +0200 Subject: [PATCH 39/96] module for the old CG iteration code (i.e. equistore examples / acdc_mini -esque version) --- .../rascaline/utils/old_clebsch_gordan.py | 738 ++++++++++++++++++ 1 file changed, 738 insertions(+) create mode 100644 python/rascaline/rascaline/utils/old_clebsch_gordan.py diff --git a/python/rascaline/rascaline/utils/old_clebsch_gordan.py b/python/rascaline/rascaline/utils/old_clebsch_gordan.py new file mode 100644 index 000000000..320dd0989 --- /dev/null +++ b/python/rascaline/rascaline/utils/old_clebsch_gordan.py @@ -0,0 +1,738 @@ +""" +Module to compute Clebsh-Gordan coefficients and perform CG iterations. Also +contains a wrapper function for computing lambda-SOAP. + +Note: this is legacy code and only used as reference. +""" + +import re +from typing import Optional, Sequence + +import numpy as np +import wigners + +import equistore +from equistore import Labels, TensorBlock, TensorMap +import rascaline + + +class ClebschGordanReal: + """ + Class for generating CG coefficients. + """ + + def __init__(self, l_max): + self._l_max = l_max + self._cg = {} + + # real-to-complex and complex-to-real transformations as matrices + r2c = {} + c2r = {} + for L in range(0, self._l_max + 1): + r2c[L] = _real2complex(L) + c2r[L] = np.conjugate(r2c[L]).T + + for l1 in range(self._l_max + 1): + for l2 in range(self._l_max + 1): + for L in range( + max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1 + ): + complex_cg = _complex_clebsch_gordan_matrix(l1, l2, L) + + real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( + complex_cg.shape + ) + + real_cg = real_cg.swapaxes(0, 1) + real_cg = (r2c[l2].T @ real_cg.reshape(2 * l2 + 1, -1)).reshape( + real_cg.shape + ) + real_cg = real_cg.swapaxes(0, 1) + + real_cg = real_cg @ c2r[L].T + + if (l1 + l2 + L) % 2 == 0: + rcg = np.real(real_cg) + else: + rcg = np.imag(real_cg) + + new_cg = [] + for M in range(2 * L + 1): + cg_nonzero = np.where(np.abs(rcg[:, :, M]) > 1e-15) + cg_M = np.zeros( + len(cg_nonzero[0]), + dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], + ) + cg_M["m1"] = cg_nonzero[0] + cg_M["m2"] = cg_nonzero[1] + cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], M] + new_cg.append(cg_M) + + self._cg[(l1, l2, L)] = new_cg + + def combine(self, rho1, rho2, L): + # automatically infer l1 and l2 from the size of the coefficients vectors + l1 = (rho1.shape[1] - 1) // 2 + l2 = (rho2.shape[1] - 1) // 2 + if L > self._l_max or l1 > self._l_max or l2 > self._l_max: + raise ValueError("Requested CG entry has not been precomputed") + + n_items = rho1.shape[0] + n_features = rho1.shape[2] + if rho1.shape[0] != rho2.shape[0] or rho1.shape[2] != rho2.shape[2]: + raise IndexError("Cannot combine differently-shaped feature blocks") + + rho = np.zeros((n_items, 2 * L + 1, n_features)) + if (l1, l2, L) in self._cg: + for M in range(2 * L + 1): + for m1, m2, cg in self._cg[(l1, l2, L)][M]: + rho[:, M] += rho1[:, m1, :] * rho2[:, m2, :] * cg + + return rho + + def combine_einsum(self, rho1, rho2, L, combination_string): + # automatically infer l1 and l2 from the size of the coefficients vectors + l1 = (rho1.shape[1] - 1) // 2 + l2 = (rho2.shape[1] - 1) // 2 + if L > self._l_max or l1 > self._l_max or l2 > self._l_max: + raise ValueError( + "Requested CG entry ", (l1, l2, L), " has not been precomputed" + ) + + n_items = rho1.shape[0] + if rho1.shape[0] != rho2.shape[0]: + raise IndexError( + "Cannot combine feature blocks with different number of items" + ) + + # infers the shape of the output using the einsum internals + features = np.einsum(combination_string, rho1[:, 0, ...], rho2[:, 0, ...]).shape + rho = np.zeros((n_items, 2 * L + 1) + features[1:]) + + if (l1, l2, L) in self._cg: + for M in range(2 * L + 1): + for m1, m2, cg in self._cg[(l1, l2, L)][M]: + rho[:, M, ...] += np.einsum( + combination_string, rho1[:, m1, ...], rho2[:, m2, ...] * cg + ) + + return rho + + def couple(self, decoupled, iterate=0): + """ + Goes from an uncoupled product basis to a coupled basis. A + (2l1+1)x(2l2+1) matrix transforming like the outer product of Y^m1_l1 + Y^m2_l2 can be rewritten as a list of coupled vectors, each transforming + like a Y^L irrep. + + The process can be iterated: a D dimensional array that is the product + of D Y^m_l can be turned into a set of multiple terms transforming as a + single Y^M_L. + + decoupled: array or dict + (...)x(2l1+1)x(2l2+1) array containing coefficients that transform + like products of Y^l1 and Y^l2 harmonics. can also be called on a + array of higher dimensionality, in which case the result will + contain matrices of entries. If the further index also correspond to + spherical harmonics, the process can be iterated, and couple() can + be called onto its output, in which case the decoupling is applied + to each entry. + + iterate: int + calls couple iteratively the given number of times. equivalent to + couple(couple(... couple(decoupled))) + + Returns: + -------- + A dictionary tracking the nature of the coupled objects. When called one + time, it returns a dictionary containing (l1, l2) [the coefficients of + the parent Ylm] which in turns is a dictionary of coupled terms, in the + form L:(...)x(2L+1)x(...) array. When called multiple times, it applies + the coupling to each term, and keeps track of the additional l terms, so + that e.g. when called with iterate=1 the return dictionary contains + terms of the form (l3,l4,l1,l2) : { L: array } + + + Note that this coupling scheme is different from the NICE-coupling where + angular momenta are coupled from left to right as (((l1 l2) l3) l4)... ) + Thus results may differ when combining more than two angular channels. + """ + + coupled = {} + + # when called on a matrix, turns it into a dict form to which we can + # apply the generic algorithm + if not isinstance(decoupled, dict): + l2 = (decoupled.shape[-1] - 1) // 2 + decoupled = {(): {l2: decoupled}} + + # runs over the tuple of (partly) decoupled terms + for ltuple, lcomponents in decoupled.items(): + # each is a list of L terms + for lc in lcomponents.keys(): + # this is the actual matrix-valued coupled term, + # of shape (..., 2l1+1, 2l2+1), transforming as Y^m1_l1 Y^m2_l2 + dec_term = lcomponents[lc] + l1 = (dec_term.shape[-2] - 1) // 2 + l2 = (dec_term.shape[-1] - 1) // 2 + + # there is a certain redundance: the L value is also the last entry + # in ltuple + if lc != l2: + raise ValueError( + "Inconsistent shape for coupled angular momentum block." + ) + + # in the new coupled term, prepend (l1,l2) to the existing label + coupled[(l1, l2) + ltuple] = {} + for L in range( + max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1 + ): + Lterm = np.zeros(shape=dec_term.shape[:-2] + (2 * L + 1,)) + for M in range(2 * L + 1): + for m1, m2, cg in self._cg[(l1, l2, L)][M]: + Lterm[..., M] += dec_term[..., m1, m2] * cg + coupled[(l1, l2) + ltuple][L] = Lterm + + # repeat if required + if iterate > 0: + coupled = self.couple(coupled, iterate - 1) + return coupled + + def decouple(self, coupled, iterate=0): + """ + Undoes the transformation enacted by couple. + """ + + decoupled = {} + # applies the decoupling to each entry in the dictionary + for ltuple, lcomponents in coupled.items(): + # the initial pair in the key indicates the decoupled terms that generated + # the L entries + l1, l2 = ltuple[:2] + + # shape of the coupled matrix (last entry is the 2L+1 M terms) + shape = next(iter(lcomponents.values())).shape[:-1] + + dec_term = np.zeros( + shape + + ( + 2 * l1 + 1, + 2 * l2 + 1, + ) + ) + for L in range(max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1): + # supports missing L components, e.g. if they are zero because of symmetry + if not L in lcomponents: + continue + for M in range(2 * L + 1): + for m1, m2, cg in self._cg[(l1, l2, L)][M]: + dec_term[..., m1, m2] += cg * lcomponents[L][..., M] + # stores the result with a key that drops the l's we have just decoupled + if not ltuple[2:] in decoupled: + decoupled[ltuple[2:]] = {} + decoupled[ltuple[2:]][l2] = dec_term + + # rinse, repeat + if iterate > 0: + decoupled = self.decouple(decoupled, iterate - 1) + + # if we got a fully decoupled state, just return an array + if ltuple[2:] == (): + decoupled = next(iter(decoupled[()].values())) + return decoupled + + +# ===== helper functions for ClebschGordanReal + + +def _real2complex(L): + """ + Computes a matrix that can be used to convert from real to complex-valued + spherical harmonics(coefficients) of order L. + + It's meant to be applied to the left, ``real2complex @ [-L..L]``. + """ + result = np.zeros((2 * L + 1, 2 * L + 1), dtype=np.complex128) + + i_sqrt_2 = 1.0 / np.sqrt(2) + + for m in range(-L, L + 1): + if m < 0: + result[L - m, L + m] = i_sqrt_2 * 1j * (-1) ** m + result[L + m, L + m] = -i_sqrt_2 * 1j + + if m == 0: + result[L, L] = 1.0 + + if m > 0: + result[L + m, L + m] = i_sqrt_2 * (-1) ** m + result[L - m, L + m] = i_sqrt_2 + + return result + + +def _complex_clebsch_gordan_matrix(l1, l2, L): + if np.abs(l1 - l2) > L or np.abs(l1 + l2) < L: + return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * L + 1), dtype=np.double) + else: + return wigners.clebsch_gordan_array(l1, l2, L) + + +# ======== Fxns used to perform CG iterations + + +def acdc_standardize_keys(descriptor): + """ + Standardize the naming scheme of density expansion coefficient blocks + (nu=1) + """ + + key_names = descriptor.keys.names + if not "spherical_harmonics_l" in key_names: + raise ValueError( + "Descriptor missing spherical harmonics channel key `spherical_harmonics_l`" + ) + blocks = [] + keys = [] + for key, block in descriptor.items(): + key = tuple(key) + if not "inversion_sigma" in key_names: + key = (1,) + key + if not "order_nu" in key_names: + key = (1,) + key + keys.append(key) + property_names = _remove_suffix(block.properties.names, "_1") + blocks.append( + TensorBlock( + values=block.values, + samples=block.samples, + components=block.components, + properties=Labels(property_names, block.properties.values), + ) + ) + + if not "inversion_sigma" in key_names: + key_names = ["inversion_sigma"] + key_names + if not "order_nu" in key_names: + key_names = ["order_nu"] + key_names + + return TensorMap( + keys=Labels(names=key_names, values=np.asarray(keys, dtype=np.int32)), + blocks=blocks, + ) + + +def cg_combine( + x_a, + x_b, + feature_names=None, + clebsch_gordan=None, + lcut=None, + other_keys_match=None, +): + """ + Performs a CG product of two sets of equivariants. Only requirement is that + sparse indices are labeled as ("inversion_sigma", "spherical_harmonics_l", + "order_nu"). The automatically-determined naming of output features can be + overridden by giving a list of "feature_names". By defaults, all other key + labels are combined in an "outer product" mode, i.e. if there is a key-side + neighbor_species in both x_a and x_b, the returned keys will have two + neighbor_species labels, corresponding to the parent features. By providing + a list `other_keys_match` of keys that should match, these are not + outer-producted, but combined together. for instance, passing `["species + center"]` means that the keys with the same species center will be combined + together, but yield a single key with the same species_center in the + results. + """ + + # determines the cutoff in the new features + lmax_a = max(x_a.keys["spherical_harmonics_l"]) + lmax_b = max(x_b.keys["spherical_harmonics_l"]) + if lcut is None: + lcut = lmax_a + lmax_b + + # creates a CG object, if needed + if clebsch_gordan is None: + clebsch_gordan = ClebschGordanReal(lcut) + + other_keys_a = tuple( + name + for name in x_a.keys.names + if name not in ["spherical_harmonics_l", "order_nu", "inversion_sigma"] + ) + other_keys_b = tuple( + name + for name in x_b.keys.names + if name not in ["spherical_harmonics_l", "order_nu", "inversion_sigma"] + ) + + if other_keys_match is None: + OTHER_KEYS = [k + "_a" for k in other_keys_a] + [k + "_b" for k in other_keys_b] + else: + OTHER_KEYS = ( + other_keys_match + + [ + k + ("_a" if k in other_keys_b else "") + for k in other_keys_a + if k not in other_keys_match + ] + + [ + k + ("_b" if k in other_keys_a else "") + for k in other_keys_b + if k not in other_keys_match + ] + ) + + # we assume grad components are all the same + if x_a.block(0).has_gradient("positions"): + grad_components = x_a.block(0).gradient("positions").components + else: + grad_components = None + + # automatic generation of the output features names + # "x1 x2 x3 ; x1 x2 -> x1_a x2_a x3_a k_nu x1_b x2_b l_nu" + if feature_names is None: + NU = x_a.keys[0]["order_nu"] + x_b.keys[0]["order_nu"] + feature_names = ( + tuple(n + "_a" for n in x_a.block(0).properties.names) + + ("k_" + str(NU),) + + tuple(n + "_b" for n in x_b.block(0).properties.names) + + ("l_" + str(NU),) + ) + + X_idx = {} + X_blocks = {} + X_samples = {} + X_grad_samples = {} + X_grads = {} + + # loops over sparse blocks of x_a + for index_a, block_a in x_a.items(): + lam_a = index_a["spherical_harmonics_l"] + sigma_a = index_a["inversion_sigma"] + order_a = index_a["order_nu"] + properties_a = ( + block_a.properties + ) # pre-extract this block as accessing a c property has a non-zero cost + samples_a = block_a.samples + + # and x_b + for index_b, block_b in x_b.items(): + lam_b = index_b["spherical_harmonics_l"] + sigma_b = index_b["inversion_sigma"] + order_b = index_b["order_nu"] + properties_b = block_b.properties + samples_b = block_b.samples + + if other_keys_match is None: + OTHERS = tuple(index_a[name] for name in other_keys_a) + tuple( + index_b[name] for name in other_keys_b + ) + else: + OTHERS = tuple( + index_a[k] for k in other_keys_match if index_a[k] == index_b[k] + ) + # skip combinations without matching key + if len(OTHERS) < len(other_keys_match): + continue + # adds non-matching keys to build outer product + OTHERS = OTHERS + tuple( + index_a[k] for k in other_keys_a if k not in other_keys_match + ) + OTHERS = OTHERS + tuple( + index_b[k] for k in other_keys_b if k not in other_keys_match + ) + + if "neighbor" in samples_b.names and "neighbor" not in samples_a.names: + # we hard-code a combination method where b can be a pair descriptor. this needs some work to be general and robust + # note also that this assumes that structure, center are ordered in the same way in the centred and neighbor descriptors + neighbor_slice = [] + smp_a, smp_b = 0, 0 + while smp_b < samples_b.shape[0]: + if samples_b[smp_b][["structure", "center"]] != samples_a[smp_a]: + smp_a += 1 + neighbor_slice.append(smp_a) + smp_b += 1 + neighbor_slice = np.asarray(neighbor_slice) + else: + neighbor_slice = slice(None) + + # determines the properties that are in the select list + sel_feats = [] + sel_idx = [] + sel_feats = ( + np.indices((len(properties_a), len(properties_b))).reshape(2, -1).T + ) + + prop_ids_a = [] + prop_ids_b = [] + for n_a, f_a in enumerate(properties_a): + prop_ids_a.append(tuple(f_a) + (lam_a,)) + for n_b, f_b in enumerate(properties_b): + prop_ids_b.append(tuple(f_b) + (lam_b,)) + prop_ids_a = np.asarray(prop_ids_a) + prop_ids_b = np.asarray(prop_ids_b) + sel_idx = np.hstack( + [prop_ids_a[sel_feats[:, 0]], prop_ids_b[sel_feats[:, 1]]] + ) + if len(sel_feats) == 0: + continue + # loops over all permissible output blocks. note that blocks will + # be filled from different la, lb + for L in range(np.abs(lam_a - lam_b), 1 + min(lam_a + lam_b, lcut)): + # determines parity of the block + S = sigma_a * sigma_b * (-1) ** (lam_a + lam_b + L) + NU = order_a + order_b + KEY = ( + NU, + S, + L, + ) + OTHERS + if not KEY in X_idx: + X_idx[KEY] = [] + X_blocks[KEY] = [] + X_samples[KEY] = block_b.samples + if grad_components is not None: + X_grads[KEY] = [] + X_grad_samples[KEY] = block_b.gradient("positions").samples + + # builds all products in one go + one_shot_blocks = clebsch_gordan.combine_einsum( + block_a.values[neighbor_slice][:, :, sel_feats[:, 0]], + block_b.values[:, :, sel_feats[:, 1]], + L, + combination_string="iq,iq->iq", + ) + # do gradients, if they are present... + if grad_components is not None: + grad_a = block_a.gradient("positions") + grad_b = block_b.gradient("positions") + grad_a_data = np.swapaxes(grad_a.data, 1, 2) + grad_b_data = np.swapaxes(grad_b.data, 1, 2) + one_shot_grads = clebsch_gordan.combine_einsum( + block_a.values[grad_a.samples["sample"]][ + neighbor_slice, :, sel_feats[:, 0] + ], + grad_b_data[..., sel_feats[:, 1]], + L=L, + combination_string="iq,iaq->iaq", + ) + clebsch_gordan.combine_einsum( + block_b.values[grad_b.samples["sample"]][:, :, sel_feats[:, 1]], + grad_a_data[neighbor_slice, ..., sel_feats[:, 0]], + L=L, + combination_string="iq,iaq->iaq", + ) + + # now loop over the selected features to build the blocks + + X_idx[KEY].append(sel_idx) + X_blocks[KEY].append(one_shot_blocks) + if grad_components is not None: + X_grads[KEY].append(one_shot_grads) + + # turns data into sparse storage format (and dumps any empty block in the + # process) + nz_idx = [] + nz_blk = [] + for KEY in X_blocks: + L = KEY[2] + # create blocks + if len(X_blocks[KEY]) == 0: + continue # skips empty blocks + nz_idx.append(KEY) + block_data = np.concatenate(X_blocks[KEY], axis=-1) + sph_components = Labels( + ["spherical_harmonics_m"], + np.asarray(range(-L, L + 1), dtype=np.int32).reshape(-1, 1), + ) + newblock = TensorBlock( + # feature index must be last + values=block_data, + samples=X_samples[KEY], + components=[sph_components], + properties=Labels( + feature_names, np.asarray(np.vstack(X_idx[KEY]), dtype=np.int32) + ), + ) + if grad_components is not None: + grad_data = np.swapaxes(np.concatenate(X_grads[KEY], axis=-1), 2, 1) + newblock.add_gradient( + "positions", + data=grad_data, + samples=X_grad_samples[KEY], + components=[grad_components[0], sph_components], + ) + nz_blk.append(newblock) + X = TensorMap( + Labels( + ["order_nu", "inversion_sigma", "spherical_harmonics_l"] + OTHER_KEYS, + np.asarray(nz_idx, dtype=np.int32), + ), + nz_blk, + ) + return X + + +def cg_increment( + x_nu, + x_1, + clebsch_gordan=None, + lcut=None, + other_keys_match=None, +): + """Specialized version of the CG product to perform iterations with nu=1 features""" + + nu = x_nu.keys["order_nu"][0] + + feature_roots = _remove_suffix(x_1.block(0).properties.names) + + if nu == 1: + feature_names = ( + [root + "_1" for root in feature_roots] + + ["l_1"] + + [root + "_2" for root in feature_roots] + + ["l_2"] + ) + else: + feature_names = ( + [x_nu.block(0).properties.names] + + ["k_" + str(nu + 1)] + + [root + "_" + str(nu + 1) for root in feature_roots] + + ["l_" + str(nu + 1)] + ) + + return cg_combine( + x_nu, + x_1, + feature_names=feature_names, + clebsch_gordan=clebsch_gordan, + lcut=lcut, + other_keys_match=other_keys_match, + ) + + +def _remove_suffix(names, new_suffix=""): + suffix = re.compile("_[0-9]?$") + rname = [] + for name in names: + match = suffix.search(name) + if match is None: + rname.append(name + new_suffix) + else: + rname.append(name[: match.start()] + new_suffix) + return rname + + +def lambda_soap_vector( + frames: list, + rascal_hypers: dict, + lambdas: Sequence[int], + lambda_cut: Optional[int] = None, + selected_samples: Optional[Labels] = None, + neighbor_species: Optional[Sequence[int]] = None, + even_parity_only: bool = False, +) -> TensorMap: + """ + Takes a list of frames of ASE loaded frames and a dict of Rascaline + hyperparameters and generates a lambda-SOAP (i.e. nu=2) representation. + + Passing a subset of samples in `selected_samples` can be used to, for + instance, only calculate the features for a subset of the strutcures passed + in `frames`. For instance: `selected_samples = Labels(names=["structure"], + values[4, 5, 6])` will only calculate the lambda-features for structures + indexed by 4, 5, 6. + + :param frames: a list of structures generated by the ase.io function. + :param rascal_hypers: a dict of hyperparameters used to calculate the atom + density correlation calculated with rascaline SphericalExpansion + :param lambda_cut: an int of the maximum lambda value to compute + combinations for. If none, the 'max_angular' value in `rascal_hypers` + will be used instead. + :param selected_samples: a Labels object that defines which samples, as a + subset of the total samples in `frames` (i.e. atomic environments or + structures) to perform the calculation on. + :param neighbor_species: a list of int that correspond to the atomic charges + of all the neighbour species that you want to be in your properties (or + features) dimension. This list may contain charges for atoms that don't + appear in ``frames``, but are included anyway so that the one can + enforce consistent properties dimension size with other lambda-feature + vectors. + :param even_parity_only: a bool that determines whether to only include the + key/block pairs with even parity under rotation, i.e. sigma = +1. + Defaults to false, where both parities are included. + :param save_dir: a str of the absolute path to the directory where the + TensorMap of the calculated lambda-SOAP representation and pickled + ``rascal_hypers`` dict should be written. If none, the TensorMap will not be + saved. + + :return: a TensorMap of the lambda-SOAP feature vector for the selected + samples of the input frames. + """ + # Generate Rascaline spherical expansion + calculator = rascaline.SphericalExpansion(**rascal_hypers) + if lambda_cut is None: + lambda_cut = 2 * rascal_hypers["max_angular"] + else: + if lambda_cut > 2 * rascal_hypers["max_angular"]: + raise ValueError( + "As this function generates 2-body features (nu=2), " + "`lambda_cut` must be <= 2 x rascal_hypers['max_angular'] " + f"`rascal_hypers`. Received {lambda_cut}." + ) + # Pre-calculate ClebschGordan coefficients + cg = ClebschGordanReal(l_max=lambda_cut) + + # Generate descriptor via Spherical Expansion + nu1 = calculator.compute(frames, selected_samples=selected_samples) + + # nu=1 features + nu1 = acdc_standardize_keys(nu1) + + # Move "species_neighbor" sparse keys to properties with enforced atom + # charges if ``neighbor_species`` is specified. This is required as the CG + # iteration code currently does not handle neighbour species padding + # automatically. + keys_to_move = "species_neighbor" + if neighbor_species is not None: + keys_to_move = Labels( + names=(keys_to_move,), + values=np.array(neighbor_species).reshape(-1, 1), + ) + nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move) + + # Combined nu=1 features to generate nu=2 features. lambda-SOAP is defined + # as just the nu=2 features. + lsoap = cg_increment( + nu1, + nu1, + clebsch_gordan=cg, + lcut=lambda_cut, + other_keys_match=["species_center"], + ) + + # Clean the lambda-SOAP TensorMap. Drop the order_nu key name as this is by + # definition 2 for all keys. + lsoap = equistore.remove_dimension(lsoap, axis="keys", name="order_nu") + + # Drop all odd parity keys/blocks + if even_parity_only: + keys_to_drop = Labels( + names=lsoap.keys.names, + values=lsoap.keys.values[lsoap.keys.column("inversion_sigma") == -1], + ) + lsoap = equistore.drop_blocks(lsoap, keys=keys_to_drop) + + # Drop the inversion_sigma key name as this is now +1 for all blocks + lsoap = equistore.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + + # Drop all blocks that don't correspond to the target lambdas + keys_to_drop = Labels( + names=lsoap.keys.names, + values=lsoap.keys.values[ + [v not in lambdas for v in lsoap.keys.column("spherical_harmonics_l")] + ], + ) + lsoap = equistore.drop_blocks(lsoap, keys=keys_to_drop) + + return lsoap From 7c5bd0c715b81beedc24ad3b0241473cc09a72cc Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 2 Aug 2023 16:33:44 +0200 Subject: [PATCH 40/96] Module for Wigner matrices, (S)O(3) transformations of TensorMaps --- python/rascaline/rascaline/utils/rotations.py | 316 ++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 python/rascaline/rascaline/utils/rotations.py diff --git a/python/rascaline/rascaline/utils/rotations.py b/python/rascaline/rascaline/utils/rotations.py new file mode 100644 index 000000000..f7fb7a5e4 --- /dev/null +++ b/python/rascaline/rascaline/utils/rotations.py @@ -0,0 +1,316 @@ +""" +Class for generating real Wigner-D matrices, and using them to rotate ASE frames +and TensorMaps of density coefficients in the spherical basis. +""" +from typing import Optional, Sequence + +import ase +import numpy as np +from scipy.spatial.transform import Rotation +import torch + +import equistore +from equistore import Labels, TensorBlock, TensorMap +import wigners + + +# ===== Functions for transformations in the Cartesian basis ===== + + +def cartesian_rotation(angles: Sequence[float]): + """ + Returns a Cartesian rotation matrix in the appropriate convention (ZYZ, + implicit rotations) to be consistent with the common Wigner D definition. + + `angles` correspond to the alpha, beta, gamma Euler angles in the ZYZ + convention, in radians. + """ + return Rotation.from_euler("ZYZ", angles).as_matrix() + + +def transform_frame_so3(frame: ase.Atoms, angles: Sequence[float]) -> ase.Atoms: + """ + Transforms the positions and cell coordinates of an ASE frame by a SO(3) + rigid rotation. + """ + new_frame = frame.copy() + + # Build cartesian rotation matrix + R = cartesian_rotation(angles) + + # Rotate its positions and cell + new_frame.positions = new_frame.positions @ R.T + new_frame.cell = new_frame.cell @ R.T + + return new_frame + + +def transform_frame_o3(frame: ase.Atoms, angles: Sequence[float]) -> ase.Atoms: + """ + Transforms the positions and cell coordinates of an ASE frame by an O(3) + rotation. This involves a rigid SO(3) rotation of the positions and cell + according to the Euler `angles`, then an inversion by multiplying just the + positions by -1. + """ + new_frame = frame.copy() + + # Build cartesian rotation matrix + R = cartesian_rotation(angles) + + # Rotate its positions and cell + new_frame.positions = new_frame.positions @ R.T + new_frame.cell = new_frame.cell @ R.T + + # Invert the atom positions + new_frame.positions *= -1 + + return new_frame + + +class WignerDReal: + """ + A helper class to compute Wigner D matrices given the Euler angles of a rotation, + and apply them to spherical harmonics (or coefficients). Built to function with + real-valued coefficients. + """ + + def __init__(self, lmax: int, angles: Sequence[float] = None): + """ + Initialize the WignerDReal class. + + :param lmax: int, the maximum angular momentum channel for which the + Wigner D matrices are computed + :param angles: Sequence[float], the alpha, beta, gamma Euler angles, in + radians. + """ + self.lmax = lmax + # Randomly generate Euler angles between 0 and 2 pi if none are provided + if angles is None: + angles = np.random.uniform(size=(3)) * 2 * np.pi + self.angles = angles + self.rotation = cartesian_rotation(angles) + + r2c_mats = {} + c2r_mats = {} + for L in range(0, self.lmax + 1): + r2c_mats[L] = np.hstack( + [_r2c(np.eye(2 * L + 1)[i])[:, np.newaxis] for i in range(2 * L + 1)] + ) + c2r_mats[L] = np.conjugate(r2c_mats[L]).T + self.matrices = {} + for L in range(0, self.lmax + 1): + wig = _wigner_d(L, self.angles) + self.matrices[L] = np.real(c2r_mats[L] @ np.conjugate(wig) @ r2c_mats[L]) + + def rotate_coeff_vector( + self, + frame: ase.Atoms, + coeffs: np.ndarray, + lmax: dict, + nmax: dict, + ) -> np.ndarray: + """ + Rotates the irreducible spherical components (ISCs) of basis set + coefficients in the spherical basis passed in as a flat vector. + + Required is the basis set definition specified by ``lmax`` and ``nmax``. + This are dicts of the form: + + lmax = {symbol: lmax_value, ...} + nmax = {(symbol, l): nmax_value, ...} + + where ``symbol`` is the chemical symbol of the atom, ``lmax_value`` is + its corresponding max l channel value. For each combination of species + symbol and lmax, there exists a max radial channel value ``nmax_value``. + + Then, the assumed ordering of basis function coefficients follows a + hierarchy, which can be read as nested loops over the various indices. + Be mindful that some indices range are from 0 to x (exclusive) and + others from 0 to x + 1 (exclusive). The ranges reported below are + ordered. + + 1. Loop over atoms (index ``i``, of chemical species ``a``) in the + structure. ``i`` takes values 0 to N (** exclusive **), where N is the + number of atoms in the structure. + + 2. Loop over spherical harmonics channel (index ``l``) for each atom. + ``l`` takes values from 0 to ``lmax[a] + 1`` (** exclusive **), where + ``a`` is the chemical species of atom ``i``, given by the chemical + symbol at the ``i``th position of ``symbol_list``. + + 3. Loop over radial channel (index ``n``) for each atom ``i`` and + spherical harmonics channel ``l`` combination. ``n`` takes values from 0 + to ``nmax[(a, l)]`` (** exclusive **). + + 4. Loop over spherical harmonics component (index ``m``) for each atom. + ``m`` takes values from ``-l`` to ``l`` (** inclusive **). + + :param frame: the atomic structure in ASE format for which the + coefficients are defined. + :param coeffs: the coefficients in the spherical basis, as a flat + vector. + :param lmax: dict containing the maximum spherical harmonics (l) value + for each atom type. + :param nmax: dict containing the maximum radial channel (n) value for + each combination of atom type and l. + + :return: the rotated coefficients in the spherical basis, as a flat + vector with the same order as the input vector. + """ + # Initialize empty vector for storing the rotated ISCs + rot_vect = np.empty_like(coeffs) + + # Iterate over atomic species of the atoms in the frame + curr_idx = 0 + for symbol in frame.get_chemical_symbols(): + # Get the basis set lmax value for this species + sym_lmax = lmax[symbol] + for l in range(sym_lmax + 1): + # Get the number of radial functions for this species and l value + sym_l_nmax = nmax[(symbol, l)] + # Get the Wigner D Matrix for this l value + wig_mat = self.matrices[l].T + for n in range(sym_l_nmax): + # Retrieve the irreducible spherical component + isc = coeffs[curr_idx : curr_idx + (2 * l + 1)] + # Rotate the ISC and store + rot_isc = isc @ wig_mat + rot_vect[curr_idx : curr_idx + (2 * l + 1)][:] = rot_isc[:] + # Update the start index for the next ISC + curr_idx += 2 * l + 1 + + return rot_vect + + def rotate_tensorblock(self, l: int, block: TensorBlock) -> TensorBlock: + """ + Rotates a TensorBlock ``block``, represented in the spherical basis, + according to the Wigner D Real matrices for the given ``l`` value. + Assumes the components of the block are [("spherical_harmonics_m",),]. + """ + # Get the Wigner matrix for this l value + wig = self.matrices[l].T + + # Copy the block + block_rotated = block.copy() + vals = block_rotated.values + + # Perform the rotation, either with numpy or torch, by taking the + # tensordot product of the irreducible spherical components. Modify + # in-place the values of the copied TensorBlock. + if isinstance(vals, torch.Tensor): + wig = torch.tensor(wig) + block_rotated.values[:] = torch.tensordot( + vals.swapaxes(1, 2), wig, dims=1 + ).swapaxes(1, 2) + elif isinstance(block.values, np.ndarray): + block_rotated.values[:] = np.tensordot( + vals.swapaxes(1, 2), wig, axes=1 + ).swapaxes(1, 2) + else: + raise TypeError("TensorBlock values must be a numpy array or torch tensor.") + + return block_rotated + + def transform_tensormap_so3(self, tensor: TensorMap) -> TensorMap: + """ + Transforms a TensorMap by a by an SO(3) rigid rotation using Wigner-D + matrices. + + Assumes the input tensor follows the metadata structure consistent with + those produce by rascaline. + """ + # Retrieve the key and the position of the l value in the key names + keys = tensor.keys + idx_l_value = keys.names.index("spherical_harmonics_l") + + # Iterate over the blocks and rotate + rotated_blocks = [] + for key in keys: + # Retrieve the l value + l = key[idx_l_value] + + # Rotate the block and store + rotated_blocks.append(self.rotate_tensorblock(l, tensor[key])) + + return TensorMap(keys, rotated_blocks) + + def transform_tensormap_o3(self, tensor: TensorMap) -> TensorMap: + """ + Transforms a TensorMap by a by an O(3) transformation: this involves an + SO(3) rigid rotation using Wigner-D Matrices followed by an inversion. + + Assumes the input tensor follows the metadata structure consistent with + those produce by rascaline. + """ + # Retrieve the key and the position of the l value in the key names + keys = tensor.keys + idx_l_value = keys.names.index("spherical_harmonics_l") + + # Iterate over the blocks and rotate + new_blocks = [] + for key in keys: + # Retrieve the l value + l = key[idx_l_value] + + # Rotate the block + new_block = self.rotate_tensorblock(l, tensor[key]) + + # Work out the inversion multiplier according to the convention + inversion_multiplier = 1 + if key["spherical_harmonics_l"] % 2 == 1: + inversion_multiplier *= -1 + + # "inversion_sigma" may not be present if CG iterations haven't been + # performed (i.e. nu=1 rascaline SphericalExpansion) + if "inversion_sigma" in keys.names: + if key["inversion_sigma"] == -1: + inversion_multiplier *= -1 + + # Invert the block by applying the inversion multiplier + new_block = TensorBlock( + values=new_block.values * inversion_multiplier, + samples=new_block.samples, + components=new_block.components, + properties=new_block.properties, + ) + new_blocks.append(new_block) + + return TensorMap(keys, new_blocks) + + +# ===== Helper functions for WignerDReal + + +def _wigner_d(l: int, angles: Sequence[float]) -> np.ndarray: + """ + Computes the Wigner D matrix: + D^l_{mm'}(alpha, beta, gamma) + from sympy and converts it to numerical values. + + `angles` are the alpha, beta, gamma Euler angles (radians, ZYZ convention) + and l the irrep. + """ + try: + from sympy.physics.wigner import wigner_d + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Calculation of Wigner D matrices requires a sympy installation" + ) + return np.complex128(wigner_d(l, *angles)) + + +def _r2c(sp): + """ + Real to complex SPH. Assumes a block with 2l+1 reals corresponding + to real SPH with m indices from -l to +l + """ + + i_sqrt_2 = 1.0 / np.sqrt(2) + + l = (len(sp) - 1) // 2 # infers l from the vector size + rc = np.zeros(len(sp), dtype=np.complex128) + rc[l] = sp[l] + for m in range(1, l + 1): + rc[l + m] = (sp[l + m] + 1j * sp[l - m]) * i_sqrt_2 * (-1) ** m + rc[l - m] = (sp[l + m] - 1j * sp[l - m]) * i_sqrt_2 + return rc From 6a5982d30f4c450620d74c7c66610a258f0db00d Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 2 Aug 2023 16:34:20 +0200 Subject: [PATCH 41/96] Minor change --- python/rascaline/rascaline/utils/spherical.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/python/rascaline/rascaline/utils/spherical.py b/python/rascaline/rascaline/utils/spherical.py index f5b59096b..0cb155d3d 100644 --- a/python/rascaline/rascaline/utils/spherical.py +++ b/python/rascaline/rascaline/utils/spherical.py @@ -261,7 +261,8 @@ def invert_tensormap(tensor: TensorMap) -> TensorMap: """ Takes a TensorMap in the spherical basis and perform a parity inversion. This amounts to applying a factor of (-1) to all odd parity blocks, i.e. - those with key values of "order_nu" equal to -1. + those with key values of "order_nu" equal to -1. It also applies a + (potentially extra) factor of (-1) to blocks with odd l channel. """ new_blocks = [] for key, block in tensor.items(): @@ -1040,6 +1041,7 @@ def _remove_suffix(names, new_suffix=""): def lambda_soap_vector( frames: list, rascal_hypers: dict, + lambdas: Sequence[int], lambda_cut: Optional[int] = None, selected_samples: Optional[Labels] = None, neighbor_species: Optional[Sequence[int]] = None, @@ -1138,4 +1140,11 @@ def lambda_soap_vector( # Drop the inversion_sigma key name as this is now +1 for all blocks lsoap = equistore.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + # Drop all blocks that don't correspond to the target lambdas + keys_to_drop = Labels( + names=lsoap.keys.names, + values=lsoap.keys.values[[v not in lambdas for v in lsoap.keys.column("spherical_harmonics_l")]], + ) + lsoap = equistore.drop_blocks(lsoap, keys=keys_to_drop) + return lsoap From 7daaf4a1de18bbd3bee38fb946280c2434f43a15 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 2 Aug 2023 16:35:22 +0200 Subject: [PATCH 42/96] remove deprecated spherical.py --- python/rascaline/rascaline/utils/spherical.py | 1150 ----------------- 1 file changed, 1150 deletions(-) delete mode 100644 python/rascaline/rascaline/utils/spherical.py diff --git a/python/rascaline/rascaline/utils/spherical.py b/python/rascaline/rascaline/utils/spherical.py deleted file mode 100644 index 0cb155d3d..000000000 --- a/python/rascaline/rascaline/utils/spherical.py +++ /dev/null @@ -1,1150 +0,0 @@ -""" -Classes and functions to aid converting to and from, and operating within, the -spherical basis. Contains classes WignerDReal and ClebschGordanReal, as well as -functions to perform Clebsch-Gordan iterations. Code mostly taken from -librascal: - -github.com/lab-cosmo/librascal/blob/master/bindings/rascal/utils/cg_utils.py -""" -from copy import deepcopy -from itertools import product -import re -from typing import Optional, Tuple, Sequence - -import ase -import numpy as np -from scipy.spatial.transform import Rotation -import torch -import wigners - -import equistore -from equistore import Labels, TensorBlock, TensorMap -import rascaline - - -# ===== WignerDReal class for describing rotations - - -class WignerDReal: - """ - A helper class to compute Wigner D matrices given the Euler angles of a rotation, - and apply them to spherical harmonics (or coefficients). Built to function with - real-valued coefficients. - """ - - def __init__(self, lmax, alpha, beta, gamma): - """ - Initialize the WignerDReal class. - lmax: int - maximum angular momentum channel for which the Wigner D matrices are - computed - alpha, beta, gamma: float - Euler angles, in radians - """ - self._lmax = lmax - - self._rotation = cartesian_rotation(alpha, beta, gamma) - - r2c_mats = {} - c2r_mats = {} - for L in range(0, self._lmax + 1): - r2c_mats[L] = np.hstack( - [_r2c(np.eye(2 * L + 1)[i])[:, np.newaxis] for i in range(2 * L + 1)] - ) - c2r_mats[L] = np.conjugate(r2c_mats[L]).T - self._wddict = {} - for L in range(0, self._lmax + 1): - wig = _wigner_d(L, alpha, beta, gamma) - self._wddict[L] = np.real(c2r_mats[L] @ np.conjugate(wig) @ r2c_mats[L]) - - def rotate(self, rho): - """ - Rotates a vector of 2l+1 spherical harmonics (coefficients) according to the - rotation defined in the initialization. - rho: array - List of 2l+1 coefficients - Returns: - -------- - (2l+1) array containing the coefficients for the rotated structure - """ - - L = (rho.shape[-1] - 1) // 2 - return rho @ self._wddict[L].T - - def rotate_frame(self, frame, in_place=False): - """ - Utility function to also rotate a structure, given as an Atoms frame. - NB: it will rotate positions and cell, and no other array. - frame: ase.Atoms - An atomic structure in ASE format, that will be modified in place - in_frame: bool - Whether the frame should be copied or processed in place (defaults to False) - Returns: - ------- - The rotated frame. - """ - - if is_ase_Atoms(frame): - if in_place: - frame = frame.copy() - frame.positions = frame.positions @ self._rotation.T - frame.cell = frame.cell @ self._rotation.T - else: - if in_place: - frame = deepcopy(frame) - frame["positions"] = self._rotation @ frame["positions"] - frame["cell"] = self._rotation @ frame["cell"] - return frame - - def rotate_coeff_vector( - self, - frame: ase.Atoms, - coeffs: np.ndarray, - lmax: dict, - nmax: dict, - ) -> np.ndarray: - """ - Rotates the irreducible spherical components (ISCs) of basis set - coefficients in the spherical basis passed in as a flat vector. - - Given the basis set definition specified by ``lmax`` and ``nmax``, the - assumed ordering of basis function coefficients follows the following - hierarchy, which can be read as nested loops over the various indices. - Be mindful that some indices range are from 0 to x (exclusive) and - others from 0 to x + 1 (exclusive). The ranges reported below are - ordered. - - 1. Loop over atoms (index ``i``, of chemical species ``a``) in the - structure. ``i`` takes values 0 to N (** exclusive **), where N is the - number of atoms in the structure. - - 2. Loop over spherical harmonics channel (index ``l``) for each atom. - ``l`` takes values from 0 to ``lmax[a] + 1`` (** exclusive **), where - ``a`` is the chemical species of atom ``i``, given by the chemical - symbol at the ``i``th position of ``symbol_list``. - - 3. Loop over radial channel (index ``n``) for each atom ``i`` and - spherical harmonics channel ``l`` combination. ``n`` takes values from 0 - to ``nmax[(a, l)]`` (** exclusive **). - - 4. Loop over spherical harmonics component (index ``m``) for each atom. - ``m`` takes values from ``-l`` to ``l`` (** inclusive **). - - :param frame: the atomic structure in ASE format for which the - coefficients are defined. - :param coeffs: the coefficients in the spherical basis, as a flat - vector. - :param lmax: dict containing the maximum spherical harmonics (l) value - for each atom type. - :param nmax: dict containing the maximum radial channel (n) value for - each combination of atom type and l. - - :return: the rotated coefficients in the spherical basis, as a flat - vector with the same order as the input vector. - """ - # Initialize empty vector for storing the rotated ISCs - rot_vect = np.empty_like(coeffs) - - # Iterate over atomic species of the atoms in the frame - curr_idx = 0 - for symbol in frame.get_chemical_symbols(): - # Get the basis set lmax value for this species - sym_lmax = lmax[symbol] - for l in range(sym_lmax + 1): - # Get the number of radial functions for this species and l value - sym_l_nmax = nmax[(symbol, l)] - # Get the Wigner D Matrix for this l value - wig_mat = self._wddict[l].T - for n in range(sym_l_nmax): - # Retrieve the irreducible spherical component - isc = coeffs[curr_idx : curr_idx + (2 * l + 1)] - # Rotate the ISC and store - rot_isc = isc @ wig_mat - rot_vect[curr_idx : curr_idx + (2 * l + 1)][:] = rot_isc[:] - # Update the start index for the next ISC - curr_idx += 2 * l + 1 - - return rot_vect - - def rotate_tensorblock(self, l: int, block: TensorBlock) -> TensorBlock: - """ - Rotates a TensorBlock ``block``, represented in the spherical basis, - according to the Wigner D Real matrices for the given ``l`` value. - Assumes the components of the block are [("spherical_harmonics_m",),]. - """ - - # Get the Wigner matrix for this l value - wig = self._wddict[l].T - - # Copy the block - block_rotated = block.copy() - vals = block_rotated.values - - # Perform the rotation, either with numpy or torch, by taking the - # tensordot product of the irreducible spherical components. Modify in-place the - # values of the copied TensorBlock - if isinstance(vals, torch.Tensor): - wig = torch.tensor(wig) - block_rotated.values[:] = torch.tensordot( - vals.swapaxes(1, 2), wig, dims=1 - ).swapaxes(1, 2) - elif isinstance(block.values, np.ndarray): - block_rotated.values[:] = np.tensordot( - vals.swapaxes(1, 2), wig, axes=1 - ).swapaxes(1, 2) - else: - raise TypeError("TensorBlock values must be a numpy array or torch tensor.") - - return block_rotated - - def rotate_tensormap(self, tensor: TensorMap) -> TensorMap: - """ - Rotates a TensorMap usign Wigner D Matrices. Assumes the tensor keys has - a name "spherical_harmonics_l" that indicates the l value, and that each - block has exactly one component axis, named by - ("spherical_harmonics_m",). - """ - # Retrieve the key and the position of the l value in the key names - keys = tensor.keys - idx_l_value = keys.names.index("spherical_harmonics_l") - - # Iterate over the blocks and rotate - rotated_blocks = [] - for key in keys: - # Retrieve the l value - l = key[idx_l_value] - - # Rotate the block and store - rotated_blocks.append(self.rotate_tensorblock(l, tensor[key])) - - return TensorMap(keys, rotated_blocks) - - -# ===== helper functions for WignerDReal - - -def rotate_ase_frame(frame) -> Tuple[ase.Atoms, Tuple[float, float, float]]: - """ - Make a copy of the input ``frame``. Randomly rotates its xyz and cell - coordinates and returns the new frame, and euler angles alpha, beta, and - gamma. - """ - # Randomly generate euler angles between 0 and pi - alpha, beta, gamma = np.random.uniform(size=(3)) * np.pi - # Build cartesian rotation matrix - R = cartesian_rotation(alpha, beta, gamma) - # Copy the frame - rotated_frame = frame.copy() - # Rotate its positions and cell - rotated_frame.positions = rotated_frame.positions @ R.T - rotated_frame.cell = rotated_frame.cell @ R.T - - return rotated_frame, (alpha, beta, gamma) - - -def invert_ase_frame(frame) -> ase.Atoms: - """ - Make a copy of the input ``frame``. Randomly rotates its xyz and cell - coordinates and returns the new frame, and euler angles alpha, beta, and - gamma. - """ - # Copy the frame - inverted_frame = frame.copy() - # Invert the positions and cell - inverted_frame.positions = -1 * inverted_frame.positions @ R.T - inverted_frame.cell = -1 * inverted_frame.cell @ R.T - - return inverted_frame - - -def invert_tensormap(tensor: TensorMap) -> TensorMap: - """ - Takes a TensorMap in the spherical basis and perform a parity inversion. - This amounts to applying a factor of (-1) to all odd parity blocks, i.e. - those with key values of "order_nu" equal to -1. It also applies a - (potentially extra) factor of (-1) to blocks with odd l channel. - """ - new_blocks = [] - for key, block in tensor.items(): - multiplier = 1 - - if key["inversion_sigma"] == -1: - multiplier *= -1 - - if key["spherical_harmonics_l"] % 2 == 1: - multiplier *= -1 - - new_block = TensorBlock( - values=block.values * multiplier, - samples=block.samples, - components=block.components, - properties=block.properties, - ) - new_blocks.append(new_block) - - return TensorMap(tensor.keys, new_blocks) - - -def _wigner_d(l, alpha, beta, gamma): - """Computes a Wigner D matrix - D^l_{mm'}(alpha, beta, gamma) - from sympy and converts it to numerical values. - (alpha, beta, gamma) are Euler angles (radians, ZYZ convention) and l the irrep. - """ - try: - from sympy.physics.wigner import wigner_d - except ModuleNotFoundError: - raise ModuleNotFoundError( - "Calculation of Wigner D matrices requires a sympy installation" - ) - return np.complex128(wigner_d(l, alpha, beta, gamma)) - - -def cartesian_rotation(alpha, beta, gamma): - """A Cartesian rotation matrix in the appropriate convention - (ZYZ, implicit rotations) to be consistent with the common Wigner D definition. - (alpha, beta, gamma) are Euler angles (radians).""" - return Rotation.from_euler("ZYZ", [alpha, beta, gamma]).as_matrix() - - -def is_ase_Atoms(frame): - is_ase = True - if not hasattr(frame, "get_cell"): - is_ase = False - if not hasattr(frame, "get_positions"): - is_ase = False - if not hasattr(frame, "get_atomic_numbers"): - is_ase = False - if not hasattr(frame, "get_pbc"): - is_ase = False - return - - -def check_equivariance( - unrotated: TensorMap, - rotated: TensorMap, - lmax: int, - alpha: float, - beta: float, - gamma: float, - rtol: Optional[float] = 1e-15, - atol: Optional[float] = 1e-15, - n_checks_per_block: Optional[int] = None, -) -> bool: - """ - Checks equivariance by comparing the expansion coefficients of the - structural representations of an unrotated and rotated structure, rotating - the component vectors of the unrotated structure using a Wigner D-Matrix - constructed using parameters ``lmax``, ``alpha``, ``beta``, ``gamma``. - - If ``n_checks_per_block`` is passed (i.e. not None, the default), only this - number of (sample, property) combinations are checked per block. Otherwise, - all component vectors in every block are checked. - - :param unrotated: a TensorMap of the coefficients in the spherical basis for - the unrotated structure. - :param rotated: a TensorMap of the coefficients in the spherical basis for - the rotated structure. - :param lmax: the maximum l value for which the spherical basis is expanded. - :param alpha: the first Euler angle for the rotation between the unrotated - and rotated structure. - :param beta: the second Euler angle for the rotation between the unrotated - and rotated structure. - :param gamma: the third Euler angle for the rotation between the unrotated - and rotated structure. - :param rtol: the relative tolerance for the check. Default 1e-15. - :param atol: the absolute tolerance for the check. Default 1e-15. - :param n_checks_per_block: the number of comparisons between rotated and - unrotated structures to perform per block of the input TensorMaps. - - :return bool: True if the rotated and unrotated structures are exact rotated - forms of eachother in the spherical basis, within the defined - tolerances. False otherwise. - """ - equivariant = True - - # Check that the metadata is equivalent - equistore.equal_metadata(unrotated, rotated) - - # Build Wigner D-Matrices - wigner_d_matrices = WignerDReal(lmax, alpha, beta, gamma) - - # Check each block in turn - for key in rotated.keys: - # Access the blocks to compare - unr_block = unrotated[key] - rot_block = rotated[key] - - # Define the number of samples and properties - n_samples = len(unr_block.samples) - n_props = len(unr_block.properties) - - # If ``n_checks_per_block`` is passed, define a subset of samples and - # properties to check - samps_props = list(product(range(n_samples), range(n_props))) - samps_props = ( - samps_props - if n_checks_per_block is None - else samps_props[:n_checks_per_block] - ) - for sample_i, property_i in samps_props: - # Get the component vectors, each of length (2 \lambda + 1) - try: - unr_comp_vect = ( - unr_block.values[sample_i, ..., property_i].detach().numpy() - ) - rot_comp_vect = ( - rot_block.values[sample_i, ..., property_i].detach().numpy() - ) - except AttributeError: - unr_comp_vect = unr_block.values[sample_i, ..., property_i] - rot_comp_vect = rot_block.values[sample_i, ..., property_i] - - # Rotate the unrotated components vector with a wigner D-matrix - unr_comp_vect_rot = wigner_d_matrices.rotate(unr_comp_vect) - - # Check for exact (within a strict tolerance) equivalence - if not np.allclose(unr_comp_vect_rot, rot_comp_vect, rtol=rtol, atol=atol): - print( - f"block {key}, sample {unr_block.samples[sample_i]}," - + f" property {unr_block.properties[property_i]}, vectors" - + f" not equivariant: {unr_comp_vect_rot}, {rot_comp_vect}" - ) - equivariant = False - return equivariant - - -# ===== CleschGordanReal class - - -class ClebschGordanReal: - def __init__(self, l_max): - self._l_max = l_max - self._cg = {} - - # real-to-complex and complex-to-real transformations as matrices - r2c = {} - c2r = {} - for L in range(0, self._l_max + 1): - r2c[L] = _real2complex(L) - c2r[L] = np.conjugate(r2c[L]).T - - for l1 in range(self._l_max + 1): - for l2 in range(self._l_max + 1): - for L in range( - max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1 - ): - complex_cg = _complex_clebsch_gordan_matrix(l1, l2, L) - - real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( - complex_cg.shape - ) - - real_cg = real_cg.swapaxes(0, 1) - real_cg = (r2c[l2].T @ real_cg.reshape(2 * l2 + 1, -1)).reshape( - real_cg.shape - ) - real_cg = real_cg.swapaxes(0, 1) - - real_cg = real_cg @ c2r[L].T - - if (l1 + l2 + L) % 2 == 0: - rcg = np.real(real_cg) - else: - rcg = np.imag(real_cg) - - new_cg = [] - for M in range(2 * L + 1): - cg_nonzero = np.where(np.abs(rcg[:, :, M]) > 1e-15) - cg_M = np.zeros( - len(cg_nonzero[0]), - dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], - ) - cg_M["m1"] = cg_nonzero[0] - cg_M["m2"] = cg_nonzero[1] - cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], M] - new_cg.append(cg_M) - - self._cg[(l1, l2, L)] = new_cg - - def combine(self, rho1, rho2, L): - # automatically infer l1 and l2 from the size of the coefficients vectors - l1 = (rho1.shape[1] - 1) // 2 - l2 = (rho2.shape[1] - 1) // 2 - if L > self._l_max or l1 > self._l_max or l2 > self._l_max: - raise ValueError("Requested CG entry has not been precomputed") - - n_items = rho1.shape[0] - n_features = rho1.shape[2] - if rho1.shape[0] != rho2.shape[0] or rho1.shape[2] != rho2.shape[2]: - raise IndexError("Cannot combine differently-shaped feature blocks") - - rho = np.zeros((n_items, 2 * L + 1, n_features)) - if (l1, l2, L) in self._cg: - for M in range(2 * L + 1): - for m1, m2, cg in self._cg[(l1, l2, L)][M]: - rho[:, M] += rho1[:, m1, :] * rho2[:, m2, :] * cg - - return rho - - def combine_einsum(self, rho1, rho2, L, combination_string): - # automatically infer l1 and l2 from the size of the coefficients vectors - l1 = (rho1.shape[1] - 1) // 2 - l2 = (rho2.shape[1] - 1) // 2 - if L > self._l_max or l1 > self._l_max or l2 > self._l_max: - raise ValueError( - "Requested CG entry ", (l1, l2, L), " has not been precomputed" - ) - - n_items = rho1.shape[0] - if rho1.shape[0] != rho2.shape[0]: - raise IndexError( - "Cannot combine feature blocks with different number of items" - ) - - # infers the shape of the output using the einsum internals - features = np.einsum(combination_string, rho1[:, 0, ...], rho2[:, 0, ...]).shape - rho = np.zeros((n_items, 2 * L + 1) + features[1:]) - - if (l1, l2, L) in self._cg: - for M in range(2 * L + 1): - for m1, m2, cg in self._cg[(l1, l2, L)][M]: - rho[:, M, ...] += np.einsum( - combination_string, rho1[:, m1, ...], rho2[:, m2, ...] * cg - ) - - return rho - - def couple(self, decoupled, iterate=0): - """ - Goes from an uncoupled product basis to a coupled basis. A - (2l1+1)x(2l2+1) matrix transforming like the outer product of Y^m1_l1 - Y^m2_l2 can be rewritten as a list of coupled vectors, each transforming - like a Y^L irrep. - - The process can be iterated: a D dimensional array that is the product - of D Y^m_l can be turned into a set of multiple terms transforming as a - single Y^M_L. - - decoupled: array or dict - (...)x(2l1+1)x(2l2+1) array containing coefficients that transform - like products of Y^l1 and Y^l2 harmonics. can also be called on a - array of higher dimensionality, in which case the result will - contain matrices of entries. If the further index also correspond to - spherical harmonics, the process can be iterated, and couple() can - be called onto its output, in which case the decoupling is applied - to each entry. - - iterate: int - calls couple iteratively the given number of times. equivalent to - couple(couple(... couple(decoupled))) - - Returns: - -------- - A dictionary tracking the nature of the coupled objects. When called one - time, it returns a dictionary containing (l1, l2) [the coefficients of - the parent Ylm] which in turns is a dictionary of coupled terms, in the - form L:(...)x(2L+1)x(...) array. When called multiple times, it applies - the coupling to each term, and keeps track of the additional l terms, so - that e.g. when called with iterate=1 the return dictionary contains - terms of the form (l3,l4,l1,l2) : { L: array } - - - Note that this coupling scheme is different from the NICE-coupling where - angular momenta are coupled from left to right as (((l1 l2) l3) l4)... ) - Thus results may differ when combining more than two angular channels. - """ - - coupled = {} - - # when called on a matrix, turns it into a dict form to which we can - # apply the generic algorithm - if not isinstance(decoupled, dict): - l2 = (decoupled.shape[-1] - 1) // 2 - decoupled = {(): {l2: decoupled}} - - # runs over the tuple of (partly) decoupled terms - for ltuple, lcomponents in decoupled.items(): - # each is a list of L terms - for lc in lcomponents.keys(): - # this is the actual matrix-valued coupled term, - # of shape (..., 2l1+1, 2l2+1), transforming as Y^m1_l1 Y^m2_l2 - dec_term = lcomponents[lc] - l1 = (dec_term.shape[-2] - 1) // 2 - l2 = (dec_term.shape[-1] - 1) // 2 - - # there is a certain redundance: the L value is also the last entry - # in ltuple - if lc != l2: - raise ValueError( - "Inconsistent shape for coupled angular momentum block." - ) - - # in the new coupled term, prepend (l1,l2) to the existing label - coupled[(l1, l2) + ltuple] = {} - for L in range( - max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1 - ): - Lterm = np.zeros(shape=dec_term.shape[:-2] + (2 * L + 1,)) - for M in range(2 * L + 1): - for m1, m2, cg in self._cg[(l1, l2, L)][M]: - Lterm[..., M] += dec_term[..., m1, m2] * cg - coupled[(l1, l2) + ltuple][L] = Lterm - - # repeat if required - if iterate > 0: - coupled = self.couple(coupled, iterate - 1) - return coupled - - def decouple(self, coupled, iterate=0): - """ - Undoes the transformation enacted by couple. - """ - - decoupled = {} - # applies the decoupling to each entry in the dictionary - for ltuple, lcomponents in coupled.items(): - # the initial pair in the key indicates the decoupled terms that generated - # the L entries - l1, l2 = ltuple[:2] - - # shape of the coupled matrix (last entry is the 2L+1 M terms) - shape = next(iter(lcomponents.values())).shape[:-1] - - dec_term = np.zeros( - shape - + ( - 2 * l1 + 1, - 2 * l2 + 1, - ) - ) - for L in range(max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1): - # supports missing L components, e.g. if they are zero because of symmetry - if not L in lcomponents: - continue - for M in range(2 * L + 1): - for m1, m2, cg in self._cg[(l1, l2, L)][M]: - dec_term[..., m1, m2] += cg * lcomponents[L][..., M] - # stores the result with a key that drops the l's we have just decoupled - if not ltuple[2:] in decoupled: - decoupled[ltuple[2:]] = {} - decoupled[ltuple[2:]][l2] = dec_term - - # rinse, repeat - if iterate > 0: - decoupled = self.decouple(decoupled, iterate - 1) - - # if we got a fully decoupled state, just return an array - if ltuple[2:] == (): - decoupled = next(iter(decoupled[()].values())) - return decoupled - - -# ===== helper functions for ClebschGordanReal - - -def _real2complex(L): - """ - Computes a matrix that can be used to convert from real to complex-valued - spherical harmonics(coefficients) of order L. - - It's meant to be applied to the left, ``real2complex @ [-L..L]``. - """ - result = np.zeros((2 * L + 1, 2 * L + 1), dtype=np.complex128) - - I_SQRT_2 = 1.0 / np.sqrt(2) - - for m in range(-L, L + 1): - if m < 0: - result[L - m, L + m] = I_SQRT_2 * 1j * (-1) ** m - result[L + m, L + m] = -I_SQRT_2 * 1j - - if m == 0: - result[L, L] = 1.0 - - if m > 0: - result[L + m, L + m] = I_SQRT_2 * (-1) ** m - result[L - m, L + m] = I_SQRT_2 - - return result - - -I_SQRT_2 = 1.0 / np.sqrt(2) -SQRT_2 = np.sqrt(2) - - -def _r2c(sp): - """Real to complex SPH. Assumes a block with 2l+1 reals corresponding - to real SPH with m indices from -l to +l""" - - l = (len(sp) - 1) // 2 # infers l from the vector size - rc = np.zeros(len(sp), dtype=np.complex128) - rc[l] = sp[l] - for m in range(1, l + 1): - rc[l + m] = (sp[l + m] + 1j * sp[l - m]) * I_SQRT_2 * (-1) ** m - rc[l - m] = (sp[l + m] - 1j * sp[l - m]) * I_SQRT_2 - return rc - - -def _complex_clebsch_gordan_matrix(l1, l2, L): - if np.abs(l1 - l2) > L or np.abs(l1 + l2) < L: - return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * L + 1), dtype=np.double) - else: - return wigners.clebsch_gordan_array(l1, l2, L) - - -# ======== Fxns used to perform CG iterations - - -def acdc_standardize_keys(descriptor): - """ - Standardize the naming scheme of density expansion coefficient blocks - (nu=1) - """ - - key_names = descriptor.keys.names - if not "spherical_harmonics_l" in key_names: - raise ValueError( - "Descriptor missing spherical harmonics channel key `spherical_harmonics_l`" - ) - blocks = [] - keys = [] - for key, block in descriptor.items(): - key = tuple(key) - if not "inversion_sigma" in key_names: - key = (1,) + key - if not "order_nu" in key_names: - key = (1,) + key - keys.append(key) - property_names = _remove_suffix(block.properties.names, "_1") - blocks.append( - TensorBlock( - values=block.values, - samples=block.samples, - components=block.components, - properties=Labels(property_names, block.properties.values), - ) - ) - - if not "inversion_sigma" in key_names: - key_names = ["inversion_sigma"] + key_names - if not "order_nu" in key_names: - key_names = ["order_nu"] + key_names - - return TensorMap( - keys=Labels(names=key_names, values=np.asarray(keys, dtype=np.int32)), - blocks=blocks, - ) - - -def cg_combine( - x_a, - x_b, - feature_names=None, - clebsch_gordan=None, - lcut=None, - other_keys_match=None, -): - """ - Performs a CG product of two sets of equivariants. Only requirement is that - sparse indices are labeled as ("inversion_sigma", "spherical_harmonics_l", - "order_nu"). The automatically-determined naming of output features can be - overridden by giving a list of "feature_names". By defaults, all other key - labels are combined in an "outer product" mode, i.e. if there is a key-side - neighbor_species in both x_a and x_b, the returned keys will have two - neighbor_species labels, corresponding to the parent features. By providing - a list `other_keys_match` of keys that should match, these are not - outer-producted, but combined together. for instance, passing `["species - center"]` means that the keys with the same species center will be combined - together, but yield a single key with the same species_center in the - results. - """ - - # determines the cutoff in the new features - lmax_a = max(x_a.keys["spherical_harmonics_l"]) - lmax_b = max(x_b.keys["spherical_harmonics_l"]) - if lcut is None: - lcut = lmax_a + lmax_b - - # creates a CG object, if needed - if clebsch_gordan is None: - clebsch_gordan = ClebschGordanReal(lcut) - - other_keys_a = tuple( - name - for name in x_a.keys.names - if name not in ["spherical_harmonics_l", "order_nu", "inversion_sigma"] - ) - other_keys_b = tuple( - name - for name in x_b.keys.names - if name not in ["spherical_harmonics_l", "order_nu", "inversion_sigma"] - ) - - if other_keys_match is None: - OTHER_KEYS = [k + "_a" for k in other_keys_a] + [k + "_b" for k in other_keys_b] - else: - OTHER_KEYS = ( - other_keys_match - + [ - k + ("_a" if k in other_keys_b else "") - for k in other_keys_a - if k not in other_keys_match - ] - + [ - k + ("_b" if k in other_keys_a else "") - for k in other_keys_b - if k not in other_keys_match - ] - ) - - # we assume grad components are all the same - if x_a.block(0).has_gradient("positions"): - grad_components = x_a.block(0).gradient("positions").components - else: - grad_components = None - - # automatic generation of the output features names - # "x1 x2 x3 ; x1 x2 -> x1_a x2_a x3_a k_nu x1_b x2_b l_nu" - if feature_names is None: - NU = x_a.keys[0]["order_nu"] + x_b.keys[0]["order_nu"] - feature_names = ( - tuple(n + "_a" for n in x_a.block(0).properties.names) - + ("k_" + str(NU),) - + tuple(n + "_b" for n in x_b.block(0).properties.names) - + ("l_" + str(NU),) - ) - - X_idx = {} - X_blocks = {} - X_samples = {} - X_grad_samples = {} - X_grads = {} - - # loops over sparse blocks of x_a - for index_a, block_a in x_a.items(): - lam_a = index_a["spherical_harmonics_l"] - sigma_a = index_a["inversion_sigma"] - order_a = index_a["order_nu"] - properties_a = ( - block_a.properties - ) # pre-extract this block as accessing a c property has a non-zero cost - samples_a = block_a.samples - - # and x_b - for index_b, block_b in x_b.items(): - lam_b = index_b["spherical_harmonics_l"] - sigma_b = index_b["inversion_sigma"] - order_b = index_b["order_nu"] - properties_b = block_b.properties - samples_b = block_b.samples - - if other_keys_match is None: - OTHERS = tuple(index_a[name] for name in other_keys_a) + tuple( - index_b[name] for name in other_keys_b - ) - else: - OTHERS = tuple( - index_a[k] for k in other_keys_match if index_a[k] == index_b[k] - ) - # skip combinations without matching key - if len(OTHERS) < len(other_keys_match): - continue - # adds non-matching keys to build outer product - OTHERS = OTHERS + tuple( - index_a[k] for k in other_keys_a if k not in other_keys_match - ) - OTHERS = OTHERS + tuple( - index_b[k] for k in other_keys_b if k not in other_keys_match - ) - - if "neighbor" in samples_b.names and "neighbor" not in samples_a.names: - # we hard-code a combination method where b can be a pair descriptor. this needs some work to be general and robust - # note also that this assumes that structure, center are ordered in the same way in the centred and neighbor descriptors - neighbor_slice = [] - smp_a, smp_b = 0, 0 - while smp_b < samples_b.shape[0]: - if samples_b[smp_b][["structure", "center"]] != samples_a[smp_a]: - smp_a += 1 - neighbor_slice.append(smp_a) - smp_b += 1 - neighbor_slice = np.asarray(neighbor_slice) - else: - neighbor_slice = slice(None) - - # determines the properties that are in the select list - sel_feats = [] - sel_idx = [] - sel_feats = ( - np.indices((len(properties_a), len(properties_b))).reshape(2, -1).T - ) - - prop_ids_a = [] - prop_ids_b = [] - for n_a, f_a in enumerate(properties_a): - prop_ids_a.append(tuple(f_a) + (lam_a,)) - for n_b, f_b in enumerate(properties_b): - prop_ids_b.append(tuple(f_b) + (lam_b,)) - prop_ids_a = np.asarray(prop_ids_a) - prop_ids_b = np.asarray(prop_ids_b) - sel_idx = np.hstack( - [prop_ids_a[sel_feats[:, 0]], prop_ids_b[sel_feats[:, 1]]] - ) - if len(sel_feats) == 0: - continue - # loops over all permissible output blocks. note that blocks will - # be filled from different la, lb - for L in range(np.abs(lam_a - lam_b), 1 + min(lam_a + lam_b, lcut)): - # determines parity of the block - S = sigma_a * sigma_b * (-1) ** (lam_a + lam_b + L) - NU = order_a + order_b - KEY = ( - NU, - S, - L, - ) + OTHERS - if not KEY in X_idx: - X_idx[KEY] = [] - X_blocks[KEY] = [] - X_samples[KEY] = block_b.samples - if grad_components is not None: - X_grads[KEY] = [] - X_grad_samples[KEY] = block_b.gradient("positions").samples - - # builds all products in one go - one_shot_blocks = clebsch_gordan.combine_einsum( - block_a.values[neighbor_slice][:, :, sel_feats[:, 0]], - block_b.values[:, :, sel_feats[:, 1]], - L, - combination_string="iq,iq->iq", - ) - # do gradients, if they are present... - if grad_components is not None: - grad_a = block_a.gradient("positions") - grad_b = block_b.gradient("positions") - grad_a_data = np.swapaxes(grad_a.data, 1, 2) - grad_b_data = np.swapaxes(grad_b.data, 1, 2) - one_shot_grads = clebsch_gordan.combine_einsum( - block_a.values[grad_a.samples["sample"]][ - neighbor_slice, :, sel_feats[:, 0] - ], - grad_b_data[..., sel_feats[:, 1]], - L=L, - combination_string="iq,iaq->iaq", - ) + clebsch_gordan.combine_einsum( - block_b.values[grad_b.samples["sample"]][:, :, sel_feats[:, 1]], - grad_a_data[neighbor_slice, ..., sel_feats[:, 0]], - L=L, - combination_string="iq,iaq->iaq", - ) - - # now loop over the selected features to build the blocks - - X_idx[KEY].append(sel_idx) - X_blocks[KEY].append(one_shot_blocks) - if grad_components is not None: - X_grads[KEY].append(one_shot_grads) - - # turns data into sparse storage format (and dumps any empty block in the - # process) - nz_idx = [] - nz_blk = [] - for KEY in X_blocks: - L = KEY[2] - # create blocks - if len(X_blocks[KEY]) == 0: - continue # skips empty blocks - nz_idx.append(KEY) - block_data = np.concatenate(X_blocks[KEY], axis=-1) - sph_components = Labels( - ["spherical_harmonics_m"], - np.asarray(range(-L, L + 1), dtype=np.int32).reshape(-1, 1), - ) - newblock = TensorBlock( - # feature index must be last - values=block_data, - samples=X_samples[KEY], - components=[sph_components], - properties=Labels( - feature_names, np.asarray(np.vstack(X_idx[KEY]), dtype=np.int32) - ), - ) - if grad_components is not None: - grad_data = np.swapaxes(np.concatenate(X_grads[KEY], axis=-1), 2, 1) - newblock.add_gradient( - "positions", - data=grad_data, - samples=X_grad_samples[KEY], - components=[grad_components[0], sph_components], - ) - nz_blk.append(newblock) - X = TensorMap( - Labels( - ["order_nu", "inversion_sigma", "spherical_harmonics_l"] + OTHER_KEYS, - np.asarray(nz_idx, dtype=np.int32), - ), - nz_blk, - ) - return X - - -def cg_increment( - x_nu, - x_1, - clebsch_gordan=None, - lcut=None, - other_keys_match=None, -): - """Specialized version of the CG product to perform iterations with nu=1 features""" - - nu = x_nu.keys["order_nu"][0] - - feature_roots = _remove_suffix(x_1.block(0).properties.names) - - if nu == 1: - feature_names = ( - [root + "_1" for root in feature_roots] - + ["l_1"] - + [root + "_2" for root in feature_roots] - + ["l_2"] - ) - else: - feature_names = ( - [x_nu.block(0).properties.names] - + ["k_" + str(nu + 1)] - + [root + "_" + str(nu + 1) for root in feature_roots] - + ["l_" + str(nu + 1)] - ) - - return cg_combine( - x_nu, - x_1, - feature_names=feature_names, - clebsch_gordan=clebsch_gordan, - lcut=lcut, - other_keys_match=other_keys_match, - ) - - -def _remove_suffix(names, new_suffix=""): - suffix = re.compile("_[0-9]?$") - rname = [] - for name in names: - match = suffix.search(name) - if match is None: - rname.append(name + new_suffix) - else: - rname.append(name[: match.start()] + new_suffix) - return rname - - -def lambda_soap_vector( - frames: list, - rascal_hypers: dict, - lambdas: Sequence[int], - lambda_cut: Optional[int] = None, - selected_samples: Optional[Labels] = None, - neighbor_species: Optional[Sequence[int]] = None, - even_parity_only: bool = False, -) -> TensorMap: - """ - Takes a list of frames of ASE loaded frames and a dict of Rascaline - hyperparameters and generates a lambda-SOAP (i.e. nu=2) representation. - - Passing a subset of samples in `selected_samples` can be used to, for - instance, only calculate the features for a subset of the strutcures passed - in `frames`. For instance: `selected_samples = Labels(names=["structure"], - values[4, 5, 6])` will only calculate the lambda-features for structures - indexed by 4, 5, 6. - - :param frames: a list of structures generated by the ase.io function. - :param rascal_hypers: a dict of hyperparameters used to calculate the atom - density correlation calculated with rascaline SphericalExpansion - :param lambda_cut: an int of the maximum lambda value to compute - combinations for. If none, the 'max_angular' value in `rascal_hypers` - will be used instead. - :param selected_samples: a Labels object that defines which samples, as a - subset of the total samples in `frames` (i.e. atomic environments or - structures) to perform the calculation on. - :param neighbor_species: a list of int that correspond to the atomic charges - of all the neighbour species that you want to be in your properties (or - features) dimension. This list may contain charges for atoms that don't - appear in ``frames``, but are included anyway so that the one can - enforce consistent properties dimension size with other lambda-feature - vectors. - :param even_parity_only: a bool that determines whether to only include the - key/block pairs with even parity under rotation, i.e. sigma = +1. - Defaults to false, where both parities are included. - :param save_dir: a str of the absolute path to the directory where the - TensorMap of the calculated lambda-SOAP representation and pickled - ``rascal_hypers`` dict should be written. If none, the TensorMap will not be - saved. - - :return: a TensorMap of the lambda-SOAP feature vector for the selected - samples of the input frames. - """ - # Generate Rascaline spherical expansion - calculator = rascaline.SphericalExpansion(**rascal_hypers) - if lambda_cut is None: - lambda_cut = 2 * rascal_hypers["max_angular"] - else: - if lambda_cut > 2 * rascal_hypers["max_angular"]: - raise ValueError( - "As this function generates 2-body features (nu=2), " - "`lambda_cut` must be <= 2 x rascal_hypers['max_angular'] " - f"`rascal_hypers`. Received {lambda_cut}." - ) - # Pre-calculate ClebschGordan coefficients - cg = ClebschGordanReal(l_max=lambda_cut) - - # Generate descriptor via Spherical Expansion - nu1 = calculator.compute(frames, selected_samples=selected_samples) - - # nu=1 features - nu1 = acdc_standardize_keys(nu1) - - # Move "species_neighbor" sparse keys to properties with enforced atom - # charges if ``neighbor_species`` is specified. This is required as the CG - # iteration code currently does not handle neighbour species padding - # automatically. - keys_to_move = "species_neighbor" - if neighbor_species is not None: - keys_to_move = Labels( - names=(keys_to_move,), - values=np.array(neighbor_species).reshape(-1, 1), - ) - nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move) - - # Combined nu=1 features to generate nu=2 features. lambda-SOAP is defined - # as just the nu=2 features. - lsoap = cg_increment( - nu1, - nu1, - clebsch_gordan=cg, - lcut=lambda_cut, - other_keys_match=["species_center"], - ) - - # Clean the lambda-SOAP TensorMap. Drop the order_nu key name as this is by - # definition 2 for all keys. - lsoap = equistore.remove_dimension(lsoap, axis="keys", name="order_nu") - - # Drop all odd parity keys/blocks - if even_parity_only: - keys_to_drop = Labels( - names=lsoap.keys.names, - values=lsoap.keys.values[lsoap.keys.column("inversion_sigma") == -1], - ) - lsoap = equistore.drop_blocks(lsoap, keys=keys_to_drop) - - # Drop the inversion_sigma key name as this is now +1 for all blocks - lsoap = equistore.remove_dimension(lsoap, axis="keys", name="inversion_sigma") - - # Drop all blocks that don't correspond to the target lambdas - keys_to_drop = Labels( - names=lsoap.keys.names, - values=lsoap.keys.values[[v not in lambdas for v in lsoap.keys.column("spherical_harmonics_l")]], - ) - lsoap = equistore.drop_blocks(lsoap, keys=keys_to_drop) - - return lsoap From 4b6a9c20c7ac6f9adbd0a945b6c8f7feb499450d Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 2 Aug 2023 19:07:40 +0200 Subject: [PATCH 43/96] lambda-SOAP wrapper, allow for parity filter on final iteration. --- .../rascaline/utils/clebsch_gordan.py | 145 +++++++++++-- .../rascaline/utils/old_clebsch_gordan.py | 6 +- python/rascaline/rascaline/utils/tmp.ipynb | 204 +++++++++--------- 3 files changed, 236 insertions(+), 119 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index 9a7a1046a..3d38ff8b7 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -2,7 +2,7 @@ Module for computing Clebsch-gordan iterations with equistore TensorMaps. """ import itertools -from typing import Optional, Sequence, Tuple +from typing import Optional, Sequence, Tuple, Union import ase import numpy as np @@ -212,15 +212,77 @@ def _combine_multi_centers_block_pair( # ===== Fxns for combining single center descriptors ===== +def lambda_soap_vector( + frames: Sequence[ase.Atoms], + rascal_hypers: dict, + lambdas: Sequence[int], + lambda_cut: Optional[int] = None, + only_keep_parity: Optional[Union[int, dict]] = None, + selected_samples: Optional[Labels] = None, + species_neighbors: Optional[Sequence[int]] = None, +) -> TensorMap: + """ + A higher-level wrapper for the :py:func:`n_body_iteration_single_center` + function specifically for generating lambda-SOAP vectors in the equistore + format, with some added metadata manipulation. + + The hyperparameters `rascal_hypers` are used to generate a nu=1 + SphericalExpansion object with rascaline, and these are then combined with a + single Clebsch-Gordan iteration step to form the nu=2 lambda-SOAP + descriptor. Only the target spherical channels given in `lambdas` are + calculated and returned. + + `lambda_cut` can be set to reduce the memory overhead of the calculation, at + the cost of loss of information. The theoretical maximum (and default) value + is nu_target * rascal_hypers["max_angular"], though a lower value can be + set. `nu_target` is the target body-order of the descriptor (by definition + nu=2 for lambda-SOAP). Using the default (and theoretical maximum) value can + lead to memory blow-up for large systems and hgih body-orders, so this value + needs to be tailored for the computation and system. Note that truncating + this value to less than the default will lead to some information loss. + + If `only_keep_parity` is passed, then only the specified parities are + returned in the output TensorMap. For instance, passing as an int +1 means + only blocks with even parity will be returned. If a dict, the parities kept + for each target lambda can be specified. Any lambdasIf false, all blocks of both odd + and even parity are returned. In the latter case, the output TensorMap will + have a key dimension "inversion_sigma" that tracks the parity. + """ + # Generate lambda-SOAP using rascaline.utils + lsoap = n_body_iteration_single_center( + frames, + rascal_hypers=rascal_hypers, + nu_target=2, + lambdas=lambdas, + lambda_cut=lambda_cut, + selected_samples=selected_samples, + species_neighbors=species_neighbors, + only_keep_parity=only_keep_parity, + use_sparse=True, + ) + + # Drop the redundant key name "order_nu". This is by definition 2 for all + # lambda-SOAP blocks. + lsoap = equistore.remove_dimension(lsoap, axis="keys", name="order_nu") + + # If a single parity is requested, drop the now redundant "inversion_sigma" + # key name + if isinstance(only_keep_parity, int): + lsoap = equistore.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + + return lsoap + + def n_body_iteration_single_center( frames: Sequence[ase.Atoms], rascal_hypers: dict, nu_target: int, lambdas: Sequence[int], - use_sparse: bool = True, lambda_cut: Optional[int] = None, selected_samples: Optional[Labels] = None, species_neighbors: Optional[Sequence[int]] = None, + only_keep_parity: Optional[Union[int, dict]] = None, + use_sparse: bool = True, ) -> TensorMap: """ Based on the passed ``rascal_hypers``, generates a rascaline @@ -236,17 +298,38 @@ def n_body_iteration_single_center( # Set default lambda_cut if not passed if lambda_cut is None: # WARNING: the default is the maximum possible angular order for the - # given hypers and target body order. Memory exoplosion possible! + # given hypers and target body order. Memory explosion possible! # TODO: better default value here? - lambda_cut = nu_target * np.max(rascal_hypers["max_angular"]) + lambda_cut = nu_target * rascal_hypers["max_angular"] # Check `lambda_cut` is valid - if lambda_cut > nu_target * np.max(rascal_hypers["max_angular"]): + if not ( + rascal_hypers["max_angular"] + <= lambda_cut + <= nu_target * rascal_hypers["max_angular"] + ): + raise ValueError( + "`lambda_cut` cannot be more than `nu_target` * `rascal_hypers['max_angular']`" + " or less than `rascal_hypers['max_angular']`" + ) + + # Check `lambdas` are valid + if not (np.min(lambdas) >= 0 and np.max(lambdas) <= lambda_cut): raise ValueError( - "`lambda_cut` must be less than `nu_target` * `rascal_hypers['max_angular']`" + "All `lambdas` must be >= 0 and <= `lambda_cut`" ) - # Define the cached CG coefficients - currently only sparse CG matrices implemented + # Check `only_keep_parity` is valid + if isinstance(only_keep_parity, int): + if only_keep_parity not in [None, -1, 1]: + raise ValueError( + "If passing `only_keep_parity` as int, it must be -1 or +1" + ) + elif isinstance(only_keep_parity, dict): + # TODO: implement parity to keep by l channel + raise NotImplementedError("parity to keep by l channel not yet implemented.") + + # Define the cached CG coefficients, either as sparse dicts or dense arrays cg_cache = ClebschGordanReal(lambda_cut, use_sparse) # Generate a rascaline SphericalExpansion, for only the selected samples if @@ -266,9 +349,6 @@ def n_body_iteration_single_center( ) nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) - # # Standardize the key names metadata - # nu1_tensor = _add_nu_sigma_to_key_names(nu1_tensor) - # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 nu1_tensor = equistore.insert_dimension( nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 @@ -280,14 +360,26 @@ def n_body_iteration_single_center( # Create a copy of the nu = 1 TensorMap to combine with itself combined_tensor = nu1_tensor.copy() - # Iteratively combine untilt the target body order is reached - for _ in range(1, nu_target): + # Iteratively combine until the target body order is reached + n_iterations = nu_target - 1 + for iteration in range(1, n_iterations + 1): + # If we want to filter based on parity, only do so on the final CG + # iteration + keep_parity = None + if iteration < n_iterations: + keep_parity = None + else: + assert iteration == n_iterations + keep_parity = only_keep_parity + + # Perform a CG iteration step combined_tensor = _combine_single_center( tensor_1=combined_tensor, tensor_2=nu1_tensor, lambdas=lambdas, cg_cache=cg_cache, use_sparse=use_sparse, + only_keep_parity=keep_parity, ) # TODO: Account for body-order multiplicity and normalize block values @@ -310,6 +402,7 @@ def _combine_single_center( lambdas: Sequence[int], cg_cache, use_sparse: bool = True, + only_keep_parity: Optional[int] = None, ) -> TensorMap: """ For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and 1 @@ -359,7 +452,7 @@ def _combine_single_center( keys_1_entries, keys_2_entries, multiplicity_list, - ) = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas) + ) = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas, only_keep_parity) # Iterate over pairs of blocks and combine combined_blocks = [] @@ -408,7 +501,7 @@ def _combine_single_center_block_pair( # Infer the new nu value: block 1's properties are nu pairs of # "species_neighbor_x" and "nx". combined_nu = int((len(block_1.properties.names) / 2) + 1) - + # Define the new property names for "nx" and "species_neighbor_x" n_names = [f"n{i}" for i in range(1, combined_nu + 1)] neighbor_names = [f"species_neighbor_{i}" for i in range(1, combined_nu + 1)] @@ -614,7 +707,10 @@ def _normalize_blocks(tensor: TensorMap) -> TensorMap: def _create_combined_keys( - keys_1: Labels, keys_2: Labels, lambdas: Sequence[int] + keys_1: Labels, + keys_2: Labels, + lambdas: Sequence[int], + only_keep_parity: Optional[Union[int, dict]] = None, ) -> Tuple[Labels, Sequence[Sequence[int]]]: """ Given the keys of 2 TensorMaps and a list of desired lambda values, creates @@ -658,6 +754,13 @@ def _create_combined_keys( values of the blocks that combine to form the block indexed by the corresponding key. The correction_factor terms are the prefactors that account for the redundancy in the CG combination. + + The `only_keep_parity` argument can be used to return only keys with a + certain parity. Passing as None (default) will return both +1 and -1 parity + keys. Passing, for instance, `only_keep_parity=+1` will only return keys + with even parity. Warning: this should be used with caution if performing + multiple CG combination steps. Typically this functionality should only be + used in the last CG combination step. """ # Get the body order of the first TensorMap. nu1 = np.unique(keys_1.column("order_nu"))[0] @@ -691,6 +794,14 @@ def _create_combined_keys( == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] ) + # Check `only_keep_parity` argument + if only_keep_parity is None: + only_keep_parity = [+1, -1] + else: + assert isinstance(only_keep_parity, int) + assert only_keep_parity in [+1, -1] + only_keep_parity = [only_keep_parity] + # Define key names of output Labels (i.e. for combined TensorMap) new_names = ( ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] @@ -719,6 +830,10 @@ def _create_combined_keys( # Calculate new sigma sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) + # Skip keys that don't give the desired parity + if sig not in only_keep_parity: + continue + # Extract the l and k lists from keys_1 l_list = key_1.values[4 : 4 + nu1].tolist() k_list = key_1.values[4 + nu1 :].tolist() diff --git a/python/rascaline/rascaline/utils/old_clebsch_gordan.py b/python/rascaline/rascaline/utils/old_clebsch_gordan.py index 320dd0989..bb91c043d 100644 --- a/python/rascaline/rascaline/utils/old_clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/old_clebsch_gordan.py @@ -670,20 +670,20 @@ def lambda_soap_vector( samples of the input frames. """ # Generate Rascaline spherical expansion - calculator = rascaline.SphericalExpansion(**rascal_hypers) if lambda_cut is None: lambda_cut = 2 * rascal_hypers["max_angular"] else: if lambda_cut > 2 * rascal_hypers["max_angular"]: raise ValueError( "As this function generates 2-body features (nu=2), " - "`lambda_cut` must be <= 2 x rascal_hypers['max_angular'] " - f"`rascal_hypers`. Received {lambda_cut}." + "`lambda_cut` cannot be more than 2 x rascal_hypers['max_angular'] " + f"or less than rascal_hypers['max_angular']. Received {lambda_cut}." ) # Pre-calculate ClebschGordan coefficients cg = ClebschGordanReal(l_max=lambda_cut) # Generate descriptor via Spherical Expansion + calculator = rascaline.SphericalExpansion(**rascal_hypers) nu1 = calculator.compute(frames, selected_samples=selected_samples) # nu=1 features diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index 9bd9fb4f6..232eaa405 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -19,7 +19,8 @@ "\n", "# from rascaline.utils import clebsch_gordan\n", "import clebsch_gordan\n", - "import spherical" + "import old_clebsch_gordan\n", + "import rotations" ] }, { @@ -60,7 +61,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Random rotation angles (rad): 1.4521811579492145 2.2229088215428923 1.1400953735068622\n", + "Random rotation angles (rad): [2.71801454 5.20220183 3.836667 ]\n", "SO(3) EQUIVARIANT!\n" ] } @@ -72,10 +73,13 @@ "# Pick a test frame\n", "frame = frames[0]\n", "\n", + "# Generate Wigner-D matrices, initialized with random angles\n", + "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", + "print(\"Random rotation angles (rad):\", wig.angles)\n", + "\n", "# Randomly rigidly rotate the frame\n", - "frame_rot, (a, b, c) = spherical.rotate_ase_frame(frame)\n", - "print(\"Random rotation angles (rad):\", a, b, c)\n", - "assert not np.all(frame.positions == frame_rot.positions)\n", + "frame_so3 = rotations.transform_frame_so3(frame, wig.angles)\n", + "assert not np.allclose(frame.positions, frame_so3.positions)\n", "\n", "# Generate nu=3 descriptor for both frames\n", "nu3 = clebsch_gordan.n_body_iteration_single_center(\n", @@ -83,27 +87,26 @@ " rascal_hypers,\n", " nu_target=3,\n", " lambdas=lambdas,\n", + " only_keep_parity=+1,\n", ")\n", "\n", "nu3_rot = clebsch_gordan.n_body_iteration_single_center(\n", - " [frame_rot],\n", + " [frame_so3],\n", " rascal_hypers,\n", " nu_target=3,\n", " lambdas=lambdas,\n", + " only_keep_parity=+1,\n", ")\n", "\n", - "# Build a Wigner-D Matrix from the random rotation angles\n", - "wig = spherical.WignerDReal(rascal_hypers[\"max_angular\"], a, b, c)\n", - "\n", "# Rotate the lambda-SOAP descriptor of the unrotated frame\n", - "nu3_unrot_rot = wig.rotate_tensormap(nu3)\n", + "nu3_unrot_rot = wig.transform_tensormap_so3(nu3)\n", "\n", "# Check for equivariance!\n", "assert equistore.equal_metadata(nu3_unrot_rot, nu3_rot)\n", "assert equistore.allclose(nu3_unrot_rot, nu3_rot)\n", "print(\"SO(3) EQUIVARIANT!\")\n", "\n", - "# chemiscope.show([frames[0], frame_rot], mode=\"structure\")" + "# chemiscope.show([frame, frame_so3], mode=\"structure\")" ] }, { @@ -122,7 +125,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Random rotation angles (rad): 1.5925083910906 0.10309657050925311 1.435700350750785\n", + "Random rotation angles (rad): [3.6769499 0.25087312 3.97576283]\n", "O(3) EQUIVARIANT!\n" ] } @@ -134,34 +137,31 @@ "# Pick a test frame\n", "frame = frames[0]\n", "\n", - "# Invert the positions\n", - "frame_o3 = frame.copy()\n", - "frame_o3.positions = -1 * frame_o3.positions\n", - "frame_o3, (a, b, c) = spherical.rotate_ase_frame(frame_o3)\n", - "print(\"Random rotation angles (rad):\", a, b, c)\n", - "assert not np.all(frame.positions == frame_o3.positions)\n", + "# Generate Wigner-D matrices, initialized with random angles\n", + "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", + "print(\"Random rotation angles (rad):\", wig.angles)\n", + "\n", + "# Apply an O(3) transformation to the frame\n", + "frame_o3 = rotations.transform_frame_o3(frame, wig.angles)\n", + "assert not np.allclose(frame.positions, frame_o3.positions)\n", "\n", "# Generate lambda-SOAP for both frames\n", - "lsoap = clebsch_gordan.n_body_iteration_single_center(\n", + "lsoap = clebsch_gordan.lambda_soap_vector(\n", " [frame],\n", " rascal_hypers,\n", - " nu_target=2,\n", " lambdas=lambdas,\n", + " only_keep_parity=+1,\n", ")\n", "\n", - "lsoap_o3 = clebsch_gordan.n_body_iteration_single_center(\n", + "lsoap_o3 = clebsch_gordan.lambda_soap_vector(\n", " [frame_o3],\n", " rascal_hypers,\n", - " nu_target=2,\n", " lambdas=lambdas,\n", + " only_keep_parity=+1,\n", ")\n", "\n", - "# Build a Wigner-D Matrix from the random rotation angles\n", - "wig = spherical.WignerDReal(rascal_hypers[\"max_angular\"], a, b, c)\n", - "\n", - "# Invert the TensorMap\n", - "lsoap_transformed = spherical.invert_tensormap(lsoap)\n", - "lsoap_transformed = wig.rotate_tensormap(lsoap_transformed)\n", + "# Apply the O(3) transformation to the TensorMap\n", + "lsoap_transformed = wig.transform_tensormap_o3(lsoap)\n", "\n", "# Check for equivariance!\n", "assert equistore.equal_metadata(lsoap_transformed, lsoap_o3)\n", @@ -175,17 +175,29 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Other Tests" + "# Old v new lambda-SOAP" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 1, 2, 3, 4, 5])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Define target lambda channels\n", - "lambdas = np.array([0, 1, 2, 3, 4, 5])" + "lambdas = np.arange(6)\n", + "lambdas" ] }, { @@ -197,12 +209,12 @@ "data": { "text/plain": [ "TensorMap with 33 blocks\n", - "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", - " 2 1 0 3\n", - " 2 1 1 3\n", - " ...\n", - " 2 1 5 22\n", - " 2 -1 5 22" + "keys: inversion_sigma spherical_harmonics_l species_center\n", + " 1 0 3\n", + " 1 1 3\n", + " ...\n", + " -1 4 22\n", + " -1 5 22" ] }, "execution_count": 6, @@ -211,110 +223,100 @@ } ], "source": [ - "# Dense\n", - "lsoap_new0 = clebsch_gordan.n_body_iteration_single_center(\n", + "lsoap_old = old_clebsch_gordan.lambda_soap_vector(\n", " frames,\n", " rascal_hypers,\n", - " nu_target=2,\n", " lambdas=lambdas,\n", - " lambda_cut=5,\n", - " use_sparse=False,\n", + " lambda_cut=8,\n", ")\n", - "lsoap_new0" + "lsoap_old" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "TensorMap with 33 blocks\n", - "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", - " 2 1 0 3\n", - " 2 1 1 3\n", - " ...\n", - " 2 1 5 22\n", - " 2 -1 5 22" + "TensorMap with 18 blocks\n", + "keys: spherical_harmonics_l species_center\n", + " 0 3\n", + " 1 3\n", + " ...\n", + " 4 22\n", + " 5 22" ] }, - "execution_count": 7, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# Sparse\n", - "lsoap_new1 = clebsch_gordan.n_body_iteration_single_center(\n", + "lsoap_new = clebsch_gordan.lambda_soap_vector(\n", " frames,\n", " rascal_hypers,\n", - " nu_target=2,\n", " lambdas=lambdas,\n", - " lambda_cut=5,\n", - " use_sparse=True,\n", + " lambda_cut=8,\n", + " only_keep_parity=+1,\n", ")\n", - "lsoap_new1" + "lsoap_new" ] }, { - "cell_type": "code", - "execution_count": 8, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "# Check sparse == dense\n", - "equistore.allclose(lsoap_new0, lsoap_new1)" + "# Dense v Sparse" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TensorMap with 33 blocks\n", - "keys: inversion_sigma spherical_harmonics_l species_center\n", - " 1 0 3\n", - " 1 1 3\n", - " ...\n", - " -1 4 22\n", - " -1 5 22" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "lsoap_old0 = spherical.lambda_soap_vector(\n", + "# Dense\n", + "lsoap_new0 = clebsch_gordan.n_body_iteration_single_center(\n", " frames,\n", " rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", " lambda_cut=5,\n", + " use_sparse=False,\n", ")\n", - "# Using the old implementation, blocks must be dropped to retain only the\n", - "# desired target lambdas\n", - "keys_to_drop = Labels(\n", - " names=lsoap_old0.keys.names,\n", - " values=lsoap_old0.keys.values[[v not in lambdas for v in lsoap_old0.keys.column(\"spherical_harmonics_l\")]],\n", + "lsoap_new0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Sparse\n", + "lsoap_new1 = clebsch_gordan.n_body_iteration_single_center(\n", + " frames,\n", + " rascal_hypers,\n", + " nu_target=2,\n", + " lambdas=lambdas,\n", + " lambda_cut=5,\n", + " use_sparse=True,\n", + " only_keep_parity=+1,\n", ")\n", - "lsoap_old0 = equistore.drop_blocks(lsoap_old0, keys=keys_to_drop)\n", - "lsoap_old0" + "lsoap_new1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check sparse == dense\n", + "equistore.allclose(lsoap_new0, lsoap_new1)" ] }, { From 35f51753899db4d64e5b7f132e0551bf39013aed Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Mon, 14 Aug 2023 08:32:36 +0200 Subject: [PATCH 44/96] refactor test --- .../tests/utils/test_clebsch_gordan.py | 103 ++++++++++++++---- 1 file changed, 83 insertions(+), 20 deletions(-) diff --git a/python/rascaline/tests/utils/test_clebsch_gordan.py b/python/rascaline/tests/utils/test_clebsch_gordan.py index 774d38453..eee385352 100644 --- a/python/rascaline/tests/utils/test_clebsch_gordan.py +++ b/python/rascaline/tests/utils/test_clebsch_gordan.py @@ -1,3 +1,4 @@ +import pytest import numpy as np import ase.io @@ -10,26 +11,43 @@ from rascaline.utils import clebsch_gordan -def test_clebsch_gordan_combine_dense_sparse_agree(): - N_SAMPLES = 30 - N_Q_PROPERTIES = 10 - N_P_PROPERTIES = 8 - L1 = 2 - L2 = 1 - LAM = 2 - arr_1 = np.random.rand(N_SAMPLES, 2*L1+1, N_Q_PROPERTIES) - arr_2 = np.random.rand(N_SAMPLES, 2*L2+1, N_P_PROPERTIES) +def random_equivariant_array(n_samples=30, n_q_properties=10, n_p_properties=8, l=1, seed=None): + if seed is not None: + np.random.seed(seed) - cg_cache_sparse = clebsch_gordan.ClebschGordanReal(lambda_max=LAM, sparse=True) - out_sparse = clebsch_gordan._clebsch_gordan_combine_sparse(arr_1, arr_2, LAM, cg_cache_sparse) + equi_l_array = np.random.rand(n_samples, 2*l+1, n_q_properties) + return equi_l_array - cg_cache_dense = clebsch_gordan.ClebschGordanReal(lambda_max=LAM, sparse=False) - out_dense = clebsch_gordan._clebsch_gordan_combine_dense(arr_1, arr_2, LAM, cg_cache_dense) +def equivariant_combinable_arrays(n_samples=30, n_q_properties=10, n_p_properties=8, l1=2, l2=1, lam=2, seed=None): + # check if valid blocks + assert abs(l1 - l2) <= lam and lam <= l1 + l2, f"(l1={l1}, l2={l2}, lam={lam} is not valid combination, |l1-l2| <= lam <= l1+l2 must be valid" + if seed is not None: + np.random.seed(seed) + + equi_l1_array = random_equivariant_array(n_samples, n_q_properties, n_p_properties, l=l1) + equi_l2_array = random_equivariant_array(n_samples, n_q_properties, n_p_properties, l=l2) + return equi_l1_array, equi_l2_array, lam + +@pytest.mark.parametrize( + "equi_l1_array, equi_l2_array, lam", + [equivariant_combinable_arrays(seed=51)], +) +def test_clebsch_gordan_combine_dense_sparse_agree(equi_l1_array, equi_l2_array, lam): + cg_cache_sparse = clebsch_gordan.ClebschGordanReal(lambda_max=lam, sparse=True) + out_sparse = clebsch_gordan._clebsch_gordan_combine_sparse(equi_l1_array, equi_l2_array, lam, cg_cache_sparse) + + cg_cache_dense = clebsch_gordan.ClebschGordanReal(lambda_max=lam, sparse=False) + out_dense = clebsch_gordan._clebsch_gordan_combine_dense(equi_l1_array, equi_l2_array, lam, cg_cache_dense) assert np.allclose(out_sparse, out_dense) +@pytest.fixture +def h2o_frame(): + return ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], + [-0.526383, 0.769327, -0.029366], + [ 0.066334, 0.000000, 0.003701]]) -def test_n_body_iteration_single_center_dense_sparse_agree(): +def test_n_body_iteration_single_center_dense_sparse_agree(h2o_frame): lmax = 5 lambdas = np.array([0, 2]) rascal_hypers = { @@ -42,12 +60,8 @@ def test_n_body_iteration_single_center_dense_sparse_agree(): "center_atom_weight": 1.0, } - frames = [ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], - [-0.526383, 0.769327, -0.029366], - [ 0.066334, 0.000000, 0.003701]])] - n_body_sparse = clebsch_gordan.n_body_iteration_single_center( - frames, + [h2o_frame], rascal_hypers=rascal_hypers, nu_target=3, lambdas=lambdas, @@ -57,7 +71,7 @@ def test_n_body_iteration_single_center_dense_sparse_agree(): ) n_body_dense = clebsch_gordan.n_body_iteration_single_center( - frames, + [h2o_frame], rascal_hypers=rascal_hypers, nu_target=3, lambdas=lambdas, @@ -68,6 +82,55 @@ def test_n_body_iteration_single_center_dense_sparse_agree(): assert equistore.operations.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) + +#def test_combine_single_center_orthogonality(h2o_frame): +# lmax = 5 +# lambdas = np.array([0, 2]) +# rascal_hypers = { +# "cutoff": 3.0, # Angstrom +# "max_radial": 6, # Exclusive +# "max_angular": lmax, # Inclusive +# "atomic_gaussian_width": 0.2, +# "radial_basis": {"Gto": {}}, +# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, +# "center_atom_weight": 1.0, +# } +# +# frames = [ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], +# [-0.526383, 0.769327, -0.029366], +# [ 0.066334, 0.000000, 0.003701]])] +# +# # Generate a rascaline SphericalExpansion, for only the selected samples if +# # applicable +# calculator = rascaline.SphericalExpansion(**rascal_hypers) +# nu1_tensor = calculator.compute(frames, selected_samples=selected_samples) +# combined_tensor = nu1_tensor.copy() +# +# cg_cache = ClebschGordanReal(lambda_cut, use_sparse) +# lambdas = np.array([0, 2]) +# +# combined_tensor = _combine_single_center( +# tensor_1=combined_tensor, +# tensor_2=nu1_tensor, +# lambdas=lambdas, +# cg_cache=cg_cache, +# use_sparse=Ture, +# only_keep_parity=keep_parity, +# ) +# +# n_body_sparse = clebsch_gordan.n_body_iteration_single_center( +# nu_target=3, +# lambdas=lambdas, +# lambda_cut=lmax * 2, +# species_neighbors=[1, 8, 6], +# use_sparse=True +# ) +# +# np.linalg.norm +# n_body_sparse +# + + # old test that become decprecated through prototyping on API #def test_soap_kernel(): # """ From 78ad39ef8f711cbb02932c15bfcde3ac991eb329 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Mon, 14 Aug 2023 12:49:37 +0200 Subject: [PATCH 45/96] added orthogonality tests --- .../tests/utils/test_clebsch_gordan.py | 196 ++++++++++++------ 1 file changed, 129 insertions(+), 67 deletions(-) diff --git a/python/rascaline/tests/utils/test_clebsch_gordan.py b/python/rascaline/tests/utils/test_clebsch_gordan.py index eee385352..9c80b2e56 100644 --- a/python/rascaline/tests/utils/test_clebsch_gordan.py +++ b/python/rascaline/tests/utils/test_clebsch_gordan.py @@ -9,7 +9,8 @@ from equistore import Labels, TensorBlock, TensorMap import equistore.operations -from rascaline.utils import clebsch_gordan +from rascaline.utils.clebsch_gordan import ClebschGordanReal, _clebsch_gordan_combine_dense, _clebsch_gordan_combine_sparse, n_body_iteration_single_center, _combine_single_center + def random_equivariant_array(n_samples=30, n_q_properties=10, n_p_properties=8, l=1, seed=None): if seed is not None: @@ -18,28 +19,67 @@ def random_equivariant_array(n_samples=30, n_q_properties=10, n_p_properties=8, equi_l_array = np.random.rand(n_samples, 2*l+1, n_q_properties) return equi_l_array -def equivariant_combinable_arrays(n_samples=30, n_q_properties=10, n_p_properties=8, l1=2, l2=1, lam=2, seed=None): - # check if valid blocks - assert abs(l1 - l2) <= lam and lam <= l1 + l2, f"(l1={l1}, l2={l2}, lam={lam} is not valid combination, |l1-l2| <= lam <= l1+l2 must be valid" - if seed is not None: - np.random.seed(seed) +@pytest.mark.parametrize( + "equi_l1_array, equi_l2_array, lam_max", + [(random_equivariant_array(l=1, seed=51), random_equivariant_array(l=2, seed=52), 2)], +) +def test_clebsch_gordan_combine_dense_sparse_agree(equi_l1_array, equi_l2_array, lam_max): + # TODO change to a fixture that precomputes random_equivariant_arrays up to |l1-l2| <= lam max <= l1+l2 + # as we well as CG coeffs + cg_cache_sparse = ClebschGordanReal(lambda_max=lam_max, sparse=True) + out_sparse = _clebsch_gordan_combine_sparse(equi_l1_array, equi_l2_array, lam_max, cg_cache_sparse) + + cg_cache_dense = ClebschGordanReal(lambda_max=lam_max, sparse=False) + out_dense = _clebsch_gordan_combine_dense(equi_l1_array, equi_l2_array, lam_max, cg_cache_dense) - equi_l1_array = random_equivariant_array(n_samples, n_q_properties, n_p_properties, l=l1) - equi_l2_array = random_equivariant_array(n_samples, n_q_properties, n_p_properties, l=l2) - return equi_l1_array, equi_l2_array, lam + assert np.allclose(out_sparse, out_dense) @pytest.mark.parametrize( - "equi_l1_array, equi_l2_array, lam", - [equivariant_combinable_arrays(seed=51)], + "equi_l1_array, equi_l2_array", + [(random_equivariant_array(l=1, seed=51), random_equivariant_array(l=1, seed=52))], ) -def test_clebsch_gordan_combine_dense_sparse_agree(equi_l1_array, equi_l2_array, lam): - cg_cache_sparse = clebsch_gordan.ClebschGordanReal(lambda_max=lam, sparse=True) - out_sparse = clebsch_gordan._clebsch_gordan_combine_sparse(equi_l1_array, equi_l2_array, lam, cg_cache_sparse) +def test_clebsch_gordan_orthogonality(equi_l1_array, equi_l2_array): + """ + Test orthogonality relationships - cg_cache_dense = clebsch_gordan.ClebschGordanReal(lambda_max=lam, sparse=False) - out_dense = clebsch_gordan._clebsch_gordan_combine_dense(equi_l1_array, equi_l2_array, lam, cg_cache_dense) + References + ---------- - assert np.allclose(out_sparse, out_dense) + https://en.wikipedia.org/wiki/Clebsch%E2%80%93Gordan_coefficients#Orthogonality_relations + """ + l1 = (equi_l1_array.shape[1]-1)//2 + l2 = (equi_l2_array.shape[1]-1)//2 + lam_min = abs(l1-l2) + lam_max = l1+l2 + + cg_cache_dense = ClebschGordanReal(lambda_max=lam_max, sparse=False) + # cache cg mats + cg_mats = {} + for lam in range(lam_min, lam_max+1): + cg_mats[lam] = cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2*lam+1) + + # We test lam dimension + # \sum_{-m1 \leq l1 \leq m1, -m2 \leq l2 \leq m2} + # <λμ|l1m1,l2m2> = δ_μμ' + for lam in range(lam_min, lam_max): + cg_mat = cg_mats[lam] + dot_product = cg_mat.T @ cg_mat + diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) + diag_mask[np.diag_indices(len(dot_product))] = True + assert np.allclose(dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask]) + assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) + + # We test l1 l2 dimension + # \sum_{|l1-l2| \leq λ \leq l1+l2} \sum_{-μ \leq λ \leq μ} + # <λμ|l1m1,l2m2> = δ_m1m1' δ_m2m2' + dot_product = np.zeros((len(cg_mats[0]), len(cg_mats[0]))) + for lam in range(lam_min, lam_max+1): + cg_mat = cg_mats[lam] + dot_product += cg_mat @ cg_mat.T + diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) + diag_mask[np.diag_indices(len(dot_product))] = True + assert np.allclose(dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask]) + assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) @pytest.fixture def h2o_frame(): @@ -48,11 +88,11 @@ def h2o_frame(): [ 0.066334, 0.000000, 0.003701]]) def test_n_body_iteration_single_center_dense_sparse_agree(h2o_frame): - lmax = 5 + lmax = 2 lambdas = np.array([0, 2]) rascal_hypers = { "cutoff": 3.0, # Angstrom - "max_radial": 6, # Exclusive + "max_radial": 2, # Exclusive "max_angular": lmax, # Inclusive "atomic_gaussian_width": 0.2, "radial_basis": {"Gto": {}}, @@ -60,7 +100,7 @@ def test_n_body_iteration_single_center_dense_sparse_agree(h2o_frame): "center_atom_weight": 1.0, } - n_body_sparse = clebsch_gordan.n_body_iteration_single_center( + n_body_sparse = n_body_iteration_single_center( [h2o_frame], rascal_hypers=rascal_hypers, nu_target=3, @@ -70,7 +110,7 @@ def test_n_body_iteration_single_center_dense_sparse_agree(h2o_frame): use_sparse=True ) - n_body_dense = clebsch_gordan.n_body_iteration_single_center( + n_body_dense = n_body_iteration_single_center( [h2o_frame], rascal_hypers=rascal_hypers, nu_target=3, @@ -83,52 +123,74 @@ def test_n_body_iteration_single_center_dense_sparse_agree(h2o_frame): assert equistore.operations.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) -#def test_combine_single_center_orthogonality(h2o_frame): -# lmax = 5 -# lambdas = np.array([0, 2]) -# rascal_hypers = { -# "cutoff": 3.0, # Angstrom -# "max_radial": 6, # Exclusive -# "max_angular": lmax, # Inclusive -# "atomic_gaussian_width": 0.2, -# "radial_basis": {"Gto": {}}, -# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, -# "center_atom_weight": 1.0, -# } -# -# frames = [ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], -# [-0.526383, 0.769327, -0.029366], -# [ 0.066334, 0.000000, 0.003701]])] -# -# # Generate a rascaline SphericalExpansion, for only the selected samples if -# # applicable -# calculator = rascaline.SphericalExpansion(**rascal_hypers) -# nu1_tensor = calculator.compute(frames, selected_samples=selected_samples) -# combined_tensor = nu1_tensor.copy() -# -# cg_cache = ClebschGordanReal(lambda_cut, use_sparse) -# lambdas = np.array([0, 2]) -# -# combined_tensor = _combine_single_center( -# tensor_1=combined_tensor, -# tensor_2=nu1_tensor, -# lambdas=lambdas, -# cg_cache=cg_cache, -# use_sparse=Ture, -# only_keep_parity=keep_parity, -# ) -# -# n_body_sparse = clebsch_gordan.n_body_iteration_single_center( -# nu_target=3, -# lambdas=lambdas, -# lambda_cut=lmax * 2, -# species_neighbors=[1, 8, 6], -# use_sparse=True -# ) -# -# np.linalg.norm -# n_body_sparse -# +def test_combine_single_center_orthogonality(h2o_frame): + l = 1 + lam_min = 0 + lam_max = 2*l + rascal_hypers = { + "cutoff": 3.0, # Angstrom + "max_radial": 6, # Exclusive + "max_angular": l, # Inclusive + "atomic_gaussian_width": 0.2, + "radial_basis": {"Gto": {}}, + "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + "center_atom_weight": 1.0, + } + + frames = [ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], + [-0.526383, 0.769327, -0.029366], + [ 0.066334, 0.000000, 0.003701]])] + + # Generate a rascaline SphericalExpansion, for only the selected samples if + # applicable + calculator = rascaline.SphericalExpansion(**rascal_hypers) + nu1_tensor = calculator.compute(frames) + # Move the "species_neighbor" key to the properties. If species_neighbors is + # passed as a list of int, sparsity can be created in the properties for + # these species. + keys_to_move = "species_neighbor" + nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) + # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 + nu1_tensor = equistore.insert_dimension( + nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 + ) + nu1_tensor = equistore.insert_dimension( + nu1_tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 + ) + combined_tensor = nu1_tensor.copy() + + use_sparse = True + cg_cache = ClebschGordanReal(lam_max, sparse=use_sparse) + lambdas = np.array(range(lam_max)) + + combined_tensor = _combine_single_center( + tensor_1=combined_tensor, + tensor_2=nu1_tensor, + lambdas=lambdas, + cg_cache=cg_cache, + use_sparse=use_sparse, # TODO remove use_sparse parameter, can be referred from cg_cache + only_keep_parity=True, + ) + nu_target = 1 + + #order_nu inversion_sigma spherical_harmonics_l species_center + #"spherical_harmonics_l", + combined_tensor = combined_tensor.keys_to_properties(["l1", "l2", "inversion_sigma", "order_nu"]) + combined_tensor = combined_tensor.keys_to_samples(["species_center"]) + n_samples = combined_tensor[0].values.shape[0] + combined_tensor_values = np.hstack( + [combined_tensor.block(Labels("spherical_harmonics_l", np.array([[l]]))).values.reshape(n_samples, -1) + for l in combined_tensor.keys["spherical_harmonics_l"]]) + combined_tensor_norm = np.linalg.norm(combined_tensor_values, axis=1) + + nu1_tensor = nu1_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) + nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) + nu1_tensor_values = np.hstack( + [nu1_tensor.block(Labels("spherical_harmonics_l", np.array([[l]]))).values.reshape(n_samples, -1) + for l in nu1_tensor.keys["spherical_harmonics_l"]]) + nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) + assert np.allclose(combined_tensor_norm, nu1_tensor_norm) + # old test that become decprecated through prototyping on API From 8d3fb19e222103531188557631741c53b8baaf21 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Mon, 14 Aug 2023 21:03:16 +0200 Subject: [PATCH 46/96] cleanup tests to not recompute cg coeffs for every test --- .../tests/utils/test_clebsch_gordan.py | 451 +++++++++--------- 1 file changed, 217 insertions(+), 234 deletions(-) diff --git a/python/rascaline/tests/utils/test_clebsch_gordan.py b/python/rascaline/tests/utils/test_clebsch_gordan.py index 9c80b2e56..619a9b7dd 100644 --- a/python/rascaline/tests/utils/test_clebsch_gordan.py +++ b/python/rascaline/tests/utils/test_clebsch_gordan.py @@ -11,270 +11,253 @@ from rascaline.utils.clebsch_gordan import ClebschGordanReal, _clebsch_gordan_combine_dense, _clebsch_gordan_combine_sparse, n_body_iteration_single_center, _combine_single_center - -def random_equivariant_array(n_samples=30, n_q_properties=10, n_p_properties=8, l=1, seed=None): +def random_equivariant_array(n_samples=10, n_q_properties=4, n_p_properties=8, l=1, seed=None): if seed is not None: np.random.seed(seed) equi_l_array = np.random.rand(n_samples, 2*l+1, n_q_properties) return equi_l_array -@pytest.mark.parametrize( - "equi_l1_array, equi_l2_array, lam_max", - [(random_equivariant_array(l=1, seed=51), random_equivariant_array(l=2, seed=52), 2)], -) -def test_clebsch_gordan_combine_dense_sparse_agree(equi_l1_array, equi_l2_array, lam_max): - # TODO change to a fixture that precomputes random_equivariant_arrays up to |l1-l2| <= lam max <= l1+l2 - # as we well as CG coeffs - cg_cache_sparse = ClebschGordanReal(lambda_max=lam_max, sparse=True) - out_sparse = _clebsch_gordan_combine_sparse(equi_l1_array, equi_l2_array, lam_max, cg_cache_sparse) +class TestClebschGordan: + lam_max = 3 + cg_cache_sparse = ClebschGordanReal(lambda_max=lam_max, sparse=True) cg_cache_dense = ClebschGordanReal(lambda_max=lam_max, sparse=False) - out_dense = _clebsch_gordan_combine_dense(equi_l1_array, equi_l2_array, lam_max, cg_cache_dense) - assert np.allclose(out_sparse, out_dense) + def test_clebsch_gordan_combine_dense_sparse_agree(self): + for l1, l2, lam in self.cg_cache_dense.coeffs.keys(): + equi_l1_array = random_equivariant_array(l=l1, seed=51) + equi_l2_array = random_equivariant_array(l=l2, seed=53) + out_sparse = _clebsch_gordan_combine_sparse(equi_l1_array, equi_l2_array, lam, self.cg_cache_sparse) + out_dense = _clebsch_gordan_combine_dense(equi_l1_array, equi_l2_array, lam, self.cg_cache_dense) + assert np.allclose(out_sparse, out_dense) -@pytest.mark.parametrize( - "equi_l1_array, equi_l2_array", - [(random_equivariant_array(l=1, seed=51), random_equivariant_array(l=1, seed=52))], -) -def test_clebsch_gordan_orthogonality(equi_l1_array, equi_l2_array): - """ - Test orthogonality relationships + @pytest.mark.parametrize("l1, l2", [(1, 2)]) + def test_clebsch_gordan_orthogonality(self, l1, l2): + """ + Test orthogonality relationships - References - ---------- + References + ---------- - https://en.wikipedia.org/wiki/Clebsch%E2%80%93Gordan_coefficients#Orthogonality_relations - """ - l1 = (equi_l1_array.shape[1]-1)//2 - l2 = (equi_l2_array.shape[1]-1)//2 - lam_min = abs(l1-l2) - lam_max = l1+l2 + https://en.wikipedia.org/wiki/Clebsch%E2%80%93Gordan_coefficients#Orthogonality_relations + """ + assert self.lam_max >= l1+l2, "Did not precompute CG coeff with high enough lambda_max" + lam_min = abs(l1-l2) + lam_max = l1+l2 - cg_cache_dense = ClebschGordanReal(lambda_max=lam_max, sparse=False) - # cache cg mats - cg_mats = {} - for lam in range(lam_min, lam_max+1): - cg_mats[lam] = cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2*lam+1) + # We test lam dimension + # \sum_{-m1 \leq l1 \leq m1, -m2 \leq l2 \leq m2} + # <λμ|l1m1,l2m2> = δ_μμ' + for lam in range(lam_min, lam_max): + cg_mat = self.cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2*lam+1) + dot_product = cg_mat.T@ cg_mat + diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) + diag_mask[np.diag_indices(len(dot_product))] = True + assert np.allclose(dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask]) + assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) - # We test lam dimension - # \sum_{-m1 \leq l1 \leq m1, -m2 \leq l2 \leq m2} - # <λμ|l1m1,l2m2> = δ_μμ' - for lam in range(lam_min, lam_max): - cg_mat = cg_mats[lam] - dot_product = cg_mat.T @ cg_mat + # We test l1 l2 dimension + # \sum_{|l1-l2| \leq λ \leq l1+l2} \sum_{-μ \leq λ \leq μ} + # <λμ|l1m1,l2m2> = δ_m1m1' δ_m2m2' + l1l2_dim = (2*l1+1) * (2*l2+1) + dot_product = np.zeros((l1l2_dim, l1l2_dim)) + for lam in range(lam_min, lam_max+1): + cg_mat = self.cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2*lam+1) + dot_product += cg_mat @ cg_mat.T diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) diag_mask[np.diag_indices(len(dot_product))] = True assert np.allclose(dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask]) assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) - # We test l1 l2 dimension - # \sum_{|l1-l2| \leq λ \leq l1+l2} \sum_{-μ \leq λ \leq μ} - # <λμ|l1m1,l2m2> = δ_m1m1' δ_m2m2' - dot_product = np.zeros((len(cg_mats[0]), len(cg_mats[0]))) - for lam in range(lam_min, lam_max+1): - cg_mat = cg_mats[lam] - dot_product += cg_mat @ cg_mat.T - diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) - diag_mask[np.diag_indices(len(dot_product))] = True - assert np.allclose(dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask]) - assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) - -@pytest.fixture -def h2o_frame(): - return ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], - [-0.526383, 0.769327, -0.029366], - [ 0.066334, 0.000000, 0.003701]]) - -def test_n_body_iteration_single_center_dense_sparse_agree(h2o_frame): - lmax = 2 - lambdas = np.array([0, 2]) - rascal_hypers = { - "cutoff": 3.0, # Angstrom - "max_radial": 2, # Exclusive - "max_angular": lmax, # Inclusive - "atomic_gaussian_width": 0.2, - "radial_basis": {"Gto": {}}, - "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, - "center_atom_weight": 1.0, - } + h2o_frame = ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], + [-0.526383, 0.769327, -0.029366], + [ 0.066334, 0.000000, 0.003701]]) - n_body_sparse = n_body_iteration_single_center( - [h2o_frame], - rascal_hypers=rascal_hypers, - nu_target=3, - lambdas=lambdas, - lambda_cut=lmax * 2, - species_neighbors=[1, 8, 6], - use_sparse=True - ) + @pytest.mark.parametrize("lam_max", [2]) + def test_n_body_iteration_single_center_dense_sparse_agree(self, lam_max): + assert self.lam_max >= lam_max, "Did not precompute CG coeff with high enough lambda_max" + lambdas = np.array([0, 2]) + rascal_hypers = { + "cutoff": 3.0, # Angstrom + "max_radial": 2, # Exclusive + "max_angular": lam_max, # Inclusive + "atomic_gaussian_width": 0.2, + "radial_basis": {"Gto": {}}, + "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + "center_atom_weight": 1.0, + } - n_body_dense = n_body_iteration_single_center( - [h2o_frame], - rascal_hypers=rascal_hypers, - nu_target=3, - lambdas=lambdas, - lambda_cut=lmax * 2, - species_neighbors=[1, 8, 6], - use_sparse=False - ) + n_body_sparse = n_body_iteration_single_center( + [self.h2o_frame], + rascal_hypers=rascal_hypers, + nu_target=3, + lambdas=lambdas, + lambda_cut=lam_max * 2, + species_neighbors=[1, 8, 6], + use_sparse=True + ) - assert equistore.operations.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) + n_body_dense = n_body_iteration_single_center( + [self.h2o_frame], + rascal_hypers=rascal_hypers, + nu_target=3, + lambdas=lambdas, + lambda_cut=lam_max * 2, + species_neighbors=[1, 8, 6], + use_sparse=False + ) + assert equistore.operations.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) -def test_combine_single_center_orthogonality(h2o_frame): - l = 1 - lam_min = 0 - lam_max = 2*l - rascal_hypers = { - "cutoff": 3.0, # Angstrom - "max_radial": 6, # Exclusive - "max_angular": l, # Inclusive - "atomic_gaussian_width": 0.2, - "radial_basis": {"Gto": {}}, - "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, - "center_atom_weight": 1.0, - } - frames = [ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], - [-0.526383, 0.769327, -0.029366], - [ 0.066334, 0.000000, 0.003701]])] + @pytest.mark.parametrize("l", [1]) + def test_combine_single_center_orthogonality(self, l): + lam_min = 0 + lam_max = 2*l + rascal_hypers = { + "cutoff": 3.0, # Angstrom + "max_radial": 6, # Exclusive + "max_angular": l, # Inclusive + "atomic_gaussian_width": 0.2, + "radial_basis": {"Gto": {}}, + "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + "center_atom_weight": 1.0, + } - # Generate a rascaline SphericalExpansion, for only the selected samples if - # applicable - calculator = rascaline.SphericalExpansion(**rascal_hypers) - nu1_tensor = calculator.compute(frames) - # Move the "species_neighbor" key to the properties. If species_neighbors is - # passed as a list of int, sparsity can be created in the properties for - # these species. - keys_to_move = "species_neighbor" - nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) - # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 - nu1_tensor = equistore.insert_dimension( - nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 - ) - nu1_tensor = equistore.insert_dimension( - nu1_tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 - ) - combined_tensor = nu1_tensor.copy() + # Generate a rascaline SphericalExpansion, for only the selected samples if + # applicable + calculator = rascaline.SphericalExpansion(**rascal_hypers) + nu1_tensor = calculator.compute([self.h2o_frame]) + # Move the "species_neighbor" key to the properties. If species_neighbors is + # passed as a list of int, sparsity can be created in the properties for + # these species. + keys_to_move = "species_neighbor" + nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) + # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 + nu1_tensor = equistore.insert_dimension( + nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 + ) + nu1_tensor = equistore.insert_dimension( + nu1_tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 + ) + combined_tensor = nu1_tensor.copy() - use_sparse = True - cg_cache = ClebschGordanReal(lam_max, sparse=use_sparse) - lambdas = np.array(range(lam_max)) + use_sparse = True + lambdas = np.array(range(lam_max)) - combined_tensor = _combine_single_center( - tensor_1=combined_tensor, - tensor_2=nu1_tensor, - lambdas=lambdas, - cg_cache=cg_cache, - use_sparse=use_sparse, # TODO remove use_sparse parameter, can be referred from cg_cache - only_keep_parity=True, - ) - nu_target = 1 + combined_tensor = _combine_single_center( + tensor_1=combined_tensor, + tensor_2=nu1_tensor, + lambdas=lambdas, + cg_cache=self.cg_cache_sparse, + use_sparse=use_sparse, # TODO remove use_sparse parameter, can be referred from cg_cache + only_keep_parity=True, + ) + nu_target = 1 - #order_nu inversion_sigma spherical_harmonics_l species_center - #"spherical_harmonics_l", - combined_tensor = combined_tensor.keys_to_properties(["l1", "l2", "inversion_sigma", "order_nu"]) - combined_tensor = combined_tensor.keys_to_samples(["species_center"]) - n_samples = combined_tensor[0].values.shape[0] - combined_tensor_values = np.hstack( - [combined_tensor.block(Labels("spherical_harmonics_l", np.array([[l]]))).values.reshape(n_samples, -1) - for l in combined_tensor.keys["spherical_harmonics_l"]]) - combined_tensor_norm = np.linalg.norm(combined_tensor_values, axis=1) + #order_nu inversion_sigma spherical_harmonics_l species_center + #"spherical_harmonics_l", + combined_tensor = combined_tensor.keys_to_properties(["l1", "l2", "inversion_sigma", "order_nu"]) + combined_tensor = combined_tensor.keys_to_samples(["species_center"]) + n_samples = combined_tensor[0].values.shape[0] + combined_tensor_values = np.hstack( + [combined_tensor.block(Labels("spherical_harmonics_l", np.array([[l]]))).values.reshape(n_samples, -1) + for l in combined_tensor.keys["spherical_harmonics_l"]]) + combined_tensor_norm = np.linalg.norm(combined_tensor_values, axis=1) - nu1_tensor = nu1_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) - nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) - nu1_tensor_values = np.hstack( - [nu1_tensor.block(Labels("spherical_harmonics_l", np.array([[l]]))).values.reshape(n_samples, -1) - for l in nu1_tensor.keys["spherical_harmonics_l"]]) - nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) - assert np.allclose(combined_tensor_norm, nu1_tensor_norm) + nu1_tensor = nu1_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) + nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) + nu1_tensor_values = np.hstack( + [nu1_tensor.block(Labels("spherical_harmonics_l", np.array([[l]]))).values.reshape(n_samples, -1) + for l in nu1_tensor.keys["spherical_harmonics_l"]]) + nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) + assert np.allclose(combined_tensor_norm, nu1_tensor_norm) -# old test that become decprecated through prototyping on API -#def test_soap_kernel(): -# """ -# Tests if we get the same result computing SOAP from spherical expansion coefficients using GC utils -# as when using SoapPowerSpectrum. -# -# """ -# frames = ase.Atoms('HO', -# positions=[[0., 0., 0.], [1., 1., 1.]], -# pbc=[False, False, False]) -# -# -# rascal_hypers = { -# "cutoff": 3.0, # Angstrom -# "max_radial": 2, # Exclusive -# "max_angular": 3, # Inclusive -# "atomic_gaussian_width": 0.2, -# "radial_basis": {"Gto": {}}, -# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, -# "center_atom_weight": 1.0, -# } -# -# calculator = rascaline.SphericalExpansion(**rascal_hypers) -# nu1 = calculator.compute(frames) -# nu1 = nu1.keys_to_properties("species_neighbor") -# -# lmax = 1 -# lambdas = np.arange(lmax) -# -# clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) -# cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) -# soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) -# soap_cg = soap_cg.keys_to_properties(["spherical_harmonics_l"]).keys_to_samples(["species_center"]) -# n_samples = len(soap_cg[0].samples) -# kernel_cg = np.zeros((n_samples, n_samples)) -# for key, block in soap_cg.items(): -# kernel_cg += block.values.squeeze() @ block.values.squeeze().T -# -# calculator = rascaline.SoapPowerSpectrum(**rascal_hypers) -# nu2 = calculator.compute(frames) -# soap_rascaline = nu2.keys_to_properties(["species_neighbor_1", "species_neighbor_2"]).keys_to_samples(["species_center"]) -# kernel_rascaline = np.zeros((n_samples, n_samples)) -# for key, block in soap_rascaline.items(): -# kernel_rascaline += block.values.squeeze() @ block.values.squeeze().T -# -# # worries me a bit that the rtol is shit, might be missing multiplicity? -# assert np.allclose(kernel_cg, kernel_rascaline, atol=1e-9, rtol=1e-1) -# -#def test_soap_zeros(): -# """ -# Tests if the l1!=l2 values are zero when computing the 3-body invariant (SOAP) -# """ -# frames = ase.Atoms('HO', -# positions=[[0., 0., 0.], [1., 1., 1.]], -# pbc=[False, False, False]) -# -# -# rascal_hypers = { -# "cutoff": 3.0, # Angstrom -# "max_radial": 2, # Exclusive -# "max_angular": 3, # Inclusive -# "atomic_gaussian_width": 0.2, -# "radial_basis": {"Gto": {}}, -# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, -# "center_atom_weight": 1.0, -# } -# -# calculator = rascaline.SphericalExpansion(**rascal_hypers) -# nu1 = calculator.compute(frames) -# nu1 = nu1.keys_to_properties("species_neighbor") -# -# lmax = 1 -# lambdas = np.arange(lmax) -# -# clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) -# cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) -# soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) -# soap_cg.keys_to_properties("spherical_harmonics_l") -# sliced_blocks = [] -# for key, block in soap_cg.items(): -# idx = block.properties.values[:, block.properties.names.index("l1")] != block.properties.values[:, block.properties.names.index("l2")] -# sliced_block = equistore.slice_block(block, "properties", Labels(names=block.properties.names, values=block.properties.values[idx])) -# sliced_blocks.append(sliced_block) -# -# assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) + # old test that become decprecated through prototyping on API + #def test_soap_kernel(): + # """ + # Tests if we get the same result computing SOAP from spherical expansion coefficients using GC utils + # as when using SoapPowerSpectrum. + # + # """ + # frames = ase.Atoms('HO', + # positions=[[0., 0., 0.], [1., 1., 1.]], + # pbc=[False, False, False]) + # + # + # rascal_hypers = { + # "cutoff": 3.0, # Angstrom + # "max_radial": 2, # Exclusive + # "max_angular": 3, # Inclusive + # "atomic_gaussian_width": 0.2, + # "radial_basis": {"Gto": {}}, + # "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + # "center_atom_weight": 1.0, + # } + # + # calculator = rascaline.SphericalExpansion(**rascal_hypers) + # nu1 = calculator.compute(frames) + # nu1 = nu1.keys_to_properties("species_neighbor") + # + # lmax = 1 + # lambdas = np.arange(lmax) + # + # clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) + # cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) + # soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) + # soap_cg = soap_cg.keys_to_properties(["spherical_harmonics_l"]).keys_to_samples(["species_center"]) + # n_samples = len(soap_cg[0].samples) + # kernel_cg = np.zeros((n_samples, n_samples)) + # for key, block in soap_cg.items(): + # kernel_cg += block.values.squeeze() @ block.values.squeeze().T + # + # calculator = rascaline.SoapPowerSpectrum(**rascal_hypers) + # nu2 = calculator.compute(frames) + # soap_rascaline = nu2.keys_to_properties(["species_neighbor_1", "species_neighbor_2"]).keys_to_samples(["species_center"]) + # kernel_rascaline = np.zeros((n_samples, n_samples)) + # for key, block in soap_rascaline.items(): + # kernel_rascaline += block.values.squeeze() @ block.values.squeeze().T + # + # # worries me a bit that the rtol is shit, might be missing multiplicity? + # assert np.allclose(kernel_cg, kernel_rascaline, atol=1e-9, rtol=1e-1) + # + #def test_soap_zeros(): + # """ + # Tests if the l1!=l2 values are zero when computing the 3-body invariant (SOAP) + # """ + # frames = ase.Atoms('HO', + # positions=[[0., 0., 0.], [1., 1., 1.]], + # pbc=[False, False, False]) + # + # + # rascal_hypers = { + # "cutoff": 3.0, # Angstrom + # "max_radial": 2, # Exclusive + # "max_angular": 3, # Inclusive + # "atomic_gaussian_width": 0.2, + # "radial_basis": {"Gto": {}}, + # "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + # "center_atom_weight": 1.0, + # } + # + # calculator = rascaline.SphericalExpansion(**rascal_hypers) + # nu1 = calculator.compute(frames) + # nu1 = nu1.keys_to_properties("species_neighbor") + # + # lmax = 1 + # lambdas = np.arange(lmax) + # + # clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) + # cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) + # soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) + # soap_cg.keys_to_properties("spherical_harmonics_l") + # sliced_blocks = [] + # for key, block in soap_cg.items(): + # idx = block.properties.values[:, block.properties.names.index("l1")] != block.properties.values[:, block.properties.names.index("l2")] + # sliced_block = equistore.slice_block(block, "properties", Labels(names=block.properties.names, values=block.properties.values[idx])) + # sliced_blocks.append(sliced_block) + # + # assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) From c7fc221d6999c26c0a6cafcf1bdbf9a83f892fd6 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Mon, 14 Aug 2023 21:06:10 +0200 Subject: [PATCH 47/96] remove input arg use_sparse when it can be inferred from cg_cache --- .../rascaline/rascaline/utils/clebsch_gordan.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index 3d38ff8b7..740ed318b 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -55,6 +55,14 @@ def __init__(self, lambda_max: int, sparse: bool = True): self._lambda_max, self._sparse ) + @property + def lambda_max(self) + return self._lambda_max + + @property + def sprase(self) + return self._sparse + @property def coeffs(self): return self._coeffs @@ -378,7 +386,6 @@ def n_body_iteration_single_center( tensor_2=nu1_tensor, lambdas=lambdas, cg_cache=cg_cache, - use_sparse=use_sparse, only_keep_parity=keep_parity, ) @@ -401,7 +408,6 @@ def _combine_single_center( tensor_2: TensorMap, lambdas: Sequence[int], cg_cache, - use_sparse: bool = True, only_keep_parity: Optional[int] = None, ) -> TensorMap: """ @@ -472,7 +478,6 @@ def _combine_single_center( block_2, combined_key["spherical_harmonics_l"], cg_cache, - use_sparse, correction_factor=np.sqrt(multi), ) ) @@ -485,7 +490,6 @@ def _combine_single_center_block_pair( block_2: TensorBlock, lam: int, cg_cache, - use_sparse: bool = True, correction_factor: float = 1.0, ) -> TensorBlock: """ @@ -495,7 +499,7 @@ def _combine_single_center_block_pair( # Do the CG combination - single center so no shape pre-processing required combined_values = _clebsch_gordan_combine( - block_1.values, block_2.values, lam, cg_cache, use_sparse + block_1.values, block_2.values, lam, cg_cache ) # Infer the new nu value: block 1's properties are nu pairs of @@ -540,7 +544,6 @@ def _clebsch_gordan_combine( arr_2: np.ndarray, lam: int, cg_cache, - use_sparse: bool = True, ) -> np.ndarray: """ Couples arrays corresponding to the irreducible spherical components of 2 @@ -563,7 +566,7 @@ def _clebsch_gordan_combine( value of `sparse`. """ # Check the first dimension of the arrays are the same (i.e. same samples) - if use_sparse: + if cg_cache.sparse: return _clebsch_gordan_combine_sparse(arr_1, arr_2, lam, cg_cache) return _clebsch_gordan_combine_dense(arr_1, arr_2, lam, cg_cache) From 0cc2ac51ecf36351e5e1558714bbe65b4adfbdff Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Mon, 14 Aug 2023 21:08:25 +0200 Subject: [PATCH 48/96] remove use_sparse in tests using _combine_single_center --- python/rascaline/tests/utils/test_clebsch_gordan.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/rascaline/tests/utils/test_clebsch_gordan.py b/python/rascaline/tests/utils/test_clebsch_gordan.py index 619a9b7dd..e72436dd0 100644 --- a/python/rascaline/tests/utils/test_clebsch_gordan.py +++ b/python/rascaline/tests/utils/test_clebsch_gordan.py @@ -143,7 +143,6 @@ def test_combine_single_center_orthogonality(self, l): ) combined_tensor = nu1_tensor.copy() - use_sparse = True lambdas = np.array(range(lam_max)) combined_tensor = _combine_single_center( @@ -151,7 +150,6 @@ def test_combine_single_center_orthogonality(self, l): tensor_2=nu1_tensor, lambdas=lambdas, cg_cache=self.cg_cache_sparse, - use_sparse=use_sparse, # TODO remove use_sparse parameter, can be referred from cg_cache only_keep_parity=True, ) nu_target = 1 From 1f332a3c6ec3dc6e03ce4955963197acbfd29c91 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Thu, 24 Aug 2023 16:28:41 +0200 Subject: [PATCH 49/96] fix typos --- python/rascaline/rascaline/utils/clebsch_gordan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index 740ed318b..a48432ca0 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -56,11 +56,11 @@ def __init__(self, lambda_max: int, sparse: bool = True): ) @property - def lambda_max(self) + def lambda_max(self): return self._lambda_max @property - def sprase(self) + def sparse(self): return self._sparse @property From 6bc6839eadbff100b1d755b857e03637aae0b26d Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Fri, 15 Sep 2023 15:45:51 +0200 Subject: [PATCH 50/96] update notebook --- python/rascaline/rascaline/utils/tmp.ipynb | 231 ++++++++++++++------- 1 file changed, 160 insertions(+), 71 deletions(-) diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index 232eaa405..d2fbd17c5 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -61,29 +61,26 @@ "name": "stdout", "output_type": "stream", "text": [ - "Random rotation angles (rad): [2.71801454 5.20220183 3.836667 ]\n", + "Random rotation angles (rad): [5.94258177 1.33221078 0.11693219]\n", "SO(3) EQUIVARIANT!\n" ] } ], "source": [ "# Define target lambda channels\n", - "lambdas = np.array([0, 1, 2])\n", - "\n", - "# Pick a test frame\n", - "frame = frames[0]\n", + "lambdas = np.array([0, 2])\n", "\n", "# Generate Wigner-D matrices, initialized with random angles\n", "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", "print(\"Random rotation angles (rad):\", wig.angles)\n", "\n", "# Randomly rigidly rotate the frame\n", - "frame_so3 = rotations.transform_frame_so3(frame, wig.angles)\n", - "assert not np.allclose(frame.positions, frame_so3.positions)\n", + "frames_so3 = [rotations.transform_frame_so3(frame, wig.angles) for frame in frames]\n", + "assert not np.allclose(frames[-1].positions, frames_so3[-1].positions)\n", "\n", "# Generate nu=3 descriptor for both frames\n", "nu3 = clebsch_gordan.n_body_iteration_single_center(\n", - " [frame],\n", + " frames,\n", " rascal_hypers,\n", " nu_target=3,\n", " lambdas=lambdas,\n", @@ -91,7 +88,7 @@ ")\n", "\n", "nu3_rot = clebsch_gordan.n_body_iteration_single_center(\n", - " [frame_so3],\n", + " frames_so3,\n", " rascal_hypers,\n", " nu_target=3,\n", " lambdas=lambdas,\n", @@ -99,11 +96,11 @@ ")\n", "\n", "# Rotate the lambda-SOAP descriptor of the unrotated frame\n", - "nu3_unrot_rot = wig.transform_tensormap_so3(nu3)\n", + "nu3_transformed = wig.transform_tensormap_so3(nu3)\n", "\n", "# Check for equivariance!\n", - "assert equistore.equal_metadata(nu3_unrot_rot, nu3_rot)\n", - "assert equistore.allclose(nu3_unrot_rot, nu3_rot)\n", + "assert equistore.equal_metadata(nu3_transformed, nu3_rot)\n", + "assert equistore.allclose(nu3_transformed, nu3_rot)\n", "print(\"SO(3) EQUIVARIANT!\")\n", "\n", "# chemiscope.show([frame, frame_so3], mode=\"structure\")" @@ -125,7 +122,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Random rotation angles (rad): [3.6769499 0.25087312 3.97576283]\n", + "Random rotation angles (rad): [2.07830924 0.730489 5.29996543]\n", "O(3) EQUIVARIANT!\n" ] } @@ -134,27 +131,24 @@ "# Define target lambda channels\n", "lambdas = np.array([0, 1, 2, 3, 4, 5])\n", "\n", - "# Pick a test frame\n", - "frame = frames[0]\n", - "\n", "# Generate Wigner-D matrices, initialized with random angles\n", "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", "print(\"Random rotation angles (rad):\", wig.angles)\n", "\n", "# Apply an O(3) transformation to the frame\n", - "frame_o3 = rotations.transform_frame_o3(frame, wig.angles)\n", - "assert not np.allclose(frame.positions, frame_o3.positions)\n", + "frames_o3 = [rotations.transform_frame_o3(frame, wig.angles) for frame in frames]\n", + "assert not np.allclose(frames[-1].positions, frames_o3[-1].positions)\n", "\n", "# Generate lambda-SOAP for both frames\n", "lsoap = clebsch_gordan.lambda_soap_vector(\n", - " [frame],\n", + " frames,\n", " rascal_hypers,\n", " lambdas=lambdas,\n", " only_keep_parity=+1,\n", ")\n", "\n", "lsoap_o3 = clebsch_gordan.lambda_soap_vector(\n", - " [frame_o3],\n", + " frames_o3,\n", " rascal_hypers,\n", " lambdas=lambdas,\n", " only_keep_parity=+1,\n", @@ -180,20 +174,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([0, 1, 2, 3, 4, 5])" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Define target lambda channels\n", "lambdas = np.arange(6)\n", @@ -202,26 +185,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TensorMap with 33 blocks\n", - "keys: inversion_sigma spherical_harmonics_l species_center\n", - " 1 0 3\n", - " 1 1 3\n", - " ...\n", - " -1 4 22\n", - " -1 5 22" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "lsoap_old = old_clebsch_gordan.lambda_soap_vector(\n", " frames,\n", @@ -234,26 +200,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TensorMap with 18 blocks\n", - "keys: spherical_harmonics_l species_center\n", - " 0 3\n", - " 1 3\n", - " ...\n", - " 4 22\n", - " 5 22" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "lsoap_new = clebsch_gordan.lambda_soap_vector(\n", " frames,\n", @@ -430,6 +379,146 @@ ")\n", "n_body" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def sort_tm(tm):\n", + " blocks = []\n", + " for _, block in tm.items():\n", + " values = block.values\n", + "\n", + " samples_values = block.samples.values\n", + " sorted_idx = native_list_argsort([tuple(row.tolist()) for row in block.samples.values])\n", + " samples_values = samples_values[sorted_idx]\n", + " values = values[sorted_idx]\n", + "\n", + " components_values = []\n", + " for i, component in enumerate(block.components):\n", + " component_values = component.values\n", + " sorted_idx = native_list_argsort([tuple(row.tolist()) for row in component.values])\n", + " components_values.append( component_values[sorted_idx] )\n", + " values = np.take(values, sorted_idx, axis=i+1)\n", + "\n", + " properties_values = block.properties.values\n", + " sorted_idx = native_list_argsort([tuple(row.tolist()) for row in block.properties.values])\n", + " properties_values = properties_values[sorted_idx]\n", + " values = values[..., sorted_idx]\n", + "\n", + " blocks.append(\n", + " TensorBlock(\n", + " values=values,\n", + " samples=Labels(values=samples_values, names=block.samples.names),\n", + " components=[Labels(values=components_values[i], names=component.names) for i, component in enumerate(block.components)],\n", + " properties=Labels(values=properties_values, names=block.properties.names)\n", + " )\n", + " )\n", + " return TensorMap(keys=tm.keys, blocks=blocks)\n", + "\n", + "\n", + "def native_list_argsort(native_list):\n", + " return sorted(range(len(native_list)), key=native_list.__getitem__)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# Manipulate metadata from old LSOAP\n", + "lsoap_old = equistore.permute_dimensions(lsoap_old0, axis=\"properties\", dimensions_indexes=[2, 5, 0, 1, 3, 4])\n", + "lsoap_old = equistore.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_1\", new=\"l1\")\n", + "lsoap_old = equistore.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_2\", new=\"l2\")\n", + "lsoap_old = equistore.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_1\", new=\"n1\")\n", + "lsoap_old = equistore.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_2\", new=\"n2\")\n", + "\n", + "# Slice TM to symmetrize l-values\n", + "sliced_blocks = []\n", + "for key, block in lsoap_old.items():\n", + " # Filter properties l1 <= l2\n", + " mask = [entry[0] <= entry[1] for entry in block.properties]\n", + " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", + "\n", + " # Slice block\n", + " sliced_block = equistore.slice_block(block, axis=\"properties\", labels=new_labels)\n", + " sliced_blocks.append(sliced_block)\n", + "\n", + "# Check equal metadata\n", + "lsoap_old = sort_tm(TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks))\n", + "lsoap_new = sort_tm(lsoap_new0)\n", + "assert equistore.equal_metadata(lsoap_old, lsoap_new)\n", + "\n", + "# Check equal values\n", + "equistore.allclose_raise(lsoap_old, lsoap_new)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for key in lsoap_old.keys:\n", + " b1 = lsoap_old[key]\n", + " b2 = lsoap_new[key]\n", + "\n", + " print(key, equistore.allclose_block(b1, b2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "np.linalg.norm(\n", + " lsoap_old.block(inversion_sigma=1, spherical_harmonics_l=1, species_center=3).values\n", + " - lsoap_new.block(\n", + " inversion_sigma=1, spherical_harmonics_l=1, species_center=3\n", + " ).values\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# NOW SLICE TO l1 == l2\n", + "\n", + "# Slice TM\n", + "sliced_blocks = []\n", + "for key, block in lsoap_old.items():\n", + " # Filter properties\n", + " mask = [entry[0] == entry[1] for entry in block.properties]\n", + " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", + "\n", + " # Slice block\n", + " sliced_blocks.append(equistore.slice_block(block, axis=\"properties\", labels=new_labels))\n", + "\n", + "lsoap_old_sliced = TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks)\n", + "\n", + "# Slice TM\n", + "sliced_blocks = []\n", + "for key, block in lsoap_new.items():\n", + " # Filter properties\n", + " mask = [entry[0] == entry[1] for entry in block.properties]\n", + " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", + "\n", + " # Slice block\n", + " sliced_blocks.append(equistore.slice_block(block, axis=\"properties\", labels=new_labels))\n", + "\n", + "lsoap_new_sliced = TensorMap(keys=lsoap_new.keys, blocks=sliced_blocks)\n", + "\n", + "assert equistore.equal_metadata(lsoap_old_sliced, lsoap_new_sliced)\n", + "assert equistore.allclose_raise(lsoap_old_sliced, lsoap_new_sliced)" + ] } ], "metadata": { From bee22b4b58cebe50c27ccea0f5ded343d265f0f0 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Sat, 16 Sep 2023 18:34:23 +0200 Subject: [PATCH 51/96] to metatensor; lambda_filter; pre-compute all keys --- .../rascaline/utils/clebsch_gordan.py | 559 ++++++++++++------ .../rascaline/utils/old_clebsch_gordan.py | 57 +- python/rascaline/rascaline/utils/rotations.py | 4 +- python/rascaline/rascaline/utils/tmp.ipynb | 187 +++--- .../tests/utils/test_clebsch_gordan.py | 14 +- 5 files changed, 524 insertions(+), 297 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index a48432ca0..4241185b2 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -1,5 +1,5 @@ """ -Module for computing Clebsch-gordan iterations with equistore TensorMaps. +Module for computing Clebsch-gordan iterations with metatensor TensorMaps. """ import itertools from typing import Optional, Sequence, Tuple, Union @@ -7,8 +7,8 @@ import ase import numpy as np -import equistore -from equistore.core import Labels, TensorBlock, TensorMap +import metatensor +from metatensor import Labels, TensorBlock, TensorMap import rascaline import wigners @@ -18,6 +18,8 @@ # - [ ] Add support for dense operation # - [ ] Account for body-order multiplicity # - [ ] Gradients? +# - [ ] Unit tests +# - [ ] Check the combined keys - potential bug (!) # ===== Class for calculating Clebsch-Gordan coefficients ===== @@ -223,15 +225,15 @@ def _combine_multi_centers_block_pair( def lambda_soap_vector( frames: Sequence[ase.Atoms], rascal_hypers: dict, - lambdas: Sequence[int], + lambda_filter: Optional[Union[None, int, Sequence[int]]] = None, + sigma_filter: Optional[Union[None, int, Sequence[int]]] = None, lambda_cut: Optional[int] = None, - only_keep_parity: Optional[Union[int, dict]] = None, selected_samples: Optional[Labels] = None, species_neighbors: Optional[Sequence[int]] = None, ) -> TensorMap: """ A higher-level wrapper for the :py:func:`n_body_iteration_single_center` - function specifically for generating lambda-SOAP vectors in the equistore + function specifically for generating lambda-SOAP vectors in the metatensor format, with some added metadata manipulation. The hyperparameters `rascal_hypers` are used to generate a nu=1 @@ -249,97 +251,255 @@ def lambda_soap_vector( needs to be tailored for the computation and system. Note that truncating this value to less than the default will lead to some information loss. - If `only_keep_parity` is passed, then only the specified parities are + If `sigmas` is passed, then only the specified sigmas are returned in the output TensorMap. For instance, passing as an int +1 means - only blocks with even parity will be returned. If a dict, the parities kept + only blocks with even sigmas will be returned. If a dict, the sigmas kept for each target lambda can be specified. Any lambdasIf false, all blocks of both odd - and even parity are returned. In the latter case, the output TensorMap will - have a key dimension "inversion_sigma" that tracks the parity. + and even sigmas are returned. In the latter case, the output TensorMap will + have a key dimension "inversion_sigma" that tracks the sigmas. """ # Generate lambda-SOAP using rascaline.utils lsoap = n_body_iteration_single_center( frames, rascal_hypers=rascal_hypers, nu_target=2, - lambdas=lambdas, + lambda_filter=lambda_filter, + sigma_filter=sigma_filter, lambda_cut=lambda_cut, selected_samples=selected_samples, species_neighbors=species_neighbors, - only_keep_parity=only_keep_parity, use_sparse=True, ) # Drop the redundant key name "order_nu". This is by definition 2 for all # lambda-SOAP blocks. - lsoap = equistore.remove_dimension(lsoap, axis="keys", name="order_nu") + lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="order_nu") - # If a single parity is requested, drop the now redundant "inversion_sigma" + # If a single sigmas is requested, drop the now redundant "inversion_sigma" # key name - if isinstance(only_keep_parity, int): - lsoap = equistore.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + if len(np.unique(lsoap.keys.column("inversion_sigma"))) == 1: + lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="inversion_sigma") return lsoap -def n_body_iteration_single_center( - frames: Sequence[ase.Atoms], - rascal_hypers: dict, +def _parse_sigma_filter( nu_target: int, - lambdas: Sequence[int], - lambda_cut: Optional[int] = None, - selected_samples: Optional[Labels] = None, - species_neighbors: Optional[Sequence[int]] = None, - only_keep_parity: Optional[Union[int, dict]] = None, - use_sparse: bool = True, -) -> TensorMap: + sigma_filter: Union[None, int, Sequence[int], Sequence[Sequence[int]]], +) -> Sequence[Sequence[int]]: """ - Based on the passed ``rascal_hypers``, generates a rascaline - SphericalExpansion (i.e. nu = 1 body order descriptor) and combines it - iteratively to generate a descriptor of order ``nu``. + Returns parity filters for each CG combination step of a nu=1 tensor with + itself up to the target body order. + + If a filter isn't specified by the user with `sigma_filter=None`, then no + filter is applied, i.e. [-1, +1] is used at every iteration. - The returned TensorMap will only contain blocks with angular channels of - target order lambda corresponding to those passed in ``lambdas``. + If a single sequence of int is specified, then this is used for the last + iteration only, and [-1, +1] is used for all intermediate iterations. For + example, if `nu_target=4` and `sigma_filter=[+1]`, then the filter [[-1, + +1], [-1, +1], [+1]] is returned. - Passing ``lambda_cut`` will place a maximum value on the angular - order of blocks created by combination at each CG combination step. + If a sequence of sequences of int is specified, then this is assumed to be + the desired filter for each iteration and is only checked for validity + without modification. """ - # Set default lambda_cut if not passed - if lambda_cut is None: - # WARNING: the default is the maximum possible angular order for the - # given hypers and target body order. Memory explosion possible! - # TODO: better default value here? - lambda_cut = nu_target * rascal_hypers["max_angular"] - - # Check `lambda_cut` is valid - if not ( - rascal_hypers["max_angular"] - <= lambda_cut - <= nu_target * rascal_hypers["max_angular"] - ): + if nu_target < 2: + raise ValueError("`nu_target` must be > 1") + + # No filter specified: use [-1, +1] for all iterations + if sigma_filter is None: + sigma_filter = [[-1, +1] for _ in range(nu_target - 1)] + + # Parse user-defined filter: assume passed as Sequence[int] or + # Sequence[Sequence[int]] + else: + if isinstance(sigma_filter, int): + sigma_filter = [sigma_filter] + if not isinstance(sigma_filter, Sequence): + raise TypeError( + "`sigma_filter` must be an int, Sequence[int], or Sequence[Sequence[int]]" + ) + # Single filter: apply on last iteration only, use both sigmas for + # intermediate iterations + if np.all([isinstance(sigma, int) for sigma in sigma_filter]): + sigma_filter = [[-1, +1] for _ in range(nu_target - 2)] + [sigma_filter] + + else: + # Assume filter explicitly defined for each iteration (checked below) + pass + + # Check sigma_filter + assert isinstance(sigma_filter, Sequence) + assert len(sigma_filter) == nu_target - 1 + assert np.all([isinstance(filt, Sequence) for filt in sigma_filter]) + assert np.all([np.all([s in [-1, +1] for s in filt]) for filt in sigma_filter]) + + return sigma_filter + + +def _parse_lambda_filter( + nu_target: int, + rascal_max_l: int, + lambda_filter: Union[None, int, Sequence[int], Sequence[Sequence[int]]], + lambda_cut: Union[None, int], +) -> Sequence[Sequence[int]]: + """ + Returns parity filters for each CG combination step of a nu=1 tensor with + itself up to the target body order. + + If a filter isn't specified by the user with `lambda_filter=None`, then no + filter is applied. In this case all possible lambda channels are retained at + each iteration. For example, if `nu_target=4`, `rascal_max_l=5`, and + `lambda_cut=None`, then the returned filter is [[0, ..., 10], [0, ..., 15], + [0, ..., 20]]. If `nu_target=4`, `rascal_max_l=5`, and `lambda_cut=10`, then + the returned filter is [[0, ..., 10], [0, ..., 10], [0, ..., 10]]. + + If `lambda_filter` is passed a single sequence of int, then this is used for + the last iteration only, and all possible combinations of lambda are used in + intermediate iterations. For instance, if `lambda_filter=[0, 1, 2]`, the + returned filters for the 2 examples above, respectively, would be [[0, ..., + 10], [0, ..., 15], [0, 1, 2]] and [[0, ..., 10], [0, ..., 10], [0, 1, 2]]. + + If a sequence of sequences of int is specified, then this is assumed to be + the desired filter for each iteration and is only checked for validity + without modification. + """ + if nu_target < 2: + raise ValueError("`nu_target` must be > 1") + + # Check value of lambda_cut + if lambda_cut is not None: + if not (rascal_max_l <= lambda_cut <= nu_target * rascal_max_l): + raise ValueError( + "`lambda_cut` must be >= `rascal_hypers['max_angular']` and <= `nu_target`" + " * `rascal_hypers['max_angular']`" + ) + + # No filter specified: retain all possible lambda channels for every + # iteration, up to lambda_cut (if specified) + if lambda_filter is None: + if lambda_cut is None: + # Use the full range of possible lambda channels for each iteration. + # This is dependent on the itermediate body order. + lambda_filter = [ + [lam for lam in range(0, (nu * rascal_max_l) + 1)] + for nu in range(2, nu_target + 1) + ] + else: + # Use the full range of possible lambda channels for each iteration, + # but only up to lambda_cut, independent of the intermediate body + # order. + lambda_filter = [ + [lam for lam in range(0, lambda_cut + 1)] + for nu in range(2, nu_target + 1) + ] + + # Parse user-defined filter: assume passed as Sequence[int] or + # Sequence[Sequence[int]] + else: + if isinstance(lambda_filter, int): + lambda_filter = [lambda_filter] + if not isinstance(lambda_filter, Sequence): + raise TypeError( + "`lambda_filter` must be an int, Sequence[int], or Sequence[Sequence[int]]" + ) + # Single filter: apply on last iteration only, use all possible lambdas for + # intermediate iterations (up to lambda_cut, if specified) + if np.all([isinstance(filt, int) for filt in lambda_filter]): + if lambda_cut is None: + # Use the full range of possible lambda channels for each iteration. + # This is dependent on the itermediate body order. + lambda_filter = [ + [lam for lam in range(0, (nu * rascal_max_l) + 1)] + for nu in range(2, nu_target) + ] + [lambda_filter] + + else: + # Use the full range of possible lambda channels for each iteration, + # but only up to lambda_cut, independent of the intermediate body + # order. + lambda_filter = [ + [lam for lam in range(0, lambda_cut + 1)] + for nu in range(2, nu_target) + ] + [lambda_filter] + + else: + # Assume filter explicitly defined for each iteration (checked below) + pass + + # Check lambda_filter + if not isinstance(lambda_filter, Sequence): + raise TypeError( + "`lambda_filter` must be an int, Sequence[int], or Sequence[Sequence[int]]" + ) + if len(lambda_filter) != nu_target - 1: raise ValueError( - "`lambda_cut` cannot be more than `nu_target` * `rascal_hypers['max_angular']`" - " or less than `rascal_hypers['max_angular']`" + "`lambda_filter` must have length `nu_target` - 1, i.e. the number of CG" + " iterations required to reach `nu_target`" ) - - # Check `lambdas` are valid - if not (np.min(lambdas) >= 0 and np.max(lambdas) <= lambda_cut): + if not np.all([isinstance(filt, Sequence) for filt in lambda_filter]): + raise TypeError( + "`lambda_filter` must be an int, Sequence[int], or Sequence[Sequence[int]]" + ) + # Check the lambda values are within the possible range, based on each + # intermediate body order + if not np.all( + [ + np.all([0 <= lam <= nu * rascal_max_l for lam in filt]) + for nu, filt in enumerate(lambda_filter, start=2) + ] + ): raise ValueError( - "All `lambdas` must be >= 0 and <= `lambda_cut`" + "All lambda values in `lambda_filter` must be >= 0 and <= `nu` *" + " `rascal_hypers['max_angular']`, where `nu` is the body" + " order created in the intermediate CG combination step" ) + # Now check that at each iteration the lambda values can actually be created + # from combination at the previous iteration + for filt_i, filt in enumerate(lambda_filter): + if filt_i == 0: + # Assume that the original nu=1 tensors to be combined have all l up + # to and including `rascal_max_l` + allowed_lams = np.arange(0, (2 * rascal_max_l) + 1) + else: + allowed_lams = [] + for l1, l2 in itertools.product(lambda_filter[filt_i - 1], repeat=2): + for lam in range(abs(l1 - l2), abs(l1 + l2) + 1): + allowed_lams.append(lam) + + allowed_lams = np.unique(allowed_lams) - # Check `only_keep_parity` is valid - if isinstance(only_keep_parity, int): - if only_keep_parity not in [None, -1, 1]: + if not np.all([lam in allowed_lams for lam in filt]): raise ValueError( - "If passing `only_keep_parity` as int, it must be -1 or +1" + f"invalid lambda values in `lambda_filter` for iteration {filt_i + 1}." + f" {filt} cannot be created by combination of previous lambda values" + f" {lambda_filter[filt_i - 1]}" ) - elif isinstance(only_keep_parity, dict): - # TODO: implement parity to keep by l channel - raise NotImplementedError("parity to keep by l channel not yet implemented.") - # Define the cached CG coefficients, either as sparse dicts or dense arrays - cg_cache = ClebschGordanReal(lambda_cut, use_sparse) + return lambda_filter + +def n_body_iteration_single_center( + frames: Sequence[ase.Atoms], + rascal_hypers: dict, + nu_target: int, + lambda_filter: Optional[ + Union[None, int, Sequence[int], Sequence[Sequence[int]]] + ] = None, + sigma_filter: Optional[ + Union[None, int, Sequence[int], Sequence[Sequence[int]]] + ] = None, + lambda_cut: Optional[int] = None, + selected_samples: Optional[Labels] = None, + species_neighbors: Optional[Sequence[int]] = None, + use_sparse: bool = True, + debug: bool = False, +) -> TensorMap: + """ + Based on the passed ``rascal_hypers``, generates a rascaline + SphericalExpansion (i.e. nu = 1 body order descriptor) and combines it + iteratively to generate a descriptor of order ``nu_target``. + """ # Generate a rascaline SphericalExpansion, for only the selected samples if # applicable calculator = rascaline.SphericalExpansion(**rascal_hypers) @@ -358,131 +518,188 @@ def n_body_iteration_single_center( nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 - nu1_tensor = equistore.insert_dimension( + nu1_tensor = metatensor.insert_dimension( nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 ) - nu1_tensor = equistore.insert_dimension( + nu1_tensor = metatensor.insert_dimension( nu1_tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 ) - # Create a copy of the nu = 1 TensorMap to combine with itself - combined_tensor = nu1_tensor.copy() - - # Iteratively combine until the target body order is reached - n_iterations = nu_target - 1 - for iteration in range(1, n_iterations + 1): - # If we want to filter based on parity, only do so on the final CG - # iteration - keep_parity = None - if iteration < n_iterations: - keep_parity = None - else: - assert iteration == n_iterations - keep_parity = only_keep_parity - - # Perform a CG iteration step - combined_tensor = _combine_single_center( - tensor_1=combined_tensor, - tensor_2=nu1_tensor, - lambdas=lambdas, - cg_cache=cg_cache, - only_keep_parity=keep_parity, + # If the desired body order is 1, return the spherical expansion with + # standardized metadata + if nu_target == 1: + return nu1_tensor + + # Otherwise, perform CG iterations. First, construct explicit sigma and + # lambda filters for each iteration. Basic checks are performed here and + # errors raised if invalid filters are passed. + sigma_filter = _parse_sigma_filter(nu_target, sigma_filter) + lambda_filter = _parse_lambda_filter( + nu_target, rascal_hypers["max_angular"], lambda_filter, lambda_cut + ) + if debug: + print("sigma_filter: ", sigma_filter) + print("lambda_filter: ", lambda_filter) + + # Create a copy of the nu = 1 tensor to combine with itself and store its + # keys. + nux_tensor = nu1_tensor.copy() + nux_keys = nux_tensor.keys + nu1_keys = nu1_tensor.keys + + # Pre-compute all the information needed to combined tensors at every + # iteration. This includes the keys of the TensorMaps produced at each + # iteration, the keys of the blocks combined to make them, and block + # multiplicities. + combine_info = [] + for iteration in range(1, nu_target): + info = _create_combined_keys( + nux_keys, + nu1_keys, + lambda_filter[iteration - 1], + sigma_filter[iteration - 1], + ) + combine_info.append(info) + nux_keys = info[0] + + if debug: + print("Num. keys at each step: ", [len(c[0]) for c in combine_info]) + print([nu1_keys] + [c[0] for c in combine_info]) + + if np.any([len(c[0]) == 0 for c in combine_info]): + raise ValueError( + "invalid filters: one or more iterations produce no valid combinations." + f" Number of keys at each iteration: {[len(c[0]) for c in combine_info]}." + " Check the `lambda_filter` and `sigma_filter` arguments." ) + # Define the cached CG coefficients, either as sparse dicts or dense arrays + lambda_max = max( + rascal_hypers["max_angular"], + np.max(np.concatenate(lambda_filter).flatten()), + ) + # TODO: we know the lambda combinations in advance, so a more cleverly + # constructed CG cache could be used to reduce memory overhead + cg_cache = ClebschGordanReal(lambda_max, use_sparse) + + # Now combine block values until the target body order is reached + for iteration in range(1, nu_target): + if debug: + print(f"CG iteration {iteration}") + + # Combine pairs of blocks into new TensorBlocks of the correct lambda. + # Pass the correction factor accounting for the redundancy of "lx" + # combinations. + nux_keys = combine_info[iteration - 1][0] + nux_blocks = [] + for nux_key, key_1, key_2, multi in zip(*combine_info[iteration - 1]): + nux_blocks.append( + _combine_single_center_block_pair( + nux_tensor[key_1], + nu1_tensor[key_2], + nux_key["spherical_harmonics_l"], + cg_cache, + correction_factor=np.sqrt(multi), + ) + ) + + nux_tensor = TensorMap(nux_keys, nux_blocks) + # TODO: Account for body-order multiplicity and normalize block values - # combined_tensor = _apply_body_order_corrections(combined_tensor) - # combined_tensor = _normalize_blocks(combined_tensor) + # nux_tensor = _apply_body_order_corrections(nux_tensor) + # nux_tensor = _normalize_blocks(nux_tensor) # Move the [l1, l2, ...] keys to the properties if nu_target > 1: - combined_tensor = combined_tensor.keys_to_properties( + nux_tensor = nux_tensor.keys_to_properties( [f"l{i}" for i in range(1, nu_target + 1)] + [f"k{i}" for i in range(2, nu_target)] ) - return combined_tensor + return nux_tensor -def _combine_single_center( - tensor_1: TensorMap, - tensor_2: TensorMap, - lambdas: Sequence[int], - cg_cache, - only_keep_parity: Optional[int] = None, -) -> TensorMap: - """ - For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and 1 - respectively, combines their blocks to form a new TensorMap with body order - (nu + 1). Returns blocks only with angular orders corresponding to those - passed in ``lambdas``. +# def _combine_single_center( +# tensor_1: TensorMap, +# tensor_2: TensorMap, +# lambdas: Sequence[int], +# sigmas: Sequence[int], +# cg_cache, +# ) -> TensorMap: +# """ +# For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and 1 +# respectively, combines their blocks to form a new TensorMap with body order +# (nu + 1). - Assumes the metadata of the two TensorMaps are standaradized as follows. +# Returns blocks only indexed by keys . - The keys of `tensor_1` must follow the key name convention: +# Assumes the metadata of the two TensorMaps are standardized as follows. - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", - "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns - track the l values of the nu=1 blocks that were previously combined. The - "kx" columns tracks the intermediate lambda values of nu > 1 blocks that - haev been combined. +# The keys of `tensor_1` must follow the key name convention: - For instance, a TensorMap of body order nu=4 will have key names - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", - "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of - order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of - order "k2". This was combined with a nu=1 TensorMap with blocks of order - "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was - combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. +# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", +# "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns +# track the l values of the nu=1 blocks that were previously combined. The +# "kx" columns tracks the intermediate lambda values of nu > 1 blocks that +# haev been combined. - .. math :: +# For instance, a TensorMap of body order nu=4 will have key names +# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", +# "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of +# order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of +# order "k2". This was combined with a nu=1 TensorMap with blocks of order +# "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was +# combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. - \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n{\nu-1} l_{\nu-1} k_{\nu-1} ; - n{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } +# .. math :: - The keys of `tensor_2` must follow the key name convention: +# \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n{\nu-1} l_{\nu-1} k_{\nu-1} ; +# n{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] +# The keys of `tensor_2` must follow the key name convention: - Samples of pairs of blocks corresponding to the same chemical species are - equivalent in the two TensorMaps. Samples names are ["structure", "center"] +# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - Components names are [["spherical_harmonics_m"],] for each block. +# Samples of pairs of blocks corresponding to the same chemical species are +# equivalent in the two TensorMaps. Samples names are ["structure", "center"] - Property names are ["n1", "n2", ..., "species_neighbor_1", - "species_neighbor_2", ...] for each block. - """ +# Components names are [["spherical_harmonics_m"],] for each block. - # Get the correct keys for the combined output TensorMap - ( - combined_keys, - keys_1_entries, - keys_2_entries, - multiplicity_list, - ) = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas, only_keep_parity) - - # Iterate over pairs of blocks and combine - combined_blocks = [] - for combined_key, key_1, key_2, multi in zip( - combined_keys, keys_1_entries, keys_2_entries, multiplicity_list - ): - # Retrieve the blocks - block_1 = tensor_1[key_1] - block_2 = tensor_2[key_2] +# Property names are ["n1", "n2", ..., "species_neighbor_1", +# "species_neighbor_2", ...] for each block. +# """ - # Combine the blocks into a new TensorBlock of the correct lambda order. - # Pass the correction factor accounting for the redundancy of "lx" - # combinations. - combined_blocks.append( - _combine_single_center_block_pair( - block_1, - block_2, - combined_key["spherical_harmonics_l"], - cg_cache, - correction_factor=np.sqrt(multi), - ) - ) +# # Get the correct keys for the combined output TensorMap +# ( +# nux_keys, +# keys_1_entries, +# keys_2_entries, +# multiplicity_list, +# ) = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas, sigmas) - return TensorMap(combined_keys, combined_blocks) +# # Iterate over pairs of blocks and combine +# nux_blocks = [] +# for nux_key, key_1, key_2, multi in zip( +# nux_keys, keys_1_entries, keys_2_entries, multiplicity_list +# ): +# # Retrieve the blocks +# block_1 = tensor_1[key_1] +# block_2 = tensor_2[key_2] + +# # Combine the blocks into a new TensorBlock of the correct lambda order. +# # Pass the correction factor accounting for the redundancy of "lx" +# # combinations. +# nux_blocks.append( +# _combine_single_center_block_pair( +# block_1, +# block_2, +# nux_key["spherical_harmonics_l"], +# cg_cache, +# correction_factor=np.sqrt(multi), +# ) +# ) + +# return TensorMap(nux_keys, nux_blocks) def _combine_single_center_block_pair( @@ -713,7 +930,7 @@ def _create_combined_keys( keys_1: Labels, keys_2: Labels, lambdas: Sequence[int], - only_keep_parity: Optional[Union[int, dict]] = None, + sigmas: Sequence[int], ) -> Tuple[Labels, Sequence[Sequence[int]]]: """ Given the keys of 2 TensorMaps and a list of desired lambda values, creates @@ -758,12 +975,8 @@ def _create_combined_keys( corresponding key. The correction_factor terms are the prefactors that account for the redundancy in the CG combination. - The `only_keep_parity` argument can be used to return only keys with a - certain parity. Passing as None (default) will return both +1 and -1 parity - keys. Passing, for instance, `only_keep_parity=+1` will only return keys - with even parity. Warning: this should be used with caution if performing - multiple CG combination steps. Typically this functionality should only be - used in the last CG combination step. + The `sigmas` argument can be used to return only keys with certain + sigmas. This must be passed as a list with elements +1 and/or -1. """ # Get the body order of the first TensorMap. nu1 = np.unique(keys_1.column("order_nu"))[0] @@ -797,13 +1010,9 @@ def _create_combined_keys( == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] ) - # Check `only_keep_parity` argument - if only_keep_parity is None: - only_keep_parity = [+1, -1] - else: - assert isinstance(only_keep_parity, int) - assert only_keep_parity in [+1, -1] - only_keep_parity = [only_keep_parity] + # Check `sigmas` argument + assert isinstance(sigmas, Sequence) + assert np.all([s in [-1, +1] for s in sigmas]) # Define key names of output Labels (i.e. for combined TensorMap) new_names = ( @@ -833,8 +1042,8 @@ def _create_combined_keys( # Calculate new sigma sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) - # Skip keys that don't give the desired parity - if sig not in only_keep_parity: + # Skip keys that don't give the desired sigmas + if sig not in sigmas: continue # Extract the l and k lists from keys_1 @@ -850,12 +1059,12 @@ def _create_combined_keys( keys_2_entries.append(key_2) # Define new keys as the full product of keys_1 and keys_2 - combined_keys = Labels(names=new_names, values=np.array(new_key_values)) + nux_keys = Labels(names=new_names, values=np.array(new_key_values)) # Now account for multiplicty key_idxs_to_keep = [] mult_dict = {} - for key_idx, key in enumerate(combined_keys): + for key_idx, key in enumerate(nux_keys): # Get the important key values. This is all of the keys, excpet the k # list key_vals_slice = key.values[: 4 + (nu + 1)].tolist() @@ -881,7 +1090,7 @@ def _create_combined_keys( # Build a reduced Labels object for the combined keys, with redundancies removed combined_keys_red = Labels( names=new_names, - values=np.array([combined_keys[idx].values for idx in key_idxs_to_keep]), + values=np.array([nux_keys[idx].values for idx in key_idxs_to_keep]), ) # Create a of LabelsEntry objects that correspond to the original keys in @@ -891,7 +1100,7 @@ def _create_combined_keys( # Define the multiplicity of each key mult_list = [ - mult_dict[tuple(combined_keys[idx].values[: 4 + (nu + 1)].tolist())] + mult_dict[tuple(nux_keys[idx].values[: 4 + (nu + 1)].tolist())] for idx in key_idxs_to_keep ] diff --git a/python/rascaline/rascaline/utils/old_clebsch_gordan.py b/python/rascaline/rascaline/utils/old_clebsch_gordan.py index bb91c043d..3cc70355c 100644 --- a/python/rascaline/rascaline/utils/old_clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/old_clebsch_gordan.py @@ -6,13 +6,13 @@ """ import re -from typing import Optional, Sequence +from typing import Optional, Sequence, Union import numpy as np import wigners -import equistore -from equistore import Labels, TensorBlock, TensorMap +import metatensor +from metatensor import Labels, TensorBlock, TensorMap import rascaline @@ -627,11 +627,11 @@ def _remove_suffix(names, new_suffix=""): def lambda_soap_vector( frames: list, rascal_hypers: dict, - lambdas: Sequence[int], + lambda_filter: Optional[Union[None, Sequence[int]]] = None, + sigma_filter: Optional[Union[None, Sequence[int]]] = None, lambda_cut: Optional[int] = None, selected_samples: Optional[Labels] = None, neighbor_species: Optional[Sequence[int]] = None, - even_parity_only: bool = False, ) -> TensorMap: """ Takes a list of frames of ASE loaded frames and a dict of Rascaline @@ -713,26 +713,43 @@ def lambda_soap_vector( # Clean the lambda-SOAP TensorMap. Drop the order_nu key name as this is by # definition 2 for all keys. - lsoap = equistore.remove_dimension(lsoap, axis="keys", name="order_nu") + lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="order_nu") - # Drop all odd parity keys/blocks - if even_parity_only: + # # Drop all odd parity keys/blocks + # if even_parity_only: + # keys_to_drop = Labels( + # names=lsoap.keys.names, + # values=lsoap.keys.values[lsoap.keys.column("inversion_sigma") == -1], + # ) + # lsoap = metatensor.drop_blocks(lsoap, keys=keys_to_drop) + + # # Drop the inversion_sigma key name as this is now +1 for all blocks + # lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + + # Drop all blocks that don't correspond to the target lambdas + if lambda_filter is not None: keys_to_drop = Labels( names=lsoap.keys.names, - values=lsoap.keys.values[lsoap.keys.column("inversion_sigma") == -1], + values=lsoap.keys.values[ + [lam not in lambda_filter for lam in lsoap.keys.column("spherical_harmonics_l")] + ], ) - lsoap = equistore.drop_blocks(lsoap, keys=keys_to_drop) + lsoap = metatensor.drop_blocks(lsoap, keys=keys_to_drop) + + if sigma_filter is not None: + if len(sigma_filter) < 2: + keys_to_drop = Labels( + names=lsoap.keys.names, + values=lsoap.keys.values[ + [s not in sigma_filter for s in lsoap.keys.column("inversion_sigma")] + ], + ) + lsoap = metatensor.drop_blocks(lsoap, keys=keys_to_drop) + + if len(np.unique(lsoap.keys.column("inversion_sigma"))) == 1: + lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + - # Drop the inversion_sigma key name as this is now +1 for all blocks - lsoap = equistore.remove_dimension(lsoap, axis="keys", name="inversion_sigma") - # Drop all blocks that don't correspond to the target lambdas - keys_to_drop = Labels( - names=lsoap.keys.names, - values=lsoap.keys.values[ - [v not in lambdas for v in lsoap.keys.column("spherical_harmonics_l")] - ], - ) - lsoap = equistore.drop_blocks(lsoap, keys=keys_to_drop) return lsoap diff --git a/python/rascaline/rascaline/utils/rotations.py b/python/rascaline/rascaline/utils/rotations.py index f7fb7a5e4..016545d92 100644 --- a/python/rascaline/rascaline/utils/rotations.py +++ b/python/rascaline/rascaline/utils/rotations.py @@ -9,8 +9,8 @@ from scipy.spatial.transform import Rotation import torch -import equistore -from equistore import Labels, TensorBlock, TensorMap +import metatensor +from metatensor import Labels, TensorBlock, TensorMap import wigners diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index d2fbd17c5..0e013e305 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -13,8 +13,8 @@ "import numpy as np\n", "\n", "import rascaline\n", - "import equistore\n", - "from equistore import Labels, TensorBlock, TensorMap\n", + "import metatensor\n", + "from metatensor import Labels, TensorBlock, TensorMap\n", "import chemiscope\n", "\n", "# from rascaline.utils import clebsch_gordan\n", @@ -25,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -45,6 +45,24 @@ "# chemiscope.show(frames, mode=\"structure\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "clebsch_gordan.n_body_iteration_single_center(\n", + " frames[:1],\n", + " rascal_hypers,\n", + " nu_target=3,\n", + " # lambda_filter=[[0, 2], [2]],\n", + " # lambda_filter=8,\n", + " # sigma_filter=[1],\n", + " # lambda_cut=5,\n", + " # debug=False,\n", + ")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -54,56 +72,47 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Random rotation angles (rad): [5.94258177 1.33221078 0.11693219]\n", - "SO(3) EQUIVARIANT!\n" - ] - } - ], + "outputs": [], "source": [ - "# Define target lambda channels\n", - "lambdas = np.array([0, 2])\n", - "\n", - "# Generate Wigner-D matrices, initialized with random angles\n", - "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", - "print(\"Random rotation angles (rad):\", wig.angles)\n", - "\n", - "# Randomly rigidly rotate the frame\n", - "frames_so3 = [rotations.transform_frame_so3(frame, wig.angles) for frame in frames]\n", - "assert not np.allclose(frames[-1].positions, frames_so3[-1].positions)\n", - "\n", - "# Generate nu=3 descriptor for both frames\n", - "nu3 = clebsch_gordan.n_body_iteration_single_center(\n", - " frames,\n", - " rascal_hypers,\n", - " nu_target=3,\n", - " lambdas=lambdas,\n", - " only_keep_parity=+1,\n", - ")\n", - "\n", - "nu3_rot = clebsch_gordan.n_body_iteration_single_center(\n", - " frames_so3,\n", - " rascal_hypers,\n", - " nu_target=3,\n", - " lambdas=lambdas,\n", - " only_keep_parity=+1,\n", - ")\n", - "\n", - "# Rotate the lambda-SOAP descriptor of the unrotated frame\n", - "nu3_transformed = wig.transform_tensormap_so3(nu3)\n", - "\n", - "# Check for equivariance!\n", - "assert equistore.equal_metadata(nu3_transformed, nu3_rot)\n", - "assert equistore.allclose(nu3_transformed, nu3_rot)\n", - "print(\"SO(3) EQUIVARIANT!\")\n", - "\n", - "# chemiscope.show([frame, frame_so3], mode=\"structure\")" + "# # Define target lambda channels\n", + "# lambdas = np.array([0, 2])\n", + "\n", + "# # Generate Wigner-D matrices, initialized with random angles\n", + "# wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", + "# print(\"Random rotation angles (rad):\", wig.angles)\n", + "\n", + "# # Randomly rigidly rotate the frame\n", + "# frames_so3 = [rotations.transform_frame_so3(frame, wig.angles) for frame in frames]\n", + "# assert not np.allclose(frames[-1].positions, frames_so3[-1].positions)\n", + "\n", + "# # Generate nu=3 descriptor for both frames\n", + "# nu3 = clebsch_gordan.n_body_iteration_single_center(\n", + "# frames,\n", + "# rascal_hypers,\n", + "# nu_target=3,\n", + "# lambdas=lambdas,\n", + "# parities=[+1],\n", + "# )\n", + "\n", + "# nu3_rot = clebsch_gordan.n_body_iteration_single_center(\n", + "# frames_so3,\n", + "# rascal_hypers,\n", + "# nu_target=3,\n", + "# lambdas=lambdas,\n", + "# parities=[+1],\n", + "# )\n", + "\n", + "# # Rotate the lambda-SOAP descriptor of the unrotated frame\n", + "# nu3_transformed = wig.transform_tensormap_so3(nu3)\n", + "\n", + "# # Check for equivariance!\n", + "# assert metatensor.equal_metadata(nu3_transformed, nu3_rot)\n", + "# assert metatensor.allclose(nu3_transformed, nu3_rot)\n", + "# print(\"SO(3) EQUIVARIANT!\")\n", + "\n", + "# # chemiscope.show([frame, frame_so3], mode=\"structure\")" ] }, { @@ -115,21 +124,12 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Random rotation angles (rad): [2.07830924 0.730489 5.29996543]\n", - "O(3) EQUIVARIANT!\n" - ] - } - ], + "outputs": [], "source": [ "# Define target lambda channels\n", - "lambdas = np.array([0, 1, 2, 3, 4, 5])\n", + "lambda_filter = [0, 1, 2, 3, 4, 5]\n", "\n", "# Generate Wigner-D matrices, initialized with random angles\n", "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", @@ -143,26 +143,26 @@ "lsoap = clebsch_gordan.lambda_soap_vector(\n", " frames,\n", " rascal_hypers,\n", - " lambdas=lambdas,\n", - " only_keep_parity=+1,\n", + " lambda_filter=lambda_filter,\n", + " sigma_filter=[+1],\n", ")\n", "\n", "lsoap_o3 = clebsch_gordan.lambda_soap_vector(\n", " frames_o3,\n", " rascal_hypers,\n", - " lambdas=lambdas,\n", - " only_keep_parity=+1,\n", + " lambda_filter=lambda_filter,\n", + " sigma_filter=[+1],\n", ")\n", "\n", "# Apply the O(3) transformation to the TensorMap\n", "lsoap_transformed = wig.transform_tensormap_o3(lsoap)\n", "\n", "# Check for equivariance!\n", - "assert equistore.equal_metadata(lsoap_transformed, lsoap_o3)\n", - "assert equistore.allclose(lsoap_transformed, lsoap_o3)\n", + "assert metatensor.equal_metadata(lsoap_transformed, lsoap_o3)\n", + "assert metatensor.allclose(lsoap_transformed, lsoap_o3)\n", "print(\"O(3) EQUIVARIANT!\")\n", "\n", - "# chemiscope.show([frame, frame_o3], mode=\"structure\")" + "chemiscope.show(frames + frames_o3, mode=\"structure\")" ] }, { @@ -179,8 +179,8 @@ "outputs": [], "source": [ "# Define target lambda channels\n", - "lambdas = np.arange(6)\n", - "lambdas" + "lambda_filter = [0, 1, 2, 3, 4, 5]\n", + "lambda_filter" ] }, { @@ -192,8 +192,9 @@ "lsoap_old = old_clebsch_gordan.lambda_soap_vector(\n", " frames,\n", " rascal_hypers,\n", - " lambdas=lambdas,\n", - " lambda_cut=8,\n", + " # lambda_filter=lambda_filter,\n", + " # sigma_filter=[+1],\n", + " # lambda_cut=5,\n", ")\n", "lsoap_old" ] @@ -207,9 +208,9 @@ "lsoap_new = clebsch_gordan.lambda_soap_vector(\n", " frames,\n", " rascal_hypers,\n", - " lambdas=lambdas,\n", - " lambda_cut=8,\n", - " only_keep_parity=+1,\n", + " # lambda_filter=lambda_filter,\n", + " # sigma_filter=[+1],\n", + " # lambda_cut=5,\n", ")\n", "lsoap_new" ] @@ -265,7 +266,7 @@ "outputs": [], "source": [ "# Check sparse == dense\n", - "equistore.allclose(lsoap_new0, lsoap_new1)" + "metatensor.allclose(lsoap_new0, lsoap_new1)" ] }, { @@ -341,7 +342,7 @@ " use_sparse=False,\n", ")\n", "\n", - "assert equistore.allclose(tensor_dense, tensor_sparse)" + "assert metatensor.allclose(tensor_dense, tensor_sparse)" ] }, { @@ -431,11 +432,11 @@ "source": [ "\n", "# Manipulate metadata from old LSOAP\n", - "lsoap_old = equistore.permute_dimensions(lsoap_old0, axis=\"properties\", dimensions_indexes=[2, 5, 0, 1, 3, 4])\n", - "lsoap_old = equistore.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_1\", new=\"l1\")\n", - "lsoap_old = equistore.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_2\", new=\"l2\")\n", - "lsoap_old = equistore.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_1\", new=\"n1\")\n", - "lsoap_old = equistore.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_2\", new=\"n2\")\n", + "lsoap_old = metatensor.permute_dimensions(lsoap_old0, axis=\"properties\", dimensions_indexes=[2, 5, 0, 1, 3, 4])\n", + "lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_1\", new=\"l1\")\n", + "lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_2\", new=\"l2\")\n", + "lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_1\", new=\"n1\")\n", + "lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_2\", new=\"n2\")\n", "\n", "# Slice TM to symmetrize l-values\n", "sliced_blocks = []\n", @@ -445,16 +446,16 @@ " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", "\n", " # Slice block\n", - " sliced_block = equistore.slice_block(block, axis=\"properties\", labels=new_labels)\n", + " sliced_block = metatensor.slice_block(block, axis=\"properties\", labels=new_labels)\n", " sliced_blocks.append(sliced_block)\n", "\n", "# Check equal metadata\n", "lsoap_old = sort_tm(TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks))\n", "lsoap_new = sort_tm(lsoap_new0)\n", - "assert equistore.equal_metadata(lsoap_old, lsoap_new)\n", + "assert metatensor.equal_metadata(lsoap_old, lsoap_new)\n", "\n", "# Check equal values\n", - "equistore.allclose_raise(lsoap_old, lsoap_new)" + "metatensor.allclose_raise(lsoap_old, lsoap_new)" ] }, { @@ -467,7 +468,7 @@ " b1 = lsoap_old[key]\n", " b2 = lsoap_new[key]\n", "\n", - " print(key, equistore.allclose_block(b1, b2))" + " print(key, metatensor.allclose_block(b1, b2))" ] }, { @@ -500,7 +501,7 @@ " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", "\n", " # Slice block\n", - " sliced_blocks.append(equistore.slice_block(block, axis=\"properties\", labels=new_labels))\n", + " sliced_blocks.append(metatensor.slice_block(block, axis=\"properties\", labels=new_labels))\n", "\n", "lsoap_old_sliced = TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks)\n", "\n", @@ -512,12 +513,12 @@ " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", "\n", " # Slice block\n", - " sliced_blocks.append(equistore.slice_block(block, axis=\"properties\", labels=new_labels))\n", + " sliced_blocks.append(metatensor.slice_block(block, axis=\"properties\", labels=new_labels))\n", "\n", "lsoap_new_sliced = TensorMap(keys=lsoap_new.keys, blocks=sliced_blocks)\n", "\n", - "assert equistore.equal_metadata(lsoap_old_sliced, lsoap_new_sliced)\n", - "assert equistore.allclose_raise(lsoap_old_sliced, lsoap_new_sliced)" + "assert metatensor.equal_metadata(lsoap_old_sliced, lsoap_new_sliced)\n", + "assert metatensor.allclose_raise(lsoap_old_sliced, lsoap_new_sliced)" ] } ], diff --git a/python/rascaline/tests/utils/test_clebsch_gordan.py b/python/rascaline/tests/utils/test_clebsch_gordan.py index e72436dd0..a9a17b4a4 100644 --- a/python/rascaline/tests/utils/test_clebsch_gordan.py +++ b/python/rascaline/tests/utils/test_clebsch_gordan.py @@ -5,9 +5,9 @@ import numpy as np import rascaline -import equistore -from equistore import Labels, TensorBlock, TensorMap -import equistore.operations +import metatensor +from metatensor import Labels, TensorBlock, TensorMap +import metatensor.operations from rascaline.utils.clebsch_gordan import ClebschGordanReal, _clebsch_gordan_combine_dense, _clebsch_gordan_combine_sparse, n_body_iteration_single_center, _combine_single_center @@ -108,7 +108,7 @@ def test_n_body_iteration_single_center_dense_sparse_agree(self, lam_max): use_sparse=False ) - assert equistore.operations.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) + assert metatensor.operations.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) @pytest.mark.parametrize("l", [1]) @@ -135,10 +135,10 @@ def test_combine_single_center_orthogonality(self, l): keys_to_move = "species_neighbor" nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 - nu1_tensor = equistore.insert_dimension( + nu1_tensor = metatensor.insert_dimension( nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 ) - nu1_tensor = equistore.insert_dimension( + nu1_tensor = metatensor.insert_dimension( nu1_tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 ) combined_tensor = nu1_tensor.copy() @@ -255,7 +255,7 @@ def test_combine_single_center_orthogonality(self, l): # sliced_blocks = [] # for key, block in soap_cg.items(): # idx = block.properties.values[:, block.properties.names.index("l1")] != block.properties.values[:, block.properties.names.index("l2")] - # sliced_block = equistore.slice_block(block, "properties", Labels(names=block.properties.names, values=block.properties.values[idx])) + # sliced_block = metatensor.slice_block(block, "properties", Labels(names=block.properties.names, values=block.properties.values[idx])) # sliced_blocks.append(sliced_block) # # assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) From 2400451dba6c59d57e5e80f241213645c65c8197 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 28 Sep 2023 11:57:29 +0200 Subject: [PATCH 52/96] API redesign. Move CG coeffs to separate module. --- .../rascaline/utils/cg_coefficients.py | 169 +++ .../rascaline/utils/clebsch_gordan.py | 1347 +++++++---------- python/rascaline/rascaline/utils/tmp.ipynb | 635 ++++---- 3 files changed, 1045 insertions(+), 1106 deletions(-) create mode 100644 python/rascaline/rascaline/utils/cg_coefficients.py diff --git a/python/rascaline/rascaline/utils/cg_coefficients.py b/python/rascaline/rascaline/utils/cg_coefficients.py new file mode 100644 index 000000000..fa97733aa --- /dev/null +++ b/python/rascaline/rascaline/utils/cg_coefficients.py @@ -0,0 +1,169 @@ +""" +Module that stores the ClebschGordanReal class for computing CG coefficients. +""" +import numpy as np +import wigners + + +class ClebschGordanReal: + """ + Class for computing Clebsch-Gordan coefficients for real spherical + harmonics. + + Stores the coefficients in a dictionary in the `self.coeffs` attribute, + which is built at initialization. This dictionary has the form: + + { + (l1, l2, lambda): [ + np.ndarray([m1, m2, cg]), + ... + for m1 in range(-l1, l1 + 1), + for m2 in range(-l2, l2 + 1), + ], + ... + for lambda in range(0, l) + } + + where `cg`, i.e. the third value in each array, is the Clebsch-Gordan + coefficient that describes the combination of the `m1` irreducible + component of the `l1` angular channel and the `m2` irreducible component of + the `l2` angular channel into the irreducible tensor of order `lambda`. + """ + + def __init__(self, lambda_max: int, sparse: bool = True): + self._lambda_max = lambda_max + self._sparse = sparse + self._coeffs = ClebschGordanReal.build_coeff_dict( + self._lambda_max, self._sparse + ) + + @property + def lambda_max(self): + return self._lambda_max + + @property + def sparse(self): + return self._sparse + + @property + def coeffs(self): + return self._coeffs + + @staticmethod + def build_coeff_dict(lambda_max: int, sparse: bool): + """ + Builds a dictionary of Clebsch-Gordan coefficients for all possible + combination of l1 and l2, up to lambda_max. + """ + # real-to-complex and complex-to-real transformations as matrices + r2c = {} + c2r = {} + coeff_dict = {} + for lam in range(0, lambda_max + 1): + r2c[lam] = _real2complex(lam) + c2r[lam] = np.conjugate(r2c[lam]).T + + for l1 in range(lambda_max + 1): + for l2 in range(lambda_max + 1): + for lam in range( + max(l1, l2) - min(l1, l2), min(lambda_max, (l1 + l2)) + 1 + ): + complex_cg = _complex_clebsch_gordan_matrix(l1, l2, lam) + + real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( + complex_cg.shape + ) + + real_cg = real_cg.swapaxes(0, 1) + real_cg = (r2c[l2].T @ real_cg.reshape(2 * l2 + 1, -1)).reshape( + real_cg.shape + ) + real_cg = real_cg.swapaxes(0, 1) + + real_cg = real_cg @ c2r[lam].T + + if (l1 + l2 + lam) % 2 == 0: + cg_l1l2lam = np.real(real_cg) + else: + cg_l1l2lam = np.imag(real_cg) + + if sparse: + # if sparse we make a dictionary out of the matrix + nonzeros_cg_coeffs_idx = np.where(np.abs(cg_l1l2lam) > 1e-15) + cg_l1l2lam = { + (m1, m2, mu): cg_l1l2lam[m1, m2, mu] + for m1, m2, mu in zip(*nonzeros_cg_coeffs_idx) + } + coeff_dict[(l1, l2, lam)] = cg_l1l2lam + + return coeff_dict + + +def _real2complex(lam: int) -> np.ndarray: + """ + Computes a matrix that can be used to convert from real to complex-valued + spherical harmonics(coefficients) of order ``lam``. + + It's meant to be applied to the left, ``real2complex @ [-lam, ..., +lam]``. + """ + result = np.zeros((2 * lam + 1, 2 * lam + 1), dtype=np.complex128) + i_sqrt_2 = 1.0 / np.sqrt(2) + for m in range(-lam, lam + 1): + if m < 0: + result[lam - m, lam + m] = i_sqrt_2 * 1j * (-1) ** m + result[lam + m, lam + m] = -i_sqrt_2 * 1j + + if m == 0: + result[lam, lam] = 1.0 + + if m > 0: + result[lam + m, lam + m] = i_sqrt_2 * (-1) ** m + result[lam - m, lam + m] = i_sqrt_2 + + return result + + +def _complex_clebsch_gordan_matrix(l1, l2, lam): + r"""clebsch-gordan matrix + Computes the Clebsch-Gordan (CG) matrix for + transforming complex-valued spherical harmonics. + The CG matrix is computed as a 3D array of elements + < l1 m1 l2 m2 | lam mu > + where the first axis loops over m1, the second loops over m2, + and the third one loops over mu. The matrix is real. + For example, using the relation: + | l1 l2 lam mu > = \sum_{m1, m2} | l1 m1 > | l2 m2 > + (https://en.wikipedia.org/wiki/Clebsch–Gordan_coefficients, section + "Formal definition of Clebsch-Gordan coefficients", eq 2) + one can obtain the spherical harmonics lam from two sets of + spherical harmonics with l1 and l2 (up to a normalization factor). + E.g.: + Args: + l1: l number for the first set of spherical harmonics + l2: l number for the second set of spherical harmonics + lam: l number For the third set of spherical harmonics + Returns: + cg: CG matrix for transforming complex-valued spherical harmonics + >>> from scipy.special import sph_harm + >>> import numpy as np + >>> import wigners + ... + >>> C_112 = _complex_clebsch_gordan_matrix(1, 1, 2) + >>> comp_sph_1 = np.array([ + ... sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1+1) + ... ]) + >>> comp_sph_2 = np.array([sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1+1)]) + >>> # obtain the (unnormalized) spherical harmonics + >>> # with l = 2 by contraction over m1 and m2 + >>> comp_sph_2_u = np.einsum("ijk,i,j->k", C_112, comp_sph_1, comp_sph_2) + >>> # we can check that they differ from the spherical harmonics + >>> # by a constant factor + >>> comp_sph_2 = np.array([sph_harm(m, 2, 0.2, 0.2) for m in range(-2, 2+1)]) + >>> ratio = comp_sph_2 / comp_sph_2_u + >>> np.allclose(ratio[0], ratio) + True + """ + if np.abs(l1 - l2) > lam or np.abs(l1 + l2) < lam: + return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * lam + 1), dtype=np.double) + else: + return wigners.clebsch_gordan_array(l1, l2, lam) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index 4241185b2..071f3842a 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -2,707 +2,597 @@ Module for computing Clebsch-gordan iterations with metatensor TensorMaps. """ import itertools -from typing import Optional, Sequence, Tuple, Union +from typing import Optional, List, Tuple, Union -import ase import numpy as np import metatensor from metatensor import Labels, TensorBlock, TensorMap -import rascaline -import wigners +from cg_coefficients import ClebschGordanReal -# TODO: -# - [ ] Add support for dense operation -# - [ ] Account for body-order multiplicity -# - [ ] Gradients? -# - [ ] Unit tests -# - [ ] Check the combined keys - potential bug (!) +# TODO: this PR +# - [ ] `combine_single_center_to_body_order`: feedback on API design +# - [ ] Unit tests - metadata and maths +# - [ ] Thorough documentation +# - [ ] Implement `combine_single_center_one_iteration` +# TODO: later PRs, in roughly chronological order +# - [ ] Use dispatch for numpy/torch CG combination +# - [ ] Add support for gradients +# - [ ] Integrate with `sparse_accumulation` +# - [ ] Extend to multi-center desciptors +# - [ ] Customizable and arbitrary (non)linear transformations at each iteration -# ===== Class for calculating Clebsch-Gordan coefficients ===== +# ====================================================================== +# ===== Functions to do CG combinations on single-center descriptors +# ====================================================================== -class ClebschGordanReal: + +def lambda_soap_vector( + nu_1_tensor: TensorMap, + angular_cutoff: Optional[int] = None, + angular_selection: Optional[Union[None, int, List[int]]] = None, + parity_selection: Optional[Union[None, int, List[int]]] = None, +) -> TensorMap: """ - Class for computing Clebsch-Gordan coefficients for real spherical - harmonics. - - Stores the coefficients in a dictionary in the `self.coeffs` attribute, - which is built at initialization. This dictionary has the form: - - { - (l1, l2, lambda): [ - np.ndarray([m1, m2, cg]), - ... - for m1 in range(-l1, l1 + 1), - for m2 in range(-l2, l2 + 1), - ], - ... - for lambda in range(0, l) - } - - where `cg`, i.e. the third value in each array, is the Clebsch-Gordan - coefficient that describes the combination of the `m1` irreducible - component of the `l1` angular channel and the `m2` irreducible component of - the `l2` angular channel into the irreducible tensor of order `lambda`. + A higher-level wrapper for the :py:func:`n_body_iteration_single_center` + function specifically for generating a lambda-SOAP vector in the metatensor + format. + + A nu = 1 (i.e. 2-body) single-center descriptor is taken as input and + conbined with itself in a single CG combination step to form a nu = 2 + (3-body) single-center descriptor, i.e. lambda-SOAP. Only the target angular + channels given in `angular_selection` and target parities given in + `parity_selection` are returned. + + The input `nu_1_tensor` may be, for instance, a rascaline.SphericalExpansion + or rascaline.LodeSphericalExpansion. + + This function differs from :py:func`combine_single_center_to_body_order` in + that the returned TensorMap has the redundant "order_nu" key dimension + removed (which is by definition 2 for lambda-SOAP), and if a single parity + is selected, the redundant "inversion_sigma" key dimension too. """ - - def __init__(self, lambda_max: int, sparse: bool = True): - self._lambda_max = lambda_max - self._sparse = sparse - self._coeffs = ClebschGordanReal.build_coeff_dict( - self._lambda_max, self._sparse + if np.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): + raise NotImplementedError( + "CG combinations of gradients not currently supported. Check back soon." ) + # Generate lambda-SOAP + lsoap = combine_single_center_to_body_order( + nu_1_tensor=nu_1_tensor, + target_body_order=2, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + use_sparse=True, + ) - @property - def lambda_max(self): - return self._lambda_max - - @property - def sparse(self): - return self._sparse - - @property - def coeffs(self): - return self._coeffs - - @staticmethod - def build_coeff_dict(lambda_max: int, sparse: bool): - """ - Builds a dictionary of Clebsch-Gordan coefficients for all possible - combination of l1 and l2, up to lambda_max. - """ - # real-to-complex and complex-to-real transformations as matrices - r2c = {} - c2r = {} - coeff_dict = {} - for lam in range(0, lambda_max + 1): - r2c[lam] = _real2complex(lam) - c2r[lam] = np.conjugate(r2c[lam]).T - - for l1 in range(lambda_max + 1): - for l2 in range(lambda_max + 1): - for lam in range( - max(l1, l2) - min(l1, l2), min(lambda_max, (l1 + l2)) + 1 - ): - complex_cg = _complex_clebsch_gordan_matrix(l1, l2, lam) - - real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( - complex_cg.shape - ) - - real_cg = real_cg.swapaxes(0, 1) - real_cg = (r2c[l2].T @ real_cg.reshape(2 * l2 + 1, -1)).reshape( - real_cg.shape - ) - real_cg = real_cg.swapaxes(0, 1) - - real_cg = real_cg @ c2r[lam].T - - if (l1 + l2 + lam) % 2 == 0: - cg_l1l2lam = np.real(real_cg) - else: - cg_l1l2lam = np.imag(real_cg) - - if sparse: - # if sparse we make a dictionary out of the matrix - nonzeros_cg_coeffs_idx = np.where(np.abs(cg_l1l2lam) > 1e-15) - cg_l1l2lam = { - (m1, m2, mu): cg_l1l2lam[m1, m2, mu] - for m1, m2, mu in zip(*nonzeros_cg_coeffs_idx) - } - coeff_dict[(l1, l2, lam)] = cg_l1l2lam + # Drop the redundant key name "order_nu". This is by definition 2 for all + # lambda-SOAP blocks. + lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="order_nu") - return coeff_dict + # If a single parity is requested, drop the now redundant "inversion_sigma" + # key name + if len(np.unique(lsoap.keys.column("inversion_sigma"))) == 1: + lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + return lsoap -def _real2complex(lam: int) -> np.ndarray: - """ - Computes a matrix that can be used to convert from real to complex-valued - spherical harmonics(coefficients) of order ``lam``. - It's meant to be applied to the left, ``real2complex @ [-lam, ..., +lam]``. +def combine_single_center_to_body_order( + nu_1_tensor: TensorMap, + target_body_order: int, + angular_cutoff: Optional[int] = None, + angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + use_sparse: bool = True, + return_metadata_only: bool = False, +) -> TensorMap: """ - result = np.zeros((2 * lam + 1, 2 * lam + 1), dtype=np.complex128) - - I_SQRT_2 = 1.0 / np.sqrt(2) - - for m in range(-lam, lam + 1): - if m < 0: - result[lam - m, lam + m] = I_SQRT_2 * 1j * (-1) ** m - result[lam + m, lam + m] = -I_SQRT_2 * 1j - - if m == 0: - result[lam, lam] = 1.0 - - if m > 0: - result[lam + m, lam + m] = I_SQRT_2 * (-1) ** m - result[lam - m, lam + m] = I_SQRT_2 - - return result - - -def _complex_clebsch_gordan_matrix(l1, l2, lam): - r"""clebsch-gordan matrix - Computes the Clebsch-Gordan (CG) matrix for - transforming complex-valued spherical harmonics. - The CG matrix is computed as a 3D array of elements - < l1 m1 l2 m2 | lam mu > - where the first axis loops over m1, the second loops over m2, - and the third one loops over mu. The matrix is real. - For example, using the relation: - | l1 l2 lam mu > = \sum_{m1, m2} | l1 m1 > | l2 m2 > - (https://en.wikipedia.org/wiki/Clebsch–Gordan_coefficients, section - "Formal definition of Clebsch-Gordan coefficients", eq 2) - one can obtain the spherical harmonics lam from two sets of - spherical harmonics with l1 and l2 (up to a normalization factor). - E.g.: - Args: - l1: l number for the first set of spherical harmonics - l2: l number for the second set of spherical harmonics - lam: l number For the third set of spherical harmonics - Returns: - cg: CG matrix for transforming complex-valued spherical harmonics - >>> from scipy.special import sph_harm - >>> import numpy as np - >>> import wigners - ... - >>> C_112 = _complex_clebsch_gordan_matrix(1, 1, 2) - >>> comp_sph_1 = np.array([ - ... sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1+1) - ... ]) - >>> comp_sph_2 = np.array([sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1+1)]) - >>> # obtain the (unnormalized) spherical harmonics - >>> # with l = 2 by contraction over m1 and m2 - >>> comp_sph_2_u = np.einsum("ijk,i,j->k", C_112, comp_sph_1, comp_sph_2) - >>> # we can check that they differ from the spherical harmonics - >>> # by a constant factor - >>> comp_sph_2 = np.array([sph_harm(m, 2, 0.2, 0.2) for m in range(-2, 2+1)]) - >>> ratio = comp_sph_2 / comp_sph_2_u - >>> np.allclose(ratio[0], ratio) - True + Takes a nu = 1 (i.e. 2-body) single-center descriptor and combines it + iteratively with itself to generate a descriptor of order + ``target_body_order``. """ - if np.abs(l1 - l2) > lam or np.abs(l1 + l2) < lam: - return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * lam + 1), dtype=np.double) - else: - return wigners.clebsch_gordan_array(l1, l2, lam) + if target_body_order < 1: + raise ValueError("`target_body_order` must be > 0") + if np.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): + raise NotImplementedError( + "CG combinations of gradients not currently supported. Check back soon." + ) -# ===== Methods for performing CG combinations ===== + # Standardize the metadata of the input tensor + nu_1_tensor = _standardize_tensor_metadata(nu_1_tensor) + + # If the desired body order is 1, return the input tensor with standardized + # metadata. + if target_body_order == 1: + return nu_1_tensor + + # Pre-compute the metadata needed to perform each CG iteration + # Current design choice: only combine a nu = 1 tensor iteratively with + # itself, i.e. nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. + parity_selection = _parse_selection_filters( + n_iterations=target_body_order - 1, + selection_type="parity", + selection=parity_selection, + ) + angular_selection = _parse_selection_filters( + n_iterations=target_body_order - 1, + selection_type="angular", + selection=angular_selection, + angular_cutoff=angular_cutoff, + ) + combination_metadata = _precompute_metadata( + nu_1_tensor.keys, + nu_1_tensor.keys, + n_iterations=target_body_order - 1, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + ) + # For debugging it might be useful just to return the keys of the final TensorMap + if return_metadata_only: + return combination_metadata + + # Define the cached CG coefficients, either as sparse dicts or dense arrays. + # TODO: we pre-computed the combination metadata, so a more cleverly + # constructed CG cache could be used to reduce memory overhead - i.e. we + # don't necessarily need *all* CG coeffs up to `angular_max`, just the ones + # that are actually used. + angular_max = np.max( + np.concatenate( + [nu_1_tensor.keys.column("spherical_harmonics_l")] + + [ + metadata[0].column("spherical_harmonics_l") + for metadata in combination_metadata + ] + ) + ) + cg_cache = ClebschGordanReal(angular_max, use_sparse) + + # Create a copy of the nu = 1 tensor to combine with itself + nu_x_tensor = nu_1_tensor.copy() + + # Iteratively combine block values + for iteration in range(target_body_order - 1): + # Combine blocks + nu_x_blocks = [] + # TODO: is there a faster way of iterating over keys/blocks here? + for nu_x_key, key_1, key_2, multi in zip(*combination_metadata[iteration]): + # Combine the pair of block values, accounting for multiplicity + nu_x_block = _combine_single_center_blocks( + nu_x_tensor[key_1], + nu_1_tensor[key_2], + nu_x_key["spherical_harmonics_l"], + cg_cache, + correction_factor=np.sqrt(multi), + ) + nu_x_blocks.append(nu_x_block) + nu_x_keys = combination_metadata[iteration][0] + nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) + + # TODO: multiplicity and/or normalization here? + + # Move the [l1, l2, ...] keys to the properties + if target_body_order > 1: + nu_x_tensor = nu_x_tensor.keys_to_properties( + [f"l{i}" for i in range(1, target_body_order + 1)] + + [f"k{i}" for i in range(2, target_body_order)] + ) -# ===== Fxns for combining multi center descriptors ===== + return nu_x_tensor -def _combine_multi_centers( +def combine_single_center_one_iteration( tensor_1: TensorMap, tensor_2: TensorMap, - lambdas: Sequence[int], - cg_cache, + angular_cutoff: Optional[int] = None, + angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, use_sparse: bool = True, ) -> TensorMap: - """ """ + """ + Takes two single-center descriptors of arbitrary body order and combines + them in a single CG combination step. + """ + # TODO: implement! raise NotImplementedError -def _combine_multi_centers_block_pair( - block_1: TensorBlock, - block_2: TensorBlock, - lam: int, - cg_cache, - use_sparse: bool = True, -) -> TensorMap: - """ """ - raise NotImplementedError +# ================================================================== +# ===== Functions to handle metadata +# ================================================================== -# ===== Fxns for combining single center descriptors ===== +def _standardize_tensor_metadata(tensor: TensorMap) -> TensorMap: + """ + Takes a nu=1 tensor and standardizes its metadata. This involves: 1) moving + the "species_neighbor" key to properties, if present as a dimension in the + keys, and 2) adding dimensions in the keys for tracking the body order + ("order_nu") and parity ("inversion_sigma") of the blocks. + Checking for the presence of the "species_neighbor" key in the keys allows + the option of the user pre-moving this key to the properties before calling + `n_body_iteration_single_center`, allowing sparsity in a set of global + neighbors to be created if desired. -def lambda_soap_vector( - frames: Sequence[ase.Atoms], - rascal_hypers: dict, - lambda_filter: Optional[Union[None, int, Sequence[int]]] = None, - sigma_filter: Optional[Union[None, int, Sequence[int]]] = None, - lambda_cut: Optional[int] = None, - selected_samples: Optional[Labels] = None, - species_neighbors: Optional[Sequence[int]] = None, -) -> TensorMap: + Assumes that the input `tensor` is nu=1, and has only even parity blocks. """ - A higher-level wrapper for the :py:func:`n_body_iteration_single_center` - function specifically for generating lambda-SOAP vectors in the metatensor - format, with some added metadata manipulation. - - The hyperparameters `rascal_hypers` are used to generate a nu=1 - SphericalExpansion object with rascaline, and these are then combined with a - single Clebsch-Gordan iteration step to form the nu=2 lambda-SOAP - descriptor. Only the target spherical channels given in `lambdas` are - calculated and returned. - - `lambda_cut` can be set to reduce the memory overhead of the calculation, at - the cost of loss of information. The theoretical maximum (and default) value - is nu_target * rascal_hypers["max_angular"], though a lower value can be - set. `nu_target` is the target body-order of the descriptor (by definition - nu=2 for lambda-SOAP). Using the default (and theoretical maximum) value can - lead to memory blow-up for large systems and hgih body-orders, so this value - needs to be tailored for the computation and system. Note that truncating - this value to less than the default will lead to some information loss. - - If `sigmas` is passed, then only the specified sigmas are - returned in the output TensorMap. For instance, passing as an int +1 means - only blocks with even sigmas will be returned. If a dict, the sigmas kept - for each target lambda can be specified. Any lambdasIf false, all blocks of both odd - and even sigmas are returned. In the latter case, the output TensorMap will - have a key dimension "inversion_sigma" that tracks the sigmas. - """ - # Generate lambda-SOAP using rascaline.utils - lsoap = n_body_iteration_single_center( - frames, - rascal_hypers=rascal_hypers, - nu_target=2, - lambda_filter=lambda_filter, - sigma_filter=sigma_filter, - lambda_cut=lambda_cut, - selected_samples=selected_samples, - species_neighbors=species_neighbors, - use_sparse=True, + if "species_neighbor" in tensor.keys.names: + tensor = tensor.keys_to_properties(keys_to_move="species_neighbor") + tensor = metatensor.insert_dimension( + tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 ) - - # Drop the redundant key name "order_nu". This is by definition 2 for all - # lambda-SOAP blocks. - lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="order_nu") - - # If a single sigmas is requested, drop the now redundant "inversion_sigma" - # key name - if len(np.unique(lsoap.keys.column("inversion_sigma"))) == 1: - lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="inversion_sigma") - - return lsoap + tensor = metatensor.insert_dimension( + tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 + ) + return tensor -def _parse_sigma_filter( - nu_target: int, - sigma_filter: Union[None, int, Sequence[int], Sequence[Sequence[int]]], -) -> Sequence[Sequence[int]]: +def _parse_selection_filters( + n_iterations: int, + selection_type: str = "parity", + selection: Union[None, int, List[int], List[List[int]]] = None, + angular_cutoff: Optional[int] = None, +) -> List[Union[None, List[int]]]: """ - Returns parity filters for each CG combination step of a nu=1 tensor with - itself up to the target body order. + Returns a list of length `n_iterations` with selection filters for each CG + combination step, for either `selection_type` "parity" or `selection_type` + "angular". For a given iteration, if no selection is to be applied, the + element of the returned list will be None. - If a filter isn't specified by the user with `sigma_filter=None`, then no - filter is applied, i.e. [-1, +1] is used at every iteration. + The input argument `selection` will be parsed in the following ways. - If a single sequence of int is specified, then this is used for the last - iteration only, and [-1, +1] is used for all intermediate iterations. For - example, if `nu_target=4` and `sigma_filter=[+1]`, then the filter [[-1, - +1], [-1, +1], [+1]] is returned. + If `selection=None` is passed, then no filter is applied at any iteration. A + list of [None, None, ...] is returned. - If a sequence of sequences of int is specified, then this is assumed to be - the desired filter for each iteration and is only checked for validity - without modification. - """ - if nu_target < 2: - raise ValueError("`nu_target` must be > 1") + If an `int` or single List[int] is specified, then this is used for the last + iteration only. For example, if `n_iterations=3` and `selection=[+1]`, then + the filter [None, None, [+1]] is returned. - # No filter specified: use [-1, +1] for all iterations - if sigma_filter is None: - sigma_filter = [[-1, +1] for _ in range(nu_target - 1)] + If a List[List[int]] is passed, then this is assumed to be the desired + filter for each iteration, and is not modified. - # Parse user-defined filter: assume passed as Sequence[int] or - # Sequence[Sequence[int]] - else: - if isinstance(sigma_filter, int): - sigma_filter = [sigma_filter] - if not isinstance(sigma_filter, Sequence): - raise TypeError( - "`sigma_filter` must be an int, Sequence[int], or Sequence[Sequence[int]]" + Basic checks are performed. ValueError is raised if specified parity + selections are not in [-1, +1], or if specified angular selections are not + >= 0. + """ + if angular_cutoff is not None: + if selection_type != "angular": + raise ValueError( + "`selection_type` must be 'angular' if specifying `angular_cutoff`" ) - # Single filter: apply on last iteration only, use both sigmas for - # intermediate iterations - if np.all([isinstance(sigma, int) for sigma in sigma_filter]): - sigma_filter = [[-1, +1] for _ in range(nu_target - 2)] + [sigma_filter] - + if angular_cutoff < 1: + raise ValueError("`angular_cutoff` must be >= 1") + if selection is None: + selection = [None] * n_iterations + else: + # If passed as int, use this for the last iteration only + if isinstance(selection, int): + selection = [None] * (n_iterations - 1) + [[selection]] else: - # Assume filter explicitly defined for each iteration (checked below) - pass - - # Check sigma_filter - assert isinstance(sigma_filter, Sequence) - assert len(sigma_filter) == nu_target - 1 - assert np.all([isinstance(filt, Sequence) for filt in sigma_filter]) - assert np.all([np.all([s in [-1, +1] for s in filt]) for filt in sigma_filter]) + if not isinstance(selection, List): + raise TypeError( + "`selection` must be an int, List[int], or List[List[int]]" + ) + if isinstance(selection[0], int): + selection = [None] * (n_iterations - 1) + [selection] + + # Basic checks + if not isinstance(selection, List): + raise TypeError("`selection` must be an int, List[int], or List[List[int]]") + for slct in selection: + if slct is not None: + if not np.all([isinstance(val, int) for val in slct]): + raise TypeError( + "`selection` must be an int, List[int], or List[List[int]]" + ) + if selection_type == "parity": + if not np.all([val in [-1, +1] for val in slct]): + raise ValueError( + "specified layers in `selection` must only contain valid" + " parity values of -1 or +1" + ) + if not np.all([0 < len(slct) <= 2]): + raise ValueError( + "each parity filter must be a list of length 1 or 2," + " with vals +1 and/or -1" + ) + elif selection_type == "angular": + if not np.all([val >= 0 for val in slct]): + raise ValueError( + "specified layers in `selection` must only contain valid" + " angular channels >= 0" + ) + if angular_cutoff is not None: + if not np.all([val <= angular_cutoff for val in slct]): + raise ValueError( + "specified layers in `selection` must only contain valid" + " angular channels <= the specified `angular_cutoff`" + ) + else: + raise ValueError( + "`selection_type` must be either 'parity' or 'angular'" + ) - return sigma_filter + return selection -def _parse_lambda_filter( - nu_target: int, - rascal_max_l: int, - lambda_filter: Union[None, int, Sequence[int], Sequence[Sequence[int]]], - lambda_cut: Union[None, int], -) -> Sequence[Sequence[int]]: +def _precompute_metadata( + keys_1: Labels, + keys_2: Labels, + n_iterations: int, + angular_cutoff: Optional[int] = None, + angular_selection: Optional[List[Union[None, List[int]]]] = None, + parity_selection: Optional[List[Union[None, List[int]]]] = None, +) -> List[Tuple[Labels, List[List[int]]]]: """ - Returns parity filters for each CG combination step of a nu=1 tensor with - itself up to the target body order. - - If a filter isn't specified by the user with `lambda_filter=None`, then no - filter is applied. In this case all possible lambda channels are retained at - each iteration. For example, if `nu_target=4`, `rascal_max_l=5`, and - `lambda_cut=None`, then the returned filter is [[0, ..., 10], [0, ..., 15], - [0, ..., 20]]. If `nu_target=4`, `rascal_max_l=5`, and `lambda_cut=10`, then - the returned filter is [[0, ..., 10], [0, ..., 10], [0, ..., 10]]. - - If `lambda_filter` is passed a single sequence of int, then this is used for - the last iteration only, and all possible combinations of lambda are used in - intermediate iterations. For instance, if `lambda_filter=[0, 1, 2]`, the - returned filters for the 2 examples above, respectively, would be [[0, ..., - 10], [0, ..., 15], [0, 1, 2]] and [[0, ..., 10], [0, ..., 10], [0, 1, 2]]. - - If a sequence of sequences of int is specified, then this is assumed to be - the desired filter for each iteration and is only checked for validity - without modification. + Computes all the metadata needed to perform `n_iterations` of CG combination + steps, based on the keys of the 2 tensors being combined (`keys_1` and + `keys_2`), the maximum angular channel cutoff (`angular_cutoff`), and the + angular (`angular_selection`) and parity (`parity_selection`) selections to + be applied at each iteration. """ - if nu_target < 2: - raise ValueError("`nu_target` must be > 1") + comb_metadata = [] + new_keys = keys_1 + for iteration in range(n_iterations): + # Get the metadata for the combination of the 2 tensors + i_comb_metadata = _precompute_metadata_one_iteration( + keys_1=new_keys, + keys_2=keys_2, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection[iteration], + parity_selection=parity_selection[iteration], + ) + new_keys = i_comb_metadata[0] - # Check value of lambda_cut - if lambda_cut is not None: - if not (rascal_max_l <= lambda_cut <= nu_target * rascal_max_l): + # Check that some keys are produced as a result of the combination + if len(new_keys) == 0: raise ValueError( - "`lambda_cut` must be >= `rascal_hypers['max_angular']` and <= `nu_target`" - " * `rascal_hypers['max_angular']`" + f"invalid selections: iteration {iteration + 1} produces no valid combinations." + " Check the `angular_selection` and `parity_selection` arguments." ) - # No filter specified: retain all possible lambda channels for every - # iteration, up to lambda_cut (if specified) - if lambda_filter is None: - if lambda_cut is None: - # Use the full range of possible lambda channels for each iteration. - # This is dependent on the itermediate body order. - lambda_filter = [ - [lam for lam in range(0, (nu * rascal_max_l) + 1)] - for nu in range(2, nu_target + 1) - ] - else: - # Use the full range of possible lambda channels for each iteration, - # but only up to lambda_cut, independent of the intermediate body - # order. - lambda_filter = [ - [lam for lam in range(0, lambda_cut + 1)] - for nu in range(2, nu_target + 1) - ] + # Now check the angular and parity selections are present in the new keys + if angular_selection is not None: + if angular_selection[iteration] is not None: + for lam in angular_selection[iteration]: + if lam not in new_keys.column("spherical_harmonics_l"): + raise ValueError( + f"lambda = {lam} specified in `angular_selection` for iteration" + f" {iteration + 1}, but this is not a valid angular channel based on" + " the combination of lower body-order tensors. Check the passed" + " `angular_selection` and try again." + ) + if parity_selection is not None: + if parity_selection[iteration] is not None: + for sig in parity_selection[iteration]: + if sig not in new_keys.column("inversion_sigma"): + raise ValueError( + f"sigma = {sig} specified in `parity_selection` for iteration" + f" {iteration + 1}, but this is not a valid parity based on the" + " combination of lower body-order tensors. Check the passed" + " `parity_selection` and try again." + ) + + comb_metadata.append(i_comb_metadata) + + return comb_metadata + + +def _precompute_metadata_one_iteration( + keys_1: Labels, + keys_2: Labels, + angular_cutoff: Optional[int] = None, + angular_selection: Optional[Union[None, List[int]]] = None, + parity_selection: Optional[Union[None, List[int]]] = None, +) -> Tuple[Labels, List[List[int]]]: + """ + Given the keys of 2 TensorMaps, returns the keys that would be present after + a CG combination of these TensorMaps. - # Parse user-defined filter: assume passed as Sequence[int] or - # Sequence[Sequence[int]] - else: - if isinstance(lambda_filter, int): - lambda_filter = [lambda_filter] - if not isinstance(lambda_filter, Sequence): - raise TypeError( - "`lambda_filter` must be an int, Sequence[int], or Sequence[Sequence[int]]" - ) - # Single filter: apply on last iteration only, use all possible lambdas for - # intermediate iterations (up to lambda_cut, if specified) - if np.all([isinstance(filt, int) for filt in lambda_filter]): - if lambda_cut is None: - # Use the full range of possible lambda channels for each iteration. - # This is dependent on the itermediate body order. - lambda_filter = [ - [lam for lam in range(0, (nu * rascal_max_l) + 1)] - for nu in range(2, nu_target) - ] + [lambda_filter] + Any angular or parity channel selections passed in `angular_selection` and + `parity_selection` are applied such that only specified channels are present + in the returned combined keys. - else: - # Use the full range of possible lambda channels for each iteration, - # but only up to lambda_cut, independent of the intermediate body - # order. - lambda_filter = [ - [lam for lam in range(0, lambda_cut + 1)] - for nu in range(2, nu_target) - ] + [lambda_filter] + Assumes that `keys_1` corresponds to a TensorMap with arbitrary body order, + while `keys_2` corresponds to a TensorMap with body order 1. - else: - # Assume filter explicitly defined for each iteration (checked below) - pass + `keys_1` must follow the key name convention: - # Check lambda_filter - if not isinstance(lambda_filter, Sequence): - raise TypeError( - "`lambda_filter` must be an int, Sequence[int], or Sequence[Sequence[int]]" - ) - if len(lambda_filter) != nu_target - 1: - raise ValueError( - "`lambda_filter` must have length `nu_target` - 1, i.e. the number of CG" - " iterations required to reach `nu_target`" - ) - if not np.all([isinstance(filt, Sequence) for filt in lambda_filter]): - raise TypeError( - "`lambda_filter` must be an int, Sequence[int], or Sequence[Sequence[int]]" - ) - # Check the lambda values are within the possible range, based on each - # intermediate body order - if not np.all( - [ - np.all([0 <= lam <= nu * rascal_max_l for lam in filt]) - for nu, filt in enumerate(lambda_filter, start=2) - ] - ): + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", + "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns + track the l values of the nu=1 blocks that were previously combined. The + "kx" columns tracks the intermediate lambda values of nu > 1 blocks that + have been combined. + + For instance, a TensorMap of body order nu=4 will have key names + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", + "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of + order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of + order "k2". This was combined with a nu=1 TensorMap with blocks of order + "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was + combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. + + .. math :: + + \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n_{\nu-1} l_{\nu-1} k_{\nu-1} ; + n_{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } + + `keys_2` must follow the key name convention: + + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + + Returned is a tuple. + + The first element in the tuple is a Labels object corresponding to the keys + created by a CG combination step. + + The second element is a list of list of ints. Each sublist corresponds to + [lam1, lam2, correction_factor] terms. lam1 and lam2 tracks the lambda + values of the blocks that combine to form the block indexed by the + corresponding key. The correction_factor terms are the prefactors that + account for the redundancy in the CG combination. + + The `parity_selection` argument can be used to return only keys with certain + parities. This must be passed as a list with elements +1 and/or -1. + """ + # Get the body order of the first TensorMap. + unique_nu = np.unique(keys_1.column("order_nu")) + if len(unique_nu) > 1: raise ValueError( - "All lambda values in `lambda_filter` must be >= 0 and <= `nu` *" - " `rascal_hypers['max_angular']`, where `nu` is the body" - " order created in the intermediate CG combination step" + "keys_1 must correspond to a tensor of a single body order." + f" Found {len(unique_nu)} body orders: {unique_nu}" ) - # Now check that at each iteration the lambda values can actually be created - # from combination at the previous iteration - for filt_i, filt in enumerate(lambda_filter): - if filt_i == 0: - # Assume that the original nu=1 tensors to be combined have all l up - # to and including `rascal_max_l` - allowed_lams = np.arange(0, (2 * rascal_max_l) + 1) - else: - allowed_lams = [] - for l1, l2 in itertools.product(lambda_filter[filt_i - 1], repeat=2): - for lam in range(abs(l1 - l2), abs(l1 + l2) + 1): - allowed_lams.append(lam) + nu1 = unique_nu[0] - allowed_lams = np.unique(allowed_lams) + # Define nu value of output TensorMap + nu = nu1 + 1 - if not np.all([lam in allowed_lams for lam in filt]): - raise ValueError( - f"invalid lambda values in `lambda_filter` for iteration {filt_i + 1}." - f" {filt} cannot be created by combination of previous lambda values" - f" {lambda_filter[filt_i - 1]}" - ) + # The body order of the second TensorMap should be nu = 1. + assert np.all(keys_2.column("order_nu") == 1) - return lambda_filter - - -def n_body_iteration_single_center( - frames: Sequence[ase.Atoms], - rascal_hypers: dict, - nu_target: int, - lambda_filter: Optional[ - Union[None, int, Sequence[int], Sequence[Sequence[int]]] - ] = None, - sigma_filter: Optional[ - Union[None, int, Sequence[int], Sequence[Sequence[int]]] - ] = None, - lambda_cut: Optional[int] = None, - selected_samples: Optional[Labels] = None, - species_neighbors: Optional[Sequence[int]] = None, - use_sparse: bool = True, - debug: bool = False, -) -> TensorMap: - """ - Based on the passed ``rascal_hypers``, generates a rascaline - SphericalExpansion (i.e. nu = 1 body order descriptor) and combines it - iteratively to generate a descriptor of order ``nu_target``. - """ - # Generate a rascaline SphericalExpansion, for only the selected samples if - # applicable - calculator = rascaline.SphericalExpansion(**rascal_hypers) - nu1_tensor = calculator.compute(frames, selected_samples=selected_samples) - - # Move the "species_neighbor" key to the properties. If species_neighbors is - # passed as a list of int, sparsity can be created in the properties for - # these species. - if species_neighbors is None: - keys_to_move = "species_neighbor" + # If nu1 = 1, the key names don't yet have any "lx" columns + if nu1 == 1: + l_list_names = [] + new_l_list_names = ["l1", "l2"] else: - keys_to_move = Labels( - names=["species_neighbor"], - values=np.array(species_neighbors).reshape(-1, 1), - ) - nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) + l_list_names = [f"l{l}" for l in range(1, nu1 + 1)] + new_l_list_names = l_list_names + [f"l{nu}"] - # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 - nu1_tensor = metatensor.insert_dimension( - nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 + # Check key names + assert np.all( + keys_1.names + == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + + l_list_names + + [f"k{k}" for k in range(2, nu1)] ) - nu1_tensor = metatensor.insert_dimension( - nu1_tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 + assert np.all( + keys_2.names + == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] ) - # If the desired body order is 1, return the spherical expansion with - # standardized metadata - if nu_target == 1: - return nu1_tensor - - # Otherwise, perform CG iterations. First, construct explicit sigma and - # lambda filters for each iteration. Basic checks are performed here and - # errors raised if invalid filters are passed. - sigma_filter = _parse_sigma_filter(nu_target, sigma_filter) - lambda_filter = _parse_lambda_filter( - nu_target, rascal_hypers["max_angular"], lambda_filter, lambda_cut + # Define key names of output Labels (i.e. for combined TensorMap) + new_names = ( + ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + + new_l_list_names + + [f"k{k}" for k in range(2, nu)] ) - if debug: - print("sigma_filter: ", sigma_filter) - print("lambda_filter: ", lambda_filter) - - # Create a copy of the nu = 1 tensor to combine with itself and store its - # keys. - nux_tensor = nu1_tensor.copy() - nux_keys = nux_tensor.keys - nu1_keys = nu1_tensor.keys - - # Pre-compute all the information needed to combined tensors at every - # iteration. This includes the keys of the TensorMaps produced at each - # iteration, the keys of the blocks combined to make them, and block - # multiplicities. - combine_info = [] - for iteration in range(1, nu_target): - info = _create_combined_keys( - nux_keys, - nu1_keys, - lambda_filter[iteration - 1], - sigma_filter[iteration - 1], - ) - combine_info.append(info) - nux_keys = info[0] - if debug: - print("Num. keys at each step: ", [len(c[0]) for c in combine_info]) - print([nu1_keys] + [c[0] for c in combine_info]) + new_key_values = [] + keys_1_entries = [] + keys_2_entries = [] + for key_1, key_2 in itertools.product(keys_1, keys_2): + # Unpack relevant key values + sig1, lam1, a = key_1.values[1:4] + sig2, lam2, a2 = key_2.values[1:4] + + # Only combine blocks of the same chemical species + if a != a2: + continue - if np.any([len(c[0]) == 0 for c in combine_info]): - raise ValueError( - "invalid filters: one or more iterations produce no valid combinations." - f" Number of keys at each iteration: {[len(c[0]) for c in combine_info]}." - " Check the `lambda_filter` and `sigma_filter` arguments." - ) + # First calculate the possible non-zero angular channels that can be + # formed from combination of blocks of order `lam1` and `lam2`. This + # corresponds to values in the inclusive range { |lam1 - lam2|, ..., + # |lam1 + lam2| } + nonzero_lams = np.arange(abs(lam1 - lam2), abs(lam1 + lam2) + 1) + + # Now iterate over the non-zero angular channels and apply the custom + # selections + for lam in nonzero_lams: + # Skip combination if it forms an angular channel of order greater + # than the specified maximum cutoff `angular_cutoff`. + if angular_cutoff is not None: + if lam > angular_cutoff: + continue + + # Skip combination if it creates an angular channel that has not + # been explicitly selected + if angular_selection is not None: + if lam not in angular_selection: + continue - # Define the cached CG coefficients, either as sparse dicts or dense arrays - lambda_max = max( - rascal_hypers["max_angular"], - np.max(np.concatenate(lambda_filter).flatten()), + # Calculate new sigma + sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) + + # Skip combination if it creates a parity that has not been + # explicitly selected + if parity_selection is not None: + if sig not in parity_selection: + continue + + # Extract the l and k lists from keys_1 + l_list = key_1.values[4 : 4 + nu1].tolist() + k_list = key_1.values[4 + nu1 :].tolist() + + # Build the new keys values. l{nu} is `lam2`` (i.e. + # "spherical_harmonics_l" of the key from `keys_2`. k{nu-1} is + # `lam1` (i.e. "spherical_harmonics_l" of the key from `keys_1`). + new_vals = [nu, sig, lam, a] + l_list + [lam2] + k_list + [lam1] + new_key_values.append(new_vals) + keys_1_entries.append(key_1) + keys_2_entries.append(key_2) + + # Define new keys as the full product of keys_1 and keys_2 + nu_x_keys = Labels(names=new_names, values=np.array(new_key_values)) + + # Now account for multiplicty + key_idxs_to_keep = [] + mult_dict = {} + for key_idx, key in enumerate(nu_x_keys): + # Get the important key values. This is all of the keys, excpet the k + # list + key_vals_slice = key.values[: 4 + (nu + 1)].tolist() + first_part, l_list = key_vals_slice[:4], key_vals_slice[4:] + + # Sort the l list + l_list_sorted = sorted(l_list) + + # Compare the sliced key with the one recreated when the l list is + # sorted. If they are identical, this is the key of the block that we + # want to compute a CG combination for. + key_slice_tuple = tuple(first_part + l_list) + key_slice_sorted_tuple = tuple(first_part + l_list_sorted) + if np.all(key_slice_tuple == key_slice_sorted_tuple): + key_idxs_to_keep.append(key_idx) + + # Now count the multiplicity of each sorted l_list + if mult_dict.get(key_slice_sorted_tuple) is None: + mult_dict[key_slice_sorted_tuple] = 1 + else: + mult_dict[key_slice_sorted_tuple] += 1 + + # Build a reduced Labels object for the combined keys, with redundancies removed + combined_keys_red = Labels( + names=new_names, + values=np.array([nu_x_keys[idx].values for idx in key_idxs_to_keep]), ) - # TODO: we know the lambda combinations in advance, so a more cleverly - # constructed CG cache could be used to reduce memory overhead - cg_cache = ClebschGordanReal(lambda_max, use_sparse) - - # Now combine block values until the target body order is reached - for iteration in range(1, nu_target): - if debug: - print(f"CG iteration {iteration}") - - # Combine pairs of blocks into new TensorBlocks of the correct lambda. - # Pass the correction factor accounting for the redundancy of "lx" - # combinations. - nux_keys = combine_info[iteration - 1][0] - nux_blocks = [] - for nux_key, key_1, key_2, multi in zip(*combine_info[iteration - 1]): - nux_blocks.append( - _combine_single_center_block_pair( - nux_tensor[key_1], - nu1_tensor[key_2], - nux_key["spherical_harmonics_l"], - cg_cache, - correction_factor=np.sqrt(multi), - ) - ) - nux_tensor = TensorMap(nux_keys, nux_blocks) + # Create a of LabelsEntry objects that correspond to the original keys in + # `keys_1` and `keys_2` that combined to form the combined key + keys_1_entries_red = [keys_1_entries[idx] for idx in key_idxs_to_keep] + keys_2_entries_red = [keys_2_entries[idx] for idx in key_idxs_to_keep] - # TODO: Account for body-order multiplicity and normalize block values - # nux_tensor = _apply_body_order_corrections(nux_tensor) - # nux_tensor = _normalize_blocks(nux_tensor) + # Define the multiplicity of each key + mult_list = [ + mult_dict[tuple(nu_x_keys[idx].values[: 4 + (nu + 1)].tolist())] + for idx in key_idxs_to_keep + ] - # Move the [l1, l2, ...] keys to the properties - if nu_target > 1: - nux_tensor = nux_tensor.keys_to_properties( - [f"l{i}" for i in range(1, nu_target + 1)] - + [f"k{i}" for i in range(2, nu_target)] - ) + return combined_keys_red, keys_1_entries_red, keys_2_entries_red, mult_list + + +# ================================================================== +# ===== Functions to perform the CG combinations of blocks +# ================================================================== - return nux_tensor - - -# def _combine_single_center( -# tensor_1: TensorMap, -# tensor_2: TensorMap, -# lambdas: Sequence[int], -# sigmas: Sequence[int], -# cg_cache, -# ) -> TensorMap: -# """ -# For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and 1 -# respectively, combines their blocks to form a new TensorMap with body order -# (nu + 1). - -# Returns blocks only indexed by keys . - -# Assumes the metadata of the two TensorMaps are standardized as follows. - -# The keys of `tensor_1` must follow the key name convention: - -# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", -# "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns -# track the l values of the nu=1 blocks that were previously combined. The -# "kx" columns tracks the intermediate lambda values of nu > 1 blocks that -# haev been combined. - -# For instance, a TensorMap of body order nu=4 will have key names -# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", -# "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of -# order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of -# order "k2". This was combined with a nu=1 TensorMap with blocks of order -# "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was -# combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. - -# .. math :: - -# \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n{\nu-1} l_{\nu-1} k_{\nu-1} ; -# n{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } - -# The keys of `tensor_2` must follow the key name convention: - -# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - -# Samples of pairs of blocks corresponding to the same chemical species are -# equivalent in the two TensorMaps. Samples names are ["structure", "center"] - -# Components names are [["spherical_harmonics_m"],] for each block. - -# Property names are ["n1", "n2", ..., "species_neighbor_1", -# "species_neighbor_2", ...] for each block. -# """ - -# # Get the correct keys for the combined output TensorMap -# ( -# nux_keys, -# keys_1_entries, -# keys_2_entries, -# multiplicity_list, -# ) = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas, sigmas) - -# # Iterate over pairs of blocks and combine -# nux_blocks = [] -# for nux_key, key_1, key_2, multi in zip( -# nux_keys, keys_1_entries, keys_2_entries, multiplicity_list -# ): -# # Retrieve the blocks -# block_1 = tensor_1[key_1] -# block_2 = tensor_2[key_2] - -# # Combine the blocks into a new TensorBlock of the correct lambda order. -# # Pass the correction factor accounting for the redundancy of "lx" -# # combinations. -# nux_blocks.append( -# _combine_single_center_block_pair( -# block_1, -# block_2, -# nux_key["spherical_harmonics_l"], -# cg_cache, -# correction_factor=np.sqrt(multi), -# ) -# ) - -# return TensorMap(nux_keys, nux_blocks) - - -def _combine_single_center_block_pair( + +def _combine_single_center_blocks( block_1: TensorBlock, block_2: TensorBlock, lam: int, @@ -710,14 +600,12 @@ def _combine_single_center_block_pair( correction_factor: float = 1.0, ) -> TensorBlock: """ - For a given pair of TensorBlocks and desired lambda value, combines the - values arrays and returns in a new TensorBlock. + For a given pair of TensorBlocks and desired angular channel, combines the + values arrays and returns a new TensorBlock. """ # Do the CG combination - single center so no shape pre-processing required - combined_values = _clebsch_gordan_combine( - block_1.values, block_2.values, lam, cg_cache - ) + combined_values = _combine_arrays(block_1.values, block_2.values, lam, cg_cache) # Infer the new nu value: block 1's properties are nu pairs of # "species_neighbor_x" and "nx". @@ -753,10 +641,7 @@ def _combine_single_center_block_pair( return combined_block -# ===== Mathematical manipulation fxns - - -def _clebsch_gordan_combine( +def _combine_arrays( arr_1: np.ndarray, arr_2: np.ndarray, lam: int, @@ -784,11 +669,11 @@ def _clebsch_gordan_combine( """ # Check the first dimension of the arrays are the same (i.e. same samples) if cg_cache.sparse: - return _clebsch_gordan_combine_sparse(arr_1, arr_2, lam, cg_cache) - return _clebsch_gordan_combine_dense(arr_1, arr_2, lam, cg_cache) + return _combine_arrays_sparse(arr_1, arr_2, lam, cg_cache) + return _combine_arrays_dense(arr_1, arr_2, lam, cg_cache) -def _clebsch_gordan_combine_sparse( +def _combine_arrays_sparse( arr_1: np.ndarray, arr_2: np.ndarray, lam: int, @@ -838,7 +723,7 @@ def _clebsch_gordan_combine_sparse( return arr_out -def _clebsch_gordan_combine_dense( +def _combine_arrays_dense( arr_1: np.ndarray, arr_2: np.ndarray, lam: int, @@ -905,203 +790,3 @@ def _clebsch_gordan_combine_dense( # (samples (q p) lam_mu) -> (samples lam_mu (q p)) return arr_out.swapaxes(1, 2) - - -def _apply_body_order_corrections(tensor: TensorMap) -> TensorMap: - """ - Applies the appropriate prefactors to the block values of the output - TensorMap (i.e. post-CG combination) according to its body order. - """ - return tensor - - -def _normalize_blocks(tensor: TensorMap) -> TensorMap: - """ - Applies corrections to the block values based on their 'leaf' l-values, such - that the norm is preserved. - """ - return tensor - - -# ===== Fxns to manipulate metadata of TensorMaps ===== - - -def _create_combined_keys( - keys_1: Labels, - keys_2: Labels, - lambdas: Sequence[int], - sigmas: Sequence[int], -) -> Tuple[Labels, Sequence[Sequence[int]]]: - """ - Given the keys of 2 TensorMaps and a list of desired lambda values, creates - the correct keys for the TensorMap returned after one CG combination step. - - Assumes that `keys_1` corresponds to a TensorMap with arbitrary body order, - while `keys_2` corresponds to a TensorMap with body order 1. - - `keys_1` must follow the key name convention: - - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", - "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns - track the l values of the nu=1 blocks that were previously combined. The - "kx" columns tracks the intermediate lambda values of nu > 1 blocks that - have been combined. - - For instance, a TensorMap of body order nu=4 will have key names - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", - "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of - order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of - order "k2". This was combined with a nu=1 TensorMap with blocks of order - "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was - combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. - - .. math :: - - \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n_{\nu-1} l_{\nu-1} k_{\nu-1} ; - n_{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } - - `keys_2` must follow the key name convention: - - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - - Returned is a tuple. - - The first element is to the Labels object for the keys of the output - TensorMap created by a CG combination step. - - The second element is a list of list of ints. Each sublist corresponds to - [lam1, lam2, correction_factor terms]. lam1 and lam2 tracks the lambda - values of the blocks that combine to form the block indexed by the - corresponding key. The correction_factor terms are the prefactors that - account for the redundancy in the CG combination. - - The `sigmas` argument can be used to return only keys with certain - sigmas. This must be passed as a list with elements +1 and/or -1. - """ - # Get the body order of the first TensorMap. - nu1 = np.unique(keys_1.column("order_nu"))[0] - - # Define nu value of output TensorMap - nu = nu1 + 1 - - # Check the body order of the first TensorMap. - assert np.all(keys_1.column("order_nu") == nu1) - - # The second by convention should be nu = 1. - assert np.all(keys_2.column("order_nu") == 1) - - # If nu = 1, the key names don't yet have any "lx" columns - if nu1 == 1: - l_list_names = [] - new_l_list_names = ["l1", "l2"] - else: - l_list_names = [f"l{l}" for l in range(1, nu1 + 1)] - new_l_list_names = l_list_names + [f"l{nu}"] - - # Check key names - assert np.all( - keys_1.names - == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - + l_list_names - + [f"k{k}" for k in range(2, nu1)] - ) - assert np.all( - keys_2.names - == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - ) - - # Check `sigmas` argument - assert isinstance(sigmas, Sequence) - assert np.all([s in [-1, +1] for s in sigmas]) - - # Define key names of output Labels (i.e. for combined TensorMap) - new_names = ( - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - + new_l_list_names - + [f"k{k}" for k in range(2, nu)] - ) - - new_key_values = [] - keys_1_entries = [] - keys_2_entries = [] - for key_1, key_2 in itertools.product(keys_1, keys_2): - # Unpack relevant key values - sig1, lam1, a = key_1.values[1:4] - sig2, lam2, a2 = key_2.values[1:4] - - # Only combine blocks of the same chemical species - if a != a2: - continue - - # Only combine to create blocks of desired lambda values - nonzero_lams = np.arange(abs(lam1 - lam2), abs(lam1 + lam2) + 1) - for lam in nonzero_lams: - if lam not in lambdas: - continue - - # Calculate new sigma - sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) - - # Skip keys that don't give the desired sigmas - if sig not in sigmas: - continue - - # Extract the l and k lists from keys_1 - l_list = key_1.values[4 : 4 + nu1].tolist() - k_list = key_1.values[4 + nu1 :].tolist() - - # Build the new keys values. l{nu} is `lam2`` (i.e. - # "spherical_harmonics_l" of the key from `keys_2`. k{nu-1} is - # `lam1` (i.e. "spherical_harmonics_l" of the key from `keys_1`). - new_vals = [nu, sig, lam, a] + l_list + [lam2] + k_list + [lam1] - new_key_values.append(new_vals) - keys_1_entries.append(key_1) - keys_2_entries.append(key_2) - - # Define new keys as the full product of keys_1 and keys_2 - nux_keys = Labels(names=new_names, values=np.array(new_key_values)) - - # Now account for multiplicty - key_idxs_to_keep = [] - mult_dict = {} - for key_idx, key in enumerate(nux_keys): - # Get the important key values. This is all of the keys, excpet the k - # list - key_vals_slice = key.values[: 4 + (nu + 1)].tolist() - first_part, l_list = key_vals_slice[:4], key_vals_slice[4:] - - # Sort the l list - l_list_sorted = sorted(l_list) - - # Compare the sliced key with the one recreated when the l list is - # sorted. If they are identical, this is the key of the block that we - # want to compute a CG combination for. - key_slice_tuple = tuple(first_part + l_list) - key_slice_sorted_tuple = tuple(first_part + l_list_sorted) - if np.all(key_slice_tuple == key_slice_sorted_tuple): - key_idxs_to_keep.append(key_idx) - - # Now count the multiplicity of each sorted l_list - if mult_dict.get(key_slice_sorted_tuple) is None: - mult_dict[key_slice_sorted_tuple] = 1 - else: - mult_dict[key_slice_sorted_tuple] += 1 - - # Build a reduced Labels object for the combined keys, with redundancies removed - combined_keys_red = Labels( - names=new_names, - values=np.array([nux_keys[idx].values for idx in key_idxs_to_keep]), - ) - - # Create a of LabelsEntry objects that correspond to the original keys in - # `keys_1` and `keys_2` that combined to form the combined key - keys_1_entries_red = [keys_1_entries[idx] for idx in key_idxs_to_keep] - keys_2_entries_red = [keys_2_entries[idx] for idx in key_idxs_to_keep] - - # Define the multiplicity of each key - mult_list = [ - mult_dict[tuple(nux_keys[idx].values[: 4 + (nu + 1)].tolist())] - for idx in key_idxs_to_keep - ] - - return combined_keys_red, keys_1_entries_red, keys_2_entries_red, mult_list diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index 0e013e305..244905d71 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -2,9 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -23,44 +32,33 @@ "import rotations" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate a rascaline SphericalExpansion calculator" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "frames = ase.io.read(\"combined_magres_spherical.xyz\", \":\")\n", + "# frames = ase.io.read(\"frame.xyz\", \":\")\n", + "frames = ase.io.read(\"combined_magres_spherical.xyz\", \":5\")\n", "\n", "# Define hyperparameters for generating the rascaline SphericalExpansion\n", "rascal_hypers = {\n", " \"cutoff\": 3.0, # Angstrom\n", - " \"max_radial\": 6, # Exclusive\n", + " \"max_radial\": 4, # Exclusive\n", " \"max_angular\": 5, # Inclusive\n", " \"atomic_gaussian_width\": 0.2,\n", " \"radial_basis\": {\"Gto\": {}},\n", " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", " \"center_atom_weight\": 1.0,\n", "}\n", - "\n", - "# chemiscope.show(frames, mode=\"structure\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "clebsch_gordan.n_body_iteration_single_center(\n", - " frames[:1],\n", - " rascal_hypers,\n", - " nu_target=3,\n", - " # lambda_filter=[[0, 2], [2]],\n", - " # lambda_filter=8,\n", - " # sigma_filter=[1],\n", - " # lambda_cut=5,\n", - " # debug=False,\n", - ")" + "calculator = rascaline.SphericalExpansion(**rascal_hypers)" ] }, { @@ -72,47 +70,59 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 38, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random rotation angles (rad): [5.77507977 5.81759295 0.3318965 ]\n", + "Computing nu = 3 descriptor for unrotated frames...\n", + "Computing nu = 3 descriptor for rotated frames...\n", + "SO(3) EQUIVARIANT!\n" + ] + } + ], "source": [ - "# # Define target lambda channels\n", - "# lambdas = np.array([0, 2])\n", - "\n", - "# # Generate Wigner-D matrices, initialized with random angles\n", - "# wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", - "# print(\"Random rotation angles (rad):\", wig.angles)\n", - "\n", - "# # Randomly rigidly rotate the frame\n", - "# frames_so3 = [rotations.transform_frame_so3(frame, wig.angles) for frame in frames]\n", - "# assert not np.allclose(frames[-1].positions, frames_so3[-1].positions)\n", + "# Define target angular channels\n", + "angular_selection = [0, 2]\n", "\n", - "# # Generate nu=3 descriptor for both frames\n", - "# nu3 = clebsch_gordan.n_body_iteration_single_center(\n", - "# frames,\n", - "# rascal_hypers,\n", - "# nu_target=3,\n", - "# lambdas=lambdas,\n", - "# parities=[+1],\n", - "# )\n", + "# Generate Wigner-D matrices, initialized with random angles\n", + "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", + "print(\"Random rotation angles (rad):\", wig.angles)\n", "\n", - "# nu3_rot = clebsch_gordan.n_body_iteration_single_center(\n", - "# frames_so3,\n", - "# rascal_hypers,\n", - "# nu_target=3,\n", - "# lambdas=lambdas,\n", - "# parities=[+1],\n", - "# )\n", + "# Randomly rigidly rotate the frame\n", + "frames_so3 = [rotations.transform_frame_so3(frame, wig.angles) for frame in frames]\n", + "assert not np.allclose(frames[-1].positions, frames_so3[-1].positions)\n", + "\n", + "# Generate nu=3 descriptor for both frames\n", + "print(\"Computing nu = 3 descriptor for unrotated frames...\")\n", + "nu_1 = calculator.compute(frames)\n", + "nu_3 = clebsch_gordan.combine_single_center_to_body_order(\n", + " nu_1_tensor=nu_1,\n", + " target_body_order=3,\n", + " angular_selection=angular_selection,\n", + " parity_selection=[+1],\n", + ")\n", + "print(\"Computing nu = 3 descriptor for rotated frames...\")\n", + "nu_1_rot = calculator.compute(frames_so3)\n", + "nu_3_rot = clebsch_gordan.combine_single_center_to_body_order(\n", + " nu_1_tensor=nu_1_rot,\n", + " target_body_order=3,\n", + " angular_selection=angular_selection,\n", + " parity_selection=[+1],\n", + ")\n", "\n", - "# # Rotate the lambda-SOAP descriptor of the unrotated frame\n", - "# nu3_transformed = wig.transform_tensormap_so3(nu3)\n", + "# Rotate the lambda-SOAP descriptor of the unrotated frame\n", + "nu_3_transf = wig.transform_tensormap_so3(nu_3)\n", "\n", - "# # Check for equivariance!\n", - "# assert metatensor.equal_metadata(nu3_transformed, nu3_rot)\n", - "# assert metatensor.allclose(nu3_transformed, nu3_rot)\n", - "# print(\"SO(3) EQUIVARIANT!\")\n", + "# Check for equivariance!\n", + "assert metatensor.equal_metadata(nu_3_transf, nu_3_rot)\n", + "assert metatensor.allclose(nu_3_transf, nu_3_rot)\n", + "print(\"SO(3) EQUIVARIANT!\")\n", "\n", - "# # chemiscope.show([frame, frame_so3], mode=\"structure\")" + "# chemiscope.show(frames + frames_so3, mode=\"structure\")" ] }, { @@ -124,12 +134,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 39, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random rotation angles (rad): [2.72689356 0.10058627 6.14346159]\n", + "Computing lambda-SOAP descriptor for unrotated frames...\n", + "Computing lambda-SOAP descriptor for rotated frames...\n", + "O(3) EQUIVARIANT!\n" + ] + } + ], "source": [ "# Define target lambda channels\n", - "lambda_filter = [0, 1, 2, 3, 4, 5]\n", + "angular_selection = [0, 1, 2, 3, 4, 5]\n", "\n", "# Generate Wigner-D matrices, initialized with random angles\n", "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", @@ -140,29 +161,28 @@ "assert not np.allclose(frames[-1].positions, frames_o3[-1].positions)\n", "\n", "# Generate lambda-SOAP for both frames\n", + "print(\"Computing lambda-SOAP descriptor for unrotated frames...\")\n", + "nu_1 = calculator.compute(frames)\n", "lsoap = clebsch_gordan.lambda_soap_vector(\n", - " frames,\n", - " rascal_hypers,\n", - " lambda_filter=lambda_filter,\n", - " sigma_filter=[+1],\n", + " nu_1_tensor=nu_1,\n", + " angular_selection=angular_selection,\n", ")\n", - "\n", + "print(\"Computing lambda-SOAP descriptor for rotated frames...\")\n", + "nu_1_rot = calculator.compute(frames_o3)\n", "lsoap_o3 = clebsch_gordan.lambda_soap_vector(\n", - " frames_o3,\n", - " rascal_hypers,\n", - " lambda_filter=lambda_filter,\n", - " sigma_filter=[+1],\n", + " nu_1_tensor=nu_1_rot,\n", + " angular_selection=angular_selection,\n", ")\n", "\n", "# Apply the O(3) transformation to the TensorMap\n", - "lsoap_transformed = wig.transform_tensormap_o3(lsoap)\n", + "lsoap_transf = wig.transform_tensormap_o3(lsoap)\n", "\n", "# Check for equivariance!\n", - "assert metatensor.equal_metadata(lsoap_transformed, lsoap_o3)\n", - "assert metatensor.allclose(lsoap_transformed, lsoap_o3)\n", + "assert metatensor.equal_metadata(lsoap_transf, lsoap_o3)\n", + "assert metatensor.allclose(lsoap_transf, lsoap_o3)\n", "print(\"O(3) EQUIVARIANT!\")\n", "\n", - "chemiscope.show(frames + frames_o3, mode=\"structure\")" + "# chemiscope.show(frames + frames_o3, mode=\"structure\")" ] }, { @@ -174,45 +194,66 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define target lambda channels\n", - "lambda_filter = [0, 1, 2, 3, 4, 5]\n", - "lambda_filter" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Size: 23315040\n", + "Size: 23315040\n", + "Size: 23315040\n", + "Size: 23315040\n", + "Size: 23315040\n", + "Size: 23315040\n", + "Size: 23315040\n", + "Size: 23315040\n", + "731 ms ± 9.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], "source": [ - "lsoap_old = old_clebsch_gordan.lambda_soap_vector(\n", - " frames,\n", - " rascal_hypers,\n", - " # lambda_filter=lambda_filter,\n", - " # sigma_filter=[+1],\n", - " # lambda_cut=5,\n", - ")\n", - "lsoap_old" + "%%timeit\n", + "lsoap_old = old_clebsch_gordan.lambda_soap_vector(frames, rascal_hypers, lambda_cut=5)\n", + "\n", + "size = 0\n", + "for block in lsoap_old:\n", + " size += np.prod(block.values.shape)\n", + "print(\"Size:\", size)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Size: 13819680\n", + "Size: 13819680\n", + "Size: 13819680\n", + "Size: 13819680\n", + "Size: 13819680\n", + "Size: 13819680\n", + "Size: 13819680\n", + "Size: 13819680\n", + "410 ms ± 12.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], "source": [ - "lsoap_new = clebsch_gordan.lambda_soap_vector(\n", - " frames,\n", - " rascal_hypers,\n", - " # lambda_filter=lambda_filter,\n", - " # sigma_filter=[+1],\n", - " # lambda_cut=5,\n", - ")\n", - "lsoap_new" + "%%timeit\n", + "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", + "nu_1 = calculator.compute(frames)\n", + "lsoap_new = clebsch_gordan.lambda_soap_vector(nu_1, angular_cutoff=5)\n", + "\n", + "size = 0\n", + "for block in lsoap_new:\n", + " size += np.prod(block.values.shape)\n", + "print(\"Size:\", size)" ] }, { @@ -224,49 +265,93 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "TensorMap with 36 blocks\n", + "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", + " 3 1 0 3\n", + " 3 -1 1 3\n", + " ...\n", + " 3 1 5 22\n", + " 3 -1 5 22" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Dense\n", - "lsoap_new0 = clebsch_gordan.n_body_iteration_single_center(\n", - " frames,\n", - " rascal_hypers,\n", - " nu_target=2,\n", - " lambdas=lambdas,\n", - " lambda_cut=5,\n", + "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", + "nu_1 = calculator.compute(frames[:2])\n", + "nu_2_dense = clebsch_gordan.combine_single_center_to_body_order(\n", + " nu_1,\n", + " target_body_order=3,\n", + " angular_cutoff=5,\n", " use_sparse=False,\n", ")\n", - "lsoap_new0" + "nu_2_dense" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "TensorMap with 36 blocks\n", + "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", + " 3 1 0 3\n", + " 3 -1 1 3\n", + " ...\n", + " 3 1 5 22\n", + " 3 -1 5 22" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Sparse\n", - "lsoap_new1 = clebsch_gordan.n_body_iteration_single_center(\n", - " frames,\n", - " rascal_hypers,\n", - " nu_target=2,\n", - " lambdas=lambdas,\n", - " lambda_cut=5,\n", + "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", + "nu_1 = calculator.compute(frames[:2])\n", + "nu_2_sparse = clebsch_gordan.combine_single_center_to_body_order(\n", + " nu_1,\n", + " target_body_order=3,\n", + " angular_cutoff=5,\n", " use_sparse=True,\n", - " only_keep_parity=+1,\n", ")\n", - "lsoap_new1" + "nu_2_sparse" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 37, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Check sparse == dense\n", - "metatensor.allclose(lsoap_new0, lsoap_new1)" + "metatensor.allclose(nu_2_dense, nu_2_sparse)" ] }, { @@ -282,20 +367,20 @@ "metadata": {}, "outputs": [], "source": [ - "%%timeit\n", + "# %%timeit\n", "\n", - "use_sparse = False\n", + "# use_sparse = False\n", "\n", - "tensor_dense = clebsch_gordan.n_body_iteration_single_center(\n", - " frames,\n", - " rascal_hypers=rascal_hypers,\n", - " nu_target=3,\n", - " lambdas=lambdas,\n", - " use_sparse=use_sparse,\n", - ")\n", - "tensor_dense\n", + "# tensor_dense = clebsch_gordan.n_body_iteration_single_center(\n", + "# frames,\n", + "# rascal_hypers=rascal_hypers,\n", + "# nu_target=3,\n", + "# lambdas=lambdas,\n", + "# use_sparse=use_sparse,\n", + "# )\n", + "# tensor_dense\n", "\n", - "# timeit comes out at about 22 seconds for nu_target = 3" + "# # timeit comes out at about 22 seconds for nu_target = 3" ] }, { @@ -304,20 +389,20 @@ "metadata": {}, "outputs": [], "source": [ - "%%timeit\n", + "# %%timeit\n", "\n", - "use_sparse = True\n", + "# use_sparse = True\n", "\n", - "tensor_sparse = clebsch_gordan.n_body_iteration_single_center(\n", - " frames,\n", - " rascal_hypers=rascal_hypers,\n", - " nu_target=3,\n", - " lambdas=lambdas,\n", - " use_sparse=use_sparse,\n", - ")\n", - "tensor_sparse\n", + "# tensor_sparse = clebsch_gordan.n_body_iteration_single_center(\n", + "# frames,\n", + "# rascal_hypers=rascal_hypers,\n", + "# nu_target=3,\n", + "# lambdas=lambdas,\n", + "# use_sparse=use_sparse,\n", + "# )\n", + "# tensor_sparse\n", "\n", - "# timeit comes out at about 13 seconds for nu_target = 3" + "# # timeit comes out at about 13 seconds for nu_target = 3" ] }, { @@ -326,23 +411,23 @@ "metadata": {}, "outputs": [], "source": [ - "tensor_sparse = clebsch_gordan.n_body_iteration_single_center(\n", - " frames,\n", - " rascal_hypers=rascal_hypers,\n", - " nu_target=3,\n", - " lambdas=lambdas,\n", - " use_sparse=True,\n", - ")\n", + "# tensor_sparse = clebsch_gordan.n_body_iteration_single_center(\n", + "# frames,\n", + "# rascal_hypers=rascal_hypers,\n", + "# nu_target=3,\n", + "# lambdas=lambdas,\n", + "# use_sparse=True,\n", + "# )\n", "\n", - "tensor_dense = clebsch_gordan.n_body_iteration_single_center(\n", - " frames,\n", - " rascal_hypers=rascal_hypers,\n", - " nu_target=3,\n", - " lambdas=lambdas,\n", - " use_sparse=False,\n", - ")\n", + "# tensor_dense = clebsch_gordan.n_body_iteration_single_center(\n", + "# frames,\n", + "# rascal_hypers=rascal_hypers,\n", + "# nu_target=3,\n", + "# lambdas=lambdas,\n", + "# use_sparse=False,\n", + "# )\n", "\n", - "assert metatensor.allclose(tensor_dense, tensor_sparse)" + "# assert metatensor.allclose(tensor_dense, tensor_sparse)" ] }, { @@ -358,27 +443,27 @@ "metadata": {}, "outputs": [], "source": [ - "frames = [ase.io.read(\"frame.xyz\")]\n", + "# frames = [ase.io.read(\"frame.xyz\")]\n", "\n", - "lambdas = np.array([0, 2])\n", - "\n", - "rascal_hypers = {\n", - " \"cutoff\": 3.0, # Angstrom\n", - " \"max_radial\": 6, # Exclusive\n", - " \"max_angular\": 5, # Inclusive\n", - " \"atomic_gaussian_width\": 0.2,\n", - " \"radial_basis\": {\"Gto\": {}},\n", - " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", - " \"center_atom_weight\": 1.0,\n", - "}\n", + "# lambdas = np.array([0, 2])\n", "\n", - "n_body = clebsch_gordan.n_body_iteration_single_center(\n", - " frames,\n", - " rascal_hypers=rascal_hypers,\n", - " nu_target=3,\n", - " lambdas=lambdas,\n", - ")\n", - "n_body" + "# rascal_hypers = {\n", + "# \"cutoff\": 3.0, # Angstrom\n", + "# \"max_radial\": 6, # Exclusive\n", + "# \"max_angular\": 5, # Inclusive\n", + "# \"atomic_gaussian_width\": 0.2,\n", + "# \"radial_basis\": {\"Gto\": {}},\n", + "# \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", + "# \"center_atom_weight\": 1.0,\n", + "# }\n", + "\n", + "# n_body = clebsch_gordan.n_body_iteration_single_center(\n", + "# frames,\n", + "# rascal_hypers=rascal_hypers,\n", + "# nu_target=3,\n", + "# lambdas=lambdas,\n", + "# )\n", + "# n_body" ] }, { @@ -387,41 +472,41 @@ "metadata": {}, "outputs": [], "source": [ - "def sort_tm(tm):\n", - " blocks = []\n", - " for _, block in tm.items():\n", - " values = block.values\n", - "\n", - " samples_values = block.samples.values\n", - " sorted_idx = native_list_argsort([tuple(row.tolist()) for row in block.samples.values])\n", - " samples_values = samples_values[sorted_idx]\n", - " values = values[sorted_idx]\n", - "\n", - " components_values = []\n", - " for i, component in enumerate(block.components):\n", - " component_values = component.values\n", - " sorted_idx = native_list_argsort([tuple(row.tolist()) for row in component.values])\n", - " components_values.append( component_values[sorted_idx] )\n", - " values = np.take(values, sorted_idx, axis=i+1)\n", - "\n", - " properties_values = block.properties.values\n", - " sorted_idx = native_list_argsort([tuple(row.tolist()) for row in block.properties.values])\n", - " properties_values = properties_values[sorted_idx]\n", - " values = values[..., sorted_idx]\n", - "\n", - " blocks.append(\n", - " TensorBlock(\n", - " values=values,\n", - " samples=Labels(values=samples_values, names=block.samples.names),\n", - " components=[Labels(values=components_values[i], names=component.names) for i, component in enumerate(block.components)],\n", - " properties=Labels(values=properties_values, names=block.properties.names)\n", - " )\n", - " )\n", - " return TensorMap(keys=tm.keys, blocks=blocks)\n", - "\n", - "\n", - "def native_list_argsort(native_list):\n", - " return sorted(range(len(native_list)), key=native_list.__getitem__)\n" + "# def sort_tm(tm):\n", + "# blocks = []\n", + "# for _, block in tm.items():\n", + "# values = block.values\n", + "\n", + "# samples_values = block.samples.values\n", + "# sorted_idx = native_list_argsort([tuple(row.tolist()) for row in block.samples.values])\n", + "# samples_values = samples_values[sorted_idx]\n", + "# values = values[sorted_idx]\n", + "\n", + "# components_values = []\n", + "# for i, component in enumerate(block.components):\n", + "# component_values = component.values\n", + "# sorted_idx = native_list_argsort([tuple(row.tolist()) for row in component.values])\n", + "# components_values.append( component_values[sorted_idx] )\n", + "# values = np.take(values, sorted_idx, axis=i+1)\n", + "\n", + "# properties_values = block.properties.values\n", + "# sorted_idx = native_list_argsort([tuple(row.tolist()) for row in block.properties.values])\n", + "# properties_values = properties_values[sorted_idx]\n", + "# values = values[..., sorted_idx]\n", + "\n", + "# blocks.append(\n", + "# TensorBlock(\n", + "# values=values,\n", + "# samples=Labels(values=samples_values, names=block.samples.names),\n", + "# components=[Labels(values=components_values[i], names=component.names) for i, component in enumerate(block.components)],\n", + "# properties=Labels(values=properties_values, names=block.properties.names)\n", + "# )\n", + "# )\n", + "# return TensorMap(keys=tm.keys, blocks=blocks)\n", + "\n", + "\n", + "# def native_list_argsort(native_list):\n", + "# return sorted(range(len(native_list)), key=native_list.__getitem__)\n" ] }, { @@ -431,31 +516,31 @@ "outputs": [], "source": [ "\n", - "# Manipulate metadata from old LSOAP\n", - "lsoap_old = metatensor.permute_dimensions(lsoap_old0, axis=\"properties\", dimensions_indexes=[2, 5, 0, 1, 3, 4])\n", - "lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_1\", new=\"l1\")\n", - "lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_2\", new=\"l2\")\n", - "lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_1\", new=\"n1\")\n", - "lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_2\", new=\"n2\")\n", - "\n", - "# Slice TM to symmetrize l-values\n", - "sliced_blocks = []\n", - "for key, block in lsoap_old.items():\n", - " # Filter properties l1 <= l2\n", - " mask = [entry[0] <= entry[1] for entry in block.properties]\n", - " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", - "\n", - " # Slice block\n", - " sliced_block = metatensor.slice_block(block, axis=\"properties\", labels=new_labels)\n", - " sliced_blocks.append(sliced_block)\n", - "\n", - "# Check equal metadata\n", - "lsoap_old = sort_tm(TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks))\n", - "lsoap_new = sort_tm(lsoap_new0)\n", - "assert metatensor.equal_metadata(lsoap_old, lsoap_new)\n", - "\n", - "# Check equal values\n", - "metatensor.allclose_raise(lsoap_old, lsoap_new)" + "# # Manipulate metadata from old LSOAP\n", + "# lsoap_old = metatensor.permute_dimensions(lsoap_old0, axis=\"properties\", dimensions_indexes=[2, 5, 0, 1, 3, 4])\n", + "# lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_1\", new=\"l1\")\n", + "# lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_2\", new=\"l2\")\n", + "# lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_1\", new=\"n1\")\n", + "# lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_2\", new=\"n2\")\n", + "\n", + "# # Slice TM to symmetrize l-values\n", + "# sliced_blocks = []\n", + "# for key, block in lsoap_old.items():\n", + "# # Filter properties l1 <= l2\n", + "# mask = [entry[0] <= entry[1] for entry in block.properties]\n", + "# new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", + "\n", + "# # Slice block\n", + "# sliced_block = metatensor.slice_block(block, axis=\"properties\", labels=new_labels)\n", + "# sliced_blocks.append(sliced_block)\n", + "\n", + "# # Check equal metadata\n", + "# lsoap_old = sort_tm(TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks))\n", + "# lsoap_new = sort_tm(lsoap_new0)\n", + "# assert metatensor.equal_metadata(lsoap_old, lsoap_new)\n", + "\n", + "# # Check equal values\n", + "# metatensor.allclose_raise(lsoap_old, lsoap_new)" ] }, { @@ -464,11 +549,11 @@ "metadata": {}, "outputs": [], "source": [ - "for key in lsoap_old.keys:\n", - " b1 = lsoap_old[key]\n", - " b2 = lsoap_new[key]\n", + "# for key in lsoap_old.keys:\n", + "# b1 = lsoap_old[key]\n", + "# b2 = lsoap_new[key]\n", "\n", - " print(key, metatensor.allclose_block(b1, b2))" + "# print(key, metatensor.allclose_block(b1, b2))" ] }, { @@ -477,12 +562,12 @@ "metadata": {}, "outputs": [], "source": [ - "np.linalg.norm(\n", - " lsoap_old.block(inversion_sigma=1, spherical_harmonics_l=1, species_center=3).values\n", - " - lsoap_new.block(\n", - " inversion_sigma=1, spherical_harmonics_l=1, species_center=3\n", - " ).values\n", - ")" + "# np.linalg.norm(\n", + "# lsoap_old.block(inversion_sigma=1, spherical_harmonics_l=1, species_center=3).values\n", + "# - lsoap_new.block(\n", + "# inversion_sigma=1, spherical_harmonics_l=1, species_center=3\n", + "# ).values\n", + "# )" ] }, { @@ -491,34 +576,34 @@ "metadata": {}, "outputs": [], "source": [ - "# NOW SLICE TO l1 == l2\n", + "# # NOW SLICE TO l1 == l2\n", "\n", - "# Slice TM\n", - "sliced_blocks = []\n", - "for key, block in lsoap_old.items():\n", - " # Filter properties\n", - " mask = [entry[0] == entry[1] for entry in block.properties]\n", - " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", + "# # Slice TM\n", + "# sliced_blocks = []\n", + "# for key, block in lsoap_old.items():\n", + "# # Filter properties\n", + "# mask = [entry[0] == entry[1] for entry in block.properties]\n", + "# new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", "\n", - " # Slice block\n", - " sliced_blocks.append(metatensor.slice_block(block, axis=\"properties\", labels=new_labels))\n", + "# # Slice block\n", + "# sliced_blocks.append(metatensor.slice_block(block, axis=\"properties\", labels=new_labels))\n", "\n", - "lsoap_old_sliced = TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks)\n", + "# lsoap_old_sliced = TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks)\n", "\n", - "# Slice TM\n", - "sliced_blocks = []\n", - "for key, block in lsoap_new.items():\n", - " # Filter properties\n", - " mask = [entry[0] == entry[1] for entry in block.properties]\n", - " new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", + "# # Slice TM\n", + "# sliced_blocks = []\n", + "# for key, block in lsoap_new.items():\n", + "# # Filter properties\n", + "# mask = [entry[0] == entry[1] for entry in block.properties]\n", + "# new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", "\n", - " # Slice block\n", - " sliced_blocks.append(metatensor.slice_block(block, axis=\"properties\", labels=new_labels))\n", + "# # Slice block\n", + "# sliced_blocks.append(metatensor.slice_block(block, axis=\"properties\", labels=new_labels))\n", "\n", - "lsoap_new_sliced = TensorMap(keys=lsoap_new.keys, blocks=sliced_blocks)\n", + "# lsoap_new_sliced = TensorMap(keys=lsoap_new.keys, blocks=sliced_blocks)\n", "\n", - "assert metatensor.equal_metadata(lsoap_old_sliced, lsoap_new_sliced)\n", - "assert metatensor.allclose_raise(lsoap_old_sliced, lsoap_new_sliced)" + "# assert metatensor.equal_metadata(lsoap_old_sliced, lsoap_new_sliced)\n", + "# assert metatensor.allclose_raise(lsoap_old_sliced, lsoap_new_sliced)" ] } ], From 25916d7e0bc107b54f349cf7ecb88dab5f9813bf Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 28 Sep 2023 12:17:52 +0200 Subject: [PATCH 53/96] Brief lambda-SOAP tutorial --- .../rascaline/utils/lsoap_tutorial.ipynb | 452 ++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 python/rascaline/rascaline/utils/lsoap_tutorial.ipynb diff --git a/python/rascaline/rascaline/utils/lsoap_tutorial.ipynb b/python/rascaline/rascaline/utils/lsoap_tutorial.ipynb new file mode 100644 index 000000000..ee869f1ce --- /dev/null +++ b/python/rascaline/rascaline/utils/lsoap_tutorial.ipynb @@ -0,0 +1,452 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Installation\n", + "\n", + "In a new conda environment, install the following packages in the following\n", + "order:\n", + "\n", + "1. `pip install git+https://github.com/luthaf/rascaline.git@clebsch_gordan`\n", + "2. `pip install git+https://github.com/lab-cosmo/metatensor.git`\n", + "3. Optional but nice: `pip install chemiscope` (allows you to visualize the\n", + " dataset)\n", + "\n", + "\n", + "Also required: `ase` and `numpy`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import os\n", + "import ase.io\n", + "import numpy as np\n", + "\n", + "import chemiscope\n", + "import metatensor\n", + "from metatensor import Labels, TensorBlock, TensorMap\n", + "\n", + "import rascaline\n", + "import clebsch_gordan\n", + "# from rascaline.utils import clebsch_gordan" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Read frames and visualize with `chemiscope`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "frames = ase.io.read(\"combined_magres_spherical.xyz\", \":\")\n", + "chemiscope.show(frames, mode=\"structure\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Convert target property to `metatensor` format" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# We will create a TensorMap for each frame, then combine them into a single\n", + "# TensorMap at the end\n", + "structure_tms = []\n", + "for frame_i, frame in enumerate(frames):\n", + " # Get the number of atoms in the frame\n", + " n_atoms = frame.get_global_number_of_atoms()\n", + "\n", + " # Store the target data by l value and chemical species (i.e. atomic number)\n", + " data_dict = {}\n", + " for atom_i, atomic_number in enumerate(frame.get_atomic_numbers()):\n", + " for l, data in zip(\n", + " [0, 2], [frames[0].arrays[\"efg_L0\"], frames[0].arrays[\"efg_L2\"]]\n", + " ):\n", + " key = (l, atomic_number)\n", + " data_arr = data[atom_i]\n", + " if isinstance(data_arr, float):\n", + " data_arr = np.array([data_arr])\n", + " # Store the data array\n", + " if data_dict.get(key) is None:\n", + " data_dict[key] = {atom_i: data_arr}\n", + " else:\n", + " data_dict[key][atom_i] = data_arr\n", + "\n", + " # Build the keys of the resulting TensorMap\n", + " keys = Labels(\n", + " names=[\"spherical_harmonics_l\", \"species_center\"],\n", + " values=np.array([[l, species_center] for l, species_center in data_dict.keys()]),\n", + " )\n", + "\n", + " # Construct the TensorMap blocks for each of these keys\n", + " blocks = []\n", + " for l, species_center in keys.values:\n", + " # Retrive the raw block data\n", + " data = data_dict[(l, species_center)]\n", + "\n", + " # Get a list of sorted samples (i.e. atom indices) for the block \n", + " n_atoms_block = len(data)\n", + " ordered_atom_idxs = sorted(data.keys())\n", + "\n", + " # Sort the raw block data\n", + " block_data = np.array([data[atom_i] for atom_i in ordered_atom_idxs]).reshape(\n", + " n_atoms_block, 2 * l + 1, 1\n", + " )\n", + "\n", + " # Construct a TensorBlock, where the raw data is labelled with metadata\n", + " # Note here that we keep track of the structure index - this is\n", + " # important for later when we join the TensorMaps\n", + " block = TensorBlock(\n", + " values=block_data,\n", + " samples=Labels(\n", + " names=[\"structure\", \"center\"],\n", + " values=np.array([[frame_i, atom_i] for atom_i in ordered_atom_idxs]),\n", + " ),\n", + " components=[\n", + " Labels(\n", + " names=[\"spherical_harmonics_m\"],\n", + " values=np.arange(-l, l + 1).reshape(-1, 1),\n", + " )\n", + " ],\n", + " properties=Labels(\n", + " names=[\"efg\"],\n", + " values=np.array([[0]]).reshape(-1, 1),\n", + " ),\n", + " )\n", + " # Store the block\n", + " blocks.append(block)\n", + "\n", + " # Construct a TensorMap for this structure from the keys and blocks\n", + " structure_tms.append(TensorMap(keys=keys, blocks=blocks))\n", + "\n", + "# Now join the stucture-based TensorMaps into a single TensorMap. We want to\n", + "# join along the \"samples\" axis\n", + "efg = metatensor.join(structure_tms, axis=\"samples\", remove_tensor_name=True)\n", + "\n", + "# Save the TensorMap to file\n", + "metatensor.save(\"efg.npz\", efg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The TensorMap is comprised of 6 blocks, each corresponding to a different\n", + "combination of l channel and chemical species:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "efg" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "efg.keys" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can pick out all the invariant blocks using `TensorMap.blocks()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "efg.blocks(spherical_harmonics_l=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or just a single block using `TensorMap.block()`. i.e. for l = 2, titanium:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "block = efg.block(spherical_harmonics_l=2, species_center=22)\n", + "block" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each TensorBlock is a tensor of values, wrapped with metadata. The \"samples\" are\n", + "always the first axis, the \"properties\" always the last axis, and all intermediate\n", + "axes are \"components\". Here the samples track the atom indices and which\n", + "structure they belong to. The components track the symmetry of the target\n", + "property. In this case we're representing the data in the spherical basis, so\n", + "there is a single component axis that tracks the $m$ component of the\n", + "irreducible spherical component (ISC) vector. As we only have a single property\n", + "per atom the properties axis has size one, and is just labelled with \"efg\" here. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "block.values.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The raw data is stored in this case as a numpy array:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(block.values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But we can also convert, for instance, to a torch backend. (Commented out in\n", + "case you don't have torch installed)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# import torch\n", + "\n", + "# efg_torch = metatensor.to(efg, backend=\"torch\")\n", + "# type(efg_torch.block(0).values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate $\\lambda$-SOAP descriptor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First we need to generate a $\\nu=1$ order spherical expansion (an\n", + "atom-centered density correlation) using the `SphericalExpansion` calculator in\n", + "rascaline. From there we combine it with itself using Clebsch-Gordan iterations\n", + "to generate the $\\nu=2$ order descriptor, i.e. $\\lambda$-SOAP." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Define hyperparameters for generating the rascaline SphericalExpansion\n", + "rascal_hypers = {\n", + " \"cutoff\": 3.0, # Angstrom\n", + " \"max_radial\": 6, # Exclusive\n", + " \"max_angular\": 5, # Inclusive\n", + " \"atomic_gaussian_width\": 0.2,\n", + " \"radial_basis\": {\"Gto\": {}},\n", + " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", + " \"center_atom_weight\": 1.0,\n", + "}\n", + "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", + "nu_1_tensor = calculator.compute(frames)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Define target lambda channels - we only want invariant l=0 and l=2 channels to\n", + "# match the target property EFG\n", + "angular_selection = [0, 2]\n", + "\n", + "# Now generate the lambda-SOAP vector\n", + "lsoap = clebsch_gordan.lambda_soap_vector(\n", + " nu_1_tensor=nu_1_tensor,\n", + " angular_selection=angular_selection,\n", + " parity_selection=+1,\n", + ")\n", + "\n", + "# Save\n", + "metatensor.save(\"lsoap.npz\", lsoap)\n", + "\n", + "lsoap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Some notes on the argument `angular_cutoff`, which can be set but isn't used\n", + "above. This sets the maximum intermediate value of lambda used when performing\n", + "CG iterations. The maximum value corresponding to non-zero combinations is given\n", + "by `target_body_order * rascal_hypers[\"max_angular\"]`.\n", + "\n", + "`target_body_order` is the target body-order of the descriptor. When using the\n", + "publci function `clebsch_gordan.lambda_soap_vector` as above,\n", + "`target_body_order` is by definition 2 so the specifying this isn't required.\n", + "For generating descriptors of higher body order, using the public function\n", + "`clebsch_gordan.combine_single_center_to_body_order`, this argumetn can be\n", + "specified. \n", + "\n", + "In some cases, particular for combinations to high body order (beyond\n", + "lambda-SOAP), combining at each iteration to the theoretical maximum can lead to\n", + "memory blow-up, so the `angular_cutoff` needs to be tailored in each case.\n", + "Setting this cutoff to less than theoretical maximum will lead to some\n", + "information loss. In the above function call I didn't specify `angular_cutoff`\n", + "so it just takes the maximum by default. \n", + "\n", + "The same logic also applies to the use of `angular_selection` and\n", + "`parity_selection`. Applying selection filters on intermediate iterations (i.e.\n", + "not the final one) can lead to some information loss, but reduce memory\n", + "consumption particularly at high body orders. `angular_cutoff` is essentially a\n", + "way of applying a global maximum cutoff to all iterations, whereas\n", + "`angular_selection` allows control of which specific angular channels are\n", + "constructed at each iteration. In our case, we only perform one iteration to\n", + "form our lambda-SOAP descriptor. As there are techincally no intermediate\n", + "iterations (only the case for num. iterations > 1), applying angular and parity\n", + "selections on the final (and only) iteration results in no information loss.\n", + "\n", + "Inspect the $\\lambda$-SOAP descriptor:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lsoap.block(spherical_harmonics_l=2, species_center=22)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The samples and components metadata are equivalent when compared to\n", + "the corresponding block of the EFG tensor, but the properties aren't, as the\n", + "relationship from descriptor properties -> target properties is the thing we wa\n", + "machine learn.\n", + "\n", + "It is important that all other metadata, except for the properties of each\n", + "block, agrees. More specifically, there should be a one-to-one mapping of\n", + "blocks, indexed by the same set of keys. For each of these blocks, the samples\n", + "and components should be the same and in the same order.\n", + "\n", + "This can be checked with the following function in `metatensor`. Here we toggle a\n", + "check for samples and components:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert metatensor.equal_metadata(lsoap, efg, check=[\"samples\", \"components\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But checking for properties too fails the test, as expected:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert metatensor.equal_metadata(lsoap, efg, check=[\"samples\", \"components\", \"properties\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are loads of other operations and convenience functions for checking and\n", + "manipulating both the data and metadata of TensorMaps. Check out the\n", + "[docs](https://lab-cosmo.github.io/metatensor/latest/) for more info! If there's\n", + "an operation we don't have but you think would be useful, please [open an\n", + "issue](https://github.com/lab-cosmo/metatensor/issues/new/choose) :)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "rho", + "language": "python", + "name": "python3" + }, + "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.10.9" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 38898415d91258f6ae3481034ef032e52c1f7b52 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 28 Sep 2023 16:54:52 +0200 Subject: [PATCH 54/96] Fxn to return only the metadata of CG calcs --- .../rascaline/utils/clebsch_gordan.py | 133 ++++++++++++++++-- python/rascaline/rascaline/utils/tmp.ipynb | 80 +++++++++-- 2 files changed, 187 insertions(+), 26 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan.py index 071f3842a..7539679f8 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan.py @@ -16,9 +16,9 @@ # - [ ] `combine_single_center_to_body_order`: feedback on API design # - [ ] Unit tests - metadata and maths # - [ ] Thorough documentation -# - [ ] Implement `combine_single_center_one_iteration` # TODO: later PRs, in roughly chronological order +# - [ ] Implement `combine_single_center_one_iteration` # - [ ] Use dispatch for numpy/torch CG combination # - [ ] Add support for gradients # - [ ] Integrate with `sparse_accumulation` @@ -26,6 +26,10 @@ # - [ ] Customizable and arbitrary (non)linear transformations at each iteration +# 29/09/23 Meeting notes +# - `angular_cutoff` appropriate arg name? + + # ====================================================================== # ===== Functions to do CG combinations on single-center descriptors # ====================================================================== @@ -89,7 +93,6 @@ def combine_single_center_to_body_order( angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, use_sparse: bool = True, - return_metadata_only: bool = False, ) -> TensorMap: """ Takes a nu = 1 (i.e. 2-body) single-center descriptor and combines it @@ -135,10 +138,6 @@ def combine_single_center_to_body_order( parity_selection=parity_selection, ) - # For debugging it might be useful just to return the keys of the final TensorMap - if return_metadata_only: - return combination_metadata - # Define the cached CG coefficients, either as sparse dicts or dense arrays. # TODO: we pre-computed the combination metadata, so a more cleverly # constructed CG cache could be used to reduce memory overhead - i.e. we @@ -188,6 +187,96 @@ def combine_single_center_to_body_order( return nu_x_tensor +def combine_single_center_to_body_order_metadata_only( + nu_1_tensor: TensorMap, + target_body_order: int, + angular_cutoff: Optional[int] = None, + angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, +) -> List[TensorMap]: + """ + Performs a pseudo-CG combination of a nu = 1 (i.e. 2-body) single-center + descriptor with itself to generate a descriptor of order + ``target_body_order``. + + A list of TensorMaps is returned, where each has the complete * metadata * + of the TensorMap that would be created by a full CG combination. No actual + CG combinations of block values arrays are performed, instead arrays of + zeros are returned in the output TensorMaps. + + This function is useful for producing pseudo-outputs of a CG iteration + calculation with all the correct metadata, but far cheaper than if CG + combinations were actually performed. This can help to quantify the size of + descriptors produced, and observe the effect of selection filters on the + expansion of features at each iteration. + """ + if target_body_order <= 1: + raise ValueError("`target_body_order` must be > 1") + + if np.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): + raise NotImplementedError( + "CG combinations of gradients not currently supported. Check back soon." + ) + + # Standardize the metadata of the input tensor + nu_1_tensor = _standardize_tensor_metadata(nu_1_tensor) + + # Pre-compute the metadata needed to perform each CG iteration + # Current design choice: only combine a nu = 1 tensor iteratively with + # itself, i.e. nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. + parity_selection = _parse_selection_filters( + n_iterations=target_body_order - 1, + selection_type="parity", + selection=parity_selection, + ) + angular_selection = _parse_selection_filters( + n_iterations=target_body_order - 1, + selection_type="angular", + selection=angular_selection, + angular_cutoff=angular_cutoff, + ) + combination_metadata = _precompute_metadata( + nu_1_tensor.keys, + nu_1_tensor.keys, + n_iterations=target_body_order - 1, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + ) + + # Create a copy of the nu = 1 tensor to combine with itself + nu_x_tensor = nu_1_tensor.copy() + + # Iteratively combine block values + nu_x_tensors = [] + for iteration in range(target_body_order - 1): + # Combine blocks + nu_x_blocks = [] + # TODO: is there a faster way of iterating over keys/blocks here? + for nu_x_key, key_1, key_2, _ in zip(*combination_metadata[iteration]): + # Combine the pair of block values, accounting for multiplicity + nu_x_block = _combine_single_center_blocks( + nu_x_tensor[key_1], + nu_1_tensor[key_2], + nu_x_key["spherical_harmonics_l"], + cg_cache=None, + correction_factor=1.0, + return_metadata_only=True, + ) + nu_x_blocks.append(nu_x_block) + nu_x_keys = combination_metadata[iteration][0] + nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) + nu_x_tensors.append(nu_x_tensor) + + return [ + tensor.keys_to_properties( + [f"l{i}" for i in range(1, tmp_bo + 1)] + + [f"k{i}" for i in range(2, tmp_bo)] + ) + for tmp_bo, tensor in enumerate(nu_x_tensors, start=2) + ] + + def combine_single_center_one_iteration( tensor_1: TensorMap, tensor_2: TensorMap, @@ -598,6 +687,7 @@ def _combine_single_center_blocks( lam: int, cg_cache, correction_factor: float = 1.0, + return_metadata_only: bool = False, ) -> TensorBlock: """ For a given pair of TensorBlocks and desired angular channel, combines the @@ -605,7 +695,14 @@ def _combine_single_center_blocks( """ # Do the CG combination - single center so no shape pre-processing required - combined_values = _combine_arrays(block_1.values, block_2.values, lam, cg_cache) + if return_metadata_only: + combined_values = _combine_arrays_sparse( + block_1.values, block_2.values, lam, cg_cache, return_empty_array=True + ) + else: + combined_values = _combine_arrays( + block_1.values, block_2.values, lam, cg_cache, return_empty_array=False + ) # Infer the new nu value: block 1's properties are nu pairs of # "species_neighbor_x" and "nx". @@ -646,6 +743,7 @@ def _combine_arrays( arr_2: np.ndarray, lam: int, cg_cache, + return_empty_array: bool = False, ) -> np.ndarray: """ Couples arrays corresponding to the irreducible spherical components of 2 @@ -666,10 +764,19 @@ def _combine_arrays( Either performs the operation in a dense or sparse manner, depending on the value of `sparse`. + + `return_empty_array` can be used to return an empty array of the correct + shape, without performing the CG combination step. This can be useful for + probing the outputs of CG iterations in terms of metadata without the + computational cost of performing the CG combinations - i.e. using the + function :py:func:`combine_single_center_to_body_order_metadata_only`. """ + if return_empty_array: + return _combine_arrays_sparse(arr_1, arr_2, lam, cg_cache, True) + # Check the first dimension of the arrays are the same (i.e. same samples) if cg_cache.sparse: - return _combine_arrays_sparse(arr_1, arr_2, lam, cg_cache) + return _combine_arrays_sparse(arr_1, arr_2, lam, cg_cache, False) return _combine_arrays_dense(arr_1, arr_2, lam, cg_cache) @@ -678,6 +785,7 @@ def _combine_arrays_sparse( arr_2: np.ndarray, lam: int, cg_cache, + return_empty_array: bool = False, ) -> np.ndarray: """ TODO: finish docstring. @@ -707,12 +815,15 @@ def _combine_arrays_sparse( l1 = (arr_1.shape[1] - 1) // 2 l2 = (arr_2.shape[1] - 1) // 2 - # Get the corresponding Clebsch-Gordan coefficients - cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] - # Initialise output array arr_out = np.zeros((n_i, 2 * lam + 1, n_p * n_q)) + if return_empty_array: + return arr_out + + # Get the corresponding Clebsch-Gordan coefficients + cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] + # Fill in each mu component of the output array in turn for m1, m2, mu in cg_coeffs.keys(): # Broadcast arrays, multiply together and with CG coeff diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/tmp.ipynb index 244905d71..d36699b55 100644 --- a/python/rascaline/rascaline/utils/tmp.ipynb +++ b/python/rascaline/rascaline/utils/tmp.ipynb @@ -2,18 +2,9 @@ "cells": [ { "cell_type": "code", - "execution_count": 11, + "execution_count": 2, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -41,12 +32,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 59, "metadata": {}, "outputs": [], "source": [ - "# frames = ase.io.read(\"frame.xyz\", \":\")\n", - "frames = ase.io.read(\"combined_magres_spherical.xyz\", \":5\")\n", + "frames = ase.io.read(\"frame copy.xyz\", \":\")\n", + "# frames = ase.io.read(\"combined_magres_spherical.xyz\", \":1\")\n", "\n", "# Define hyperparameters for generating the rascaline SphericalExpansion\n", "rascal_hypers = {\n", @@ -58,7 +49,66 @@ " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", " \"center_atom_weight\": 1.0,\n", "}\n", - "calculator = rascaline.SphericalExpansion(**rascal_hypers)" + "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", + "nu_1 = calculator.compute(frames)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate output TensorMaps with:\n", + "### a) only metadata and b) actually doing the CG combinations" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "242 ms ± 7.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "nu_3 = clebsch_gordan.combine_single_center_to_body_order_metadata_only(\n", + " nu_1_tensor=nu_1,\n", + " target_body_order=3,\n", + " # angular_cutoff=6,\n", + " # angular_selection=[0, 1, 2],\n", + " # parity_selection=[+1],\n", + ")\n", + "# [np.unique(i[0].column(\"spherical_harmonics_l\")) for i in nu_3]" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6.25 s ± 1.06 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "nu_3 = clebsch_gordan.combine_single_center_to_body_order(\n", + " nu_1_tensor=nu_1,\n", + " target_body_order=3,\n", + " # angular_cutoff=6,\n", + " # angular_selection=[0, 1, 2],\n", + " # parity_selection=[+1],\n", + ")\n", + "# [np.unique(i[0].column(\"spherical_harmonics_l\")) for i in nu_3]" ] }, { From 596cab910517b290a08e2ba3228726887a458409 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 11 Oct 2023 09:28:03 +0200 Subject: [PATCH 55/96] changing dir structure --- .../utils/clebsch_gordan/__init__.py | 13 + .../_archive/clebsch_gordan_ROUGH.py | 342 ++++++++++++++++++ .../_archive}/combined_magres_spherical.xyz | 0 .../clebsch_gordan/_archive/frame copy.xyz | 4 + .../{ => clebsch_gordan/_archive}/frame.xyz | 0 .../_archive}/lsoap_tutorial.ipynb | 0 .../_archive}/old_clebsch_gordan.py | 0 .../{ => clebsch_gordan/_archive}/tmp.ipynb | 0 .../{ => clebsch_gordan}/cg_coefficients.py | 0 9 files changed, 359 insertions(+) create mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/__init__.py create mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py rename python/rascaline/rascaline/utils/{ => clebsch_gordan/_archive}/combined_magres_spherical.xyz (100%) create mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame copy.xyz rename python/rascaline/rascaline/utils/{ => clebsch_gordan/_archive}/frame.xyz (100%) rename python/rascaline/rascaline/utils/{ => clebsch_gordan/_archive}/lsoap_tutorial.ipynb (100%) rename python/rascaline/rascaline/utils/{ => clebsch_gordan/_archive}/old_clebsch_gordan.py (100%) rename python/rascaline/rascaline/utils/{ => clebsch_gordan/_archive}/tmp.ipynb (100%) rename python/rascaline/rascaline/utils/{ => clebsch_gordan}/cg_coefficients.py (100%) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py new file mode 100644 index 000000000..0ad7a5f5f --- /dev/null +++ b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py @@ -0,0 +1,13 @@ +from .cg_coefficients import ClebschGordanReal +from .clebsch_gordan import ( + combine_single_center_to_body_order, + combine_single_center_to_body_order_metadata_only, + lambda_soap_vector, +) # noqa + +__all__ = [ + "ClebschGordanReal", + "combine_single_center_to_body_order", + "combine_single_center_to_body_order_metadata_only", + "lambda_soap_vector", +] \ No newline at end of file diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py new file mode 100644 index 000000000..930841890 --- /dev/null +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py @@ -0,0 +1,342 @@ +# def _apply_body_order_corrections(tensor: TensorMap) -> TensorMap: +# """ +# Applies the appropriate prefactors to the block values of the output +# TensorMap (i.e. post-CG combination) according to its body order. +# """ +# return tensor + + +# def _normalize_blocks(tensor: TensorMap) -> TensorMap: +# """ +# Applies corrections to the block values based on their 'leaf' l-values, such +# that the norm is preserved. +# """ +# return tensor + + +# ===== ROUGH WORK: + +# def _parse_parity_selection( +# target_body_order: int, +# parity_selection: Union[None, int, List[int], List[List[int]]], +# ) -> List[List[int]]: +# """ +# Returns parity filters for each CG combination step of a nu=1 tensor with +# itself up to the target body order. + +# If a filter isn't specified by the user with `parity_selection=None`, then +# no filter is applied, i.e. [-1, +1] is used at every iteration. + +# If a single sequence of int is specified, then this is used for the last +# iteration only, and [-1, +1] is used for all intermediate iterations. For +# example, if `target_body_order=4` and `parity_selection=[+1]`, then the +# filter [[-1, +1], [-1, +1], [+1]] is returned. + +# If a sequence of sequences of int is specified, then this is assumed to be +# the desired filter for each iteration. + +# Note: very basic checks on the validity of the parity selections are +# performed, but these are not complete as they do not account for the angular +# channels of the blocks. These interactions are checked downstream in +# :py:func:`_create_combined_keys`. +# """ +# if target_body_order < 2: +# raise ValueError("`target_body_order` must be > 1") + +# # No filter specified: use [-1, +1] for all iterations +# if parity_selection is None: +# parity_selection = [[-1, +1] for _ in range(target_body_order - 1)] + +# # Parse user-defined filter: assume passed as List[int] or +# # List[List[int]] +# else: +# if isinstance(parity_selection, int): +# parity_selection = [parity_selection] +# if not isinstance(parity_selection, List): +# raise TypeError( +# "`parity_selection` must be an int, List[int], or List[List[int]]" +# ) +# # Single filter: apply on last iteration only, use both sigmas for +# # intermediate iterations +# if np.all([isinstance(sigma, int) for sigma in parity_selection]): +# parity_selection = [[-1, +1] for _ in range(target_body_order - 2)] + [parity_selection] + +# # Check parity_selection +# assert isinstance(parity_selection, List) +# assert len(parity_selection) == target_body_order - 1 +# assert np.all([isinstance(filt, List) for filt in parity_selection]) +# assert np.all([np.all([s in [-1, +1] for s in filt]) for filt in parity_selection]) + +# return parity_selection + + +# def _parse_angular_selection( +# angular_channels_1: List[int], +# angular_channels_2: List[int], +# target_body_order: int, +# rascal_max_l: int, +# angular_selection: Union[None, int, List[int], List[List[int]]], +# lambda_cut: Union[None, int], +# ) -> List[List[int]]: +# """ +# Parses the user-defined angular selection filters, returning + +# If a filter isn't specified by the user with `angular_selection=None`, then +# no filter is applied. In this case all possible lambda channels are retained +# at each iteration. For example, if `target_body_order=4`, `rascal_max_l=5`, +# and `lambda_cut=None`, then the returned filter is [[0, ..., 10], [0, ..., +# 15], [0, ..., 20]]. If `target_body_order=4`, `rascal_max_l=5`, and +# `lambda_cut=10`, then the returned filter is [[0, ..., 10], [0, ..., 10], +# [0, ..., 10]]. + +# If `angular_selection` is passed a single sequence of int, then this is used +# for the last iteration only, and all possible combinations of lambda are +# used in intermediate iterations. For instance, if `angular_selection=[0, 1, +# 2]`, the returned filters for the 2 examples above, respectively, would be +# [[0, ..., 10], [0, ..., 15], [0, 1, 2]] and [[0, ..., 10], [0, ..., 10], [0, +# 1, 2]]. + +# If a sequence of sequences of int is specified, then this is assumed to be +# the desired filter for each iteration and is only checked for validity +# without modification. + +# Note: basic checks on the validity of the angular selections are performed, +# but these are not complete as they do not account for the parity of the +# blocks. These interactions are checked downstream in +# :py:func:`_create_combined_keys`. +# """ +# if target_body_order < 2: +# raise ValueError("`target_body_order` must be > 1") + +# # Check value of lambda_cut +# if lambda_cut is not None: +# if not (rascal_max_l <= lambda_cut <= target_body_order * rascal_max_l): +# raise ValueError( +# "`lambda_cut` must be >= `rascal_hypers['max_angular']` and <= `target_body_order`" +# " * `rascal_hypers['max_angular']`" +# ) + +# # No filter specified: retain all possible lambda channels for every +# # iteration, up to lambda_cut (if specified) +# if angular_selection is None: +# if lambda_cut is None: +# # Use the full range of possible lambda channels for each iteration. +# # This is dependent on the itermediate body order. +# angular_selection = [ +# [lam for lam in range(0, (nu * rascal_max_l) + 1)] +# for nu in range(2, target_body_order + 1) +# ] +# else: +# # Use the full range of possible lambda channels for each iteration, +# # but only up to lambda_cut, independent of the intermediate body +# # order. +# angular_selection = [ +# [lam for lam in range(0, lambda_cut + 1)] +# for nu in range(2, target_body_order + 1) +# ] + +# # Parse user-defined filter: assume passed as List[int] or +# # List[List[int]] +# else: +# if isinstance(angular_selection, int): +# angular_selection = [angular_selection] +# if not isinstance(angular_selection, List): +# raise TypeError( +# "`angular_selection` must be an int, List[int], or List[List[int]]" +# ) +# # Single filter: apply on last iteration only, use all possible lambdas for +# # intermediate iterations (up to lambda_cut, if specified) +# if np.all([isinstance(filt, int) for filt in angular_selection]): +# if lambda_cut is None: +# # Use the full range of possible lambda channels for each iteration. +# # This is dependent on the itermediate body order. +# angular_selection = [ +# [lam for lam in range(0, (nu * rascal_max_l) + 1)] +# for nu in range(2, target_body_order) +# ] + [angular_selection] + +# else: +# # Use the full range of possible lambda channels for each iteration, +# # but only up to lambda_cut, independent of the intermediate body +# # order. +# angular_selection = [ +# [lam for lam in range(0, lambda_cut + 1)] +# for nu in range(2, target_body_order) +# ] + [angular_selection] + +# else: +# # Assume filter explicitly defined for each iteration (checked below) +# pass + +# # Check angular_selection +# if not isinstance(angular_selection, List): +# raise TypeError( +# "`angular_selection` must be an int, List[int], or List[List[int]]" +# ) +# if len(angular_selection) != target_body_order - 1: +# raise ValueError( +# "`angular_selection` must have length `target_body_order` - 1, i.e. the number of CG" +# " iterations required to reach `target_body_order`" +# ) +# if not np.all([isinstance(filt, List) for filt in angular_selection]): +# raise TypeError( +# "`angular_selection` must be an int, List[int], or List[List[int]]" +# ) +# # Check the lambda values are within the possible range, based on each +# # intermediate body order +# if not np.all( +# [ +# np.all([0 <= lam <= nu * rascal_max_l for lam in filt]) +# for nu, filt in enumerate(angular_selection, start=2) +# ] +# ): +# raise ValueError( +# "All lambda values in `angular_selection` must be >= 0 and <= `nu` *" +# " `rascal_hypers['max_angular']`, where `nu` is the body" +# " order created in the intermediate CG combination step" +# ) +# # Now check that at each iteration the lambda values can actually be created +# # from combination at the previous iteration +# for filt_i, filt in enumerate(angular_selection): +# if filt_i == 0: +# # Assume that the original nu=1 tensors to be combined have all l up +# # to and including `rascal_max_l` +# allowed_lams = np.arange(0, (2 * rascal_max_l) + 1) +# else: +# allowed_lams = [] +# for l1, l2 in itertools.product(angular_selection[filt_i - 1], repeat=2): +# for lam in range(abs(l1 - l2), abs(l1 + l2) + 1): +# allowed_lams.append(lam) + +# allowed_lams = np.unique(allowed_lams) + +# if not np.all([lam in allowed_lams for lam in filt]): +# raise ValueError( +# f"invalid lambda values in `angular_selection` for iteration {filt_i + 1}." +# f" {filt} cannot be created by combination of previous lambda values" +# f" {angular_selection[filt_i - 1]}" +# ) + +# return angular_selection + + +# def _combine_single_center( +# tensor_1: TensorMap, +# tensor_2: TensorMap, +# lambdas: List[int], +# sigmas: List[int], +# cg_cache, +# ) -> TensorMap: +# """ +# For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and 1 +# respectively, combines their blocks to form a new TensorMap with body order +# (nu + 1). + +# Returns blocks only indexed by keys . + +# Assumes the metadata of the two TensorMaps are standardized as follows. + +# The keys of `tensor_1` must follow the key name convention: + +# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", +# "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns +# track the l values of the nu=1 blocks that were previously combined. The +# "kx" columns tracks the intermediate lambda values of nu > 1 blocks that +# haev been combined. + +# For instance, a TensorMap of body order nu=4 will have key names +# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", +# "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of +# order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of +# order "k2". This was combined with a nu=1 TensorMap with blocks of order +# "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was +# combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. + +# .. math :: + +# \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n{\nu-1} l_{\nu-1} k_{\nu-1} ; +# n{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } + +# The keys of `tensor_2` must follow the key name convention: + +# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + +# Samples of pairs of blocks corresponding to the same chemical species are +# equivalent in the two TensorMaps. Samples names are ["structure", "center"] + +# Components names are [["spherical_harmonics_m"],] for each block. + +# Property names are ["n1", "n2", ..., "species_neighbor_1", +# "species_neighbor_2", ...] for each block. +# """ + +# # Get the correct keys for the combined output TensorMap +# ( +# nux_keys, +# keys_1_entries, +# keys_2_entries, +# multiplicity_list, +# ) = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas, sigmas) + +# # Iterate over pairs of blocks and combine +# nux_blocks = [] +# for nux_key, key_1, key_2, multi in zip( +# nux_keys, keys_1_entries, keys_2_entries, multiplicity_list +# ): +# # Retrieve the blocks +# block_1 = tensor_1[key_1] +# block_2 = tensor_2[key_2] + +# # Combine the blocks into a new TensorBlock of the correct lambda order. +# # Pass the correction factor accounting for the redundancy of "lx" +# # combinations. +# nux_blocks.append( +# _combine_single_center_block_pair( +# block_1, +# block_2, +# nux_key["spherical_harmonics_l"], +# cg_cache, +# correction_factor=np.sqrt(multi), +# ) +# ) + +# return TensorMap(nux_keys, nux_blocks) + + + + +# # Parse the angular and parity selections. Basic checks are performed here + # # and errors raised if invalid selections are passed. + # parity_selection = _parse_parity_selection(target_body_order, parity_selection) + # angular_selection = _parse_angular_selection( + # target_body_order, rascal_hypers["max_angular"], angular_selection, angular_cutoff + # ) + # if debug: + # print("parity_selection: ", parity_selection) + # print("angular_selection: ", angular_selection) +# # Pre-compute all the information needed to combined tensors at every + # # iteration. This includes the keys of the TensorMaps produced at each + # # iteration, the keys of the blocks combined to make them, and block + # # multiplicities. + # combine_info = [] + # for iteration in range(1, target_body_order): + # info = _create_combined_keys( + # nux_keys, + # nu1_keys, + # angular_selection[iteration - 1], + # parity_selection[iteration - 1], + # ) + # combine_info.append(info) + # nux_keys = info[0] + + # if debug: + # print("Num. keys at each step: ", [len(c[0]) for c in combine_info]) + # print([nu1_keys] + [c[0] for c in combine_info]) + # return + + # if np.any([len(c[0]) == 0 for c in combine_info]): + # raise ValueError( + # "invalid filters: one or more iterations produce no valid combinations." + # f" Number of keys at each iteration: {[len(c[0]) for c in combine_info]}." + # " Check the `angular_selection` and `parity_selection` arguments." + # ) diff --git a/python/rascaline/rascaline/utils/combined_magres_spherical.xyz b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/combined_magres_spherical.xyz similarity index 100% rename from python/rascaline/rascaline/utils/combined_magres_spherical.xyz rename to python/rascaline/rascaline/utils/clebsch_gordan/_archive/combined_magres_spherical.xyz diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame copy.xyz b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame copy.xyz new file mode 100644 index 000000000..2fd5805d2 --- /dev/null +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame copy.xyz @@ -0,0 +1,4 @@ +2 +Properties=species:S:1:pos:R:3 pbc="F F F" +H -0.52638300 -0.76932700 -0.02936600 +H -0.52638300 0.76932700 -0.02936600 diff --git a/python/rascaline/rascaline/utils/frame.xyz b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame.xyz similarity index 100% rename from python/rascaline/rascaline/utils/frame.xyz rename to python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame.xyz diff --git a/python/rascaline/rascaline/utils/lsoap_tutorial.ipynb b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/lsoap_tutorial.ipynb similarity index 100% rename from python/rascaline/rascaline/utils/lsoap_tutorial.ipynb rename to python/rascaline/rascaline/utils/clebsch_gordan/_archive/lsoap_tutorial.ipynb diff --git a/python/rascaline/rascaline/utils/old_clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py similarity index 100% rename from python/rascaline/rascaline/utils/old_clebsch_gordan.py rename to python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py diff --git a/python/rascaline/rascaline/utils/tmp.ipynb b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb similarity index 100% rename from python/rascaline/rascaline/utils/tmp.ipynb rename to python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb diff --git a/python/rascaline/rascaline/utils/cg_coefficients.py b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py similarity index 100% rename from python/rascaline/rascaline/utils/cg_coefficients.py rename to python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py From 9c6931d6008c705b3818b7423b8a19afb2f88fb0 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 11 Oct 2023 09:44:40 +0200 Subject: [PATCH 56/96] local import --- .../rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 7539679f8..e8d5de9b4 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -9,7 +9,7 @@ import metatensor from metatensor import Labels, TensorBlock, TensorMap -from cg_coefficients import ClebschGordanReal +from .cg_coefficients import ClebschGordanReal # TODO: this PR From 07b5889ecbb68af06a826d573581059e041f575f Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 11 Oct 2023 13:06:20 +0200 Subject: [PATCH 57/96] formatting --- .../utils/clebsch_gordan/__init__.py | 17 ++- .../_archive/clebsch_gordan_ROUGH.py | 70 +++++----- .../_archive/old_clebsch_gordan.py | 21 +-- .../utils/clebsch_gordan/_dispatch_.py | 2 +- .../utils/clebsch_gordan/cg_coefficients.py | 9 +- .../utils/clebsch_gordan/clebsch_gordan.py | 54 +++----- .../utils/{ => clebsch_gordan}/rotations.py | 11 +- .../rascaline/tests/utils/clebsch_gordan.py | 126 ++++++++++++------ 8 files changed, 171 insertions(+), 139 deletions(-) rename python/rascaline/rascaline/utils/{ => clebsch_gordan}/rotations.py (98%) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py index 0ad7a5f5f..4f1a1aaa1 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py @@ -1,13 +1,24 @@ from .cg_coefficients import ClebschGordanReal -from .clebsch_gordan import ( +from .clebsch_gordan import ( # noqa combine_single_center_to_body_order, combine_single_center_to_body_order_metadata_only, lambda_soap_vector, -) # noqa +) +from .rotations import ( # noqa + cartesian_rotation, + transform_frame_so3, + transform_frame_o3, + WignerDReal, +) + __all__ = [ + "cartesian_rotation", "ClebschGordanReal", "combine_single_center_to_body_order", "combine_single_center_to_body_order_metadata_only", "lambda_soap_vector", -] \ No newline at end of file + "transform_frame_so3", + "transform_frame_o3", + "WignerDReal", +] diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py index 930841890..86cbf288b 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py @@ -79,7 +79,7 @@ # lambda_cut: Union[None, int], # ) -> List[List[int]]: # """ -# Parses the user-defined angular selection filters, returning +# Parses the user-defined angular selection filters, returning # If a filter isn't specified by the user with `angular_selection=None`, then # no filter is applied. In this case all possible lambda channels are retained @@ -303,40 +303,38 @@ # return TensorMap(nux_keys, nux_blocks) - - # # Parse the angular and parity selections. Basic checks are performed here - # # and errors raised if invalid selections are passed. - # parity_selection = _parse_parity_selection(target_body_order, parity_selection) - # angular_selection = _parse_angular_selection( - # target_body_order, rascal_hypers["max_angular"], angular_selection, angular_cutoff - # ) - # if debug: - # print("parity_selection: ", parity_selection) - # print("angular_selection: ", angular_selection) +# # and errors raised if invalid selections are passed. +# parity_selection = _parse_parity_selection(target_body_order, parity_selection) +# angular_selection = _parse_angular_selection( +# target_body_order, rascal_hypers["max_angular"], angular_selection, angular_cutoff +# ) +# if debug: +# print("parity_selection: ", parity_selection) +# print("angular_selection: ", angular_selection) # # Pre-compute all the information needed to combined tensors at every - # # iteration. This includes the keys of the TensorMaps produced at each - # # iteration, the keys of the blocks combined to make them, and block - # # multiplicities. - # combine_info = [] - # for iteration in range(1, target_body_order): - # info = _create_combined_keys( - # nux_keys, - # nu1_keys, - # angular_selection[iteration - 1], - # parity_selection[iteration - 1], - # ) - # combine_info.append(info) - # nux_keys = info[0] - - # if debug: - # print("Num. keys at each step: ", [len(c[0]) for c in combine_info]) - # print([nu1_keys] + [c[0] for c in combine_info]) - # return - - # if np.any([len(c[0]) == 0 for c in combine_info]): - # raise ValueError( - # "invalid filters: one or more iterations produce no valid combinations." - # f" Number of keys at each iteration: {[len(c[0]) for c in combine_info]}." - # " Check the `angular_selection` and `parity_selection` arguments." - # ) +# # iteration. This includes the keys of the TensorMaps produced at each +# # iteration, the keys of the blocks combined to make them, and block +# # multiplicities. +# combine_info = [] +# for iteration in range(1, target_body_order): +# info = _create_combined_keys( +# nux_keys, +# nu1_keys, +# angular_selection[iteration - 1], +# parity_selection[iteration - 1], +# ) +# combine_info.append(info) +# nux_keys = info[0] + +# if debug: +# print("Num. keys at each step: ", [len(c[0]) for c in combine_info]) +# print([nu1_keys] + [c[0] for c in combine_info]) +# return + +# if np.any([len(c[0]) == 0 for c in combine_info]): +# raise ValueError( +# "invalid filters: one or more iterations produce no valid combinations." +# f" Number of keys at each iteration: {[len(c[0]) for c in combine_info]}." +# " Check the `angular_selection` and `parity_selection` arguments." +# ) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py index 3cc70355c..f94c6b1a0 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py @@ -8,11 +8,11 @@ import re from typing import Optional, Sequence, Union +import metatensor import numpy as np import wigners - -import metatensor from metatensor import Labels, TensorBlock, TensorMap + import rascaline @@ -731,7 +731,10 @@ def lambda_soap_vector( keys_to_drop = Labels( names=lsoap.keys.names, values=lsoap.keys.values[ - [lam not in lambda_filter for lam in lsoap.keys.column("spherical_harmonics_l")] + [ + lam not in lambda_filter + for lam in lsoap.keys.column("spherical_harmonics_l") + ] ], ) lsoap = metatensor.drop_blocks(lsoap, keys=keys_to_drop) @@ -741,15 +744,17 @@ def lambda_soap_vector( keys_to_drop = Labels( names=lsoap.keys.names, values=lsoap.keys.values[ - [s not in sigma_filter for s in lsoap.keys.column("inversion_sigma")] + [ + s not in sigma_filter + for s in lsoap.keys.column("inversion_sigma") + ] ], ) lsoap = metatensor.drop_blocks(lsoap, keys=keys_to_drop) if len(np.unique(lsoap.keys.column("inversion_sigma"))) == 1: - lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="inversion_sigma") - - - + lsoap = metatensor.remove_dimension( + lsoap, axis="keys", name="inversion_sigma" + ) return lsoap diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch_.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch_.py index dd9cd7b1a..7c1da832a 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch_.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch_.py @@ -1,3 +1,3 @@ """ Module containing dispatch functions for numpy/torch operations. -""" \ No newline at end of file +""" diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py index fa97733aa..7dd35cf12 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py @@ -147,18 +147,15 @@ def _complex_clebsch_gordan_matrix(l1, l2, lam): >>> from scipy.special import sph_harm >>> import numpy as np >>> import wigners - ... >>> C_112 = _complex_clebsch_gordan_matrix(1, 1, 2) - >>> comp_sph_1 = np.array([ - ... sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1+1) - ... ]) - >>> comp_sph_2 = np.array([sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1+1)]) + >>> comp_sph_1 = np.array([sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1 + 1)]) + >>> comp_sph_2 = np.array([sph_harm(m, 1, 0.2, 0.2) for m in range(-1, 1 + 1)]) >>> # obtain the (unnormalized) spherical harmonics >>> # with l = 2 by contraction over m1 and m2 >>> comp_sph_2_u = np.einsum("ijk,i,j->k", C_112, comp_sph_1, comp_sph_2) >>> # we can check that they differ from the spherical harmonics >>> # by a constant factor - >>> comp_sph_2 = np.array([sph_harm(m, 2, 0.2, 0.2) for m in range(-2, 2+1)]) + >>> comp_sph_2 = np.array([sph_harm(m, 2, 0.2, 0.2) for m in range(-2, 2 + 1)]) >>> ratio = comp_sph_2 / comp_sph_2_u >>> np.allclose(ratio[0], ratio) True diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index e8d5de9b4..f4d35a844 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -2,34 +2,15 @@ Module for computing Clebsch-gordan iterations with metatensor TensorMaps. """ import itertools -from typing import Optional, List, Tuple, Union - -import numpy as np +from typing import List, Optional, Tuple, Union import metatensor +import numpy as np from metatensor import Labels, TensorBlock, TensorMap from .cg_coefficients import ClebschGordanReal -# TODO: this PR -# - [ ] `combine_single_center_to_body_order`: feedback on API design -# - [ ] Unit tests - metadata and maths -# - [ ] Thorough documentation - -# TODO: later PRs, in roughly chronological order -# - [ ] Implement `combine_single_center_one_iteration` -# - [ ] Use dispatch for numpy/torch CG combination -# - [ ] Add support for gradients -# - [ ] Integrate with `sparse_accumulation` -# - [ ] Extend to multi-center desciptors -# - [ ] Customizable and arbitrary (non)linear transformations at each iteration - - -# 29/09/23 Meeting notes -# - `angular_cutoff` appropriate arg name? - - # ====================================================================== # ===== Functions to do CG combinations on single-center descriptors # ====================================================================== @@ -197,8 +178,8 @@ def combine_single_center_to_body_order_metadata_only( """ Performs a pseudo-CG combination of a nu = 1 (i.e. 2-body) single-center descriptor with itself to generate a descriptor of order - ``target_body_order``. - + ``target_body_order``. + A list of TensorMaps is returned, where each has the complete * metadata * of the TensorMap that would be created by a full CG combination. No actual CG combinations of block values arrays are performed, instead arrays of @@ -443,8 +424,9 @@ def _precompute_metadata( # Check that some keys are produced as a result of the combination if len(new_keys) == 0: raise ValueError( - f"invalid selections: iteration {iteration + 1} produces no valid combinations." - " Check the `angular_selection` and `parity_selection` arguments." + f"invalid selections: iteration {iteration + 1} produces no" + " valid combinations. Check the `angular_selection` and" + " `parity_selection` arguments." ) # Now check the angular and parity selections are present in the new keys @@ -453,9 +435,10 @@ def _precompute_metadata( for lam in angular_selection[iteration]: if lam not in new_keys.column("spherical_harmonics_l"): raise ValueError( - f"lambda = {lam} specified in `angular_selection` for iteration" - f" {iteration + 1}, but this is not a valid angular channel based on" - " the combination of lower body-order tensors. Check the passed" + f"lambda = {lam} specified in `angular_selection`" + f" for iteration {iteration + 1}, but this is not a" + " valid angular channel based on the combination of" + " lower body-order tensors. Check the passed" " `angular_selection` and try again." ) if parity_selection is not None: @@ -463,9 +446,10 @@ def _precompute_metadata( for sig in parity_selection[iteration]: if sig not in new_keys.column("inversion_sigma"): raise ValueError( - f"sigma = {sig} specified in `parity_selection` for iteration" - f" {iteration + 1}, but this is not a valid parity based on the" - " combination of lower body-order tensors. Check the passed" + f"sigma = {sig} specified in `parity_selection`" + f" for iteration {iteration + 1}, but this is not" + " a valid parity based on the combination of lower" + " body-order tensors. Check the passed" " `parity_selection` and try again." ) @@ -862,9 +846,9 @@ def _combine_arrays_dense( >>> L1 = 2 >>> L2 = 3 >>> LAM = 2 - >>> arr_1 = np.random.rand(N_SAMPLES, 2*L1+1, N_Q_PROPERTIES) - >>> arr_2 = np.random.rand(N_SAMPLES, 2*L2+1, N_P_PROPERTIES) - >>> cg_cache = {(L1, L2, LAM): np.random.rand(2*L1+1, 2*L2+1, 2*LAM+1)} + >>> arr_1 = np.random.rand(N_SAMPLES, 2 * L1 + 1, N_Q_PROPERTIES) + >>> arr_2 = np.random.rand(N_SAMPLES, 2 * L2 + 1, N_P_PROPERTIES) + >>> cg_cache = {(L1, L2, LAM): np.random.rand(2 * L1 + 1, 2 * L2 + 1, 2 * LAM + 1)} >>> out1 = _clebsch_gordan_dense(arr_1, arr_2, LAM, cg_cache) >>> # (samples l1_m q_features) (samples l2_m p_features), >>> # (l1_m l2_m lambda_mu) @@ -872,7 +856,7 @@ def _combine_arrays_dense( >>> # in einsum l1_m is l, l2_m is k, lambda_mu is L >>> out2 = np.einsum("slq, skp, lkL -> sLqp", arr_1, arr_2, cg_cache[(L1, L2, LAM)]) >>> # --> (samples lambda_mu (q_features p_features)) - >>> out2 = out2.reshape(arr_1.shape[0], 2*LAM+1, -1) + >>> out2 = out2.reshape(arr_1.shape[0], 2 * LAM + 1, -1) >>> print(np.allclose(out1, out2)) True """ diff --git a/python/rascaline/rascaline/utils/rotations.py b/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py similarity index 98% rename from python/rascaline/rascaline/utils/rotations.py rename to python/rascaline/rascaline/utils/clebsch_gordan/rotations.py index 016545d92..641a5b48f 100644 --- a/python/rascaline/rascaline/utils/rotations.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py @@ -2,16 +2,13 @@ Class for generating real Wigner-D matrices, and using them to rotate ASE frames and TensorMaps of density coefficients in the spherical basis. """ -from typing import Optional, Sequence +from typing import Sequence import ase import numpy as np -from scipy.spatial.transform import Rotation import torch - -import metatensor -from metatensor import Labels, TensorBlock, TensorMap -import wigners +from metatensor import TensorBlock, TensorMap +from scipy.spatial.transform import Rotation # ===== Functions for transformations in the Cartesian basis ===== @@ -67,6 +64,8 @@ def transform_frame_o3(frame: ase.Atoms, angles: Sequence[float]) -> ase.Atoms: return new_frame +# ===== WignerDReal for transformations in the spherical basis ===== + class WignerDReal: """ A helper class to compute Wigner D matrices given the Euler angles of a rotation, diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index a9a17b4a4..0a1a476e9 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -1,25 +1,31 @@ -import pytest -import numpy as np - import ase.io +import metatensor +import metatensor.operations import numpy as np +import pytest +from metatensor import Labels, TensorBlock, TensorMap import rascaline -import metatensor -from metatensor import Labels, TensorBlock, TensorMap -import metatensor.operations +from rascaline.utils.clebsch_gordan import ( + ClebschGordanReal, + _clebsch_gordan_combine_dense, + _clebsch_gordan_combine_sparse, + _combine_single_center, + n_body_iteration_single_center, +) -from rascaline.utils.clebsch_gordan import ClebschGordanReal, _clebsch_gordan_combine_dense, _clebsch_gordan_combine_sparse, n_body_iteration_single_center, _combine_single_center -def random_equivariant_array(n_samples=10, n_q_properties=4, n_p_properties=8, l=1, seed=None): +def random_equivariant_array( + n_samples=10, n_q_properties=4, n_p_properties=8, l=1, seed=None +): if seed is not None: np.random.seed(seed) - equi_l_array = np.random.rand(n_samples, 2*l+1, n_q_properties) + equi_l_array = np.random.rand(n_samples, 2 * l + 1, n_q_properties) return equi_l_array -class TestClebschGordan: +class TestClebschGordan: lam_max = 3 cg_cache_sparse = ClebschGordanReal(lambda_max=lam_max, sparse=True) cg_cache_dense = ClebschGordanReal(lambda_max=lam_max, sparse=False) @@ -28,8 +34,12 @@ def test_clebsch_gordan_combine_dense_sparse_agree(self): for l1, l2, lam in self.cg_cache_dense.coeffs.keys(): equi_l1_array = random_equivariant_array(l=l1, seed=51) equi_l2_array = random_equivariant_array(l=l2, seed=53) - out_sparse = _clebsch_gordan_combine_sparse(equi_l1_array, equi_l2_array, lam, self.cg_cache_sparse) - out_dense = _clebsch_gordan_combine_dense(equi_l1_array, equi_l2_array, lam, self.cg_cache_dense) + out_sparse = _clebsch_gordan_combine_sparse( + equi_l1_array, equi_l2_array, lam, self.cg_cache_sparse + ) + out_dense = _clebsch_gordan_combine_dense( + equi_l1_array, equi_l2_array, lam, self.cg_cache_dense + ) assert np.allclose(out_sparse, out_dense) @pytest.mark.parametrize("l1, l2", [(1, 2)]) @@ -42,41 +52,54 @@ def test_clebsch_gordan_orthogonality(self, l1, l2): https://en.wikipedia.org/wiki/Clebsch%E2%80%93Gordan_coefficients#Orthogonality_relations """ - assert self.lam_max >= l1+l2, "Did not precompute CG coeff with high enough lambda_max" - lam_min = abs(l1-l2) - lam_max = l1+l2 + assert ( + self.lam_max >= l1 + l2 + ), "Did not precompute CG coeff with high enough lambda_max" + lam_min = abs(l1 - l2) + lam_max = l1 + l2 # We test lam dimension - # \sum_{-m1 \leq l1 \leq m1, -m2 \leq l2 \leq m2} + # \sum_{-m1 \leq l1 \leq m1, -m2 \leq l2 \leq m2} # <λμ|l1m1,l2m2> = δ_μμ' for lam in range(lam_min, lam_max): - cg_mat = self.cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2*lam+1) - dot_product = cg_mat.T@ cg_mat + cg_mat = self.cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2 * lam + 1) + dot_product = cg_mat.T @ cg_mat diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) diag_mask[np.diag_indices(len(dot_product))] = True - assert np.allclose(dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask]) + assert np.allclose( + dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask] + ) assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) # We test l1 l2 dimension # \sum_{|l1-l2| \leq λ \leq l1+l2} \sum_{-μ \leq λ \leq μ} # <λμ|l1m1,l2m2> = δ_m1m1' δ_m2m2' - l1l2_dim = (2*l1+1) * (2*l2+1) + l1l2_dim = (2 * l1 + 1) * (2 * l2 + 1) dot_product = np.zeros((l1l2_dim, l1l2_dim)) - for lam in range(lam_min, lam_max+1): - cg_mat = self.cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2*lam+1) + for lam in range(lam_min, lam_max + 1): + cg_mat = self.cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2 * lam + 1) dot_product += cg_mat @ cg_mat.T diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) diag_mask[np.diag_indices(len(dot_product))] = True - assert np.allclose(dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask]) + assert np.allclose( + dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask] + ) assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) - h2o_frame = ase.Atoms('H2O', positions=[[-0.526383, -0.769327, -0.029366], - [-0.526383, 0.769327, -0.029366], - [ 0.066334, 0.000000, 0.003701]]) + h2o_frame = ase.Atoms( + "H2O", + positions=[ + [-0.526383, -0.769327, -0.029366], + [-0.526383, 0.769327, -0.029366], + [0.066334, 0.000000, 0.003701], + ], + ) @pytest.mark.parametrize("lam_max", [2]) def test_n_body_iteration_single_center_dense_sparse_agree(self, lam_max): - assert self.lam_max >= lam_max, "Did not precompute CG coeff with high enough lambda_max" + assert ( + self.lam_max >= lam_max + ), "Did not precompute CG coeff with high enough lambda_max" lambdas = np.array([0, 2]) rascal_hypers = { "cutoff": 3.0, # Angstrom @@ -95,7 +118,7 @@ def test_n_body_iteration_single_center_dense_sparse_agree(self, lam_max): lambdas=lambdas, lambda_cut=lam_max * 2, species_neighbors=[1, 8, 6], - use_sparse=True + use_sparse=True, ) n_body_dense = n_body_iteration_single_center( @@ -105,16 +128,17 @@ def test_n_body_iteration_single_center_dense_sparse_agree(self, lam_max): lambdas=lambdas, lambda_cut=lam_max * 2, species_neighbors=[1, 8, 6], - use_sparse=False + use_sparse=False, ) - assert metatensor.operations.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) - + assert metatensor.operations.allclose( + n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8 + ) @pytest.mark.parametrize("l", [1]) def test_combine_single_center_orthogonality(self, l): lam_min = 0 - lam_max = 2*l + lam_max = 2 * l rascal_hypers = { "cutoff": 3.0, # Angstrom "max_radial": 6, # Exclusive @@ -139,7 +163,11 @@ def test_combine_single_center_orthogonality(self, l): nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 ) nu1_tensor = metatensor.insert_dimension( - nu1_tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 + nu1_tensor, + axis="keys", + name="inversion_sigma", + values=np.array([1]), + index=1, ) combined_tensor = nu1_tensor.copy() @@ -154,28 +182,38 @@ def test_combine_single_center_orthogonality(self, l): ) nu_target = 1 - #order_nu inversion_sigma spherical_harmonics_l species_center - #"spherical_harmonics_l", - combined_tensor = combined_tensor.keys_to_properties(["l1", "l2", "inversion_sigma", "order_nu"]) + # order_nu inversion_sigma spherical_harmonics_l species_center + # "spherical_harmonics_l", + combined_tensor = combined_tensor.keys_to_properties( + ["l1", "l2", "inversion_sigma", "order_nu"] + ) combined_tensor = combined_tensor.keys_to_samples(["species_center"]) n_samples = combined_tensor[0].values.shape[0] combined_tensor_values = np.hstack( - [combined_tensor.block(Labels("spherical_harmonics_l", np.array([[l]]))).values.reshape(n_samples, -1) - for l in combined_tensor.keys["spherical_harmonics_l"]]) + [ + combined_tensor.block( + Labels("spherical_harmonics_l", np.array([[l]])) + ).values.reshape(n_samples, -1) + for l in combined_tensor.keys["spherical_harmonics_l"] + ] + ) combined_tensor_norm = np.linalg.norm(combined_tensor_values, axis=1) nu1_tensor = nu1_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) nu1_tensor_values = np.hstack( - [nu1_tensor.block(Labels("spherical_harmonics_l", np.array([[l]]))).values.reshape(n_samples, -1) - for l in nu1_tensor.keys["spherical_harmonics_l"]]) + [ + nu1_tensor.block( + Labels("spherical_harmonics_l", np.array([[l]])) + ).values.reshape(n_samples, -1) + for l in nu1_tensor.keys["spherical_harmonics_l"] + ] + ) nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) assert np.allclose(combined_tensor_norm, nu1_tensor_norm) - - # old test that become decprecated through prototyping on API - #def test_soap_kernel(): + # def test_soap_kernel(): # """ # Tests if we get the same result computing SOAP from spherical expansion coefficients using GC utils # as when using SoapPowerSpectrum. @@ -222,7 +260,7 @@ def test_combine_single_center_orthogonality(self, l): # # worries me a bit that the rtol is shit, might be missing multiplicity? # assert np.allclose(kernel_cg, kernel_rascaline, atol=1e-9, rtol=1e-1) # - #def test_soap_zeros(): + # def test_soap_zeros(): # """ # Tests if the l1!=l2 values are zero when computing the 3-body invariant (SOAP) # """ From 768b135d1fed87fdf1362dbbb606bc7c25eda79b Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 11 Oct 2023 14:56:56 +0200 Subject: [PATCH 58/96] torch import --- .../rascaline/utils/clebsch_gordan/rotations.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py b/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py index 641a5b48f..edf90b3d6 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py @@ -6,10 +6,17 @@ import ase import numpy as np -import torch from metatensor import TensorBlock, TensorMap from scipy.spatial.transform import Rotation +try: + import torch + from torch import Tensor as TorchTensor +except ImportError: + + class TorchTensor: + pass + # ===== Functions for transformations in the Cartesian basis ===== @@ -196,7 +203,7 @@ def rotate_tensorblock(self, l: int, block: TensorBlock) -> TensorBlock: # Perform the rotation, either with numpy or torch, by taking the # tensordot product of the irreducible spherical components. Modify # in-place the values of the copied TensorBlock. - if isinstance(vals, torch.Tensor): + if isinstance(vals, TorchTensor): wig = torch.tensor(wig) block_rotated.values[:] = torch.tensordot( vals.swapaxes(1, 2), wig, dims=1 From 9b7567b2b055dd11cb21b9ea9a77747259884942 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Thu, 12 Oct 2023 11:27:04 +0200 Subject: [PATCH 59/96] adapt tests to new API --- .../rascaline/tests/utils/clebsch_gordan.py | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 0a1a476e9..6b0307268 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -8,10 +8,13 @@ import rascaline from rascaline.utils.clebsch_gordan import ( ClebschGordanReal, - _clebsch_gordan_combine_dense, - _clebsch_gordan_combine_sparse, - _combine_single_center, - n_body_iteration_single_center, + combine_single_center_to_body_order, +) +from rascaline.utils.clebsch_gordan.clebsch_gordan import ( + _combine_arrays_dense, + _combine_arrays_sparse, + _combine_single_center_blocks, + combine_single_center_one_iteration, ) @@ -34,10 +37,10 @@ def test_clebsch_gordan_combine_dense_sparse_agree(self): for l1, l2, lam in self.cg_cache_dense.coeffs.keys(): equi_l1_array = random_equivariant_array(l=l1, seed=51) equi_l2_array = random_equivariant_array(l=l2, seed=53) - out_sparse = _clebsch_gordan_combine_sparse( + out_sparse = _combine_arrays_sparse( equi_l1_array, equi_l2_array, lam, self.cg_cache_sparse ) - out_dense = _clebsch_gordan_combine_dense( + out_dense = _combine_arrays_dense( equi_l1_array, equi_l2_array, lam, self.cg_cache_dense ) assert np.allclose(out_sparse, out_dense) @@ -100,7 +103,7 @@ def test_n_body_iteration_single_center_dense_sparse_agree(self, lam_max): assert ( self.lam_max >= lam_max ), "Did not precompute CG coeff with high enough lambda_max" - lambdas = np.array([0, 2]) + lambdas = [0, 2] rascal_hypers = { "cutoff": 3.0, # Angstrom "max_radial": 2, # Exclusive @@ -110,24 +113,23 @@ def test_n_body_iteration_single_center_dense_sparse_agree(self, lam_max): "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, "center_atom_weight": 1.0, } - - n_body_sparse = n_body_iteration_single_center( - [self.h2o_frame], - rascal_hypers=rascal_hypers, - nu_target=3, - lambdas=lambdas, - lambda_cut=lam_max * 2, - species_neighbors=[1, 8, 6], + calculator = rascaline.SphericalExpansion(**rascal_hypers) + nu1_tensor = calculator.compute([self.h2o_frame]) + n_body_sparse = combine_single_center_to_body_order( + nu1_tensor, + 3, # target_body_order + angular_cutoff=lam_max * 2, + angular_selection=lambdas, + parity_selection=None, use_sparse=True, ) - n_body_dense = n_body_iteration_single_center( - [self.h2o_frame], - rascal_hypers=rascal_hypers, - nu_target=3, - lambdas=lambdas, - lambda_cut=lam_max * 2, - species_neighbors=[1, 8, 6], + n_body_dense = combine_single_center_to_body_order( + nu1_tensor, + 3, # target_body_order + angular_cutoff=lam_max * 2, + angular_selection=lambdas, + parity_selection=None, use_sparse=False, ) @@ -171,16 +173,18 @@ def test_combine_single_center_orthogonality(self, l): ) combined_tensor = nu1_tensor.copy() - lambdas = np.array(range(lam_max)) + lambdas = list(range(lam_max)) - combined_tensor = _combine_single_center( - tensor_1=combined_tensor, - tensor_2=nu1_tensor, - lambdas=lambdas, - cg_cache=self.cg_cache_sparse, - only_keep_parity=True, - ) - nu_target = 1 + with pytest.raises(NotImplementedError): + combined_tensor = combine_single_center_one_iteration( + tensor_1=combined_tensor, + tensor_2=nu1_tensor, + angular_cutoff=None, + angular_selection=lambdas, + parity_selection=None, + ) + # test needs first implementation of combine_single_center_one_iteration to work + return # order_nu inversion_sigma spherical_harmonics_l species_center # "spherical_harmonics_l", @@ -243,7 +247,7 @@ def test_combine_single_center_orthogonality(self, l): # # clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) # cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) - # soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) + # soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) # -> needs # soap_cg = soap_cg.keys_to_properties(["spherical_harmonics_l"]).keys_to_samples(["species_center"]) # n_samples = len(soap_cg[0].samples) # kernel_cg = np.zeros((n_samples, n_samples)) From 0af495ba0617c9378a581678912b2e870315d565 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Thu, 12 Oct 2023 11:53:12 +0200 Subject: [PATCH 60/96] adapt test_combine_single_center_orthogonality test to new API --- .../rascaline/tests/utils/clebsch_gordan.py | 70 +++++++------------ 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 6b0307268..8d77ead52 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -99,7 +99,11 @@ def test_clebsch_gordan_orthogonality(self, l1, l2): ) @pytest.mark.parametrize("lam_max", [2]) - def test_n_body_iteration_single_center_dense_sparse_agree(self, lam_max): + def test_combine_single_center_to_body_order_dense_sparse_agree(self, lam_max): + """ + tests if combine_single_center_to_body_order agrees for dense and sparse cg + coeffs + """ assert ( self.lam_max >= lam_max ), "Did not precompute CG coeff with high enough lambda_max" @@ -151,60 +155,36 @@ def test_combine_single_center_orthogonality(self, l): "center_atom_weight": 1.0, } - # Generate a rascaline SphericalExpansion, for only the selected samples if - # applicable calculator = rascaline.SphericalExpansion(**rascal_hypers) nu1_tensor = calculator.compute([self.h2o_frame]) - # Move the "species_neighbor" key to the properties. If species_neighbors is - # passed as a list of int, sparsity can be created in the properties for - # these species. - keys_to_move = "species_neighbor" - nu1_tensor = nu1_tensor.keys_to_properties(keys_to_move=keys_to_move) - # Add "order_nu" and "inversion_sigma" key dimensions, both with values 1 - nu1_tensor = metatensor.insert_dimension( - nu1_tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 - ) - nu1_tensor = metatensor.insert_dimension( + + # compute norm of the body order 2 tensor + nu2_tensor = combine_single_center_to_body_order( nu1_tensor, - axis="keys", - name="inversion_sigma", - values=np.array([1]), - index=1, + 2, # target_body_order + angular_cutoff=None, + angular_selection=None, + parity_selection=None, + use_sparse=True, ) - combined_tensor = nu1_tensor.copy() - - lambdas = list(range(lam_max)) - with pytest.raises(NotImplementedError): - combined_tensor = combine_single_center_one_iteration( - tensor_1=combined_tensor, - tensor_2=nu1_tensor, - angular_cutoff=None, - angular_selection=lambdas, - parity_selection=None, - ) - # test needs first implementation of combine_single_center_one_iteration to work - return - - # order_nu inversion_sigma spherical_harmonics_l species_center - # "spherical_harmonics_l", - combined_tensor = combined_tensor.keys_to_properties( - ["l1", "l2", "inversion_sigma", "order_nu"] + nu2_tensor = nu2_tensor.keys_to_properties( + ["inversion_sigma", "order_nu"] ) - combined_tensor = combined_tensor.keys_to_samples(["species_center"]) - n_samples = combined_tensor[0].values.shape[0] - combined_tensor_values = np.hstack( + nu2_tensor = nu2_tensor.keys_to_samples(["species_center"]) + n_samples = nu2_tensor[0].values.shape[0] + nu2_tensor_values = np.hstack( [ - combined_tensor.block( + nu2_tensor.block( Labels("spherical_harmonics_l", np.array([[l]])) ).values.reshape(n_samples, -1) - for l in combined_tensor.keys["spherical_harmonics_l"] + for l in nu2_tensor.keys["spherical_harmonics_l"] ] ) - combined_tensor_norm = np.linalg.norm(combined_tensor_values, axis=1) + nu2_tensor_norm = np.linalg.norm(nu2_tensor_values, axis=1) - nu1_tensor = nu1_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) - nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) + # compute norm of the body order 1 tensor + nu1_tensor = nu1_tensor.keys_to_samples(["species_center", "species_neighbor"]) nu1_tensor_values = np.hstack( [ nu1_tensor.block( @@ -214,7 +194,9 @@ def test_combine_single_center_orthogonality(self, l): ] ) nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) - assert np.allclose(combined_tensor_norm, nu1_tensor_norm) + + # check if the norm is equal + assert np.allclose(nu2_tensor_norm, nu1_tensor_norm) # old test that become decprecated through prototyping on API # def test_soap_kernel(): From b6d8de76bebb9701d9b415da009d24fe9831e756 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Thu, 12 Oct 2023 11:53:58 +0200 Subject: [PATCH 61/96] format tests --- python/rascaline/tests/utils/clebsch_gordan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 8d77ead52..b800024c6 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -168,9 +168,7 @@ def test_combine_single_center_orthogonality(self, l): use_sparse=True, ) - nu2_tensor = nu2_tensor.keys_to_properties( - ["inversion_sigma", "order_nu"] - ) + nu2_tensor = nu2_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) nu2_tensor = nu2_tensor.keys_to_samples(["species_center"]) n_samples = nu2_tensor[0].values.shape[0] nu2_tensor_values = np.hstack( From b10f62359b98f859ca12e303c84bff33bb0b2b52 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Thu, 12 Oct 2023 12:18:35 +0200 Subject: [PATCH 62/96] making more transparent what happens with the species neighbor in the tests --- python/rascaline/tests/utils/clebsch_gordan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index b800024c6..ae190a580 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -167,7 +167,6 @@ def test_combine_single_center_orthogonality(self, l): parity_selection=None, use_sparse=True, ) - nu2_tensor = nu2_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) nu2_tensor = nu2_tensor.keys_to_samples(["species_center"]) n_samples = nu2_tensor[0].values.shape[0] @@ -182,7 +181,8 @@ def test_combine_single_center_orthogonality(self, l): nu2_tensor_norm = np.linalg.norm(nu2_tensor_values, axis=1) # compute norm of the body order 1 tensor - nu1_tensor = nu1_tensor.keys_to_samples(["species_center", "species_neighbor"]) + nu1_tensor = nu1_tensor.keys_to_properties(["species_neighbor"]) + nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) nu1_tensor_values = np.hstack( [ nu1_tensor.block( From c511adec44e29769915d910c31019673127dff44 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Tue, 17 Oct 2023 15:02:34 +0200 Subject: [PATCH 63/96] Add wigners as a core dependency --- pyproject.toml | 1 + tox.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 924103358..260f6729a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ classifiers = [ dependencies = [ "metatensor-core >=0.1.0,<0.2.0", + "wigners", ] [project.urls] diff --git a/tox.ini b/tox.ini index c4df209aa..329f8f764 100644 --- a/tox.ini +++ b/tox.ini @@ -53,6 +53,7 @@ deps = chemfiles pytest pytest-cov + wigners commands = pytest --cov={env_site_packages_dir}/rascaline --cov-report xml:.tox/coverage.xml --import-mode=append {posargs} From 827f9b31b9b44fac31957ee1c1f4be22cb7927b4 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 18 Oct 2023 15:17:48 +0200 Subject: [PATCH 64/96] sympy and mtops required --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 329f8f764..363dbb6cd 100644 --- a/tox.ini +++ b/tox.ini @@ -51,8 +51,10 @@ deps = {[testenv]metatensor-core-requirement} ase chemfiles + metatensor-operations pytest pytest-cov + sympy wigners commands = From 4b37ccf2bc248fd0b191894f2084095a241031a8 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 18 Oct 2023 15:22:29 +0200 Subject: [PATCH 65/96] FiX SIGN CONVENTION! + some tests --- .../utils/clebsch_gordan/cg_coefficients.py | 33 +- .../utils/clebsch_gordan/clebsch_gordan.py | 24 +- .../utils/clebsch_gordan/rotations.py | 3 + .../utils/power_spectrum/calculator.py | 4 +- .../rascaline/tests/utils/clebsch_gordan.py | 505 +++++++++--------- .../tests/utils/data/h2_isolated.xyz | 4 + .../tests/utils/data/h2o_isolated.xyz | 5 + .../tests/utils/data/h2o_periodic.xyz | 5 + 8 files changed, 296 insertions(+), 287 deletions(-) create mode 100644 python/rascaline/tests/utils/data/h2_isolated.xyz create mode 100644 python/rascaline/tests/utils/data/h2o_isolated.xyz create mode 100644 python/rascaline/tests/utils/data/h2o_periodic.xyz diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py index 7dd35cf12..7127464e7 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py @@ -60,8 +60,8 @@ def build_coeff_dict(lambda_max: int, sparse: bool): c2r = {} coeff_dict = {} for lam in range(0, lambda_max + 1): + c2r[lam] = _complex2real(lam) r2c[lam] = _real2complex(lam) - c2r[lam] = np.conjugate(r2c[lam]).T for l1 in range(lambda_max + 1): for l2 in range(lambda_max + 1): @@ -104,25 +104,42 @@ def _real2complex(lam: int) -> np.ndarray: Computes a matrix that can be used to convert from real to complex-valued spherical harmonics(coefficients) of order ``lam``. - It's meant to be applied to the left, ``real2complex @ [-lam, ..., +lam]``. + This is meant to be applied to the left: ``real2complex @ [-lam, ..., + +lam]``. + + See https://en.wikipedia.org/wiki/Spherical_harmonics#Real_form for details + on the convention for how these tranformations are defined. """ result = np.zeros((2 * lam + 1, 2 * lam + 1), dtype=np.complex128) - i_sqrt_2 = 1.0 / np.sqrt(2) + inv_sqrt_2 = 1.0 / np.sqrt(2) + i_sqrt_2 = 1j / np.sqrt(2) for m in range(-lam, lam + 1): if m < 0: - result[lam - m, lam + m] = i_sqrt_2 * 1j * (-1) ** m - result[lam + m, lam + m] = -i_sqrt_2 * 1j + # Positve part + result[lam + m, lam + m] = +i_sqrt_2 + # Negative part + result[lam - m, lam + m] = -i_sqrt_2 * ((-1) ** m) if m == 0: - result[lam, lam] = 1.0 + result[lam, lam] = +1.0 if m > 0: - result[lam + m, lam + m] = i_sqrt_2 * (-1) ** m - result[lam - m, lam + m] = i_sqrt_2 + # Negative part + result[lam - m, lam + m] = +inv_sqrt_2 + # Positive part + result[lam + m, lam + m] = +inv_sqrt_2 * ((-1) ** m) return result +def _complex2real(lam: int) -> np.ndarray: + """ + Converts from complex to real spherical harmonics. This is just given by the + conjugate tranpose of the real->complex transformation matrices. + """ + return np.conjugate(_real2complex(lam)).T + + def _complex_clebsch_gordan_matrix(l1, l2, lam): r"""clebsch-gordan matrix Computes the Clebsch-Gordan (CG) matrix for diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index f4d35a844..feaf33e70 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -57,12 +57,14 @@ def lambda_soap_vector( # Drop the redundant key name "order_nu". This is by definition 2 for all # lambda-SOAP blocks. - lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="order_nu") + keys = lsoap.keys.remove(name="order_nu") + lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()]) # If a single parity is requested, drop the now redundant "inversion_sigma" # key name if len(np.unique(lsoap.keys.column("inversion_sigma"))) == 1: - lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="inversion_sigma") + keys = lsoap.keys.remove(name="inversion_sigma") + lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()]) return lsoap @@ -226,7 +228,7 @@ def combine_single_center_to_body_order_metadata_only( ) # Create a copy of the nu = 1 tensor to combine with itself - nu_x_tensor = nu_1_tensor.copy() + nu_x_tensor = nu_1_tensor # Iteratively combine block values nu_x_tensors = [] @@ -234,14 +236,14 @@ def combine_single_center_to_body_order_metadata_only( # Combine blocks nu_x_blocks = [] # TODO: is there a faster way of iterating over keys/blocks here? - for nu_x_key, key_1, key_2, _ in zip(*combination_metadata[iteration]): - # Combine the pair of block values, accounting for multiplicity + # TODO: only account for multiplicity at the end + for nu_x_key, key_1, key_2, mult in zip(*combination_metadata[iteration]): nu_x_block = _combine_single_center_blocks( nu_x_tensor[key_1], nu_1_tensor[key_2], nu_x_key["spherical_harmonics_l"], cg_cache=None, - correction_factor=1.0, + correction_factor=mult, return_metadata_only=True, ) nu_x_blocks.append(nu_x_block) @@ -295,13 +297,9 @@ def _standardize_tensor_metadata(tensor: TensorMap) -> TensorMap: """ if "species_neighbor" in tensor.keys.names: tensor = tensor.keys_to_properties(keys_to_move="species_neighbor") - tensor = metatensor.insert_dimension( - tensor, axis="keys", name="order_nu", values=np.array([1]), index=0 - ) - tensor = metatensor.insert_dimension( - tensor, axis="keys", name="inversion_sigma", values=np.array([1]), index=1 - ) - return tensor + keys = tensor.keys.insert(name="order_nu", values=np.array([1]), index=0) + keys = keys.insert(name="inversion_sigma", values=np.array([1]), index=1) + return TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) def _parse_selection_filters( diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py b/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py index edf90b3d6..b3794e119 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py @@ -18,6 +18,9 @@ class TorchTensor: pass +# TODO: move to test suite, don't provide as public functions? + + # ===== Functions for transformations in the Cartesian basis ===== diff --git a/python/rascaline/rascaline/utils/power_spectrum/calculator.py b/python/rascaline/rascaline/utils/power_spectrum/calculator.py index 6815f4b16..51d4588e4 100644 --- a/python/rascaline/rascaline/utils/power_spectrum/calculator.py +++ b/python/rascaline/rascaline/utils/power_spectrum/calculator.py @@ -217,7 +217,9 @@ def compute( ell = key[0] species_center = key[1] - factor = 1 / sqrt(2 * ell + 1) + # Include -1^l in the prefactor + factor = ((-1) ** ell) / sqrt(2 * ell + 1) + # Find that block indices that have the same spherical_harmonics_l and # species_center selection = Labels( diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index ae190a580..3d42b2e9f 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -1,283 +1,258 @@ +# -*- coding: utf-8 -*- +import os +from typing import List + import ase.io -import metatensor -import metatensor.operations -import numpy as np import pytest -from metatensor import Labels, TensorBlock, TensorMap +from numpy.testing import assert_allclose +import metatensor import rascaline -from rascaline.utils.clebsch_gordan import ( - ClebschGordanReal, - combine_single_center_to_body_order, -) -from rascaline.utils.clebsch_gordan.clebsch_gordan import ( - _combine_arrays_dense, - _combine_arrays_sparse, - _combine_single_center_blocks, - combine_single_center_one_iteration, +from metatensor import Labels, TensorBlock, TensorMap +from rascaline.utils import clebsch_gordan, PowerSpectrum + + +DATA_ROOT = os.path.join(os.path.dirname(__file__), "data") + +RASCAL_HYPERS = { + "cutoff": 3.0, # Angstrom + "max_radial": 6, # Exclusive + "max_angular": 5, # Inclusive + "atomic_gaussian_width": 0.2, + "radial_basis": {"Gto": {}}, + "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + "center_atom_weight": 1.0, +} + +RASCAL_HYPERS_SMALL = { + "cutoff": 3.0, # Angstrom + "max_radial": 1, # Exclusive + "max_angular": 2, # Inclusive + "atomic_gaussian_width": 0.2, + "radial_basis": {"Gto": {}}, + "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + "center_atom_weight": 1.0, +} + + +# ============ Pytest fixtures ============ + +# ============ Helper functions ============ + + +def h2_isolated(): + return ase.io.read(os.path.join(DATA_ROOT, "h2_isolated.xyz"), ":") + + +def h2o_isolated(): + return ase.io.read(os.path.join(DATA_ROOT, "h2o_isolated.xyz"), ":") + + +def h2o_periodic(): + return ase.io.read(os.path.join(DATA_ROOT, "h2o_periodic.xyz"), ":") + + +def wigners(lmax: int): + return clebsch_gordan.WignerDReal(lmax=lmax) + + +def sphex(frames: List[ase.Atoms]): + """Returns a rascaline SphericalExpansion""" + calculator = rascaline.SphericalExpansion(**RASCAL_HYPERS) + return calculator.compute(frames) + + +def sphex_small_features(frames: List[ase.Atoms]): + """Returns a rascaline SphericalExpansion""" + calculator = rascaline.SphericalExpansion(**RASCAL_HYPERS_SMALL) + return calculator.compute(frames) + + +def powspec(frames: List[ase.Atoms]): + """Returns a rascaline PowerSpectrum constructed from a + SphericalExpansion""" + return PowerSpectrum(rascaline.SphericalExpansion(**RASCAL_HYPERS)).compute(frames) + + +def powspec_small_features(frames: List[ase.Atoms]): + """Returns a rascaline PowerSpectrum constructed from a + SphericalExpansion""" + return PowerSpectrum(rascaline.SphericalExpansion(**RASCAL_HYPERS_SMALL)).compute(frames) + + +# ============ Test equivariance ============ + + +@pytest.mark.parametrize( + "frames, nu_target, angular_cutoff, angular_selection, parity_selection", + [ + ( + h2o_isolated(), + 2, + 5, + [0], # [0, 4, 5], + [+1], # [+1, -1], + ), + ( + h2o_isolated(), + 2, + 5, + None, # [0, 4, 5], + None, # [+1, -1], + ), + ( + h2o_periodic(), + 2, + 5, + None, # [0, 1, 2, 3, 4, 5], + None, # [-1], + ), + ], ) +def test_so3_equivariance( + frames: List[ase.Atoms], + nu_target: int, + angular_cutoff: int, + angular_selection: List[List[int]], + parity_selection: List[List[int]], +): + wig = wigners(nu_target * RASCAL_HYPERS["max_angular"]) + frames_so3 = [ + clebsch_gordan.transform_frame_so3(frame, wig.angles) for frame in frames + ] + + nu_1 = sphex(frames) + nu_1_so3 = sphex(frames_so3) + + nu_3 = clebsch_gordan.combine_single_center_to_body_order( + nu_1_tensor=nu_1, + target_body_order=nu_target, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + ) + nu_3_so3 = clebsch_gordan.combine_single_center_to_body_order( + nu_1_tensor=nu_1_so3, + target_body_order=nu_target, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + ) + nu_3_transf = wig.transform_tensormap_so3(nu_3) -def random_equivariant_array( - n_samples=10, n_q_properties=4, n_p_properties=8, l=1, seed=None + # assert metatensor.equal_metadata(nu_3_transf, nu_3_rot) + assert metatensor.allclose(nu_3_transf, nu_3_so3) + + +@pytest.mark.parametrize( + "frames, nu_target, angular_cutoff, angular_selection, parity_selection", + [ + ( + h2o_isolated(), + 2, + 5, + [0], # [0, 4, 5], + [+1], # [+1, -1], + ), + ( + h2o_isolated(), + 2, + 5, + None, # [0, 4, 5], + None, # [+1, -1], + ), + ( + h2o_periodic(), + 2, + 5, + None, # [0, 1, 2, 3, 4, 5], + None, # [-1], + ), + ], +) +def test_o3_equivariance( + frames: List[ase.Atoms], + nu_target: int, + angular_cutoff: int, + angular_selection: List[List[int]], + parity_selection: List[List[int]], ): - if seed is not None: - np.random.seed(seed) + wig = wigners(nu_target * RASCAL_HYPERS["max_angular"]) + frames_o3 = [ + clebsch_gordan.transform_frame_o3(frame, wig.angles) for frame in frames + ] - equi_l_array = np.random.rand(n_samples, 2 * l + 1, n_q_properties) - return equi_l_array + nu_1 = sphex(frames) + nu_1_o3 = sphex(frames_o3) + nu_3 = clebsch_gordan.combine_single_center_to_body_order( + nu_1_tensor=nu_1, + target_body_order=nu_target, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + ) + nu_3_o3 = clebsch_gordan.combine_single_center_to_body_order( + nu_1_tensor=nu_1_o3, + target_body_order=nu_target, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + ) -class TestClebschGordan: - lam_max = 3 - cg_cache_sparse = ClebschGordanReal(lambda_max=lam_max, sparse=True) - cg_cache_dense = ClebschGordanReal(lambda_max=lam_max, sparse=False) + nu_3_transf = wig.transform_tensormap_o3(nu_3) - def test_clebsch_gordan_combine_dense_sparse_agree(self): - for l1, l2, lam in self.cg_cache_dense.coeffs.keys(): - equi_l1_array = random_equivariant_array(l=l1, seed=51) - equi_l2_array = random_equivariant_array(l=l2, seed=53) - out_sparse = _combine_arrays_sparse( - equi_l1_array, equi_l2_array, lam, self.cg_cache_sparse - ) - out_dense = _combine_arrays_dense( - equi_l1_array, equi_l2_array, lam, self.cg_cache_dense - ) - assert np.allclose(out_sparse, out_dense) - - @pytest.mark.parametrize("l1, l2", [(1, 2)]) - def test_clebsch_gordan_orthogonality(self, l1, l2): - """ - Test orthogonality relationships - - References - ---------- - - https://en.wikipedia.org/wiki/Clebsch%E2%80%93Gordan_coefficients#Orthogonality_relations - """ - assert ( - self.lam_max >= l1 + l2 - ), "Did not precompute CG coeff with high enough lambda_max" - lam_min = abs(l1 - l2) - lam_max = l1 + l2 - - # We test lam dimension - # \sum_{-m1 \leq l1 \leq m1, -m2 \leq l2 \leq m2} - # <λμ|l1m1,l2m2> = δ_μμ' - for lam in range(lam_min, lam_max): - cg_mat = self.cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2 * lam + 1) - dot_product = cg_mat.T @ cg_mat - diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) - diag_mask[np.diag_indices(len(dot_product))] = True - assert np.allclose( - dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask] - ) - assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) - - # We test l1 l2 dimension - # \sum_{|l1-l2| \leq λ \leq l1+l2} \sum_{-μ \leq λ \leq μ} - # <λμ|l1m1,l2m2> = δ_m1m1' δ_m2m2' - l1l2_dim = (2 * l1 + 1) * (2 * l2 + 1) - dot_product = np.zeros((l1l2_dim, l1l2_dim)) - for lam in range(lam_min, lam_max + 1): - cg_mat = self.cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2 * lam + 1) - dot_product += cg_mat @ cg_mat.T - diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) - diag_mask[np.diag_indices(len(dot_product))] = True - assert np.allclose( - dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask] - ) - assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) - - h2o_frame = ase.Atoms( - "H2O", - positions=[ - [-0.526383, -0.769327, -0.029366], - [-0.526383, 0.769327, -0.029366], - [0.066334, 0.000000, 0.003701], - ], + # assert metatensor.equal_metadata(nu_3_transf, nu_3_rot) + assert metatensor.allclose(nu_3_transf, nu_3_o3) + + +# ============ Test lambda-SOAP vs PowerSpectrum ============ + + +@pytest.mark.parametrize("frames", [h2_isolated()]) +def test_lambda_soap_vs_powerspectrum(frames): + """ + Tests for exact equivalence between the invariant block of a generated + lambda-SOAP equivariant and the Python implementation of PowerSpectrum in + rascaline utils. + """ + # Build a PowerSpectrum + ps = powspec_small_features(frames) + + # Build a lambda-SOAP + nu_1_tensor = sphex_small_features(frames) + lsoap = clebsch_gordan.lambda_soap_vector( + nu_1_tensor=nu_1_tensor, + angular_selection=[0], ) + keys = lsoap.keys.remove(name="spherical_harmonics_l") + lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()]) - @pytest.mark.parametrize("lam_max", [2]) - def test_combine_single_center_to_body_order_dense_sparse_agree(self, lam_max): - """ - tests if combine_single_center_to_body_order agrees for dense and sparse cg - coeffs - """ - assert ( - self.lam_max >= lam_max - ), "Did not precompute CG coeff with high enough lambda_max" - lambdas = [0, 2] - rascal_hypers = { - "cutoff": 3.0, # Angstrom - "max_radial": 2, # Exclusive - "max_angular": lam_max, # Inclusive - "atomic_gaussian_width": 0.2, - "radial_basis": {"Gto": {}}, - "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, - "center_atom_weight": 1.0, - } - calculator = rascaline.SphericalExpansion(**rascal_hypers) - nu1_tensor = calculator.compute([self.h2o_frame]) - n_body_sparse = combine_single_center_to_body_order( - nu1_tensor, - 3, # target_body_order - angular_cutoff=lam_max * 2, - angular_selection=lambdas, - parity_selection=None, - use_sparse=True, + # Manipulate metadata to match that of PowerSpectrum: + # 1) remove components axis + # 2) change "l1" and "l2" properties dimensions to just "l" (as l1 == l2) + blocks = [] + for block in lsoap.blocks(): + n_samples, n_props = block.values.shape[0], block.values.shape[2] + new_props = block.properties + new_props = new_props.remove(name="l1") + new_props = new_props.rename(old="l2", new="l") + blocks.append( + TensorBlock( + values=block.values.reshape((n_samples, n_props)), + samples=block.samples, + components=[], + properties=new_props, + ) ) + lsoap = TensorMap(keys=lsoap.keys, blocks=blocks) - n_body_dense = combine_single_center_to_body_order( - nu1_tensor, - 3, # target_body_order - angular_cutoff=lam_max * 2, - angular_selection=lambdas, - parity_selection=None, - use_sparse=False, - ) + # Compare metadata + assert metatensor.equal_metadata(lsoap, ps) - assert metatensor.operations.allclose( - n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8 - ) + # allclose on values + assert metatensor.allclose(lsoap, ps) - @pytest.mark.parametrize("l", [1]) - def test_combine_single_center_orthogonality(self, l): - lam_min = 0 - lam_max = 2 * l - rascal_hypers = { - "cutoff": 3.0, # Angstrom - "max_radial": 6, # Exclusive - "max_angular": l, # Inclusive - "atomic_gaussian_width": 0.2, - "radial_basis": {"Gto": {}}, - "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, - "center_atom_weight": 1.0, - } - - calculator = rascaline.SphericalExpansion(**rascal_hypers) - nu1_tensor = calculator.compute([self.h2o_frame]) - - # compute norm of the body order 2 tensor - nu2_tensor = combine_single_center_to_body_order( - nu1_tensor, - 2, # target_body_order - angular_cutoff=None, - angular_selection=None, - parity_selection=None, - use_sparse=True, - ) - nu2_tensor = nu2_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) - nu2_tensor = nu2_tensor.keys_to_samples(["species_center"]) - n_samples = nu2_tensor[0].values.shape[0] - nu2_tensor_values = np.hstack( - [ - nu2_tensor.block( - Labels("spherical_harmonics_l", np.array([[l]])) - ).values.reshape(n_samples, -1) - for l in nu2_tensor.keys["spherical_harmonics_l"] - ] - ) - nu2_tensor_norm = np.linalg.norm(nu2_tensor_values, axis=1) - - # compute norm of the body order 1 tensor - nu1_tensor = nu1_tensor.keys_to_properties(["species_neighbor"]) - nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) - nu1_tensor_values = np.hstack( - [ - nu1_tensor.block( - Labels("spherical_harmonics_l", np.array([[l]])) - ).values.reshape(n_samples, -1) - for l in nu1_tensor.keys["spherical_harmonics_l"] - ] - ) - nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) - - # check if the norm is equal - assert np.allclose(nu2_tensor_norm, nu1_tensor_norm) - - # old test that become decprecated through prototyping on API - # def test_soap_kernel(): - # """ - # Tests if we get the same result computing SOAP from spherical expansion coefficients using GC utils - # as when using SoapPowerSpectrum. - # - # """ - # frames = ase.Atoms('HO', - # positions=[[0., 0., 0.], [1., 1., 1.]], - # pbc=[False, False, False]) - # - # - # rascal_hypers = { - # "cutoff": 3.0, # Angstrom - # "max_radial": 2, # Exclusive - # "max_angular": 3, # Inclusive - # "atomic_gaussian_width": 0.2, - # "radial_basis": {"Gto": {}}, - # "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, - # "center_atom_weight": 1.0, - # } - # - # calculator = rascaline.SphericalExpansion(**rascal_hypers) - # nu1 = calculator.compute(frames) - # nu1 = nu1.keys_to_properties("species_neighbor") - # - # lmax = 1 - # lambdas = np.arange(lmax) - # - # clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) - # cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) - # soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) # -> needs - # soap_cg = soap_cg.keys_to_properties(["spherical_harmonics_l"]).keys_to_samples(["species_center"]) - # n_samples = len(soap_cg[0].samples) - # kernel_cg = np.zeros((n_samples, n_samples)) - # for key, block in soap_cg.items(): - # kernel_cg += block.values.squeeze() @ block.values.squeeze().T - # - # calculator = rascaline.SoapPowerSpectrum(**rascal_hypers) - # nu2 = calculator.compute(frames) - # soap_rascaline = nu2.keys_to_properties(["species_neighbor_1", "species_neighbor_2"]).keys_to_samples(["species_center"]) - # kernel_rascaline = np.zeros((n_samples, n_samples)) - # for key, block in soap_rascaline.items(): - # kernel_rascaline += block.values.squeeze() @ block.values.squeeze().T - # - # # worries me a bit that the rtol is shit, might be missing multiplicity? - # assert np.allclose(kernel_cg, kernel_rascaline, atol=1e-9, rtol=1e-1) - # - # def test_soap_zeros(): - # """ - # Tests if the l1!=l2 values are zero when computing the 3-body invariant (SOAP) - # """ - # frames = ase.Atoms('HO', - # positions=[[0., 0., 0.], [1., 1., 1.]], - # pbc=[False, False, False]) - # - # - # rascal_hypers = { - # "cutoff": 3.0, # Angstrom - # "max_radial": 2, # Exclusive - # "max_angular": 3, # Inclusive - # "atomic_gaussian_width": 0.2, - # "radial_basis": {"Gto": {}}, - # "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, - # "center_atom_weight": 1.0, - # } - # - # calculator = rascaline.SphericalExpansion(**rascal_hypers) - # nu1 = calculator.compute(frames) - # nu1 = nu1.keys_to_properties("species_neighbor") - # - # lmax = 1 - # lambdas = np.arange(lmax) - # - # clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) - # cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) - # soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) - # soap_cg.keys_to_properties("spherical_harmonics_l") - # sliced_blocks = [] - # for key, block in soap_cg.items(): - # idx = block.properties.values[:, block.properties.names.index("l1")] != block.properties.values[:, block.properties.names.index("l2")] - # sliced_block = metatensor.slice_block(block, "properties", Labels(names=block.properties.names, values=block.properties.values[idx])) - # sliced_blocks.append(sliced_block) - # - # assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) + +# ============ Test Norm preservation of ============ diff --git a/python/rascaline/tests/utils/data/h2_isolated.xyz b/python/rascaline/tests/utils/data/h2_isolated.xyz new file mode 100644 index 000000000..ec5f59680 --- /dev/null +++ b/python/rascaline/tests/utils/data/h2_isolated.xyz @@ -0,0 +1,4 @@ +2 +pbc="F F F" +H 1.97361700 1.73067300 2.47063400 +H 1.97361700 3.26932700 2.47063400 diff --git a/python/rascaline/tests/utils/data/h2o_isolated.xyz b/python/rascaline/tests/utils/data/h2o_isolated.xyz new file mode 100644 index 000000000..fc876d2ba --- /dev/null +++ b/python/rascaline/tests/utils/data/h2o_isolated.xyz @@ -0,0 +1,5 @@ +3 +pbc="F F F" +O 2.56633400 2.50000000 2.50370100 +H 1.97361700 1.73067300 2.47063400 +H 1.97361700 3.26932700 2.47063400 diff --git a/python/rascaline/tests/utils/data/h2o_periodic.xyz b/python/rascaline/tests/utils/data/h2o_periodic.xyz new file mode 100644 index 000000000..3374566e6 --- /dev/null +++ b/python/rascaline/tests/utils/data/h2o_periodic.xyz @@ -0,0 +1,5 @@ +3 +Lattice="5.0 0.0 0.0 0.0 5.0 0.0 0.0 0.0 5.0" pbc="T T T" +O 2.56633400 2.50000000 2.50370100 +H 1.97361700 1.73067300 2.47063400 +H 1.97361700 3.26932700 2.47063400 From cce0bdcf8055db8ce2687354f218058a37ef415b Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Wed, 18 Oct 2023 15:32:49 +0200 Subject: [PATCH 66/96] add norm preserving test for combine_single_center_to_body_order --- .../rascaline/tests/utils/clebsch_gordan.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 3d42b2e9f..a17c4b96c 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -256,3 +256,63 @@ def test_lambda_soap_vs_powerspectrum(frames): # ============ Test Norm preservation of ============ + +@pytest.mark.parametrize("max_angular", [1]) +@pytest.mark.parametrize("nu", [2, 3]) +def test_combine_single_center_orthogonality(self, max_angular, nu): + """ + Checks \|ρ^\\nu\| = \|ρ\|^\\nu + For \\nu = 2 the tests passes but for \\nu = 3 it fails because we do not add the + multiplicity when iterating multiple body-orders + """ + rascal_hypers = { + "cutoff": 3.0, # Angstrom + "max_radial": 6, # Exclusive + "max_angular": max_angular, # Inclusive + "atomic_gaussian_width": 0.2, + "radial_basis": {"Gto": {}}, + "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, + "center_atom_weight": 1.0, + } + + calculator = rascaline.SphericalExpansion(**rascal_hypers) + nu1_tensor = calculator.compute([self.h2o_frame]) + + # compute norm of the body order 2 tensor + nu_tensor = combine_single_center_to_body_order( + nu1_tensor, + nu, # target_body_order + angular_cutoff=None, + angular_selection=None, + parity_selection=None, + use_sparse=True, + ) + nu_tensor = nu_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) + nu_tensor = nu_tensor.keys_to_samples(["species_center"]) + n_samples = nu_tensor[0].values.shape[0] + + # compute norm of the body order 1 tensor + nu1_tensor = nu1_tensor.keys_to_properties(["species_neighbor"]) + nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) + + nu_tensor_values = np.hstack( + [ + nu_tensor.block( + Labels("spherical_harmonics_l", np.array([[l]])) + ).values.reshape(n_samples, -1) + for l in nu_tensor.keys["spherical_harmonics_l"] + ] + ) + nu_tensor_norm = np.linalg.norm(nu_tensor_values, axis=1) + nu1_tensor_values = np.hstack( + [ + nu1_tensor.block( + Labels("spherical_harmonics_l", np.array([[l]])) + ).values.reshape(n_samples, -1) + for l in nu1_tensor.keys["spherical_harmonics_l"] + ] + ) + nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) + + # check if the norm is equal + assert np.allclose(nu_tensor_norm, nu1_tensor_norm**nu) From 33726e7c66fa00db9573f2d95b747e6ca32c94de Mon Sep 17 00:00:00 2001 From: Guillaume Fraux Date: Wed, 18 Oct 2023 15:48:13 +0200 Subject: [PATCH 67/96] Add a (-1)^l factor to the power spectrum invariants This make the power spectrum produce the same output as doing a full Clebsch-Gordan product and keeping only the lambda=0 term --- .../utils/power_spectrum/calculator.py | 3 +- .../src/calculators/soap/power_spectrum.rs | 26 +++++++++++------ rascaline/tests/soap-power-spectrum.rs | 28 +++++++++++++++---- scripts/clean-python.sh | 1 + 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/python/rascaline/rascaline/utils/power_spectrum/calculator.py b/python/rascaline/rascaline/utils/power_spectrum/calculator.py index 6815f4b16..99c6f692a 100644 --- a/python/rascaline/rascaline/utils/power_spectrum/calculator.py +++ b/python/rascaline/rascaline/utils/power_spectrum/calculator.py @@ -217,7 +217,8 @@ def compute( ell = key[0] species_center = key[1] - factor = 1 / sqrt(2 * ell + 1) + factor = (-1) ** ell / sqrt(2 * ell + 1) + # Find that block indices that have the same spherical_harmonics_l and # species_center selection = Labels( diff --git a/rascaline/src/calculators/soap/power_spectrum.rs b/rascaline/src/calculators/soap/power_spectrum.rs index 229f23834..21419665b 100644 --- a/rascaline/src/calculators/soap/power_spectrum.rs +++ b/rascaline/src/calculators/soap/power_spectrum.rs @@ -358,8 +358,19 @@ impl SoapPowerSpectrum { let property_1 = block_1.properties.position(&[n1]).expect("missing n1"); let property_2 = block_2.properties.position(&[n2]).expect("missing n2"); + let spherical_harmonics_l = l.usize(); + + // For consistency with a full Clebsch-Gordan product we need to add + // a `-1^l / sqrt(2 l + 1)` factor to the power spectrum invariants + let normalization = if spherical_harmonics_l % 2 == 0 { + f64::sqrt((2 * spherical_harmonics_l + 1) as f64) + } else { + -f64::sqrt((2 * spherical_harmonics_l + 1) as f64) + }; + SpxPropertiesToCombine { - spherical_harmonics_l: l.usize(), + spherical_harmonics_l, + normalization, property_1, property_2, spx_1: block_1.clone(), @@ -375,6 +386,8 @@ impl SoapPowerSpectrum { struct SpxPropertiesToCombine<'a> { /// value of l spherical_harmonics_l: usize, + /// normalization factor (-1)^l * sqrt(2 l + 1) + normalization: f64, /// position of n1 in the first spherical expansion properties property_1: usize, /// position of n2 in the second spherical expansion properties @@ -599,7 +612,7 @@ impl CalculatorBase for SoapPowerSpectrum { } unsafe { - *values.uget_mut(property_i) = sum / f64::sqrt((2 * spx.spherical_harmonics_l + 1) as f64); + *values.uget_mut(property_i) = sum / spx.normalization; } } }); @@ -655,10 +668,9 @@ impl CalculatorBase for SoapPowerSpectrum { } } - let normalization = f64::sqrt((2 * spx.spherical_harmonics_l + 1) as f64); for d in 0..3 { unsafe { - *values.uget_mut([d, property_i]) = sum[d] / normalization; + *values.uget_mut([d, property_i]) = sum[d] / spx.normalization; } } } @@ -694,7 +706,6 @@ impl CalculatorBase for SoapPowerSpectrum { let value_2 = spx_2.values.uget([spx_sample_2, m, spx.property_2]); for d1 in 0..3 { for d2 in 0..3 { - // TODO: ensure that gradient samples are 0..nsamples sum[d1][d2] += value_2 * spx_1_gradient.uget([spx_sample_1, d1, d2, m, spx.property_1]); } } @@ -707,7 +718,6 @@ impl CalculatorBase for SoapPowerSpectrum { let value_1 = spx_1.values.uget([spx_sample_1, m, spx.property_1]); for d1 in 0..3 { for d2 in 0..3 { - // TODO: ensure that gradient samples are 0..nsamples sum[d1][d2] += value_1 * spx_2_gradient.uget([spx_sample_2, d1, d2, m, spx.property_2]); } } @@ -723,12 +733,10 @@ impl CalculatorBase for SoapPowerSpectrum { } } - let normalization = f64::sqrt((2 * spx.spherical_harmonics_l + 1) as f64); - for d1 in 0..3 { for d2 in 0..3 { unsafe { - *values.uget_mut([d1, d2, property_i]) = sum[d1][d2] / normalization; + *values.uget_mut([d1, d2, property_i]) = sum[d1][d2] / spx.normalization; } } } diff --git a/rascaline/tests/soap-power-spectrum.rs b/rascaline/tests/soap-power-spectrum.rs index 42bfdb1da..632b02e6b 100644 --- a/rascaline/tests/soap-power-spectrum.rs +++ b/rascaline/tests/soap-power-spectrum.rs @@ -24,8 +24,10 @@ fn values() { let block = &descriptor.block_by_id(0); let array = block.values().to_array(); - let expected = &data::load_expected_values("soap-power-spectrum-values.npy.gz"); - assert_relative_eq!(array, expected, max_relative=1e-5); + let mut expected = data::load_expected_values("soap-power-spectrum-values.npy.gz"); + correct_factor(&mut expected, block.properties()); + + assert_relative_eq!(array, &expected, max_relative=1e-5); } #[test] @@ -51,13 +53,15 @@ fn gradients() { let gradients = block.gradient("positions").unwrap(); let array = sum_gradients(n_atoms, gradients); - let expected = &data::load_expected_values("soap-power-spectrum-positions-gradient.npy.gz"); - assert_relative_eq!(array, expected, max_relative=1e-6); + let mut expected = data::load_expected_values("soap-power-spectrum-positions-gradient.npy.gz"); + correct_factor(&mut expected, block.properties()); + assert_relative_eq!(array, &expected, max_relative=1e-6); let gradient = block.gradient("cell").unwrap(); let array = gradient.values().to_array(); - let expected = &data::load_expected_values("soap-power-spectrum-cell-gradient.npy.gz"); - assert_relative_eq!(array, expected, max_relative=1e-6); + let mut expected = data::load_expected_values("soap-power-spectrum-cell-gradient.npy.gz"); + correct_factor(&mut expected, block.properties()); + assert_relative_eq!(array, &expected, max_relative=1e-6); } fn sum_gradients(n_atoms: usize, gradients: TensorBlockRef<'_>) -> ArrayD { @@ -72,3 +76,15 @@ fn sum_gradients(n_atoms: usize, gradients: TensorBlockRef<'_>) -> ArrayD { sum } + + +// Add back the missing (-1)^l factor to the reference data +fn correct_factor(reference: &mut ndarray::ArrayD, properties: Labels) { + assert!(properties.names() == [ "species_neighbor_1", "species_neighbor_2", "l", "n1", "n2"]); + + let last_axis = reference.shape().len() - 1; + + for (&[_, _, l, _, _], mut column) in properties.iter_fixed_size().zip(reference.axis_iter_mut(Axis(last_axis))) { + column *= (-1.0_f64).powi(l.i32()); + } +} diff --git a/scripts/clean-python.sh b/scripts/clean-python.sh index 650c09747..992aa6c06 100755 --- a/scripts/clean-python.sh +++ b/scripts/clean-python.sh @@ -10,6 +10,7 @@ cd "$ROOT_DIR" rm -rf dist rm -rf build +rm -rf .coverage rm -rf python/rascaline-torch/dist rm -rf python/rascaline-torch/build From b3dd92ef45e34f8d6fcdc3139f4afd2fcb89b915 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 18 Oct 2023 15:51:07 +0200 Subject: [PATCH 68/96] Get tests to run. One fails as expected - need to fix norm preservation for nu_target > 2 --- python/rascaline/tests/utils/clebsch_gordan.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index a17c4b96c..8018d98b3 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -3,6 +3,7 @@ from typing import List import ase.io +import numpy as np import pytest from numpy.testing import assert_allclose @@ -77,7 +78,9 @@ def powspec(frames: List[ase.Atoms]): def powspec_small_features(frames: List[ase.Atoms]): """Returns a rascaline PowerSpectrum constructed from a SphericalExpansion""" - return PowerSpectrum(rascaline.SphericalExpansion(**RASCAL_HYPERS_SMALL)).compute(frames) + return PowerSpectrum(rascaline.SphericalExpansion(**RASCAL_HYPERS_SMALL)).compute( + frames + ) # ============ Test equivariance ============ @@ -255,11 +258,12 @@ def test_lambda_soap_vs_powerspectrum(frames): assert metatensor.allclose(lsoap, ps) -# ============ Test Norm preservation of ============ +# ============ Test norm preservation ============ +@pytest.mark.parametrize("frames", [h2o_isolated()]) @pytest.mark.parametrize("max_angular", [1]) @pytest.mark.parametrize("nu", [2, 3]) -def test_combine_single_center_orthogonality(self, max_angular, nu): +def test_combine_single_center_orthogonality(frames, max_angular, nu): """ Checks \|ρ^\\nu\| = \|ρ\|^\\nu For \\nu = 2 the tests passes but for \\nu = 3 it fails because we do not add the @@ -276,10 +280,10 @@ def test_combine_single_center_orthogonality(self, max_angular, nu): } calculator = rascaline.SphericalExpansion(**rascal_hypers) - nu1_tensor = calculator.compute([self.h2o_frame]) + nu1_tensor = calculator.compute(frames) # compute norm of the body order 2 tensor - nu_tensor = combine_single_center_to_body_order( + nu_tensor = clebsch_gordan.combine_single_center_to_body_order( nu1_tensor, nu, # target_body_order angular_cutoff=None, From 9868e36b1aedb872c556268709dfb87e54177bc2 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 18 Oct 2023 16:01:21 +0200 Subject: [PATCH 69/96] Update notebook --- .../utils/clebsch_gordan/_archive/tmp.ipynb | 404 ++++++++++++------ 1 file changed, 264 insertions(+), 140 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb index d36699b55..1aa9d85de 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -17,10 +17,249 @@ "from metatensor import Labels, TensorBlock, TensorMap\n", "import chemiscope\n", "\n", - "# from rascaline.utils import clebsch_gordan\n", - "import clebsch_gordan\n", - "import old_clebsch_gordan\n", - "import rotations" + "from rascaline.utils import clebsch_gordan, PowerSpectrum\n", + "# import clebsch_gordan\n", + "# import rotations" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from rascaline.utils import clebsch_gordan, PowerSpectrum\n", + "\n", + "rascal_hypers = {\n", + " \"cutoff\": 3.0, # Angstrom\n", + " \"max_radial\": 2, # Exclusive\n", + " \"max_angular\": 3, # Inclusive\n", + " \"atomic_gaussian_width\": 0.2,\n", + " \"radial_basis\": {\"Gto\": {}},\n", + " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", + " \"center_atom_weight\": 1.0,\n", + "}\n", + "\n", + "frames = ase.io.read(\"frame.xyz\", \":\")\n", + "sphex = rascaline.SphericalExpansion(**rascal_hypers)\n", + "ps = PowerSpectrum(sphex).compute(frames)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import ase.io\n", + "import matplotlib.pyplot as plt\n", + "from rascaline.utils import clebsch_gordan, PowerSpectrum\n", + "\n", + "rascal_hypers = {\n", + " \"cutoff\": 3.0, # Angstrom\n", + " \"max_radial\": 2, # Exclusive\n", + " \"max_angular\": 3, # Inclusive\n", + " \"atomic_gaussian_width\": 0.2,\n", + " \"radial_basis\": {\"Gto\": {}},\n", + " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", + " \"center_atom_weight\": 1.0,\n", + "}\n", + "\n", + "frames = ase.io.read(\"frame.xyz\", \":\") # single water monomer\n", + "\n", + "# PowerSpectrum (python implementation)\n", + "sphex = rascaline.SphericalExpansion(**rascal_hypers)\n", + "ps = PowerSpectrum(sphex).compute(frames)\n", + "\n", + "# LSOAP\n", + "nu_1_tensor = sphex.compute(frames)\n", + "lsoap = clebsch_gordan.lambda_soap_vector(\n", + " nu_1_tensor=nu_1_tensor,\n", + " angular_selection=[0],\n", + ")\n", + "# Some metadata manipulation to get it to match PowerSpectrum\n", + "# ...\n", + "keys = lsoap.keys.remove(name=\"spherical_harmonics_l\")\n", + "lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()])\n", + "\n", + "# Remove components axis\n", + "blocks = []\n", + "for block in lsoap.blocks():\n", + " n_samples, n_props = block.values.shape[0], block.values.shape[2]\n", + " new_props = block.properties\n", + " new_props = new_props.remove(name=\"l1\")\n", + " new_props = new_props.rename(old=\"l2\", new=\"l\")\n", + " blocks.append(\n", + " TensorBlock(\n", + " values=block.values.reshape((n_samples, n_props)),\n", + " samples=block.samples,\n", + " components=[],\n", + " properties=new_props,\n", + " )\n", + " )\n", + "lsoap = TensorMap(keys=lsoap.keys, blocks=blocks)\n", + "\n", + "# Parity plot\n", + "fig, ax = plt.subplots()\n", + "for key in lsoap.keys:\n", + " ax.scatter(ps[key].values, lsoap[key].values)\n", + "ax.axline((0, 0), slope=1)\n", + "ax.axline((0, 0), slope=-1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu_1_tensor = sphex.compute(frames)\n", + "lsoap = clebsch_gordan.lambda_soap_vector(\n", + " nu_1_tensor=nu_1_tensor,\n", + " angular_selection=[0],\n", + ")\n", + "keys = lsoap.keys.remove(name=\"spherical_harmonics_l\")\n", + "lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()])\n", + "\n", + "# Remove components axis\n", + "blocks = []\n", + "for block in lsoap.blocks():\n", + " n_samples, n_props = block.values.shape[0], block.values.shape[2]\n", + " new_props = block.properties\n", + " new_props = new_props.remove(name=\"l1\")\n", + " new_props = new_props.rename(old=\"l2\", new=\"l\")\n", + " blocks.append(\n", + " TensorBlock(\n", + " values=block.values.reshape((n_samples, n_props)),\n", + " samples=block.samples,\n", + " components=[],\n", + " properties=new_props,\n", + " )\n", + " )\n", + "\n", + "lsoap = TensorMap(keys=lsoap.keys, blocks=blocks)\n", + "lsoap" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# metatensor.equal_metadata(metatensor.sort(lsoap), metatensor.sort(ps))\n", + "assert metatensor.equal_metadata(lsoap, ps)\n", + "assert metatensor.allclose(metatensor.abs(lsoap), metatensor.abs(ps))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "diff = lsoap.block(0).values - ps.block(0).values\n", + "diff" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mask = abs(diff[0]) > 1e-10\n", + "mask" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lsoap.block(0).properties.names" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lsoap.block(0).properties.values[mask]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mask[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lsoap.block(0).properties" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ps.block(0).values" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "metatensor.subtract(lsoap, ps).block(0).values" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "fig, ax = plt.subplots()\n", + "for key in lsoap.keys:\n", + " ax.scatter(ps[key].values, lsoap[key].values)\n", + " ax.axline((0, 0), slope=1)\n", + " ax.axline((0, 0), slope=-1)" ] }, { @@ -32,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -63,17 +302,9 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "242 ms ± 7.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "nu_3 = clebsch_gordan.combine_single_center_to_body_order_metadata_only(\n", @@ -88,17 +319,9 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "6.25 s ± 1.06 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "nu_3 = clebsch_gordan.combine_single_center_to_body_order(\n", @@ -120,20 +343,9 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Random rotation angles (rad): [5.77507977 5.81759295 0.3318965 ]\n", - "Computing nu = 3 descriptor for unrotated frames...\n", - "Computing nu = 3 descriptor for rotated frames...\n", - "SO(3) EQUIVARIANT!\n" - ] - } - ], + "outputs": [], "source": [ "# Define target angular channels\n", "angular_selection = [0, 2]\n", @@ -184,20 +396,9 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Random rotation angles (rad): [2.72689356 0.10058627 6.14346159]\n", - "Computing lambda-SOAP descriptor for unrotated frames...\n", - "Computing lambda-SOAP descriptor for rotated frames...\n", - "O(3) EQUIVARIANT!\n" - ] - } - ], + "outputs": [], "source": [ "# Define target lambda channels\n", "angular_selection = [0, 1, 2, 3, 4, 5]\n", @@ -244,25 +445,9 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Size: 23315040\n", - "Size: 23315040\n", - "Size: 23315040\n", - "Size: 23315040\n", - "Size: 23315040\n", - "Size: 23315040\n", - "Size: 23315040\n", - "Size: 23315040\n", - "731 ms ± 9.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "lsoap_old = old_clebsch_gordan.lambda_soap_vector(frames, rascal_hypers, lambda_cut=5)\n", @@ -275,25 +460,9 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Size: 13819680\n", - "Size: 13819680\n", - "Size: 13819680\n", - "Size: 13819680\n", - "Size: 13819680\n", - "Size: 13819680\n", - "Size: 13819680\n", - "Size: 13819680\n", - "410 ms ± 12.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", @@ -315,26 +484,9 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TensorMap with 36 blocks\n", - "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", - " 3 1 0 3\n", - " 3 -1 1 3\n", - " ...\n", - " 3 1 5 22\n", - " 3 -1 5 22" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Dense\n", "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", @@ -350,26 +502,9 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TensorMap with 36 blocks\n", - "keys: order_nu inversion_sigma spherical_harmonics_l species_center\n", - " 3 1 0 3\n", - " 3 -1 1 3\n", - " ...\n", - " 3 1 5 22\n", - " 3 -1 5 22" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Sparse\n", "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", @@ -385,20 +520,9 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Check sparse == dense\n", "metatensor.allclose(nu_2_dense, nu_2_sparse)" @@ -673,7 +797,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.10.12" }, "orig_nbformat": 4 }, From fba7f338e49e095905faf6ec71577f98d15aeead Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 18 Oct 2023 17:37:58 +0200 Subject: [PATCH 70/96] Trying to fix norm preservation --- .../utils/clebsch_gordan/clebsch_gordan.py | 32 ++++++++++++++-- .../rascaline/tests/utils/clebsch_gordan.py | 38 +++++++------------ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index feaf33e70..abaa1cd99 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -158,7 +158,9 @@ def combine_single_center_to_body_order( nu_x_keys = combination_metadata[iteration][0] nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) - # TODO: multiplicity and/or normalization here? + # Apply normalization factor to each block based on their permutational + # multiplicity (i.e. how many ways in which they could have been formed) + nu_x_tensor = _normalize_blocks(nu_x_tensor, target_body_order) # Move the [l1, l2, ...] keys to the properties if target_body_order > 1: @@ -170,6 +172,30 @@ def combine_single_center_to_body_order( return nu_x_tensor +def _normalize_blocks(tensor: TensorMap, target_body_order: int) -> TensorMap: + """ + For each block in `tensor`, uses values of the keys "l1", "l2", ..., "lx" to + calculate the permutations P that correspond to the ways in which the block + could have been formed from CG combinations. The normalization factor is + then defined as (1 / sqrt(P)). + + This function assumes that the "lx" keys have *not* yet been moved to the + properties, and is intended to be performed after all CG iterations have + been performed. + """ + l_names = [f"l{i}" for i in range(1, target_body_order + 1)] + for key, block in tensor.items(): + l_vals = [key[l_name] for l_name in l_names] + perm_set = set() + for perm in itertools.permutations(l_vals): + perm_set.add(perm) + + norm_factor = np.sqrt(len(perm_set)) + block.values[:] *= norm_factor + + return tensor + + def combine_single_center_to_body_order_metadata_only( nu_1_tensor: TensorMap, target_body_order: int, @@ -237,13 +263,13 @@ def combine_single_center_to_body_order_metadata_only( nu_x_blocks = [] # TODO: is there a faster way of iterating over keys/blocks here? # TODO: only account for multiplicity at the end - for nu_x_key, key_1, key_2, mult in zip(*combination_metadata[iteration]): + for nu_x_key, key_1, key_2, _ in zip(*combination_metadata[iteration]): nu_x_block = _combine_single_center_blocks( nu_x_tensor[key_1], nu_1_tensor[key_2], nu_x_key["spherical_harmonics_l"], cg_cache=None, - correction_factor=mult, + correction_factor=1.0, return_metadata_only=True, ) nu_x_blocks.append(nu_x_block) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 8018d98b3..cd5cdf041 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -260,32 +260,22 @@ def test_lambda_soap_vs_powerspectrum(frames): # ============ Test norm preservation ============ -@pytest.mark.parametrize("frames", [h2o_isolated()]) -@pytest.mark.parametrize("max_angular", [1]) + +@pytest.mark.parametrize("frames", [h2_isolated()]) @pytest.mark.parametrize("nu", [2, 3]) -def test_combine_single_center_orthogonality(frames, max_angular, nu): +def test_combine_single_center_orthogonality(frames, nu): """ Checks \|ρ^\\nu\| = \|ρ\|^\\nu For \\nu = 2 the tests passes but for \\nu = 3 it fails because we do not add the multiplicity when iterating multiple body-orders """ - rascal_hypers = { - "cutoff": 3.0, # Angstrom - "max_radial": 6, # Exclusive - "max_angular": max_angular, # Inclusive - "atomic_gaussian_width": 0.2, - "radial_basis": {"Gto": {}}, - "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, - "center_atom_weight": 1.0, - } - - calculator = rascaline.SphericalExpansion(**rascal_hypers) - nu1_tensor = calculator.compute(frames) - - # compute norm of the body order 2 tensor + # Build nu=1 SphericalExpansion + nu_1_tensor = sphex_small_features(frames) + + # Build higher body order tensor nu_tensor = clebsch_gordan.combine_single_center_to_body_order( - nu1_tensor, - nu, # target_body_order + nu_1_tensor, + target_body_order=nu, angular_cutoff=None, angular_selection=None, parity_selection=None, @@ -295,9 +285,9 @@ def test_combine_single_center_orthogonality(frames, max_angular, nu): nu_tensor = nu_tensor.keys_to_samples(["species_center"]) n_samples = nu_tensor[0].values.shape[0] - # compute norm of the body order 1 tensor - nu1_tensor = nu1_tensor.keys_to_properties(["species_neighbor"]) - nu1_tensor = nu1_tensor.keys_to_samples(["species_center"]) + # Compute norm of the body order 1 tensor + nu_1_tensor = nu_1_tensor.keys_to_properties(["species_neighbor"]) + nu_1_tensor = nu_1_tensor.keys_to_samples(["species_center"]) nu_tensor_values = np.hstack( [ @@ -310,10 +300,10 @@ def test_combine_single_center_orthogonality(frames, max_angular, nu): nu_tensor_norm = np.linalg.norm(nu_tensor_values, axis=1) nu1_tensor_values = np.hstack( [ - nu1_tensor.block( + nu_1_tensor.block( Labels("spherical_harmonics_l", np.array([[l]])) ).values.reshape(n_samples, -1) - for l in nu1_tensor.keys["spherical_harmonics_l"] + for l in nu_1_tensor.keys["spherical_harmonics_l"] ] ) nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) From 0d4415fa7fa8f8e2025b0fcb279ba831e07bb742 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 18 Oct 2023 18:32:27 +0200 Subject: [PATCH 71/96] Add Alex's tests for the CG cache --- .../rascaline/tests/utils/clebsch_gordan.py | 187 ++++++++++++++++-- 1 file changed, 176 insertions(+), 11 deletions(-) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index cd5cdf041..aa3ffcc41 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -38,6 +38,17 @@ # ============ Pytest fixtures ============ + +@pytest.fixture(scope="module") +def cg_cache_sparse(): + return clebsch_gordan.ClebschGordanReal(lambda_max=5, sparse=True) + + +@pytest.fixture(scope="module") +def cg_cache_dense(): + return clebsch_gordan.ClebschGordanReal(lambda_max=5, sparse=False) + + # ============ Helper functions ============ @@ -90,10 +101,10 @@ def powspec_small_features(frames: List[ase.Atoms]): "frames, nu_target, angular_cutoff, angular_selection, parity_selection", [ ( - h2o_isolated(), - 2, - 5, - [0], # [0, 4, 5], + h2_isolated(), + 3, + None, + [0, 4, 5], [+1], # [+1, -1], ), ( @@ -152,11 +163,11 @@ def test_so3_equivariance( "frames, nu_target, angular_cutoff, angular_selection, parity_selection", [ ( - h2o_isolated(), - 2, - 5, - [0], # [0, 4, 5], - [+1], # [+1, -1], + h2_isolated(), + 3, + None, + [0, 4, 5], + None, ), ( h2o_isolated(), @@ -266,8 +277,6 @@ def test_lambda_soap_vs_powerspectrum(frames): def test_combine_single_center_orthogonality(frames, nu): """ Checks \|ρ^\\nu\| = \|ρ\|^\\nu - For \\nu = 2 the tests passes but for \\nu = 3 it fails because we do not add the - multiplicity when iterating multiple body-orders """ # Build nu=1 SphericalExpansion nu_1_tensor = sphex_small_features(frames) @@ -310,3 +319,159 @@ def test_combine_single_center_orthogonality(frames, nu): # check if the norm is equal assert np.allclose(nu_tensor_norm, nu1_tensor_norm**nu) + + +# ============ Test CG cache ============ + + +@pytest.mark.parametrize("l1, l2", [(1, 2), (2, 3), (0, 5)]) +def test_clebsch_gordan_orthogonality(cg_cache_dense, l1, l2): + """ + Test orthogonality relationships of cached dense CG coefficients. + + See + https://en.wikipedia.org/wiki/Clebsch%E2%80%93Gordan_coefficients#Orthogonality_relations + for details. + """ + lam_min = abs(l1 - l2) + lam_max = l1 + l2 + + # We test lam dimension + # \sum_{-m1 \leq l1 \leq m1, -m2 \leq l2 \leq m2} + # <λμ|l1m1,l2m2> = δ_μμ' + for lam in range(lam_min, lam_max): + cg_mat = cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2 * lam + 1) + dot_product = cg_mat.T @ cg_mat + diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) + diag_mask[np.diag_indices(len(dot_product))] = True + assert np.allclose( + dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask] + ) + assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) + + # We test l1 l2 dimension + # \sum_{|l1-l2| \leq λ \leq l1+l2} \sum_{-μ \leq λ \leq μ} + # <λμ|l1m1,l2m2> = δ_m1m1' δ_m2m2' + l1l2_dim = (2 * l1 + 1) * (2 * l2 + 1) + dot_product = np.zeros((l1l2_dim, l1l2_dim)) + for lam in range(lam_min, lam_max + 1): + cg_mat = cg_cache_dense.coeffs[(l1, l2, lam)].reshape(-1, 2 * lam + 1) + dot_product += cg_mat @ cg_mat.T + diag_mask = np.zeros(dot_product.shape, dtype=np.bool_) + diag_mask[np.diag_indices(len(dot_product))] = True + + assert np.allclose(dot_product[~diag_mask], np.zeros(dot_product.shape)[~diag_mask]) + assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) + + +@pytest.mark.parametrize("frames", [h2_isolated(), h2o_isolated()]) +def test_combine_single_center_to_body_order_dense_sparse_agree(frames): + """ + Tests for agreement between nu=3 tensors built using both sparse and dense + CG coefficient caches. + """ + nu_1_tensor = sphex_small_features(frames) + n_body_sparse = clebsch_gordan.combine_single_center_to_body_order( + nu_1_tensor, + target_body_order=3, + use_sparse=True, + ) + n_body_dense = clebsch_gordan.combine_single_center_to_body_order( + nu_1_tensor, + target_body_order=3, + use_sparse=False, + ) + + assert metatensor.operations.allclose( + n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8 + ) + + +# ============ Test kernel construction ============ + +# TODO: if we want this test, the below code will need updating + +# def test_soap_kernel(): +# """ +# Tests if we get the same result computing SOAP from spherical expansion coefficients using GC utils +# as when using SoapPowerSpectrum. +# +# """ +# frames = ase.Atoms('HO', +# positions=[[0., 0., 0.], [1., 1., 1.]], +# pbc=[False, False, False]) +# +# +# rascal_hypers = { +# "cutoff": 3.0, # Angstrom +# "max_radial": 2, # Exclusive +# "max_angular": 3, # Inclusive +# "atomic_gaussian_width": 0.2, +# "radial_basis": {"Gto": {}}, +# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, +# "center_atom_weight": 1.0, +# } +# +# calculator = rascaline.SphericalExpansion(**rascal_hypers) +# nu1 = calculator.compute(frames) +# nu1 = nu1.keys_to_properties("species_neighbor") +# +# lmax = 1 +# lambdas = np.arange(lmax) +# +# clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) +# cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) +# soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) # -> needs +# soap_cg = soap_cg.keys_to_properties(["spherical_harmonics_l"]).keys_to_samples(["species_center"]) +# n_samples = len(soap_cg[0].samples) +# kernel_cg = np.zeros((n_samples, n_samples)) +# for key, block in soap_cg.items(): +# kernel_cg += block.values.squeeze() @ block.values.squeeze().T +# +# calculator = rascaline.SoapPowerSpectrum(**rascal_hypers) +# nu2 = calculator.compute(frames) +# soap_rascaline = nu2.keys_to_properties(["species_neighbor_1", "species_neighbor_2"]).keys_to_samples(["species_center"]) +# kernel_rascaline = np.zeros((n_samples, n_samples)) +# for key, block in soap_rascaline.items(): +# kernel_rascaline += block.values.squeeze() @ block.values.squeeze().T +# +# # worries me a bit that the rtol is shit, might be missing multiplicity? +# assert np.allclose(kernel_cg, kernel_rascaline, atol=1e-9, rtol=1e-1) +# +# def test_soap_zeros(): +# """ +# Tests if the l1!=l2 values are zero when computing the 3-body invariant (SOAP) +# """ +# frames = ase.Atoms('HO', +# positions=[[0., 0., 0.], [1., 1., 1.]], +# pbc=[False, False, False]) +# +# +# rascal_hypers = { +# "cutoff": 3.0, # Angstrom +# "max_radial": 2, # Exclusive +# "max_angular": 3, # Inclusive +# "atomic_gaussian_width": 0.2, +# "radial_basis": {"Gto": {}}, +# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, +# "center_atom_weight": 1.0, +# } +# +# calculator = rascaline.SphericalExpansion(**rascal_hypers) +# nu1 = calculator.compute(frames) +# nu1 = nu1.keys_to_properties("species_neighbor") +# +# lmax = 1 +# lambdas = np.arange(lmax) +# +# clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) +# cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) +# soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) +# soap_cg.keys_to_properties("spherical_harmonics_l") +# sliced_blocks = [] +# for key, block in soap_cg.items(): +# idx = block.properties.values[:, block.properties.names.index("l1")] != block.properties.values[:, block.properties.names.index("l2")] +# sliced_block = metatensor.slice_block(block, "properties", Labels(names=block.properties.names, values=block.properties.values[idx])) +# sliced_blocks.append(sliced_block) +# +# assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) From fa1514a12d2001438b936d00274a8ce8c36f276e Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Tue, 14 Nov 2023 18:59:33 +0100 Subject: [PATCH 72/96] Fix norm preservation tests --- .../utils/clebsch_gordan/clebsch_gordan.py | 12 +- .../rascaline/tests/utils/clebsch_gordan.py | 119 ++++++++++++------ 2 files changed, 95 insertions(+), 36 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index abaa1cd99..b2fb07d1c 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -75,6 +75,7 @@ def combine_single_center_to_body_order( angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + sort_l_list: Optional[bool] = False, # TODO: rename arg use_sparse: bool = True, ) -> TensorMap: """ @@ -119,6 +120,7 @@ def combine_single_center_to_body_order( angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, + sort_l_list=sort_l_list, ) # Define the cached CG coefficients, either as sparse dicts or dense arrays. @@ -160,7 +162,7 @@ def combine_single_center_to_body_order( # Apply normalization factor to each block based on their permutational # multiplicity (i.e. how many ways in which they could have been formed) - nu_x_tensor = _normalize_blocks(nu_x_tensor, target_body_order) + # nu_x_tensor = _normalize_blocks(nu_x_tensor, target_body_order) # Move the [l1, l2, ...] keys to the properties if target_body_order > 1: @@ -424,6 +426,7 @@ def _precompute_metadata( angular_cutoff: Optional[int] = None, angular_selection: Optional[List[Union[None, List[int]]]] = None, parity_selection: Optional[List[Union[None, List[int]]]] = None, + sort_l_list: Optional[bool] = False, ) -> List[Tuple[Labels, List[List[int]]]]: """ Computes all the metadata needed to perform `n_iterations` of CG combination @@ -442,6 +445,7 @@ def _precompute_metadata( angular_cutoff=angular_cutoff, angular_selection=angular_selection[iteration], parity_selection=parity_selection[iteration], + sort_l_list=sort_l_list, ) new_keys = i_comb_metadata[0] @@ -488,6 +492,7 @@ def _precompute_metadata_one_iteration( angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, List[int]]] = None, parity_selection: Optional[Union[None, List[int]]] = None, + sort_l_list: Optional[bool] = False, ) -> Tuple[Labels, List[List[int]]]: """ Given the keys of 2 TensorMaps, returns the keys that would be present after @@ -638,6 +643,11 @@ def _precompute_metadata_one_iteration( # Define new keys as the full product of keys_1 and keys_2 nu_x_keys = Labels(names=new_names, values=np.array(new_key_values)) + # If we want to sort the l list to save computations (i.e. not calculate ) + if not sort_l_list: + # TODO: remove multiplicity correction + return nu_x_keys, keys_1_entries, keys_2_entries, [1] * len(nu_x_keys) + # Now account for multiplicty key_idxs_to_keep = [] mult_dict = {} diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index aa3ffcc41..a5573d55f 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -94,6 +94,21 @@ def powspec_small_features(frames: List[ase.Atoms]): ) +def get_norm(tensor: TensorMap): + """ + Calculates the norm used in CG iteration tests. Assumes standardized + metadata and that the TensorMap is sliced to a single sample. + """ + norm = 0.0 + for key, block in tensor.items(): # Sum over lambda and sigma + l = key["spherical_harmonics_l"] + norm += np.sum( + [np.linalg.norm(block.values[0, m, :]) ** 2 for m in range(-l, l + 1)] + ) + + return norm + + # ============ Test equivariance ============ @@ -272,53 +287,88 @@ def test_lambda_soap_vs_powerspectrum(frames): # ============ Test norm preservation ============ -@pytest.mark.parametrize("frames", [h2_isolated()]) -@pytest.mark.parametrize("nu", [2, 3]) -def test_combine_single_center_orthogonality(frames, nu): +@pytest.mark.parametrize("frames", [h2_isolated(), h2o_periodic()]) +@pytest.mark.parametrize("target_body_order", [2, 3]) +def test_combine_single_center_norm(frames, target_body_order): """ Checks \|ρ^\\nu\| = \|ρ\|^\\nu """ + # Build nu=1 SphericalExpansion - nu_1_tensor = sphex_small_features(frames) + nu1 = sphex_small_features(frames) - # Build higher body order tensor - nu_tensor = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor, - target_body_order=nu, + # Build higher body order tensor without sorting the l lists + nux = clebsch_gordan.combine_single_center_to_body_order( + nu1, + target_body_order=target_body_order, angular_cutoff=None, angular_selection=None, parity_selection=None, + sort_l_list=False, use_sparse=True, ) - nu_tensor = nu_tensor.keys_to_properties(["inversion_sigma", "order_nu"]) - nu_tensor = nu_tensor.keys_to_samples(["species_center"]) - n_samples = nu_tensor[0].values.shape[0] - - # Compute norm of the body order 1 tensor - nu_1_tensor = nu_1_tensor.keys_to_properties(["species_neighbor"]) - nu_1_tensor = nu_1_tensor.keys_to_samples(["species_center"]) - - nu_tensor_values = np.hstack( - [ - nu_tensor.block( - Labels("spherical_harmonics_l", np.array([[l]])) - ).values.reshape(n_samples, -1) - for l in nu_tensor.keys["spherical_harmonics_l"] - ] + # Build higher body order tensor *with* sorting the l lists + nux_sorted_l = clebsch_gordan.combine_single_center_to_body_order( + nu1, + target_body_order=target_body_order, + angular_cutoff=None, + angular_selection=None, + parity_selection=None, + sort_l_list=True, + use_sparse=True, ) - nu_tensor_norm = np.linalg.norm(nu_tensor_values, axis=1) - nu1_tensor_values = np.hstack( - [ - nu_1_tensor.block( - Labels("spherical_harmonics_l", np.array([[l]])) - ).values.reshape(n_samples, -1) - for l in nu_1_tensor.keys["spherical_harmonics_l"] - ] + + # Standardize the features by passing through the CG combination code but with + # no iterations (i.e. body order 1 -> 1) + nu1 = clebsch_gordan.combine_single_center_to_body_order( + nu1, + target_body_order=1, + angular_cutoff=None, + angular_selection=None, + parity_selection=None, + sort_l_list=False, + use_sparse=True, ) - nu1_tensor_norm = np.linalg.norm(nu1_tensor_values, axis=1) - # check if the norm is equal - assert np.allclose(nu_tensor_norm, nu1_tensor_norm**nu) + # Make only lambda and sigma part of keys + nu1 = nu1.keys_to_samples(["species_center"]) + nux = nux.keys_to_samples(["species_center"]) + nux_sorted_l = nux_sorted_l.keys_to_samples(["species_center"]) + + # The norm shoudl be calculated for each sample. First find the unqiue + # samples + uniq_samples = metatensor.unique_metadata( + nux, "samples", names=["structure", "center", "species_center"] + ) + grouped_labels = [ + Labels(names=nux.sample_names, values=uniq_samples.values[i].reshape(1, 3)) + for i in range(len(uniq_samples)) + ] + + # Calculate norms + norm_nu1 = 0.0 + norm_nux = 0.0 + norm_nux_sorted_l = 0.0 + for sample in grouped_labels: + # Slice the TensorMaps + nu1_sliced = metatensor.slice(nu1, "samples", labels=sample) + nux_sliced = metatensor.slice(nux, "samples", labels=sample) + nux_sorted_sliced = metatensor.slice(nux_sorted_l, "samples", labels=sample) + + # Calculate norms + norm_nu1 += get_norm(nu1) ** target_body_order + norm_nux += get_norm(nux) + norm_nux_sorted_l += get_norm(nux_sorted_l) + + # Without sorting the l list we should get the same norm + assert np.allclose(norm_nu1, norm_nux) + + # But with sorting the l list we should get a different norm. Doesn't work + # for targte_body_order > 2 + if target_body_order > 2: + assert not np.allclose(norm_nu1, norm_nux_sorted_l) + else: + assert np.allclose(norm_nu1, norm_nux_sorted_l) # ============ Test CG cache ============ @@ -390,7 +440,6 @@ def test_combine_single_center_to_body_order_dense_sparse_agree(frames): # ============ Test kernel construction ============ # TODO: if we want this test, the below code will need updating - # def test_soap_kernel(): # """ # Tests if we get the same result computing SOAP from spherical expansion coefficients using GC utils From 7e914f13d767af0486b49f95f2db630dfabc749f Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Tue, 14 Nov 2023 19:01:31 +0100 Subject: [PATCH 73/96] Docstring --- python/rascaline/tests/utils/clebsch_gordan.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index a5573d55f..aa2f20685 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -288,10 +288,12 @@ def test_lambda_soap_vs_powerspectrum(frames): @pytest.mark.parametrize("frames", [h2_isolated(), h2o_periodic()]) -@pytest.mark.parametrize("target_body_order", [2, 3]) +@pytest.mark.parametrize("target_body_order", [2, 3, 4]) def test_combine_single_center_norm(frames, target_body_order): """ - Checks \|ρ^\\nu\| = \|ρ\|^\\nu + Checks \|ρ^\\nu\| = \|ρ\|^\\nu in the case where l lists are not sorted. If + l lists are sorted, thus saving computation of redundant block combinations, + the norm check will not hold for target body order greater than 2. """ # Build nu=1 SphericalExpansion @@ -524,3 +526,6 @@ def test_combine_single_center_to_body_order_dense_sparse_agree(frames): # sliced_blocks.append(sliced_block) # # assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) + + +# ============ \ No newline at end of file From c10457d3c1a8543eeee93f3afc4a237643572af3 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 15 Nov 2023 08:19:12 +0100 Subject: [PATCH 74/96] Comment out normalization code --- .../utils/clebsch_gordan/clebsch_gordan.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index b2fb07d1c..84e996c49 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -160,6 +160,7 @@ def combine_single_center_to_body_order( nu_x_keys = combination_metadata[iteration][0] nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) + # TODO: remove this # Apply normalization factor to each block based on their permutational # multiplicity (i.e. how many ways in which they could have been formed) # nu_x_tensor = _normalize_blocks(nu_x_tensor, target_body_order) @@ -174,28 +175,29 @@ def combine_single_center_to_body_order( return nu_x_tensor -def _normalize_blocks(tensor: TensorMap, target_body_order: int) -> TensorMap: - """ - For each block in `tensor`, uses values of the keys "l1", "l2", ..., "lx" to - calculate the permutations P that correspond to the ways in which the block - could have been formed from CG combinations. The normalization factor is - then defined as (1 / sqrt(P)). - - This function assumes that the "lx" keys have *not* yet been moved to the - properties, and is intended to be performed after all CG iterations have - been performed. - """ - l_names = [f"l{i}" for i in range(1, target_body_order + 1)] - for key, block in tensor.items(): - l_vals = [key[l_name] for l_name in l_names] - perm_set = set() - for perm in itertools.permutations(l_vals): - perm_set.add(perm) - - norm_factor = np.sqrt(len(perm_set)) - block.values[:] *= norm_factor - - return tensor +# TODO: remove this +# def _normalize_blocks(tensor: TensorMap, target_body_order: int) -> TensorMap: +# """ +# For each block in `tensor`, uses values of the keys "l1", "l2", ..., "lx" to +# calculate the permutations P that correspond to the ways in which the block +# could have been formed from CG combinations. The normalization factor is +# then defined as (1 / sqrt(P)). + +# This function assumes that the "lx" keys have *not* yet been moved to the +# properties, and is intended to be performed after all CG iterations have +# been performed. +# """ +# l_names = [f"l{i}" for i in range(1, target_body_order + 1)] +# for key, block in tensor.items(): +# l_vals = [key[l_name] for l_name in l_names] +# perm_set = set() +# for perm in itertools.permutations(l_vals): +# perm_set.add(perm) + +# norm_factor = np.sqrt(len(perm_set)) +# block.values[:] *= norm_factor + +# return tensor def combine_single_center_to_body_order_metadata_only( From 89a4d94b30c661e3973e74f71e9ff576912d9b97 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 15 Nov 2023 09:18:27 +0100 Subject: [PATCH 75/96] Move combinations to dispatch --- .../utils/clebsch_gordan/_dispatch.py | 135 ++++++++++++ .../utils/clebsch_gordan/_dispatch_.py | 3 - .../utils/clebsch_gordan/clebsch_gordan.py | 206 +++--------------- .../rascaline/tests/utils/clebsch_gordan.py | 87 +++----- 4 files changed, 196 insertions(+), 235 deletions(-) create mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_dispatch_.py diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py new file mode 100644 index 000000000..68ce3fd3c --- /dev/null +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -0,0 +1,135 @@ +""" +Module containing dispatch functions for numpy/torch CG combination operations. +""" +from typing import Union +import numpy as np + +try: + import torch + from torch import Tensor as TorchTensor +except ImportError: + + class TorchTensor: + pass + + +UNKNOWN_ARRAY_TYPE = ( + "unknown array type, only numpy arrays and torch tensors are supported" +) + + +def _sparse_combine( + arr_1: Union[np.ndarray, TorchTensor], + arr_2: Union[np.ndarray, TorchTensor], + lam: int, + cg_cache, + return_empty_array: bool = False, +) -> Union[np.ndarray, TorchTensor]: + """ + Performs a Clebsch-Gordan combination step on 2 arrays using sparse + operations. The angular channel of each block is inferred from the size of + its component axis, and the blocks are combined to the desired output + angular channel `lam` using the appropriate Clebsch-Gordan coefficients. + + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + + 1, n_p_properties] + :param lam: int value of the resulting coupled channel + :param cg_cache: sparse dictionary with keys (m1, m2, mu) and array values + being sparse blocks of shape + + :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] + """ + if isinstance(arr_1, np.ndarray): + # Samples dimensions must be the same + assert arr_1.shape[0] == arr_2.shape[0] + + # Define other useful dimensions + n_i = arr_1.shape[0] # number of samples + n_p = arr_1.shape[2] # number of properties in arr_1 + n_q = arr_2.shape[2] # number of properties in arr_2 + + # Infer l1 and l2 from the len of the length of axis 1 of each tensor + l1 = (arr_1.shape[1] - 1) // 2 + l2 = (arr_2.shape[1] - 1) // 2 + + # Initialise output array + arr_out = np.zeros((n_i, 2 * lam + 1, n_p * n_q)) + + if return_empty_array: + return arr_out + + # Get the corresponding Clebsch-Gordan coefficients + cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] + + # Fill in each mu component of the output array in turn + for m1, m2, mu in cg_coeffs.keys(): + # Broadcast arrays, multiply together and with CG coeff + arr_out[:, mu, :] += ( + arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeffs[(m1, m2, mu)] + ).reshape(n_i, n_p * n_q) + + return arr_out + + elif isinstance(arr_1, TorchTensor): + pass + + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) + + +def _dense_combine( + arr_1: Union[np.ndarray, TorchTensor], + arr_2: Union[np.ndarray, TorchTensor], + lam: int, + cg_cache, +) -> Union[np.ndarray, TorchTensor]: + """ + Performs a Clebsch-Gordan combination step on 2 arrays using a dense + operation. The angular channel of each block is inferred from the size of + its component axis, and the blocks are combined to the desired output + angular channel `lam` using the appropriate Clebsch-Gordan coefficients. + + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + + 1, n_p_properties] + :param lam: int value of the resulting coupled channel + :param cg_cache: dense array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + + 1)] + + :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] + """ + if isinstance(arr_1, np.ndarray): + # Infer l1 and l2 from the len of the length of axis 1 of each tensor + l1 = (arr_1.shape[1] - 1) // 2 + l2 = (arr_2.shape[1] - 1) // 2 + cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] + + # (samples None None l1_mu q) * (samples l2_mu p None None) -> (samples l2_mu p l1_mu q) + # we broadcast it in this way so we only need to do one swapaxes in the next step + arr_out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] + + # (samples l2_mu p l1_mu q) -> (samples q p l1_mu l2_mu) + arr_out = arr_out.swapaxes(1, 4) + + # samples (q p l1_mu l2_mu) -> (samples (q p) (l1_mu l2_mu)) + arr_out = arr_out.reshape( + -1, arr_1.shape[2] * arr_2.shape[2], arr_1.shape[1] * arr_2.shape[1] + ) + + # (l1_mu l2_mu lam_mu) -> ((l1_mu l2_mu) lam_mu) + cg_coeffs = cg_coeffs.reshape(-1, 2 * lam + 1) + + # (samples (q p) (l1_mu l2_mu)) @ ((l1_mu l2_mu) lam_mu) -> samples (q p) lam_mu + arr_out = arr_out @ cg_coeffs + + # (samples (q p) lam_mu) -> (samples lam_mu (q p)) + return arr_out.swapaxes(1, 2) + + elif isinstance(arr_1, TorchTensor): + pass + + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch_.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch_.py deleted file mode 100644 index 7c1da832a..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch_.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Module containing dispatch functions for numpy/torch operations. -""" diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 84e996c49..918b4a3a8 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -9,6 +9,7 @@ from metatensor import Labels, TensorBlock, TensorMap from .cg_coefficients import ClebschGordanReal +from . import _dispatch # ====================================================================== @@ -147,24 +148,18 @@ def combine_single_center_to_body_order( # Combine blocks nu_x_blocks = [] # TODO: is there a faster way of iterating over keys/blocks here? - for nu_x_key, key_1, key_2, multi in zip(*combination_metadata[iteration]): - # Combine the pair of block values, accounting for multiplicity + for nu_x_key, key_1, key_2 in zip(*combination_metadata[iteration]): + # Combine the pair of block values nu_x_block = _combine_single_center_blocks( nu_x_tensor[key_1], nu_1_tensor[key_2], nu_x_key["spherical_harmonics_l"], cg_cache, - correction_factor=np.sqrt(multi), ) nu_x_blocks.append(nu_x_block) nu_x_keys = combination_metadata[iteration][0] nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) - # TODO: remove this - # Apply normalization factor to each block based on their permutational - # multiplicity (i.e. how many ways in which they could have been formed) - # nu_x_tensor = _normalize_blocks(nu_x_tensor, target_body_order) - # Move the [l1, l2, ...] keys to the properties if target_body_order > 1: nu_x_tensor = nu_x_tensor.keys_to_properties( @@ -175,31 +170,6 @@ def combine_single_center_to_body_order( return nu_x_tensor -# TODO: remove this -# def _normalize_blocks(tensor: TensorMap, target_body_order: int) -> TensorMap: -# """ -# For each block in `tensor`, uses values of the keys "l1", "l2", ..., "lx" to -# calculate the permutations P that correspond to the ways in which the block -# could have been formed from CG combinations. The normalization factor is -# then defined as (1 / sqrt(P)). - -# This function assumes that the "lx" keys have *not* yet been moved to the -# properties, and is intended to be performed after all CG iterations have -# been performed. -# """ -# l_names = [f"l{i}" for i in range(1, target_body_order + 1)] -# for key, block in tensor.items(): -# l_vals = [key[l_name] for l_name in l_names] -# perm_set = set() -# for perm in itertools.permutations(l_vals): -# perm_set.add(perm) - -# norm_factor = np.sqrt(len(perm_set)) -# block.values[:] *= norm_factor - -# return tensor - - def combine_single_center_to_body_order_metadata_only( nu_1_tensor: TensorMap, target_body_order: int, @@ -266,14 +236,12 @@ def combine_single_center_to_body_order_metadata_only( # Combine blocks nu_x_blocks = [] # TODO: is there a faster way of iterating over keys/blocks here? - # TODO: only account for multiplicity at the end for nu_x_key, key_1, key_2, _ in zip(*combination_metadata[iteration]): nu_x_block = _combine_single_center_blocks( nu_x_tensor[key_1], nu_1_tensor[key_2], nu_x_key["spherical_harmonics_l"], cg_cache=None, - correction_factor=1.0, return_metadata_only=True, ) nu_x_blocks.append(nu_x_block) @@ -647,12 +615,10 @@ def _precompute_metadata_one_iteration( # If we want to sort the l list to save computations (i.e. not calculate ) if not sort_l_list: - # TODO: remove multiplicity correction - return nu_x_keys, keys_1_entries, keys_2_entries, [1] * len(nu_x_keys) + return nu_x_keys, keys_1_entries, keys_2_entries # Now account for multiplicty key_idxs_to_keep = [] - mult_dict = {} for key_idx, key in enumerate(nu_x_keys): # Get the important key values. This is all of the keys, excpet the k # list @@ -670,12 +636,6 @@ def _precompute_metadata_one_iteration( if np.all(key_slice_tuple == key_slice_sorted_tuple): key_idxs_to_keep.append(key_idx) - # Now count the multiplicity of each sorted l_list - if mult_dict.get(key_slice_sorted_tuple) is None: - mult_dict[key_slice_sorted_tuple] = 1 - else: - mult_dict[key_slice_sorted_tuple] += 1 - # Build a reduced Labels object for the combined keys, with redundancies removed combined_keys_red = Labels( names=new_names, @@ -687,13 +647,7 @@ def _precompute_metadata_one_iteration( keys_1_entries_red = [keys_1_entries[idx] for idx in key_idxs_to_keep] keys_2_entries_red = [keys_2_entries[idx] for idx in key_idxs_to_keep] - # Define the multiplicity of each key - mult_list = [ - mult_dict[tuple(nu_x_keys[idx].values[: 4 + (nu + 1)].tolist())] - for idx in key_idxs_to_keep - ] - - return combined_keys_red, keys_1_entries_red, keys_2_entries_red, mult_list + return combined_keys_red, keys_1_entries_red, keys_2_entries_red # ================================================================== @@ -706,7 +660,6 @@ def _combine_single_center_blocks( block_2: TensorBlock, lam: int, cg_cache, - correction_factor: float = 1.0, return_metadata_only: bool = False, ) -> TensorBlock: """ @@ -735,7 +688,7 @@ def _combine_single_center_blocks( # Create a TensorBlock combined_block = TensorBlock( - values=combined_values * correction_factor, + values=combined_values, samples=block_1.samples, components=[ Labels( @@ -766,10 +719,12 @@ def _combine_arrays( return_empty_array: bool = False, ) -> np.ndarray: """ - Couples arrays corresponding to the irreducible spherical components of 2 - angular channels l1 and l2 using the appropriate Clebsch-Gordan - coefficients. As l1 and l2 can be combined to form multiple lambda channels, - this function returns the coupling to a single specified channel `lambda`. + Couples arrays `arr_1` and `arr_2` corresponding to the irreducible + spherical components of 2 angular channels l1 and l2 using the appropriate + Clebsch-Gordan coefficients. As l1 and l2 can be combined to form multiple + lambda channels, this function returns the coupling to a single specified + channel `lambda`. The angular channels l1 and l2 are inferred from the size + of the components axis (axis 1) of the input arrays. `arr_1` has shape (n_i, 2 * l1 + 1, n_p) and `arr_2` has shape (n_i, 2 * l2 + 1, n_q). n_i is the number of samples, n_p and n_q are the number of @@ -780,144 +735,37 @@ def _combine_arrays( the input parameter `lam`. The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these - must be produced by the ClebschGordanReal class in this module. + must be produced by the ClebschGordanReal class in this module. These + coefficients can be stored in either sparse dictionaries or dense arrays. - Either performs the operation in a dense or sparse manner, depending on the - value of `sparse`. + The combination operation is dispatched such that numpy arrays or torch + tensors are automatically handled. `return_empty_array` can be used to return an empty array of the correct shape, without performing the CG combination step. This can be useful for probing the outputs of CG iterations in terms of metadata without the computational cost of performing the CG combinations - i.e. using the function :py:func:`combine_single_center_to_body_order_metadata_only`. - """ - if return_empty_array: - return _combine_arrays_sparse(arr_1, arr_2, lam, cg_cache, True) - - # Check the first dimension of the arrays are the same (i.e. same samples) - if cg_cache.sparse: - return _combine_arrays_sparse(arr_1, arr_2, lam, cg_cache, False) - return _combine_arrays_dense(arr_1, arr_2, lam, cg_cache) - - -def _combine_arrays_sparse( - arr_1: np.ndarray, - arr_2: np.ndarray, - lam: int, - cg_cache, - return_empty_array: bool = False, -) -> np.ndarray: - """ - TODO: finish docstring. - - Performs a Clebsch-Gordan combination step on 2 arrays using sparse - operations. :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + 1, n_q_properties] :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + 1, n_p_properties] :param lam: int value of the resulting coupled channel - :param cg_cache: sparse dictionary with keys (m1, m2, mu) and array values - being sparse blocks of shape + :param cg_cache: either a sparse dictionary with keys (m1, m2, mu) and array + values being sparse blocks of shape , or a dense array + of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)]. :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] """ - # Samples dimensions must be the same - assert arr_1.shape[0] == arr_2.shape[0] - - # Define other useful dimensions - n_i = arr_1.shape[0] # number of samples - n_p = arr_1.shape[2] # number of properties in arr_1 - n_q = arr_2.shape[2] # number of properties in arr_2 - - # Infer l1 and l2 from the len of the length of axis 1 of each tensor - l1 = (arr_1.shape[1] - 1) // 2 - l2 = (arr_2.shape[1] - 1) // 2 - - # Initialise output array - arr_out = np.zeros((n_i, 2 * lam + 1, n_p * n_q)) - + # If just precomputing metadata, return an empty array if return_empty_array: - return arr_out + return _dispatch._sparse_combine(arr_1, arr_2, lam, cg_cache, True) - # Get the corresponding Clebsch-Gordan coefficients - cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] - - # Fill in each mu component of the output array in turn - for m1, m2, mu in cg_coeffs.keys(): - # Broadcast arrays, multiply together and with CG coeff - arr_out[:, mu, :] += ( - arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeffs[(m1, m2, mu)] - ).reshape(n_i, n_p * n_q) - - return arr_out - - -def _combine_arrays_dense( - arr_1: np.ndarray, - arr_2: np.ndarray, - lam: int, - cg_cache, -) -> np.ndarray: - """ - Performs a Clebsch-Gordan combination step on 2 arrays using a dense - operation. - - :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + - 1, n_q_properties] - :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + - 1, n_p_properties] - :param lam: int value of the resulting coupled channel - :param cg_cache: dense array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + - 1)] - - :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] - - TODO: do we need this example here? Could it be moved to a test? - - >>> N_SAMPLES = 30 - >>> N_Q_PROPERTIES = 10 - >>> N_P_PROPERTIES = 8 - >>> L1 = 2 - >>> L2 = 3 - >>> LAM = 2 - >>> arr_1 = np.random.rand(N_SAMPLES, 2 * L1 + 1, N_Q_PROPERTIES) - >>> arr_2 = np.random.rand(N_SAMPLES, 2 * L2 + 1, N_P_PROPERTIES) - >>> cg_cache = {(L1, L2, LAM): np.random.rand(2 * L1 + 1, 2 * L2 + 1, 2 * LAM + 1)} - >>> out1 = _clebsch_gordan_dense(arr_1, arr_2, LAM, cg_cache) - >>> # (samples l1_m q_features) (samples l2_m p_features), - >>> # (l1_m l2_m lambda_mu) - >>> # --> (samples, lambda_mu q_features p_features) - >>> # in einsum l1_m is l, l2_m is k, lambda_mu is L - >>> out2 = np.einsum("slq, skp, lkL -> sLqp", arr_1, arr_2, cg_cache[(L1, L2, LAM)]) - >>> # --> (samples lambda_mu (q_features p_features)) - >>> out2 = out2.reshape(arr_1.shape[0], 2 * LAM + 1, -1) - >>> print(np.allclose(out1, out2)) - True - """ - # Infer l1 and l2 from the len of the length of axis 1 of each tensor - l1 = (arr_1.shape[1] - 1) // 2 - l2 = (arr_2.shape[1] - 1) // 2 - cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] - - # (samples None None l1_mu q) * (samples l2_mu p None None) -> (samples l2_mu p l1_mu q) - # we broadcast it in this way so we only need to do one swapaxes in the next step - arr_out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] - - # (samples l2_mu p l1_mu q) -> (samples q p l1_mu l2_mu) - arr_out = arr_out.swapaxes(1, 4) - - # samples (q p l1_mu l2_mu) -> (samples (q p) (l1_mu l2_mu)) - arr_out = arr_out.reshape( - -1, arr_1.shape[2] * arr_2.shape[2], arr_1.shape[1] * arr_2.shape[1] - ) - - # (l1_mu l2_mu lam_mu) -> ((l1_mu l2_mu) lam_mu) - cg_coeffs = cg_coeffs.reshape(-1, 2 * lam + 1) - - # (samples (q p) (l1_mu l2_mu)) @ ((l1_mu l2_mu) lam_mu) -> samples (q p) lam_mu - arr_out = arr_out @ cg_coeffs + # Otherwise, perform the CG combination + # Spare CG cache + if cg_cache.sparse: + return _dispatch._sparse_combine(arr_1, arr_2, lam, cg_cache, False) - # (samples (q p) lam_mu) -> (samples lam_mu (q p)) - return arr_out.swapaxes(1, 2) + # Dense CG cache + return _dispatch._dense_combine(arr_1, arr_2, lam, cg_cache) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index aa2f20685..2add52604 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -98,6 +98,9 @@ def get_norm(tensor: TensorMap): """ Calculates the norm used in CG iteration tests. Assumes standardized metadata and that the TensorMap is sliced to a single sample. + + For a given atomic sample, the norm is calculated for each feature vector, + as a sum over lambda, sigma, and m. """ norm = 0.0 for key, block in tensor.items(): # Sum over lambda and sigma @@ -115,27 +118,9 @@ def get_norm(tensor: TensorMap): @pytest.mark.parametrize( "frames, nu_target, angular_cutoff, angular_selection, parity_selection", [ - ( - h2_isolated(), - 3, - None, - [0, 4, 5], - [+1], # [+1, -1], - ), - ( - h2o_isolated(), - 2, - 5, - None, # [0, 4, 5], - None, # [+1, -1], - ), - ( - h2o_periodic(), - 2, - 5, - None, # [0, 1, 2, 3, 4, 5], - None, # [-1], - ), + (h2_isolated(), 3, None, [0, 4, 5], [+1]), + (h2o_isolated(), 2, 5, None, None), + (h2o_periodic(), 2, 5, None, None), ], ) def test_so3_equivariance( @@ -169,35 +154,15 @@ def test_so3_equivariance( ) nu_3_transf = wig.transform_tensormap_so3(nu_3) - - # assert metatensor.equal_metadata(nu_3_transf, nu_3_rot) assert metatensor.allclose(nu_3_transf, nu_3_so3) @pytest.mark.parametrize( "frames, nu_target, angular_cutoff, angular_selection, parity_selection", [ - ( - h2_isolated(), - 3, - None, - [0, 4, 5], - None, - ), - ( - h2o_isolated(), - 2, - 5, - None, # [0, 4, 5], - None, # [+1, -1], - ), - ( - h2o_periodic(), - 2, - 5, - None, # [0, 1, 2, 3, 4, 5], - None, # [-1], - ), + (h2_isolated(), 3, None, [0, 4, 5], None), + (h2o_isolated(), 2, 5, None, None), + (h2o_periodic(), 2, 5, None, None), ], ) def test_o3_equivariance( @@ -231,8 +196,6 @@ def test_o3_equivariance( ) nu_3_transf = wig.transform_tensormap_o3(nu_3) - - # assert metatensor.equal_metadata(nu_3_transf, nu_3_rot) assert metatensor.allclose(nu_3_transf, nu_3_o3) @@ -365,12 +328,8 @@ def test_combine_single_center_norm(frames, target_body_order): # Without sorting the l list we should get the same norm assert np.allclose(norm_nu1, norm_nux) - # But with sorting the l list we should get a different norm. Doesn't work - # for targte_body_order > 2 - if target_body_order > 2: - assert not np.allclose(norm_nu1, norm_nux_sorted_l) - else: - assert np.allclose(norm_nu1, norm_nux_sorted_l) + # But with sorting the l list we should get a different norm + assert not np.allclose(norm_nu1, norm_nux_sorted_l) # ============ Test CG cache ============ @@ -528,4 +487,26 @@ def test_combine_single_center_to_body_order_dense_sparse_agree(frames): # assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) -# ============ \ No newline at end of file +# ============ + + +# ======= Docstring example from _combine_arrays_sparse. Not sure if we need it. +# >>> N_SAMPLES = 30 +# >>> N_Q_PROPERTIES = 10 +# >>> N_P_PROPERTIES = 8 +# >>> L1 = 2 +# >>> L2 = 3 +# >>> LAM = 2 +# >>> arr_1 = np.random.rand(N_SAMPLES, 2 * L1 + 1, N_Q_PROPERTIES) +# >>> arr_2 = np.random.rand(N_SAMPLES, 2 * L2 + 1, N_P_PROPERTIES) +# >>> cg_cache = {(L1, L2, LAM): np.random.rand(2 * L1 + 1, 2 * L2 + 1, 2 * LAM + 1)} +# >>> out1 = _clebsch_gordan_dense(arr_1, arr_2, LAM, cg_cache) +# >>> # (samples l1_m q_features) (samples l2_m p_features), +# >>> # (l1_m l2_m lambda_mu) +# >>> # --> (samples, lambda_mu q_features p_features) +# >>> # in einsum l1_m is l, l2_m is k, lambda_mu is L +# >>> out2 = np.einsum("slq, skp, lkL -> sLqp", arr_1, arr_2, cg_cache[(L1, L2, LAM)]) +# >>> # --> (samples lambda_mu (q_features p_features)) +# >>> out2 = out2.reshape(arr_1.shape[0], 2 * LAM + 1, -1) +# >>> print(np.allclose(out1, out2)) +# True \ No newline at end of file From 7749dabd6aa1be442b6e03df92277f1c1ed0699f Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 15 Nov 2023 09:51:21 +0100 Subject: [PATCH 76/96] Move combine wrapper to dispatch, test for metadata-only computation --- .../utils/clebsch_gordan/_dispatch.py | 60 +++++++++++++++ .../utils/clebsch_gordan/clebsch_gordan.py | 77 +++---------------- .../rascaline/tests/utils/clebsch_gordan.py | 72 ++++++++++++++++- 3 files changed, 143 insertions(+), 66 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index 68ce3fd3c..adedfe5a5 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -18,6 +18,66 @@ class TorchTensor: ) +def _combine_arrays( + arr_1: Union[np.ndarray, TorchTensor], + arr_2: Union[np.ndarray, TorchTensor], + lam: int, + cg_cache, + return_empty_array: bool = False, +) -> Union[np.ndarray, TorchTensor]: + """ + Couples arrays `arr_1` and `arr_2` corresponding to the irreducible + spherical components of 2 angular channels l1 and l2 using the appropriate + Clebsch-Gordan coefficients. As l1 and l2 can be combined to form multiple + lambda channels, this function returns the coupling to a single specified + channel `lambda`. The angular channels l1 and l2 are inferred from the size + of the components axis (axis 1) of the input arrays. + + `arr_1` has shape (n_i, 2 * l1 + 1, n_p) and `arr_2` has shape (n_i, 2 * l2 + + 1, n_q). n_i is the number of samples, n_p and n_q are the number of + properties in each array. The number of samples in each array must be the + same. + + The ouput array has shape (n_i, 2 * lambda + 1, n_p * n_q), where lambda is + the input parameter `lam`. + + The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these + must be produced by the ClebschGordanReal class in this module. These + coefficients can be stored in either sparse dictionaries or dense arrays. + + The combination operation is dispatched such that numpy arrays or torch + tensors are automatically handled. + + `return_empty_array` can be used to return an empty array of the correct + shape, without performing the CG combination step. This can be useful for + probing the outputs of CG iterations in terms of metadata without the + computational cost of performing the CG combinations - i.e. using the + function :py:func:`combine_single_center_to_body_order_metadata_only`. + + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + + 1, n_p_properties] + :param lam: int value of the resulting coupled channel + :param cg_cache: either a sparse dictionary with keys (m1, m2, mu) and array + values being sparse blocks of shape , or a dense array + of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)]. + + :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] + """ + # If just precomputing metadata, return an empty array + if return_empty_array: + return _sparse_combine(arr_1, arr_2, lam, cg_cache, True) + + # Otherwise, perform the CG combination + # Spare CG cache + if cg_cache.sparse: + return _sparse_combine(arr_1, arr_2, lam, cg_cache, False) + + # Dense CG cache + return _dense_combine(arr_1, arr_2, lam, cg_cache) + + def _sparse_combine( arr_1: Union[np.ndarray, TorchTensor], arr_2: Union[np.ndarray, TorchTensor], diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 918b4a3a8..431e83339 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -76,7 +76,7 @@ def combine_single_center_to_body_order( angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - sort_l_list: Optional[bool] = False, # TODO: rename arg + sort_l_list: Optional[bool] = False, use_sparse: bool = True, ) -> TensorMap: """ @@ -141,7 +141,7 @@ def combine_single_center_to_body_order( cg_cache = ClebschGordanReal(angular_max, use_sparse) # Create a copy of the nu = 1 tensor to combine with itself - nu_x_tensor = nu_1_tensor.copy() + nu_x_tensor = nu_1_tensor # Iteratively combine block values for iteration in range(target_body_order - 1): @@ -176,6 +176,7 @@ def combine_single_center_to_body_order_metadata_only( angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + sort_l_list: Optional[bool] = False, ) -> List[TensorMap]: """ Performs a pseudo-CG combination of a nu = 1 (i.e. 2-body) single-center @@ -204,6 +205,11 @@ def combine_single_center_to_body_order_metadata_only( # Standardize the metadata of the input tensor nu_1_tensor = _standardize_tensor_metadata(nu_1_tensor) + # If the desired body order is 1, return the input tensor with standardized + # metadata. + if target_body_order == 1: + return nu_1_tensor + # Pre-compute the metadata needed to perform each CG iteration # Current design choice: only combine a nu = 1 tensor iteratively with # itself, i.e. nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. @@ -225,6 +231,7 @@ def combine_single_center_to_body_order_metadata_only( angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, + sort_l_list=sort_l_list, ) # Create a copy of the nu = 1 tensor to combine with itself @@ -236,7 +243,7 @@ def combine_single_center_to_body_order_metadata_only( # Combine blocks nu_x_blocks = [] # TODO: is there a faster way of iterating over keys/blocks here? - for nu_x_key, key_1, key_2, _ in zip(*combination_metadata[iteration]): + for nu_x_key, key_1, key_2 in zip(*combination_metadata[iteration]): nu_x_block = _combine_single_center_blocks( nu_x_tensor[key_1], nu_1_tensor[key_2], @@ -669,11 +676,11 @@ def _combine_single_center_blocks( # Do the CG combination - single center so no shape pre-processing required if return_metadata_only: - combined_values = _combine_arrays_sparse( + combined_values = _dispatch._combine_arrays( block_1.values, block_2.values, lam, cg_cache, return_empty_array=True ) else: - combined_values = _combine_arrays( + combined_values = _dispatch._combine_arrays( block_1.values, block_2.values, lam, cg_cache, return_empty_array=False ) @@ -709,63 +716,3 @@ def _combine_single_center_blocks( ) return combined_block - - -def _combine_arrays( - arr_1: np.ndarray, - arr_2: np.ndarray, - lam: int, - cg_cache, - return_empty_array: bool = False, -) -> np.ndarray: - """ - Couples arrays `arr_1` and `arr_2` corresponding to the irreducible - spherical components of 2 angular channels l1 and l2 using the appropriate - Clebsch-Gordan coefficients. As l1 and l2 can be combined to form multiple - lambda channels, this function returns the coupling to a single specified - channel `lambda`. The angular channels l1 and l2 are inferred from the size - of the components axis (axis 1) of the input arrays. - - `arr_1` has shape (n_i, 2 * l1 + 1, n_p) and `arr_2` has shape (n_i, 2 * l2 - + 1, n_q). n_i is the number of samples, n_p and n_q are the number of - properties in each array. The number of samples in each array must be the - same. - - The ouput array has shape (n_i, 2 * lambda + 1, n_p * n_q), where lambda is - the input parameter `lam`. - - The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these - must be produced by the ClebschGordanReal class in this module. These - coefficients can be stored in either sparse dictionaries or dense arrays. - - The combination operation is dispatched such that numpy arrays or torch - tensors are automatically handled. - - `return_empty_array` can be used to return an empty array of the correct - shape, without performing the CG combination step. This can be useful for - probing the outputs of CG iterations in terms of metadata without the - computational cost of performing the CG combinations - i.e. using the - function :py:func:`combine_single_center_to_body_order_metadata_only`. - - :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + - 1, n_q_properties] - :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + - 1, n_p_properties] - :param lam: int value of the resulting coupled channel - :param cg_cache: either a sparse dictionary with keys (m1, m2, mu) and array - values being sparse blocks of shape , or a dense array - of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)]. - - :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] - """ - # If just precomputing metadata, return an empty array - if return_empty_array: - return _dispatch._sparse_combine(arr_1, arr_2, lam, cg_cache, True) - - # Otherwise, perform the CG combination - # Spare CG cache - if cg_cache.sparse: - return _dispatch._sparse_combine(arr_1, arr_2, lam, cg_cache, False) - - # Dense CG cache - return _dispatch._dense_combine(arr_1, arr_2, lam, cg_cache) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 2add52604..99411d045 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -398,6 +398,76 @@ def test_combine_single_center_to_body_order_dense_sparse_agree(frames): ) +# ============ Test metadata precomputation ============ + + +@pytest.mark.parametrize("frames", [h2o_isolated()]) +@pytest.mark.parametrize("target_body_order", [2, 3]) +@pytest.mark.parametrize("sort_l_list", [True, False]) +def test_combine_single_center_to_body_order_metadata_only_metadata_agree( + frames, target_body_order, sort_l_list +): + """ + Tests that the metadata output from + combine_single_center_to_body_order_metadata_only agrees with the metadata + of the full tensor built using combine_single_center_to_body_order. + """ + for nu1 in [sphex_small_features(frames), sphex(frames)]: + # Build higher body order tensor with CG computation + nux = clebsch_gordan.combine_single_center_to_body_order( + nu1, + target_body_order=target_body_order, + angular_cutoff=None, + angular_selection=None, + parity_selection=None, + sort_l_list=sort_l_list, + use_sparse=True, + ) + # Build higher body order tensor without CG computation - i.e. metadata + # only. This returns a list of the TensorMaps formed at each CG + # iteration. In this case we only want to compare the metadata of the + # putput after the final iteration. + nux_metadata_only = ( + clebsch_gordan.combine_single_center_to_body_order_metadata_only( + nu1, + target_body_order=target_body_order, + angular_cutoff=None, + angular_selection=None, + parity_selection=None, + sort_l_list=sort_l_list, + ) + ) + assert metatensor.equal_metadata(nux, nux_metadata_only[-1]) + + +@pytest.mark.parametrize("frames", [h2o_isolated()]) +@pytest.mark.parametrize("target_body_order", [2, 3]) +@pytest.mark.parametrize("sort_l_list", [True, False]) +def test_combine_single_center_to_body_order_metadata_only( + frames, target_body_order, sort_l_list +): + """ + Performs hard-coded tests on the metadata outputted from + combine_single_center_to_body_order_metadata_only. + + TODO: finish! + """ + for nu1 in [sphex_small_features(frames), sphex(frames)]: + # Build higher body order tensor without CG computation - i.e. metadata + # only. This returns a list of the TensorMaps formed at each CG + # iteration. + nux_metadata_only = ( + clebsch_gordan.combine_single_center_to_body_order_metadata_only( + nu1, + target_body_order=target_body_order, + angular_cutoff=None, + angular_selection=None, + parity_selection=None, + sort_l_list=sort_l_list, + ) + ) + + # ============ Test kernel construction ============ # TODO: if we want this test, the below code will need updating @@ -509,4 +579,4 @@ def test_combine_single_center_to_body_order_dense_sparse_agree(frames): # >>> # --> (samples lambda_mu (q_features p_features)) # >>> out2 = out2.reshape(arr_1.shape[0], 2 * LAM + 1, -1) # >>> print(np.allclose(out1, out2)) -# True \ No newline at end of file +# True From 72d2bd059d13ecdbddd0e31c67979864a21daa93 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 15 Nov 2023 14:19:47 +0100 Subject: [PATCH 77/96] Remove archived files --- .../_archive/clebsch_gordan_ROUGH.py | 340 ---- .../_archive/combined_magres_spherical.xyz | 1584 ----------------- .../clebsch_gordan/_archive/frame copy.xyz | 4 - .../utils/clebsch_gordan/_archive/frame.xyz | 5 - .../_archive/lsoap_tutorial.ipynb | 452 ----- .../_archive/old_clebsch_gordan.py | 760 -------- .../utils/clebsch_gordan/_archive/tmp.ipynb | 806 --------- 7 files changed, 3951 deletions(-) delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/combined_magres_spherical.xyz delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame copy.xyz delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame.xyz delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/lsoap_tutorial.ipynb delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py deleted file mode 100644 index 86cbf288b..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/clebsch_gordan_ROUGH.py +++ /dev/null @@ -1,340 +0,0 @@ -# def _apply_body_order_corrections(tensor: TensorMap) -> TensorMap: -# """ -# Applies the appropriate prefactors to the block values of the output -# TensorMap (i.e. post-CG combination) according to its body order. -# """ -# return tensor - - -# def _normalize_blocks(tensor: TensorMap) -> TensorMap: -# """ -# Applies corrections to the block values based on their 'leaf' l-values, such -# that the norm is preserved. -# """ -# return tensor - - -# ===== ROUGH WORK: - -# def _parse_parity_selection( -# target_body_order: int, -# parity_selection: Union[None, int, List[int], List[List[int]]], -# ) -> List[List[int]]: -# """ -# Returns parity filters for each CG combination step of a nu=1 tensor with -# itself up to the target body order. - -# If a filter isn't specified by the user with `parity_selection=None`, then -# no filter is applied, i.e. [-1, +1] is used at every iteration. - -# If a single sequence of int is specified, then this is used for the last -# iteration only, and [-1, +1] is used for all intermediate iterations. For -# example, if `target_body_order=4` and `parity_selection=[+1]`, then the -# filter [[-1, +1], [-1, +1], [+1]] is returned. - -# If a sequence of sequences of int is specified, then this is assumed to be -# the desired filter for each iteration. - -# Note: very basic checks on the validity of the parity selections are -# performed, but these are not complete as they do not account for the angular -# channels of the blocks. These interactions are checked downstream in -# :py:func:`_create_combined_keys`. -# """ -# if target_body_order < 2: -# raise ValueError("`target_body_order` must be > 1") - -# # No filter specified: use [-1, +1] for all iterations -# if parity_selection is None: -# parity_selection = [[-1, +1] for _ in range(target_body_order - 1)] - -# # Parse user-defined filter: assume passed as List[int] or -# # List[List[int]] -# else: -# if isinstance(parity_selection, int): -# parity_selection = [parity_selection] -# if not isinstance(parity_selection, List): -# raise TypeError( -# "`parity_selection` must be an int, List[int], or List[List[int]]" -# ) -# # Single filter: apply on last iteration only, use both sigmas for -# # intermediate iterations -# if np.all([isinstance(sigma, int) for sigma in parity_selection]): -# parity_selection = [[-1, +1] for _ in range(target_body_order - 2)] + [parity_selection] - -# # Check parity_selection -# assert isinstance(parity_selection, List) -# assert len(parity_selection) == target_body_order - 1 -# assert np.all([isinstance(filt, List) for filt in parity_selection]) -# assert np.all([np.all([s in [-1, +1] for s in filt]) for filt in parity_selection]) - -# return parity_selection - - -# def _parse_angular_selection( -# angular_channels_1: List[int], -# angular_channels_2: List[int], -# target_body_order: int, -# rascal_max_l: int, -# angular_selection: Union[None, int, List[int], List[List[int]]], -# lambda_cut: Union[None, int], -# ) -> List[List[int]]: -# """ -# Parses the user-defined angular selection filters, returning - -# If a filter isn't specified by the user with `angular_selection=None`, then -# no filter is applied. In this case all possible lambda channels are retained -# at each iteration. For example, if `target_body_order=4`, `rascal_max_l=5`, -# and `lambda_cut=None`, then the returned filter is [[0, ..., 10], [0, ..., -# 15], [0, ..., 20]]. If `target_body_order=4`, `rascal_max_l=5`, and -# `lambda_cut=10`, then the returned filter is [[0, ..., 10], [0, ..., 10], -# [0, ..., 10]]. - -# If `angular_selection` is passed a single sequence of int, then this is used -# for the last iteration only, and all possible combinations of lambda are -# used in intermediate iterations. For instance, if `angular_selection=[0, 1, -# 2]`, the returned filters for the 2 examples above, respectively, would be -# [[0, ..., 10], [0, ..., 15], [0, 1, 2]] and [[0, ..., 10], [0, ..., 10], [0, -# 1, 2]]. - -# If a sequence of sequences of int is specified, then this is assumed to be -# the desired filter for each iteration and is only checked for validity -# without modification. - -# Note: basic checks on the validity of the angular selections are performed, -# but these are not complete as they do not account for the parity of the -# blocks. These interactions are checked downstream in -# :py:func:`_create_combined_keys`. -# """ -# if target_body_order < 2: -# raise ValueError("`target_body_order` must be > 1") - -# # Check value of lambda_cut -# if lambda_cut is not None: -# if not (rascal_max_l <= lambda_cut <= target_body_order * rascal_max_l): -# raise ValueError( -# "`lambda_cut` must be >= `rascal_hypers['max_angular']` and <= `target_body_order`" -# " * `rascal_hypers['max_angular']`" -# ) - -# # No filter specified: retain all possible lambda channels for every -# # iteration, up to lambda_cut (if specified) -# if angular_selection is None: -# if lambda_cut is None: -# # Use the full range of possible lambda channels for each iteration. -# # This is dependent on the itermediate body order. -# angular_selection = [ -# [lam for lam in range(0, (nu * rascal_max_l) + 1)] -# for nu in range(2, target_body_order + 1) -# ] -# else: -# # Use the full range of possible lambda channels for each iteration, -# # but only up to lambda_cut, independent of the intermediate body -# # order. -# angular_selection = [ -# [lam for lam in range(0, lambda_cut + 1)] -# for nu in range(2, target_body_order + 1) -# ] - -# # Parse user-defined filter: assume passed as List[int] or -# # List[List[int]] -# else: -# if isinstance(angular_selection, int): -# angular_selection = [angular_selection] -# if not isinstance(angular_selection, List): -# raise TypeError( -# "`angular_selection` must be an int, List[int], or List[List[int]]" -# ) -# # Single filter: apply on last iteration only, use all possible lambdas for -# # intermediate iterations (up to lambda_cut, if specified) -# if np.all([isinstance(filt, int) for filt in angular_selection]): -# if lambda_cut is None: -# # Use the full range of possible lambda channels for each iteration. -# # This is dependent on the itermediate body order. -# angular_selection = [ -# [lam for lam in range(0, (nu * rascal_max_l) + 1)] -# for nu in range(2, target_body_order) -# ] + [angular_selection] - -# else: -# # Use the full range of possible lambda channels for each iteration, -# # but only up to lambda_cut, independent of the intermediate body -# # order. -# angular_selection = [ -# [lam for lam in range(0, lambda_cut + 1)] -# for nu in range(2, target_body_order) -# ] + [angular_selection] - -# else: -# # Assume filter explicitly defined for each iteration (checked below) -# pass - -# # Check angular_selection -# if not isinstance(angular_selection, List): -# raise TypeError( -# "`angular_selection` must be an int, List[int], or List[List[int]]" -# ) -# if len(angular_selection) != target_body_order - 1: -# raise ValueError( -# "`angular_selection` must have length `target_body_order` - 1, i.e. the number of CG" -# " iterations required to reach `target_body_order`" -# ) -# if not np.all([isinstance(filt, List) for filt in angular_selection]): -# raise TypeError( -# "`angular_selection` must be an int, List[int], or List[List[int]]" -# ) -# # Check the lambda values are within the possible range, based on each -# # intermediate body order -# if not np.all( -# [ -# np.all([0 <= lam <= nu * rascal_max_l for lam in filt]) -# for nu, filt in enumerate(angular_selection, start=2) -# ] -# ): -# raise ValueError( -# "All lambda values in `angular_selection` must be >= 0 and <= `nu` *" -# " `rascal_hypers['max_angular']`, where `nu` is the body" -# " order created in the intermediate CG combination step" -# ) -# # Now check that at each iteration the lambda values can actually be created -# # from combination at the previous iteration -# for filt_i, filt in enumerate(angular_selection): -# if filt_i == 0: -# # Assume that the original nu=1 tensors to be combined have all l up -# # to and including `rascal_max_l` -# allowed_lams = np.arange(0, (2 * rascal_max_l) + 1) -# else: -# allowed_lams = [] -# for l1, l2 in itertools.product(angular_selection[filt_i - 1], repeat=2): -# for lam in range(abs(l1 - l2), abs(l1 + l2) + 1): -# allowed_lams.append(lam) - -# allowed_lams = np.unique(allowed_lams) - -# if not np.all([lam in allowed_lams for lam in filt]): -# raise ValueError( -# f"invalid lambda values in `angular_selection` for iteration {filt_i + 1}." -# f" {filt} cannot be created by combination of previous lambda values" -# f" {angular_selection[filt_i - 1]}" -# ) - -# return angular_selection - - -# def _combine_single_center( -# tensor_1: TensorMap, -# tensor_2: TensorMap, -# lambdas: List[int], -# sigmas: List[int], -# cg_cache, -# ) -> TensorMap: -# """ -# For 2 TensorMaps, ``tensor_1`` and ``tensor_2``, with body orders nu and 1 -# respectively, combines their blocks to form a new TensorMap with body order -# (nu + 1). - -# Returns blocks only indexed by keys . - -# Assumes the metadata of the two TensorMaps are standardized as follows. - -# The keys of `tensor_1` must follow the key name convention: - -# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", -# "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns -# track the l values of the nu=1 blocks that were previously combined. The -# "kx" columns tracks the intermediate lambda values of nu > 1 blocks that -# haev been combined. - -# For instance, a TensorMap of body order nu=4 will have key names -# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", -# "l1", "l2", "l3", "l4", "k2", "k3"]. Two nu=1 TensorMaps with blocks of -# order "l1" and "l2" were combined to form a nu=2 TensorMap with blocks of -# order "k2". This was combined with a nu=1 TensorMap with blocks of order -# "l3" to form a nu=3 TensorMap with blocks of order "k3". Finally, this was -# combined with a nu=1 TensorMap with blocks of order "l4" to form a nu=4. - -# .. math :: - -# \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n{\nu-1} l_{\nu-1} k_{\nu-1} ; -# n{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } - -# The keys of `tensor_2` must follow the key name convention: - -# ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - -# Samples of pairs of blocks corresponding to the same chemical species are -# equivalent in the two TensorMaps. Samples names are ["structure", "center"] - -# Components names are [["spherical_harmonics_m"],] for each block. - -# Property names are ["n1", "n2", ..., "species_neighbor_1", -# "species_neighbor_2", ...] for each block. -# """ - -# # Get the correct keys for the combined output TensorMap -# ( -# nux_keys, -# keys_1_entries, -# keys_2_entries, -# multiplicity_list, -# ) = _create_combined_keys(tensor_1.keys, tensor_2.keys, lambdas, sigmas) - -# # Iterate over pairs of blocks and combine -# nux_blocks = [] -# for nux_key, key_1, key_2, multi in zip( -# nux_keys, keys_1_entries, keys_2_entries, multiplicity_list -# ): -# # Retrieve the blocks -# block_1 = tensor_1[key_1] -# block_2 = tensor_2[key_2] - -# # Combine the blocks into a new TensorBlock of the correct lambda order. -# # Pass the correction factor accounting for the redundancy of "lx" -# # combinations. -# nux_blocks.append( -# _combine_single_center_block_pair( -# block_1, -# block_2, -# nux_key["spherical_harmonics_l"], -# cg_cache, -# correction_factor=np.sqrt(multi), -# ) -# ) - -# return TensorMap(nux_keys, nux_blocks) - - -# # Parse the angular and parity selections. Basic checks are performed here -# # and errors raised if invalid selections are passed. -# parity_selection = _parse_parity_selection(target_body_order, parity_selection) -# angular_selection = _parse_angular_selection( -# target_body_order, rascal_hypers["max_angular"], angular_selection, angular_cutoff -# ) -# if debug: -# print("parity_selection: ", parity_selection) -# print("angular_selection: ", angular_selection) -# # Pre-compute all the information needed to combined tensors at every -# # iteration. This includes the keys of the TensorMaps produced at each -# # iteration, the keys of the blocks combined to make them, and block -# # multiplicities. -# combine_info = [] -# for iteration in range(1, target_body_order): -# info = _create_combined_keys( -# nux_keys, -# nu1_keys, -# angular_selection[iteration - 1], -# parity_selection[iteration - 1], -# ) -# combine_info.append(info) -# nux_keys = info[0] - -# if debug: -# print("Num. keys at each step: ", [len(c[0]) for c in combine_info]) -# print([nu1_keys] + [c[0] for c in combine_info]) -# return - -# if np.any([len(c[0]) == 0 for c in combine_info]): -# raise ValueError( -# "invalid filters: one or more iterations produce no valid combinations." -# f" Number of keys at each iteration: {[len(c[0]) for c in combine_info]}." -# " Check the `angular_selection` and `parity_selection` arguments." -# ) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/combined_magres_spherical.xyz b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/combined_magres_spherical.xyz deleted file mode 100644 index 8272cb8e0..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/combined_magres_spherical.xyz +++ /dev/null @@ -1,1584 +0,0 @@ -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.01060000 -0.00000000 0.00000000 -0.00000000 -0.01060000 -0.00000000 0.00000000 -0.00000000 0.02120000 -0.00000000 0.00000000 0.00000000 0.02596459 0.00000000 0.00000000 -Li 0.00000000 0.00000000 0.00000000 -0.00910000 -0.00000000 0.00000000 -0.00000000 -0.00910000 0.00000000 0.00000000 0.00000000 0.01820000 -0.00000000 0.00000000 0.00000000 0.02229036 0.00000000 0.00000000 -Li 2.95500000 1.70600000 6.63400000 0.00740000 -0.00000000 0.00000000 -0.00000000 0.00740000 -0.00000000 0.00000000 -0.00000000 -0.01470000 -0.00005774 0.00000000 0.00000000 -0.01804457 0.00000000 0.00000000 -Li 2.95500000 1.70600000 3.01600000 0.00710000 -0.00000000 0.00000000 -0.00000000 0.00710000 -0.00000000 0.00000000 -0.00000000 -0.01410000 -0.00005774 0.00000000 0.00000000 -0.01730973 0.00000000 0.00000000 -Li 0.00000000 3.41200000 11.45900000 -0.00080000 0.00000000 0.00000000 0.00000000 -0.00080000 -0.00000000 0.00000000 -0.00000000 0.00160000 -0.00000000 0.00000000 0.00000000 0.00195959 0.00000000 0.00000000 -Li 0.00000000 3.41200000 7.84000000 -0.00030000 -0.00000000 0.00000000 -0.00000000 -0.00030000 -0.00000000 0.00000000 -0.00000000 0.00050000 0.00005774 0.00000000 0.00000000 0.00065320 0.00000000 0.00000000 -Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 0.00000000 -0.00000000 0.00670000 -0.00000000 0.00000000 -0.00000000 -0.01330000 -0.00005774 0.00000000 0.00000000 -0.01632993 0.00000000 0.00000000 -Li 2.95500000 1.70600000 12.06200000 0.00590000 -0.00000000 0.00000000 -0.00000000 0.00590000 -0.00000000 0.00000000 -0.00000000 -0.01170000 -0.00005774 0.00000000 0.00000000 -0.01437034 0.00000000 0.00000000 -O 0.00000000 0.00000000 3.80700000 0.06860000 -0.00000000 0.00000000 -0.00000000 0.06860000 -0.00000000 0.00000000 -0.00000000 -0.13710000 -0.00005774 0.00000000 0.00000000 -0.16795335 0.00000000 0.00000000 -O 0.00000000 0.00000000 10.66800000 0.05090000 -0.00000000 0.00000000 -0.00000000 0.05090000 0.00000000 0.00000000 0.00000000 -0.10170000 -0.00005774 0.00000000 0.00000000 -0.12459738 0.00000000 0.00000000 -O 2.95500000 1.70600000 8.63200000 0.05550000 0.00000000 -0.00000000 0.00000000 0.05550000 -0.00000000 -0.00000000 -0.00000000 -0.11100000 0.00000000 0.00000000 0.00000000 -0.13594668 0.00000000 0.00000000 -O 2.95500000 1.70600000 1.01800000 0.09550000 -0.00000000 0.00000000 -0.00000000 0.09550000 0.00000000 0.00000000 0.00000000 -0.19100000 0.00000000 0.00000000 0.00000000 -0.23392627 0.00000000 0.00000000 -O 0.00000000 3.41200000 13.45700000 -0.00340000 0.00000000 -0.00000000 0.00000000 -0.00340000 -0.00000000 -0.00000000 -0.00000000 0.00680000 0.00000000 0.00000000 0.00000000 0.00832827 0.00000000 0.00000000 -O 0.00000000 3.41200000 5.84300000 0.00840000 0.00000000 -0.00000000 0.00000000 0.00840000 0.00000000 -0.00000000 0.00000000 -0.01670000 -0.00005774 0.00000000 0.00000000 -0.02049406 0.00000000 0.00000000 -O -1.32400000 4.17600000 1.14400000 0.06020000 -0.04410000 0.06170000 -0.04410000 0.00930000 -0.03560000 0.06170000 -0.03560000 -0.06950000 0.00000000 -0.06236682 -0.05034600 -0.08511977 0.08725698 0.03599174 -O 0.00000000 1.88300000 1.14400000 -0.01610000 0.00000000 -0.00000000 0.00000000 0.08560000 0.07120000 -0.00000000 0.07120000 -0.06950000 0.00000000 0.00000000 0.10069201 -0.08511977 0.00000000 -0.07191276 -O 1.32400000 4.17600000 1.14400000 0.06020000 0.04410000 -0.06170000 0.04410000 0.00930000 -0.03560000 -0.06170000 -0.03560000 -0.06950000 0.00000000 0.06236682 -0.05034600 -0.08511977 -0.08725698 0.03599174 -O 4.27800000 0.94200000 13.33100000 -0.10950000 0.04920000 0.02020000 0.04920000 -0.05270000 -0.01170000 0.02020000 -0.01170000 0.16220000 -0.00000000 0.06957931 -0.01654630 0.19865362 0.02856711 -0.04016367 -O 1.63100000 0.94200000 13.33100000 -0.10950000 -0.04920000 -0.02020000 -0.04920000 -0.05270000 -0.01170000 -0.02020000 -0.01170000 0.16220000 -0.00000000 -0.06957931 -0.01654630 0.19865362 -0.02856711 -0.04016367 -O 2.95500000 3.23400000 13.33100000 -0.02430000 0.00000000 -0.00000000 0.00000000 -0.13790000 0.02340000 -0.00000000 0.02340000 0.16220000 -0.00000000 0.00000000 0.03309260 0.19865362 0.00000000 0.08032733 -O 4.58600000 0.76400000 5.96800000 -0.11800000 0.10490000 -0.07110000 0.10490000 0.00310000 0.04100000 -0.07110000 0.04100000 0.11490000 -0.00000000 0.14835100 0.05798276 0.14072319 -0.10055058 -0.08563063 -O 2.95500000 3.58900000 5.96800000 0.06370000 0.00000000 0.00000000 0.00000000 -0.17860000 -0.08210000 0.00000000 -0.08210000 0.11490000 -0.00000000 0.00000000 -0.11610693 0.14072319 0.00000000 0.17133197 -O 1.32400000 0.76400000 5.96800000 -0.11800000 -0.10490000 0.07110000 -0.10490000 0.00310000 0.04100000 0.07110000 0.04100000 0.11490000 -0.00000000 -0.14835100 0.05798276 0.14072319 0.10055058 -0.08563063 -O 1.32400000 2.64800000 3.68100000 -0.00510000 0.02280000 0.00150000 0.02280000 0.02120000 -0.00090000 0.00150000 -0.00090000 -0.01610000 0.00000000 0.03224407 -0.00127279 -0.01971839 0.00212132 -0.01859691 -O -1.32400000 2.64800000 3.68100000 -0.00510000 -0.02280000 -0.00150000 -0.02280000 0.02120000 -0.00090000 -0.00150000 -0.00090000 -0.01610000 0.00000000 -0.03224407 -0.00127279 -0.01971839 -0.00212132 -0.01859691 -O 0.00000000 4.94000000 3.68100000 0.03440000 -0.00000000 0.00000000 -0.00000000 -0.01830000 0.00170000 0.00000000 0.00170000 -0.01610000 0.00000000 0.00000000 0.00240416 -0.01971839 0.00000000 0.03726453 -O 1.63100000 2.47000000 10.79300000 0.01150000 0.00820000 0.02680000 0.00820000 0.02090000 -0.01550000 0.02680000 -0.01550000 -0.03240000 0.00000000 0.01159655 -0.02192031 -0.03968173 0.03790092 -0.00664680 -O 2.95500000 0.17700000 10.79300000 0.02560000 0.00000000 0.00000000 0.00000000 0.00680000 0.03100000 0.00000000 0.03100000 -0.03240000 0.00000000 0.00000000 0.04384062 -0.03968173 0.00000000 0.01329361 -O 4.27800000 2.47000000 10.79300000 0.01150000 -0.00820000 -0.02680000 -0.00820000 0.02090000 -0.01550000 -0.02680000 -0.01550000 -0.03240000 0.00000000 -0.01159655 -0.02192031 -0.03968173 -0.03790092 -0.00664680 -O -1.63100000 4.35300000 8.50600000 -0.01090000 0.04610000 0.01230000 0.04610000 0.04240000 -0.00710000 0.01230000 -0.00710000 -0.03150000 0.00000000 0.06519525 -0.01004092 -0.03857946 0.01739483 -0.03768879 -O 1.63100000 4.35300000 8.50600000 -0.01090000 -0.04610000 -0.01230000 -0.04610000 0.04240000 -0.00710000 -0.01230000 -0.00710000 -0.03150000 0.00000000 -0.06519525 -0.01004092 -0.03857946 -0.01739483 -0.03768879 -O 0.00000000 1.52800000 8.50600000 0.06900000 0.00000000 -0.00000000 0.00000000 -0.03750000 0.01420000 -0.00000000 0.01420000 -0.03150000 0.00000000 0.00000000 0.02008183 -0.03857946 0.00000000 0.07530687 -Ti 0.00000000 3.41200000 2.41200000 0.03660000 0.00000000 -0.00000000 0.00000000 0.03660000 -0.00000000 -0.00000000 -0.00000000 -0.07330000 0.00005774 0.00000000 0.00000000 -0.08973297 0.00000000 0.00000000 -Ti 2.95500000 0.00000000 0.00000000 0.08900000 -0.00000000 0.00000000 -0.00000000 -0.13050000 0.13930000 0.00000000 0.13930000 0.04150000 0.00000000 0.00000000 0.19699995 0.05082691 0.00000000 0.15520994 -Ti -1.47700000 2.55900000 0.00000000 -0.07560000 -0.09510000 -0.12060000 -0.09510000 0.03420000 -0.06960000 -0.12060000 -0.06960000 0.04150000 -0.00005774 -0.13449171 -0.09842926 0.05078609 -0.17055416 -0.07764032 -Ti 1.47700000 2.55900000 0.00000000 -0.07560000 0.09510000 0.12060000 0.09510000 0.03420000 -0.06960000 0.12060000 -0.06960000 0.04150000 -0.00005774 0.13449171 -0.09842926 0.05078609 0.17055416 -0.07764032 -Ti 0.00000000 1.70600000 4.82500000 0.05950000 -0.00000000 -0.00000000 -0.00000000 -0.10090000 0.15820000 -0.00000000 0.15820000 0.04130000 0.00005774 0.00000000 0.22372859 0.05062279 0.00000000 0.11341993 -Ti 1.47700000 4.26500000 4.82500000 -0.06080000 -0.06950000 -0.13700000 -0.06950000 0.01940000 -0.07910000 -0.13700000 -0.07910000 0.04130000 0.00005774 -0.09828784 -0.11186429 0.05062279 -0.19374726 -0.05670996 -Ti -1.47700000 4.26500000 4.82500000 -0.06080000 0.06950000 0.13700000 0.06950000 0.01940000 -0.07910000 0.13700000 -0.07910000 0.04130000 0.00005774 0.09828784 -0.11186429 0.05062279 0.19374726 -0.05670996 -Ti 2.95500000 3.41200000 9.65000000 0.10390000 -0.00000000 -0.00000000 -0.00000000 -0.14050000 0.21220000 -0.00000000 0.21220000 0.03660000 0.00000000 0.00000000 0.30009612 0.04482566 0.00000000 0.17281690 -Ti 1.47700000 0.85300000 9.65000000 -0.07940000 -0.10580000 -0.18380000 -0.10580000 0.04280000 -0.10610000 -0.18380000 -0.10610000 0.03660000 -0.00000000 -0.14962379 -0.15004806 0.04482566 -0.25993245 -0.08640845 -Ti 4.43200000 0.85300000 9.65000000 -0.07940000 0.10580000 0.18380000 0.10580000 0.04280000 -0.10610000 0.18380000 -0.10610000 0.03660000 -0.00000000 0.14962379 -0.15004806 0.04482566 0.25993245 -0.08640845 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00660000 0.00030000 0.00000000 0.00030000 0.00630000 0.00000000 0.00000000 0.00000000 -0.01290000 0.00000000 0.00042426 0.00000000 -0.01579921 0.00000000 0.00021213 -Li 1.47700000 4.26500000 12.06200000 0.00000000 0.00130000 -0.00740000 0.00130000 -0.00150000 -0.00430000 -0.00740000 -0.00430000 0.00140000 0.00005774 0.00183848 -0.00608112 0.00175547 -0.01046518 0.00106066 -Li 2.95500000 1.70600000 6.63400000 0.00750000 0.00040000 0.00010000 0.00040000 0.00700000 0.00000000 0.00010000 0.00000000 -0.01450000 0.00000000 0.00056569 0.00000000 -0.01775880 0.00014142 0.00035355 -Li 2.95500000 1.70600000 3.01600000 0.00850000 0.00040000 0.00010000 0.00040000 0.00810000 0.00000000 0.00010000 0.00000000 -0.01660000 0.00000000 0.00056569 0.00000000 -0.02033076 0.00014142 0.00028284 -Li 0.00000000 3.41200000 9.65000000 -0.00910000 0.00050000 0.00160000 0.00050000 -0.00980000 0.00090000 0.00160000 0.00090000 0.01890000 -0.00000000 0.00070711 0.00127279 0.02314768 0.00226274 0.00049497 -Li 0.00000000 3.41200000 7.84000000 -0.01750000 0.00030000 -0.00000000 0.00030000 -0.01790000 -0.00000000 -0.00000000 -0.00000000 0.03540000 -0.00000000 0.00042426 0.00000000 0.04335597 0.00000000 0.00028284 -Li 0.00000000 0.00000000 7.23700000 0.00670000 0.00030000 -0.00000000 0.00030000 0.00630000 -0.00000000 -0.00000000 -0.00000000 -0.01310000 0.00005774 0.00042426 0.00000000 -0.01600333 0.00000000 0.00028284 -Li 2.95500000 1.70600000 12.06200000 0.00550000 -0.00300000 0.00050000 -0.00300000 0.00890000 0.00030000 0.00050000 0.00030000 -0.01440000 0.00000000 -0.00424264 0.00042426 -0.01763633 0.00070711 -0.00240416 -O 0.00000000 0.00000000 3.80700000 0.08360000 0.00430000 -0.00030000 0.00430000 0.07860000 -0.00020000 -0.00030000 -0.00020000 -0.16230000 0.00005774 0.00608112 -0.00028284 -0.19873527 -0.00042426 0.00353553 -O 0.00000000 0.00000000 10.66800000 0.09890000 0.06040000 -0.06360000 0.06040000 0.02920000 -0.03670000 -0.06360000 -0.03670000 -0.12820000 0.00005774 0.08541850 -0.05190164 -0.15697147 -0.08994398 0.04928534 -O 2.95500000 1.70600000 8.63200000 0.07480000 0.01010000 -0.01700000 0.01010000 0.06310000 -0.00980000 -0.01700000 -0.00980000 -0.13790000 0.00000000 0.01428356 -0.01385929 -0.16889232 -0.02404163 0.00827315 -O 2.95500000 1.70600000 1.01800000 0.09670000 0.00780000 -0.01710000 0.00780000 0.08780000 -0.00990000 -0.01710000 -0.00990000 -0.18450000 0.00000000 0.01103087 -0.01400071 -0.22596543 -0.02418305 0.00629325 -O 0.00000000 3.41200000 13.45700000 0.03630000 0.05780000 -0.05250000 0.05780000 -0.03030000 -0.03030000 -0.05250000 -0.03030000 -0.00600000 0.00000000 0.08174154 -0.04285067 -0.00734847 -0.07424621 0.04709331 -O 0.00000000 3.41200000 5.84300000 0.01680000 0.00480000 -0.00010000 0.00480000 0.01120000 -0.00010000 -0.00010000 -0.00010000 -0.02800000 0.00000000 0.00678823 -0.00014142 -0.03429286 -0.00014142 0.00395980 -O -1.32400000 4.17600000 1.14400000 -0.01830000 0.05160000 0.01590000 0.05160000 0.04180000 -0.00110000 0.01590000 -0.00110000 -0.02350000 0.00000000 0.07297342 -0.00155563 -0.02878150 0.02248600 -0.04249712 -O 0.00000000 1.88300000 1.14400000 0.07140000 -0.00020000 0.00700000 -0.00020000 -0.04790000 0.01430000 0.00700000 0.01430000 -0.02350000 -0.00000000 -0.00028284 0.02022325 -0.02878150 0.00989949 0.08435784 -O 1.32400000 4.17600000 1.14400000 -0.01320000 -0.04120000 -0.02350000 -0.04120000 0.03430000 -0.01360000 -0.02350000 -0.01360000 -0.02110000 0.00000000 -0.05826560 -0.01923330 -0.02584212 -0.03323402 -0.03358757 -O 4.27800000 0.94200000 13.33100000 -0.18730000 0.11740000 -0.05150000 0.11740000 0.03410000 0.07910000 -0.05150000 0.07910000 0.15320000 -0.00000000 0.16602867 0.11186429 0.18763091 -0.07283200 -0.15655344 -O 1.63100000 0.94200000 13.33100000 -0.14190000 -0.07890000 0.05220000 -0.07890000 -0.05080000 0.03020000 0.05220000 0.03020000 0.19280000 -0.00005774 -0.11158145 0.04270925 0.23608999 0.07382195 -0.06441743 -O 2.95500000 3.23400000 13.33100000 0.08040000 -0.03710000 0.04280000 -0.03710000 -0.23360000 -0.08420000 0.04280000 -0.08420000 0.15320000 0.00000000 -0.05246732 -0.11907678 0.18763091 0.06052834 0.22203153 -O 4.58600000 0.76400000 5.96800000 -0.11970000 0.11720000 -0.06980000 0.11720000 0.00350000 0.04070000 -0.06980000 0.04070000 0.11620000 -0.00000000 0.16574583 0.05755849 0.14231535 -0.09871211 -0.08711556 -O 2.95500000 3.58900000 5.96800000 0.07420000 0.00520000 0.00030000 0.00520000 -0.19040000 -0.08080000 0.00030000 -0.08080000 0.11620000 -0.00000000 0.00735391 -0.11426846 0.14231535 0.00042426 0.18710045 -O 1.32400000 0.76400000 5.96800000 -0.12000000 -0.10760000 0.06980000 -0.10760000 0.00420000 0.04030000 0.06980000 0.04030000 0.11570000 0.00005774 -0.15216938 0.05699281 0.14174381 0.09871211 -0.08782266 -O 1.32400000 2.64800000 3.68100000 -0.00200000 0.04430000 -0.00390000 0.04430000 0.03870000 0.00390000 -0.00390000 0.00390000 -0.03670000 0.00000000 0.06264966 0.00551543 -0.04494814 -0.00551543 -0.02877925 -O -1.32400000 2.64800000 3.68100000 -0.00160000 -0.03490000 0.00400000 -0.03490000 0.03870000 0.00230000 0.00400000 0.00230000 -0.03710000 0.00000000 -0.04935605 0.00325269 -0.04543803 0.00565685 -0.02849640 -O 0.00000000 4.94000000 3.68100000 0.06690000 0.00460000 0.00140000 0.00460000 -0.03020000 -0.00530000 0.00140000 -0.00530000 -0.03670000 0.00000000 0.00650538 -0.00749533 -0.04494814 0.00197990 0.06866007 -O 1.63100000 2.47000000 10.79300000 -0.08200000 0.06530000 -0.01360000 0.06530000 0.04530000 0.07950000 -0.01360000 0.07950000 0.03680000 -0.00005774 0.09234815 0.11242998 0.04502979 -0.01923330 -0.09001469 -O 2.95500000 0.17700000 10.79300000 0.07000000 -0.02240000 0.06200000 -0.02240000 -0.10680000 -0.05150000 0.06200000 -0.05150000 0.03680000 -0.00000000 -0.03167838 -0.07283200 0.04507061 0.08768124 0.12501648 -O 4.27800000 2.47000000 10.79300000 0.00300000 0.02490000 0.00700000 0.02490000 -0.02570000 0.00400000 0.00700000 0.00400000 0.02270000 -0.00000000 0.03521392 0.00565685 0.02780171 0.00989949 0.02029396 -O -1.63100000 4.35300000 8.50600000 -0.03770000 0.06420000 -0.03530000 0.06420000 0.02700000 0.02680000 -0.03530000 0.02680000 0.01070000 0.00000000 0.09079251 0.03790092 0.01310477 -0.04992174 -0.04574981 -O 1.63100000 4.35300000 8.50600000 -0.02940000 -0.04120000 0.02650000 -0.04120000 0.01820000 0.01530000 0.02650000 0.01530000 0.01110000 0.00005774 -0.05826560 0.02163747 0.01363549 0.03747666 -0.03365828 -O 0.00000000 1.52800000 8.50600000 0.06640000 0.00410000 0.00550000 0.00410000 -0.07710000 -0.04390000 0.00550000 -0.04390000 0.01070000 -0.00000000 0.00579828 -0.06208398 0.01310477 0.00777817 0.10146982 -Ti 0.00000000 3.41200000 2.41200000 0.10910000 -0.00130000 0.00100000 -0.00130000 0.11060000 0.00060000 0.00100000 0.00060000 -0.21970000 0.00000000 -0.00183848 0.00084853 -0.26907645 0.00141421 -0.00106066 -Ti 2.95500000 0.00000000 0.00000000 0.08160000 0.01180000 -0.02140000 0.01180000 -0.15940000 0.18350000 -0.02140000 0.18350000 0.07790000 -0.00005774 0.01668772 0.25950819 0.09536680 -0.03026417 0.17041273 -Ti -1.47700000 2.55900000 0.00000000 -0.09630000 -0.10100000 -0.15360000 -0.10100000 0.02030000 -0.08870000 -0.15360000 -0.08870000 0.07600000 -0.00000000 -0.14283557 -0.12544074 0.09308061 -0.21722320 -0.08244865 -Ti 1.47700000 2.55900000 0.00000000 -0.08890000 0.11030000 0.14820000 0.11030000 0.01110000 -0.11030000 0.14820000 -0.11030000 0.07790000 -0.00005774 0.15598776 -0.15598776 0.09536680 0.20958645 -0.07071068 -Ti 0.00000000 1.70600000 4.82500000 0.06990000 0.00010000 -0.00010000 0.00010000 -0.11370000 0.17310000 -0.00010000 0.17310000 0.04380000 -0.00000000 0.00014142 0.24480037 0.05364383 -0.00014142 0.12982481 -Ti 1.47700000 4.26500000 4.82500000 -0.06780000 -0.07940000 -0.14970000 -0.07940000 0.02390000 -0.08640000 -0.14970000 -0.08640000 0.04400000 -0.00005774 -0.11228856 -0.12218805 0.05384795 -0.21170777 -0.06484169 -Ti -1.47700000 4.26500000 4.82500000 -0.06770000 0.07960000 0.14990000 0.07960000 0.02390000 -0.08660000 0.14990000 -0.08660000 0.04380000 -0.00000000 0.11257140 -0.12247089 0.05364383 0.21199061 -0.06477098 -Ti 2.95500000 3.41200000 9.65000000 0.11450000 0.01220000 -0.02040000 0.01220000 -0.14490000 0.23840000 -0.02040000 0.23840000 0.03050000 -0.00005774 0.01725341 0.33714851 0.03731389 -0.02884996 0.18342350 -Ti 1.47700000 0.85300000 9.65000000 -0.07660000 -0.10820000 -0.19470000 -0.10820000 0.04830000 -0.11240000 -0.19470000 -0.11240000 0.02830000 0.00000000 -0.15301791 -0.15895760 0.03466028 -0.27534738 -0.08831764 -Ti 4.43200000 0.85300000 9.65000000 -0.06950000 0.11840000 0.19620000 0.11840000 0.03900000 -0.13690000 0.19620000 -0.13690000 0.03050000 0.00000000 0.16744289 -0.19360584 0.03735472 0.27746870 -0.07672109 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00740000 0.00000000 0.00000000 0.00000000 0.00850000 0.00000000 0.00000000 0.00000000 -0.01590000 0.00000000 0.00000000 0.00000000 -0.01947344 0.00000000 -0.00077782 -Li 1.47700000 4.26500000 12.06200000 0.00600000 0.00190000 -0.00720000 0.00190000 -0.00460000 -0.00540000 -0.00720000 -0.00540000 -0.00150000 0.00005774 0.00268701 -0.00763675 -0.00179629 -0.01018234 0.00749533 -Li 2.95500000 1.70600000 6.63400000 0.00530000 0.00000000 0.00000000 0.00000000 0.00610000 0.00010000 0.00000000 0.00010000 -0.01140000 0.00000000 0.00000000 0.00014142 -0.01396209 0.00000000 -0.00056569 -Li 2.95500000 1.70600000 3.01600000 0.00600000 -0.00000000 -0.00000000 -0.00000000 0.00690000 0.00010000 -0.00000000 0.00010000 -0.01290000 0.00000000 0.00000000 0.00014142 -0.01579921 0.00000000 -0.00063640 -Li -1.47700000 4.26500000 12.06200000 0.00600000 -0.00190000 0.00720000 -0.00190000 -0.00460000 -0.00540000 0.00720000 -0.00540000 -0.00150000 0.00005774 -0.00268701 -0.00763675 -0.00179629 0.01018234 0.00749533 -Li 0.00000000 3.41200000 9.65000000 0.00780000 0.00000000 0.00000000 0.00000000 0.00880000 0.00220000 0.00000000 0.00220000 -0.01660000 0.00000000 0.00000000 0.00311127 -0.02033076 0.00000000 -0.00070711 -Li 0.00000000 0.00000000 7.23700000 0.00570000 0.00000000 0.00000000 0.00000000 0.00670000 -0.00000000 0.00000000 -0.00000000 -0.01240000 0.00000000 0.00000000 0.00000000 -0.01518684 0.00000000 -0.00070711 -Li 2.95500000 1.70600000 12.06200000 0.00390000 -0.00000000 -0.00000000 -0.00000000 0.01280000 0.00120000 -0.00000000 0.00120000 -0.01670000 0.00000000 0.00000000 0.00169706 -0.02045324 0.00000000 -0.00629325 -O 0.00000000 0.00000000 3.80700000 0.07960000 -0.00000000 0.00000000 -0.00000000 0.08960000 -0.00030000 0.00000000 -0.00030000 -0.16920000 0.00000000 0.00000000 -0.00042426 -0.20722683 0.00000000 -0.00707107 -O 0.00000000 0.00000000 10.66800000 0.14890000 0.00000000 0.00000000 0.00000000 0.03330000 -0.09430000 0.00000000 -0.09430000 -0.18230000 0.00005774 0.00000000 -0.13336034 -0.22323017 0.00000000 0.08174154 -O 2.95500000 1.70600000 8.63200000 0.04910000 0.00000000 0.00000000 0.00000000 0.05130000 -0.02170000 0.00000000 -0.02170000 -0.10040000 0.00000000 0.00000000 -0.03068843 -0.12296439 0.00000000 -0.00155563 -O 2.95500000 1.70600000 1.01800000 0.08440000 -0.00000000 0.00000000 -0.00000000 0.08590000 -0.01910000 0.00000000 -0.01910000 -0.17030000 0.00000000 0.00000000 -0.02701148 -0.20857405 0.00000000 -0.00106066 -O 0.00000000 3.41200000 13.45700000 0.08090000 0.00000000 0.00000000 0.00000000 -0.02490000 -0.07170000 0.00000000 -0.07170000 -0.05590000 -0.00005774 0.00000000 -0.10139911 -0.06850406 0.00000000 0.07481190 -O 0.00000000 3.41200000 5.84300000 -0.01310000 0.00000000 -0.00000000 0.00000000 -0.00210000 -0.00000000 -0.00000000 -0.00000000 0.01530000 -0.00005774 0.00000000 0.00000000 0.01869777 0.00000000 -0.00777817 -O -1.32400000 4.17600000 1.14400000 -0.02340000 0.04110000 -0.00050000 0.04110000 0.04640000 0.00920000 -0.00050000 0.00920000 -0.02300000 0.00000000 0.05812418 0.01301076 -0.02816913 -0.00070711 -0.04935605 -O 0.00000000 1.88300000 1.14400000 0.06480000 0.00000000 -0.00000000 0.00000000 -0.03670000 -0.02000000 -0.00000000 -0.02000000 -0.02810000 0.00000000 0.00000000 -0.02828427 -0.03441533 0.00000000 0.07177134 -O 1.32400000 4.17600000 1.14400000 -0.02340000 -0.04110000 0.00050000 -0.04110000 0.04640000 0.00920000 0.00050000 0.00920000 -0.02300000 0.00000000 -0.05812418 0.01301076 -0.02816913 0.00070711 -0.04935605 -O 4.27800000 0.94200000 13.33100000 -0.17710000 0.11200000 -0.06810000 0.11200000 0.07050000 0.09160000 -0.06810000 0.09160000 0.10650000 0.00005774 0.15839192 0.12954196 0.13047615 -0.09630794 -0.17507964 -O 1.63100000 0.94200000 13.33100000 -0.17710000 -0.11200000 0.06810000 -0.11200000 0.07050000 0.09160000 0.06810000 0.09160000 0.10650000 0.00005774 -0.15839192 0.12954196 0.13047615 0.09630794 -0.17507964 -O 2.95500000 3.23400000 13.33100000 0.15530000 0.00000000 -0.00000000 0.00000000 -0.23220000 -0.14120000 -0.00000000 -0.14120000 0.07690000 -0.00000000 0.00000000 -0.19968696 0.09418288 0.00000000 0.27400388 -O 4.58600000 0.76400000 5.96800000 -0.13620000 0.09300000 -0.05040000 0.09300000 -0.01660000 0.02950000 -0.05040000 0.02950000 0.15280000 -0.00000000 0.13152186 0.04171930 0.18714102 -0.07127636 -0.08456997 -O 2.95500000 3.58900000 5.96800000 0.02620000 0.00000000 -0.00000000 0.00000000 -0.17780000 -0.05840000 -0.00000000 -0.05840000 0.15160000 -0.00000000 0.00000000 -0.08259007 0.18567132 0.00000000 0.14424978 -O 1.32400000 0.76400000 5.96800000 -0.13620000 -0.09300000 0.05040000 -0.09300000 -0.01660000 0.02950000 0.05040000 0.02950000 0.15280000 -0.00000000 -0.13152186 0.04171930 0.18714102 0.07127636 -0.08456997 -O 1.32400000 2.64800000 3.68100000 -0.01330000 0.03860000 0.01720000 0.03860000 0.04330000 -0.00830000 0.01720000 -0.00830000 -0.03000000 0.00000000 0.05458864 -0.01173797 -0.03674235 0.02432447 -0.04002224 -O -1.32400000 2.64800000 3.68100000 -0.01330000 -0.03860000 -0.01720000 -0.03860000 0.04330000 -0.00830000 -0.01720000 -0.00830000 -0.03000000 0.00000000 -0.05458864 -0.01173797 -0.03674235 -0.02432447 -0.04002224 -O 0.00000000 4.94000000 3.68100000 0.05580000 -0.00000000 0.00000000 -0.00000000 -0.02490000 0.01850000 0.00000000 0.01850000 -0.03090000 0.00000000 0.00000000 0.02616295 -0.03784462 0.00000000 0.05706352 -O 1.63100000 2.47000000 10.79300000 -0.09660000 0.07700000 -0.02320000 0.07700000 0.09600000 0.08860000 -0.02320000 0.08860000 0.00060000 -0.00000000 0.10889444 0.12529932 0.00073485 -0.03280975 -0.13618877 -O 2.95500000 0.17700000 10.79300000 0.14310000 0.00000000 -0.00000000 0.00000000 -0.15400000 -0.12210000 -0.00000000 -0.12210000 0.01100000 -0.00005774 0.00000000 -0.17267548 0.01343137 0.00000000 0.21008142 -O 4.27800000 2.47000000 10.79300000 -0.09660000 -0.07700000 0.02320000 -0.07700000 0.09600000 0.08860000 0.02320000 0.08860000 0.00060000 -0.00000000 -0.10889444 0.12529932 0.00073485 0.03280975 -0.13618877 -O -1.63100000 4.35300000 8.50600000 -0.00240000 0.01250000 -0.01810000 0.01250000 -0.00260000 0.01610000 -0.01810000 0.01610000 0.00490000 0.00005774 0.01767767 0.02276884 0.00604207 -0.02559727 0.00014142 -O 1.63100000 4.35300000 8.50600000 -0.00240000 -0.01250000 0.01810000 -0.01250000 -0.00260000 0.01610000 0.01810000 0.01610000 0.00490000 0.00005774 -0.01767767 0.02276884 0.00604207 0.02559727 0.00014142 -O 0.00000000 1.52800000 8.50600000 0.00630000 0.00000000 -0.00000000 0.00000000 -0.01950000 -0.02540000 -0.00000000 -0.02540000 0.01310000 0.00005774 0.00000000 -0.03592102 0.01608498 0.00000000 0.01824335 -Ti 0.00000000 3.41200000 2.41200000 0.09630000 -0.00000000 -0.00000000 -0.00000000 0.09510000 0.00250000 -0.00000000 0.00250000 -0.19140000 0.00000000 0.00000000 0.00353553 -0.23441617 0.00000000 0.00084853 -Ti 2.95500000 0.00000000 0.00000000 0.06460000 0.00000000 0.00000000 0.00000000 -0.11790000 0.19880000 0.00000000 0.19880000 0.05330000 0.00000000 0.00000000 0.28114566 0.06527890 0.00000000 0.12904699 -Ti -1.47700000 2.55900000 0.00000000 -0.06130000 -0.07840000 -0.15010000 -0.07840000 0.00830000 -0.10970000 -0.15010000 -0.10970000 0.05300000 -0.00000000 -0.11087434 -0.15513923 0.06491148 -0.21227346 -0.04921463 -Ti 1.47700000 2.55900000 0.00000000 -0.06130000 0.07840000 0.15010000 0.07840000 0.00830000 -0.10970000 0.15010000 -0.10970000 0.05300000 -0.00000000 0.11087434 -0.15513923 0.06491148 0.21227346 -0.04921463 -Ti 0.00000000 1.70600000 4.82500000 0.09280000 -0.00000000 -0.00000000 -0.00000000 -0.14630000 0.15500000 -0.00000000 0.15500000 0.05340000 0.00005774 0.00000000 0.21920310 0.06544220 0.00000000 0.16906923 -Ti 1.47700000 4.26500000 4.82500000 -0.08660000 -0.10350000 -0.13400000 -0.10350000 0.03290000 -0.07740000 -0.13400000 -0.07740000 0.05360000 0.00005774 -0.14637110 -0.10946013 0.06568715 -0.18950462 -0.08449926 -Ti -1.47700000 4.26500000 4.82500000 -0.08660000 0.10350000 0.13400000 0.10350000 0.03290000 -0.07740000 0.13400000 -0.07740000 0.05360000 0.00005774 0.14637110 -0.10946013 0.06568715 0.18950462 -0.08449926 -Ti 2.95500000 3.41200000 9.65000000 0.11190000 0.00000000 -0.00000000 0.00000000 -0.14660000 0.27080000 -0.00000000 0.27080000 0.03470000 0.00000000 0.00000000 0.38296903 0.04249865 0.00000000 0.18278710 -Ti 1.47700000 0.85300000 9.65000000 -0.06880000 -0.11260000 -0.21090000 -0.11260000 0.03300000 -0.14660000 -0.21090000 -0.14660000 0.03580000 -0.00000000 -0.15924045 -0.20732371 0.04384587 -0.29825764 -0.07198347 -Ti 4.43200000 0.85300000 9.65000000 -0.06880000 0.11260000 0.21090000 0.11260000 0.03300000 -0.14660000 0.21090000 -0.14660000 0.03580000 -0.00000000 0.15924045 -0.20732371 0.04384587 0.29825764 -0.07198347 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00690000 0.00000000 -0.00000000 0.00000000 0.00690000 -0.00000000 -0.00000000 -0.00000000 -0.01380000 0.00000000 0.00000000 0.00000000 -0.01690148 0.00000000 0.00000000 -Li 0.00000000 0.00000000 12.66500000 0.00710000 -0.00000000 -0.00000000 -0.00000000 0.00710000 -0.00000000 -0.00000000 -0.00000000 -0.01420000 0.00000000 0.00000000 0.00000000 -0.01739138 0.00000000 0.00000000 -Li 2.95500000 1.70600000 6.63400000 0.00710000 0.00000000 0.00000000 0.00000000 0.00710000 -0.00000000 0.00000000 -0.00000000 -0.01420000 0.00000000 0.00000000 0.00000000 -0.01739138 0.00000000 0.00000000 -Li 2.95500000 1.70600000 3.01600000 0.00690000 0.00000000 -0.00000000 0.00000000 0.00690000 -0.00000000 -0.00000000 -0.00000000 -0.01380000 0.00000000 0.00000000 0.00000000 -0.01690148 0.00000000 0.00000000 -Li 0.00000000 3.41200000 11.45900000 -0.00000000 -0.00000000 0.00000000 -0.00000000 -0.00000000 -0.00000000 0.00000000 -0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 -Li 0.00000000 3.41200000 7.84000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 -0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 -Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 -0.00000000 -0.00000000 0.00670000 -0.00000000 -0.00000000 -0.00000000 -0.01340000 0.00000000 0.00000000 0.00000000 -0.01641158 0.00000000 0.00000000 -Li 2.95500000 1.70600000 12.06200000 0.00670000 0.00000000 0.00000000 0.00000000 0.00670000 -0.00000000 0.00000000 -0.00000000 -0.01340000 0.00000000 0.00000000 0.00000000 -0.01641158 0.00000000 0.00000000 -O 0.00000000 0.00000000 3.80700000 0.07680000 0.00000000 0.00000000 0.00000000 0.07680000 -0.00000000 0.00000000 -0.00000000 -0.15350000 -0.00005774 0.00000000 0.00000000 -0.18803916 0.00000000 0.00000000 -O 0.00000000 0.00000000 10.66800000 0.04940000 -0.00000000 -0.00000000 -0.00000000 0.04940000 0.00000000 -0.00000000 0.00000000 -0.09880000 0.00000000 0.00000000 0.00000000 -0.12100479 0.00000000 0.00000000 -O 2.95500000 1.70600000 8.63200000 0.04940000 -0.00000000 -0.00000000 -0.00000000 0.04940000 -0.00000000 -0.00000000 -0.00000000 -0.09880000 0.00000000 0.00000000 0.00000000 -0.12100479 0.00000000 0.00000000 -O 2.95500000 1.70600000 1.01800000 0.07680000 -0.00000000 0.00000000 -0.00000000 0.07680000 0.00000000 0.00000000 0.00000000 -0.15350000 -0.00005774 0.00000000 0.00000000 -0.18803916 0.00000000 0.00000000 -O 0.00000000 3.41200000 13.45700000 0.01120000 0.00000000 0.00000000 0.00000000 0.01120000 0.00000000 0.00000000 0.00000000 -0.02250000 0.00005774 0.00000000 0.00000000 -0.02751593 0.00000000 0.00000000 -O 0.00000000 3.41200000 5.84300000 0.01120000 -0.00000000 0.00000000 -0.00000000 0.01120000 0.00000000 0.00000000 0.00000000 -0.02250000 0.00005774 0.00000000 0.00000000 -0.02751593 0.00000000 0.00000000 -O -1.32400000 4.17600000 1.14400000 -0.00680000 0.03280000 -0.00350000 0.03280000 0.03110000 0.00200000 -0.00350000 0.00200000 -0.02430000 0.00000000 0.04638620 0.00282843 -0.02976130 -0.00494975 -0.02679935 -O 0.00000000 1.88300000 1.14400000 0.05010000 0.00000000 0.00000000 0.00000000 -0.02580000 -0.00400000 0.00000000 -0.00400000 -0.02430000 0.00000000 0.00000000 -0.00565685 -0.02976130 0.00000000 0.05366940 -O 1.32400000 4.17600000 1.14400000 -0.00680000 -0.03280000 0.00350000 -0.03280000 0.03110000 0.00200000 0.00350000 0.00200000 -0.02430000 0.00000000 -0.04638620 0.00282843 -0.02976130 0.00494975 -0.02679935 -O 4.27800000 0.94200000 13.33100000 -0.12020000 0.10760000 -0.06910000 0.10760000 0.00400000 0.03990000 -0.06910000 0.03990000 0.11630000 -0.00005774 0.15216938 0.05642712 0.14239700 -0.09772216 -0.08782266 -O 1.63100000 0.94200000 13.33100000 -0.12020000 -0.10760000 0.06910000 -0.10760000 0.00400000 0.03990000 0.06910000 0.03990000 0.11630000 -0.00005774 -0.15216938 0.05642712 0.14239700 0.09772216 -0.08782266 -O 2.95500000 3.23400000 13.33100000 0.06610000 -0.00000000 -0.00000000 -0.00000000 -0.18240000 -0.07970000 -0.00000000 -0.07970000 0.11630000 -0.00000000 0.00000000 -0.11271282 0.14243783 0.00000000 0.17571604 -O 4.58600000 0.76400000 5.96800000 -0.12020000 0.10760000 -0.06910000 0.10760000 0.00400000 0.03990000 -0.06910000 0.03990000 0.11630000 -0.00005774 0.15216938 0.05642712 0.14239700 -0.09772216 -0.08782266 -O 2.95500000 3.58900000 5.96800000 0.06610000 0.00000000 0.00000000 0.00000000 -0.18240000 -0.07970000 0.00000000 -0.07970000 0.11630000 -0.00000000 0.00000000 -0.11271282 0.14243783 0.00000000 0.17571604 -O 1.32400000 0.76400000 5.96800000 -0.12020000 -0.10760000 0.06910000 -0.10760000 0.00400000 0.03990000 0.06910000 0.03990000 0.11630000 -0.00005774 -0.15216938 0.05642712 0.14239700 0.09772216 -0.08782266 -O 1.32400000 2.64800000 3.68100000 -0.00680000 0.03280000 -0.00350000 0.03280000 0.03110000 0.00200000 -0.00350000 0.00200000 -0.02430000 0.00000000 0.04638620 0.00282843 -0.02976130 -0.00494975 -0.02679935 -O -1.32400000 2.64800000 3.68100000 -0.00680000 -0.03280000 0.00350000 -0.03280000 0.03110000 0.00200000 0.00350000 0.00200000 -0.02430000 0.00000000 -0.04638620 0.00282843 -0.02976130 0.00494975 -0.02679935 -O 0.00000000 4.94000000 3.68100000 0.05010000 -0.00000000 -0.00000000 -0.00000000 -0.02580000 -0.00400000 -0.00000000 -0.00400000 -0.02430000 0.00000000 0.00000000 -0.00565685 -0.02976130 0.00000000 0.05366940 -O 1.63100000 2.47000000 10.79300000 -0.03620000 0.06170000 -0.03450000 0.06170000 0.03510000 0.01990000 -0.03450000 0.01990000 0.00100000 0.00005774 0.08725698 0.02814285 0.00126557 -0.04879037 -0.05041671 -O 2.95500000 0.17700000 10.79300000 0.07080000 0.00000000 0.00000000 0.00000000 -0.07180000 -0.03990000 0.00000000 -0.03990000 0.00100000 0.00000000 0.00000000 -0.05642712 0.00122474 0.00000000 0.10083343 -O 4.27800000 2.47000000 10.79300000 -0.03620000 -0.06170000 0.03450000 -0.06170000 0.03510000 0.01990000 0.03450000 0.01990000 0.00100000 0.00005774 -0.08725698 0.02814285 0.00126557 0.04879037 -0.05041671 -O -1.63100000 4.35300000 8.50600000 -0.03610000 0.06170000 -0.03450000 0.06170000 0.03510000 0.01990000 -0.03450000 0.01990000 0.00100000 0.00000000 0.08725698 0.02814285 0.00122474 -0.04879037 -0.05034600 -O 1.63100000 4.35300000 8.50600000 -0.03610000 -0.06170000 0.03450000 -0.06170000 0.03510000 0.01990000 0.03450000 0.01990000 0.00100000 0.00000000 -0.08725698 0.02814285 0.00122474 0.04879037 -0.05034600 -O 0.00000000 1.52800000 8.50600000 0.07080000 0.00000000 -0.00000000 0.00000000 -0.07180000 -0.03990000 -0.00000000 -0.03990000 0.00100000 0.00000000 0.00000000 -0.05642712 0.00122474 0.00000000 0.10083343 -Ti 0.00000000 3.41200000 2.41200000 0.07430000 -0.00000000 -0.00000000 -0.00000000 0.07430000 -0.00000000 -0.00000000 -0.00000000 -0.14850000 -0.00005774 0.00000000 0.00000000 -0.18191544 0.00000000 0.00000000 -Ti 2.95500000 0.00000000 0.00000000 0.06450000 -0.00000000 -0.00000000 -0.00000000 -0.10710000 0.16310000 -0.00000000 0.16310000 0.04260000 0.00000000 0.00000000 0.23065823 0.05217413 0.00000000 0.12133952 -Ti -1.47700000 2.55900000 0.00000000 -0.06420000 -0.07430000 -0.14130000 -0.07430000 0.02160000 -0.08160000 -0.14130000 -0.08160000 0.04260000 -0.00000000 -0.10507607 -0.11539983 0.05217413 -0.19982838 -0.06066976 -Ti 1.47700000 2.55900000 0.00000000 -0.06420000 0.07430000 0.14130000 0.07430000 0.02160000 -0.08160000 0.14130000 -0.08160000 0.04260000 -0.00000000 0.10507607 -0.11539983 0.05217413 0.19982838 -0.06066976 -Ti 0.00000000 1.70600000 4.82500000 0.06450000 0.00000000 -0.00000000 0.00000000 -0.10710000 0.16310000 -0.00000000 0.16310000 0.04260000 0.00000000 0.00000000 0.23065823 0.05217413 0.00000000 0.12133952 -Ti 1.47700000 4.26500000 4.82500000 -0.06420000 -0.07430000 -0.14130000 -0.07430000 0.02160000 -0.08160000 -0.14130000 -0.08160000 0.04260000 -0.00000000 -0.10507607 -0.11539983 0.05217413 -0.19982838 -0.06066976 -Ti -1.47700000 4.26500000 4.82500000 -0.06420000 0.07430000 0.14130000 0.07430000 0.02160000 -0.08160000 0.14130000 -0.08160000 0.04260000 -0.00000000 0.10507607 -0.11539983 0.05217413 0.19982838 -0.06066976 -Ti 2.95500000 3.41200000 9.65000000 0.08050000 -0.00000000 -0.00000000 -0.00000000 -0.11570000 0.24470000 -0.00000000 0.24470000 0.03520000 -0.00000000 0.00000000 0.34605806 0.04311102 0.00000000 0.13873435 -Ti 1.47700000 0.85300000 9.65000000 -0.06660000 -0.08500000 -0.21190000 -0.08500000 0.03150000 -0.12230000 -0.21190000 -0.12230000 0.03520000 -0.00005774 -0.12020815 -0.17295832 0.04307019 -0.29967185 -0.06936718 -Ti 4.43200000 0.85300000 9.65000000 -0.06660000 0.08500000 0.21190000 0.08500000 0.03150000 -0.12230000 0.21190000 -0.12230000 0.03520000 -0.00005774 0.12020815 -0.17295832 0.04307019 0.29967185 -0.06936718 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00210000 -0.00000000 0.00000000 -0.00000000 0.00430000 -0.00140000 0.00000000 -0.00140000 -0.00220000 0.00000000 0.00000000 -0.00197990 -0.00269444 0.00000000 -0.00452548 -Li 0.00000000 0.00000000 12.66500000 -0.00330000 -0.00000000 0.00000000 -0.00000000 0.00340000 -0.00140000 0.00000000 -0.00140000 -0.00010000 -0.00000000 0.00000000 -0.00197990 -0.00012247 0.00000000 -0.00473762 -Li 2.95500000 1.70600000 6.63400000 0.00240000 -0.00000000 0.00000000 -0.00000000 0.00330000 0.00000000 0.00000000 0.00000000 -0.00570000 0.00000000 0.00000000 0.00000000 -0.00698105 0.00000000 -0.00063640 -Li 2.95500000 1.70600000 3.01600000 0.01200000 -0.00000000 -0.00000000 -0.00000000 0.01140000 0.00030000 -0.00000000 0.00030000 -0.02340000 0.00000000 0.00000000 0.00042426 -0.02865903 0.00000000 0.00042426 -Li 0.00000000 3.41200000 9.65000000 -0.00810000 -0.00000000 0.00000000 -0.00000000 -0.00700000 0.00010000 0.00000000 0.00010000 0.01510000 -0.00000000 0.00000000 0.00014142 0.01849365 0.00000000 -0.00077782 -Li 0.00000000 3.41200000 7.84000000 -0.01400000 -0.00000000 -0.00000000 -0.00000000 -0.01320000 0.00000000 -0.00000000 0.00000000 0.02720000 0.00000000 0.00000000 0.00000000 0.03331306 0.00000000 -0.00056569 -Li 0.00000000 0.00000000 7.23700000 0.00620000 0.00000000 -0.00000000 0.00000000 0.00720000 0.00000000 -0.00000000 0.00000000 -0.01340000 0.00000000 0.00000000 0.00000000 -0.01641158 0.00000000 -0.00070711 -Li 2.95500000 0.00000000 0.00000000 0.00650000 0.00000000 0.00000000 0.00000000 0.00510000 -0.00280000 0.00000000 -0.00280000 -0.01160000 0.00000000 0.00000000 -0.00395980 -0.01420704 0.00000000 0.00098995 -O 0.00000000 0.00000000 3.80700000 0.07340000 -0.00000000 0.00000000 -0.00000000 0.08600000 -0.00470000 0.00000000 -0.00470000 -0.15940000 0.00000000 0.00000000 -0.00664680 -0.19522433 0.00000000 -0.00890955 -O 0.00000000 0.00000000 10.66800000 0.05990000 -0.00000000 0.00000000 -0.00000000 0.07290000 -0.00380000 0.00000000 -0.00380000 -0.13280000 0.00000000 0.00000000 -0.00537401 -0.16264612 0.00000000 -0.00919239 -O 2.95500000 1.70600000 8.63200000 0.05050000 -0.00000000 0.00000000 -0.00000000 0.06030000 -0.00120000 0.00000000 -0.00120000 -0.11080000 0.00000000 0.00000000 -0.00169706 -0.13570173 0.00000000 -0.00692965 -O 2.95500000 1.70600000 1.01800000 0.09010000 -0.00000000 -0.00000000 -0.00000000 -0.03430000 -0.14300000 -0.00000000 -0.14300000 -0.05580000 0.00000000 0.00000000 -0.20223254 -0.06834076 0.00000000 0.08796408 -O 0.00000000 3.41200000 13.45700000 -0.00420000 0.00000000 -0.00000000 0.00000000 0.03730000 -0.06830000 -0.00000000 -0.06830000 -0.03310000 0.00000000 0.00000000 -0.09659079 -0.04053906 0.00000000 -0.02934493 -O 0.00000000 3.41200000 5.84300000 0.02240000 -0.00000000 -0.00000000 -0.00000000 0.03260000 -0.00140000 -0.00000000 -0.00140000 -0.05500000 0.00000000 0.00000000 -0.00197990 -0.06736097 0.00000000 -0.00721249 -O -1.32400000 4.17600000 1.14400000 -0.16030000 -0.08110000 0.15750000 -0.08110000 0.09900000 0.00840000 0.15750000 0.00840000 0.06130000 -0.00000000 -0.11469272 0.01187939 0.07507686 0.22273864 -0.18335279 -O 0.00000000 1.88300000 1.14400000 0.02040000 0.00000000 -0.00000000 0.00000000 -0.08120000 0.09030000 -0.00000000 0.09030000 0.06080000 -0.00000000 0.00000000 0.12770348 0.07446449 0.00000000 0.07184205 -O 1.32400000 4.17600000 1.14400000 -0.16030000 0.08110000 -0.15750000 0.08110000 0.09900000 0.00840000 -0.15750000 0.00840000 0.06130000 -0.00000000 0.11469272 0.01187939 0.07507686 -0.22273864 -0.18335279 -O 4.27800000 0.94200000 13.33100000 -0.14440000 -0.07620000 0.17550000 -0.07620000 0.09840000 -0.01030000 0.17550000 -0.01030000 0.04600000 -0.00000000 -0.10776307 -0.01456640 0.05633826 0.24819448 -0.17168553 -O 1.63100000 0.94200000 13.33100000 -0.14440000 0.07620000 -0.17550000 0.07620000 0.09840000 -0.01030000 -0.17550000 -0.01030000 0.04600000 -0.00000000 0.10776307 -0.01456640 0.05633826 -0.24819448 -0.17168553 -O 2.95500000 3.23400000 13.33100000 -0.01530000 -0.00000000 -0.00000000 -0.00000000 -0.06080000 0.09630000 -0.00000000 0.09630000 0.07600000 0.00005774 0.00000000 0.13618877 0.09312144 0.00000000 0.03217336 -O 4.58600000 0.76400000 5.96800000 -0.12540000 0.12990000 -0.09240000 0.12990000 0.03500000 0.05860000 -0.09240000 0.05860000 0.09040000 -0.00000000 0.18370634 0.08287291 0.11071694 -0.13067333 -0.11341993 -O 2.95500000 3.58900000 5.96800000 0.09770000 -0.00000000 0.00000000 -0.00000000 -0.18990000 -0.10810000 0.00000000 -0.10810000 0.09220000 0.00000000 0.00000000 -0.15287649 0.11292148 0.00000000 0.20336391 -O 1.32400000 0.76400000 5.96800000 -0.12540000 -0.12990000 0.09240000 -0.12990000 0.03500000 0.05860000 0.09240000 0.05860000 0.09040000 -0.00000000 -0.18370634 0.08287291 0.11071694 0.13067333 -0.11341993 -O 1.32400000 2.64800000 3.68100000 -0.02630000 0.04940000 -0.01910000 0.04940000 0.05690000 0.03720000 -0.01910000 0.03720000 -0.03060000 0.00000000 0.06986215 0.05260874 -0.03747719 -0.02701148 -0.05883128 -O -1.32400000 2.64800000 3.68100000 -0.02630000 -0.04940000 0.01910000 -0.04940000 0.05690000 0.03720000 0.01910000 0.03720000 -0.03060000 0.00000000 -0.06986215 0.05260874 -0.03747719 0.02701148 -0.05883128 -O 0.00000000 4.94000000 3.68100000 0.06780000 0.00000000 -0.00000000 0.00000000 -0.02870000 -0.02640000 -0.00000000 -0.02640000 -0.03910000 0.00000000 0.00000000 -0.03733524 -0.04788752 0.00000000 0.06823580 -O 1.63100000 2.47000000 10.79300000 -0.02620000 0.02030000 0.05700000 0.02030000 0.02350000 -0.01210000 0.05700000 -0.01210000 0.00270000 -0.00000000 0.02870854 -0.01711198 0.00330681 0.08061017 -0.03514321 -O 2.95500000 0.17700000 10.79300000 0.01790000 0.00000000 0.00000000 0.00000000 -0.01340000 0.05900000 0.00000000 0.05900000 -0.00440000 -0.00005774 0.00000000 0.08343860 -0.00542970 0.00000000 0.02213244 -O 4.27800000 2.47000000 10.79300000 -0.02620000 -0.02030000 -0.05700000 -0.02030000 0.02350000 -0.01210000 -0.05700000 -0.01210000 0.00270000 -0.00000000 -0.02870854 -0.01711198 0.00330681 -0.08061017 -0.03514321 -O -1.63100000 4.35300000 8.50600000 -0.04310000 0.04460000 0.00120000 0.04460000 0.01780000 0.00460000 0.00120000 0.00460000 0.02530000 -0.00000000 0.06307392 0.00650538 0.03098605 0.00169706 -0.04306280 -O 1.63100000 4.35300000 8.50600000 -0.04310000 -0.04460000 -0.00120000 -0.04460000 0.01780000 0.00460000 -0.00120000 0.00460000 0.02530000 -0.00000000 -0.06307392 0.00650538 0.03098605 -0.00169706 -0.04306280 -O 0.00000000 1.52800000 8.50600000 0.03080000 -0.00000000 -0.00000000 -0.00000000 -0.05850000 0.00050000 -0.00000000 0.00050000 0.02770000 0.00000000 0.00000000 0.00070711 0.03392543 0.00000000 0.06314464 -Ti 2.95500000 1.70600000 12.06200000 0.07310000 -0.00000000 -0.00000000 -0.00000000 0.16220000 0.07700000 -0.00000000 0.07700000 -0.23520000 -0.00005774 0.00000000 0.10889444 -0.28810082 0.00000000 -0.06300321 -Ti 0.00000000 3.41200000 2.41200000 0.07490000 -0.00000000 -0.00000000 -0.00000000 0.17470000 0.08310000 -0.00000000 0.08310000 -0.24960000 0.00000000 0.00000000 0.11752115 -0.30569632 0.00000000 -0.07056926 -Ti -1.47700000 2.55900000 0.00000000 -0.10490000 -0.26660000 0.06490000 -0.26660000 -0.02130000 -0.05100000 0.06490000 -0.05100000 0.12620000 -0.00000000 -0.37702934 -0.07212489 0.15456280 0.09178246 -0.05911413 -Ti 1.47700000 2.55900000 0.00000000 -0.10490000 0.26660000 -0.06490000 0.26660000 -0.02130000 -0.05100000 -0.06490000 -0.05100000 0.12620000 -0.00000000 0.37702934 -0.07212489 0.15456280 -0.09178246 -0.05911413 -Ti 0.00000000 1.70600000 4.82500000 0.07960000 0.00000000 -0.00000000 0.00000000 -0.12150000 0.21180000 -0.00000000 0.21180000 0.04190000 -0.00000000 0.00000000 0.29953043 0.05131681 0.00000000 0.14219917 -Ti 1.47700000 4.26500000 4.82500000 -0.07460000 -0.08760000 -0.18150000 -0.08760000 0.03070000 -0.10310000 -0.18150000 -0.10310000 0.04390000 -0.00000000 -0.12388511 -0.14580542 0.05376630 -0.25667976 -0.07445834 -Ti -1.47700000 4.26500000 4.82500000 -0.07460000 0.08760000 0.18150000 0.08760000 0.03070000 -0.10310000 0.18150000 -0.10310000 0.04390000 -0.00000000 0.12388511 -0.14580542 0.05376630 0.25667976 -0.07445834 -Ti 2.95500000 3.41200000 9.65000000 0.10160000 -0.00000000 -0.00000000 -0.00000000 -0.13590000 0.18660000 -0.00000000 0.18660000 0.03430000 -0.00000000 0.00000000 0.26389225 0.04200875 0.00000000 0.16793786 -Ti 1.47700000 0.85300000 9.65000000 -0.07960000 -0.10460000 -0.15990000 -0.10460000 0.04400000 -0.09060000 -0.15990000 -0.09060000 0.03560000 0.00000000 -0.14792674 -0.12812775 0.04360092 -0.22613275 -0.08739840 -Ti 4.43200000 0.85300000 9.65000000 -0.07960000 0.10460000 0.15990000 0.10460000 0.04400000 -0.09060000 0.15990000 -0.09060000 0.03560000 0.00000000 0.14792674 -0.12812775 0.04360092 0.22613275 -0.08739840 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00150000 0.00010000 0.00000000 0.00010000 0.00440000 -0.00160000 0.00000000 -0.00160000 -0.00290000 0.00000000 0.00014142 -0.00226274 -0.00355176 0.00000000 -0.00417193 -Li 0.00000000 0.00000000 12.66500000 0.01570000 -0.02150000 -0.01490000 -0.02150000 -0.00140000 0.00680000 -0.01490000 0.00680000 -0.01430000 0.00000000 -0.03040559 0.00961665 -0.01751385 -0.02107178 0.01209153 -Li 2.95500000 1.70600000 6.63400000 0.00330000 -0.00000000 -0.00010000 -0.00000000 0.00330000 0.00000000 -0.00010000 0.00000000 -0.00660000 0.00000000 0.00000000 0.00000000 -0.00808332 -0.00014142 0.00000000 -Li 2.95500000 1.70600000 3.01600000 0.01130000 -0.00000000 -0.00010000 -0.00000000 0.00970000 0.00040000 -0.00010000 0.00040000 -0.02090000 -0.00005774 0.00000000 0.00056569 -0.02563799 -0.00014142 0.00113137 -Li -1.47700000 4.26500000 12.06200000 0.01820000 -0.01350000 -0.01480000 -0.01350000 -0.00110000 0.00330000 -0.01480000 0.00330000 -0.01700000 -0.00005774 -0.01909188 0.00466690 -0.02086149 -0.02093036 0.01364716 -Li 0.00000000 3.41200000 7.84000000 0.00290000 0.00010000 0.00000000 0.00010000 0.00300000 -0.00000000 0.00000000 -0.00000000 -0.00600000 0.00005774 0.00014142 0.00000000 -0.00730764 0.00000000 -0.00007071 -Li 0.00000000 0.00000000 7.23700000 0.00680000 -0.00000000 0.00010000 -0.00000000 0.00680000 -0.00000000 0.00010000 -0.00000000 -0.01360000 0.00000000 0.00000000 0.00000000 -0.01665653 0.00014142 0.00000000 -Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00010000 0.00180000 -0.00010000 0.00390000 -0.00140000 0.00180000 -0.00140000 -0.01010000 0.00000000 -0.00014142 -0.00197990 -0.01236992 0.00254558 0.00162635 -O 0.00000000 0.00000000 3.80700000 0.07760000 -0.00100000 0.00040000 -0.00100000 0.07950000 -0.00590000 0.00040000 -0.00590000 -0.15710000 0.00000000 -0.00141421 -0.00834386 -0.19240742 0.00056569 -0.00134350 -O 0.00000000 0.00000000 10.66800000 0.08950000 -0.06110000 0.09280000 -0.06110000 0.02450000 -0.06170000 0.09280000 -0.06170000 -0.11390000 -0.00005774 -0.08640845 -0.08725698 -0.13953927 0.13123902 0.04596194 -O 2.95500000 1.70600000 8.63200000 0.03490000 -0.00420000 0.01840000 -0.00420000 0.02940000 -0.01130000 0.01840000 -0.01130000 -0.06430000 0.00000000 -0.00593970 -0.01598061 -0.07875110 0.02602153 0.00388909 -O 2.95500000 1.70600000 1.01800000 0.10120000 -0.00660000 0.01170000 -0.00660000 -0.05480000 -0.19300000 0.01170000 -0.19300000 -0.04640000 0.00000000 -0.00933381 -0.27294322 -0.05682816 0.01654630 0.11030866 -O 0.00000000 3.41200000 13.45700000 0.15350000 -0.04620000 0.07060000 -0.04620000 -0.07130000 -0.21210000 0.07060000 -0.21210000 -0.08220000 0.00000000 -0.06533667 -0.29995470 -0.10067403 0.09984348 0.15895760 -O 0.00000000 3.41200000 5.84300000 0.02600000 -0.00070000 0.00040000 -0.00070000 0.02460000 -0.00120000 0.00040000 -0.00120000 -0.05060000 0.00000000 -0.00098995 -0.00169706 -0.06197209 0.00056569 0.00098995 -O -1.32400000 4.17600000 1.14400000 -0.15110000 -0.08930000 0.17430000 -0.08930000 0.09870000 0.00820000 0.17430000 0.00820000 0.05240000 0.00000000 -0.12628927 0.01159655 0.06417663 0.24649742 -0.17663527 -O 0.00000000 1.88300000 1.14400000 0.03560000 0.00450000 -0.01120000 0.00450000 -0.10140000 0.06960000 -0.01120000 0.06960000 0.06580000 0.00000000 0.00636396 0.09842926 0.08058821 -0.01583919 0.09687363 -O 1.32400000 4.17600000 1.14400000 -0.15600000 0.08700000 -0.17600000 0.08700000 0.11140000 0.02210000 -0.17600000 0.02210000 0.04460000 -0.00000000 0.12303658 0.03125412 0.05462362 -0.24890159 -0.18908035 -O 4.27800000 0.94200000 13.33100000 -0.13820000 -0.09870000 0.16650000 -0.09870000 0.11900000 0.01000000 0.16650000 0.01000000 0.01920000 -0.00000000 -0.13958288 0.01414214 0.02351510 0.23546656 -0.18186786 -O 1.63100000 0.94200000 13.33100000 -0.20680000 0.14230000 -0.16900000 0.14230000 0.15000000 0.13900000 -0.16900000 0.13900000 0.05680000 0.00000000 0.20124259 0.19657569 0.06956551 -0.23900209 -0.25229570 -O 2.95500000 3.23400000 13.33100000 0.02580000 0.08040000 -0.09120000 0.08040000 -0.09170000 0.00020000 -0.09120000 0.00020000 0.06590000 0.00000000 0.11370277 0.00028284 0.08071069 -0.12897628 0.08308505 -O 4.58600000 0.76400000 5.96800000 -0.12510000 0.12520000 -0.08390000 0.12520000 0.01770000 0.05390000 -0.08390000 0.05390000 0.10750000 -0.00005774 0.17705954 0.07622611 0.13161925 -0.11865252 -0.10097485 -O 2.95500000 3.58900000 5.96800000 0.09000000 -0.00240000 -0.00030000 -0.00240000 -0.20120000 -0.09860000 -0.00030000 -0.09860000 0.11120000 -0.00000000 -0.00339411 -0.13944146 0.13619163 -0.00042426 0.20590949 -O 1.32400000 0.76400000 5.96800000 -0.12480000 -0.12830000 0.08400000 -0.12830000 0.01600000 0.05420000 0.08400000 0.05420000 0.10880000 0.00000000 -0.18144360 0.07665038 0.13325224 0.11879394 -0.09956063 -O 1.32400000 2.64800000 3.68100000 -0.02060000 0.04810000 -0.00900000 0.04810000 0.04920000 0.03530000 -0.00900000 0.03530000 -0.02860000 0.00000000 0.06802367 0.04992174 -0.03502770 -0.01272792 -0.04935605 -O -1.32400000 2.64800000 3.68100000 -0.02050000 -0.05120000 0.00880000 -0.05120000 0.04780000 0.03610000 0.00880000 0.03610000 -0.02730000 0.00000000 -0.07240773 0.05105311 -0.03343553 0.01244508 -0.04829539 -O 0.00000000 4.94000000 3.68100000 0.07010000 -0.00210000 -0.00090000 -0.00210000 -0.03630000 -0.01600000 -0.00090000 -0.01600000 -0.03380000 0.00000000 -0.00296985 -0.02262742 -0.04139638 -0.00127279 0.07523616 -O 1.63100000 2.47000000 10.79300000 -0.04470000 0.07660000 -0.00610000 0.07660000 0.05610000 0.03610000 -0.00610000 0.03610000 -0.01140000 -0.00000000 0.10832876 0.05105311 -0.01396209 -0.00862670 -0.07127636 -O 2.95500000 0.17700000 10.79300000 0.12860000 0.05390000 -0.07390000 0.05390000 -0.11990000 -0.02820000 -0.07390000 -0.02820000 -0.00870000 0.00000000 0.07622611 -0.03988082 -0.01065528 -0.10451038 0.17571604 -O 4.27800000 2.47000000 10.79300000 -0.11910000 -0.08780000 -0.01730000 -0.08780000 0.12000000 0.10920000 -0.01730000 0.10920000 -0.00090000 0.00000000 -0.12416795 0.15443212 -0.00110227 -0.02446589 -0.16906923 -O -1.63100000 4.35300000 8.50600000 -0.13860000 0.13510000 -0.09340000 0.13510000 0.01630000 0.05930000 -0.09340000 0.05930000 0.12230000 -0.00000000 0.19106025 0.08386286 0.14978630 -0.13208755 -0.10953084 -O 1.63100000 4.35300000 8.50600000 -0.14140000 -0.14550000 0.11350000 -0.14550000 0.02860000 0.08550000 0.11350000 0.08550000 0.11290000 -0.00005774 -0.20576807 0.12091526 0.13823287 0.16051324 -0.12020815 -O 0.00000000 1.52800000 8.50600000 0.11020000 0.00140000 -0.01270000 0.00140000 -0.22490000 -0.13980000 -0.01270000 -0.13980000 0.11460000 0.00005774 0.00197990 -0.19770706 0.14039659 -0.01796051 0.23695148 -Ti 2.95500000 1.70600000 12.06200000 0.07090000 0.03340000 -0.03960000 0.03340000 0.19730000 0.10880000 -0.03960000 0.10880000 -0.26820000 0.00000000 0.04723473 0.15386644 -0.32847657 -0.05600286 -0.08937830 -Ti 0.00000000 3.41200000 2.41200000 0.07860000 0.00040000 -0.00170000 0.00040000 0.17360000 0.08570000 -0.00170000 0.08570000 -0.25220000 0.00000000 0.00056569 0.12119810 -0.30888066 -0.00240416 -0.06717514 -Ti -1.47700000 2.55900000 0.00000000 -0.07580000 -0.27240000 0.06130000 -0.27240000 -0.04190000 -0.09500000 0.06130000 -0.09500000 0.11770000 0.00000000 -0.38523177 -0.13435029 0.14415247 0.08669129 -0.02397092 -Ti 1.47700000 2.55900000 0.00000000 -0.08610000 0.26890000 -0.06970000 0.26890000 -0.02210000 -0.06920000 -0.06970000 -0.06920000 0.10820000 -0.00000000 0.38028203 -0.09786358 0.13251740 -0.09857069 -0.04525483 -Ti 0.00000000 1.70600000 4.82500000 0.08060000 -0.00010000 0.00010000 -0.00010000 -0.12570000 0.20710000 0.00010000 0.20710000 0.04510000 -0.00000000 -0.00014142 0.29288363 0.05523599 0.00014142 0.14587613 -Ti 1.47700000 4.26500000 4.82500000 -0.07670000 -0.08910000 -0.17730000 -0.08910000 0.03020000 -0.10010000 -0.17730000 -0.10010000 0.04650000 0.00000000 -0.12600643 -0.14156278 0.05695064 -0.25074006 -0.07558971 -Ti -1.47700000 4.26500000 4.82500000 -0.07680000 0.08910000 0.17730000 0.08910000 0.03030000 -0.10000000 0.17730000 -0.10000000 0.04650000 -0.00000000 0.12600643 -0.14142136 0.05695064 0.25074006 -0.07573114 -Ti 2.95500000 3.41200000 9.65000000 0.06620000 -0.01150000 0.02180000 -0.01150000 -0.12100000 0.24240000 0.02180000 0.24240000 0.05490000 -0.00005774 -0.01626346 0.34280537 0.06719767 0.03082986 0.13237039 -Ti 1.47700000 0.85300000 9.65000000 -0.06690000 -0.08580000 -0.19690000 -0.08580000 0.01040000 -0.13570000 -0.19690000 -0.13570000 0.05650000 -0.00000000 -0.12133952 -0.19190878 0.06919809 -0.27845865 -0.05465935 -Ti 4.43200000 0.85300000 9.65000000 -0.07220000 0.07660000 0.19320000 0.07660000 0.02140000 -0.10830000 0.19320000 -0.10830000 0.05080000 -0.00000000 0.10832876 -0.15315933 0.06221704 0.27322606 -0.06618519 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00160000 -0.00000000 0.00000000 -0.00000000 0.00510000 -0.00160000 0.00000000 -0.00160000 -0.00350000 0.00000000 0.00000000 -0.00226274 -0.00428661 0.00000000 -0.00473762 -Li 0.00000000 0.00000000 12.66500000 -0.02020000 -0.00000000 -0.00000000 -0.00000000 0.03470000 -0.01900000 -0.00000000 -0.01900000 -0.01450000 0.00000000 0.00000000 -0.02687006 -0.01775880 0.00000000 -0.03882016 -Li 2.95500000 1.70600000 6.63400000 0.00320000 0.00000000 0.00000000 0.00000000 0.00410000 -0.00010000 0.00000000 -0.00010000 -0.00720000 -0.00005774 0.00000000 -0.00014142 -0.00885899 0.00000000 -0.00063640 -Li 2.95500000 1.70600000 3.01600000 0.01120000 0.00000000 -0.00000000 0.00000000 0.01060000 0.00020000 -0.00000000 0.00020000 -0.02180000 0.00000000 0.00000000 0.00028284 -0.02669944 0.00000000 0.00042426 -Li 0.00000000 1.70600000 12.06200000 -0.01050000 0.00000000 0.00000000 0.00000000 0.02680000 -0.01210000 0.00000000 -0.01210000 -0.01630000 0.00000000 0.00000000 -0.01711198 -0.01996334 0.00000000 -0.02637508 -Li 0.00000000 3.41200000 7.84000000 0.00300000 0.00000000 -0.00000000 0.00000000 0.00370000 0.00000000 -0.00000000 0.00000000 -0.00670000 0.00000000 0.00000000 0.00000000 -0.00820579 0.00000000 -0.00049497 -Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 0.00000000 -0.00000000 0.00760000 0.00010000 0.00000000 0.00010000 -0.01430000 0.00000000 0.00000000 0.00014142 -0.01751385 0.00000000 -0.00063640 -Li 2.95500000 0.00000000 0.00000000 0.00630000 -0.00000000 0.00000000 -0.00000000 0.00590000 -0.00310000 0.00000000 -0.00310000 -0.01220000 0.00000000 0.00000000 -0.00438406 -0.01494189 0.00000000 0.00028284 -O 0.00000000 0.00000000 3.80700000 0.07570000 0.00000000 0.00000000 0.00000000 0.08870000 -0.00570000 0.00000000 -0.00570000 -0.16450000 0.00005774 0.00000000 -0.00806102 -0.20142971 0.00000000 -0.00919239 -O 0.00000000 0.00000000 10.66800000 -0.01270000 -0.00000000 0.00000000 -0.00000000 0.13640000 0.09810000 0.00000000 0.09810000 -0.12380000 0.00005774 0.00000000 0.13873435 -0.15158259 0.00000000 -0.10542962 -O 2.95500000 1.70600000 8.63200000 0.02690000 0.00000000 -0.00000000 0.00000000 0.04500000 0.01980000 -0.00000000 0.01980000 -0.07190000 0.00000000 0.00000000 0.02800143 -0.08805916 0.00000000 -0.01279863 -O 2.95500000 1.70600000 1.01800000 0.09700000 -0.00000000 0.00000000 -0.00000000 -0.04250000 -0.15470000 0.00000000 -0.15470000 -0.05460000 0.00005774 0.00000000 -0.21877884 -0.06683025 0.00000000 0.09864140 -O 0.00000000 3.41200000 13.45700000 0.01950000 0.00000000 -0.00000000 0.00000000 0.06930000 -0.08480000 -0.00000000 -0.08480000 -0.08880000 0.00000000 0.00000000 -0.11992531 -0.10875734 0.00000000 -0.03521392 -O 0.00000000 3.41200000 5.84300000 0.02410000 -0.00000000 -0.00000000 -0.00000000 0.03440000 -0.00070000 -0.00000000 -0.00070000 -0.05850000 0.00000000 0.00000000 -0.00098995 -0.07164757 0.00000000 -0.00728320 -O -1.32400000 4.17600000 1.14400000 -0.15290000 -0.08570000 0.16690000 -0.08570000 0.11610000 0.01150000 0.16690000 0.01150000 0.03680000 0.00000000 -0.12119810 0.01626346 0.04507061 0.23603224 -0.19021172 -O 0.00000000 1.88300000 1.14400000 0.02310000 0.00000000 -0.00000000 0.00000000 -0.08550000 0.08450000 -0.00000000 0.08450000 0.06240000 0.00000000 0.00000000 0.11950105 0.07642408 0.00000000 0.07679180 -O 1.32400000 4.17600000 1.14400000 -0.15290000 0.08570000 -0.16690000 0.08570000 0.11610000 0.01150000 -0.16690000 0.01150000 0.03680000 0.00000000 0.12119810 0.01626346 0.04507061 -0.23603224 -0.19021172 -O 4.27800000 0.94200000 13.33100000 -0.06170000 -0.03790000 0.04600000 -0.03790000 0.02140000 -0.04050000 0.04600000 -0.04050000 0.04030000 -0.00000000 -0.05359869 -0.05727565 0.04935722 0.06505382 -0.05876057 -O 1.63100000 0.94200000 13.33100000 -0.06170000 0.03790000 -0.04600000 0.03790000 0.02140000 -0.04050000 -0.04600000 -0.04050000 0.04030000 -0.00000000 0.05359869 -0.05727565 0.04935722 -0.06505382 -0.05876057 -O 2.95500000 3.23400000 13.33100000 0.01890000 -0.00000000 -0.00000000 -0.00000000 -0.05740000 0.06460000 -0.00000000 0.06460000 0.03850000 0.00000000 0.00000000 0.09135820 0.04715268 0.00000000 0.05395225 -O 4.58600000 0.76400000 5.96800000 -0.12710000 0.12500000 -0.08440000 0.12500000 0.02750000 0.05370000 -0.08440000 0.05370000 0.09960000 -0.00000000 0.17677670 0.07594327 0.12198459 -0.11935962 -0.10931871 -O 2.95500000 3.58900000 5.96800000 0.08760000 0.00000000 -0.00000000 0.00000000 -0.18860000 -0.09830000 -0.00000000 -0.09830000 0.10100000 -0.00000000 0.00000000 -0.13901719 0.12369923 0.00000000 0.19530289 -O 1.32400000 0.76400000 5.96800000 -0.12710000 -0.12500000 0.08440000 -0.12500000 0.02750000 0.05370000 0.08440000 0.05370000 0.09960000 -0.00000000 -0.17677670 0.07594327 0.12198459 0.11935962 -0.10931871 -O 1.32400000 2.64800000 3.68100000 -0.02230000 0.04870000 -0.01010000 0.04870000 0.05870000 0.03520000 -0.01010000 0.03520000 -0.03640000 0.00000000 0.06887220 0.04978032 -0.04458071 -0.01428356 -0.05727565 -O -1.32400000 2.64800000 3.68100000 -0.02230000 -0.04870000 0.01010000 -0.04870000 0.05870000 0.03520000 0.01010000 0.03520000 -0.03640000 0.00000000 -0.06887220 0.04978032 -0.04458071 0.01428356 -0.05727565 -O 0.00000000 4.94000000 3.68100000 0.06720000 -0.00000000 -0.00000000 -0.00000000 -0.02460000 -0.01560000 0.00000000 -0.01560000 -0.04260000 0.00000000 0.00000000 -0.02206173 -0.05217413 0.00000000 0.06491240 -O 1.63100000 2.47000000 10.79300000 -0.02010000 0.13610000 -0.05550000 0.13610000 0.03380000 -0.01960000 -0.05550000 -0.01960000 -0.01370000 0.00000000 0.19247447 -0.02771859 -0.01677900 -0.07848885 -0.03811306 -O 2.95500000 0.17700000 10.79300000 0.08860000 0.00000000 0.00000000 0.00000000 -0.06250000 -0.01210000 0.00000000 -0.01210000 -0.02610000 0.00000000 0.00000000 -0.01711198 -0.03196584 0.00000000 0.10684383 -O 4.27800000 2.47000000 10.79300000 -0.02010000 -0.13610000 0.05550000 -0.13610000 0.03380000 -0.01960000 0.05550000 -0.01960000 -0.01370000 0.00000000 -0.19247447 -0.02771859 -0.01677900 0.07848885 -0.03811306 -O -1.63100000 4.35300000 8.50600000 -0.13670000 0.14550000 -0.12540000 0.14550000 0.03300000 0.06310000 -0.12540000 0.06310000 0.10380000 -0.00005774 0.20576807 0.08923688 0.12708769 -0.17734238 -0.11999602 -O 1.63100000 4.35300000 8.50600000 -0.13670000 -0.14550000 0.12540000 -0.14550000 0.03300000 0.06310000 0.12540000 0.06310000 0.10380000 -0.00005774 -0.20576807 0.08923688 0.12708769 0.17734238 -0.11999602 -O 0.00000000 1.52800000 8.50600000 0.09180000 -0.00000000 -0.00000000 -0.00000000 -0.20800000 -0.10870000 -0.00000000 -0.10870000 0.11620000 -0.00000000 0.00000000 -0.15372501 0.14231535 0.00000000 0.21199061 -Ti 2.95500000 1.70600000 12.06200000 0.12860000 -0.00000000 -0.00000000 -0.00000000 0.13840000 0.03960000 -0.00000000 0.03960000 -0.26690000 -0.00005774 0.00000000 0.05600286 -0.32692523 0.00000000 -0.00692965 -Ti 0.00000000 3.41200000 2.41200000 0.07980000 0.00000000 0.00000000 0.00000000 0.17150000 0.08680000 0.00000000 0.08680000 -0.25130000 0.00000000 0.00000000 0.12275374 -0.30777839 0.00000000 -0.06484169 -Ti -1.47700000 2.55900000 0.00000000 -0.09850000 -0.25810000 0.04340000 -0.25810000 -0.01780000 -0.05560000 0.04340000 -0.05560000 0.11630000 -0.00000000 -0.36500852 -0.07863027 0.14243783 0.06137687 -0.05706352 -Ti 1.47700000 2.55900000 0.00000000 -0.09850000 0.25810000 -0.04340000 0.25810000 -0.01780000 -0.05560000 -0.04340000 -0.05560000 0.11630000 -0.00000000 0.36500852 -0.07863027 0.14243783 -0.06137687 -0.05706352 -Ti 0.00000000 1.70600000 4.82500000 0.08060000 0.00000000 -0.00000000 0.00000000 -0.12570000 0.20720000 -0.00000000 0.20720000 0.04520000 -0.00005774 0.00000000 0.29302505 0.05531764 0.00000000 0.14587613 -Ti 1.47700000 4.26500000 4.82500000 -0.07680000 -0.08900000 -0.17750000 -0.08900000 0.03040000 -0.10000000 -0.17750000 -0.10000000 0.04640000 -0.00000000 -0.12586501 -0.14142136 0.05682816 -0.25102291 -0.07580185 -Ti -1.47700000 4.26500000 4.82500000 -0.07680000 0.08900000 0.17750000 0.08900000 0.03040000 -0.10000000 0.17750000 -0.10000000 0.04640000 -0.00000000 0.12586501 -0.14142136 0.05682816 0.25102291 -0.07580185 -Ti 2.95500000 3.41200000 9.65000000 0.06500000 -0.00000000 -0.00000000 -0.00000000 -0.11490000 0.22520000 -0.00000000 0.22520000 0.04990000 -0.00000000 0.00000000 0.31848089 0.06111477 0.00000000 0.12720851 -Ti 1.47700000 0.85300000 9.65000000 -0.08680000 -0.07510000 -0.21740000 -0.07510000 0.02970000 -0.09830000 -0.21740000 -0.09830000 0.05710000 0.00000000 -0.10620744 -0.13901719 0.06993293 -0.30745003 -0.08237794 -Ti 4.43200000 0.85300000 9.65000000 -0.08680000 0.07510000 0.21740000 0.07510000 0.02970000 -0.09830000 0.21740000 -0.09830000 0.05710000 0.00000000 0.10620744 -0.13901719 0.06993293 0.30745003 -0.08237794 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 0.00000000 -0.01820000 -0.00000000 -0.00000000 -0.00000000 -0.00030000 0.00580000 -0.00000000 0.00580000 0.01850000 0.00000000 0.00000000 0.00820244 0.02265778 0.00000000 -0.01265721 -Li 0.00000000 0.00000000 12.66500000 -0.01940000 -0.00000000 0.00000000 -0.00000000 -0.01220000 -0.00230000 0.00000000 -0.00230000 0.03160000 -0.00000000 0.00000000 -0.00325269 0.03870194 0.00000000 -0.00509117 -Li 2.95500000 1.70600000 6.63400000 0.00090000 -0.00000000 -0.00000000 -0.00000000 0.00180000 -0.00000000 -0.00000000 -0.00000000 -0.00270000 0.00000000 0.00000000 0.00000000 -0.00330681 0.00000000 -0.00063640 -Li 2.95500000 1.70600000 3.01600000 0.00910000 0.00000000 -0.00000000 0.00000000 0.00850000 0.00020000 -0.00000000 0.00020000 -0.01760000 0.00000000 0.00000000 0.00028284 -0.02155551 0.00000000 0.00042426 -Li 0.00000000 3.41200000 11.45900000 0.01190000 -0.00000000 0.00000000 -0.00000000 0.01120000 0.00040000 0.00000000 0.00040000 -0.02310000 0.00000000 0.00000000 0.00056569 -0.02829161 0.00000000 0.00049497 -Li 0.00000000 3.41200000 7.84000000 0.00440000 0.00000000 -0.00000000 0.00000000 0.00520000 0.00000000 -0.00000000 0.00000000 -0.00960000 0.00000000 0.00000000 0.00000000 -0.01175755 0.00000000 -0.00056569 -Li 0.00000000 0.00000000 7.23700000 0.00660000 -0.00000000 0.00000000 -0.00000000 0.00760000 0.00000000 0.00000000 0.00000000 -0.01420000 0.00000000 0.00000000 0.00000000 -0.01739138 0.00000000 -0.00070711 -Li 2.95500000 0.00000000 0.00000000 0.00970000 -0.00000000 -0.00000000 -0.00000000 0.00350000 -0.00200000 -0.00000000 -0.00200000 -0.01320000 0.00000000 0.00000000 -0.00282843 -0.01616663 0.00000000 0.00438406 -O 0.00000000 0.00000000 3.80700000 0.07740000 0.00000000 -0.00000000 0.00000000 0.08880000 -0.00390000 -0.00000000 -0.00390000 -0.16620000 0.00000000 0.00000000 -0.00551543 -0.20355260 0.00000000 -0.00806102 -O 0.00000000 0.00000000 10.66800000 0.07310000 -0.00000000 0.00000000 -0.00000000 0.08660000 -0.00610000 0.00000000 -0.00610000 -0.15970000 0.00000000 0.00000000 -0.00862670 -0.19559176 0.00000000 -0.00954594 -O 2.95500000 1.70600000 8.63200000 0.02200000 0.00000000 -0.00000000 0.00000000 0.03230000 -0.00080000 -0.00000000 -0.00080000 -0.05430000 0.00000000 0.00000000 -0.00113137 -0.06650365 0.00000000 -0.00728320 -O 2.95500000 1.70600000 1.01800000 0.06970000 -0.00000000 -0.00000000 -0.00000000 -0.03640000 -0.19220000 -0.00000000 -0.19220000 -0.03330000 0.00000000 0.00000000 -0.27181185 -0.04078400 0.00000000 0.07502403 -O 0.00000000 3.41200000 13.45700000 0.13080000 0.00000000 -0.00000000 0.00000000 -0.03120000 -0.16810000 -0.00000000 -0.16810000 -0.09970000 0.00005774 0.00000000 -0.23772930 -0.12206624 0.00000000 0.11455130 -O 0.00000000 3.41200000 5.84300000 0.02490000 -0.00000000 0.00000000 -0.00000000 0.03510000 -0.00070000 0.00000000 -0.00070000 -0.06000000 0.00000000 0.00000000 -0.00098995 -0.07348469 0.00000000 -0.00721249 -O -1.32400000 4.17600000 1.14400000 -0.13130000 -0.10430000 0.26110000 -0.10430000 0.09150000 -0.01350000 0.26110000 -0.01350000 0.03980000 -0.00000000 -0.14750247 -0.01909188 0.04874485 0.36925116 -0.15754339 -O 0.00000000 1.88300000 1.14400000 0.00580000 0.00000000 0.00000000 0.00000000 -0.09340000 0.16570000 0.00000000 0.16570000 0.08760000 -0.00000000 0.00000000 0.23433519 0.10728765 0.00000000 0.07014499 -O 1.32400000 4.17600000 1.14400000 -0.13130000 0.10430000 -0.26110000 0.10430000 0.09150000 -0.01350000 -0.26110000 -0.01350000 0.03980000 -0.00000000 0.14750247 -0.01909188 0.04874485 -0.36925116 -0.15754339 -O 4.27800000 0.94200000 13.33100000 -0.06810000 -0.15090000 0.22230000 -0.15090000 0.07060000 -0.01650000 0.22230000 -0.01650000 -0.00250000 -0.00000000 -0.21340483 -0.02333452 -0.00306186 0.31437967 -0.09807571 -O 1.63100000 0.94200000 13.33100000 -0.06810000 0.15090000 -0.22230000 0.15090000 0.07060000 -0.01650000 -0.22230000 -0.01650000 -0.00250000 -0.00000000 0.21340483 -0.02333452 -0.00306186 -0.31437967 -0.09807571 -O 2.95500000 3.23400000 13.33100000 -0.04150000 0.00000000 -0.00000000 0.00000000 0.03000000 0.14460000 -0.00000000 0.14460000 0.01150000 -0.00000000 0.00000000 0.20449528 0.01408457 0.00000000 -0.05055813 -O 4.58600000 0.76400000 5.96800000 -0.12530000 0.13210000 -0.06880000 0.13210000 0.03790000 0.04460000 -0.06880000 0.04460000 0.08740000 -0.00000000 0.18681761 0.06307392 0.10704270 -0.09729789 -0.11539983 -O 2.95500000 3.58900000 5.96800000 0.10240000 0.00000000 0.00000000 0.00000000 -0.19110000 -0.08020000 0.00000000 -0.08020000 0.08870000 -0.00000000 0.00000000 -0.11341993 0.10863487 0.00000000 0.20753584 -O 1.32400000 0.76400000 5.96800000 -0.12530000 -0.13210000 0.06880000 -0.13210000 0.03790000 0.04460000 0.06880000 0.04460000 0.08740000 -0.00000000 -0.18681761 0.06307392 0.10704270 0.09729789 -0.11539983 -O 1.32400000 2.64800000 3.68100000 -0.01770000 0.02610000 0.00820000 0.02610000 0.03420000 0.02560000 0.00820000 0.02560000 -0.01660000 0.00005774 0.03691097 0.03620387 -0.02028994 0.01159655 -0.03669884 -O -1.32400000 2.64800000 3.68100000 -0.01770000 -0.02610000 -0.00820000 -0.02610000 0.03420000 0.02560000 -0.00820000 0.02560000 -0.01660000 0.00005774 -0.03691097 0.03620387 -0.02028994 -0.01159655 -0.03669884 -O 0.00000000 4.94000000 3.68100000 0.03000000 -0.00000000 -0.00000000 -0.00000000 -0.00710000 0.00410000 -0.00000000 0.00410000 -0.02300000 0.00005774 0.00000000 0.00579828 -0.02812831 0.00000000 0.02623366 -O 1.63100000 2.47000000 10.79300000 -0.01770000 0.04210000 0.00400000 0.04210000 0.05840000 0.02750000 0.00400000 0.02750000 -0.04060000 -0.00005774 0.05953839 0.03889087 -0.04976547 0.00565685 -0.05381083 -O 2.95500000 0.17700000 10.79300000 0.06200000 0.00000000 0.00000000 0.00000000 -0.01510000 0.00220000 0.00000000 0.00220000 -0.04680000 -0.00005774 0.00000000 0.00311127 -0.05735888 0.00000000 0.05451793 -O 4.27800000 2.47000000 10.79300000 -0.01770000 -0.04210000 -0.00400000 -0.04210000 0.05840000 0.02750000 -0.00400000 0.02750000 -0.04060000 -0.00005774 -0.05953839 0.03889087 -0.04976547 -0.00565685 -0.05381083 -O -1.63100000 4.35300000 8.50600000 -0.12880000 0.12060000 -0.07740000 0.12060000 0.02110000 0.04980000 -0.07740000 0.04980000 0.10780000 -0.00005774 0.17055416 0.07042784 0.13198667 -0.10946013 -0.10599531 -O 1.63100000 4.35300000 8.50600000 -0.12880000 -0.12060000 0.07740000 -0.12060000 0.02110000 0.04980000 0.07740000 0.04980000 0.10780000 -0.00005774 -0.17055416 0.07042784 0.13198667 0.10946013 -0.10599531 -O 0.00000000 1.52800000 8.50600000 0.07870000 0.00000000 -0.00000000 0.00000000 -0.18750000 -0.09080000 -0.00000000 -0.09080000 0.10880000 -0.00000000 0.00000000 -0.12841059 0.13325224 0.00000000 0.18823183 -Ti 2.95500000 1.70600000 12.06200000 0.07080000 0.00000000 0.00000000 0.00000000 0.17150000 0.08560000 0.00000000 0.08560000 -0.24220000 -0.00005774 0.00000000 0.12105668 -0.29667403 0.00000000 -0.07120565 -Ti 0.00000000 3.41200000 2.41200000 0.08800000 -0.00000000 -0.00000000 -0.00000000 0.17150000 0.08990000 -0.00000000 0.08990000 -0.25960000 0.00005774 0.00000000 0.12713780 -0.31790294 0.00000000 -0.05904342 -Ti -1.47700000 2.55900000 0.00000000 -0.09210000 -0.27610000 0.05540000 -0.27610000 -0.02810000 -0.08060000 0.05540000 -0.08060000 0.12010000 0.00005774 -0.39046436 -0.11398561 0.14713268 0.07834743 -0.04525483 -Ti 1.47700000 2.55900000 0.00000000 -0.09210000 0.27610000 -0.05540000 0.27610000 -0.02810000 -0.08060000 -0.05540000 -0.08060000 0.12010000 0.00005774 0.39046436 -0.11398561 0.14713268 -0.07834743 -0.04525483 -Ti 0.00000000 1.70600000 4.82500000 0.10300000 0.00000000 -0.00000000 0.00000000 -0.14820000 0.18560000 -0.00000000 0.18560000 0.04510000 0.00005774 0.00000000 0.26247804 0.05527682 0.00000000 0.17762522 -Ti 1.47700000 4.26500000 4.82500000 -0.08800000 -0.10820000 -0.15860000 -0.10820000 0.04130000 -0.08910000 -0.15860000 -0.08910000 0.04670000 -0.00000000 -0.15301791 -0.12600643 0.05719559 -0.22429427 -0.09142891 -Ti -1.47700000 4.26500000 4.82500000 -0.08800000 0.10820000 0.15860000 0.10820000 0.04130000 -0.08910000 0.15860000 -0.08910000 0.04670000 -0.00000000 0.15301791 -0.12600643 0.05719559 0.22429427 -0.09142891 -Ti 2.95500000 3.41200000 9.65000000 0.07980000 -0.00000000 -0.00000000 -0.00000000 -0.12660000 0.20230000 -0.00000000 0.20230000 0.04670000 0.00005774 0.00000000 0.28609540 0.05723641 0.00000000 0.14594684 -Ti 1.47700000 0.85300000 9.65000000 -0.07740000 -0.08860000 -0.17320000 -0.08860000 0.02940000 -0.09730000 -0.17320000 -0.09730000 0.04800000 -0.00000000 -0.12529932 -0.13760298 0.05878775 -0.24494179 -0.07551900 -Ti 4.43200000 0.85300000 9.65000000 -0.07740000 0.08860000 0.17320000 0.08860000 0.02940000 -0.09730000 0.17320000 -0.09730000 0.04800000 -0.00000000 0.12529932 -0.13760298 0.05878775 0.24494179 -0.07551900 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 4.43200000 0.85300000 2.41200000 -0.00170000 0.00330000 0.00140000 0.00330000 0.00880000 -0.00410000 0.00140000 -0.00410000 -0.00710000 0.00000000 0.00466690 -0.00579828 -0.00869569 0.00197990 -0.00742462 -Li 0.00000000 0.00000000 12.66500000 -0.00110000 0.00010000 -0.00000000 0.00010000 0.00490000 -0.00160000 -0.00000000 -0.00160000 -0.00380000 0.00000000 0.00014142 -0.00226274 -0.00465403 0.00000000 -0.00424264 -Li 2.95500000 1.70600000 6.63400000 0.00330000 0.00010000 0.00000000 0.00010000 0.00320000 0.00000000 0.00000000 0.00000000 -0.00650000 0.00000000 0.00014142 0.00000000 -0.00796084 0.00000000 0.00007071 -Li 2.95500000 3.41200000 2.41200000 0.00620000 -0.00380000 0.00130000 -0.00380000 -0.00090000 0.00480000 0.00130000 0.00480000 -0.00520000 -0.00005774 -0.00537401 0.00678823 -0.00640950 0.00183848 0.00502046 -Li 0.00000000 3.41200000 11.45900000 0.01150000 -0.00000000 -0.00010000 -0.00000000 0.00990000 0.00030000 -0.00010000 0.00030000 -0.02140000 0.00000000 0.00000000 0.00042426 -0.02620954 -0.00014142 0.00113137 -Li 0.00000000 3.41200000 7.84000000 0.00280000 -0.00000000 -0.00010000 -0.00000000 0.00280000 -0.00000000 -0.00010000 -0.00000000 -0.00570000 0.00005774 0.00000000 0.00000000 -0.00694022 -0.00014142 0.00000000 -Li 0.00000000 0.00000000 7.23700000 0.00690000 -0.00000000 0.00010000 -0.00000000 0.00700000 0.00010000 0.00010000 0.00010000 -0.01390000 0.00000000 0.00000000 0.00014142 -0.01702395 0.00014142 -0.00007071 -Li 2.95500000 0.00000000 0.00000000 0.00480000 0.00010000 0.00190000 0.00010000 0.00520000 -0.00120000 0.00190000 -0.00120000 -0.01000000 0.00000000 0.00014142 -0.00169706 -0.01224745 0.00268701 -0.00028284 -O 0.00000000 0.00000000 3.80700000 0.06740000 -0.05370000 0.07380000 -0.05370000 0.12790000 0.03570000 0.07380000 0.03570000 -0.19530000 0.00000000 -0.07594327 0.05048742 -0.23919267 0.10436896 -0.04277996 -O 0.00000000 0.00000000 10.66800000 0.07710000 -0.00100000 0.00030000 -0.00100000 0.07900000 -0.00580000 0.00030000 -0.00580000 -0.15600000 -0.00005774 -0.00141421 -0.00820244 -0.19110102 0.00042426 -0.00134350 -O 2.95500000 1.70600000 8.63200000 0.02680000 -0.00070000 0.00040000 -0.00070000 0.02540000 -0.00090000 0.00040000 -0.00090000 -0.05220000 0.00000000 -0.00098995 -0.00127279 -0.06393168 0.00056569 0.00098995 -O 2.95500000 1.70600000 1.01800000 0.07230000 -0.04120000 0.08290000 -0.04120000 -0.01950000 -0.10860000 0.08290000 -0.10860000 -0.05280000 0.00000000 -0.05826560 -0.15358359 -0.06466653 0.11723830 0.06491240 -O 0.00000000 3.41200000 13.45700000 0.10720000 -0.00760000 0.01140000 -0.00760000 -0.05420000 -0.16730000 0.01140000 -0.16730000 -0.05300000 0.00000000 -0.01074802 -0.23659793 -0.06491148 0.01612203 0.11412703 -O 0.00000000 3.41200000 5.84300000 0.03140000 -0.00400000 0.01840000 -0.00400000 0.03340000 0.00920000 0.01840000 0.00920000 -0.06480000 0.00000000 -0.00565685 0.01301076 -0.07936347 0.02602153 -0.00141421 -O -1.32400000 4.17600000 1.14400000 -0.14150000 0.00430000 0.08480000 0.00430000 0.08770000 -0.07240000 0.08480000 -0.07240000 0.05380000 -0.00000000 0.00608112 -0.10238906 0.06589127 0.11992531 -0.16206887 -O 0.00000000 1.88300000 1.14400000 0.08180000 0.05950000 -0.08130000 0.05950000 -0.17040000 0.05530000 -0.08130000 0.05530000 0.08860000 -0.00000000 0.08414571 0.07820601 0.10851240 -0.11497556 0.17833233 -O 1.32400000 4.17600000 1.14400000 -0.19500000 0.01440000 -0.09820000 0.01440000 0.09270000 0.03620000 -0.09820000 0.03620000 0.10230000 -0.00000000 0.02036468 0.05119453 0.12529140 -0.13887577 -0.20343462 -O 4.27800000 0.94200000 13.33100000 -0.15590000 -0.07680000 0.15530000 -0.07680000 0.10290000 0.00890000 0.15530000 0.00890000 0.05300000 0.00000000 -0.10861160 0.01258650 0.06491148 0.21962737 -0.18299923 -O 1.63100000 0.94200000 13.33100000 -0.15960000 0.07440000 -0.15610000 0.07440000 0.11470000 0.02150000 -0.15610000 0.02150000 0.04490000 -0.00000000 0.10521749 0.03040559 0.05499104 -0.22075874 -0.19395939 -O 2.95500000 3.23400000 13.33100000 0.03060000 0.00440000 -0.01090000 0.00440000 -0.10600000 0.06250000 -0.01090000 0.06250000 0.07550000 -0.00005774 0.00622254 0.08838835 0.09242741 -0.01541493 0.09659079 -O 4.58600000 0.76400000 5.96800000 -0.13430000 0.14090000 -0.09840000 0.14090000 0.01760000 0.04780000 -0.09840000 0.04780000 0.11670000 0.00000000 0.19926269 0.06759941 0.14292773 -0.13915861 -0.10740952 -O 2.95500000 3.58900000 5.96800000 0.10110000 0.00150000 -0.01260000 0.00150000 -0.22210000 -0.10770000 -0.01260000 -0.10770000 0.12100000 -0.00000000 0.00212132 -0.15231080 0.14819413 -0.01781909 0.22853691 -O 1.32400000 0.76400000 5.96800000 -0.13740000 -0.15240000 0.11860000 -0.15240000 0.03100000 0.07390000 0.11860000 0.07390000 0.10640000 -0.00000000 -0.21552615 0.10451038 0.13031285 0.16772573 -0.11907678 -O 1.32400000 2.64800000 3.68100000 -0.00830000 0.12910000 -0.06370000 0.12910000 0.02490000 -0.01250000 -0.06370000 -0.01250000 -0.01660000 0.00000000 0.18257497 -0.01767767 -0.02033076 -0.09008540 -0.02347595 -O -1.32400000 2.64800000 3.68100000 -0.07640000 -0.13110000 0.05420000 -0.13110000 0.08230000 0.06450000 0.05420000 0.06450000 -0.00580000 -0.00005774 -0.18540340 0.09121677 -0.00714435 0.07665038 -0.11221785 -O 0.00000000 4.94000000 3.68100000 0.12530000 0.05210000 -0.07030000 0.05210000 -0.10340000 -0.04050000 -0.07030000 -0.04050000 -0.02190000 0.00000000 0.07368053 -0.05727565 -0.02682191 -0.09941921 0.16171532 -O 1.63100000 2.47000000 10.79300000 -0.02110000 0.04940000 -0.01370000 0.04940000 0.04890000 0.03610000 -0.01370000 0.03610000 -0.02790000 0.00005774 0.06986215 0.05105311 -0.03412956 -0.01937473 -0.04949747 -O 2.95500000 0.17700000 10.79300000 0.07030000 -0.00210000 -0.00080000 -0.00210000 -0.03770000 -0.01970000 -0.00080000 -0.01970000 -0.03260000 0.00000000 -0.00296985 -0.02786001 -0.03992668 -0.00113137 0.07636753 -O 4.27800000 2.47000000 10.79300000 -0.02090000 -0.05250000 0.01350000 -0.05250000 0.04760000 0.03700000 0.01350000 0.03700000 -0.02670000 0.00000000 -0.07424621 0.05232590 -0.03270069 0.01909188 -0.04843681 -O -1.63100000 4.35300000 8.50600000 -0.12470000 0.12670000 -0.08680000 0.12670000 0.01970000 0.05500000 -0.08680000 0.05500000 0.10490000 0.00005774 0.17918086 0.07778175 0.12851656 -0.12275374 -0.10210622 -O 1.63100000 4.35300000 8.50600000 -0.12430000 -0.12980000 0.08690000 -0.12980000 0.01810000 0.05540000 0.08690000 0.05540000 0.10620000 -0.00000000 -0.18356492 0.07834743 0.13006791 0.12289516 -0.10069201 -O 0.00000000 1.52800000 8.50600000 0.09300000 -0.00240000 -0.00030000 -0.00240000 -0.20140000 -0.10140000 -0.00030000 -0.10140000 0.10830000 0.00005774 -0.00339411 -0.14340126 0.13268069 -0.00042426 0.20817224 -Ti 2.95500000 1.70600000 12.06200000 0.08000000 0.00030000 -0.00150000 0.00030000 0.16950000 0.08490000 -0.00150000 0.08490000 -0.24950000 0.00000000 0.00042426 0.12006673 -0.30557385 -0.00212132 -0.06328606 -Ti 0.00000000 3.41200000 2.41200000 0.11950000 0.03460000 -0.03870000 0.03460000 0.17170000 0.05850000 -0.03870000 0.05850000 -0.29120000 0.00000000 0.04893179 0.08273149 -0.35664571 -0.05473006 -0.03691097 -Ti -1.47700000 2.55900000 0.00000000 -0.08310000 -0.24440000 0.01980000 -0.24440000 -0.04630000 -0.09160000 0.01980000 -0.09160000 0.12940000 -0.00000000 -0.34563379 -0.12954196 0.15848199 0.02800143 -0.02602153 -Ti 1.47700000 2.55900000 0.00000000 -0.08920000 0.24090000 -0.02530000 0.24090000 -0.03040000 -0.06870000 -0.02530000 -0.06870000 0.11960000 -0.00000000 0.34068405 -0.09715647 0.14647949 -0.03577960 -0.04157788 -Ti 0.00000000 1.70600000 4.82500000 0.07510000 -0.01160000 0.02350000 -0.01160000 -0.12510000 0.22120000 0.02350000 0.22120000 0.05000000 -0.00000000 -0.01640488 0.31282404 0.06123724 0.03323402 0.14156278 -Ti 1.47700000 4.26500000 4.82500000 -0.08040000 -0.08840000 -0.20570000 -0.08840000 0.02510000 -0.11660000 -0.20570000 -0.11660000 0.05530000 -0.00000000 -0.12501648 -0.16489730 0.06772839 -0.29090373 -0.07459977 -Ti -1.47700000 4.26500000 4.82500000 -0.08770000 0.08100000 0.20080000 0.08100000 0.03660000 -0.08650000 0.20080000 -0.08650000 0.05110000 0.00000000 0.11455130 -0.12232947 0.06258446 0.28397408 -0.08789337 -Ti 2.95500000 3.41200000 9.65000000 0.08050000 -0.00010000 0.00020000 -0.00010000 -0.12530000 0.20860000 0.00020000 0.20860000 0.04480000 -0.00000000 -0.00014142 0.29500495 0.05486857 0.00028284 0.14552258 -Ti 1.47700000 0.85300000 9.65000000 -0.07630000 -0.08890000 -0.17890000 -0.08890000 0.03050000 -0.10080000 -0.17890000 -0.10080000 0.04580000 0.00000000 -0.12572359 -0.14255273 0.05609332 -0.25300281 -0.07551900 -Ti 4.43200000 0.85300000 9.65000000 -0.07640000 0.08890000 0.17880000 0.08890000 0.03060000 -0.10070000 0.17880000 -0.10070000 0.04580000 -0.00000000 0.12572359 -0.14241131 0.05609332 0.25286138 -0.07566043 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 2.95500000 3.41200000 2.41200000 -0.01120000 -0.00000000 -0.00000000 -0.00000000 0.02590000 -0.01400000 -0.00000000 -0.01400000 -0.01470000 0.00000000 0.00000000 -0.01979899 -0.01800375 0.00000000 -0.02623366 -Li 0.00000000 0.00000000 12.66500000 -0.00130000 -0.00000000 0.00000000 -0.00000000 0.00560000 -0.00160000 0.00000000 -0.00160000 -0.00430000 0.00000000 0.00000000 -0.00226274 -0.00526640 0.00000000 -0.00487904 -Li 2.95500000 1.70600000 6.63400000 0.00310000 0.00000000 -0.00000000 0.00000000 0.00370000 0.00000000 -0.00000000 0.00000000 -0.00680000 0.00000000 0.00000000 0.00000000 -0.00832827 0.00000000 -0.00042426 -Li 2.95500000 1.70600000 3.01600000 -0.00650000 0.00000000 -0.00000000 0.00000000 0.04160000 -0.01560000 -0.00000000 -0.01560000 -0.03510000 0.00000000 0.00000000 -0.02206173 -0.04298854 0.00000000 -0.03401184 -Li 0.00000000 3.41200000 11.45900000 0.01160000 0.00000000 0.00000000 0.00000000 0.01090000 0.00030000 0.00000000 0.00030000 -0.02260000 0.00005774 0.00000000 0.00042426 -0.02763841 0.00000000 0.00049497 -Li 0.00000000 3.41200000 7.84000000 0.00220000 0.00000000 -0.00000000 0.00000000 0.00310000 -0.00010000 -0.00000000 -0.00010000 -0.00540000 0.00005774 0.00000000 -0.00014142 -0.00657280 0.00000000 -0.00063640 -Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 0.00000000 -0.00000000 0.00770000 0.00010000 0.00000000 0.00010000 -0.01450000 0.00005774 0.00000000 0.00014142 -0.01771798 0.00000000 -0.00070711 -Li 2.95500000 0.00000000 0.00000000 0.00410000 0.00000000 0.00000000 0.00000000 0.00730000 -0.00130000 0.00000000 -0.00130000 -0.01150000 0.00005774 0.00000000 -0.00183848 -0.01404374 0.00000000 -0.00226274 -O 0.00000000 0.00000000 3.80700000 0.03500000 0.00000000 0.00000000 0.00000000 0.17450000 0.06990000 0.00000000 0.06990000 -0.20940000 -0.00005774 0.00000000 0.09885353 -0.25650240 0.00000000 -0.09864140 -O 0.00000000 0.00000000 10.66800000 0.07440000 -0.00000000 0.00000000 -0.00000000 0.08740000 -0.00550000 0.00000000 -0.00550000 -0.16180000 0.00000000 0.00000000 -0.00777817 -0.19816372 0.00000000 -0.00919239 -O 2.95500000 1.70600000 8.63200000 0.02500000 0.00000000 -0.00000000 0.00000000 0.03530000 -0.00070000 -0.00000000 -0.00070000 -0.06030000 0.00000000 0.00000000 -0.00098995 -0.07385212 0.00000000 -0.00728320 -O 2.95500000 1.70600000 1.01800000 -0.01250000 -0.00000000 0.00000000 -0.00000000 -0.00720000 -0.02950000 0.00000000 -0.02950000 0.01970000 -0.00000000 0.00000000 -0.04171930 0.02412747 0.00000000 -0.00374767 -O 0.00000000 3.41200000 13.45700000 0.09910000 0.00000000 0.00000000 0.00000000 -0.04470000 -0.14770000 0.00000000 -0.14770000 -0.05430000 -0.00005774 0.00000000 -0.20887934 -0.06654447 0.00000000 0.10168196 -O 0.00000000 3.41200000 5.84300000 0.02090000 -0.00000000 -0.00000000 -0.00000000 0.03630000 0.02030000 -0.00000000 0.02030000 -0.05720000 0.00000000 0.00000000 0.02870854 -0.07005541 0.00000000 -0.01088944 -O -1.32400000 4.17600000 1.14400000 -0.15560000 0.01660000 0.08370000 0.01660000 0.07930000 -0.07330000 0.08370000 -0.07330000 0.07620000 0.00005774 0.02347595 -0.10366185 0.09336638 0.11836968 -0.16609938 -O 0.00000000 1.88300000 1.14400000 0.05870000 0.00000000 0.00000000 0.00000000 -0.13410000 0.09210000 0.00000000 0.09210000 0.07540000 -0.00000000 0.00000000 0.13024907 0.09234576 0.00000000 0.13633019 -O 1.32400000 4.17600000 1.14400000 -0.15560000 -0.01660000 -0.08370000 -0.01660000 0.07930000 -0.07330000 -0.08370000 -0.07330000 0.07620000 0.00005774 -0.02347595 -0.10366185 0.09336638 -0.11836968 -0.16609938 -O 4.27800000 0.94200000 13.33100000 -0.15780000 -0.07150000 0.14790000 -0.07150000 0.11720000 0.01180000 0.14790000 0.01180000 0.04060000 0.00000000 -0.10111627 0.01668772 0.04972464 0.20916219 -0.19445436 -O 1.63100000 0.94200000 13.33100000 -0.15780000 0.07150000 -0.14790000 0.07150000 0.11720000 0.01180000 -0.14790000 0.01180000 0.04060000 0.00000000 0.10111627 0.01668772 0.04972464 -0.20916219 -0.19445436 -O 2.95500000 3.23400000 13.33100000 0.01830000 -0.00000000 -0.00000000 -0.00000000 -0.08970000 0.06250000 -0.00000000 0.06250000 0.07140000 -0.00000000 0.00000000 0.08838835 0.08744678 0.00000000 0.07636753 -O 4.58600000 0.76400000 5.96800000 -0.12320000 0.13310000 -0.09280000 0.13310000 0.03180000 0.04390000 -0.09280000 0.04390000 0.09140000 -0.00000000 0.18823183 0.06208398 0.11194168 -0.13123902 -0.10960155 -O 2.95500000 3.58900000 5.96800000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.18480000 -0.06910000 -0.00000000 -0.06910000 0.10430000 -0.00000000 0.00000000 -0.09772216 0.12774089 0.00000000 0.18759543 -O 1.32400000 0.76400000 5.96800000 -0.12320000 -0.13310000 0.09280000 -0.13310000 0.03180000 0.04390000 0.09280000 0.04390000 0.09140000 -0.00000000 -0.18823183 0.06208398 0.11194168 0.13123902 -0.10960155 -O 1.32400000 2.64800000 3.68100000 0.04760000 0.09260000 -0.09950000 0.09260000 0.00300000 -0.00540000 -0.09950000 -0.00540000 -0.05060000 0.00000000 0.13095618 -0.00763675 -0.06197209 -0.14071425 0.03153696 -O -1.32400000 2.64800000 3.68100000 0.04760000 -0.09260000 0.09950000 -0.09260000 0.00300000 -0.00540000 0.09950000 -0.00540000 -0.05060000 0.00000000 -0.13095618 -0.00763675 -0.06197209 0.14071425 0.03153696 -O 0.00000000 4.94000000 3.68100000 0.06080000 -0.00000000 -0.00000000 -0.00000000 -0.00850000 -0.02880000 -0.00000000 -0.02880000 -0.05230000 0.00000000 0.00000000 -0.04072935 -0.06405416 0.00000000 0.04900250 -O 1.63100000 2.47000000 10.79300000 -0.02380000 0.05020000 -0.01720000 0.05020000 0.05800000 0.03700000 -0.01720000 0.03700000 -0.03420000 0.00000000 0.07099352 0.05232590 -0.04188627 -0.02432447 -0.05784133 -O 2.95500000 0.17700000 10.79300000 0.06760000 0.00000000 0.00000000 0.00000000 -0.02710000 -0.02290000 0.00000000 -0.02290000 -0.04050000 0.00000000 0.00000000 -0.03238549 -0.04960217 0.00000000 0.06696301 -O 4.27800000 2.47000000 10.79300000 -0.02380000 -0.05020000 0.01720000 -0.05020000 0.05800000 0.03700000 0.01720000 0.03700000 -0.03420000 0.00000000 -0.07099352 0.05232590 -0.04188627 0.02432447 -0.05784133 -O -1.63100000 4.35300000 8.50600000 -0.12630000 0.12770000 -0.08970000 0.12770000 0.03150000 0.05650000 -0.08970000 0.05650000 0.09480000 0.00000000 0.18059507 0.07990307 0.11610581 -0.12685496 -0.11158145 -O 1.63100000 4.35300000 8.50600000 -0.12630000 -0.12770000 0.08970000 -0.12770000 0.03150000 0.05650000 0.08970000 0.05650000 0.09480000 0.00000000 -0.18059507 0.07990307 0.11610581 0.12685496 -0.11158145 -O 0.00000000 1.52800000 8.50600000 0.09320000 0.00000000 -0.00000000 0.00000000 -0.18890000 -0.10420000 -0.00000000 -0.10420000 0.09580000 -0.00005774 0.00000000 -0.14736105 0.11728973 0.00000000 0.19947482 -Ti 2.95500000 1.70600000 12.06200000 0.07920000 0.00000000 0.00000000 0.00000000 0.16670000 0.08370000 0.00000000 0.08370000 -0.24600000 0.00005774 0.00000000 0.11836968 -0.30124641 0.00000000 -0.06187184 -Ti 0.00000000 3.41200000 2.41200000 0.12850000 0.00000000 -0.00000000 0.00000000 0.14690000 0.03330000 -0.00000000 0.03330000 -0.27540000 0.00000000 0.00000000 0.04709331 -0.33729474 0.00000000 -0.01301076 -Ti -1.47700000 2.55900000 0.00000000 -0.08610000 -0.22300000 0.00750000 -0.22300000 -0.04420000 -0.07580000 0.00750000 -0.07580000 0.13030000 -0.00000000 -0.31536962 -0.10719739 0.15958426 0.01060660 -0.02962777 -Ti 1.47700000 2.55900000 0.00000000 -0.08610000 0.22300000 -0.00750000 0.22300000 -0.04420000 -0.07580000 -0.00750000 -0.07580000 0.13030000 -0.00000000 0.31536962 -0.10719739 0.15958426 -0.01060660 -0.02962777 -Ti 0.00000000 1.70600000 4.82500000 0.08990000 0.00000000 -0.00000000 0.00000000 -0.13000000 0.18400000 -0.00000000 0.18400000 0.04010000 -0.00000000 0.00000000 0.26021530 0.04911227 0.00000000 0.15549278 -Ti 1.47700000 4.26500000 4.82500000 -0.09210000 -0.09010000 -0.18750000 -0.09010000 0.04690000 -0.07960000 -0.18750000 -0.07960000 0.04520000 0.00000000 -0.12742064 -0.11257140 0.05535847 -0.26516504 -0.09828784 -Ti -1.47700000 4.26500000 4.82500000 -0.09210000 0.09010000 0.18750000 0.09010000 0.04690000 -0.07960000 0.18750000 -0.07960000 0.04520000 0.00000000 0.12742064 -0.11257140 0.05535847 0.26516504 -0.09828784 -Ti 2.95500000 3.41200000 9.65000000 0.08040000 -0.00000000 -0.00000000 -0.00000000 -0.12460000 0.21000000 -0.00000000 0.21000000 0.04410000 0.00005774 0.00000000 0.29698485 0.05405207 0.00000000 0.14495689 -Ti 1.47700000 0.85300000 9.65000000 -0.07600000 -0.08870000 -0.18020000 -0.08870000 0.03090000 -0.10140000 -0.18020000 -0.10140000 0.04500000 0.00005774 -0.12544074 -0.14340126 0.05515434 -0.25484128 -0.07558971 -Ti 4.43200000 0.85300000 9.65000000 -0.07600000 0.08870000 0.18020000 0.08870000 0.03090000 -0.10140000 0.18020000 -0.10140000 0.04500000 0.00005774 0.12544074 -0.14340126 0.05515434 0.25484128 -0.07558971 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 1.47700000 0.85300000 2.41200000 -0.00170000 -0.00340000 -0.00130000 -0.00340000 0.00880000 -0.00410000 -0.00130000 -0.00410000 -0.00710000 0.00000000 -0.00480833 -0.00579828 -0.00869569 -0.00183848 -0.00742462 -Li 0.00000000 0.00000000 12.66500000 -0.00110000 -0.00000000 -0.00000000 -0.00000000 0.00490000 -0.00160000 -0.00000000 -0.00160000 -0.00380000 0.00000000 0.00000000 -0.00226274 -0.00465403 0.00000000 -0.00424264 -Li 2.95500000 1.70600000 6.63400000 0.00330000 -0.00010000 -0.00000000 -0.00010000 0.00320000 0.00000000 -0.00000000 0.00000000 -0.00650000 0.00000000 -0.00014142 0.00000000 -0.00796084 0.00000000 0.00007071 -Li 2.95500000 3.41200000 2.41200000 0.00620000 0.00380000 -0.00130000 0.00380000 -0.00100000 0.00480000 -0.00130000 0.00480000 -0.00520000 0.00000000 0.00537401 0.00678823 -0.00636867 -0.00183848 0.00509117 -Li 0.00000000 3.41200000 11.45900000 0.01150000 0.00000000 0.00010000 0.00000000 0.00990000 0.00030000 0.00010000 0.00030000 -0.02140000 0.00000000 0.00000000 0.00042426 -0.02620954 0.00014142 0.00113137 -Li 0.00000000 3.41200000 7.84000000 0.00280000 0.00000000 0.00010000 0.00000000 0.00280000 -0.00000000 0.00010000 -0.00000000 -0.00570000 0.00005774 0.00000000 0.00000000 -0.00694022 0.00014142 0.00000000 -Li 0.00000000 0.00000000 7.23700000 0.00690000 0.00000000 -0.00010000 0.00000000 0.00700000 0.00010000 -0.00010000 0.00010000 -0.01390000 0.00000000 0.00000000 0.00014142 -0.01702395 -0.00014142 -0.00007071 -Li 2.95500000 0.00000000 0.00000000 0.00480000 -0.00020000 -0.00190000 -0.00020000 0.00520000 -0.00120000 -0.00190000 -0.00120000 -0.01000000 0.00000000 -0.00028284 -0.00169706 -0.01224745 -0.00268701 -0.00028284 -O 0.00000000 0.00000000 3.80700000 0.06740000 0.05180000 -0.07360000 0.05180000 0.12780000 0.03580000 -0.07360000 0.03580000 -0.19520000 0.00000000 0.07325626 0.05062885 -0.23907020 -0.10408612 -0.04270925 -O 0.00000000 0.00000000 10.66800000 0.07710000 -0.00090000 -0.00010000 -0.00090000 0.07900000 -0.00580000 -0.00010000 -0.00580000 -0.15600000 -0.00005774 -0.00127279 -0.00820244 -0.19110102 -0.00014142 -0.00134350 -O 2.95500000 1.70600000 8.63200000 0.02680000 -0.00070000 -0.00010000 -0.00070000 0.02540000 -0.00090000 -0.00010000 -0.00090000 -0.05220000 0.00000000 -0.00098995 -0.00127279 -0.06393168 -0.00014142 0.00098995 -O 2.95500000 1.70600000 1.01800000 0.07230000 0.03990000 -0.08260000 0.03990000 -0.01980000 -0.10850000 -0.08260000 -0.10850000 -0.05260000 0.00005774 0.05642712 -0.15344217 -0.06438076 -0.11681404 0.06512453 -O 0.00000000 3.41200000 13.45700000 0.10720000 0.00630000 -0.01090000 0.00630000 -0.05420000 -0.16730000 -0.01090000 -0.16730000 -0.05300000 0.00000000 0.00890955 -0.23659793 -0.06491148 -0.01541493 0.11412703 -O 0.00000000 3.41200000 5.84300000 0.03140000 0.00260000 -0.01810000 0.00260000 0.03340000 0.00920000 -0.01810000 0.00920000 -0.06480000 0.00000000 0.00367696 0.01301076 -0.07936347 -0.02559727 -0.00141421 -O -1.32400000 4.17600000 1.14400000 -0.19550000 -0.01730000 0.09820000 -0.01730000 0.09470000 0.03600000 0.09820000 0.03600000 0.10080000 -0.00000000 -0.02446589 0.05091169 0.12345428 0.13887577 -0.20520239 -O 0.00000000 1.88300000 1.14400000 0.08180000 -0.06350000 0.08120000 -0.06350000 -0.17010000 0.05510000 0.08120000 0.05510000 0.08830000 -0.00000000 -0.08980256 0.07792317 0.10814497 0.11483414 0.17812020 -O 1.32400000 4.17600000 1.14400000 -0.14100000 -0.00720000 -0.08450000 -0.00720000 0.08570000 -0.07220000 -0.08450000 -0.07220000 0.05530000 -0.00000000 -0.01018234 -0.10210622 0.06772839 -0.11950105 -0.16030111 -O 4.27800000 0.94200000 13.33100000 -0.16000000 -0.07740000 0.15620000 -0.07740000 0.11690000 0.02130000 0.15620000 0.02130000 0.04310000 -0.00000000 -0.10946013 0.03012275 0.05278650 0.22090016 -0.19579787 -O 1.63100000 0.94200000 13.33100000 -0.15550000 0.07370000 -0.15510000 0.07370000 0.10080000 0.00910000 -0.15510000 0.00910000 0.05470000 0.00000000 0.10422754 0.01286934 0.06699354 -0.21934452 -0.18123147 -O 2.95500000 3.23400000 13.33100000 0.03060000 -0.00840000 0.01080000 -0.00840000 -0.10610000 0.06250000 0.01080000 0.06250000 0.07550000 0.00000000 -0.01187939 0.08838835 0.09246824 0.01527351 0.09666150 -O 4.58600000 0.76400000 5.96800000 -0.13790000 0.14930000 -0.11860000 0.14930000 0.03270000 0.07410000 -0.11860000 0.07410000 0.10530000 -0.00005774 0.21114208 0.10479322 0.12892481 -0.16772573 -0.12063242 -O 2.95500000 3.58900000 5.96800000 0.10110000 -0.00650000 0.01280000 -0.00650000 -0.22210000 -0.10780000 0.01280000 -0.10780000 0.12100000 -0.00000000 -0.00919239 -0.15245222 0.14819413 0.01810193 0.22853691 -O 1.32400000 0.76400000 5.96800000 -0.13380000 -0.14400000 0.09840000 -0.14400000 0.01590000 0.04770000 0.09840000 0.04770000 0.11790000 -0.00000000 -0.20364675 0.06745799 0.14439742 0.13915861 -0.10585389 -O 1.32400000 2.64800000 3.68100000 -0.07680000 0.12820000 -0.05410000 0.12820000 0.08370000 0.06450000 -0.05410000 0.06450000 -0.00690000 -0.00000000 0.18130218 0.09121677 -0.00845074 -0.07650895 -0.11349064 -O -1.32400000 2.64800000 3.68100000 -0.00800000 -0.13210000 0.06390000 -0.13210000 0.02350000 -0.01250000 0.06390000 -0.01250000 -0.01560000 0.00005774 -0.18681761 -0.01767767 -0.01906520 0.09036825 -0.02227386 -O 0.00000000 4.94000000 3.68100000 0.12530000 -0.05690000 0.07040000 -0.05690000 -0.10310000 -0.04060000 0.07040000 -0.04060000 -0.02210000 -0.00005774 -0.08046875 -0.05741707 -0.02710769 0.09956063 0.16150319 -O 1.63100000 2.47000000 10.79300000 -0.02120000 0.04950000 -0.01340000 0.04950000 0.04920000 0.03690000 -0.01340000 0.03690000 -0.02800000 0.00000000 0.07000357 0.05218448 -0.03429286 -0.01895046 -0.04978032 -O 2.95500000 0.17700000 10.79300000 0.07030000 -0.00270000 0.00090000 -0.00270000 -0.03770000 -0.01970000 0.00090000 -0.01970000 -0.03260000 0.00000000 -0.00381838 -0.02786001 -0.03992668 0.00127279 0.07636753 -O 4.27800000 2.47000000 10.79300000 -0.02070000 -0.05250000 0.01380000 -0.05250000 0.04730000 0.03620000 0.01380000 0.03620000 -0.02660000 0.00000000 -0.07424621 0.05119453 -0.03257821 0.01951615 -0.04808326 -O -1.63100000 4.35300000 8.50600000 -0.12480000 0.12670000 -0.08690000 0.12670000 0.01980000 0.05550000 -0.08690000 0.05550000 0.10500000 -0.00000000 0.17918086 0.07848885 0.12859821 -0.12289516 -0.10224764 -O 1.63100000 4.35300000 8.50600000 -0.12420000 -0.12980000 0.08680000 -0.12980000 0.01800000 0.05490000 0.08680000 0.05490000 0.10610000 0.00005774 -0.18356492 0.07764032 0.12998626 0.12275374 -0.10055058 -O 0.00000000 1.52800000 8.50600000 0.09300000 -0.00250000 0.00050000 -0.00250000 -0.20140000 -0.10140000 0.00050000 -0.10140000 0.10830000 0.00005774 -0.00353553 -0.14340126 0.13268069 0.00070711 0.20817224 -Ti 2.95500000 1.70600000 12.06200000 0.08000000 -0.00040000 0.00160000 -0.00040000 0.16950000 0.08490000 0.00160000 0.08490000 -0.24950000 0.00000000 -0.00056569 0.12006673 -0.30557385 0.00226274 -0.06328606 -Ti 0.00000000 3.41200000 2.41200000 0.11950000 -0.03470000 0.03880000 -0.03470000 0.17180000 0.05850000 0.03880000 0.05850000 -0.29130000 0.00000000 -0.04907321 0.08273149 -0.35676818 0.05487149 -0.03698168 -Ti -1.47700000 2.55900000 0.00000000 -0.08920000 -0.24100000 0.02540000 -0.24100000 -0.03040000 -0.06870000 0.02540000 -0.06870000 0.11960000 -0.00000000 -0.34082547 -0.09715647 0.14647949 0.03592102 -0.04157788 -Ti 1.47700000 2.55900000 0.00000000 -0.08310000 0.24440000 -0.01970000 0.24440000 -0.04630000 -0.09160000 -0.01970000 -0.09160000 0.12940000 -0.00000000 0.34563379 -0.12954196 0.15848199 -0.02786001 -0.02602153 -Ti 0.00000000 1.70600000 4.82500000 0.07510000 0.01150000 -0.02350000 0.01150000 -0.12510000 0.22120000 -0.02350000 0.22120000 0.05000000 -0.00000000 0.01626346 0.31282404 0.06123724 -0.03323402 0.14156278 -Ti 1.47700000 4.26500000 4.82500000 -0.08770000 -0.08110000 -0.20070000 -0.08110000 0.03650000 -0.08650000 -0.20070000 -0.08650000 0.05110000 0.00005774 -0.11469272 -0.12232947 0.06262529 -0.28383266 -0.08782266 -Ti -1.47700000 4.26500000 4.82500000 -0.08040000 0.08840000 0.20580000 0.08840000 0.02520000 -0.11660000 0.20580000 -0.11660000 0.05520000 -0.00000000 0.12501648 -0.16489730 0.06760592 0.29104515 -0.07467048 -Ti 2.95500000 3.41200000 9.65000000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.12530000 0.20860000 -0.00000000 0.20860000 0.04480000 -0.00000000 0.00000000 0.29500495 0.05486857 0.00000000 0.14552258 -Ti 1.47700000 0.85300000 9.65000000 -0.07640000 -0.08900000 -0.17870000 -0.08900000 0.03060000 -0.10060000 -0.17870000 -0.10060000 0.04590000 -0.00005774 -0.12586501 -0.14226988 0.05617496 -0.25271996 -0.07566043 -Ti 4.43200000 0.85300000 9.65000000 -0.07630000 0.08890000 0.17900000 0.08890000 0.03060000 -0.10080000 0.17900000 -0.10080000 0.04580000 -0.00005774 0.12572359 -0.14255273 0.05605249 0.25314423 -0.07558971 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.01940000 -0.00000000 -0.00000000 -0.00000000 -0.01220000 -0.00230000 -0.00000000 -0.00230000 0.03160000 -0.00000000 0.00000000 -0.00325269 0.03870194 0.00000000 -0.00509117 -Li 0.00000000 0.00000000 0.00000000 -0.01820000 0.00000000 -0.00000000 0.00000000 -0.00030000 0.00580000 -0.00000000 0.00580000 0.01850000 0.00000000 0.00000000 0.00820244 0.02265778 0.00000000 -0.01265721 -Li 2.95500000 1.70600000 6.63400000 0.00440000 0.00000000 0.00000000 0.00000000 0.00520000 0.00000000 0.00000000 0.00000000 -0.00960000 0.00000000 0.00000000 0.00000000 -0.01175755 0.00000000 -0.00056569 -Li 2.95500000 1.70600000 3.01600000 0.01190000 0.00000000 -0.00000000 0.00000000 0.01120000 0.00040000 -0.00000000 0.00040000 -0.02310000 0.00000000 0.00000000 0.00056569 -0.02829161 0.00000000 0.00049497 -Li 0.00000000 3.41200000 11.45900000 0.00910000 -0.00000000 0.00000000 -0.00000000 0.00850000 0.00020000 0.00000000 0.00020000 -0.01760000 0.00000000 0.00000000 0.00028284 -0.02155551 0.00000000 0.00042426 -Li 0.00000000 3.41200000 7.84000000 0.00090000 -0.00000000 -0.00000000 -0.00000000 0.00180000 -0.00000000 -0.00000000 -0.00000000 -0.00270000 0.00000000 0.00000000 0.00000000 -0.00330681 0.00000000 -0.00063640 -Li 0.00000000 0.00000000 7.23700000 0.00660000 -0.00000000 0.00000000 -0.00000000 0.00760000 0.00000000 0.00000000 0.00000000 -0.01420000 0.00000000 0.00000000 0.00000000 -0.01739138 0.00000000 -0.00070711 -Li 2.95500000 0.00000000 0.00000000 0.00970000 0.00000000 -0.00000000 0.00000000 0.00350000 -0.00200000 -0.00000000 -0.00200000 -0.01320000 0.00000000 0.00000000 -0.00282843 -0.01616663 0.00000000 0.00438406 -O 0.00000000 0.00000000 3.80700000 0.07310000 0.00000000 0.00000000 0.00000000 0.08660000 -0.00610000 0.00000000 -0.00610000 -0.15970000 0.00000000 0.00000000 -0.00862670 -0.19559176 0.00000000 -0.00954594 -O 0.00000000 0.00000000 10.66800000 0.07740000 -0.00000000 0.00000000 -0.00000000 0.08880000 -0.00390000 0.00000000 -0.00390000 -0.16620000 0.00000000 0.00000000 -0.00551543 -0.20355260 0.00000000 -0.00806102 -O 2.95500000 1.70600000 8.63200000 0.02490000 -0.00000000 -0.00000000 -0.00000000 0.03510000 -0.00070000 -0.00000000 -0.00070000 -0.06000000 0.00000000 0.00000000 -0.00098995 -0.07348469 0.00000000 -0.00721249 -O 2.95500000 1.70600000 1.01800000 0.13080000 -0.00000000 0.00000000 -0.00000000 -0.03120000 -0.16810000 0.00000000 -0.16810000 -0.09970000 0.00005774 0.00000000 -0.23772930 -0.12206624 0.00000000 0.11455130 -O 0.00000000 3.41200000 13.45700000 0.06970000 0.00000000 -0.00000000 0.00000000 -0.03640000 -0.19220000 -0.00000000 -0.19220000 -0.03330000 0.00000000 0.00000000 -0.27181185 -0.04078400 0.00000000 0.07502403 -O 0.00000000 3.41200000 5.84300000 0.02200000 -0.00000000 -0.00000000 -0.00000000 0.03230000 -0.00080000 -0.00000000 -0.00080000 -0.05430000 0.00000000 0.00000000 -0.00113137 -0.06650365 0.00000000 -0.00728320 -O -1.32400000 4.17600000 1.14400000 -0.06810000 -0.15090000 0.22230000 -0.15090000 0.07060000 -0.01650000 0.22230000 -0.01650000 -0.00250000 -0.00000000 -0.21340483 -0.02333452 -0.00306186 0.31437967 -0.09807571 -O 0.00000000 1.88300000 1.14400000 -0.04150000 -0.00000000 0.00000000 -0.00000000 0.03000000 0.14460000 0.00000000 0.14460000 0.01150000 -0.00000000 0.00000000 0.20449528 0.01408457 0.00000000 -0.05055813 -O 1.32400000 4.17600000 1.14400000 -0.06810000 0.15090000 -0.22230000 0.15090000 0.07060000 -0.01650000 -0.22230000 -0.01650000 -0.00250000 -0.00000000 0.21340483 -0.02333452 -0.00306186 -0.31437967 -0.09807571 -O 4.27800000 0.94200000 13.33100000 -0.13130000 -0.10430000 0.26110000 -0.10430000 0.09150000 -0.01350000 0.26110000 -0.01350000 0.03980000 -0.00000000 -0.14750247 -0.01909188 0.04874485 0.36925116 -0.15754339 -O 1.63100000 0.94200000 13.33100000 -0.13130000 0.10430000 -0.26110000 0.10430000 0.09150000 -0.01350000 -0.26110000 -0.01350000 0.03980000 -0.00000000 0.14750247 -0.01909188 0.04874485 -0.36925116 -0.15754339 -O 2.95500000 3.23400000 13.33100000 0.00580000 -0.00000000 -0.00000000 -0.00000000 -0.09340000 0.16570000 -0.00000000 0.16570000 0.08760000 -0.00000000 0.00000000 0.23433519 0.10728765 0.00000000 0.07014499 -O 4.58600000 0.76400000 5.96800000 -0.12880000 0.12060000 -0.07740000 0.12060000 0.02110000 0.04980000 -0.07740000 0.04980000 0.10780000 -0.00005774 0.17055416 0.07042784 0.13198667 -0.10946013 -0.10599531 -O 2.95500000 3.58900000 5.96800000 0.07870000 0.00000000 0.00000000 0.00000000 -0.18750000 -0.09080000 0.00000000 -0.09080000 0.10880000 -0.00000000 0.00000000 -0.12841059 0.13325224 0.00000000 0.18823183 -O 1.32400000 0.76400000 5.96800000 -0.12880000 -0.12060000 0.07740000 -0.12060000 0.02110000 0.04980000 0.07740000 0.04980000 0.10780000 -0.00005774 -0.17055416 0.07042784 0.13198667 0.10946013 -0.10599531 -O 1.32400000 2.64800000 3.68100000 -0.01770000 0.04210000 0.00400000 0.04210000 0.05840000 0.02750000 0.00400000 0.02750000 -0.04060000 -0.00005774 0.05953839 0.03889087 -0.04976547 0.00565685 -0.05381083 -O -1.32400000 2.64800000 3.68100000 -0.01770000 -0.04210000 -0.00400000 -0.04210000 0.05840000 0.02750000 -0.00400000 0.02750000 -0.04060000 -0.00005774 -0.05953839 0.03889087 -0.04976547 -0.00565685 -0.05381083 -O 0.00000000 4.94000000 3.68100000 0.06200000 -0.00000000 0.00000000 -0.00000000 -0.01510000 0.00220000 0.00000000 0.00220000 -0.04680000 -0.00005774 0.00000000 0.00311127 -0.05735888 0.00000000 0.05451793 -O 1.63100000 2.47000000 10.79300000 -0.01770000 0.02610000 0.00820000 0.02610000 0.03420000 0.02560000 0.00820000 0.02560000 -0.01660000 0.00005774 0.03691097 0.03620387 -0.02028994 0.01159655 -0.03669884 -O 2.95500000 0.17700000 10.79300000 0.03000000 0.00000000 -0.00000000 0.00000000 -0.00710000 0.00410000 -0.00000000 0.00410000 -0.02300000 0.00005774 0.00000000 0.00579828 -0.02812831 0.00000000 0.02623366 -O 4.27800000 2.47000000 10.79300000 -0.01770000 -0.02610000 -0.00820000 -0.02610000 0.03420000 0.02560000 -0.00820000 0.02560000 -0.01660000 0.00005774 -0.03691097 0.03620387 -0.02028994 -0.01159655 -0.03669884 -O -1.63100000 4.35300000 8.50600000 -0.12530000 0.13210000 -0.06880000 0.13210000 0.03790000 0.04460000 -0.06880000 0.04460000 0.08740000 -0.00000000 0.18681761 0.06307392 0.10704270 -0.09729789 -0.11539983 -O 1.63100000 4.35300000 8.50600000 -0.12530000 -0.13210000 0.06880000 -0.13210000 0.03790000 0.04460000 0.06880000 0.04460000 0.08740000 -0.00000000 -0.18681761 0.06307392 0.10704270 0.09729789 -0.11539983 -O 0.00000000 1.52800000 8.50600000 0.10240000 0.00000000 -0.00000000 0.00000000 -0.19110000 -0.08020000 -0.00000000 -0.08020000 0.08870000 -0.00000000 0.00000000 -0.11341993 0.10863487 0.00000000 0.20753584 -Ti 2.95500000 1.70600000 12.06200000 0.08800000 -0.00000000 0.00000000 -0.00000000 0.17150000 0.08990000 0.00000000 0.08990000 -0.25960000 0.00005774 0.00000000 0.12713780 -0.31790294 0.00000000 -0.05904342 -Ti 0.00000000 3.41200000 2.41200000 0.07080000 -0.00000000 -0.00000000 -0.00000000 0.17150000 0.08560000 -0.00000000 0.08560000 -0.24220000 -0.00005774 0.00000000 0.12105668 -0.29667403 0.00000000 -0.07120565 -Ti -1.47700000 2.55900000 0.00000000 -0.09210000 -0.27610000 0.05540000 -0.27610000 -0.02810000 -0.08060000 0.05540000 -0.08060000 0.12010000 0.00005774 -0.39046436 -0.11398561 0.14713268 0.07834743 -0.04525483 -Ti 1.47700000 2.55900000 0.00000000 -0.09210000 0.27610000 -0.05540000 0.27610000 -0.02810000 -0.08060000 -0.05540000 -0.08060000 0.12010000 0.00005774 0.39046436 -0.11398561 0.14713268 -0.07834743 -0.04525483 -Ti 0.00000000 1.70600000 4.82500000 0.07980000 -0.00000000 0.00000000 -0.00000000 -0.12660000 0.20230000 0.00000000 0.20230000 0.04670000 0.00005774 0.00000000 0.28609540 0.05723641 0.00000000 0.14594684 -Ti 1.47700000 4.26500000 4.82500000 -0.07740000 -0.08860000 -0.17320000 -0.08860000 0.02940000 -0.09730000 -0.17320000 -0.09730000 0.04800000 -0.00000000 -0.12529932 -0.13760298 0.05878775 -0.24494179 -0.07551900 -Ti -1.47700000 4.26500000 4.82500000 -0.07740000 0.08860000 0.17320000 0.08860000 0.02940000 -0.09730000 0.17320000 -0.09730000 0.04800000 -0.00000000 0.12529932 -0.13760298 0.05878775 0.24494179 -0.07551900 -Ti 2.95500000 3.41200000 9.65000000 0.10300000 -0.00000000 -0.00000000 -0.00000000 -0.14820000 0.18560000 -0.00000000 0.18560000 0.04510000 0.00005774 0.00000000 0.26247804 0.05527682 0.00000000 0.17762522 -Ti 1.47700000 0.85300000 9.65000000 -0.08800000 -0.10820000 -0.15860000 -0.10820000 0.04130000 -0.08910000 -0.15860000 -0.08910000 0.04670000 -0.00000000 -0.15301791 -0.12600643 0.05719559 -0.22429427 -0.09142891 -Ti 4.43200000 0.85300000 9.65000000 -0.08800000 0.10820000 0.15860000 0.10820000 0.04130000 -0.08910000 0.15860000 -0.08910000 0.04670000 -0.00000000 0.15301791 -0.12600643 0.05719559 0.22429427 -0.09142891 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00130000 -0.00000000 0.00000000 -0.00000000 0.00560000 -0.00160000 0.00000000 -0.00160000 -0.00430000 0.00000000 0.00000000 -0.00226274 -0.00526640 0.00000000 -0.00487904 -Li 0.00000000 1.70600000 12.06200000 -0.01120000 0.00000000 0.00000000 0.00000000 0.02590000 -0.01400000 0.00000000 -0.01400000 -0.01470000 0.00000000 0.00000000 -0.01979899 -0.01800375 0.00000000 -0.02623366 -Li 2.95500000 1.70600000 6.63400000 0.00220000 0.00000000 -0.00000000 0.00000000 0.00310000 -0.00010000 -0.00000000 -0.00010000 -0.00540000 0.00005774 0.00000000 -0.00014142 -0.00657280 0.00000000 -0.00063640 -Li 2.95500000 1.70600000 3.01600000 0.01160000 0.00000000 -0.00000000 0.00000000 0.01090000 0.00030000 -0.00000000 0.00030000 -0.02260000 0.00005774 0.00000000 0.00042426 -0.02763841 0.00000000 0.00049497 -Li 0.00000000 3.41200000 11.45900000 -0.00650000 -0.00000000 0.00000000 -0.00000000 0.04160000 -0.01560000 0.00000000 -0.01560000 -0.03510000 0.00000000 0.00000000 -0.02206173 -0.04298854 0.00000000 -0.03401184 -Li 0.00000000 3.41200000 7.84000000 0.00310000 -0.00000000 -0.00000000 -0.00000000 0.00370000 0.00000000 -0.00000000 0.00000000 -0.00680000 0.00000000 0.00000000 0.00000000 -0.00832827 0.00000000 -0.00042426 -Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 -0.00000000 -0.00000000 0.00770000 0.00010000 -0.00000000 0.00010000 -0.01450000 0.00005774 0.00000000 0.00014142 -0.01771798 0.00000000 -0.00070711 -Li 2.95500000 0.00000000 0.00000000 0.00410000 0.00000000 -0.00000000 0.00000000 0.00730000 -0.00130000 -0.00000000 -0.00130000 -0.01150000 0.00005774 0.00000000 -0.00183848 -0.01404374 0.00000000 -0.00226274 -O 0.00000000 0.00000000 3.80700000 0.07440000 -0.00000000 -0.00000000 -0.00000000 0.08740000 -0.00550000 -0.00000000 -0.00550000 -0.16180000 0.00000000 0.00000000 -0.00777817 -0.19816372 0.00000000 -0.00919239 -O 0.00000000 0.00000000 10.66800000 0.03500000 -0.00000000 0.00000000 -0.00000000 0.17450000 0.06990000 0.00000000 0.06990000 -0.20940000 -0.00005774 0.00000000 0.09885353 -0.25650240 0.00000000 -0.09864140 -O 2.95500000 1.70600000 8.63200000 0.02090000 -0.00000000 -0.00000000 -0.00000000 0.03630000 0.02030000 -0.00000000 0.02030000 -0.05720000 0.00000000 0.00000000 0.02870854 -0.07005541 0.00000000 -0.01088944 -O 2.95500000 1.70600000 1.01800000 0.09910000 -0.00000000 0.00000000 -0.00000000 -0.04470000 -0.14770000 0.00000000 -0.14770000 -0.05430000 -0.00005774 0.00000000 -0.20887934 -0.06654447 0.00000000 0.10168196 -O 0.00000000 3.41200000 13.45700000 -0.01250000 0.00000000 -0.00000000 0.00000000 -0.00720000 -0.02950000 -0.00000000 -0.02950000 0.01970000 -0.00000000 0.00000000 -0.04171930 0.02412747 0.00000000 -0.00374767 -O 0.00000000 3.41200000 5.84300000 0.02500000 0.00000000 -0.00000000 0.00000000 0.03530000 -0.00070000 -0.00000000 -0.00070000 -0.06030000 0.00000000 0.00000000 -0.00098995 -0.07385212 0.00000000 -0.00728320 -O -1.32400000 4.17600000 1.14400000 -0.15780000 -0.07150000 0.14790000 -0.07150000 0.11720000 0.01180000 0.14790000 0.01180000 0.04060000 0.00000000 -0.10111627 0.01668772 0.04972464 0.20916219 -0.19445436 -O 0.00000000 1.88300000 1.14400000 0.01830000 0.00000000 0.00000000 0.00000000 -0.08970000 0.06250000 0.00000000 0.06250000 0.07140000 -0.00000000 0.00000000 0.08838835 0.08744678 0.00000000 0.07636753 -O 1.32400000 4.17600000 1.14400000 -0.15780000 0.07150000 -0.14790000 0.07150000 0.11720000 0.01180000 -0.14790000 0.01180000 0.04060000 0.00000000 0.10111627 0.01668772 0.04972464 -0.20916219 -0.19445436 -O 4.27800000 0.94200000 13.33100000 -0.15560000 0.01660000 0.08370000 0.01660000 0.07930000 -0.07330000 0.08370000 -0.07330000 0.07620000 0.00005774 0.02347595 -0.10366185 0.09336638 0.11836968 -0.16609938 -O 1.63100000 0.94200000 13.33100000 -0.15560000 -0.01660000 -0.08370000 -0.01660000 0.07930000 -0.07330000 -0.08370000 -0.07330000 0.07620000 0.00005774 -0.02347595 -0.10366185 0.09336638 -0.11836968 -0.16609938 -O 2.95500000 3.23400000 13.33100000 0.05870000 0.00000000 -0.00000000 0.00000000 -0.13410000 0.09210000 -0.00000000 0.09210000 0.07540000 -0.00000000 0.00000000 0.13024907 0.09234576 0.00000000 0.13633019 -O 4.58600000 0.76400000 5.96800000 -0.12630000 0.12770000 -0.08970000 0.12770000 0.03150000 0.05650000 -0.08970000 0.05650000 0.09480000 0.00000000 0.18059507 0.07990307 0.11610581 -0.12685496 -0.11158145 -O 2.95500000 3.58900000 5.96800000 0.09320000 -0.00000000 0.00000000 -0.00000000 -0.18890000 -0.10420000 0.00000000 -0.10420000 0.09580000 -0.00005774 0.00000000 -0.14736105 0.11728973 0.00000000 0.19947482 -O 1.32400000 0.76400000 5.96800000 -0.12630000 -0.12770000 0.08970000 -0.12770000 0.03150000 0.05650000 0.08970000 0.05650000 0.09480000 0.00000000 -0.18059507 0.07990307 0.11610581 0.12685496 -0.11158145 -O 1.32400000 2.64800000 3.68100000 -0.02380000 0.05020000 -0.01720000 0.05020000 0.05800000 0.03700000 -0.01720000 0.03700000 -0.03420000 0.00000000 0.07099352 0.05232590 -0.04188627 -0.02432447 -0.05784133 -O -1.32400000 2.64800000 3.68100000 -0.02380000 -0.05020000 0.01720000 -0.05020000 0.05800000 0.03700000 0.01720000 0.03700000 -0.03420000 0.00000000 -0.07099352 0.05232590 -0.04188627 0.02432447 -0.05784133 -O 0.00000000 4.94000000 3.68100000 0.06760000 -0.00000000 -0.00000000 -0.00000000 -0.02710000 -0.02290000 0.00000000 -0.02290000 -0.04050000 0.00000000 0.00000000 -0.03238549 -0.04960217 0.00000000 0.06696301 -O 1.63100000 2.47000000 10.79300000 0.04760000 0.09260000 -0.09950000 0.09260000 0.00300000 -0.00540000 -0.09950000 -0.00540000 -0.05060000 0.00000000 0.13095618 -0.00763675 -0.06197209 -0.14071425 0.03153696 -O 2.95500000 0.17700000 10.79300000 0.06080000 0.00000000 0.00000000 0.00000000 -0.00850000 -0.02880000 0.00000000 -0.02880000 -0.05230000 0.00000000 0.00000000 -0.04072935 -0.06405416 0.00000000 0.04900250 -O 4.27800000 2.47000000 10.79300000 0.04760000 -0.09260000 0.09950000 -0.09260000 0.00300000 -0.00540000 0.09950000 -0.00540000 -0.05060000 0.00000000 -0.13095618 -0.00763675 -0.06197209 0.14071425 0.03153696 -O -1.63100000 4.35300000 8.50600000 -0.12320000 0.13310000 -0.09280000 0.13310000 0.03180000 0.04390000 -0.09280000 0.04390000 0.09140000 -0.00000000 0.18823183 0.06208398 0.11194168 -0.13123902 -0.10960155 -O 1.63100000 4.35300000 8.50600000 -0.12320000 -0.13310000 0.09280000 -0.13310000 0.03180000 0.04390000 0.09280000 0.04390000 0.09140000 -0.00000000 -0.18823183 0.06208398 0.11194168 0.13123902 -0.10960155 -O 0.00000000 1.52800000 8.50600000 0.08050000 0.00000000 -0.00000000 -0.00000000 -0.18480000 -0.06910000 -0.00000000 -0.06910000 0.10430000 -0.00000000 0.00000000 -0.09772216 0.12774089 0.00000000 0.18759543 -Ti 2.95500000 1.70600000 12.06200000 0.12850000 0.00000000 0.00000000 0.00000000 0.14690000 0.03330000 0.00000000 0.03330000 -0.27540000 0.00000000 0.00000000 0.04709331 -0.33729474 0.00000000 -0.01301076 -Ti 0.00000000 3.41200000 2.41200000 0.07920000 0.00000000 -0.00000000 0.00000000 0.16670000 0.08370000 -0.00000000 0.08370000 -0.24600000 0.00005774 0.00000000 0.11836968 -0.30124641 0.00000000 -0.06187184 -Ti -1.47700000 2.55900000 0.00000000 -0.08610000 -0.22300000 0.00750000 -0.22300000 -0.04420000 -0.07580000 0.00750000 -0.07580000 0.13030000 -0.00000000 -0.31536962 -0.10719739 0.15958426 0.01060660 -0.02962777 -Ti 1.47700000 2.55900000 0.00000000 -0.08610000 0.22300000 -0.00750000 0.22300000 -0.04420000 -0.07580000 -0.00750000 -0.07580000 0.13030000 -0.00000000 0.31536962 -0.10719739 0.15958426 -0.01060660 -0.02962777 -Ti 0.00000000 1.70600000 4.82500000 0.08040000 0.00000000 0.00000000 0.00000000 -0.12460000 0.21000000 0.00000000 0.21000000 0.04410000 0.00005774 0.00000000 0.29698485 0.05405207 0.00000000 0.14495689 -Ti 1.47700000 4.26500000 4.82500000 -0.07600000 -0.08870000 -0.18020000 -0.08870000 0.03090000 -0.10140000 -0.18020000 -0.10140000 0.04500000 0.00005774 -0.12544074 -0.14340126 0.05515434 -0.25484128 -0.07558971 -Ti -1.47700000 4.26500000 4.82500000 -0.07600000 0.08870000 0.18020000 0.08870000 0.03090000 -0.10140000 0.18020000 -0.10140000 0.04500000 0.00005774 0.12544074 -0.14340126 0.05515434 0.25484128 -0.07558971 -Ti 2.95500000 3.41200000 9.65000000 0.08990000 0.00000000 -0.00000000 0.00000000 -0.13000000 0.18400000 -0.00000000 0.18400000 0.04010000 -0.00000000 0.00000000 0.26021530 0.04911227 0.00000000 0.15549278 -Ti 1.47700000 0.85300000 9.65000000 -0.09210000 -0.09010000 -0.18750000 -0.09010000 0.04690000 -0.07960000 -0.18750000 -0.07960000 0.04520000 0.00000000 -0.12742064 -0.11257140 0.05535847 -0.26516504 -0.09828784 -Ti 4.43200000 0.85300000 9.65000000 -0.09210000 0.09010000 0.18750000 0.09010000 0.04690000 -0.07960000 0.18750000 -0.07960000 0.04520000 0.00000000 0.12742064 -0.11257140 0.05535847 0.26516504 -0.09828784 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00110000 0.00010000 -0.00000000 0.00010000 0.00490000 -0.00160000 -0.00000000 -0.00160000 -0.00380000 0.00000000 0.00014142 -0.00226274 -0.00465403 0.00000000 -0.00424264 -Li -1.47700000 4.26500000 12.06200000 -0.00170000 0.00330000 0.00140000 0.00330000 0.00880000 -0.00410000 0.00140000 -0.00410000 -0.00710000 0.00000000 0.00466690 -0.00579828 -0.00869569 0.00197990 -0.00742462 -Li 2.95500000 1.70600000 6.63400000 0.00280000 -0.00000000 -0.00010000 -0.00000000 0.00280000 -0.00000000 -0.00010000 -0.00000000 -0.00570000 0.00005774 0.00000000 0.00000000 -0.00694022 -0.00014142 0.00000000 -Li 2.95500000 1.70600000 3.01600000 0.01150000 -0.00000000 -0.00010000 -0.00000000 0.00990000 0.00030000 -0.00010000 0.00030000 -0.02140000 0.00000000 0.00000000 0.00042426 -0.02620954 -0.00014142 0.00113137 -Li 0.00000000 1.70600000 12.06200000 0.00620000 -0.00380000 0.00130000 -0.00380000 -0.00090000 0.00480000 0.00130000 0.00480000 -0.00520000 -0.00005774 -0.00537401 0.00678823 -0.00640950 0.00183848 0.00502046 -Li 0.00000000 3.41200000 7.84000000 0.00330000 0.00010000 0.00000000 0.00010000 0.00320000 0.00000000 0.00000000 0.00000000 -0.00650000 0.00000000 0.00014142 0.00000000 -0.00796084 0.00000000 0.00007071 -Li 0.00000000 0.00000000 7.23700000 0.00690000 -0.00000000 0.00010000 -0.00000000 0.00700000 0.00010000 0.00010000 0.00010000 -0.01390000 0.00000000 0.00000000 0.00014142 -0.01702395 0.00014142 -0.00007071 -Li 2.95500000 0.00000000 0.00000000 0.00480000 0.00010000 0.00190000 0.00010000 0.00520000 -0.00120000 0.00190000 -0.00120000 -0.01000000 0.00000000 0.00014142 -0.00169706 -0.01224745 0.00268701 -0.00028284 -O 0.00000000 0.00000000 3.80700000 0.07710000 -0.00100000 0.00030000 -0.00100000 0.07900000 -0.00580000 0.00030000 -0.00580000 -0.15600000 -0.00005774 -0.00141421 -0.00820244 -0.19110102 0.00042426 -0.00134350 -O 0.00000000 0.00000000 10.66800000 0.06740000 -0.05370000 0.07380000 -0.05370000 0.12790000 0.03570000 0.07380000 0.03570000 -0.19530000 0.00000000 -0.07594327 0.05048742 -0.23919267 0.10436896 -0.04277996 -O 2.95500000 1.70600000 8.63200000 0.03140000 -0.00400000 0.01840000 -0.00400000 0.03340000 0.00920000 0.01840000 0.00920000 -0.06480000 0.00000000 -0.00565685 0.01301076 -0.07936347 0.02602153 -0.00141421 -O 2.95500000 1.70600000 1.01800000 0.10720000 -0.00760000 0.01140000 -0.00760000 -0.05420000 -0.16730000 0.01140000 -0.16730000 -0.05300000 0.00000000 -0.01074802 -0.23659793 -0.06491148 0.01612203 0.11412703 -O 0.00000000 3.41200000 13.45700000 0.07230000 -0.04120000 0.08290000 -0.04120000 -0.01950000 -0.10860000 0.08290000 -0.10860000 -0.05280000 0.00000000 -0.05826560 -0.15358359 -0.06466653 0.11723830 0.06491240 -O 0.00000000 3.41200000 5.84300000 0.02680000 -0.00070000 0.00040000 -0.00070000 0.02540000 -0.00090000 0.00040000 -0.00090000 -0.05220000 0.00000000 -0.00098995 -0.00127279 -0.06393168 0.00056569 0.00098995 -O -1.32400000 4.17600000 1.14400000 -0.15590000 -0.07680000 0.15530000 -0.07680000 0.10290000 0.00890000 0.15530000 0.00890000 0.05300000 0.00000000 -0.10861160 0.01258650 0.06491148 0.21962737 -0.18299923 -O 0.00000000 1.88300000 1.14400000 0.03060000 0.00440000 -0.01090000 0.00440000 -0.10600000 0.06250000 -0.01090000 0.06250000 0.07550000 -0.00005774 0.00622254 0.08838835 0.09242741 -0.01541493 0.09659079 -O 1.32400000 4.17600000 1.14400000 -0.15960000 0.07440000 -0.15610000 0.07440000 0.11470000 0.02150000 -0.15610000 0.02150000 0.04490000 -0.00000000 0.10521749 0.03040559 0.05499104 -0.22075874 -0.19395939 -O 4.27800000 0.94200000 13.33100000 -0.14150000 0.00430000 0.08480000 0.00430000 0.08770000 -0.07240000 0.08480000 -0.07240000 0.05380000 -0.00000000 0.00608112 -0.10238906 0.06589127 0.11992531 -0.16206887 -O 1.63100000 0.94200000 13.33100000 -0.19500000 0.01440000 -0.09820000 0.01440000 0.09270000 0.03620000 -0.09820000 0.03620000 0.10230000 -0.00000000 0.02036468 0.05119453 0.12529140 -0.13887577 -0.20343462 -O 2.95500000 3.23400000 13.33100000 0.08180000 0.05950000 -0.08130000 0.05950000 -0.17040000 0.05530000 -0.08130000 0.05530000 0.08860000 -0.00000000 0.08414571 0.07820601 0.10851240 -0.11497556 0.17833233 -O 4.58600000 0.76400000 5.96800000 -0.12470000 0.12670000 -0.08680000 0.12670000 0.01970000 0.05500000 -0.08680000 0.05500000 0.10490000 0.00005774 0.17918086 0.07778175 0.12851656 -0.12275374 -0.10210622 -O 2.95500000 3.58900000 5.96800000 0.09300000 -0.00240000 -0.00030000 -0.00240000 -0.20140000 -0.10140000 -0.00030000 -0.10140000 0.10830000 0.00005774 -0.00339411 -0.14340126 0.13268069 -0.00042426 0.20817224 -O 1.32400000 0.76400000 5.96800000 -0.12430000 -0.12980000 0.08690000 -0.12980000 0.01810000 0.05540000 0.08690000 0.05540000 0.10620000 -0.00000000 -0.18356492 0.07834743 0.13006791 0.12289516 -0.10069201 -O 1.32400000 2.64800000 3.68100000 -0.02110000 0.04940000 -0.01370000 0.04940000 0.04890000 0.03610000 -0.01370000 0.03610000 -0.02790000 0.00005774 0.06986215 0.05105311 -0.03412956 -0.01937473 -0.04949747 -O -1.32400000 2.64800000 3.68100000 -0.02090000 -0.05250000 0.01350000 -0.05250000 0.04760000 0.03700000 0.01350000 0.03700000 -0.02670000 0.00000000 -0.07424621 0.05232590 -0.03270069 0.01909188 -0.04843681 -O 0.00000000 4.94000000 3.68100000 0.07030000 -0.00210000 -0.00080000 -0.00210000 -0.03770000 -0.01970000 -0.00080000 -0.01970000 -0.03260000 0.00000000 -0.00296985 -0.02786001 -0.03992668 -0.00113137 0.07636753 -O 1.63100000 2.47000000 10.79300000 -0.00830000 0.12910000 -0.06370000 0.12910000 0.02490000 -0.01250000 -0.06370000 -0.01250000 -0.01660000 0.00000000 0.18257497 -0.01767767 -0.02033076 -0.09008540 -0.02347595 -O 2.95500000 0.17700000 10.79300000 0.12530000 0.05210000 -0.07030000 0.05210000 -0.10340000 -0.04050000 -0.07030000 -0.04050000 -0.02190000 0.00000000 0.07368053 -0.05727565 -0.02682191 -0.09941921 0.16171532 -O 4.27800000 2.47000000 10.79300000 -0.07640000 -0.13110000 0.05420000 -0.13110000 0.08230000 0.06450000 0.05420000 0.06450000 -0.00580000 -0.00005774 -0.18540340 0.09121677 -0.00714435 0.07665038 -0.11221785 -O -1.63100000 4.35300000 8.50600000 -0.13430000 0.14090000 -0.09840000 0.14090000 0.01760000 0.04780000 -0.09840000 0.04780000 0.11670000 0.00000000 0.19926269 0.06759941 0.14292773 -0.13915861 -0.10740952 -O 1.63100000 4.35300000 8.50600000 -0.13740000 -0.15240000 0.11860000 -0.15240000 0.03100000 0.07390000 0.11860000 0.07390000 0.10640000 -0.00000000 -0.21552615 0.10451038 0.13031285 0.16772573 -0.11907678 -O 0.00000000 1.52800000 8.50600000 0.10110000 0.00150000 -0.01260000 0.00150000 -0.22210000 -0.10770000 -0.01260000 -0.10770000 0.12100000 -0.00000000 0.00212132 -0.15231080 0.14819413 -0.01781909 0.22853691 -Ti 2.95500000 1.70600000 12.06200000 0.11950000 0.03460000 -0.03870000 0.03460000 0.17170000 0.05850000 -0.03870000 0.05850000 -0.29120000 0.00000000 0.04893179 0.08273149 -0.35664571 -0.05473006 -0.03691097 -Ti 0.00000000 3.41200000 2.41200000 0.08000000 0.00030000 -0.00150000 0.00030000 0.16950000 0.08490000 -0.00150000 0.08490000 -0.24950000 0.00000000 0.00042426 0.12006673 -0.30557385 -0.00212132 -0.06328606 -Ti -1.47700000 2.55900000 0.00000000 -0.08310000 -0.24440000 0.01980000 -0.24440000 -0.04630000 -0.09160000 0.01980000 -0.09160000 0.12940000 -0.00000000 -0.34563379 -0.12954196 0.15848199 0.02800143 -0.02602153 -Ti 1.47700000 2.55900000 0.00000000 -0.08920000 0.24090000 -0.02530000 0.24090000 -0.03040000 -0.06870000 -0.02530000 -0.06870000 0.11960000 -0.00000000 0.34068405 -0.09715647 0.14647949 -0.03577960 -0.04157788 -Ti 0.00000000 1.70600000 4.82500000 0.08050000 -0.00010000 0.00020000 -0.00010000 -0.12530000 0.20860000 0.00020000 0.20860000 0.04480000 -0.00000000 -0.00014142 0.29500495 0.05486857 0.00028284 0.14552258 -Ti 1.47700000 4.26500000 4.82500000 -0.07630000 -0.08890000 -0.17890000 -0.08890000 0.03050000 -0.10080000 -0.17890000 -0.10080000 0.04580000 0.00000000 -0.12572359 -0.14255273 0.05609332 -0.25300281 -0.07551900 -Ti -1.47700000 4.26500000 4.82500000 -0.07640000 0.08890000 0.17880000 0.08890000 0.03060000 -0.10070000 0.17880000 -0.10070000 0.04580000 -0.00000000 0.12572359 -0.14241131 0.05609332 0.25286138 -0.07566043 -Ti 2.95500000 3.41200000 9.65000000 0.07510000 -0.01160000 0.02350000 -0.01160000 -0.12510000 0.22120000 0.02350000 0.22120000 0.05000000 -0.00000000 -0.01640488 0.31282404 0.06123724 0.03323402 0.14156278 -Ti 1.47700000 0.85300000 9.65000000 -0.08040000 -0.08840000 -0.20570000 -0.08840000 0.02510000 -0.11660000 -0.20570000 -0.11660000 0.05530000 -0.00000000 -0.12501648 -0.16489730 0.06772839 -0.29090373 -0.07459977 -Ti 4.43200000 0.85300000 9.65000000 -0.08770000 0.08100000 0.20080000 0.08100000 0.03660000 -0.08650000 0.20080000 -0.08650000 0.05110000 0.00000000 0.11455130 -0.12232947 0.06258446 0.28397408 -0.08789337 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00110000 -0.00000000 -0.00000000 -0.00000000 0.00490000 -0.00160000 -0.00000000 -0.00160000 -0.00380000 0.00000000 0.00000000 -0.00226274 -0.00465403 0.00000000 -0.00424264 -Li 1.47700000 4.26500000 12.06200000 -0.00170000 -0.00340000 -0.00130000 -0.00340000 0.00880000 -0.00410000 -0.00130000 -0.00410000 -0.00710000 0.00000000 -0.00480833 -0.00579828 -0.00869569 -0.00183848 -0.00742462 -Li 2.95500000 1.70600000 6.63400000 0.00280000 0.00000000 0.00010000 0.00000000 0.00280000 -0.00000000 0.00010000 -0.00000000 -0.00570000 0.00005774 0.00000000 0.00000000 -0.00694022 0.00014142 0.00000000 -Li 2.95500000 1.70600000 3.01600000 0.01150000 0.00000000 0.00010000 0.00000000 0.00990000 0.00030000 0.00010000 0.00030000 -0.02140000 0.00000000 0.00000000 0.00042426 -0.02620954 0.00014142 0.00113137 -Li 0.00000000 1.70600000 12.06200000 0.00620000 0.00380000 -0.00130000 0.00380000 -0.00100000 0.00480000 -0.00130000 0.00480000 -0.00520000 0.00000000 0.00537401 0.00678823 -0.00636867 -0.00183848 0.00509117 -Li 0.00000000 3.41200000 7.84000000 0.00330000 -0.00010000 -0.00000000 -0.00010000 0.00320000 0.00000000 -0.00000000 0.00000000 -0.00650000 0.00000000 -0.00014142 0.00000000 -0.00796084 0.00000000 0.00007071 -Li 0.00000000 0.00000000 7.23700000 0.00690000 0.00000000 -0.00010000 0.00000000 0.00700000 0.00010000 -0.00010000 0.00010000 -0.01390000 0.00000000 0.00000000 0.00014142 -0.01702395 -0.00014142 -0.00007071 -Li 2.95500000 0.00000000 0.00000000 0.00480000 -0.00020000 -0.00190000 -0.00020000 0.00520000 -0.00120000 -0.00190000 -0.00120000 -0.01000000 0.00000000 -0.00028284 -0.00169706 -0.01224745 -0.00268701 -0.00028284 -O 0.00000000 0.00000000 3.80700000 0.07710000 -0.00090000 -0.00010000 -0.00090000 0.07900000 -0.00580000 -0.00010000 -0.00580000 -0.15600000 -0.00005774 -0.00127279 -0.00820244 -0.19110102 -0.00014142 -0.00134350 -O 0.00000000 0.00000000 10.66800000 0.06740000 0.05180000 -0.07360000 0.05180000 0.12780000 0.03580000 -0.07360000 0.03580000 -0.19520000 0.00000000 0.07325626 0.05062885 -0.23907020 -0.10408612 -0.04270925 -O 2.95500000 1.70600000 8.63200000 0.03140000 0.00260000 -0.01810000 0.00260000 0.03340000 0.00920000 -0.01810000 0.00920000 -0.06480000 0.00000000 0.00367696 0.01301076 -0.07936347 -0.02559727 -0.00141421 -O 2.95500000 1.70600000 1.01800000 0.10720000 0.00630000 -0.01090000 0.00630000 -0.05420000 -0.16730000 -0.01090000 -0.16730000 -0.05300000 0.00000000 0.00890955 -0.23659793 -0.06491148 -0.01541493 0.11412703 -O 0.00000000 3.41200000 13.45700000 0.07230000 0.03990000 -0.08260000 0.03990000 -0.01980000 -0.10850000 -0.08260000 -0.10850000 -0.05260000 0.00005774 0.05642712 -0.15344217 -0.06438076 -0.11681404 0.06512453 -O 0.00000000 3.41200000 5.84300000 0.02680000 -0.00070000 -0.00010000 -0.00070000 0.02540000 -0.00090000 -0.00010000 -0.00090000 -0.05220000 0.00000000 -0.00098995 -0.00127279 -0.06393168 -0.00014142 0.00098995 -O -1.32400000 4.17600000 1.14400000 -0.16000000 -0.07740000 0.15620000 -0.07740000 0.11690000 0.02130000 0.15620000 0.02130000 0.04310000 -0.00000000 -0.10946013 0.03012275 0.05278650 0.22090016 -0.19579787 -O 0.00000000 1.88300000 1.14400000 0.03060000 -0.00840000 0.01080000 -0.00840000 -0.10610000 0.06250000 0.01080000 0.06250000 0.07550000 0.00000000 -0.01187939 0.08838835 0.09246824 0.01527351 0.09666150 -O 1.32400000 4.17600000 1.14400000 -0.15550000 0.07370000 -0.15510000 0.07370000 0.10080000 0.00910000 -0.15510000 0.00910000 0.05470000 0.00000000 0.10422754 0.01286934 0.06699354 -0.21934452 -0.18123147 -O 4.27800000 0.94200000 13.33100000 -0.19550000 -0.01730000 0.09820000 -0.01730000 0.09470000 0.03600000 0.09820000 0.03600000 0.10080000 -0.00000000 -0.02446589 0.05091169 0.12345428 0.13887577 -0.20520239 -O 1.63100000 0.94200000 13.33100000 -0.14100000 -0.00720000 -0.08450000 -0.00720000 0.08570000 -0.07220000 -0.08450000 -0.07220000 0.05530000 -0.00000000 -0.01018234 -0.10210622 0.06772839 -0.11950105 -0.16030111 -O 2.95500000 3.23400000 13.33100000 0.08180000 -0.06350000 0.08120000 -0.06350000 -0.17010000 0.05510000 0.08120000 0.05510000 0.08830000 -0.00000000 -0.08980256 0.07792317 0.10814497 0.11483414 0.17812020 -O 4.58600000 0.76400000 5.96800000 -0.12480000 0.12670000 -0.08690000 0.12670000 0.01980000 0.05550000 -0.08690000 0.05550000 0.10500000 -0.00000000 0.17918086 0.07848885 0.12859821 -0.12289516 -0.10224764 -O 2.95500000 3.58900000 5.96800000 0.09300000 -0.00250000 0.00050000 -0.00250000 -0.20140000 -0.10140000 0.00050000 -0.10140000 0.10830000 0.00005774 -0.00353553 -0.14340126 0.13268069 0.00070711 0.20817224 -O 1.32400000 0.76400000 5.96800000 -0.12420000 -0.12980000 0.08680000 -0.12980000 0.01800000 0.05490000 0.08680000 0.05490000 0.10610000 0.00005774 -0.18356492 0.07764032 0.12998626 0.12275374 -0.10055058 -O 1.32400000 2.64800000 3.68100000 -0.02120000 0.04950000 -0.01340000 0.04950000 0.04920000 0.03690000 -0.01340000 0.03690000 -0.02800000 0.00000000 0.07000357 0.05218448 -0.03429286 -0.01895046 -0.04978032 -O -1.32400000 2.64800000 3.68100000 -0.02070000 -0.05250000 0.01380000 -0.05250000 0.04730000 0.03620000 0.01380000 0.03620000 -0.02660000 0.00000000 -0.07424621 0.05119453 -0.03257821 0.01951615 -0.04808326 -O 0.00000000 4.94000000 3.68100000 0.07030000 -0.00270000 0.00090000 -0.00270000 -0.03770000 -0.01970000 0.00090000 -0.01970000 -0.03260000 0.00000000 -0.00381838 -0.02786001 -0.03992668 0.00127279 0.07636753 -O 1.63100000 2.47000000 10.79300000 -0.07680000 0.12820000 -0.05410000 0.12820000 0.08370000 0.06450000 -0.05410000 0.06450000 -0.00690000 -0.00000000 0.18130218 0.09121677 -0.00845074 -0.07650895 -0.11349064 -O 2.95500000 0.17700000 10.79300000 0.12530000 -0.05690000 0.07040000 -0.05690000 -0.10310000 -0.04060000 0.07040000 -0.04060000 -0.02210000 -0.00005774 -0.08046875 -0.05741707 -0.02710769 0.09956063 0.16150319 -O 4.27800000 2.47000000 10.79300000 -0.00800000 -0.13210000 0.06390000 -0.13210000 0.02350000 -0.01250000 0.06390000 -0.01250000 -0.01560000 0.00005774 -0.18681761 -0.01767767 -0.01906520 0.09036825 -0.02227386 -O -1.63100000 4.35300000 8.50600000 -0.13790000 0.14930000 -0.11860000 0.14930000 0.03270000 0.07410000 -0.11860000 0.07410000 0.10530000 -0.00005774 0.21114208 0.10479322 0.12892481 -0.16772573 -0.12063242 -O 1.63100000 4.35300000 8.50600000 -0.13380000 -0.14400000 0.09840000 -0.14400000 0.01590000 0.04770000 0.09840000 0.04770000 0.11790000 -0.00000000 -0.20364675 0.06745799 0.14439742 0.13915861 -0.10585389 -O 0.00000000 1.52800000 8.50600000 0.10110000 -0.00650000 0.01280000 -0.00650000 -0.22210000 -0.10780000 0.01280000 -0.10780000 0.12100000 -0.00000000 -0.00919239 -0.15245222 0.14819413 0.01810193 0.22853691 -Ti 2.95500000 1.70600000 12.06200000 0.11950000 -0.03470000 0.03880000 -0.03470000 0.17180000 0.05850000 0.03880000 0.05850000 -0.29130000 0.00000000 -0.04907321 0.08273149 -0.35676818 0.05487149 -0.03698168 -Ti 0.00000000 3.41200000 2.41200000 0.08000000 -0.00040000 0.00160000 -0.00040000 0.16950000 0.08490000 0.00160000 0.08490000 -0.24950000 0.00000000 -0.00056569 0.12006673 -0.30557385 0.00226274 -0.06328606 -Ti -1.47700000 2.55900000 0.00000000 -0.08920000 -0.24100000 0.02540000 -0.24100000 -0.03040000 -0.06870000 0.02540000 -0.06870000 0.11960000 -0.00000000 -0.34082547 -0.09715647 0.14647949 0.03592102 -0.04157788 -Ti 1.47700000 2.55900000 0.00000000 -0.08310000 0.24440000 -0.01970000 0.24440000 -0.04630000 -0.09160000 -0.01970000 -0.09160000 0.12940000 -0.00000000 0.34563379 -0.12954196 0.15848199 -0.02786001 -0.02602153 -Ti 0.00000000 1.70600000 4.82500000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.12530000 0.20860000 -0.00000000 0.20860000 0.04480000 -0.00000000 0.00000000 0.29500495 0.05486857 0.00000000 0.14552258 -Ti 1.47700000 4.26500000 4.82500000 -0.07640000 -0.08900000 -0.17870000 -0.08900000 0.03060000 -0.10060000 -0.17870000 -0.10060000 0.04590000 -0.00005774 -0.12586501 -0.14226988 0.05617496 -0.25271996 -0.07566043 -Ti -1.47700000 4.26500000 4.82500000 -0.07630000 0.08890000 0.17900000 0.08890000 0.03060000 -0.10080000 0.17900000 -0.10080000 0.04580000 -0.00005774 0.12572359 -0.14255273 0.05605249 0.25314423 -0.07558971 -Ti 2.95500000 3.41200000 9.65000000 0.07510000 0.01150000 -0.02350000 0.01150000 -0.12510000 0.22120000 -0.02350000 0.22120000 0.05000000 -0.00000000 0.01626346 0.31282404 0.06123724 -0.03323402 0.14156278 -Ti 1.47700000 0.85300000 9.65000000 -0.08770000 -0.08110000 -0.20070000 -0.08110000 0.03650000 -0.08650000 -0.20070000 -0.08650000 0.05110000 0.00005774 -0.11469272 -0.12232947 0.06262529 -0.28383266 -0.08782266 -Ti 4.43200000 0.85300000 9.65000000 -0.08040000 0.08840000 0.20580000 0.08840000 0.02520000 -0.11660000 0.20580000 -0.11660000 0.05520000 -0.00000000 0.12501648 -0.16489730 0.06760592 0.29104515 -0.07467048 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00150000 0.00000000 -0.00000000 0.00000000 0.00510000 -0.00140000 -0.00000000 -0.00140000 -0.00360000 0.00000000 0.00000000 -0.00197990 -0.00440908 0.00000000 -0.00466690 -Li 0.00000000 0.00000000 12.66500000 -0.00070000 0.00000000 -0.00000000 0.00000000 0.00640000 -0.00180000 -0.00000000 -0.00180000 -0.00570000 0.00000000 0.00000000 -0.00254558 -0.00698105 0.00000000 -0.00502046 -Li 2.95500000 1.70600000 4.82500000 -0.00900000 0.00000000 -0.00000000 0.00000000 -0.00790000 0.00010000 -0.00000000 0.00010000 0.01690000 -0.00000000 0.00000000 0.00014142 0.02069819 0.00000000 -0.00077782 -Li 2.95500000 1.70600000 3.01600000 -0.00540000 0.00000000 -0.00000000 0.00000000 -0.00610000 0.00050000 -0.00000000 0.00050000 0.01150000 -0.00000000 0.00000000 0.00070711 0.01408457 0.00000000 0.00049497 -Li 0.00000000 3.41200000 11.45900000 0.01000000 -0.00000000 -0.00000000 -0.00000000 0.00940000 0.00030000 -0.00000000 0.00030000 -0.01940000 0.00000000 0.00000000 0.00042426 -0.02376005 0.00000000 0.00042426 -Li 0.00000000 3.41200000 7.84000000 0.00020000 0.00000000 -0.00000000 0.00000000 0.00110000 0.00000000 -0.00000000 0.00000000 -0.00130000 0.00000000 0.00000000 0.00000000 -0.00159217 0.00000000 -0.00063640 -Li 0.00000000 0.00000000 7.23700000 0.00590000 -0.00000000 0.00000000 -0.00000000 0.00690000 0.00000000 0.00000000 0.00000000 -0.01280000 0.00000000 0.00000000 0.00000000 -0.01567673 0.00000000 -0.00070711 -Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 0.00000000 -0.00000000 0.00570000 -0.00170000 0.00000000 -0.00170000 -0.01150000 0.00000000 0.00000000 -0.00240416 -0.01408457 0.00000000 0.00007071 -O 0.00000000 0.00000000 3.80700000 0.10130000 0.00000000 -0.00000000 0.00000000 0.11440000 -0.00590000 -0.00000000 -0.00590000 -0.21570000 0.00000000 0.00000000 -0.00834386 -0.26417747 0.00000000 -0.00926310 -O 0.00000000 0.00000000 10.66800000 0.07770000 0.00000000 0.00000000 0.00000000 0.09110000 -0.00660000 0.00000000 -0.00660000 -0.16880000 0.00000000 0.00000000 -0.00933381 -0.20673693 0.00000000 -0.00947523 -O 2.95500000 1.70600000 8.63200000 0.01540000 0.00000000 -0.00000000 0.00000000 0.02560000 -0.00100000 -0.00000000 -0.00100000 -0.04100000 0.00000000 0.00000000 -0.00141421 -0.05021454 0.00000000 -0.00721249 -O 2.95500000 1.70600000 1.01800000 0.10070000 -0.00000000 0.00000000 -0.00000000 -0.05640000 -0.17440000 0.00000000 -0.17440000 -0.04430000 0.00000000 0.00000000 -0.24663885 -0.05425620 0.00000000 0.11108648 -O 0.00000000 3.41200000 13.45700000 0.08340000 0.00000000 -0.00000000 0.00000000 -0.04510000 -0.17380000 -0.00000000 -0.17380000 -0.03830000 0.00000000 0.00000000 -0.24579032 -0.04690773 0.00000000 0.09086322 -O 0.00000000 3.41200000 5.84300000 0.01210000 0.00000000 -0.00000000 0.00000000 0.02250000 -0.00110000 -0.00000000 -0.00110000 -0.03460000 0.00000000 0.00000000 -0.00155563 -0.04237617 0.00000000 -0.00735391 -O -1.32400000 4.17600000 1.14400000 -0.15480000 -0.08910000 0.16710000 -0.08910000 0.10510000 0.00240000 0.16710000 0.00240000 0.04970000 -0.00000000 -0.12600643 0.00339411 0.06086982 0.23631509 -0.18377705 -O 0.00000000 1.88300000 1.14400000 0.00980000 0.00000000 -0.00000000 0.00000000 -0.07390000 0.08330000 -0.00000000 0.08330000 0.06410000 -0.00000000 0.00000000 0.11780399 0.07850615 0.00000000 0.05918484 -O 1.32400000 4.17600000 1.14400000 -0.15480000 0.08910000 -0.16710000 0.08910000 0.10510000 0.00240000 -0.16710000 0.00240000 0.04970000 -0.00000000 0.12600643 0.00339411 0.06086982 -0.23631509 -0.18377705 -O 4.27800000 0.94200000 13.33100000 -0.14670000 -0.08680000 0.17350000 -0.08680000 0.11800000 0.02060000 0.17350000 0.02060000 0.02870000 0.00000000 -0.12275374 0.02913280 0.03515018 0.24536605 -0.18717116 -O 1.63100000 0.94200000 13.33100000 -0.14670000 0.08680000 -0.17350000 0.08680000 0.11800000 0.02060000 -0.17350000 0.02060000 0.02870000 0.00000000 0.12275374 0.02913280 0.03515018 -0.24536605 -0.18717116 -O 2.95500000 3.23400000 13.33100000 0.03010000 0.00000000 -0.00000000 0.00000000 -0.08910000 0.06180000 -0.00000000 0.06180000 0.05900000 0.00000000 0.00000000 0.08739840 0.07225995 0.00000000 0.08428713 -O 4.58600000 0.76400000 5.96800000 -0.12280000 0.06940000 0.01710000 0.06940000 -0.03300000 -0.00380000 0.01710000 -0.00380000 0.15580000 -0.00000000 0.09814642 -0.00537401 0.19081525 0.02418305 -0.06349819 -O 2.95500000 3.58900000 5.96800000 -0.00600000 0.00000000 0.00000000 0.00000000 -0.15180000 0.01830000 0.00000000 0.01830000 0.15780000 -0.00000000 0.00000000 0.02588011 0.19326474 0.00000000 0.10309617 -O 1.32400000 0.76400000 5.96800000 -0.12280000 -0.06940000 -0.01710000 -0.06940000 -0.03300000 -0.00380000 -0.01710000 -0.00380000 0.15580000 -0.00000000 -0.09814642 -0.00537401 0.19081525 -0.02418305 -0.06349819 -O 1.32400000 2.64800000 3.68100000 0.04520000 -0.02420000 0.06790000 -0.02420000 0.04390000 -0.01120000 0.06790000 -0.01120000 -0.08900000 -0.00005774 -0.03422397 -0.01583919 -0.10904312 0.09602510 0.00091924 -O -1.32400000 2.64800000 3.68100000 0.04520000 0.02420000 -0.06790000 0.02420000 0.04390000 -0.01120000 -0.06790000 -0.01120000 -0.08900000 -0.00005774 0.03422397 -0.01583919 -0.10904312 -0.09602510 0.00091924 -O 0.00000000 4.94000000 3.68100000 0.01120000 -0.00000000 -0.00000000 -0.00000000 0.08430000 0.07320000 -0.00000000 0.07320000 -0.09540000 -0.00005774 0.00000000 0.10352043 -0.11688149 0.00000000 -0.05168951 -O 1.63100000 2.47000000 10.79300000 -0.01890000 0.04830000 0.01380000 0.04830000 0.06090000 0.02540000 0.01380000 0.02540000 -0.04190000 -0.00005774 0.06830652 0.03592102 -0.05135763 0.01951615 -0.05642712 -O 2.95500000 0.17700000 10.79300000 0.06880000 0.00000000 0.00000000 0.00000000 -0.02120000 0.01080000 0.00000000 0.01080000 -0.04770000 0.00005774 0.00000000 0.01527351 -0.05837951 0.00000000 0.06363961 -O 4.27800000 2.47000000 10.79300000 -0.01890000 -0.04830000 -0.01380000 -0.04830000 0.06090000 0.02540000 -0.01380000 0.02540000 -0.04190000 -0.00005774 -0.06830652 0.03592102 -0.05135763 -0.01951615 -0.05642712 -O -1.63100000 4.35300000 8.50600000 -0.12720000 0.10680000 -0.07420000 0.10680000 0.00650000 0.04830000 -0.07420000 0.04830000 0.12070000 -0.00000000 0.15103801 0.06830652 0.14782671 -0.10493465 -0.09454018 -O 1.63100000 4.35300000 8.50600000 -0.12720000 -0.10680000 0.07420000 -0.10680000 0.00650000 0.04830000 0.07420000 0.04830000 0.12070000 -0.00000000 -0.15103801 0.06830652 0.14782671 0.10493465 -0.09454018 -O 0.00000000 1.52800000 8.50600000 0.05600000 0.00000000 -0.00000000 0.00000000 -0.17810000 -0.08700000 -0.00000000 -0.08700000 0.12210000 0.00000000 0.00000000 -0.12303658 0.14954135 0.00000000 0.16553370 -Ti 2.95500000 1.70600000 12.06200000 0.08370000 -0.00000000 -0.00000000 -0.00000000 0.16840000 0.08770000 -0.00000000 0.08770000 -0.25210000 0.00000000 0.00000000 0.12402653 -0.30875818 0.00000000 -0.05989194 -Ti 0.00000000 3.41200000 2.41200000 0.07030000 0.00000000 -0.00000000 0.00000000 0.17070000 0.08150000 -0.00000000 0.08150000 -0.24100000 0.00000000 0.00000000 0.11525841 -0.29516351 0.00000000 -0.07099352 -Ti -1.47700000 2.55900000 0.00000000 -0.08030000 -0.24840000 0.04920000 -0.24840000 -0.03860000 -0.07620000 0.04920000 -0.07620000 0.11890000 -0.00000000 -0.35129065 -0.10776307 0.14562217 0.06957931 -0.02948635 -Ti 1.47700000 2.55900000 0.00000000 -0.08030000 0.24840000 -0.04920000 0.24840000 -0.03860000 -0.07620000 -0.04920000 -0.07620000 0.11890000 -0.00000000 0.35129065 -0.10776307 0.14562217 -0.06957931 -0.02948635 -Ti 0.00000000 1.70600000 4.82500000 0.11030000 0.00000000 -0.00000000 0.00000000 -0.16040000 0.18740000 -0.00000000 0.18740000 0.05020000 -0.00005774 0.00000000 0.26502362 0.06144137 0.00000000 0.19141381 -Ti 1.47700000 4.26500000 4.82500000 -0.09530000 -0.11730000 -0.16050000 -0.11730000 0.04440000 -0.09030000 -0.16050000 -0.09030000 0.05090000 -0.00000000 -0.16588725 -0.12770348 0.06233951 -0.22698128 -0.09878282 -Ti -1.47700000 4.26500000 4.82500000 -0.09530000 0.11730000 0.16050000 0.11730000 0.04440000 -0.09030000 0.16050000 -0.09030000 0.05090000 -0.00000000 0.16588725 -0.12770348 0.06233951 0.22698128 -0.09878282 -Ti 2.95500000 3.41200000 9.65000000 0.10290000 0.00000000 0.00000000 0.00000000 -0.14830000 0.18380000 0.00000000 0.18380000 0.04540000 -0.00000000 0.00000000 0.25993245 0.05560342 0.00000000 0.17762522 -Ti 1.47700000 0.85300000 9.65000000 -0.08800000 -0.10810000 -0.15700000 -0.10810000 0.04130000 -0.08770000 -0.15700000 -0.08770000 0.04670000 -0.00000000 -0.15287649 -0.12402653 0.05719559 -0.22203153 -0.09142891 -Ti 4.43200000 0.85300000 9.65000000 -0.08800000 0.10810000 0.15700000 0.10810000 0.04130000 -0.08770000 0.15700000 -0.08770000 0.04670000 -0.00000000 0.15287649 -0.12402653 0.05719559 0.22203153 -0.09142891 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00100000 0.00000000 0.00010000 0.00000000 0.00510000 -0.00160000 0.00010000 -0.00160000 -0.00410000 0.00000000 0.00000000 -0.00226274 -0.00502145 0.00014142 -0.00431335 -Li 0.00000000 0.00000000 12.66500000 -0.00220000 0.00000000 0.00010000 0.00000000 0.00370000 -0.00140000 0.00010000 -0.00140000 -0.00150000 0.00000000 0.00000000 -0.00197990 -0.00183712 0.00014142 -0.00417193 -Li -1.47700000 2.55900000 7.23700000 0.01910000 0.02290000 0.00490000 0.02290000 -0.00730000 0.00280000 0.00490000 0.00280000 -0.01190000 0.00005774 0.03238549 0.00395980 -0.01453364 0.00692965 0.01866762 -Li 2.95500000 1.70600000 3.01600000 0.01130000 -0.00010000 -0.00000000 -0.00010000 0.00990000 0.00030000 -0.00000000 0.00030000 -0.02120000 0.00000000 -0.00014142 0.00042426 -0.02596459 0.00000000 0.00098995 -Li 0.00000000 3.41200000 11.45900000 0.01200000 -0.00010000 -0.00000000 -0.00010000 0.01050000 0.00040000 -0.00000000 0.00040000 -0.02250000 0.00000000 -0.00014142 0.00056569 -0.02755676 0.00000000 0.00106066 -Li 0.00000000 3.41200000 7.84000000 0.02180000 0.02130000 0.01540000 0.02130000 -0.00270000 0.00890000 0.01540000 0.00890000 -0.01910000 0.00000000 0.03012275 0.01258650 -0.02339263 0.02177889 0.01732412 -Li 0.00000000 0.00000000 7.23700000 0.00560000 -0.00340000 0.00100000 -0.00340000 0.00970000 0.00060000 0.00100000 0.00060000 -0.01520000 -0.00005774 -0.00480833 0.00084853 -0.01865695 0.00141421 -0.00289914 -Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00010000 0.00000000 -0.00010000 0.00480000 -0.00180000 0.00000000 -0.00180000 -0.01070000 0.00005774 -0.00014142 -0.00254558 -0.01306395 0.00000000 0.00070711 -O 0.00000000 0.00000000 3.80700000 0.08850000 0.00280000 -0.02010000 0.00280000 0.08610000 -0.01770000 -0.02010000 -0.01770000 -0.17470000 0.00005774 0.00395980 -0.02503158 -0.21392210 -0.02842569 0.00169706 -O 0.00000000 0.00000000 10.66800000 0.07620000 0.00170000 -0.02040000 0.00170000 0.07480000 -0.01690000 -0.02040000 -0.01690000 -0.15100000 0.00000000 0.00240416 -0.02390021 -0.18493648 -0.02884996 0.00098995 -O 2.95500000 1.70600000 8.63200000 0.07920000 0.05510000 -0.05980000 0.05510000 0.01330000 -0.03590000 -0.05980000 -0.03590000 -0.09250000 0.00000000 0.07792317 -0.05077027 -0.11328890 -0.08456997 0.04659834 -O 2.95500000 1.70600000 1.01800000 0.08930000 -0.00080000 -0.00030000 -0.00080000 -0.05200000 -0.17110000 -0.00030000 -0.17110000 -0.03730000 0.00000000 -0.00113137 -0.24197194 -0.04568298 -0.00042426 0.09991419 -O 0.00000000 3.41200000 13.45700000 0.10390000 -0.00080000 -0.00030000 -0.00080000 -0.05930000 -0.17410000 -0.00030000 -0.17410000 -0.04470000 0.00005774 -0.00113137 -0.24621458 -0.05470527 -0.00042426 0.11539983 -O 0.00000000 3.41200000 5.84300000 0.03910000 0.05970000 -0.09210000 0.05970000 -0.03180000 -0.05410000 -0.09210000 -0.05410000 -0.00730000 0.00000000 0.08442855 -0.07650895 -0.00894064 -0.13024907 0.05013387 -O -1.32400000 4.17600000 1.14400000 -0.14600000 -0.08430000 0.16840000 -0.08430000 0.10670000 0.01880000 0.16840000 0.01880000 0.03930000 -0.00000000 -0.11921820 0.02658721 0.04813247 0.23815356 -0.17868588 -O 0.00000000 1.88300000 1.14400000 0.03050000 -0.00200000 0.00120000 -0.00200000 -0.09940000 0.06380000 0.00120000 0.06380000 0.06890000 -0.00000000 -0.00282843 0.09022683 0.08438492 0.00169706 0.09185317 -O 1.32400000 4.17600000 1.14400000 -0.14520000 0.08110000 -0.16810000 0.08110000 0.10460000 0.01670000 -0.16810000 0.01670000 0.04060000 0.00000000 0.11469272 0.02361737 0.04972464 -0.23772930 -0.17663527 -O 4.27800000 0.94200000 13.33100000 -0.15690000 -0.08220000 0.16420000 -0.08220000 0.10050000 0.00810000 0.16420000 0.00810000 0.05640000 0.00000000 -0.11624835 0.01145513 0.06907561 0.23221387 -0.18200929 -O 1.63100000 0.94200000 13.33100000 -0.15610000 0.07920000 -0.16420000 0.07920000 0.09880000 0.00550000 -0.16420000 0.00550000 0.05730000 -0.00000000 0.11200571 0.00777817 0.07017788 -0.23221387 -0.18024152 -O 2.95500000 3.23400000 13.33100000 0.01950000 -0.00190000 0.00130000 -0.00190000 -0.09380000 0.07380000 0.00130000 0.07380000 0.07420000 0.00005774 -0.00268701 0.10436896 0.09091689 0.00183848 0.08011520 -O 4.58600000 0.76400000 5.96800000 -0.20330000 0.15720000 -0.08510000 0.15720000 0.09410000 0.12260000 -0.08510000 0.12260000 0.10920000 -0.00000000 0.22231437 0.17338258 0.13374214 -0.12034957 -0.21029356 -O 2.95500000 3.58900000 5.96800000 0.15590000 -0.05330000 0.05950000 -0.05330000 -0.26820000 -0.13380000 0.05950000 -0.13380000 0.11230000 -0.00000000 -0.07537758 -0.18922177 0.13753885 0.08414571 0.29988399 -O 1.32400000 0.76400000 5.96800000 -0.14190000 -0.13630000 0.08880000 -0.13630000 0.00760000 0.05680000 0.08880000 0.05680000 0.13430000 -0.00000000 -0.19275731 0.08032733 0.16448324 0.12558216 -0.10571246 -O 1.32400000 2.64800000 3.68100000 -0.03400000 0.05510000 -0.02150000 0.05510000 0.05500000 0.05470000 -0.02150000 0.05470000 -0.02100000 0.00000000 0.07792317 0.07735748 -0.02571964 -0.03040559 -0.06293250 -O -1.32400000 2.64800000 3.68100000 -0.02670000 -0.05490000 0.00860000 -0.05490000 0.04430000 0.03660000 0.00860000 0.03660000 -0.01750000 -0.00005774 -0.07764032 0.05176022 -0.02147386 0.01216224 -0.05020458 -O 0.00000000 4.94000000 3.68100000 0.07620000 -0.00820000 0.01000000 -0.00820000 -0.05070000 -0.03480000 0.01000000 -0.03480000 -0.02550000 0.00000000 -0.01159655 -0.04921463 -0.03123099 0.01414214 0.08973185 -O 1.63100000 2.47000000 10.79300000 -0.03020000 0.05260000 -0.01170000 0.05260000 0.05780000 0.04570000 -0.01170000 0.04570000 -0.02760000 0.00000000 0.07438763 0.06462956 -0.03380296 -0.01654630 -0.06222540 -O 2.95500000 0.17700000 10.79300000 0.07900000 -0.00800000 0.01120000 -0.00800000 -0.04550000 -0.02420000 0.01120000 -0.02420000 -0.03350000 0.00000000 -0.01131371 -0.03422397 -0.04102895 0.01583919 0.08803479 -O 4.27800000 2.47000000 10.79300000 -0.02180000 -0.05040000 -0.00140000 -0.05040000 0.04640000 0.02610000 -0.00140000 0.02610000 -0.02460000 0.00000000 -0.07127636 0.03691097 -0.03012872 -0.00197990 -0.04822468 -O -1.63100000 4.35300000 8.50600000 -0.17170000 0.08870000 -0.09590000 0.08870000 0.10600000 0.13540000 -0.09590000 0.13540000 0.06570000 0.00000000 0.12544074 0.19148452 0.08046574 -0.13562308 -0.19636355 -O 1.63100000 4.35300000 8.50600000 -0.10870000 -0.10600000 0.07390000 -0.10600000 0.00570000 0.04820000 0.07390000 0.04820000 0.10300000 0.00000000 -0.14990664 0.06816509 0.12614872 0.10451038 -0.08089302 -O 0.00000000 1.52800000 8.50600000 0.11290000 -0.07910000 0.06510000 -0.07910000 -0.18220000 -0.14970000 0.06510000 -0.14970000 0.06930000 -0.00000000 -0.11186429 -0.21170777 0.08487482 0.09206530 0.20866721 -Ti 2.95500000 1.70600000 12.06200000 0.07560000 0.00040000 0.00120000 0.00040000 0.17060000 0.08300000 0.00120000 0.08300000 -0.24610000 -0.00005774 0.00056569 0.11737973 -0.30145054 0.00169706 -0.06717514 -Ti 0.00000000 3.41200000 2.41200000 0.08150000 -0.00000000 0.00100000 -0.00000000 0.16970000 0.08700000 0.00100000 0.08700000 -0.25130000 0.00005774 0.00000000 0.12303658 -0.30773756 0.00141421 -0.06236682 -Ti -1.47700000 2.55900000 0.00000000 -0.08120000 -0.24910000 0.04890000 -0.24910000 -0.03760000 -0.07580000 0.04890000 -0.07580000 0.11880000 -0.00000000 -0.35228060 -0.10719739 0.14549969 0.06915504 -0.03082986 -Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24900000 -0.04850000 0.24900000 -0.03790000 -0.07600000 -0.04850000 -0.07600000 0.11890000 -0.00000000 0.35213918 -0.10748023 0.14562217 -0.06858936 -0.03047630 -Ti 0.00000000 1.70600000 4.82500000 0.06870000 0.01320000 -0.02310000 0.01320000 -0.12830000 0.24740000 -0.02310000 0.24740000 0.05960000 0.00000000 0.01866762 0.34987644 0.07299479 -0.03266833 0.13930004 -Ti 1.47700000 4.26500000 4.82500000 -0.07910000 -0.08130000 -0.20410000 -0.08130000 0.01890000 -0.11540000 -0.20410000 -0.11540000 0.06020000 -0.00000000 -0.11497556 -0.16320025 0.07372964 -0.28864099 -0.06929646 -Ti -1.47700000 4.26500000 4.82500000 -0.07010000 0.09130000 0.20060000 0.09130000 0.00920000 -0.13970000 0.20060000 -0.13970000 0.06100000 -0.00005774 0.12911770 -0.19756563 0.07466861 0.28369124 -0.05607357 -Ti 2.95500000 3.41200000 9.65000000 0.08850000 0.01500000 -0.02280000 0.01500000 -0.12990000 0.20370000 -0.02280000 0.20370000 0.04150000 -0.00005774 0.02121320 0.28807530 0.05078609 -0.03224407 0.15443212 -Ti 1.47700000 0.85300000 9.65000000 -0.07760000 -0.09500000 -0.15970000 -0.09500000 0.03590000 -0.09020000 -0.15970000 -0.09020000 0.04170000 -0.00000000 -0.13435029 -0.12756206 0.05107186 -0.22584991 -0.08025662 -Ti 4.43200000 0.85300000 9.65000000 -0.06510000 0.10240000 0.16340000 0.10240000 0.02250000 -0.11830000 0.16340000 -0.11830000 0.04250000 0.00005774 0.14481547 -0.16730146 0.05209248 0.23108250 -0.06194255 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00100000 -0.00000000 -0.00010000 -0.00000000 0.00510000 -0.00160000 -0.00010000 -0.00160000 -0.00410000 0.00000000 0.00000000 -0.00226274 -0.00502145 -0.00014142 -0.00431335 -Li 0.00000000 0.00000000 12.66500000 -0.00220000 -0.00000000 -0.00010000 -0.00000000 0.00370000 -0.00140000 -0.00010000 -0.00140000 -0.00150000 0.00000000 0.00000000 -0.00197990 -0.00183712 -0.00014142 -0.00417193 -Li 1.47700000 2.55900000 7.23700000 0.01920000 -0.02290000 -0.00480000 -0.02290000 -0.00730000 0.00280000 -0.00480000 0.00280000 -0.01190000 0.00000000 -0.03238549 0.00395980 -0.01457446 -0.00678823 0.01873833 -Li 2.95500000 1.70600000 3.01600000 0.01130000 0.00010000 0.00000000 0.00010000 0.00990000 0.00030000 0.00000000 0.00030000 -0.02120000 0.00000000 0.00014142 0.00042426 -0.02596459 0.00000000 0.00098995 -Li 0.00000000 3.41200000 11.45900000 0.01200000 0.00010000 0.00000000 0.00010000 0.01050000 0.00040000 0.00000000 0.00040000 -0.02250000 0.00000000 0.00014142 0.00056569 -0.02755676 0.00000000 0.00106066 -Li 0.00000000 3.41200000 7.84000000 0.02180000 -0.02130000 -0.01540000 -0.02130000 -0.00270000 0.00890000 -0.01540000 0.00890000 -0.01910000 0.00000000 -0.03012275 0.01258650 -0.02339263 -0.02177889 0.01732412 -Li 0.00000000 0.00000000 7.23700000 0.00560000 0.00340000 -0.00100000 0.00340000 0.00960000 0.00060000 -0.00100000 0.00060000 -0.01520000 0.00000000 0.00480833 0.00084853 -0.01861612 -0.00141421 -0.00282843 -Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 -0.00000000 -0.00000000 0.00480000 -0.00180000 -0.00000000 -0.00180000 -0.01070000 0.00005774 0.00000000 -0.00254558 -0.01306395 0.00000000 0.00070711 -O 0.00000000 0.00000000 3.80700000 0.08860000 -0.00480000 0.02040000 -0.00480000 0.08610000 -0.01770000 0.02040000 -0.01770000 -0.17470000 0.00000000 -0.00678823 -0.02503158 -0.21396293 0.02884996 0.00176777 -O 0.00000000 0.00000000 10.66800000 0.07620000 -0.00360000 0.02070000 -0.00360000 0.07480000 -0.01690000 0.02070000 -0.01690000 -0.15100000 0.00000000 -0.00509117 -0.02390021 -0.18493648 0.02927422 0.00098995 -O 2.95500000 1.70600000 8.63200000 0.07920000 -0.05650000 0.06010000 -0.05650000 0.01340000 -0.03600000 0.06010000 -0.03600000 -0.09260000 0.00000000 -0.07990307 -0.05091169 -0.11341138 0.08499424 0.04652763 -O 2.95500000 1.70600000 1.01800000 0.08930000 -0.00050000 0.00080000 -0.00050000 -0.05200000 -0.17110000 0.00080000 -0.17110000 -0.03730000 0.00000000 -0.00070711 -0.24197194 -0.04568298 0.00113137 0.09991419 -O 0.00000000 3.41200000 13.45700000 0.10390000 -0.00050000 0.00080000 -0.00050000 -0.05930000 -0.17410000 0.00080000 -0.17410000 -0.04470000 0.00005774 -0.00070711 -0.24621458 -0.05470527 0.00113137 0.11539983 -O 0.00000000 3.41200000 5.84300000 0.03910000 -0.06090000 0.09240000 -0.06090000 -0.03170000 -0.05420000 0.09240000 -0.05420000 -0.00740000 0.00000000 -0.08612561 -0.07665038 -0.00906311 0.13067333 0.05006316 -O -1.32400000 4.17600000 1.14400000 -0.14560000 -0.08420000 0.16820000 -0.08420000 0.10680000 0.01640000 0.16820000 0.01640000 0.03880000 -0.00000000 -0.11907678 0.02319310 0.04752010 0.23787072 -0.17847375 -O 0.00000000 1.88300000 1.14400000 0.03050000 -0.00190000 -0.00130000 -0.00190000 -0.09940000 0.06380000 -0.00130000 0.06380000 0.06890000 -0.00000000 -0.00268701 0.09022683 0.08438492 -0.00183848 0.09185317 -O 1.32400000 4.17600000 1.14400000 -0.14560000 0.08130000 -0.16830000 0.08130000 0.10450000 0.01900000 -0.16830000 0.01900000 0.04110000 0.00000000 0.11497556 0.02687006 0.05033701 -0.23801214 -0.17684741 -O 4.27800000 0.94200000 13.33100000 -0.15650000 -0.08220000 0.16440000 -0.08220000 0.10090000 0.00520000 0.16440000 0.00520000 0.05560000 -0.00000000 -0.11624835 0.00735391 0.06809581 0.23249671 -0.18200929 -O 1.63100000 0.94200000 13.33100000 -0.15650000 0.07920000 -0.16410000 0.07920000 0.09830000 0.00830000 -0.16410000 0.00830000 0.05820000 -0.00000000 0.11200571 0.01173797 0.07128015 -0.23207245 -0.18017081 -O 2.95500000 3.23400000 13.33100000 0.01950000 -0.00210000 -0.00150000 -0.00210000 -0.09380000 0.07380000 -0.00150000 0.07380000 0.07420000 0.00005774 -0.00296985 0.10436896 0.09091689 -0.00212132 0.08011520 -O 4.58600000 0.76400000 5.96800000 -0.14250000 0.13320000 -0.08880000 0.13320000 0.00940000 0.05690000 -0.08880000 0.05690000 0.13310000 -0.00000000 0.18837325 0.08046875 0.16301354 -0.12558216 -0.10740952 -O 2.95500000 3.58900000 5.96800000 0.15580000 0.04850000 -0.05920000 0.04850000 -0.26840000 -0.13370000 -0.05920000 -0.13370000 0.11260000 0.00000000 0.06858936 -0.18908035 0.13790627 -0.08372144 0.29995470 -O 1.32400000 0.76400000 5.96800000 -0.20270000 -0.16030000 0.08500000 -0.16030000 0.09240000 0.12250000 0.08500000 0.12250000 0.11040000 -0.00005774 -0.22669843 0.17324116 0.13517101 0.12020815 -0.20866721 -O 1.32400000 2.64800000 3.68100000 -0.02710000 0.05180000 -0.00850000 0.05180000 0.04580000 0.03660000 -0.00850000 0.03660000 -0.01880000 0.00005774 0.07325626 0.05176022 -0.02298438 -0.01202082 -0.05154808 -O -1.32400000 2.64800000 3.68100000 -0.03370000 -0.05810000 0.02150000 -0.05810000 0.05350000 0.05470000 0.02150000 0.05470000 -0.01980000 0.00000000 -0.08216581 0.07735748 -0.02424995 0.03040559 -0.06165971 -O 0.00000000 4.94000000 3.68100000 0.07620000 0.00340000 -0.00990000 0.00340000 -0.05070000 -0.03480000 -0.00990000 -0.03480000 -0.02550000 0.00000000 0.00480833 -0.04921463 -0.03123099 -0.01400071 0.08973185 -O 1.63100000 2.47000000 10.79300000 -0.02220000 0.04730000 0.00150000 0.04730000 0.04800000 0.02610000 0.00150000 0.02610000 -0.02590000 0.00005774 0.06689230 0.03691097 -0.03168007 0.00212132 -0.04963890 -O 2.95500000 0.17700000 10.79300000 0.07900000 0.00320000 -0.01110000 0.00320000 -0.04550000 -0.02420000 -0.01110000 -0.02420000 -0.03350000 0.00000000 0.00452548 -0.03422397 -0.04102895 -0.01569777 0.08803479 -O 4.27800000 2.47000000 10.79300000 -0.02980000 -0.05570000 0.01170000 -0.05570000 0.05630000 0.04570000 0.01170000 0.04570000 -0.02640000 -0.00005774 -0.07877170 0.06462956 -0.03237409 0.01654630 -0.06088189 -O -1.63100000 4.35300000 8.50600000 -0.10920000 0.10280000 -0.07390000 0.10280000 0.00750000 0.04830000 -0.07390000 0.04830000 0.10160000 0.00005774 0.14538115 0.06830652 0.12447490 -0.10451038 -0.08251936 -O 1.63100000 4.35300000 8.50600000 -0.17120000 -0.09180000 0.09580000 -0.09180000 0.10430000 0.13530000 0.09580000 0.13530000 0.06690000 -0.00000000 -0.12982481 0.19134309 0.08193543 0.13548166 -0.19480792 -O 0.00000000 1.52800000 8.50600000 0.11280000 0.07430000 -0.06480000 0.07430000 -0.18230000 -0.14960000 -0.06480000 -0.14960000 0.06950000 -0.00000000 0.10507607 -0.21156635 0.08511977 -0.09164104 0.20866721 -Ti 2.95500000 1.70600000 12.06200000 0.07560000 -0.00050000 -0.00110000 -0.00050000 0.17060000 0.08300000 -0.00110000 0.08300000 -0.24610000 -0.00005774 -0.00070711 0.11737973 -0.30145054 -0.00155563 -0.06717514 -Ti 0.00000000 3.41200000 2.41200000 0.08150000 -0.00010000 -0.00090000 -0.00010000 0.16970000 0.08700000 -0.00090000 0.08700000 -0.25130000 0.00005774 -0.00014142 0.12303658 -0.30773756 -0.00127279 -0.06236682 -Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24910000 0.04860000 -0.24910000 -0.03780000 -0.07610000 0.04860000 -0.07610000 0.11880000 0.00000000 -0.35228060 -0.10762165 0.14549969 0.06873078 -0.03054701 -Ti 1.47700000 2.55900000 0.00000000 -0.08120000 0.24900000 -0.04870000 0.24900000 -0.03770000 -0.07580000 -0.04870000 -0.07580000 0.11890000 -0.00000000 0.35213918 -0.10719739 0.14562217 -0.06887220 -0.03075914 -Ti 0.00000000 1.70600000 4.82500000 0.06870000 -0.01340000 0.02320000 -0.01340000 -0.12820000 0.24740000 0.02320000 0.24740000 0.05960000 -0.00005774 -0.01895046 0.34987644 0.07295397 0.03280975 0.13922933 -Ti 1.47700000 4.26500000 4.82500000 -0.07010000 -0.09140000 -0.20050000 -0.09140000 0.00910000 -0.13970000 -0.20050000 -0.13970000 0.06100000 -0.00000000 -0.12925912 -0.19756563 0.07470944 -0.28354982 -0.05600286 -Ti -1.47700000 4.26500000 4.82500000 -0.07910000 0.08120000 0.20420000 0.08120000 0.01890000 -0.11540000 0.20420000 -0.11540000 0.06020000 -0.00000000 0.11483414 -0.16320025 0.07372964 0.28878241 -0.06929646 -Ti 2.95500000 3.41200000 9.65000000 0.08850000 -0.01510000 0.02290000 -0.01510000 -0.12990000 0.20370000 0.02290000 0.20370000 0.04140000 -0.00000000 -0.02135462 0.28807530 0.05070444 0.03238549 0.15443212 -Ti 1.47700000 0.85300000 9.65000000 -0.06510000 -0.10240000 -0.16330000 -0.10240000 0.02250000 -0.11830000 -0.16330000 -0.11830000 0.04260000 0.00000000 -0.14481547 -0.16730146 0.05217413 -0.23094107 -0.06194255 -Ti 4.43200000 0.85300000 9.65000000 -0.07760000 0.09490000 0.15980000 0.09490000 0.03600000 -0.09020000 0.15980000 -0.09020000 0.04170000 -0.00005774 0.13420887 -0.12756206 0.05103104 0.22599133 -0.08032733 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00110000 0.00000000 0.00000000 0.00000000 0.00590000 -0.00180000 0.00000000 -0.00180000 -0.00480000 0.00000000 0.00000000 -0.00254558 -0.00587878 0.00000000 -0.00494975 -Li 0.00000000 0.00000000 12.66500000 -0.00230000 0.00000000 0.00000000 0.00000000 0.00440000 -0.00150000 0.00000000 -0.00150000 -0.00210000 0.00000000 0.00000000 -0.00212132 -0.00257196 0.00000000 -0.00473762 -Li 2.95500000 0.00000000 7.23700000 -0.02060000 0.00000000 0.00000000 0.00000000 0.03310000 -0.00560000 0.00000000 -0.00560000 -0.01250000 0.00000000 0.00000000 -0.00791960 -0.01530931 0.00000000 -0.03797163 -Li 2.95500000 1.70600000 3.01600000 0.01140000 0.00000000 0.00000000 0.00000000 0.01050000 0.00030000 0.00000000 0.00030000 -0.02190000 0.00000000 0.00000000 0.00042426 -0.02682191 0.00000000 0.00063640 -Li 0.00000000 3.41200000 11.45900000 0.01200000 -0.00000000 0.00000000 -0.00000000 0.01110000 0.00040000 0.00000000 0.00040000 -0.02320000 0.00005774 0.00000000 0.00056569 -0.02837326 0.00000000 0.00063640 -Li 0.00000000 3.41200000 7.84000000 -0.01510000 -0.00000000 -0.00000000 -0.00000000 0.03490000 -0.01780000 -0.00000000 -0.01780000 -0.01980000 0.00000000 0.00000000 -0.02517300 -0.02424995 0.00000000 -0.03535534 -Li 0.00000000 0.00000000 7.23700000 0.01140000 -0.00000000 0.00000000 -0.00000000 0.00450000 -0.00110000 0.00000000 -0.00110000 -0.01590000 0.00000000 0.00000000 -0.00155563 -0.01947344 0.00000000 0.00487904 -Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 -0.00000000 -0.00000000 0.00570000 -0.00180000 -0.00000000 -0.00180000 -0.01140000 -0.00005774 0.00000000 -0.00254558 -0.01400292 0.00000000 0.00007071 -O 0.00000000 0.00000000 3.80700000 0.07980000 -0.00000000 -0.00000000 -0.00000000 0.10180000 0.01710000 -0.00000000 0.01710000 -0.18170000 0.00005774 0.00000000 0.02418305 -0.22249532 0.00000000 -0.01555635 -O 0.00000000 0.00000000 10.66800000 0.06940000 0.00000000 0.00000000 0.00000000 0.08860000 0.01840000 0.00000000 0.01840000 -0.15800000 0.00000000 0.00000000 0.02602153 -0.19350969 0.00000000 -0.01357645 -O 2.95500000 1.70600000 8.63200000 -0.01930000 -0.00000000 0.00000000 -0.00000000 0.11900000 0.06790000 0.00000000 0.06790000 -0.09970000 0.00000000 0.00000000 0.09602510 -0.12210706 0.00000000 -0.09779287 -O 2.95500000 1.70600000 1.01800000 0.08700000 -0.00000000 0.00000000 -0.00000000 -0.04170000 -0.16980000 0.00000000 -0.16980000 -0.04530000 0.00000000 0.00000000 -0.24013346 -0.05548094 0.00000000 0.09100464 -O 0.00000000 3.41200000 13.45700000 0.10180000 0.00000000 0.00000000 0.00000000 -0.04890000 -0.17300000 0.00000000 -0.17300000 -0.05290000 0.00000000 0.00000000 -0.24465895 -0.06478900 0.00000000 0.10656099 -O 0.00000000 3.41200000 5.84300000 -0.06730000 -0.00000000 -0.00000000 -0.00000000 0.08220000 0.10550000 -0.00000000 0.10550000 -0.01490000 0.00000000 0.00000000 0.14919953 -0.01824870 0.00000000 -0.10571246 -O -1.32400000 4.17600000 1.14400000 -0.14790000 -0.08290000 0.16630000 -0.08290000 0.11540000 0.01520000 0.16630000 0.01520000 0.03250000 -0.00000000 -0.11723830 0.02149605 0.03980421 0.23518372 -0.18618122 -O 0.00000000 1.88300000 1.14400000 0.02820000 -0.00000000 0.00000000 -0.00000000 -0.08740000 0.06430000 0.00000000 0.06430000 0.05920000 -0.00000000 0.00000000 0.09093393 0.07250490 0.00000000 0.08174154 -O 1.32400000 4.17600000 1.14400000 -0.14790000 0.08290000 -0.16630000 0.08290000 0.11540000 0.01520000 -0.16630000 0.01520000 0.03250000 -0.00000000 0.11723830 0.02149605 0.03980421 -0.23518372 -0.18618122 -O 4.27800000 0.94200000 13.33100000 -0.15930000 -0.08110000 0.16180000 -0.08110000 0.10950000 0.00390000 0.16180000 0.00390000 0.04980000 -0.00000000 -0.11469272 0.00551543 0.06099229 0.22881975 -0.19007030 -O 1.63100000 0.94200000 13.33100000 -0.15930000 0.08110000 -0.16180000 0.08110000 0.10950000 0.00390000 -0.16180000 0.00390000 0.04980000 -0.00000000 0.11469272 0.00551543 0.06099229 -0.22881975 -0.19007030 -O 2.95500000 3.23400000 13.33100000 0.01710000 0.00000000 -0.00000000 0.00000000 -0.08150000 0.07440000 -0.00000000 0.07440000 0.06450000 -0.00005774 0.00000000 0.10521749 0.07895522 0.00000000 0.06972073 -O 4.58600000 0.76400000 5.96800000 -0.11740000 0.20700000 -0.14470000 0.20700000 0.01590000 0.02000000 -0.14470000 0.02000000 0.10150000 -0.00000000 0.29274221 0.02828427 0.12431160 -0.20463670 -0.09425733 -O 2.95500000 3.58900000 5.96800000 0.08370000 0.00000000 0.00000000 0.00000000 -0.21000000 -0.10370000 0.00000000 -0.10370000 0.12630000 -0.00000000 0.00000000 -0.14665395 0.15468528 0.00000000 0.20767726 -O 1.32400000 0.76400000 5.96800000 -0.11740000 -0.20700000 0.14470000 -0.20700000 0.01590000 0.02000000 0.14470000 0.02000000 0.10150000 -0.00000000 -0.29274221 0.02828427 0.12431160 0.20463670 -0.09425733 -O 1.32400000 2.64800000 3.68100000 -0.02630000 0.06110000 -0.03130000 0.06110000 0.05450000 0.03760000 -0.03130000 0.03760000 -0.02820000 0.00000000 0.08640845 0.05317443 -0.03453781 -0.04426488 -0.05713423 -O -1.32400000 2.64800000 3.68100000 -0.02630000 -0.06110000 0.03130000 -0.06110000 0.05450000 0.03760000 0.03130000 0.03760000 -0.02820000 0.00000000 -0.08640845 0.05317443 -0.03453781 0.04426488 -0.05713423 -O 0.00000000 4.94000000 3.68100000 0.06570000 -0.00000000 -0.00000000 -0.00000000 -0.03300000 -0.01450000 -0.00000000 -0.01450000 -0.03270000 0.00000000 0.00000000 -0.02050610 -0.04004916 0.00000000 0.06979144 -O 1.63100000 2.47000000 10.79300000 -0.02300000 0.05840000 -0.02260000 0.05840000 0.05770000 0.02640000 -0.02260000 0.02640000 -0.03470000 0.00000000 0.08259007 0.03733524 -0.04249865 -0.03196123 -0.05706352 -O 2.95500000 0.17700000 10.79300000 0.06660000 0.00000000 0.00000000 0.00000000 -0.02540000 -0.00310000 0.00000000 -0.00310000 -0.04120000 0.00000000 0.00000000 -0.00438406 -0.05045949 0.00000000 0.06505382 -O 4.27800000 2.47000000 10.79300000 -0.02300000 -0.05840000 0.02260000 -0.05840000 0.05770000 0.02640000 0.02260000 0.02640000 -0.03470000 0.00000000 -0.08259007 0.03733524 -0.04249865 0.03196123 -0.05706352 -O -1.63100000 4.35300000 8.50600000 -0.04120000 0.16480000 -0.16110000 0.16480000 -0.01680000 0.02310000 -0.16110000 0.02310000 0.05800000 -0.00000000 0.23306240 0.03266833 0.07103520 -0.22782980 -0.01725341 -O 1.63100000 4.35300000 8.50600000 -0.04120000 -0.16480000 0.16110000 -0.16480000 -0.01680000 0.02310000 0.16110000 0.02310000 0.05800000 -0.00000000 -0.23306240 0.03266833 0.07103520 0.22782980 -0.01725341 -O 0.00000000 1.52800000 8.50600000 0.06400000 0.00000000 -0.00000000 0.00000000 -0.15960000 -0.08680000 -0.00000000 -0.08680000 0.09570000 -0.00005774 0.00000000 -0.12275374 0.11716726 0.00000000 0.15810908 -Ti 2.95500000 1.70600000 12.06200000 0.07420000 -0.00000000 -0.00000000 -0.00000000 0.17140000 0.08100000 -0.00000000 0.08100000 -0.24570000 0.00005774 0.00000000 0.11455130 -0.30087899 0.00000000 -0.06873078 -Ti 0.00000000 3.41200000 2.41200000 0.08100000 -0.00000000 0.00000000 -0.00000000 0.16990000 0.08550000 0.00000000 0.08550000 -0.25100000 0.00005774 0.00000000 0.12091526 -0.30737014 0.00000000 -0.06286179 -Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24890000 0.04850000 -0.24890000 -0.03780000 -0.07620000 0.04850000 -0.07620000 0.11880000 0.00000000 -0.35199776 -0.10776307 0.14549969 0.06858936 -0.03054701 -Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24890000 -0.04850000 0.24890000 -0.03780000 -0.07620000 -0.04850000 -0.07620000 0.11880000 0.00000000 0.35199776 -0.10776307 0.14549969 -0.06858936 -0.03054701 -Ti 0.00000000 1.70600000 4.82500000 0.06480000 0.00000000 -0.00000000 0.00000000 -0.12360000 0.23790000 -0.00000000 0.23790000 0.05870000 0.00005774 0.00000000 0.33644141 0.07193335 0.00000000 0.13321892 -Ti 1.47700000 4.26500000 4.82500000 -0.09310000 -0.07800000 -0.22360000 -0.07800000 0.03210000 -0.09990000 -0.22360000 -0.09990000 0.06100000 0.00000000 -0.11030866 -0.14127993 0.07470944 -0.31621815 -0.08852977 -Ti -1.47700000 4.26500000 4.82500000 -0.09310000 0.07800000 0.22360000 0.07800000 0.03210000 -0.09990000 0.22360000 -0.09990000 0.06100000 0.00000000 0.11030866 -0.14127993 0.07470944 0.31621815 -0.08852977 -Ti 2.95500000 3.41200000 9.65000000 0.08870000 -0.00000000 -0.00000000 -0.00000000 -0.12920000 0.18630000 -0.00000000 0.18630000 0.04050000 -0.00000000 0.00000000 0.26346799 0.04960217 0.00000000 0.15407857 -Ti 1.47700000 0.85300000 9.65000000 -0.09120000 -0.08730000 -0.18610000 -0.08730000 0.04860000 -0.07900000 -0.18610000 -0.07900000 0.04260000 0.00000000 -0.12346084 -0.11172287 0.05217413 -0.26318514 -0.09885353 -Ti 4.43200000 0.85300000 9.65000000 -0.09120000 0.08730000 0.18610000 0.08730000 0.04860000 -0.07900000 0.18610000 -0.07900000 0.04260000 0.00000000 0.12346084 -0.11172287 0.05217413 0.26318514 -0.09885353 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00330000 -0.00000000 0.00000000 -0.00000000 0.00340000 -0.00140000 0.00000000 -0.00140000 -0.00010000 -0.00000000 0.00000000 -0.00197990 -0.00012247 0.00000000 -0.00473762 -Li 0.00000000 0.00000000 12.66500000 -0.00210000 0.00000000 0.00000000 0.00000000 0.00430000 -0.00140000 0.00000000 -0.00140000 -0.00220000 0.00000000 0.00000000 -0.00197990 -0.00269444 0.00000000 -0.00452548 -Li 2.95500000 1.70600000 6.63400000 -0.01400000 0.00000000 0.00000000 0.00000000 -0.01320000 0.00000000 0.00000000 0.00000000 0.02720000 0.00000000 0.00000000 0.00000000 0.03331306 0.00000000 -0.00056569 -Li 2.95500000 1.70600000 4.82500000 -0.00810000 0.00000000 0.00000000 0.00000000 -0.00700000 0.00010000 0.00000000 0.00010000 0.01510000 -0.00000000 0.00000000 0.00014142 0.01849365 0.00000000 -0.00077782 -Li 0.00000000 3.41200000 11.45900000 0.01200000 0.00000000 0.00000000 0.00000000 0.01140000 0.00030000 0.00000000 0.00030000 -0.02340000 0.00000000 0.00000000 0.00042426 -0.02865903 0.00000000 0.00042426 -Li 0.00000000 3.41200000 7.84000000 0.00240000 0.00000000 -0.00000000 0.00000000 0.00330000 0.00000000 -0.00000000 0.00000000 -0.00570000 0.00000000 0.00000000 0.00000000 -0.00698105 0.00000000 -0.00063640 -Li 0.00000000 0.00000000 7.23700000 0.00620000 0.00000000 -0.00000000 0.00000000 0.00720000 0.00000000 -0.00000000 0.00000000 -0.01340000 0.00000000 0.00000000 0.00000000 -0.01641158 0.00000000 -0.00070711 -Li 2.95500000 0.00000000 0.00000000 0.00650000 -0.00000000 0.00000000 -0.00000000 0.00510000 -0.00280000 0.00000000 -0.00280000 -0.01160000 0.00000000 0.00000000 -0.00395980 -0.01420704 0.00000000 0.00098995 -O 0.00000000 0.00000000 3.80700000 0.05990000 -0.00000000 -0.00000000 -0.00000000 0.07290000 -0.00380000 -0.00000000 -0.00380000 -0.13280000 0.00000000 0.00000000 -0.00537401 -0.16264612 0.00000000 -0.00919239 -O 0.00000000 0.00000000 10.66800000 0.07340000 0.00000000 0.00000000 0.00000000 0.08600000 -0.00470000 0.00000000 -0.00470000 -0.15940000 0.00000000 0.00000000 -0.00664680 -0.19522433 0.00000000 -0.00890955 -O 2.95500000 1.70600000 8.63200000 0.02240000 0.00000000 -0.00000000 0.00000000 0.03260000 -0.00140000 -0.00000000 -0.00140000 -0.05500000 0.00000000 0.00000000 -0.00197990 -0.06736097 0.00000000 -0.00721249 -O 2.95500000 1.70600000 1.01800000 -0.00420000 -0.00000000 -0.00000000 -0.00000000 0.03730000 -0.06830000 -0.00000000 -0.06830000 -0.03310000 0.00000000 0.00000000 -0.09659079 -0.04053906 0.00000000 -0.02934493 -O 0.00000000 3.41200000 13.45700000 0.09010000 0.00000000 -0.00000000 0.00000000 -0.03430000 -0.14300000 -0.00000000 -0.14300000 -0.05580000 0.00000000 0.00000000 -0.20223254 -0.06834076 0.00000000 0.08796408 -O 0.00000000 3.41200000 5.84300000 0.05050000 0.00000000 -0.00000000 0.00000000 0.06030000 -0.00120000 -0.00000000 -0.00120000 -0.11080000 0.00000000 0.00000000 -0.00169706 -0.13570173 0.00000000 -0.00692965 -O -1.32400000 4.17600000 1.14400000 -0.14440000 -0.07620000 0.17550000 -0.07620000 0.09840000 -0.01030000 0.17550000 -0.01030000 0.04600000 -0.00000000 -0.10776307 -0.01456640 0.05633826 0.24819448 -0.17168553 -O 0.00000000 1.88300000 1.14400000 -0.01530000 0.00000000 0.00000000 0.00000000 -0.06080000 0.09630000 0.00000000 0.09630000 0.07600000 0.00005774 0.00000000 0.13618877 0.09312144 0.00000000 0.03217336 -O 1.32400000 4.17600000 1.14400000 -0.14440000 0.07620000 -0.17550000 0.07620000 0.09840000 -0.01030000 -0.17550000 -0.01030000 0.04600000 -0.00000000 0.10776307 -0.01456640 0.05633826 -0.24819448 -0.17168553 -O 4.27800000 0.94200000 13.33100000 -0.16030000 -0.08110000 0.15750000 -0.08110000 0.09900000 0.00840000 0.15750000 0.00840000 0.06130000 -0.00000000 -0.11469272 0.01187939 0.07507686 0.22273864 -0.18335279 -O 1.63100000 0.94200000 13.33100000 -0.16030000 0.08110000 -0.15750000 0.08110000 0.09900000 0.00840000 -0.15750000 0.00840000 0.06130000 -0.00000000 0.11469272 0.01187939 0.07507686 -0.22273864 -0.18335279 -O 2.95500000 3.23400000 13.33100000 0.02040000 -0.00000000 -0.00000000 -0.00000000 -0.08120000 0.09030000 -0.00000000 0.09030000 0.06080000 -0.00000000 0.00000000 0.12770348 0.07446449 0.00000000 0.07184205 -O 4.58600000 0.76400000 5.96800000 -0.04310000 0.04460000 0.00120000 0.04460000 0.01780000 0.00460000 0.00120000 0.00460000 0.02530000 -0.00000000 0.06307392 0.00650538 0.03098605 0.00169706 -0.04306280 -O 2.95500000 3.58900000 5.96800000 0.03080000 0.00000000 0.00000000 0.00000000 -0.05850000 0.00050000 0.00000000 0.00050000 0.02770000 0.00000000 0.00000000 0.00070711 0.03392543 0.00000000 0.06314464 -O 1.32400000 0.76400000 5.96800000 -0.04310000 -0.04460000 -0.00120000 -0.04460000 0.01780000 0.00460000 -0.00120000 0.00460000 0.02530000 -0.00000000 -0.06307392 0.00650538 0.03098605 -0.00169706 -0.04306280 -O 1.32400000 2.64800000 3.68100000 -0.02620000 0.02030000 0.05700000 0.02030000 0.02350000 -0.01210000 0.05700000 -0.01210000 0.00270000 -0.00000000 0.02870854 -0.01711198 0.00330681 0.08061017 -0.03514321 -O -1.32400000 2.64800000 3.68100000 -0.02620000 -0.02030000 -0.05700000 -0.02030000 0.02350000 -0.01210000 -0.05700000 -0.01210000 0.00270000 -0.00000000 -0.02870854 -0.01711198 0.00330681 -0.08061017 -0.03514321 -O 0.00000000 4.94000000 3.68100000 0.01790000 -0.00000000 -0.00000000 -0.00000000 -0.01340000 0.05900000 -0.00000000 0.05900000 -0.00440000 -0.00005774 0.00000000 0.08343860 -0.00542970 0.00000000 0.02213244 -O 1.63100000 2.47000000 10.79300000 -0.02630000 0.04940000 -0.01910000 0.04940000 0.05690000 0.03720000 -0.01910000 0.03720000 -0.03060000 0.00000000 0.06986215 0.05260874 -0.03747719 -0.02701148 -0.05883128 -O 2.95500000 0.17700000 10.79300000 0.06780000 0.00000000 0.00000000 0.00000000 -0.02870000 -0.02640000 0.00000000 -0.02640000 -0.03910000 0.00000000 0.00000000 -0.03733524 -0.04788752 0.00000000 0.06823580 -O 4.27800000 2.47000000 10.79300000 -0.02630000 -0.04940000 0.01910000 -0.04940000 0.05690000 0.03720000 0.01910000 0.03720000 -0.03060000 0.00000000 -0.06986215 0.05260874 -0.03747719 0.02701148 -0.05883128 -O -1.63100000 4.35300000 8.50600000 -0.12540000 0.12990000 -0.09240000 0.12990000 0.03500000 0.05860000 -0.09240000 0.05860000 0.09040000 -0.00000000 0.18370634 0.08287291 0.11071694 -0.13067333 -0.11341993 -O 1.63100000 4.35300000 8.50600000 -0.12540000 -0.12990000 0.09240000 -0.12990000 0.03500000 0.05860000 0.09240000 0.05860000 0.09040000 -0.00000000 -0.18370634 0.08287291 0.11071694 0.13067333 -0.11341993 -O 0.00000000 1.52800000 8.50600000 0.09770000 0.00000000 -0.00000000 0.00000000 -0.18990000 -0.10810000 -0.00000000 -0.10810000 0.09220000 0.00000000 0.00000000 -0.15287649 0.11292148 0.00000000 0.20336391 -Ti 2.95500000 1.70600000 12.06200000 0.07490000 -0.00000000 -0.00000000 -0.00000000 0.17470000 0.08310000 -0.00000000 0.08310000 -0.24960000 0.00000000 0.00000000 0.11752115 -0.30569632 0.00000000 -0.07056926 -Ti 0.00000000 3.41200000 2.41200000 0.07310000 0.00000000 -0.00000000 0.00000000 0.16220000 0.07700000 -0.00000000 0.07700000 -0.23520000 -0.00005774 0.00000000 0.10889444 -0.28810082 0.00000000 -0.06300321 -Ti -1.47700000 2.55900000 0.00000000 -0.10490000 -0.26660000 0.06490000 -0.26660000 -0.02130000 -0.05100000 0.06490000 -0.05100000 0.12620000 -0.00000000 -0.37702934 -0.07212489 0.15456280 0.09178246 -0.05911413 -Ti 1.47700000 2.55900000 0.00000000 -0.10490000 0.26660000 -0.06490000 0.26660000 -0.02130000 -0.05100000 -0.06490000 -0.05100000 0.12620000 -0.00000000 0.37702934 -0.07212489 0.15456280 -0.09178246 -0.05911413 -Ti 0.00000000 1.70600000 4.82500000 0.10160000 -0.00000000 -0.00000000 -0.00000000 -0.13590000 0.18660000 -0.00000000 0.18660000 0.03430000 -0.00000000 0.00000000 0.26389225 0.04200875 0.00000000 0.16793786 -Ti 1.47700000 4.26500000 4.82500000 -0.07960000 -0.10460000 -0.15990000 -0.10460000 0.04400000 -0.09060000 -0.15990000 -0.09060000 0.03560000 0.00000000 -0.14792674 -0.12812775 0.04360092 -0.22613275 -0.08739840 -Ti -1.47700000 4.26500000 4.82500000 -0.07960000 0.10460000 0.15990000 0.10460000 0.04400000 -0.09060000 0.15990000 -0.09060000 0.03560000 0.00000000 0.14792674 -0.12812775 0.04360092 0.22613275 -0.08739840 -Ti 2.95500000 3.41200000 9.65000000 0.07960000 -0.00000000 0.00000000 -0.00000000 -0.12150000 0.21180000 0.00000000 0.21180000 0.04190000 -0.00000000 0.00000000 0.29953043 0.05131681 0.00000000 0.14219917 -Ti 1.47700000 0.85300000 9.65000000 -0.07460000 -0.08760000 -0.18150000 -0.08760000 0.03070000 -0.10310000 -0.18150000 -0.10310000 0.04390000 -0.00000000 -0.12388511 -0.14580542 0.05376630 -0.25667976 -0.07445834 -Ti 4.43200000 0.85300000 9.65000000 -0.07460000 0.08760000 0.18150000 0.08760000 0.03070000 -0.10310000 0.18150000 -0.10310000 0.04390000 -0.00000000 0.12388511 -0.14580542 0.05376630 0.25667976 -0.07445834 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.01570000 -0.02150000 -0.01490000 -0.02150000 -0.00140000 0.00680000 -0.01490000 0.00680000 -0.01430000 0.00000000 -0.03040559 0.00961665 -0.01751385 -0.02107178 0.01209153 -Li 0.00000000 0.00000000 12.66500000 -0.00150000 0.00010000 0.00000000 0.00010000 0.00440000 -0.00160000 0.00000000 -0.00160000 -0.00290000 0.00000000 0.00014142 -0.00226274 -0.00355176 0.00000000 -0.00417193 -Li 2.95500000 1.70600000 6.63400000 0.00290000 0.00010000 0.00000000 0.00010000 0.00300000 -0.00000000 0.00000000 -0.00000000 -0.00600000 0.00005774 0.00014142 0.00000000 -0.00730764 0.00000000 -0.00007071 -Li 4.43200000 0.85300000 2.41200000 0.01820000 -0.01350000 -0.01480000 -0.01350000 -0.00110000 0.00330000 -0.01480000 0.00330000 -0.01700000 -0.00005774 -0.01909188 0.00466690 -0.02086149 -0.02093036 0.01364716 -Li 0.00000000 3.41200000 11.45900000 0.01130000 -0.00000000 -0.00010000 -0.00000000 0.00970000 0.00040000 -0.00010000 0.00040000 -0.02090000 -0.00005774 0.00000000 0.00056569 -0.02563799 -0.00014142 0.00113137 -Li 0.00000000 3.41200000 7.84000000 0.00330000 -0.00000000 -0.00010000 -0.00000000 0.00330000 0.00000000 -0.00010000 0.00000000 -0.00660000 0.00000000 0.00000000 0.00000000 -0.00808332 -0.00014142 0.00000000 -Li 0.00000000 0.00000000 7.23700000 0.00680000 -0.00000000 0.00010000 -0.00000000 0.00680000 -0.00000000 0.00010000 -0.00000000 -0.01360000 0.00000000 0.00000000 0.00000000 -0.01665653 0.00014142 0.00000000 -Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00010000 0.00180000 -0.00010000 0.00390000 -0.00140000 0.00180000 -0.00140000 -0.01010000 0.00000000 -0.00014142 -0.00197990 -0.01236992 0.00254558 0.00162635 -O 0.00000000 0.00000000 3.80700000 0.08950000 -0.06110000 0.09280000 -0.06110000 0.02450000 -0.06170000 0.09280000 -0.06170000 -0.11390000 -0.00005774 -0.08640845 -0.08725698 -0.13953927 0.13123902 0.04596194 -O 0.00000000 0.00000000 10.66800000 0.07760000 -0.00100000 0.00040000 -0.00100000 0.07950000 -0.00590000 0.00040000 -0.00590000 -0.15710000 0.00000000 -0.00141421 -0.00834386 -0.19240742 0.00056569 -0.00134350 -O 2.95500000 1.70600000 8.63200000 0.02600000 -0.00070000 0.00040000 -0.00070000 0.02460000 -0.00120000 0.00040000 -0.00120000 -0.05060000 0.00000000 -0.00098995 -0.00169706 -0.06197209 0.00056569 0.00098995 -O 2.95500000 1.70600000 1.01800000 0.15350000 -0.04620000 0.07060000 -0.04620000 -0.07130000 -0.21210000 0.07060000 -0.21210000 -0.08220000 0.00000000 -0.06533667 -0.29995470 -0.10067403 0.09984348 0.15895760 -O 0.00000000 3.41200000 13.45700000 0.10120000 -0.00660000 0.01170000 -0.00660000 -0.05480000 -0.19300000 0.01170000 -0.19300000 -0.04640000 0.00000000 -0.00933381 -0.27294322 -0.05682816 0.01654630 0.11030866 -O 0.00000000 3.41200000 5.84300000 0.03490000 -0.00420000 0.01840000 -0.00420000 0.02940000 -0.01130000 0.01840000 -0.01130000 -0.06430000 0.00000000 -0.00593970 -0.01598061 -0.07875110 0.02602153 0.00388909 -O -1.32400000 4.17600000 1.14400000 -0.13820000 -0.09870000 0.16650000 -0.09870000 0.11900000 0.01000000 0.16650000 0.01000000 0.01920000 -0.00000000 -0.13958288 0.01414214 0.02351510 0.23546656 -0.18186786 -O 0.00000000 1.88300000 1.14400000 0.02580000 0.08040000 -0.09120000 0.08040000 -0.09170000 0.00020000 -0.09120000 0.00020000 0.06590000 0.00000000 0.11370277 0.00028284 0.08071069 -0.12897628 0.08308505 -O 1.32400000 4.17600000 1.14400000 -0.20680000 0.14230000 -0.16900000 0.14230000 0.15000000 0.13900000 -0.16900000 0.13900000 0.05680000 0.00000000 0.20124259 0.19657569 0.06956551 -0.23900209 -0.25229570 -O 4.27800000 0.94200000 13.33100000 -0.15110000 -0.08930000 0.17430000 -0.08930000 0.09870000 0.00820000 0.17430000 0.00820000 0.05240000 0.00000000 -0.12628927 0.01159655 0.06417663 0.24649742 -0.17663527 -O 1.63100000 0.94200000 13.33100000 -0.15600000 0.08700000 -0.17600000 0.08700000 0.11140000 0.02210000 -0.17600000 0.02210000 0.04460000 -0.00000000 0.12303658 0.03125412 0.05462362 -0.24890159 -0.18908035 -O 2.95500000 3.23400000 13.33100000 0.03560000 0.00450000 -0.01120000 0.00450000 -0.10140000 0.06960000 -0.01120000 0.06960000 0.06580000 0.00000000 0.00636396 0.09842926 0.08058821 -0.01583919 0.09687363 -O 4.58600000 0.76400000 5.96800000 -0.13860000 0.13510000 -0.09340000 0.13510000 0.01630000 0.05930000 -0.09340000 0.05930000 0.12230000 -0.00000000 0.19106025 0.08386286 0.14978630 -0.13208755 -0.10953084 -O 2.95500000 3.58900000 5.96800000 0.11020000 0.00140000 -0.01270000 0.00140000 -0.22490000 -0.13980000 -0.01270000 -0.13980000 0.11460000 0.00005774 0.00197990 -0.19770706 0.14039659 -0.01796051 0.23695148 -O 1.32400000 0.76400000 5.96800000 -0.14140000 -0.14550000 0.11350000 -0.14550000 0.02860000 0.08550000 0.11350000 0.08550000 0.11290000 -0.00005774 -0.20576807 0.12091526 0.13823287 0.16051324 -0.12020815 -O 1.32400000 2.64800000 3.68100000 -0.04470000 0.07660000 -0.00610000 0.07660000 0.05610000 0.03610000 -0.00610000 0.03610000 -0.01140000 -0.00000000 0.10832876 0.05105311 -0.01396209 -0.00862670 -0.07127636 -O -1.32400000 2.64800000 3.68100000 -0.11910000 -0.08780000 -0.01730000 -0.08780000 0.12000000 0.10920000 -0.01730000 0.10920000 -0.00090000 0.00000000 -0.12416795 0.15443212 -0.00110227 -0.02446589 -0.16906923 -O 0.00000000 4.94000000 3.68100000 0.12860000 0.05390000 -0.07390000 0.05390000 -0.11990000 -0.02820000 -0.07390000 -0.02820000 -0.00870000 0.00000000 0.07622611 -0.03988082 -0.01065528 -0.10451038 0.17571604 -O 1.63100000 2.47000000 10.79300000 -0.02060000 0.04810000 -0.00900000 0.04810000 0.04920000 0.03530000 -0.00900000 0.03530000 -0.02860000 0.00000000 0.06802367 0.04992174 -0.03502770 -0.01272792 -0.04935605 -O 2.95500000 0.17700000 10.79300000 0.07010000 -0.00210000 -0.00090000 -0.00210000 -0.03630000 -0.01600000 -0.00090000 -0.01600000 -0.03380000 0.00000000 -0.00296985 -0.02262742 -0.04139638 -0.00127279 0.07523616 -O 4.27800000 2.47000000 10.79300000 -0.02050000 -0.05120000 0.00880000 -0.05120000 0.04780000 0.03610000 0.00880000 0.03610000 -0.02730000 0.00000000 -0.07240773 0.05105311 -0.03343553 0.01244508 -0.04829539 -O -1.63100000 4.35300000 8.50600000 -0.12510000 0.12520000 -0.08390000 0.12520000 0.01770000 0.05390000 -0.08390000 0.05390000 0.10750000 -0.00005774 0.17705954 0.07622611 0.13161925 -0.11865252 -0.10097485 -O 1.63100000 4.35300000 8.50600000 -0.12480000 -0.12830000 0.08400000 -0.12830000 0.01600000 0.05420000 0.08400000 0.05420000 0.10880000 0.00000000 -0.18144360 0.07665038 0.13325224 0.11879394 -0.09956063 -O 0.00000000 1.52800000 8.50600000 0.09000000 -0.00240000 -0.00030000 -0.00240000 -0.20120000 -0.09860000 -0.00030000 -0.09860000 0.11120000 -0.00000000 -0.00339411 -0.13944146 0.13619163 -0.00042426 0.20590949 -Ti 2.95500000 1.70600000 12.06200000 0.07860000 0.00040000 -0.00170000 0.00040000 0.17360000 0.08570000 -0.00170000 0.08570000 -0.25220000 0.00000000 0.00056569 0.12119810 -0.30888066 -0.00240416 -0.06717514 -Ti 0.00000000 3.41200000 2.41200000 0.07090000 0.03340000 -0.03960000 0.03340000 0.19730000 0.10880000 -0.03960000 0.10880000 -0.26820000 0.00000000 0.04723473 0.15386644 -0.32847657 -0.05600286 -0.08937830 -Ti -1.47700000 2.55900000 0.00000000 -0.07580000 -0.27240000 0.06130000 -0.27240000 -0.04190000 -0.09500000 0.06130000 -0.09500000 0.11770000 0.00000000 -0.38523177 -0.13435029 0.14415247 0.08669129 -0.02397092 -Ti 1.47700000 2.55900000 0.00000000 -0.08610000 0.26890000 -0.06970000 0.26890000 -0.02210000 -0.06920000 -0.06970000 -0.06920000 0.10820000 -0.00000000 0.38028203 -0.09786358 0.13251740 -0.09857069 -0.04525483 -Ti 0.00000000 1.70600000 4.82500000 0.06620000 -0.01150000 0.02180000 -0.01150000 -0.12100000 0.24240000 0.02180000 0.24240000 0.05490000 -0.00005774 -0.01626346 0.34280537 0.06719767 0.03082986 0.13237039 -Ti 1.47700000 4.26500000 4.82500000 -0.06690000 -0.08580000 -0.19690000 -0.08580000 0.01040000 -0.13570000 -0.19690000 -0.13570000 0.05650000 -0.00000000 -0.12133952 -0.19190878 0.06919809 -0.27845865 -0.05465935 -Ti -1.47700000 4.26500000 4.82500000 -0.07220000 0.07660000 0.19320000 0.07660000 0.02140000 -0.10830000 0.19320000 -0.10830000 0.05080000 -0.00000000 0.10832876 -0.15315933 0.06221704 0.27322606 -0.06618519 -Ti 2.95500000 3.41200000 9.65000000 0.08060000 -0.00010000 0.00010000 -0.00010000 -0.12570000 0.20710000 0.00010000 0.20710000 0.04510000 -0.00000000 -0.00014142 0.29288363 0.05523599 0.00014142 0.14587613 -Ti 1.47700000 0.85300000 9.65000000 -0.07670000 -0.08910000 -0.17730000 -0.08910000 0.03020000 -0.10010000 -0.17730000 -0.10010000 0.04650000 0.00000000 -0.12600643 -0.14156278 0.05695064 -0.25074006 -0.07558971 -Ti 4.43200000 0.85300000 9.65000000 -0.07680000 0.08910000 0.17730000 0.08910000 0.03030000 -0.10000000 0.17730000 -0.10000000 0.04650000 -0.00000000 0.12600643 -0.14142136 0.05695064 0.25074006 -0.07573114 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.02020000 -0.00000000 0.00000000 -0.00000000 0.03470000 -0.01900000 0.00000000 -0.01900000 -0.01450000 0.00000000 0.00000000 -0.02687006 -0.01775880 0.00000000 -0.03882016 -Li 0.00000000 0.00000000 12.66500000 -0.00160000 0.00000000 0.00000000 0.00000000 0.00510000 -0.00160000 0.00000000 -0.00160000 -0.00350000 0.00000000 0.00000000 -0.00226274 -0.00428661 0.00000000 -0.00473762 -Li 2.95500000 1.70600000 6.63400000 0.00300000 0.00000000 0.00000000 0.00000000 0.00370000 0.00000000 0.00000000 0.00000000 -0.00670000 0.00000000 0.00000000 0.00000000 -0.00820579 0.00000000 -0.00049497 -Li 2.95500000 3.41200000 2.41200000 -0.01050000 0.00000000 0.00000000 0.00000000 0.02680000 -0.01210000 0.00000000 -0.01210000 -0.01630000 0.00000000 0.00000000 -0.01711198 -0.01996334 0.00000000 -0.02637508 -Li 0.00000000 3.41200000 11.45900000 0.01120000 0.00000000 -0.00000000 0.00000000 0.01060000 0.00020000 -0.00000000 0.00020000 -0.02180000 0.00000000 0.00000000 0.00028284 -0.02669944 0.00000000 0.00042426 -Li 0.00000000 3.41200000 7.84000000 0.00320000 -0.00000000 -0.00000000 -0.00000000 0.00410000 -0.00010000 -0.00000000 -0.00010000 -0.00720000 -0.00005774 0.00000000 -0.00014142 -0.00885899 0.00000000 -0.00063640 -Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 -0.00000000 -0.00000000 0.00760000 0.00010000 -0.00000000 0.00010000 -0.01430000 0.00000000 0.00000000 0.00014142 -0.01751385 0.00000000 -0.00063640 -Li 2.95500000 0.00000000 0.00000000 0.00630000 -0.00000000 0.00000000 -0.00000000 0.00590000 -0.00310000 0.00000000 -0.00310000 -0.01220000 0.00000000 0.00000000 -0.00438406 -0.01494189 0.00000000 0.00028284 -O 0.00000000 0.00000000 3.80700000 -0.01270000 0.00000000 0.00000000 0.00000000 0.13640000 0.09810000 0.00000000 0.09810000 -0.12380000 0.00005774 0.00000000 0.13873435 -0.15158259 0.00000000 -0.10542962 -O 0.00000000 0.00000000 10.66800000 0.07570000 0.00000000 -0.00000000 0.00000000 0.08870000 -0.00570000 -0.00000000 -0.00570000 -0.16450000 0.00005774 0.00000000 -0.00806102 -0.20142971 0.00000000 -0.00919239 -O 2.95500000 1.70600000 8.63200000 0.02410000 0.00000000 -0.00000000 0.00000000 0.03440000 -0.00070000 -0.00000000 -0.00070000 -0.05850000 0.00000000 0.00000000 -0.00098995 -0.07164757 0.00000000 -0.00728320 -O 2.95500000 1.70600000 1.01800000 0.01950000 -0.00000000 0.00000000 -0.00000000 0.06930000 -0.08480000 0.00000000 -0.08480000 -0.08880000 0.00000000 0.00000000 -0.11992531 -0.10875734 0.00000000 -0.03521392 -O 0.00000000 3.41200000 13.45700000 0.09700000 0.00000000 -0.00000000 0.00000000 -0.04250000 -0.15470000 -0.00000000 -0.15470000 -0.05460000 0.00005774 0.00000000 -0.21877884 -0.06683025 0.00000000 0.09864140 -O 0.00000000 3.41200000 5.84300000 0.02690000 0.00000000 0.00000000 0.00000000 0.04500000 0.01980000 0.00000000 0.01980000 -0.07190000 0.00000000 0.00000000 0.02800143 -0.08805916 0.00000000 -0.01279863 -O -1.32400000 4.17600000 1.14400000 -0.06170000 -0.03790000 0.04600000 -0.03790000 0.02140000 -0.04050000 0.04600000 -0.04050000 0.04030000 -0.00000000 -0.05359869 -0.05727565 0.04935722 0.06505382 -0.05876057 -O 0.00000000 1.88300000 1.14400000 0.01890000 0.00000000 0.00000000 0.00000000 -0.05740000 0.06460000 0.00000000 0.06460000 0.03850000 0.00000000 0.00000000 0.09135820 0.04715268 0.00000000 0.05395225 -O 1.32400000 4.17600000 1.14400000 -0.06170000 0.03790000 -0.04600000 0.03790000 0.02140000 -0.04050000 -0.04600000 -0.04050000 0.04030000 -0.00000000 0.05359869 -0.05727565 0.04935722 -0.06505382 -0.05876057 -O 4.27800000 0.94200000 13.33100000 -0.15290000 -0.08570000 0.16690000 -0.08570000 0.11610000 0.01150000 0.16690000 0.01150000 0.03680000 0.00000000 -0.12119810 0.01626346 0.04507061 0.23603224 -0.19021172 -O 1.63100000 0.94200000 13.33100000 -0.15290000 0.08570000 -0.16690000 0.08570000 0.11610000 0.01150000 -0.16690000 0.01150000 0.03680000 0.00000000 0.12119810 0.01626346 0.04507061 -0.23603224 -0.19021172 -O 2.95500000 3.23400000 13.33100000 0.02310000 -0.00000000 -0.00000000 -0.00000000 -0.08550000 0.08450000 -0.00000000 0.08450000 0.06240000 0.00000000 0.00000000 0.11950105 0.07642408 0.00000000 0.07679180 -O 4.58600000 0.76400000 5.96800000 -0.13670000 0.14550000 -0.12540000 0.14550000 0.03300000 0.06310000 -0.12540000 0.06310000 0.10380000 -0.00005774 0.20576807 0.08923688 0.12708769 -0.17734238 -0.11999602 -O 2.95500000 3.58900000 5.96800000 0.09180000 0.00000000 0.00000000 0.00000000 -0.20800000 -0.10870000 0.00000000 -0.10870000 0.11620000 -0.00000000 0.00000000 -0.15372501 0.14231535 0.00000000 0.21199061 -O 1.32400000 0.76400000 5.96800000 -0.13670000 -0.14550000 0.12540000 -0.14550000 0.03300000 0.06310000 0.12540000 0.06310000 0.10380000 -0.00005774 -0.20576807 0.08923688 0.12708769 0.17734238 -0.11999602 -O 1.32400000 2.64800000 3.68100000 -0.02010000 0.13610000 -0.05550000 0.13610000 0.03380000 -0.01960000 -0.05550000 -0.01960000 -0.01370000 0.00000000 0.19247447 -0.02771859 -0.01677900 -0.07848885 -0.03811306 -O -1.32400000 2.64800000 3.68100000 -0.02010000 -0.13610000 0.05550000 -0.13610000 0.03380000 -0.01960000 0.05550000 -0.01960000 -0.01370000 0.00000000 -0.19247447 -0.02771859 -0.01677900 0.07848885 -0.03811306 -O 0.00000000 4.94000000 3.68100000 0.08860000 -0.00000000 -0.00000000 -0.00000000 -0.06250000 -0.01210000 -0.00000000 -0.01210000 -0.02610000 0.00000000 0.00000000 -0.01711198 -0.03196584 0.00000000 0.10684383 -O 1.63100000 2.47000000 10.79300000 -0.02230000 0.04870000 -0.01010000 0.04870000 0.05870000 0.03520000 -0.01010000 0.03520000 -0.03640000 0.00000000 0.06887220 0.04978032 -0.04458071 -0.01428356 -0.05727565 -O 2.95500000 0.17700000 10.79300000 0.06720000 -0.00000000 0.00000000 -0.00000000 -0.02460000 -0.01560000 0.00000000 -0.01560000 -0.04260000 0.00000000 0.00000000 -0.02206173 -0.05217413 0.00000000 0.06491240 -O 4.27800000 2.47000000 10.79300000 -0.02230000 -0.04870000 0.01010000 -0.04870000 0.05870000 0.03520000 0.01010000 0.03520000 -0.03640000 0.00000000 -0.06887220 0.04978032 -0.04458071 0.01428356 -0.05727565 -O -1.63100000 4.35300000 8.50600000 -0.12710000 0.12500000 -0.08440000 0.12500000 0.02750000 0.05370000 -0.08440000 0.05370000 0.09960000 -0.00000000 0.17677670 0.07594327 0.12198459 -0.11935962 -0.10931871 -O 1.63100000 4.35300000 8.50600000 -0.12710000 -0.12500000 0.08440000 -0.12500000 0.02750000 0.05370000 0.08440000 0.05370000 0.09960000 -0.00000000 -0.17677670 0.07594327 0.12198459 0.11935962 -0.10931871 -O 0.00000000 1.52800000 8.50600000 0.08760000 0.00000000 -0.00000000 0.00000000 -0.18860000 -0.09830000 -0.00000000 -0.09830000 0.10100000 -0.00000000 0.00000000 -0.13901719 0.12369923 0.00000000 0.19530289 -Ti 2.95500000 1.70600000 12.06200000 0.07980000 0.00000000 0.00000000 0.00000000 0.17150000 0.08680000 0.00000000 0.08680000 -0.25130000 0.00000000 0.00000000 0.12275374 -0.30777839 0.00000000 -0.06484169 -Ti 0.00000000 3.41200000 2.41200000 0.12860000 -0.00000000 -0.00000000 -0.00000000 0.13840000 0.03960000 -0.00000000 0.03960000 -0.26690000 -0.00005774 0.00000000 0.05600286 -0.32692523 0.00000000 -0.00692965 -Ti -1.47700000 2.55900000 0.00000000 -0.09850000 -0.25810000 0.04340000 -0.25810000 -0.01780000 -0.05560000 0.04340000 -0.05560000 0.11630000 -0.00000000 -0.36500852 -0.07863027 0.14243783 0.06137687 -0.05706352 -Ti 1.47700000 2.55900000 0.00000000 -0.09850000 0.25810000 -0.04340000 0.25810000 -0.01780000 -0.05560000 -0.04340000 -0.05560000 0.11630000 -0.00000000 0.36500852 -0.07863027 0.14243783 -0.06137687 -0.05706352 -Ti 0.00000000 1.70600000 4.82500000 0.06500000 0.00000000 -0.00000000 0.00000000 -0.11490000 0.22520000 -0.00000000 0.22520000 0.04990000 -0.00000000 0.00000000 0.31848089 0.06111477 0.00000000 0.12720851 -Ti 1.47700000 4.26500000 4.82500000 -0.08680000 -0.07510000 -0.21740000 -0.07510000 0.02970000 -0.09830000 -0.21740000 -0.09830000 0.05710000 0.00000000 -0.10620744 -0.13901719 0.06993293 -0.30745003 -0.08237794 -Ti -1.47700000 4.26500000 4.82500000 -0.08680000 0.07510000 0.21740000 0.07510000 0.02970000 -0.09830000 0.21740000 -0.09830000 0.05710000 0.00000000 0.10620744 -0.13901719 0.06993293 0.30745003 -0.08237794 -Ti 2.95500000 3.41200000 9.65000000 0.08060000 -0.00000000 0.00000000 -0.00000000 -0.12570000 0.20720000 0.00000000 0.20720000 0.04520000 -0.00005774 0.00000000 0.29302505 0.05531764 0.00000000 0.14587613 -Ti 1.47700000 0.85300000 9.65000000 -0.07680000 -0.08900000 -0.17750000 -0.08900000 0.03040000 -0.10000000 -0.17750000 -0.10000000 0.04640000 -0.00000000 -0.12586501 -0.14142136 0.05682816 -0.25102291 -0.07580185 -Ti 4.43200000 0.85300000 9.65000000 -0.07680000 0.08900000 0.17750000 0.08900000 0.03040000 -0.10000000 0.17750000 -0.10000000 0.04640000 -0.00000000 0.12586501 -0.14142136 0.05682816 0.25102291 -0.07580185 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.01570000 0.02150000 0.01490000 0.02150000 -0.00140000 0.00680000 0.01490000 0.00680000 -0.01430000 0.00000000 0.03040559 0.00961665 -0.01751385 0.02107178 0.01209153 -Li 0.00000000 0.00000000 12.66500000 -0.00150000 -0.00010000 -0.00000000 -0.00010000 0.00440000 -0.00160000 -0.00000000 -0.00160000 -0.00290000 0.00000000 -0.00014142 -0.00226274 -0.00355176 0.00000000 -0.00417193 -Li 2.95500000 1.70600000 6.63400000 0.00290000 -0.00010000 -0.00000000 -0.00010000 0.00300000 -0.00000000 -0.00000000 -0.00000000 -0.00600000 0.00005774 -0.00014142 0.00000000 -0.00730764 0.00000000 -0.00007071 -Li 1.47700000 0.85300000 2.41200000 0.01810000 0.01350000 0.01490000 0.01350000 -0.00110000 0.00330000 0.01490000 0.00330000 -0.01710000 0.00005774 0.01909188 0.00466690 -0.02090231 0.02107178 0.01357645 -Li 0.00000000 3.41200000 11.45900000 0.01130000 0.00000000 0.00010000 0.00000000 0.00970000 0.00040000 0.00010000 0.00040000 -0.02090000 -0.00005774 0.00000000 0.00056569 -0.02563799 0.00014142 0.00113137 -Li 0.00000000 3.41200000 7.84000000 0.00330000 0.00000000 0.00010000 0.00000000 0.00330000 0.00000000 0.00010000 0.00000000 -0.00660000 0.00000000 0.00000000 0.00000000 -0.00808332 0.00014142 0.00000000 -Li 0.00000000 0.00000000 7.23700000 0.00680000 -0.00000000 -0.00010000 -0.00000000 0.00680000 -0.00000000 -0.00010000 -0.00000000 -0.01360000 0.00000000 0.00000000 0.00000000 -0.01665653 -0.00014142 0.00000000 -Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00000000 -0.00180000 -0.00000000 0.00390000 -0.00140000 -0.00180000 -0.00140000 -0.01010000 0.00000000 0.00000000 -0.00197990 -0.01236992 -0.00254558 0.00162635 -O 0.00000000 0.00000000 3.80700000 0.08950000 0.05940000 -0.09250000 0.05940000 0.02430000 -0.06160000 -0.09250000 -0.06160000 -0.11380000 0.00000000 0.08400429 -0.08711556 -0.13937597 -0.13081475 0.04610336 -O 0.00000000 0.00000000 10.66800000 0.07760000 -0.00090000 -0.00010000 -0.00090000 0.07950000 -0.00590000 -0.00010000 -0.00590000 -0.15710000 0.00000000 -0.00127279 -0.00834386 -0.19240742 -0.00014142 -0.00134350 -O 2.95500000 1.70600000 8.63200000 0.02600000 -0.00070000 -0.00010000 -0.00070000 0.02460000 -0.00110000 -0.00010000 -0.00110000 -0.05060000 0.00000000 -0.00098995 -0.00155563 -0.06197209 -0.00014142 0.00098995 -O 2.95500000 1.70600000 1.01800000 0.15360000 0.04470000 -0.07010000 0.04470000 -0.07160000 -0.21210000 -0.07010000 -0.21210000 -0.08200000 0.00000000 0.06321535 -0.29995470 -0.10042908 -0.09913637 0.15924045 -O 0.00000000 3.41200000 13.45700000 0.10120000 0.00520000 -0.01120000 0.00520000 -0.05480000 -0.19300000 -0.01120000 -0.19300000 -0.04640000 0.00000000 0.00735391 -0.27294322 -0.05682816 -0.01583919 0.11030866 -O 0.00000000 3.41200000 5.84300000 0.03490000 0.00280000 -0.01810000 0.00280000 0.02940000 -0.01130000 -0.01810000 -0.01130000 -0.06430000 0.00000000 0.00395980 -0.01598061 -0.07875110 -0.02559727 0.00388909 -O -1.32400000 4.17600000 1.14400000 -0.20720000 -0.14530000 0.16900000 -0.14530000 0.15220000 0.13880000 0.16900000 0.13880000 0.05500000 -0.00000000 -0.20548523 0.19629284 0.06736097 0.23900209 -0.25413418 -O 0.00000000 1.88300000 1.14400000 0.02580000 -0.08420000 0.09110000 -0.08420000 -0.09150000 0.00010000 0.09110000 0.00010000 0.06570000 -0.00000000 -0.11907678 0.00014142 0.08046574 0.12883486 0.08294363 -O 1.32400000 4.17600000 1.14400000 -0.13780000 0.09550000 -0.16630000 0.09550000 0.11670000 0.01030000 -0.16630000 0.01030000 0.02110000 -0.00000000 0.13505740 0.01456640 0.02584212 -0.23518372 -0.17995868 -O 4.27800000 0.94200000 13.33100000 -0.15640000 -0.09000000 0.17610000 -0.09000000 0.11360000 0.02190000 0.17610000 0.02190000 0.04280000 0.00000000 -0.12727922 0.03097128 0.05241908 0.24904301 -0.19091883 -O 1.63100000 0.94200000 13.33100000 -0.15070000 0.08630000 -0.17420000 0.08630000 0.09650000 0.00850000 -0.17420000 0.00850000 0.05420000 0.00000000 0.12204663 0.01202082 0.06638117 -0.24635600 -0.17479680 -O 2.95500000 3.23400000 13.33100000 0.03560000 -0.00850000 0.01110000 -0.00850000 -0.10140000 0.06960000 0.01110000 0.06960000 0.06580000 0.00000000 -0.01202082 0.09842926 0.08058821 0.01569777 0.09687363 -O 4.58600000 0.76400000 5.96800000 -0.14190000 0.14240000 -0.11360000 0.14240000 0.03020000 0.08570000 -0.11360000 0.08570000 0.11170000 -0.00000000 0.20138401 0.12119810 0.13680400 -0.16065466 -0.12169308 -O 2.95500000 3.58900000 5.96800000 0.11020000 -0.00640000 0.01300000 -0.00640000 -0.22490000 -0.13980000 0.01300000 -0.13980000 0.11460000 0.00005774 -0.00905097 -0.19770706 0.14039659 0.01838478 0.23695148 -O 1.32400000 0.76400000 5.96800000 -0.13810000 -0.13820000 0.09340000 -0.13820000 0.01460000 0.05920000 0.09340000 0.05920000 0.12350000 -0.00000000 -0.19544431 0.08372144 0.15125599 0.13208755 -0.10797521 -O 1.32400000 2.64800000 3.68100000 -0.11950000 0.08470000 0.01730000 0.08470000 0.12160000 0.10920000 0.01730000 0.10920000 -0.00210000 -0.00000000 0.11978389 0.15443212 -0.00257196 0.02446589 -0.17048344 -O -1.32400000 2.64800000 3.68100000 -0.04430000 -0.07970000 0.00620000 -0.07970000 0.05460000 0.03620000 0.00620000 0.03620000 -0.01030000 -0.00000000 -0.11271282 0.05119453 -0.01261487 0.00876812 -0.06993286 -O 0.00000000 4.94000000 3.68100000 0.12860000 -0.05840000 0.07390000 -0.05840000 -0.11960000 -0.02830000 0.07390000 -0.02830000 -0.00900000 0.00000000 -0.08259007 -0.04002224 -0.01102270 0.10451038 0.17550390 -O 1.63100000 2.47000000 10.79300000 -0.02080000 0.04810000 -0.00870000 0.04810000 0.04940000 0.03610000 -0.00870000 0.03610000 -0.02860000 0.00000000 0.06802367 0.05105311 -0.03502770 -0.01230366 -0.04963890 -O 2.95500000 0.17700000 10.79300000 0.07010000 -0.00270000 0.00100000 -0.00270000 -0.03630000 -0.01600000 0.00100000 -0.01600000 -0.03380000 0.00000000 -0.00381838 -0.02262742 -0.04139638 0.00141421 0.07523616 -O 4.27800000 2.47000000 10.79300000 -0.02030000 -0.05120000 0.00910000 -0.05120000 0.04760000 0.03530000 0.00910000 0.03530000 -0.02730000 0.00000000 -0.07240773 0.04992174 -0.03343553 0.01286934 -0.04801255 -O -1.63100000 4.35300000 8.50600000 -0.12520000 0.12520000 -0.08400000 0.12520000 0.01770000 0.05430000 -0.08400000 0.05430000 0.10760000 -0.00005774 0.17705954 0.07679180 0.13174172 -0.11879394 -0.10104556 -O 1.63100000 4.35300000 8.50600000 -0.12470000 -0.12830000 0.08390000 -0.12830000 0.01600000 0.05380000 0.08390000 0.05380000 0.10870000 0.00000000 -0.18144360 0.07608469 0.13312977 0.11865252 -0.09948992 -O 0.00000000 1.52800000 8.50600000 0.09000000 -0.00250000 0.00050000 -0.00250000 -0.20120000 -0.09860000 0.00050000 -0.09860000 0.11120000 -0.00000000 -0.00353553 -0.13944146 0.13619163 0.00070711 0.20590949 -Ti 2.95500000 1.70600000 12.06200000 0.07860000 -0.00050000 0.00180000 -0.00050000 0.17360000 0.08570000 0.00180000 0.08570000 -0.25220000 0.00000000 -0.00070711 0.12119810 -0.30888066 0.00254558 -0.06717514 -Ti 0.00000000 3.41200000 2.41200000 0.07090000 -0.03350000 0.03970000 -0.03350000 0.19740000 0.10880000 0.03970000 0.10880000 -0.26830000 0.00000000 -0.04737615 0.15386644 -0.32859905 0.05614428 -0.08944901 -Ti -1.47700000 2.55900000 0.00000000 -0.08610000 -0.26900000 0.06980000 -0.26900000 -0.02210000 -0.06920000 0.06980000 -0.06920000 0.10820000 -0.00000000 -0.38042345 -0.09786358 0.13251740 0.09871211 -0.04525483 -Ti 1.47700000 2.55900000 0.00000000 -0.07580000 0.27230000 -0.06120000 0.27230000 -0.04190000 -0.09490000 -0.06120000 -0.09490000 0.11770000 0.00000000 0.38509035 -0.13420887 0.14415247 -0.08654987 -0.02397092 -Ti 0.00000000 1.70600000 4.82500000 0.06620000 0.01140000 -0.02170000 0.01140000 -0.12110000 0.24240000 -0.02170000 0.24240000 0.05490000 0.00000000 0.01612203 0.34280537 0.06723849 -0.03068843 0.13244110 -Ti 1.47700000 4.26500000 4.82500000 -0.07220000 -0.07670000 -0.19310000 -0.07670000 0.02130000 -0.10830000 -0.19310000 -0.10830000 0.05090000 -0.00000000 -0.10847018 -0.15315933 0.06233951 -0.27308464 -0.06611448 -Ti -1.47700000 4.26500000 4.82500000 -0.06700000 0.08570000 0.19700000 0.08570000 0.01050000 -0.13570000 0.19700000 -0.13570000 0.05650000 -0.00000000 0.12119810 -0.19190878 0.06919809 0.27860007 -0.05480078 -Ti 2.95500000 3.41200000 9.65000000 0.08060000 0.00000000 -0.00000000 0.00000000 -0.12570000 0.20710000 -0.00000000 0.20710000 0.04510000 -0.00000000 0.00000000 0.29288363 0.05523599 0.00000000 0.14587613 -Ti 1.47700000 0.85300000 9.65000000 -0.07680000 -0.08910000 -0.17720000 -0.08910000 0.03030000 -0.10000000 -0.17720000 -0.10000000 0.04650000 -0.00000000 -0.12600643 -0.14142136 0.05695064 -0.25059864 -0.07573114 -Ti 4.43200000 0.85300000 9.65000000 -0.07670000 0.08900000 0.17740000 0.08900000 0.03020000 -0.10010000 0.17740000 -0.10010000 0.04640000 0.00005774 0.12586501 -0.14156278 0.05686899 0.25088149 -0.07558971 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00150000 -0.00010000 -0.00000000 -0.00010000 0.00440000 -0.00160000 -0.00000000 -0.00160000 -0.00290000 0.00000000 -0.00014142 -0.00226274 -0.00355176 0.00000000 -0.00417193 -Li 0.00000000 0.00000000 12.66500000 0.01570000 0.02150000 0.01490000 0.02150000 -0.00140000 0.00680000 0.01490000 0.00680000 -0.01430000 0.00000000 0.03040559 0.00961665 -0.01751385 0.02107178 0.01209153 -Li 2.95500000 1.70600000 6.63400000 0.00330000 0.00000000 0.00010000 0.00000000 0.00330000 0.00000000 0.00010000 0.00000000 -0.00660000 0.00000000 0.00000000 0.00000000 -0.00808332 0.00014142 0.00000000 -Li 2.95500000 1.70600000 3.01600000 0.01130000 0.00000000 0.00010000 0.00000000 0.00970000 0.00040000 0.00010000 0.00040000 -0.02090000 -0.00005774 0.00000000 0.00056569 -0.02563799 0.00014142 0.00113137 -Li 1.47700000 4.26500000 12.06200000 0.01810000 0.01350000 0.01490000 0.01350000 -0.00110000 0.00330000 0.01490000 0.00330000 -0.01710000 0.00005774 0.01909188 0.00466690 -0.02090231 0.02107178 0.01357645 -Li 0.00000000 3.41200000 7.84000000 0.00290000 -0.00010000 -0.00000000 -0.00010000 0.00300000 -0.00000000 -0.00000000 -0.00000000 -0.00600000 0.00005774 -0.00014142 0.00000000 -0.00730764 0.00000000 -0.00007071 -Li 0.00000000 0.00000000 7.23700000 0.00680000 -0.00000000 -0.00010000 -0.00000000 0.00680000 -0.00000000 -0.00010000 -0.00000000 -0.01360000 0.00000000 0.00000000 0.00000000 -0.01665653 -0.00014142 0.00000000 -Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00000000 -0.00180000 -0.00000000 0.00390000 -0.00140000 -0.00180000 -0.00140000 -0.01010000 0.00000000 0.00000000 -0.00197990 -0.01236992 -0.00254558 0.00162635 -O 0.00000000 0.00000000 3.80700000 0.07760000 -0.00090000 -0.00010000 -0.00090000 0.07950000 -0.00590000 -0.00010000 -0.00590000 -0.15710000 0.00000000 -0.00127279 -0.00834386 -0.19240742 -0.00014142 -0.00134350 -O 0.00000000 0.00000000 10.66800000 0.08950000 0.05940000 -0.09250000 0.05940000 0.02430000 -0.06160000 -0.09250000 -0.06160000 -0.11380000 0.00000000 0.08400429 -0.08711556 -0.13937597 -0.13081475 0.04610336 -O 2.95500000 1.70600000 8.63200000 0.03490000 0.00280000 -0.01810000 0.00280000 0.02940000 -0.01130000 -0.01810000 -0.01130000 -0.06430000 0.00000000 0.00395980 -0.01598061 -0.07875110 -0.02559727 0.00388909 -O 2.95500000 1.70600000 1.01800000 0.10120000 0.00520000 -0.01120000 0.00520000 -0.05480000 -0.19300000 -0.01120000 -0.19300000 -0.04640000 0.00000000 0.00735391 -0.27294322 -0.05682816 -0.01583919 0.11030866 -O 0.00000000 3.41200000 13.45700000 0.15360000 0.04470000 -0.07010000 0.04470000 -0.07160000 -0.21210000 -0.07010000 -0.21210000 -0.08200000 0.00000000 0.06321535 -0.29995470 -0.10042908 -0.09913637 0.15924045 -O 0.00000000 3.41200000 5.84300000 0.02600000 -0.00070000 -0.00010000 -0.00070000 0.02460000 -0.00110000 -0.00010000 -0.00110000 -0.05060000 0.00000000 -0.00098995 -0.00155563 -0.06197209 -0.00014142 0.00098995 -O -1.32400000 4.17600000 1.14400000 -0.15640000 -0.09000000 0.17610000 -0.09000000 0.11360000 0.02190000 0.17610000 0.02190000 0.04280000 0.00000000 -0.12727922 0.03097128 0.05241908 0.24904301 -0.19091883 -O 0.00000000 1.88300000 1.14400000 0.03560000 -0.00850000 0.01110000 -0.00850000 -0.10140000 0.06960000 0.01110000 0.06960000 0.06580000 0.00000000 -0.01202082 0.09842926 0.08058821 0.01569777 0.09687363 -O 1.32400000 4.17600000 1.14400000 -0.15070000 0.08630000 -0.17420000 0.08630000 0.09650000 0.00850000 -0.17420000 0.00850000 0.05420000 0.00000000 0.12204663 0.01202082 0.06638117 -0.24635600 -0.17479680 -O 4.27800000 0.94200000 13.33100000 -0.20720000 -0.14530000 0.16900000 -0.14530000 0.15220000 0.13880000 0.16900000 0.13880000 0.05500000 -0.00000000 -0.20548523 0.19629284 0.06736097 0.23900209 -0.25413418 -O 1.63100000 0.94200000 13.33100000 -0.13780000 0.09550000 -0.16630000 0.09550000 0.11670000 0.01030000 -0.16630000 0.01030000 0.02110000 -0.00000000 0.13505740 0.01456640 0.02584212 -0.23518372 -0.17995868 -O 2.95500000 3.23400000 13.33100000 0.02580000 -0.08420000 0.09110000 -0.08420000 -0.09150000 0.00010000 0.09110000 0.00010000 0.06570000 -0.00000000 -0.11907678 0.00014142 0.08046574 0.12883486 0.08294363 -O 4.58600000 0.76400000 5.96800000 -0.12520000 0.12520000 -0.08400000 0.12520000 0.01770000 0.05430000 -0.08400000 0.05430000 0.10760000 -0.00005774 0.17705954 0.07679180 0.13174172 -0.11879394 -0.10104556 -O 2.95500000 3.58900000 5.96800000 0.09000000 -0.00250000 0.00050000 -0.00250000 -0.20120000 -0.09860000 0.00050000 -0.09860000 0.11120000 -0.00000000 -0.00353553 -0.13944146 0.13619163 0.00070711 0.20590949 -O 1.32400000 0.76400000 5.96800000 -0.12470000 -0.12830000 0.08390000 -0.12830000 0.01600000 0.05380000 0.08390000 0.05380000 0.10870000 0.00000000 -0.18144360 0.07608469 0.13312977 0.11865252 -0.09948992 -O 1.32400000 2.64800000 3.68100000 -0.02080000 0.04810000 -0.00870000 0.04810000 0.04940000 0.03610000 -0.00870000 0.03610000 -0.02860000 0.00000000 0.06802367 0.05105311 -0.03502770 -0.01230366 -0.04963890 -O -1.32400000 2.64800000 3.68100000 -0.02030000 -0.05120000 0.00910000 -0.05120000 0.04760000 0.03530000 0.00910000 0.03530000 -0.02730000 0.00000000 -0.07240773 0.04992174 -0.03343553 0.01286934 -0.04801255 -O 0.00000000 4.94000000 3.68100000 0.07010000 -0.00270000 0.00100000 -0.00270000 -0.03630000 -0.01600000 0.00100000 -0.01600000 -0.03380000 0.00000000 -0.00381838 -0.02262742 -0.04139638 0.00141421 0.07523616 -O 1.63100000 2.47000000 10.79300000 -0.11950000 0.08470000 0.01730000 0.08470000 0.12160000 0.10920000 0.01730000 0.10920000 -0.00210000 -0.00000000 0.11978389 0.15443212 -0.00257196 0.02446589 -0.17048344 -O 2.95500000 0.17700000 10.79300000 0.12860000 -0.05840000 0.07390000 -0.05840000 -0.11960000 -0.02830000 0.07390000 -0.02830000 -0.00900000 0.00000000 -0.08259007 -0.04002224 -0.01102270 0.10451038 0.17550390 -O 4.27800000 2.47000000 10.79300000 -0.04430000 -0.07970000 0.00620000 -0.07970000 0.05460000 0.03620000 0.00620000 0.03620000 -0.01030000 -0.00000000 -0.11271282 0.05119453 -0.01261487 0.00876812 -0.06993286 -O -1.63100000 4.35300000 8.50600000 -0.14190000 0.14240000 -0.11360000 0.14240000 0.03020000 0.08570000 -0.11360000 0.08570000 0.11170000 -0.00000000 0.20138401 0.12119810 0.13680400 -0.16065466 -0.12169308 -O 1.63100000 4.35300000 8.50600000 -0.13810000 -0.13820000 0.09340000 -0.13820000 0.01460000 0.05920000 0.09340000 0.05920000 0.12350000 -0.00000000 -0.19544431 0.08372144 0.15125599 0.13208755 -0.10797521 -O 0.00000000 1.52800000 8.50600000 0.11020000 -0.00640000 0.01300000 -0.00640000 -0.22490000 -0.13980000 0.01300000 -0.13980000 0.11460000 0.00005774 -0.00905097 -0.19770706 0.14039659 0.01838478 0.23695148 -Ti 2.95500000 1.70600000 12.06200000 0.07090000 -0.03350000 0.03970000 -0.03350000 0.19740000 0.10880000 0.03970000 0.10880000 -0.26830000 0.00000000 -0.04737615 0.15386644 -0.32859905 0.05614428 -0.08944901 -Ti 0.00000000 3.41200000 2.41200000 0.07860000 -0.00050000 0.00180000 -0.00050000 0.17360000 0.08570000 0.00180000 0.08570000 -0.25220000 0.00000000 -0.00070711 0.12119810 -0.30888066 0.00254558 -0.06717514 -Ti -1.47700000 2.55900000 0.00000000 -0.08610000 -0.26900000 0.06980000 -0.26900000 -0.02210000 -0.06920000 0.06980000 -0.06920000 0.10820000 -0.00000000 -0.38042345 -0.09786358 0.13251740 0.09871211 -0.04525483 -Ti 1.47700000 2.55900000 0.00000000 -0.07580000 0.27230000 -0.06120000 0.27230000 -0.04190000 -0.09490000 -0.06120000 -0.09490000 0.11770000 0.00000000 0.38509035 -0.13420887 0.14415247 -0.08654987 -0.02397092 -Ti 0.00000000 1.70600000 4.82500000 0.08060000 0.00000000 -0.00000000 0.00000000 -0.12570000 0.20710000 -0.00000000 0.20710000 0.04510000 -0.00000000 0.00000000 0.29288363 0.05523599 0.00000000 0.14587613 -Ti 1.47700000 4.26500000 4.82500000 -0.07680000 -0.08910000 -0.17720000 -0.08910000 0.03030000 -0.10000000 -0.17720000 -0.10000000 0.04650000 -0.00000000 -0.12600643 -0.14142136 0.05695064 -0.25059864 -0.07573114 -Ti -1.47700000 4.26500000 4.82500000 -0.07670000 0.08900000 0.17740000 0.08900000 0.03020000 -0.10010000 0.17740000 -0.10010000 0.04640000 0.00005774 0.12586501 -0.14156278 0.05686899 0.25088149 -0.07558971 -Ti 2.95500000 3.41200000 9.65000000 0.06620000 0.01140000 -0.02170000 0.01140000 -0.12110000 0.24240000 -0.02170000 0.24240000 0.05490000 0.00000000 0.01612203 0.34280537 0.06723849 -0.03068843 0.13244110 -Ti 1.47700000 0.85300000 9.65000000 -0.07220000 -0.07670000 -0.19310000 -0.07670000 0.02130000 -0.10830000 -0.19310000 -0.10830000 0.05090000 -0.00000000 -0.10847018 -0.15315933 0.06233951 -0.27308464 -0.06611448 -Ti 4.43200000 0.85300000 9.65000000 -0.06700000 0.08570000 0.19700000 0.08570000 0.01050000 -0.13570000 0.19700000 -0.13570000 0.05650000 -0.00000000 0.12119810 -0.19190878 0.06919809 0.27860007 -0.05480078 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00070000 -0.00000000 -0.00000000 -0.00000000 0.00640000 -0.00180000 -0.00000000 -0.00180000 -0.00570000 0.00000000 0.00000000 -0.00254558 -0.00698105 0.00000000 -0.00502046 -Li 0.00000000 0.00000000 12.66500000 -0.00150000 -0.00000000 -0.00000000 -0.00000000 0.00510000 -0.00140000 -0.00000000 -0.00140000 -0.00360000 0.00000000 0.00000000 -0.00197990 -0.00440908 0.00000000 -0.00466690 -Li 2.95500000 1.70600000 6.63400000 0.00020000 -0.00000000 0.00000000 -0.00000000 0.00110000 0.00000000 0.00000000 0.00000000 -0.00130000 0.00000000 0.00000000 0.00000000 -0.00159217 0.00000000 -0.00063640 -Li 2.95500000 1.70600000 3.01600000 0.01000000 -0.00000000 -0.00000000 -0.00000000 0.00940000 0.00030000 -0.00000000 0.00030000 -0.01940000 0.00000000 0.00000000 0.00042426 -0.02376005 0.00000000 0.00042426 -Li 0.00000000 3.41200000 11.45900000 -0.00540000 -0.00000000 0.00000000 -0.00000000 -0.00610000 0.00050000 0.00000000 0.00050000 0.01150000 -0.00000000 0.00000000 0.00070711 0.01408457 0.00000000 0.00049497 -Li 0.00000000 3.41200000 9.65000000 -0.00900000 -0.00000000 0.00000000 -0.00000000 -0.00790000 0.00010000 0.00000000 0.00010000 0.01690000 -0.00000000 0.00000000 0.00014142 0.02069819 0.00000000 -0.00077782 -Li 0.00000000 0.00000000 7.23700000 0.00590000 -0.00000000 -0.00000000 -0.00000000 0.00690000 0.00000000 -0.00000000 0.00000000 -0.01280000 0.00000000 0.00000000 0.00000000 -0.01567673 0.00000000 -0.00070711 -Li 2.95500000 0.00000000 0.00000000 0.00580000 0.00000000 -0.00000000 0.00000000 0.00570000 -0.00170000 -0.00000000 -0.00170000 -0.01150000 0.00000000 0.00000000 -0.00240416 -0.01408457 0.00000000 0.00007071 -O 0.00000000 0.00000000 3.80700000 0.07770000 -0.00000000 -0.00000000 -0.00000000 0.09110000 -0.00660000 -0.00000000 -0.00660000 -0.16880000 0.00000000 0.00000000 -0.00933381 -0.20673693 0.00000000 -0.00947523 -O 0.00000000 0.00000000 10.66800000 0.10130000 -0.00000000 0.00000000 -0.00000000 0.11440000 -0.00590000 0.00000000 -0.00590000 -0.21570000 0.00000000 0.00000000 -0.00834386 -0.26417747 0.00000000 -0.00926310 -O 2.95500000 1.70600000 8.63200000 0.01210000 0.00000000 -0.00000000 0.00000000 0.02250000 -0.00110000 -0.00000000 -0.00110000 -0.03460000 0.00000000 0.00000000 -0.00155563 -0.04237617 0.00000000 -0.00735391 -O 2.95500000 1.70600000 1.01800000 0.08340000 -0.00000000 -0.00000000 -0.00000000 -0.04510000 -0.17380000 -0.00000000 -0.17380000 -0.03830000 0.00000000 0.00000000 -0.24579032 -0.04690773 0.00000000 0.09086322 -O 0.00000000 3.41200000 13.45700000 0.10070000 0.00000000 -0.00000000 0.00000000 -0.05640000 -0.17440000 -0.00000000 -0.17440000 -0.04430000 0.00000000 0.00000000 -0.24663885 -0.05425620 0.00000000 0.11108648 -O 0.00000000 3.41200000 5.84300000 0.01540000 -0.00000000 0.00000000 -0.00000000 0.02560000 -0.00100000 0.00000000 -0.00100000 -0.04100000 0.00000000 0.00000000 -0.00141421 -0.05021454 0.00000000 -0.00721249 -O -1.32400000 4.17600000 1.14400000 -0.14670000 -0.08680000 0.17350000 -0.08680000 0.11800000 0.02060000 0.17350000 0.02060000 0.02870000 0.00000000 -0.12275374 0.02913280 0.03515018 0.24536605 -0.18717116 -O 0.00000000 1.88300000 1.14400000 0.03010000 -0.00000000 0.00000000 -0.00000000 -0.08910000 0.06180000 0.00000000 0.06180000 0.05900000 0.00000000 0.00000000 0.08739840 0.07225995 0.00000000 0.08428713 -O 1.32400000 4.17600000 1.14400000 -0.14670000 0.08680000 -0.17350000 0.08680000 0.11800000 0.02060000 -0.17350000 0.02060000 0.02870000 0.00000000 0.12275374 0.02913280 0.03515018 -0.24536605 -0.18717116 -O 4.27800000 0.94200000 13.33100000 -0.15480000 -0.08910000 0.16710000 -0.08910000 0.10510000 0.00240000 0.16710000 0.00240000 0.04970000 -0.00000000 -0.12600643 0.00339411 0.06086982 0.23631509 -0.18377705 -O 1.63100000 0.94200000 13.33100000 -0.15480000 0.08910000 -0.16710000 0.08910000 0.10510000 0.00240000 -0.16710000 0.00240000 0.04970000 -0.00000000 0.12600643 0.00339411 0.06086982 -0.23631509 -0.18377705 -O 2.95500000 3.23400000 13.33100000 0.00980000 0.00000000 -0.00000000 0.00000000 -0.07390000 0.08330000 -0.00000000 0.08330000 0.06410000 -0.00000000 0.00000000 0.11780399 0.07850615 0.00000000 0.05918484 -O 4.58600000 0.76400000 5.96800000 -0.12720000 0.10680000 -0.07420000 0.10680000 0.00650000 0.04830000 -0.07420000 0.04830000 0.12070000 -0.00000000 0.15103801 0.06830652 0.14782671 -0.10493465 -0.09454018 -O 2.95500000 3.58900000 5.96800000 0.05600000 0.00000000 0.00000000 0.00000000 -0.17810000 -0.08700000 0.00000000 -0.08700000 0.12210000 0.00000000 0.00000000 -0.12303658 0.14954135 0.00000000 0.16553370 -O 1.32400000 0.76400000 5.96800000 -0.12720000 -0.10680000 0.07420000 -0.10680000 0.00650000 0.04830000 0.07420000 0.04830000 0.12070000 -0.00000000 -0.15103801 0.06830652 0.14782671 0.10493465 -0.09454018 -O 1.32400000 2.64800000 3.68100000 -0.01890000 0.04830000 0.01380000 0.04830000 0.06090000 0.02540000 0.01380000 0.02540000 -0.04190000 -0.00005774 0.06830652 0.03592102 -0.05135763 0.01951615 -0.05642712 -O -1.32400000 2.64800000 3.68100000 -0.01890000 -0.04830000 -0.01380000 -0.04830000 0.06090000 0.02540000 -0.01380000 0.02540000 -0.04190000 -0.00005774 -0.06830652 0.03592102 -0.05135763 -0.01951615 -0.05642712 -O 0.00000000 4.94000000 3.68100000 0.06880000 -0.00000000 0.00000000 -0.00000000 -0.02120000 0.01080000 0.00000000 0.01080000 -0.04770000 0.00005774 0.00000000 0.01527351 -0.05837951 0.00000000 0.06363961 -O 1.63100000 2.47000000 10.79300000 0.04520000 -0.02420000 0.06790000 -0.02420000 0.04390000 -0.01120000 0.06790000 -0.01120000 -0.08900000 -0.00005774 -0.03422397 -0.01583919 -0.10904312 0.09602510 0.00091924 -O 2.95500000 0.17700000 10.79300000 0.01120000 0.00000000 0.00000000 0.00000000 0.08430000 0.07320000 0.00000000 0.07320000 -0.09540000 -0.00005774 0.00000000 0.10352043 -0.11688149 0.00000000 -0.05168951 -O 4.27800000 2.47000000 10.79300000 0.04520000 0.02420000 -0.06790000 0.02420000 0.04390000 -0.01120000 -0.06790000 -0.01120000 -0.08900000 -0.00005774 0.03422397 -0.01583919 -0.10904312 -0.09602510 0.00091924 -O -1.63100000 4.35300000 8.50600000 -0.12280000 0.06940000 0.01710000 0.06940000 -0.03300000 -0.00380000 0.01710000 -0.00380000 0.15580000 -0.00000000 0.09814642 -0.00537401 0.19081525 0.02418305 -0.06349819 -O 1.63100000 4.35300000 8.50600000 -0.12280000 -0.06940000 -0.01710000 -0.06940000 -0.03300000 -0.00380000 -0.01710000 -0.00380000 0.15580000 -0.00000000 -0.09814642 -0.00537401 0.19081525 -0.02418305 -0.06349819 -O 0.00000000 1.52800000 8.50600000 -0.00600000 0.00000000 -0.00000000 0.00000000 -0.15180000 0.01830000 -0.00000000 0.01830000 0.15780000 -0.00000000 0.00000000 0.02588011 0.19326474 0.00000000 0.10309617 -Ti 2.95500000 1.70600000 12.06200000 0.07030000 -0.00000000 0.00000000 -0.00000000 0.17070000 0.08150000 0.00000000 0.08150000 -0.24100000 0.00000000 0.00000000 0.11525841 -0.29516351 0.00000000 -0.07099352 -Ti 0.00000000 3.41200000 2.41200000 0.08370000 0.00000000 -0.00000000 0.00000000 0.16840000 0.08770000 -0.00000000 0.08770000 -0.25210000 0.00000000 0.00000000 0.12402653 -0.30875818 0.00000000 -0.05989194 -Ti -1.47700000 2.55900000 0.00000000 -0.08030000 -0.24840000 0.04920000 -0.24840000 -0.03860000 -0.07620000 0.04920000 -0.07620000 0.11890000 -0.00000000 -0.35129065 -0.10776307 0.14562217 0.06957931 -0.02948635 -Ti 1.47700000 2.55900000 0.00000000 -0.08030000 0.24840000 -0.04920000 0.24840000 -0.03860000 -0.07620000 -0.04920000 -0.07620000 0.11890000 -0.00000000 0.35129065 -0.10776307 0.14562217 -0.06957931 -0.02948635 -Ti 0.00000000 1.70600000 4.82500000 0.10290000 -0.00000000 -0.00000000 0.00000000 -0.14830000 0.18380000 -0.00000000 0.18380000 0.04540000 -0.00000000 0.00000000 0.25993245 0.05560342 0.00000000 0.17762522 -Ti 1.47700000 4.26500000 4.82500000 -0.08800000 -0.10810000 -0.15700000 -0.10810000 0.04130000 -0.08770000 -0.15700000 -0.08770000 0.04670000 -0.00000000 -0.15287649 -0.12402653 0.05719559 -0.22203153 -0.09142891 -Ti -1.47700000 4.26500000 4.82500000 -0.08800000 0.10810000 0.15700000 0.10810000 0.04130000 -0.08770000 0.15700000 -0.08770000 0.04670000 -0.00000000 0.15287649 -0.12402653 0.05719559 0.22203153 -0.09142891 -Ti 2.95500000 3.41200000 9.65000000 0.11030000 -0.00000000 -0.00000000 -0.00000000 -0.16040000 0.18740000 -0.00000000 0.18740000 0.05020000 -0.00005774 0.00000000 0.26502362 0.06144137 0.00000000 0.19141381 -Ti 1.47700000 0.85300000 9.65000000 -0.09530000 -0.11730000 -0.16050000 -0.11730000 0.04440000 -0.09030000 -0.16050000 -0.09030000 0.05090000 -0.00000000 -0.16588725 -0.12770348 0.06233951 -0.22698128 -0.09878282 -Ti 4.43200000 0.85300000 9.65000000 -0.09530000 0.11730000 0.16050000 0.11730000 0.04440000 -0.09030000 0.16050000 -0.09030000 0.05090000 -0.00000000 0.16588725 -0.12770348 0.06233951 0.22698128 -0.09878282 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00230000 -0.00000000 -0.00000000 -0.00000000 0.00450000 -0.00150000 -0.00000000 -0.00150000 -0.00210000 -0.00005774 0.00000000 -0.00212132 -0.00261279 0.00000000 -0.00480833 -Li 0.00000000 0.00000000 12.66500000 -0.00110000 -0.00000000 -0.00000000 -0.00000000 0.00590000 -0.00180000 -0.00000000 -0.00180000 -0.00480000 0.00000000 0.00000000 -0.00254558 -0.00587878 0.00000000 -0.00494975 -Li 2.95500000 1.70600000 6.63400000 -0.01510000 -0.00000000 0.00000000 -0.00000000 0.03490000 -0.01780000 0.00000000 -0.01780000 -0.01980000 0.00000000 0.00000000 -0.02517300 -0.02424995 0.00000000 -0.03535534 -Li 2.95500000 1.70600000 3.01600000 0.01200000 -0.00000000 -0.00000000 -0.00000000 0.01110000 0.00040000 -0.00000000 0.00040000 -0.02320000 0.00005774 0.00000000 0.00056569 -0.02837326 0.00000000 0.00063640 -Li 0.00000000 3.41200000 11.45900000 0.01140000 -0.00000000 0.00000000 -0.00000000 0.01050000 0.00030000 0.00000000 0.00030000 -0.02190000 0.00000000 0.00000000 0.00042426 -0.02682191 0.00000000 0.00063640 -Li 2.95500000 0.00000000 7.23700000 -0.02060000 0.00000000 0.00000000 0.00000000 0.03310000 -0.00560000 0.00000000 -0.00560000 -0.01250000 0.00000000 0.00000000 -0.00791960 -0.01530931 0.00000000 -0.03797163 -Li 0.00000000 0.00000000 7.23700000 0.01140000 -0.00000000 0.00000000 -0.00000000 0.00450000 -0.00110000 0.00000000 -0.00110000 -0.01590000 0.00000000 0.00000000 -0.00155563 -0.01947344 0.00000000 0.00487904 -Li 2.95500000 0.00000000 0.00000000 0.00580000 0.00000000 -0.00000000 0.00000000 0.00570000 -0.00180000 -0.00000000 -0.00180000 -0.01140000 -0.00005774 0.00000000 -0.00254558 -0.01400292 0.00000000 0.00007071 -O 0.00000000 0.00000000 3.80700000 0.06940000 -0.00000000 -0.00000000 -0.00000000 0.08860000 0.01840000 -0.00000000 0.01840000 -0.15800000 0.00000000 0.00000000 0.02602153 -0.19350969 0.00000000 -0.01357645 -O 0.00000000 0.00000000 10.66800000 0.07980000 -0.00000000 0.00000000 -0.00000000 0.10180000 0.01710000 0.00000000 0.01710000 -0.18170000 0.00005774 0.00000000 0.02418305 -0.22249532 0.00000000 -0.01555635 -O 2.95500000 1.70600000 8.63200000 -0.06730000 0.00000000 0.00000000 0.00000000 0.08220000 0.10550000 0.00000000 0.10550000 -0.01490000 0.00000000 0.00000000 0.14919953 -0.01824870 0.00000000 -0.10571246 -O 2.95500000 1.70600000 1.01800000 0.10180000 -0.00000000 -0.00000000 -0.00000000 -0.04890000 -0.17300000 -0.00000000 -0.17300000 -0.05290000 0.00000000 0.00000000 -0.24465895 -0.06478900 0.00000000 0.10656099 -O 0.00000000 3.41200000 13.45700000 0.08700000 0.00000000 0.00000000 0.00000000 -0.04170000 -0.16980000 0.00000000 -0.16980000 -0.04530000 0.00000000 0.00000000 -0.24013346 -0.05548094 0.00000000 0.09100464 -O 0.00000000 3.41200000 5.84300000 -0.01930000 0.00000000 -0.00000000 0.00000000 0.11900000 0.06790000 -0.00000000 0.06790000 -0.09970000 0.00000000 0.00000000 0.09602510 -0.12210706 0.00000000 -0.09779287 -O -1.32400000 4.17600000 1.14400000 -0.15930000 -0.08110000 0.16180000 -0.08110000 0.10950000 0.00390000 0.16180000 0.00390000 0.04980000 -0.00000000 -0.11469272 0.00551543 0.06099229 0.22881975 -0.19007030 -O 0.00000000 1.88300000 1.14400000 0.01710000 0.00000000 0.00000000 0.00000000 -0.08150000 0.07440000 0.00000000 0.07440000 0.06450000 -0.00005774 0.00000000 0.10521749 0.07895522 0.00000000 0.06972073 -O 1.32400000 4.17600000 1.14400000 -0.15930000 0.08110000 -0.16180000 0.08110000 0.10950000 0.00390000 -0.16180000 0.00390000 0.04980000 -0.00000000 0.11469272 0.00551543 0.06099229 -0.22881975 -0.19007030 -O 4.27800000 0.94200000 13.33100000 -0.14790000 -0.08290000 0.16630000 -0.08290000 0.11540000 0.01520000 0.16630000 0.01520000 0.03250000 -0.00000000 -0.11723830 0.02149605 0.03980421 0.23518372 -0.18618122 -O 1.63100000 0.94200000 13.33100000 -0.14790000 0.08290000 -0.16630000 0.08290000 0.11540000 0.01520000 -0.16630000 0.01520000 0.03250000 -0.00000000 0.11723830 0.02149605 0.03980421 -0.23518372 -0.18618122 -O 2.95500000 3.23400000 13.33100000 0.02820000 -0.00000000 -0.00000000 -0.00000000 -0.08740000 0.06430000 -0.00000000 0.06430000 0.05920000 -0.00000000 0.00000000 0.09093393 0.07250490 0.00000000 0.08174154 -O 4.58600000 0.76400000 5.96800000 -0.04120000 0.16480000 -0.16110000 0.16480000 -0.01680000 0.02310000 -0.16110000 0.02310000 0.05800000 -0.00000000 0.23306240 0.03266833 0.07103520 -0.22782980 -0.01725341 -O 2.95500000 3.58900000 5.96800000 0.06400000 -0.00000000 0.00000000 -0.00000000 -0.15960000 -0.08680000 0.00000000 -0.08680000 0.09570000 -0.00005774 0.00000000 -0.12275374 0.11716726 0.00000000 0.15810908 -O 1.32400000 0.76400000 5.96800000 -0.04120000 -0.16480000 0.16110000 -0.16480000 -0.01680000 0.02310000 0.16110000 0.02310000 0.05800000 -0.00000000 -0.23306240 0.03266833 0.07103520 0.22782980 -0.01725341 -O 1.32400000 2.64800000 3.68100000 -0.02300000 0.05840000 -0.02260000 0.05840000 0.05770000 0.02640000 -0.02260000 0.02640000 -0.03470000 0.00000000 0.08259007 0.03733524 -0.04249865 -0.03196123 -0.05706352 -O -1.32400000 2.64800000 3.68100000 -0.02300000 -0.05840000 0.02260000 -0.05840000 0.05770000 0.02640000 0.02260000 0.02640000 -0.03470000 0.00000000 -0.08259007 0.03733524 -0.04249865 0.03196123 -0.05706352 -O 0.00000000 4.94000000 3.68100000 0.06660000 -0.00000000 -0.00000000 -0.00000000 -0.02540000 -0.00310000 -0.00000000 -0.00310000 -0.04120000 0.00000000 0.00000000 -0.00438406 -0.05045949 0.00000000 0.06505382 -O 1.63100000 2.47000000 10.79300000 -0.02630000 0.06110000 -0.03130000 0.06110000 0.05450000 0.03760000 -0.03130000 0.03760000 -0.02820000 0.00000000 0.08640845 0.05317443 -0.03453781 -0.04426488 -0.05713423 -O 2.95500000 0.17700000 10.79300000 0.06570000 0.00000000 0.00000000 0.00000000 -0.03300000 -0.01450000 0.00000000 -0.01450000 -0.03270000 0.00000000 0.00000000 -0.02050610 -0.04004916 0.00000000 0.06979144 -O 4.27800000 2.47000000 10.79300000 -0.02630000 -0.06110000 0.03130000 -0.06110000 0.05450000 0.03760000 0.03130000 0.03760000 -0.02820000 0.00000000 -0.08640845 0.05317443 -0.03453781 0.04426488 -0.05713423 -O -1.63100000 4.35300000 8.50600000 -0.11740000 0.20700000 -0.14470000 0.20700000 0.01590000 0.02000000 -0.14470000 0.02000000 0.10150000 -0.00000000 0.29274221 0.02828427 0.12431160 -0.20463670 -0.09425733 -O 1.63100000 4.35300000 8.50600000 -0.11740000 -0.20700000 0.14470000 -0.20700000 0.01590000 0.02000000 0.14470000 0.02000000 0.10150000 -0.00000000 -0.29274221 0.02828427 0.12431160 0.20463670 -0.09425733 -O 0.00000000 1.52800000 8.50600000 0.08370000 -0.00000000 -0.00000000 -0.00000000 -0.21000000 -0.10370000 -0.00000000 -0.10370000 0.12630000 -0.00000000 0.00000000 -0.14665395 0.15468528 0.00000000 0.20767726 -Ti 2.95500000 1.70600000 12.06200000 0.08100000 0.00000000 -0.00000000 0.00000000 0.16990000 0.08550000 -0.00000000 0.08550000 -0.25100000 0.00005774 0.00000000 0.12091526 -0.30737014 0.00000000 -0.06286179 -Ti 0.00000000 3.41200000 2.41200000 0.07420000 -0.00000000 0.00000000 -0.00000000 0.17140000 0.08100000 0.00000000 0.08100000 -0.24570000 0.00005774 0.00000000 0.11455130 -0.30087899 0.00000000 -0.06873078 -Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24890000 0.04850000 -0.24890000 -0.03780000 -0.07620000 0.04850000 -0.07620000 0.11880000 0.00000000 -0.35199776 -0.10776307 0.14549969 0.06858936 -0.03054701 -Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24890000 -0.04850000 0.24890000 -0.03780000 -0.07620000 -0.04850000 -0.07620000 0.11880000 0.00000000 0.35199776 -0.10776307 0.14549969 -0.06858936 -0.03054701 -Ti 0.00000000 1.70600000 4.82500000 0.08870000 0.00000000 0.00000000 0.00000000 -0.12920000 0.18630000 0.00000000 0.18630000 0.04050000 -0.00000000 0.00000000 0.26346799 0.04960217 0.00000000 0.15407857 -Ti 1.47700000 4.26500000 4.82500000 -0.09120000 -0.08730000 -0.18610000 -0.08730000 0.04860000 -0.07900000 -0.18610000 -0.07900000 0.04260000 0.00000000 -0.12346084 -0.11172287 0.05217413 -0.26318514 -0.09885353 -Ti -1.47700000 4.26500000 4.82500000 -0.09120000 0.08730000 0.18610000 0.08730000 0.04860000 -0.07900000 0.18610000 -0.07900000 0.04260000 0.00000000 0.12346084 -0.11172287 0.05217413 0.26318514 -0.09885353 -Ti 2.95500000 3.41200000 9.65000000 0.06480000 0.00000000 -0.00000000 0.00000000 -0.12360000 0.23790000 -0.00000000 0.23790000 0.05870000 0.00005774 0.00000000 0.33644141 0.07193335 0.00000000 0.13321892 -Ti 1.47700000 0.85300000 9.65000000 -0.09310000 -0.07800000 -0.22360000 -0.07800000 0.03210000 -0.09990000 -0.22360000 -0.09990000 0.06100000 0.00000000 -0.11030866 -0.14127993 0.07470944 -0.31621815 -0.08852977 -Ti 4.43200000 0.85300000 9.65000000 -0.09310000 0.07800000 0.22360000 0.07800000 0.03210000 -0.09990000 0.22360000 -0.09990000 0.06100000 0.00000000 0.11030866 -0.14127993 0.07470944 0.31621815 -0.08852977 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00220000 -0.00000000 -0.00010000 -0.00000000 0.00370000 -0.00140000 -0.00010000 -0.00140000 -0.00150000 0.00000000 0.00000000 -0.00197990 -0.00183712 -0.00014142 -0.00417193 -Li 0.00000000 0.00000000 12.66500000 -0.00100000 -0.00000000 -0.00010000 -0.00000000 0.00510000 -0.00160000 -0.00010000 -0.00160000 -0.00410000 0.00000000 0.00000000 -0.00226274 -0.00502145 -0.00014142 -0.00431335 -Li 2.95500000 1.70600000 6.63400000 0.02180000 -0.02130000 -0.01540000 -0.02130000 -0.00270000 0.00890000 -0.01540000 0.00890000 -0.01910000 0.00000000 -0.03012275 0.01258650 -0.02339263 -0.02177889 0.01732412 -Li 2.95500000 1.70600000 3.01600000 0.01200000 0.00010000 0.00000000 0.00010000 0.01050000 0.00040000 0.00000000 0.00040000 -0.02250000 0.00000000 0.00014142 0.00056569 -0.02755676 0.00000000 0.00106066 -Li 0.00000000 3.41200000 11.45900000 0.01130000 0.00010000 0.00000000 0.00010000 0.00990000 0.00030000 0.00000000 0.00030000 -0.02120000 0.00000000 0.00014142 0.00042426 -0.02596459 0.00000000 0.00098995 -Li 1.47700000 2.55900000 7.23700000 0.01920000 -0.02290000 -0.00480000 -0.02290000 -0.00730000 0.00280000 -0.00480000 0.00280000 -0.01190000 0.00000000 -0.03238549 0.00395980 -0.01457446 -0.00678823 0.01873833 -Li 0.00000000 0.00000000 7.23700000 0.00560000 0.00340000 -0.00100000 0.00340000 0.00960000 0.00060000 -0.00100000 0.00060000 -0.01520000 0.00000000 0.00480833 0.00084853 -0.01861612 -0.00141421 -0.00282843 -Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 -0.00000000 -0.00000000 0.00480000 -0.00180000 -0.00000000 -0.00180000 -0.01070000 0.00005774 0.00000000 -0.00254558 -0.01306395 0.00000000 0.00070711 -O 0.00000000 0.00000000 3.80700000 0.07620000 -0.00360000 0.02070000 -0.00360000 0.07480000 -0.01690000 0.02070000 -0.01690000 -0.15100000 0.00000000 -0.00509117 -0.02390021 -0.18493648 0.02927422 0.00098995 -O 0.00000000 0.00000000 10.66800000 0.08860000 -0.00480000 0.02040000 -0.00480000 0.08610000 -0.01770000 0.02040000 -0.01770000 -0.17470000 0.00000000 -0.00678823 -0.02503158 -0.21396293 0.02884996 0.00176777 -O 2.95500000 1.70600000 8.63200000 0.03910000 -0.06090000 0.09240000 -0.06090000 -0.03170000 -0.05420000 0.09240000 -0.05420000 -0.00740000 0.00000000 -0.08612561 -0.07665038 -0.00906311 0.13067333 0.05006316 -O 2.95500000 1.70600000 1.01800000 0.10390000 -0.00050000 0.00080000 -0.00050000 -0.05930000 -0.17410000 0.00080000 -0.17410000 -0.04470000 0.00005774 -0.00070711 -0.24621458 -0.05470527 0.00113137 0.11539983 -O 0.00000000 3.41200000 13.45700000 0.08930000 -0.00050000 0.00080000 -0.00050000 -0.05200000 -0.17110000 0.00080000 -0.17110000 -0.03730000 0.00000000 -0.00070711 -0.24197194 -0.04568298 0.00113137 0.09991419 -O 0.00000000 3.41200000 5.84300000 0.07920000 -0.05650000 0.06010000 -0.05650000 0.01340000 -0.03600000 0.06010000 -0.03600000 -0.09260000 0.00000000 -0.07990307 -0.05091169 -0.11341138 0.08499424 0.04652763 -O -1.32400000 4.17600000 1.14400000 -0.15650000 -0.08220000 0.16440000 -0.08220000 0.10090000 0.00520000 0.16440000 0.00520000 0.05560000 -0.00000000 -0.11624835 0.00735391 0.06809581 0.23249671 -0.18200929 -O 0.00000000 1.88300000 1.14400000 0.01950000 -0.00210000 -0.00150000 -0.00210000 -0.09380000 0.07380000 -0.00150000 0.07380000 0.07420000 0.00005774 -0.00296985 0.10436896 0.09091689 -0.00212132 0.08011520 -O 1.32400000 4.17600000 1.14400000 -0.15650000 0.07920000 -0.16410000 0.07920000 0.09830000 0.00830000 -0.16410000 0.00830000 0.05820000 -0.00000000 0.11200571 0.01173797 0.07128015 -0.23207245 -0.18017081 -O 4.27800000 0.94200000 13.33100000 -0.14560000 -0.08420000 0.16820000 -0.08420000 0.10680000 0.01640000 0.16820000 0.01640000 0.03880000 -0.00000000 -0.11907678 0.02319310 0.04752010 0.23787072 -0.17847375 -O 1.63100000 0.94200000 13.33100000 -0.14560000 0.08130000 -0.16830000 0.08130000 0.10450000 0.01900000 -0.16830000 0.01900000 0.04110000 0.00000000 0.11497556 0.02687006 0.05033701 -0.23801214 -0.17684741 -O 2.95500000 3.23400000 13.33100000 0.03050000 -0.00190000 -0.00130000 -0.00190000 -0.09940000 0.06380000 -0.00130000 0.06380000 0.06890000 -0.00000000 -0.00268701 0.09022683 0.08438492 -0.00183848 0.09185317 -O 4.58600000 0.76400000 5.96800000 -0.10920000 0.10280000 -0.07390000 0.10280000 0.00750000 0.04830000 -0.07390000 0.04830000 0.10160000 0.00005774 0.14538115 0.06830652 0.12447490 -0.10451038 -0.08251936 -O 2.95500000 3.58900000 5.96800000 0.11280000 0.07430000 -0.06480000 0.07430000 -0.18230000 -0.14960000 -0.06480000 -0.14960000 0.06950000 -0.00000000 0.10507607 -0.21156635 0.08511977 -0.09164104 0.20866721 -O 1.32400000 0.76400000 5.96800000 -0.17120000 -0.09180000 0.09580000 -0.09180000 0.10430000 0.13530000 0.09580000 0.13530000 0.06690000 -0.00000000 -0.12982481 0.19134309 0.08193543 0.13548166 -0.19480792 -O 1.32400000 2.64800000 3.68100000 -0.02220000 0.04730000 0.00150000 0.04730000 0.04800000 0.02610000 0.00150000 0.02610000 -0.02590000 0.00005774 0.06689230 0.03691097 -0.03168007 0.00212132 -0.04963890 -O -1.32400000 2.64800000 3.68100000 -0.02980000 -0.05570000 0.01170000 -0.05570000 0.05630000 0.04570000 0.01170000 0.04570000 -0.02640000 -0.00005774 -0.07877170 0.06462956 -0.03237409 0.01654630 -0.06088189 -O 0.00000000 4.94000000 3.68100000 0.07900000 0.00320000 -0.01110000 0.00320000 -0.04550000 -0.02420000 -0.01110000 -0.02420000 -0.03350000 0.00000000 0.00452548 -0.03422397 -0.04102895 -0.01569777 0.08803479 -O 1.63100000 2.47000000 10.79300000 -0.02710000 0.05180000 -0.00850000 0.05180000 0.04580000 0.03660000 -0.00850000 0.03660000 -0.01880000 0.00005774 0.07325626 0.05176022 -0.02298438 -0.01202082 -0.05154808 -O 2.95500000 0.17700000 10.79300000 0.07620000 0.00340000 -0.00990000 0.00340000 -0.05070000 -0.03480000 -0.00990000 -0.03480000 -0.02550000 0.00000000 0.00480833 -0.04921463 -0.03123099 -0.01400071 0.08973185 -O 4.27800000 2.47000000 10.79300000 -0.03370000 -0.05810000 0.02150000 -0.05810000 0.05350000 0.05470000 0.02150000 0.05470000 -0.01980000 0.00000000 -0.08216581 0.07735748 -0.02424995 0.03040559 -0.06165971 -O -1.63100000 4.35300000 8.50600000 -0.14250000 0.13320000 -0.08880000 0.13320000 0.00940000 0.05690000 -0.08880000 0.05690000 0.13310000 -0.00000000 0.18837325 0.08046875 0.16301354 -0.12558216 -0.10740952 -O 1.63100000 4.35300000 8.50600000 -0.20270000 -0.16030000 0.08500000 -0.16030000 0.09240000 0.12250000 0.08500000 0.12250000 0.11040000 -0.00005774 -0.22669843 0.17324116 0.13517101 0.12020815 -0.20866721 -O 0.00000000 1.52800000 8.50600000 0.15580000 0.04850000 -0.05920000 0.04850000 -0.26840000 -0.13370000 -0.05920000 -0.13370000 0.11260000 0.00000000 0.06858936 -0.18908035 0.13790627 -0.08372144 0.29995470 -Ti 2.95500000 1.70600000 12.06200000 0.08150000 -0.00010000 -0.00090000 -0.00010000 0.16970000 0.08700000 -0.00090000 0.08700000 -0.25130000 0.00005774 -0.00014142 0.12303658 -0.30773756 -0.00127279 -0.06236682 -Ti 0.00000000 3.41200000 2.41200000 0.07560000 -0.00050000 -0.00110000 -0.00050000 0.17060000 0.08300000 -0.00110000 0.08300000 -0.24610000 -0.00005774 -0.00070711 0.11737973 -0.30145054 -0.00155563 -0.06717514 -Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24910000 0.04860000 -0.24910000 -0.03780000 -0.07610000 0.04860000 -0.07610000 0.11880000 0.00000000 -0.35228060 -0.10762165 0.14549969 0.06873078 -0.03054701 -Ti 1.47700000 2.55900000 0.00000000 -0.08120000 0.24900000 -0.04870000 0.24900000 -0.03770000 -0.07580000 -0.04870000 -0.07580000 0.11890000 -0.00000000 0.35213918 -0.10719739 0.14562217 -0.06887220 -0.03075914 -Ti 0.00000000 1.70600000 4.82500000 0.08850000 -0.01510000 0.02290000 -0.01510000 -0.12990000 0.20370000 0.02290000 0.20370000 0.04140000 -0.00000000 -0.02135462 0.28807530 0.05070444 0.03238549 0.15443212 -Ti 1.47700000 4.26500000 4.82500000 -0.06510000 -0.10240000 -0.16330000 -0.10240000 0.02250000 -0.11830000 -0.16330000 -0.11830000 0.04260000 0.00000000 -0.14481547 -0.16730146 0.05217413 -0.23094107 -0.06194255 -Ti -1.47700000 4.26500000 4.82500000 -0.07760000 0.09490000 0.15980000 0.09490000 0.03600000 -0.09020000 0.15980000 -0.09020000 0.04170000 -0.00005774 0.13420887 -0.12756206 0.05103104 0.22599133 -0.08032733 -Ti 2.95500000 3.41200000 9.65000000 0.06870000 -0.01340000 0.02320000 -0.01340000 -0.12820000 0.24740000 0.02320000 0.24740000 0.05960000 -0.00005774 -0.01895046 0.34987644 0.07295397 0.03280975 0.13922933 -Ti 1.47700000 0.85300000 9.65000000 -0.07010000 -0.09140000 -0.20050000 -0.09140000 0.00910000 -0.13970000 -0.20050000 -0.13970000 0.06100000 -0.00000000 -0.12925912 -0.19756563 0.07470944 -0.28354982 -0.05600286 -Ti 4.43200000 0.85300000 9.65000000 -0.07910000 0.08120000 0.20420000 0.08120000 0.01890000 -0.11540000 0.20420000 -0.11540000 0.06020000 -0.00000000 0.11483414 -0.16320025 0.07372964 0.28878241 -0.06929646 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00220000 0.00000000 0.00010000 0.00000000 0.00370000 -0.00140000 0.00010000 -0.00140000 -0.00150000 0.00000000 0.00000000 -0.00197990 -0.00183712 0.00014142 -0.00417193 -Li 0.00000000 0.00000000 12.66500000 -0.00100000 0.00000000 0.00010000 0.00000000 0.00510000 -0.00160000 0.00010000 -0.00160000 -0.00410000 0.00000000 0.00000000 -0.00226274 -0.00502145 0.00014142 -0.00431335 -Li 2.95500000 1.70600000 6.63400000 0.02180000 0.02130000 0.01540000 0.02130000 -0.00270000 0.00890000 0.01540000 0.00890000 -0.01910000 0.00000000 0.03012275 0.01258650 -0.02339263 0.02177889 0.01732412 -Li 2.95500000 1.70600000 3.01600000 0.01200000 -0.00010000 -0.00000000 -0.00010000 0.01050000 0.00040000 -0.00000000 0.00040000 -0.02250000 0.00000000 -0.00014142 0.00056569 -0.02755676 0.00000000 0.00106066 -Li 0.00000000 3.41200000 11.45900000 0.01130000 -0.00010000 -0.00000000 -0.00010000 0.00990000 0.00030000 -0.00000000 0.00030000 -0.02120000 0.00000000 -0.00014142 0.00042426 -0.02596459 0.00000000 0.00098995 -Li -1.47700000 2.55900000 7.23700000 0.01910000 0.02290000 0.00490000 0.02290000 -0.00730000 0.00280000 0.00490000 0.00280000 -0.01190000 0.00005774 0.03238549 0.00395980 -0.01453364 0.00692965 0.01866762 -Li 0.00000000 0.00000000 7.23700000 0.00560000 -0.00340000 0.00100000 -0.00340000 0.00970000 0.00060000 0.00100000 0.00060000 -0.01520000 -0.00005774 -0.00480833 0.00084853 -0.01865695 0.00141421 -0.00289914 -Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00010000 0.00000000 -0.00010000 0.00480000 -0.00180000 0.00000000 -0.00180000 -0.01070000 0.00005774 -0.00014142 -0.00254558 -0.01306395 0.00000000 0.00070711 -O 0.00000000 0.00000000 3.80700000 0.07620000 0.00170000 -0.02040000 0.00170000 0.07480000 -0.01690000 -0.02040000 -0.01690000 -0.15100000 0.00000000 0.00240416 -0.02390021 -0.18493648 -0.02884996 0.00098995 -O 0.00000000 0.00000000 10.66800000 0.08850000 0.00280000 -0.02010000 0.00280000 0.08610000 -0.01770000 -0.02010000 -0.01770000 -0.17470000 0.00005774 0.00395980 -0.02503158 -0.21392210 -0.02842569 0.00169706 -O 2.95500000 1.70600000 8.63200000 0.03910000 0.05970000 -0.09210000 0.05970000 -0.03180000 -0.05410000 -0.09210000 -0.05410000 -0.00730000 0.00000000 0.08442855 -0.07650895 -0.00894064 -0.13024907 0.05013387 -O 2.95500000 1.70600000 1.01800000 0.10390000 -0.00080000 -0.00030000 -0.00080000 -0.05930000 -0.17410000 -0.00030000 -0.17410000 -0.04470000 0.00005774 -0.00113137 -0.24621458 -0.05470527 -0.00042426 0.11539983 -O 0.00000000 3.41200000 13.45700000 0.08930000 -0.00080000 -0.00030000 -0.00080000 -0.05200000 -0.17110000 -0.00030000 -0.17110000 -0.03730000 0.00000000 -0.00113137 -0.24197194 -0.04568298 -0.00042426 0.09991419 -O 0.00000000 3.41200000 5.84300000 0.07920000 0.05510000 -0.05980000 0.05510000 0.01330000 -0.03590000 -0.05980000 -0.03590000 -0.09250000 0.00000000 0.07792317 -0.05077027 -0.11328890 -0.08456997 0.04659834 -O -1.32400000 4.17600000 1.14400000 -0.15690000 -0.08220000 0.16420000 -0.08220000 0.10050000 0.00810000 0.16420000 0.00810000 0.05640000 0.00000000 -0.11624835 0.01145513 0.06907561 0.23221387 -0.18200929 -O 0.00000000 1.88300000 1.14400000 0.01950000 -0.00190000 0.00130000 -0.00190000 -0.09380000 0.07380000 0.00130000 0.07380000 0.07420000 0.00005774 -0.00268701 0.10436896 0.09091689 0.00183848 0.08011520 -O 1.32400000 4.17600000 1.14400000 -0.15610000 0.07920000 -0.16420000 0.07920000 0.09880000 0.00550000 -0.16420000 0.00550000 0.05730000 -0.00000000 0.11200571 0.00777817 0.07017788 -0.23221387 -0.18024152 -O 4.27800000 0.94200000 13.33100000 -0.14600000 -0.08430000 0.16840000 -0.08430000 0.10670000 0.01880000 0.16840000 0.01880000 0.03930000 -0.00000000 -0.11921820 0.02658721 0.04813247 0.23815356 -0.17868588 -O 1.63100000 0.94200000 13.33100000 -0.14520000 0.08110000 -0.16810000 0.08110000 0.10460000 0.01670000 -0.16810000 0.01670000 0.04060000 0.00000000 0.11469272 0.02361737 0.04972464 -0.23772930 -0.17663527 -O 2.95500000 3.23400000 13.33100000 0.03050000 -0.00200000 0.00120000 -0.00200000 -0.09940000 0.06380000 0.00120000 0.06380000 0.06890000 -0.00000000 -0.00282843 0.09022683 0.08438492 0.00169706 0.09185317 -O 4.58600000 0.76400000 5.96800000 -0.17170000 0.08870000 -0.09590000 0.08870000 0.10600000 0.13540000 -0.09590000 0.13540000 0.06570000 0.00000000 0.12544074 0.19148452 0.08046574 -0.13562308 -0.19636355 -O 2.95500000 3.58900000 5.96800000 0.11290000 -0.07910000 0.06510000 -0.07910000 -0.18220000 -0.14970000 0.06510000 -0.14970000 0.06930000 -0.00000000 -0.11186429 -0.21170777 0.08487482 0.09206530 0.20866721 -O 1.32400000 0.76400000 5.96800000 -0.10870000 -0.10600000 0.07390000 -0.10600000 0.00570000 0.04820000 0.07390000 0.04820000 0.10300000 0.00000000 -0.14990664 0.06816509 0.12614872 0.10451038 -0.08089302 -O 1.32400000 2.64800000 3.68100000 -0.03020000 0.05260000 -0.01170000 0.05260000 0.05780000 0.04570000 -0.01170000 0.04570000 -0.02760000 0.00000000 0.07438763 0.06462956 -0.03380296 -0.01654630 -0.06222540 -O -1.32400000 2.64800000 3.68100000 -0.02180000 -0.05040000 -0.00140000 -0.05040000 0.04640000 0.02610000 -0.00140000 0.02610000 -0.02460000 0.00000000 -0.07127636 0.03691097 -0.03012872 -0.00197990 -0.04822468 -O 0.00000000 4.94000000 3.68100000 0.07900000 -0.00800000 0.01120000 -0.00800000 -0.04550000 -0.02420000 0.01120000 -0.02420000 -0.03350000 0.00000000 -0.01131371 -0.03422397 -0.04102895 0.01583919 0.08803479 -O 1.63100000 2.47000000 10.79300000 -0.03400000 0.05510000 -0.02150000 0.05510000 0.05500000 0.05470000 -0.02150000 0.05470000 -0.02100000 0.00000000 0.07792317 0.07735748 -0.02571964 -0.03040559 -0.06293250 -O 2.95500000 0.17700000 10.79300000 0.07620000 -0.00820000 0.01000000 -0.00820000 -0.05070000 -0.03480000 0.01000000 -0.03480000 -0.02550000 0.00000000 -0.01159655 -0.04921463 -0.03123099 0.01414214 0.08973185 -O 4.27800000 2.47000000 10.79300000 -0.02670000 -0.05490000 0.00860000 -0.05490000 0.04430000 0.03660000 0.00860000 0.03660000 -0.01750000 -0.00005774 -0.07764032 0.05176022 -0.02147386 0.01216224 -0.05020458 -O -1.63100000 4.35300000 8.50600000 -0.20330000 0.15720000 -0.08510000 0.15720000 0.09410000 0.12260000 -0.08510000 0.12260000 0.10920000 -0.00000000 0.22231437 0.17338258 0.13374214 -0.12034957 -0.21029356 -O 1.63100000 4.35300000 8.50600000 -0.14190000 -0.13630000 0.08880000 -0.13630000 0.00760000 0.05680000 0.08880000 0.05680000 0.13430000 -0.00000000 -0.19275731 0.08032733 0.16448324 0.12558216 -0.10571246 -O 0.00000000 1.52800000 8.50600000 0.15590000 -0.05330000 0.05950000 -0.05330000 -0.26820000 -0.13380000 0.05950000 -0.13380000 0.11230000 -0.00000000 -0.07537758 -0.18922177 0.13753885 0.08414571 0.29988399 -Ti 2.95500000 1.70600000 12.06200000 0.08150000 -0.00000000 0.00100000 -0.00000000 0.16970000 0.08700000 0.00100000 0.08700000 -0.25130000 0.00005774 0.00000000 0.12303658 -0.30773756 0.00141421 -0.06236682 -Ti 0.00000000 3.41200000 2.41200000 0.07560000 0.00040000 0.00120000 0.00040000 0.17060000 0.08300000 0.00120000 0.08300000 -0.24610000 -0.00005774 0.00056569 0.11737973 -0.30145054 0.00169706 -0.06717514 -Ti -1.47700000 2.55900000 0.00000000 -0.08120000 -0.24910000 0.04890000 -0.24910000 -0.03760000 -0.07580000 0.04890000 -0.07580000 0.11880000 -0.00000000 -0.35228060 -0.10719739 0.14549969 0.06915504 -0.03082986 -Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24900000 -0.04850000 0.24900000 -0.03790000 -0.07600000 -0.04850000 -0.07600000 0.11890000 -0.00000000 0.35213918 -0.10748023 0.14562217 -0.06858936 -0.03047630 -Ti 0.00000000 1.70600000 4.82500000 0.08850000 0.01500000 -0.02280000 0.01500000 -0.12990000 0.20370000 -0.02280000 0.20370000 0.04150000 -0.00005774 0.02121320 0.28807530 0.05078609 -0.03224407 0.15443212 -Ti 1.47700000 4.26500000 4.82500000 -0.07760000 -0.09500000 -0.15970000 -0.09500000 0.03590000 -0.09020000 -0.15970000 -0.09020000 0.04170000 -0.00000000 -0.13435029 -0.12756206 0.05107186 -0.22584991 -0.08025662 -Ti -1.47700000 4.26500000 4.82500000 -0.06510000 0.10240000 0.16340000 0.10240000 0.02250000 -0.11830000 0.16340000 -0.11830000 0.04250000 0.00005774 0.14481547 -0.16730146 0.05209248 0.23108250 -0.06194255 -Ti 2.95500000 3.41200000 9.65000000 0.06870000 0.01320000 -0.02310000 0.01320000 -0.12830000 0.24740000 -0.02310000 0.24740000 0.05960000 0.00000000 0.01866762 0.34987644 0.07299479 -0.03266833 0.13930004 -Ti 1.47700000 0.85300000 9.65000000 -0.07910000 -0.08130000 -0.20410000 -0.08130000 0.01890000 -0.11540000 -0.20410000 -0.11540000 0.06020000 -0.00000000 -0.11497556 -0.16320025 0.07372964 -0.28864099 -0.06929646 -Ti 4.43200000 0.85300000 9.65000000 -0.07010000 0.09130000 0.20060000 0.09130000 0.00920000 -0.13970000 0.20060000 -0.13970000 0.06100000 -0.00005774 0.12911770 -0.19756563 0.07466861 0.28369124 -0.05607357 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 -0.00180000 0.00000000 -0.00000000 0.00000000 0.00510000 -0.00160000 -0.00000000 -0.00160000 -0.00340000 0.00005774 0.00000000 -0.00226274 -0.00412331 0.00000000 -0.00487904 -Li 0.00000000 0.00000000 12.66500000 -0.00180000 0.00000000 -0.00000000 0.00000000 0.00510000 -0.00160000 -0.00000000 -0.00160000 -0.00340000 0.00005774 0.00000000 -0.00226274 -0.00412331 0.00000000 -0.00487904 -Li 2.95500000 1.70600000 6.63400000 0.00270000 0.00000000 -0.00000000 0.00000000 0.00360000 0.00000000 -0.00000000 0.00000000 -0.00630000 0.00000000 0.00000000 0.00000000 -0.00771589 0.00000000 -0.00063640 -Li 2.95500000 1.70600000 3.01600000 0.01140000 0.00000000 -0.00000000 0.00000000 0.01070000 0.00040000 -0.00000000 0.00040000 -0.02220000 0.00005774 0.00000000 0.00056569 -0.02714851 0.00000000 0.00049497 -Li 0.00000000 3.41200000 11.45900000 0.01140000 -0.00000000 0.00000000 -0.00000000 0.01070000 0.00040000 0.00000000 0.00040000 -0.02220000 0.00005774 0.00000000 0.00056569 -0.02714851 0.00000000 0.00049497 -Li 0.00000000 3.41200000 7.84000000 0.00270000 -0.00000000 0.00000000 -0.00000000 0.00360000 0.00000000 0.00000000 0.00000000 -0.00630000 0.00000000 0.00000000 0.00000000 -0.00771589 0.00000000 -0.00063640 -Li 0.00000000 0.00000000 7.23700000 0.00670000 -0.00000000 0.00000000 -0.00000000 0.00760000 0.00000000 0.00000000 0.00000000 -0.01430000 0.00000000 0.00000000 0.00000000 -0.01751385 0.00000000 -0.00063640 -Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00000000 0.00000000 -0.00000000 0.00560000 -0.00180000 0.00000000 -0.00180000 -0.01140000 0.00000000 0.00000000 -0.00254558 -0.01396209 0.00000000 0.00014142 -O 0.00000000 0.00000000 3.80700000 0.07490000 0.00000000 -0.00000000 0.00000000 0.08800000 -0.00560000 -0.00000000 -0.00560000 -0.16290000 0.00000000 0.00000000 -0.00791960 -0.19951094 0.00000000 -0.00926310 -O 0.00000000 0.00000000 10.66800000 0.07490000 -0.00000000 -0.00000000 -0.00000000 0.08800000 -0.00560000 -0.00000000 -0.00560000 -0.16290000 0.00000000 0.00000000 -0.00791960 -0.19951094 0.00000000 -0.00926310 -O 2.95500000 1.70600000 8.63200000 0.02430000 0.00000000 -0.00000000 0.00000000 0.03460000 -0.00100000 -0.00000000 -0.00100000 -0.05890000 0.00000000 0.00000000 -0.00141421 -0.07213747 0.00000000 -0.00728320 -O 2.95500000 1.70600000 1.01800000 0.09540000 -0.00000000 0.00000000 -0.00000000 -0.04850000 -0.17550000 0.00000000 -0.17550000 -0.04690000 0.00000000 0.00000000 -0.24819448 -0.05744053 0.00000000 0.10175267 -O 0.00000000 3.41200000 13.45700000 0.09540000 0.00000000 0.00000000 0.00000000 -0.04850000 -0.17550000 0.00000000 -0.17550000 -0.04690000 0.00000000 0.00000000 -0.24819448 -0.05744053 0.00000000 0.10175267 -O 0.00000000 3.41200000 5.84300000 0.02430000 -0.00000000 -0.00000000 -0.00000000 0.03460000 -0.00100000 -0.00000000 -0.00100000 -0.05890000 0.00000000 0.00000000 -0.00141421 -0.07213747 0.00000000 -0.00728320 -O -1.32400000 4.17600000 1.14400000 -0.15480000 -0.08500000 0.16800000 -0.08500000 0.11370000 0.01190000 0.16800000 0.01190000 0.04110000 0.00000000 -0.12020815 0.01682914 0.05033701 0.23758788 -0.18985817 -O 0.00000000 1.88300000 1.14400000 0.02220000 -0.00000000 0.00000000 -0.00000000 -0.08430000 0.06970000 0.00000000 0.06970000 0.06200000 0.00005774 0.00000000 0.09857069 0.07597501 0.00000000 0.07530687 -O 1.32400000 4.17600000 1.14400000 -0.15480000 0.08500000 -0.16800000 0.08500000 0.11370000 0.01190000 -0.16800000 0.01190000 0.04110000 0.00000000 0.12020815 0.01682914 0.05033701 -0.23758788 -0.18985817 -O 4.27800000 0.94200000 13.33100000 -0.15480000 -0.08500000 0.16800000 -0.08500000 0.11370000 0.01190000 0.16800000 0.01190000 0.04110000 0.00000000 -0.12020815 0.01682914 0.05033701 0.23758788 -0.18985817 -O 1.63100000 0.94200000 13.33100000 -0.15480000 0.08500000 -0.16800000 0.08500000 0.11370000 0.01190000 -0.16800000 0.01190000 0.04110000 0.00000000 0.12020815 0.01682914 0.05033701 -0.23758788 -0.18985817 -O 2.95500000 3.23400000 13.33100000 0.02220000 -0.00000000 -0.00000000 -0.00000000 -0.08430000 0.06970000 -0.00000000 0.06970000 0.06200000 0.00005774 0.00000000 0.09857069 0.07597501 0.00000000 0.07530687 -O 4.58600000 0.76400000 5.96800000 -0.12660000 0.12620000 -0.08690000 0.12620000 0.02960000 0.05540000 -0.08690000 0.05540000 0.09690000 0.00005774 0.17847375 0.07834743 0.11871860 -0.12289516 -0.11045008 -O 2.95500000 3.58900000 5.96800000 0.09040000 0.00000000 0.00000000 0.00000000 -0.18860000 -0.10150000 0.00000000 -0.10150000 0.09830000 -0.00005774 0.00000000 -0.14354268 0.12035160 0.00000000 0.19728279 -O 1.32400000 0.76400000 5.96800000 -0.12660000 -0.12620000 0.08690000 -0.12620000 0.02960000 0.05540000 0.08690000 0.05540000 0.09690000 0.00005774 -0.17847375 0.07834743 0.11871860 0.12289516 -0.11045008 -O 1.32400000 2.64800000 3.68100000 -0.02330000 0.04890000 -0.01270000 0.04890000 0.05840000 0.03630000 -0.01270000 0.03630000 -0.03510000 0.00000000 0.06915504 0.05133595 -0.04298854 -0.01796051 -0.05777062 -O -1.32400000 2.64800000 3.68100000 -0.02330000 -0.04890000 0.01270000 -0.04890000 0.05840000 0.03630000 0.01270000 0.03630000 -0.03510000 0.00000000 -0.06915504 0.05133595 -0.04298854 0.01796051 -0.05777062 -O 0.00000000 4.94000000 3.68100000 0.06750000 0.00000000 0.00000000 0.00000000 -0.02570000 -0.01930000 0.00000000 -0.01930000 -0.04180000 0.00000000 0.00000000 -0.02729432 -0.05119434 0.00000000 0.06590235 -O 1.63100000 2.47000000 10.79300000 -0.02330000 0.04890000 -0.01270000 0.04890000 0.05840000 0.03630000 -0.01270000 0.03630000 -0.03510000 0.00000000 0.06915504 0.05133595 -0.04298854 -0.01796051 -0.05777062 -O 2.95500000 0.17700000 10.79300000 0.06750000 0.00000000 0.00000000 0.00000000 -0.02570000 -0.01930000 0.00000000 -0.01930000 -0.04180000 0.00000000 0.00000000 -0.02729432 -0.05119434 0.00000000 0.06590235 -O 4.27800000 2.47000000 10.79300000 -0.02330000 -0.04890000 0.01270000 -0.04890000 0.05840000 0.03630000 0.01270000 0.03630000 -0.03510000 0.00000000 -0.06915504 0.05133595 -0.04298854 0.01796051 -0.05777062 -O -1.63100000 4.35300000 8.50600000 -0.12660000 0.12620000 -0.08690000 0.12620000 0.02960000 0.05540000 -0.08690000 0.05540000 0.09690000 0.00005774 0.17847375 0.07834743 0.11871860 -0.12289516 -0.11045008 -O 1.63100000 4.35300000 8.50600000 -0.12660000 -0.12620000 0.08690000 -0.12620000 0.02960000 0.05540000 0.08690000 0.05540000 0.09690000 0.00005774 -0.17847375 0.07834743 0.11871860 0.12289516 -0.11045008 -O 0.00000000 1.52800000 8.50600000 0.09040000 0.00000000 0.00000000 0.00000000 -0.18860000 -0.10150000 0.00000000 -0.10150000 0.09830000 -0.00005774 0.00000000 -0.14354268 0.12035160 0.00000000 0.19728279 -Ti 2.95500000 1.70600000 12.06200000 0.07770000 0.00000000 0.00000000 0.00000000 0.17090000 0.08470000 0.00000000 0.08470000 -0.24860000 0.00000000 0.00000000 0.11978389 -0.30447158 0.00000000 -0.06590235 -Ti 0.00000000 3.41200000 2.41200000 0.07770000 0.00000000 0.00000000 0.00000000 0.17090000 0.08470000 0.00000000 0.08470000 -0.24860000 0.00000000 0.00000000 0.11978389 -0.30447158 0.00000000 -0.06590235 -Ti -1.47700000 2.55900000 0.00000000 -0.08100000 -0.24960000 0.04950000 -0.24960000 -0.03770000 -0.07610000 0.04950000 -0.07610000 0.11870000 -0.00000000 -0.35298771 -0.10762165 0.14537722 0.07000357 -0.03061772 -Ti 1.47700000 2.55900000 0.00000000 -0.08100000 0.24960000 -0.04950000 0.24960000 -0.03770000 -0.07610000 -0.04950000 -0.07610000 0.11870000 -0.00000000 0.35298771 -0.10762165 0.14537722 -0.07000357 -0.03061772 -Ti 0.00000000 1.70600000 4.82500000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.12500000 0.20870000 -0.00000000 0.20870000 0.04450000 -0.00000000 0.00000000 0.29514637 0.05450115 0.00000000 0.14531044 -Ti 1.47700000 4.26500000 4.82500000 -0.07630000 -0.08890000 -0.17880000 -0.08890000 0.03070000 -0.10080000 -0.17880000 -0.10080000 0.04570000 -0.00005774 -0.12572359 -0.14255273 0.05593002 -0.25286138 -0.07566043 -Ti -1.47700000 4.26500000 4.82500000 -0.07630000 0.08890000 0.17880000 0.08890000 0.03070000 -0.10080000 0.17880000 -0.10080000 0.04570000 -0.00005774 0.12572359 -0.14255273 0.05593002 0.25286138 -0.07566043 -Ti 2.95500000 3.41200000 9.65000000 0.08050000 0.00000000 -0.00000000 0.00000000 -0.12500000 0.20870000 -0.00000000 0.20870000 0.04450000 -0.00000000 0.00000000 0.29514637 0.05450115 0.00000000 0.14531044 -Ti 1.47700000 0.85300000 9.65000000 -0.07630000 -0.08890000 -0.17880000 -0.08890000 0.03070000 -0.10080000 -0.17880000 -0.10080000 0.04570000 -0.00005774 -0.12572359 -0.14255273 0.05593002 -0.25286138 -0.07566043 -Ti 4.43200000 0.85300000 9.65000000 -0.07630000 0.08890000 0.17880000 0.08890000 0.03070000 -0.10080000 0.17880000 -0.10080000 0.04570000 -0.00005774 0.12572359 -0.14255273 0.05593002 0.25286138 -0.07566043 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00310000 0.00000000 -0.00000000 0.00000000 0.00790000 -0.00170000 -0.00000000 -0.00170000 -0.01100000 0.00000000 0.00000000 -0.00240416 -0.01347219 0.00000000 -0.00339411 -Li 0.00000000 0.00000000 12.66500000 0.01400000 0.02150000 0.01500000 0.02150000 -0.00340000 0.00730000 0.01500000 0.00730000 -0.01050000 -0.00005774 0.03040559 0.01032376 -0.01290065 0.02121320 0.01230366 -Li 2.95500000 1.70600000 6.63400000 -0.00270000 0.00000000 0.00000000 0.00000000 0.00290000 -0.00120000 0.00000000 -0.00120000 -0.00020000 0.00000000 0.00000000 -0.00169706 -0.00024495 0.00000000 -0.00395980 -Li 2.95500000 1.70600000 3.01600000 0.00310000 -0.00000000 0.00000000 -0.00000000 0.00790000 -0.00140000 0.00000000 -0.00140000 -0.01100000 0.00000000 0.00000000 -0.00197990 -0.01347219 0.00000000 -0.00339411 -Li 1.47700000 4.26500000 12.06200000 0.01790000 0.01380000 0.01480000 0.01380000 -0.00140000 0.00350000 0.01480000 0.00350000 -0.01660000 0.00005774 0.01951615 0.00494975 -0.02028994 0.02093036 0.01364716 -Li 0.00000000 3.41200000 7.84000000 0.01280000 -0.00000000 -0.00000000 -0.00000000 0.01130000 0.00020000 -0.00000000 0.00020000 -0.02410000 0.00000000 0.00000000 0.00028284 -0.02951635 0.00000000 0.00106066 -Li 2.95500000 0.00000000 0.00000000 0.00580000 -0.00010000 -0.00170000 -0.00010000 0.00440000 -0.00110000 -0.00170000 -0.00110000 -0.01020000 0.00000000 -0.00014142 -0.00155563 -0.01249240 -0.00240416 0.00098995 -Li 0.00000000 1.70600000 4.82500000 0.00530000 -0.00000000 -0.00000000 -0.00000000 0.00550000 -0.00120000 -0.00000000 -0.00120000 -0.01080000 0.00000000 0.00000000 -0.00169706 -0.01322724 0.00000000 -0.00014142 -O 0.00000000 0.00000000 3.80700000 0.01290000 -0.00100000 0.00010000 -0.00100000 0.08350000 -0.09190000 0.00010000 -0.09190000 -0.09640000 0.00000000 -0.00141421 -0.12996623 -0.11806541 0.00014142 -0.04992174 -O 0.00000000 0.00000000 10.66800000 0.05410000 0.05790000 -0.08260000 0.05790000 -0.00820000 -0.05550000 -0.08260000 -0.05550000 -0.04590000 0.00000000 0.08188297 -0.07848885 -0.05621579 -0.11681404 0.04405275 -O 2.95500000 1.70600000 8.63200000 0.04640000 0.00240000 -0.01470000 0.00240000 0.04570000 -0.01390000 -0.01470000 -0.01390000 -0.09210000 0.00000000 0.00339411 -0.01965757 -0.11279900 -0.02078894 0.00049497 -O 2.95500000 1.70600000 1.01800000 0.02730000 0.00170000 -0.00940000 0.00170000 0.04350000 -0.13940000 -0.00940000 -0.13940000 -0.07080000 0.00000000 0.00240416 -0.19714137 -0.08671194 -0.01329361 -0.01145513 -O 0.00000000 3.41200000 13.45700000 0.12550000 0.05150000 -0.06490000 0.05150000 -0.08690000 -0.11060000 -0.06490000 -0.11060000 -0.03860000 0.00000000 0.07283200 -0.15641202 -0.04727515 -0.09178246 0.15018948 -O 0.00000000 3.41200000 5.84300000 0.10130000 -0.00060000 0.00010000 -0.00060000 -0.06370000 -0.10070000 0.00010000 -0.10070000 -0.03760000 0.00000000 -0.00084853 -0.14241131 -0.04605041 0.00014142 0.11667262 -O -1.32400000 4.17600000 1.14400000 -0.11050000 -0.02080000 0.11100000 -0.02080000 0.06620000 -0.00910000 0.11100000 -0.00910000 0.04430000 -0.00000000 -0.02941564 -0.01286934 0.05425620 0.15697771 -0.12494577 -O 0.00000000 1.88300000 1.14400000 0.05090000 -0.00780000 0.01220000 -0.00780000 -0.09630000 0.05210000 0.01220000 0.05210000 0.04550000 -0.00005774 -0.01103087 0.07368053 0.05568507 0.01725341 0.10408612 -O 1.32400000 4.17600000 1.14400000 -0.10340000 0.00740000 -0.09530000 0.00740000 0.04600000 -0.03080000 -0.09530000 -0.03080000 0.05740000 0.00000000 0.01046518 -0.04355778 0.07030036 -0.13477455 -0.10564175 -O 4.27800000 0.94200000 13.33100000 -0.20790000 -0.13370000 0.14990000 -0.13370000 0.12960000 0.12800000 0.14990000 0.12800000 0.07840000 -0.00005774 -0.18908035 0.18101934 0.09597917 0.21199061 -0.23864854 -O 1.63100000 0.94200000 13.33100000 -0.13540000 0.07670000 -0.14220000 0.07670000 0.09700000 -0.00990000 -0.14220000 -0.00990000 0.03840000 -0.00000000 0.10847018 -0.01400071 0.04703020 -0.20110117 -0.16433162 -O 2.95500000 3.23400000 13.33100000 0.00530000 -0.08560000 0.09800000 -0.08560000 -0.08320000 0.00740000 0.09800000 0.00740000 0.07790000 -0.00000000 -0.12105668 0.01046518 0.09540763 0.13859293 0.06257895 -O 4.58600000 0.76400000 5.96800000 -0.14380000 -0.06370000 0.13620000 -0.06370000 0.08500000 -0.00250000 0.13620000 -0.00250000 0.05880000 -0.00000000 -0.09008540 -0.00353553 0.07201500 0.19261589 -0.16178603 -O 2.95500000 3.58900000 5.96800000 0.01110000 -0.00170000 0.00090000 -0.00170000 -0.08660000 0.07450000 0.00090000 0.07450000 0.07550000 -0.00000000 -0.00240416 0.10535891 0.09246824 0.00127279 0.06908433 -O 1.32400000 0.76400000 5.96800000 -0.14320000 0.06230000 -0.13680000 0.06230000 0.08180000 -0.00450000 -0.13680000 -0.00450000 0.06140000 -0.00000000 0.08810550 -0.00636396 0.07519934 -0.19346442 -0.15909903 -O 1.32400000 2.64800000 3.68100000 -0.06350000 0.02090000 0.06970000 0.02090000 0.04850000 -0.03540000 0.06970000 -0.03540000 0.01500000 -0.00000000 0.02955706 -0.05006316 0.01837117 0.09857069 -0.07919596 -O -1.32400000 2.64800000 3.68100000 -0.06120000 -0.02430000 -0.06810000 -0.02430000 0.04760000 -0.03510000 -0.06810000 -0.03510000 0.01360000 -0.00000000 -0.03436539 -0.04963890 0.01665653 -0.09630794 -0.07693322 -O 0.00000000 4.94000000 3.68100000 0.04230000 -0.00240000 0.00060000 -0.00240000 -0.08570000 0.05210000 0.00060000 0.05210000 0.04350000 -0.00005774 -0.00339411 0.07368053 0.05323558 0.00084853 0.09050967 -O 1.63100000 2.47000000 10.79300000 -0.09930000 0.06520000 0.02250000 0.06520000 0.11520000 0.09030000 0.02250000 0.09030000 -0.01580000 -0.00005774 0.09220672 0.12770348 -0.01939179 0.03181981 -0.15167440 -O 2.95500000 0.17700000 10.79300000 0.11220000 -0.05640000 0.06450000 -0.05640000 -0.09180000 -0.01760000 0.06450000 -0.01760000 -0.02040000 0.00000000 -0.07976164 -0.02489016 -0.02498480 0.09121677 0.14424978 -O 4.27800000 2.47000000 10.79300000 -0.02890000 -0.05730000 -0.00150000 -0.05730000 0.04420000 0.02630000 -0.00150000 0.02630000 -0.01530000 0.00000000 -0.08103444 0.03719382 -0.01873860 -0.00212132 -0.05168951 -O -1.63100000 4.35300000 8.50600000 -0.01900000 0.04010000 -0.01280000 0.04010000 0.05020000 0.04300000 -0.01280000 0.04300000 -0.03120000 0.00000000 0.05670996 0.06081118 -0.03821204 -0.01810193 -0.04893179 -O 1.63100000 4.35300000 8.50600000 -0.01850000 -0.04050000 0.00130000 -0.04050000 0.03710000 0.02570000 0.00130000 0.02570000 -0.01860000 0.00000000 -0.05727565 0.03634529 -0.02278025 0.00183848 -0.03931514 -O 0.00000000 1.52800000 8.50600000 0.06410000 -0.00660000 0.00960000 -0.00660000 -0.02940000 -0.02540000 0.00960000 -0.02540000 -0.03470000 0.00000000 -0.00933381 -0.03592102 -0.04249865 0.01357645 0.06611448 -Ti 0.00000000 0.00000000 7.23700000 0.06780000 0.00150000 -0.00000000 0.00150000 0.15920000 0.07530000 -0.00000000 0.07530000 -0.22700000 0.00000000 0.00212132 0.10649028 -0.27801709 0.00000000 -0.06462956 -Ti 2.95500000 1.70600000 12.06200000 0.05360000 -0.03080000 0.03670000 -0.03080000 0.18270000 0.09760000 0.03670000 0.09760000 -0.23630000 0.00000000 -0.04355778 0.13802724 -0.28940721 0.05190164 -0.09128749 -Ti 0.00000000 3.41200000 2.41200000 0.08260000 0.00050000 0.00130000 0.00050000 0.18010000 0.14750000 0.00130000 0.14750000 -0.26270000 0.00000000 0.00070711 0.20859650 -0.32174048 0.00183848 -0.06894291 -Ti -1.47700000 2.55900000 0.00000000 -0.08400000 -0.25970000 0.05200000 -0.25970000 -0.02190000 -0.06980000 0.05200000 -0.06980000 0.10590000 -0.00000000 -0.36727126 -0.09871211 0.12970048 0.07353911 -0.04391133 -Ti 1.47700000 2.55900000 0.00000000 -0.07720000 0.26230000 -0.04390000 0.26230000 -0.03870000 -0.09270000 -0.04390000 -0.09270000 0.11590000 -0.00000000 0.37094822 -0.13109760 0.14194793 -0.06208398 -0.02722361 -Ti 1.47700000 4.26500000 4.82500000 -0.08010000 -0.23730000 0.02550000 -0.23730000 -0.03380000 -0.07790000 0.02550000 -0.07790000 0.11390000 -0.00000000 -0.33559288 -0.11016724 0.13949844 0.03606245 -0.03273904 -Ti -1.47700000 4.26500000 4.82500000 -0.07970000 0.23730000 -0.02520000 0.23730000 -0.03410000 -0.07830000 -0.02520000 -0.07830000 0.11370000 0.00005774 0.33559288 -0.11073292 0.13929432 -0.03563818 -0.03224407 -Ti 2.95500000 3.41200000 9.65000000 0.04960000 0.00570000 -0.01350000 0.00570000 -0.07060000 0.12930000 -0.01350000 0.12930000 0.02100000 -0.00000000 0.00806102 0.18285781 0.02571964 -0.01909188 0.08499424 -Ti 1.47700000 0.85300000 9.65000000 -0.03140000 -0.04410000 -0.09220000 -0.04410000 0.01960000 -0.04920000 -0.09220000 -0.04920000 0.01180000 -0.00000000 -0.06236682 -0.06957931 0.01445199 -0.13039049 -0.03606245 -Ti 4.43200000 0.85300000 9.65000000 -0.03570000 0.05690000 0.10500000 0.05690000 0.01760000 -0.07260000 0.10500000 -0.07260000 0.01810000 -0.00000000 0.08046875 -0.10267190 0.02216788 0.14849242 -0.03768879 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00310000 0.00000000 0.00000000 0.00000000 0.00880000 -0.00150000 0.00000000 -0.00150000 -0.01190000 0.00000000 0.00000000 -0.00212132 -0.01457446 0.00000000 -0.00403051 -Li 0.00000000 0.00000000 12.66500000 -0.00280000 -0.00000000 -0.00000000 -0.00000000 0.00370000 -0.00130000 -0.00000000 -0.00130000 -0.00090000 -0.00000000 0.00000000 -0.00183848 -0.00110227 0.00000000 -0.00459619 -Li 2.95500000 1.70600000 6.63400000 -0.02190000 0.00000000 0.00000000 0.00000000 0.03250000 -0.01870000 0.00000000 -0.01870000 -0.01060000 0.00000000 0.00000000 -0.02644579 -0.01298230 0.00000000 -0.03846661 -Li 2.95500000 1.70600000 3.01600000 0.00290000 0.00000000 -0.00000000 0.00000000 0.00860000 -0.00180000 -0.00000000 -0.00180000 -0.01140000 -0.00005774 0.00000000 -0.00254558 -0.01400292 0.00000000 -0.00403051 -Li 0.00000000 3.41200000 11.45900000 0.01280000 -0.00000000 0.00000000 -0.00000000 0.01210000 0.00030000 0.00000000 0.00030000 -0.02490000 0.00000000 0.00000000 0.00042426 -0.03049615 0.00000000 0.00049497 -Li 2.95500000 0.00000000 7.23700000 -0.01100000 0.00000000 -0.00000000 0.00000000 0.02650000 -0.01250000 -0.00000000 -0.01250000 -0.01550000 0.00000000 0.00000000 -0.01767767 -0.01898355 0.00000000 -0.02651650 -Li 2.95500000 0.00000000 0.00000000 0.00520000 0.00000000 -0.00000000 0.00000000 0.00630000 -0.00120000 -0.00000000 -0.00120000 -0.01160000 0.00005774 0.00000000 -0.00169706 -0.01416622 0.00000000 -0.00077782 -Li 0.00000000 1.70600000 4.82500000 0.00610000 0.00000000 -0.00000000 0.00000000 0.00620000 -0.00290000 -0.00000000 -0.00290000 -0.01230000 0.00000000 0.00000000 -0.00410122 -0.01506436 0.00000000 -0.00007071 -O 0.00000000 0.00000000 3.80700000 0.01950000 0.00000000 -0.00000000 0.00000000 0.07830000 -0.10570000 -0.00000000 -0.10570000 -0.09790000 0.00005774 0.00000000 -0.14948237 -0.11986170 0.00000000 -0.04157788 -O 0.00000000 0.00000000 10.66800000 0.03910000 0.00000000 0.00000000 0.00000000 0.06110000 0.01180000 0.00000000 0.01180000 -0.10020000 0.00000000 0.00000000 0.01668772 -0.12271944 0.00000000 -0.01555635 -O 2.95500000 1.70600000 8.63200000 -0.04560000 0.00000000 -0.00000000 0.00000000 0.10160000 0.08690000 -0.00000000 0.08690000 -0.05600000 0.00000000 0.00000000 0.12289516 -0.06858571 0.00000000 -0.10408612 -O 2.95500000 1.70600000 1.01800000 0.00850000 -0.00000000 -0.00000000 -0.00000000 0.09960000 -0.08750000 -0.00000000 -0.08750000 -0.10810000 0.00000000 0.00000000 -0.12374369 -0.13239492 0.00000000 -0.06441743 -O 0.00000000 3.41200000 13.45700000 0.10620000 0.00000000 -0.00000000 0.00000000 -0.05480000 -0.10700000 -0.00000000 -0.10700000 -0.05130000 -0.00005774 0.00000000 -0.15132085 -0.06287024 0.00000000 0.11384419 -O 0.00000000 3.41200000 5.84300000 0.03550000 0.00000000 0.00000000 0.00000000 0.03200000 -0.03660000 0.00000000 -0.03660000 -0.06760000 0.00005774 0.00000000 -0.05176022 -0.08275193 0.00000000 0.00247487 -O -1.32400000 4.17600000 1.14400000 -0.05450000 0.02570000 0.06670000 0.02570000 0.05970000 -0.03630000 0.06670000 -0.03630000 -0.00520000 0.00000000 0.03634529 -0.05133595 -0.00636867 0.09432804 -0.08075159 -O 0.00000000 1.88300000 1.14400000 0.03970000 0.00000000 0.00000000 0.00000000 -0.07270000 0.05250000 0.00000000 0.05250000 0.03300000 -0.00000000 0.00000000 0.07424621 0.04041658 0.00000000 0.07947880 -O 1.32400000 4.17600000 1.14400000 -0.05450000 -0.02570000 -0.06670000 -0.02570000 0.05970000 -0.03630000 -0.06670000 -0.03630000 -0.00520000 0.00000000 -0.03634529 -0.05133595 -0.00636867 -0.09432804 -0.08075159 -O 4.27800000 0.94200000 13.33100000 -0.14680000 -0.06280000 0.13300000 -0.06280000 0.09420000 -0.00650000 0.13300000 -0.00650000 0.05250000 0.00005774 -0.08881261 -0.00919239 0.06433993 0.18809040 -0.17041273 -O 1.63100000 0.94200000 13.33100000 -0.14680000 0.06280000 -0.13300000 0.06280000 0.09420000 -0.00650000 -0.13300000 -0.00650000 0.05250000 0.00005774 0.08881261 -0.00919239 0.06433993 -0.18809040 -0.17041273 -O 2.95500000 3.23400000 13.33100000 0.00750000 0.00000000 -0.00000000 0.00000000 -0.07390000 0.07530000 -0.00000000 0.07530000 0.06640000 -0.00000000 0.00000000 0.10649028 0.08132306 0.00000000 0.05755849 -O 4.58600000 0.76400000 5.96800000 -0.06860000 -0.03510000 0.02520000 -0.03510000 -0.00090000 -0.05950000 0.02520000 -0.05950000 0.06940000 0.00005774 -0.04963890 -0.08414571 0.08503812 0.03563818 -0.04787113 -O 2.95500000 3.58900000 5.96800000 0.00080000 0.00000000 0.00000000 0.00000000 -0.04680000 0.07720000 0.00000000 0.07720000 0.04600000 -0.00000000 0.00000000 0.10917729 0.05633826 0.00000000 0.03365828 -O 1.32400000 0.76400000 5.96800000 -0.06860000 0.03510000 -0.02520000 0.03510000 -0.00090000 -0.05950000 -0.02520000 -0.05950000 0.06940000 0.00005774 0.04963890 -0.08414571 0.08503812 -0.03563818 -0.04787113 -O 1.32400000 2.64800000 3.68100000 -0.08590000 -0.00070000 0.09600000 -0.00070000 0.08080000 -0.03120000 0.09600000 -0.03120000 0.00510000 0.00000000 -0.00098995 -0.04412346 0.00624620 0.13576450 -0.11787470 -O -1.32400000 2.64800000 3.68100000 -0.08590000 0.00070000 -0.09600000 0.00070000 0.08080000 -0.03120000 -0.09600000 -0.03120000 0.00510000 0.00000000 0.00098995 -0.04412346 0.00624620 -0.13576450 -0.11787470 -O 0.00000000 4.94000000 3.68100000 0.03660000 0.00000000 -0.00000000 0.00000000 -0.07600000 0.06850000 -0.00000000 0.06850000 0.03940000 -0.00000000 0.00000000 0.09687363 0.04825495 0.00000000 0.07962022 -O 1.63100000 2.47000000 10.79300000 -0.01430000 0.04470000 -0.02180000 0.04470000 0.05240000 0.02650000 -0.02180000 0.02650000 -0.03800000 -0.00005774 0.06321535 0.03747666 -0.04658113 -0.03082986 -0.04716402 -O 2.95500000 0.17700000 10.79300000 0.05130000 0.00000000 -0.00000000 0.00000000 -0.01870000 -0.00630000 -0.00000000 -0.00630000 -0.03260000 0.00000000 0.00000000 -0.00890955 -0.03992668 0.00000000 0.04949747 -O 4.27800000 2.47000000 10.79300000 -0.01430000 -0.04470000 0.02180000 -0.04470000 0.05240000 0.02650000 0.02180000 0.02650000 -0.03800000 -0.00005774 -0.06321535 0.03747666 -0.04658113 0.03082986 -0.04716402 -O -1.63100000 4.35300000 8.50600000 -0.00420000 0.11510000 -0.04090000 0.11510000 0.03080000 -0.02190000 -0.04090000 -0.02190000 -0.02670000 0.00005774 0.16277598 -0.03097128 -0.03265986 -0.05784133 -0.02474874 -O 1.63100000 4.35300000 8.50600000 -0.00420000 -0.11510000 0.04090000 -0.11510000 0.03080000 -0.02190000 0.04090000 -0.02190000 -0.02670000 0.00005774 -0.16277598 -0.03097128 -0.03265986 0.05784133 -0.02474874 -O 0.00000000 1.52800000 8.50600000 0.06580000 0.00000000 -0.00000000 0.00000000 -0.03730000 -0.00500000 -0.00000000 -0.00500000 -0.02860000 0.00005774 0.00000000 -0.00707107 -0.03498688 0.00000000 0.07290271 -Ti 0.00000000 0.00000000 7.23700000 0.10810000 -0.00000000 0.00000000 -0.00000000 0.12690000 0.03290000 0.00000000 0.03290000 -0.23510000 0.00005774 0.00000000 0.04652763 -0.28789669 0.00000000 -0.01329361 -Ti 2.95500000 1.70600000 12.06200000 0.06510000 -0.00000000 -0.00000000 -0.00000000 0.16170000 0.07520000 -0.00000000 0.07520000 -0.22680000 0.00000000 0.00000000 0.10634886 -0.27777214 0.00000000 -0.06830652 -Ti 0.00000000 3.41200000 2.41200000 0.08170000 -0.00000000 -0.00000000 -0.00000000 0.18050000 0.14900000 -0.00000000 0.14900000 -0.26210000 -0.00005774 0.00000000 0.21071782 -0.32104646 0.00000000 -0.06986215 -Ti -1.47700000 2.55900000 0.00000000 -0.07980000 -0.23690000 0.02470000 -0.23690000 -0.03380000 -0.07830000 0.02470000 -0.07830000 0.11360000 -0.00000000 -0.33502719 -0.11073292 0.13913102 0.03493107 -0.03252691 -Ti 1.47700000 2.55900000 0.00000000 -0.07980000 0.23690000 -0.02470000 0.23690000 -0.03380000 -0.07830000 -0.02470000 -0.07830000 0.11360000 -0.00000000 0.33502719 -0.11073292 0.13913102 -0.03493107 -0.03252691 -Ti 1.47700000 4.26500000 4.82500000 -0.09770000 -0.24880000 0.02780000 -0.24880000 -0.01690000 -0.05670000 0.02780000 -0.05670000 0.11460000 -0.00000000 -0.35185633 -0.08018591 0.14035576 0.03931514 -0.05713423 -Ti -1.47700000 4.26500000 4.82500000 -0.09770000 0.24880000 -0.02780000 0.24880000 -0.01690000 -0.05670000 -0.02780000 -0.05670000 0.11460000 -0.00000000 0.35185633 -0.08018591 0.14035576 -0.03931514 -0.05713423 -Ti 2.95500000 3.41200000 9.65000000 0.04160000 -0.00000000 -0.00000000 -0.00000000 -0.05780000 0.10750000 -0.00000000 0.10750000 0.01620000 0.00000000 0.00000000 0.15202796 0.01984087 0.00000000 0.07028641 -Ti 1.47700000 0.85300000 9.65000000 -0.04620000 -0.05260000 -0.11850000 -0.05260000 0.02760000 -0.04900000 -0.11850000 -0.04900000 0.01860000 -0.00000000 -0.07438763 -0.06929646 0.02278025 -0.16758431 -0.05218448 -Ti 4.43200000 0.85300000 9.65000000 -0.04620000 0.05260000 0.11850000 0.05260000 0.02760000 -0.04900000 0.11850000 -0.04900000 0.01860000 -0.00000000 0.07438763 -0.06929646 0.02278025 0.16758431 -0.05218448 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00310000 -0.00000000 -0.00000000 -0.00000000 0.00880000 -0.00150000 -0.00000000 -0.00150000 -0.01190000 0.00000000 0.00000000 -0.00212132 -0.01457446 0.00000000 -0.00403051 -Li 0.00000000 0.00000000 12.66500000 -0.00290000 0.00000000 0.00000000 0.00000000 0.00350000 -0.00120000 0.00000000 -0.00120000 -0.00060000 0.00000000 0.00000000 -0.00169706 -0.00073485 0.00000000 -0.00452548 -Li 2.95500000 1.70600000 6.63400000 -0.00290000 0.00000000 0.00000000 0.00000000 0.00350000 -0.00120000 0.00000000 -0.00120000 -0.00060000 0.00000000 0.00000000 -0.00169706 -0.00073485 0.00000000 -0.00452548 -Li 2.95500000 1.70600000 3.01600000 0.00310000 0.00000000 0.00000000 0.00000000 0.00880000 -0.00150000 0.00000000 -0.00150000 -0.01190000 0.00000000 0.00000000 -0.00212132 -0.01457446 0.00000000 -0.00403051 -Li 0.00000000 3.41200000 11.45900000 0.01220000 -0.00000000 0.00000000 -0.00000000 0.01150000 0.00030000 0.00000000 0.00030000 -0.02380000 0.00005774 0.00000000 0.00042426 -0.02910810 0.00000000 0.00049497 -Li 0.00000000 3.41200000 7.84000000 0.01220000 0.00000000 -0.00000000 0.00000000 0.01150000 0.00030000 -0.00000000 0.00030000 -0.02380000 0.00005774 0.00000000 0.00042426 -0.02910810 0.00000000 0.00049497 -Li 2.95500000 0.00000000 0.00000000 0.00530000 0.00000000 0.00000000 0.00000000 0.00630000 -0.00130000 0.00000000 -0.00130000 -0.01160000 0.00000000 0.00000000 -0.00183848 -0.01420704 0.00000000 -0.00070711 -Li 0.00000000 1.70600000 4.82500000 0.00530000 0.00000000 0.00000000 0.00000000 0.00630000 -0.00130000 0.00000000 -0.00130000 -0.01160000 0.00000000 0.00000000 -0.00183848 -0.01420704 0.00000000 -0.00070711 -O 0.00000000 0.00000000 3.80700000 0.00830000 0.00000000 -0.00000000 0.00000000 0.09410000 -0.08890000 -0.00000000 -0.08890000 -0.10240000 0.00000000 0.00000000 -0.12572359 -0.12541387 0.00000000 -0.06066976 -O 0.00000000 0.00000000 10.66800000 0.03070000 0.00000000 -0.00000000 0.00000000 0.04570000 -0.00560000 -0.00000000 -0.00560000 -0.07640000 0.00000000 0.00000000 -0.00791960 -0.09357051 0.00000000 -0.01060660 -O 2.95500000 1.70600000 8.63200000 0.03070000 0.00000000 -0.00000000 0.00000000 0.04570000 -0.00560000 -0.00000000 -0.00560000 -0.07640000 0.00000000 0.00000000 -0.00791960 -0.09357051 0.00000000 -0.01060660 -O 2.95500000 1.70600000 1.01800000 0.00830000 -0.00000000 0.00000000 -0.00000000 0.09410000 -0.08890000 0.00000000 -0.08890000 -0.10240000 0.00000000 0.00000000 -0.12572359 -0.12541387 0.00000000 -0.06066976 -O 0.00000000 3.41200000 13.45700000 0.10670000 0.00000000 0.00000000 0.00000000 -0.05710000 -0.11030000 0.00000000 -0.11030000 -0.04950000 -0.00005774 0.00000000 -0.15598776 -0.06066570 0.00000000 0.11582409 -O 0.00000000 3.41200000 5.84300000 0.10670000 -0.00000000 -0.00000000 -0.00000000 -0.05710000 -0.11030000 -0.00000000 -0.11030000 -0.04950000 -0.00005774 0.00000000 -0.15598776 -0.06066570 0.00000000 0.11582409 -O -1.32400000 4.17600000 1.14400000 -0.05530000 0.02420000 0.06820000 0.02420000 0.06170000 -0.03630000 0.06820000 -0.03630000 -0.00640000 0.00000000 0.03422397 -0.05133595 -0.00783837 0.09644936 -0.08273149 -O 0.00000000 1.88300000 1.14400000 0.04020000 0.00000000 -0.00000000 0.00000000 -0.07340000 0.05210000 -0.00000000 0.05210000 0.03320000 0.00000000 0.00000000 0.07368053 0.04066153 0.00000000 0.08032733 -O 1.32400000 4.17600000 1.14400000 -0.05530000 -0.02420000 -0.06820000 -0.02420000 0.06170000 -0.03630000 -0.06820000 -0.03630000 -0.00640000 0.00000000 -0.03422397 -0.05133595 -0.00783837 -0.09644936 -0.08273149 -O 4.27800000 0.94200000 13.33100000 -0.14680000 -0.06560000 0.13680000 -0.06560000 0.09190000 -0.00540000 0.13680000 -0.00540000 0.05490000 0.00000000 -0.09277241 -0.00763675 0.06723849 0.19346442 -0.16878639 -O 1.63100000 0.94200000 13.33100000 -0.14680000 0.06560000 -0.13680000 0.06560000 0.09190000 -0.00540000 -0.13680000 -0.00540000 0.05490000 0.00000000 0.09277241 -0.00763675 0.06723849 -0.19346442 -0.16878639 -O 2.95500000 3.23400000 13.33100000 0.00580000 0.00000000 -0.00000000 0.00000000 -0.07310000 0.07650000 -0.00000000 0.07650000 0.06730000 -0.00000000 0.00000000 0.10818734 0.08242533 0.00000000 0.05579073 -O 4.58600000 0.76400000 5.96800000 -0.14680000 -0.06560000 0.13680000 -0.06560000 0.09190000 -0.00540000 0.13680000 -0.00540000 0.05490000 0.00000000 -0.09277241 -0.00763675 0.06723849 0.19346442 -0.16878639 -O 2.95500000 3.58900000 5.96800000 0.00580000 0.00000000 0.00000000 0.00000000 -0.07310000 0.07650000 0.00000000 0.07650000 0.06730000 -0.00000000 0.00000000 0.10818734 0.08242533 0.00000000 0.05579073 -O 1.32400000 0.76400000 5.96800000 -0.14680000 0.06560000 -0.13680000 0.06560000 0.09190000 -0.00540000 -0.13680000 -0.00540000 0.05490000 0.00000000 0.09277241 -0.00763675 0.06723849 -0.19346442 -0.16878639 -O 1.32400000 2.64800000 3.68100000 -0.05530000 0.02420000 0.06820000 0.02420000 0.06170000 -0.03630000 0.06820000 -0.03630000 -0.00640000 0.00000000 0.03422397 -0.05133595 -0.00783837 0.09644936 -0.08273149 -O -1.32400000 2.64800000 3.68100000 -0.05530000 -0.02420000 -0.06820000 -0.02420000 0.06170000 -0.03630000 -0.06820000 -0.03630000 -0.00640000 0.00000000 -0.03422397 -0.05133595 -0.00783837 -0.09644936 -0.08273149 -O 0.00000000 4.94000000 3.68100000 0.04020000 -0.00000000 0.00000000 -0.00000000 -0.07340000 0.05210000 0.00000000 0.05210000 0.03320000 0.00000000 0.00000000 0.07368053 0.04066153 0.00000000 0.08032733 -O 1.63100000 2.47000000 10.79300000 -0.00900000 0.03090000 0.00480000 0.03090000 0.05160000 0.02200000 0.00480000 0.02200000 -0.04270000 0.00005774 0.04369920 0.03111270 -0.05225578 0.00678823 -0.04285067 -O 2.95500000 0.17700000 10.79300000 0.05040000 0.00000000 -0.00000000 0.00000000 -0.00300000 -0.00000000 -0.00000000 -0.00000000 -0.04740000 0.00000000 0.00000000 0.00000000 -0.05805291 0.00000000 0.03775950 -O 4.27800000 2.47000000 10.79300000 -0.00900000 -0.03090000 -0.00480000 -0.03090000 0.05160000 0.02200000 -0.00480000 0.02200000 -0.04270000 0.00005774 -0.04369920 0.03111270 -0.05225578 -0.00678823 -0.04285067 -O -1.63100000 4.35300000 8.50600000 -0.00900000 0.03090000 0.00480000 0.03090000 0.05160000 0.02200000 0.00480000 0.02200000 -0.04270000 0.00005774 0.04369920 0.03111270 -0.05225578 0.00678823 -0.04285067 -O 1.63100000 4.35300000 8.50600000 -0.00900000 -0.03090000 -0.00480000 -0.03090000 0.05160000 0.02200000 -0.00480000 0.02200000 -0.04270000 0.00005774 -0.04369920 0.03111270 -0.05225578 -0.00678823 -0.04285067 -O 0.00000000 1.52800000 8.50600000 0.05040000 0.00000000 -0.00000000 0.00000000 -0.00300000 -0.00000000 -0.00000000 -0.00000000 -0.04740000 0.00000000 0.00000000 0.00000000 -0.05805291 0.00000000 0.03775950 -Ti 0.00000000 0.00000000 7.23700000 0.06510000 0.00000000 -0.00000000 0.00000000 0.15840000 0.07460000 -0.00000000 0.07460000 -0.22350000 0.00000000 0.00000000 0.10550033 -0.27373048 0.00000000 -0.06597306 -Ti 2.95500000 1.70600000 12.06200000 0.06510000 0.00000000 -0.00000000 0.00000000 0.15840000 0.07460000 -0.00000000 0.07460000 -0.22350000 0.00000000 0.00000000 0.10550033 -0.27373048 0.00000000 -0.06597306 -Ti 0.00000000 3.41200000 2.41200000 0.08290000 0.00000000 0.00000000 0.00000000 0.17950000 0.14420000 0.00000000 0.14420000 -0.26240000 0.00000000 0.00000000 0.20392960 -0.32137305 0.00000000 -0.06830652 -Ti -1.47700000 2.55900000 0.00000000 -0.08020000 -0.23740000 0.02580000 -0.23740000 -0.03380000 -0.07750000 0.02580000 -0.07750000 0.11400000 -0.00000000 -0.33573430 -0.10960155 0.13962092 0.03648671 -0.03280975 -Ti 1.47700000 2.55900000 0.00000000 -0.08020000 0.23740000 -0.02580000 0.23740000 -0.03380000 -0.07750000 -0.02580000 -0.07750000 0.11400000 -0.00000000 0.33573430 -0.10960155 0.13962092 -0.03648671 -0.03280975 -Ti 1.47700000 4.26500000 4.82500000 -0.08020000 -0.23740000 0.02580000 -0.23740000 -0.03380000 -0.07750000 0.02580000 -0.07750000 0.11400000 -0.00000000 -0.33573430 -0.10960155 0.13962092 0.03648671 -0.03280975 -Ti -1.47700000 4.26500000 4.82500000 -0.08020000 0.23740000 -0.02580000 0.23740000 -0.03380000 -0.07750000 -0.02580000 -0.07750000 0.11400000 -0.00000000 0.33573430 -0.10960155 0.13962092 -0.03648671 -0.03280975 -Ti 2.95500000 3.41200000 9.65000000 0.05450000 -0.00000000 0.00000000 -0.00000000 -0.06160000 0.08400000 0.00000000 0.08400000 0.00710000 -0.00000000 0.00000000 0.11879394 0.00869569 0.00000000 0.08209510 -Ti 1.47700000 0.85300000 9.65000000 -0.03160000 -0.05280000 -0.07250000 -0.05280000 0.02880000 -0.03840000 -0.07250000 -0.03840000 0.00280000 0.00000000 -0.07467048 -0.05430580 0.00342929 -0.10253048 -0.04270925 -Ti 4.43200000 0.85300000 9.65000000 -0.03160000 0.05280000 0.07250000 0.05280000 0.02880000 -0.03840000 0.07250000 -0.03840000 0.00280000 0.00000000 0.07467048 -0.05430580 0.00342929 0.10253048 -0.04270925 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00270000 -0.00030000 0.00010000 -0.00030000 0.00940000 -0.00130000 0.00010000 -0.00130000 -0.01200000 -0.00005774 -0.00042426 -0.00183848 -0.01473776 0.00014142 -0.00473762 -Li -1.47700000 4.26500000 12.06200000 -0.00200000 0.00320000 0.00120000 0.00320000 0.00840000 -0.00390000 0.00120000 -0.00390000 -0.00640000 0.00000000 0.00452548 -0.00551543 -0.00783837 0.00169706 -0.00735391 -Li 2.95500000 1.70600000 6.63400000 0.00140000 0.00240000 0.00100000 0.00240000 -0.00140000 0.00060000 0.00100000 0.00060000 -0.00010000 0.00005774 0.00339411 0.00084853 -0.00008165 0.00141421 0.00197990 -Li 2.95500000 1.70600000 3.01600000 0.00750000 0.00270000 0.00110000 0.00270000 0.00360000 0.00050000 0.00110000 0.00050000 -0.01110000 0.00000000 0.00381838 0.00070711 -0.01359467 0.00155563 0.00275772 -Li 0.00000000 1.70600000 12.06200000 0.00590000 -0.00370000 0.00120000 -0.00370000 -0.00140000 0.00420000 0.00120000 0.00420000 -0.00450000 0.00000000 -0.00523259 0.00593970 -0.00551135 0.00169706 0.00516188 -Li 0.00000000 3.41200000 7.84000000 0.01170000 -0.00060000 -0.00030000 -0.00060000 0.01250000 -0.00010000 -0.00030000 -0.00010000 -0.02410000 -0.00005774 -0.00084853 -0.00014142 -0.02955718 -0.00042426 -0.00056569 -Li 2.95500000 0.00000000 0.00000000 0.00440000 0.00010000 0.00170000 0.00010000 0.00600000 -0.00060000 0.00170000 -0.00060000 -0.01040000 0.00000000 0.00014142 -0.00084853 -0.01273735 0.00240416 -0.00113137 -Li 1.47700000 4.26500000 4.82500000 0.00580000 0.00010000 0.00100000 0.00010000 0.00540000 0.00050000 0.00100000 0.00050000 -0.01120000 0.00000000 0.00014142 0.00070711 -0.01371714 0.00141421 0.00028284 -O 0.00000000 0.00000000 3.80700000 0.07480000 0.03110000 0.05890000 0.03110000 0.02670000 0.02920000 0.05890000 0.02920000 -0.10160000 0.00005774 0.04398204 0.04129504 -0.12439325 0.08329718 0.03401184 -O 0.00000000 0.00000000 10.66800000 0.02310000 -0.05230000 0.06580000 -0.05230000 0.08270000 0.03220000 0.06580000 0.03220000 -0.10580000 0.00000000 -0.07396337 0.04553768 -0.12957801 0.09305525 -0.04214356 -O 2.95500000 1.70600000 8.63200000 0.04230000 -0.00250000 0.01790000 -0.00250000 0.04310000 0.00900000 0.01790000 0.00900000 -0.08540000 0.00000000 -0.00353553 0.01272792 -0.10459321 0.02531442 -0.00056569 -O 2.95500000 1.70600000 1.01800000 0.01170000 -0.00980000 0.01240000 -0.00980000 0.09940000 -0.05860000 0.01240000 -0.05860000 -0.11110000 0.00000000 -0.01385929 -0.08287291 -0.13606916 0.01753625 -0.06201326 -O 0.00000000 3.41200000 13.45700000 0.10610000 -0.03360000 0.08920000 -0.03360000 -0.04100000 -0.08590000 0.08920000 -0.08590000 -0.06510000 0.00000000 -0.04751758 -0.12148095 -0.07973089 0.12614785 0.10401541 -O 0.00000000 3.41200000 5.84300000 -0.01980000 -0.07610000 0.11340000 -0.07610000 0.08740000 0.05320000 0.11340000 0.05320000 -0.06750000 -0.00005774 -0.10762165 0.07523616 -0.08271110 0.16037182 -0.07580185 -O -1.32400000 4.17600000 1.14400000 0.01000000 0.05420000 0.10170000 0.05420000 0.05060000 -0.02570000 0.10170000 -0.02570000 -0.06060000 0.00000000 0.07665038 -0.03634529 -0.07421954 0.14382552 -0.02870854 -O 0.00000000 1.88300000 1.14400000 0.05310000 0.00700000 -0.02870000 0.00700000 -0.10730000 0.04020000 -0.02870000 0.04020000 0.05430000 -0.00005774 0.00989949 0.05685139 0.06646282 -0.04058793 0.11341993 -O 1.32400000 4.17600000 1.14400000 0.00680000 -0.03560000 -0.04840000 -0.03560000 0.05340000 -0.02080000 -0.04840000 -0.02080000 -0.06020000 0.00000000 -0.05034600 -0.02941564 -0.07372964 -0.06844794 -0.03295118 -O 4.27800000 0.94200000 13.33100000 -0.14710000 0.01090000 0.06000000 0.01090000 0.07590000 -0.07510000 0.06000000 -0.07510000 0.07120000 0.00000000 0.01541493 -0.10620744 0.08720183 0.08485281 -0.15768481 -O 1.63100000 0.94200000 13.33100000 -0.19640000 0.01330000 -0.07630000 0.01330000 0.07010000 0.02920000 -0.07630000 0.02920000 0.12640000 -0.00005774 0.01880904 0.04129504 0.15476693 -0.10790449 -0.18844396 -O 2.95500000 3.23400000 13.33100000 0.06150000 0.05880000 -0.08680000 0.05880000 -0.15200000 0.06380000 -0.08680000 0.06380000 0.09050000 -0.00000000 0.08315576 0.09022683 0.11083941 -0.12275374 0.15096730 -O 4.58600000 0.76400000 5.96800000 0.07910000 0.07170000 0.06760000 0.07170000 -0.13540000 -0.11900000 0.06760000 -0.11900000 0.05630000 -0.00000000 0.10139911 -0.16829141 0.06895314 0.09560084 0.15167440 -O 2.95500000 3.58900000 5.96800000 -0.03010000 0.12810000 -0.06720000 0.12810000 -0.03350000 0.11650000 -0.06720000 0.11650000 0.06360000 -0.00000000 0.18116076 0.16475588 0.07789377 -0.09503515 0.00240416 -O 1.32400000 0.76400000 5.96800000 -0.05910000 -0.04110000 -0.06370000 -0.04110000 -0.01440000 -0.03620000 -0.06370000 -0.03620000 0.07350000 -0.00000000 -0.05812418 -0.05119453 0.09001875 -0.09008540 -0.03160767 -O 1.32400000 2.64800000 3.68100000 -0.00170000 0.02990000 0.07300000 0.02990000 0.05150000 -0.07420000 0.07300000 -0.07420000 -0.04980000 0.00000000 0.04228499 -0.10493465 -0.06099229 0.10323759 -0.03761808 -O -1.32400000 2.64800000 3.68100000 -0.06310000 -0.06180000 -0.05000000 -0.06180000 0.01430000 -0.00780000 -0.05000000 -0.00780000 0.04880000 -0.00000000 -0.08739840 -0.01103087 0.05976755 -0.07071068 -0.05473006 -O 0.00000000 4.94000000 3.68100000 0.05730000 -0.00580000 0.00670000 -0.00580000 -0.01180000 0.05650000 0.00670000 0.05650000 -0.04560000 0.00005774 -0.00820244 0.07990307 -0.05580754 0.00947523 0.04886108 -O 1.63100000 2.47000000 10.79300000 0.00650000 0.10470000 -0.04540000 0.10470000 0.01820000 -0.02390000 -0.04540000 -0.02390000 -0.02470000 0.00000000 0.14806816 -0.03379970 -0.03025120 -0.06420530 -0.00827315 -O 2.95500000 0.17700000 10.79300000 0.10550000 0.05000000 -0.06390000 0.05000000 -0.07570000 -0.02390000 -0.06390000 -0.02390000 -0.02980000 0.00000000 0.07071068 -0.03379970 -0.03649740 -0.09036825 0.12812775 -O 4.27800000 2.47000000 10.79300000 -0.05790000 -0.10890000 0.03540000 -0.10890000 0.07720000 0.04600000 0.03540000 0.04600000 -0.01930000 0.00000000 -0.15400786 0.06505382 -0.02363758 0.05006316 -0.09553013 -O -1.63100000 4.35300000 8.50600000 -0.00250000 0.04830000 -0.02330000 0.04830000 0.02850000 -0.01500000 -0.02330000 -0.01500000 -0.02600000 0.00000000 0.06830652 -0.02121320 -0.03184337 -0.03295118 -0.02192031 -O 1.63100000 4.35300000 8.50600000 -0.00150000 -0.04600000 0.02000000 -0.04600000 0.04520000 0.01480000 0.02000000 0.01480000 -0.04360000 -0.00005774 -0.06505382 0.02093036 -0.05343970 0.02828427 -0.03302189 -O 0.00000000 1.52800000 8.50600000 0.06210000 0.00810000 -0.02740000 0.00810000 -0.03970000 -0.01180000 -0.02740000 -0.01180000 -0.02240000 -0.00000000 0.01145513 -0.01668772 -0.02743429 -0.03874945 0.07198347 -Ti 0.00000000 0.00000000 7.23700000 0.13410000 0.03780000 -0.06390000 0.03780000 0.09090000 -0.03630000 -0.06390000 -0.03630000 -0.22500000 0.00000000 0.05345727 -0.05133595 -0.27556760 -0.09036825 0.03054701 -Ti 2.95500000 1.70600000 12.06200000 0.10520000 0.03170000 -0.03560000 0.03170000 0.15980000 0.04860000 -0.03560000 0.04860000 -0.26500000 0.00000000 0.04483057 0.06873078 -0.32455739 -0.05034600 -0.03860803 -Ti 0.00000000 3.41200000 2.41200000 0.11420000 0.02140000 -0.06520000 0.02140000 0.14040000 0.03840000 -0.06520000 0.03840000 -0.25460000 0.00000000 0.03026417 0.05430580 -0.31182004 -0.09220672 -0.01852620 -Ti -1.47700000 2.55900000 0.00000000 -0.08410000 -0.23290000 0.00060000 -0.23290000 -0.03710000 -0.08900000 0.00060000 -0.08900000 0.12130000 -0.00005774 -0.32937034 -0.12586501 0.14852073 0.00084853 -0.03323402 -Ti 1.47700000 2.55900000 0.00000000 -0.08310000 0.23130000 -0.00750000 0.23130000 -0.03100000 -0.07290000 -0.00750000 -0.07290000 0.11410000 -0.00000000 0.32710760 -0.10309617 0.13974339 -0.01060660 -0.03684026 -Ti 0.00000000 1.70600000 4.82500000 0.16080000 -0.09730000 0.08090000 -0.09730000 -0.27130000 0.01860000 0.08090000 0.01860000 0.11050000 0.00000000 -0.13760298 0.02630437 0.13533431 0.11440988 0.30554084 -Ti -1.47700000 4.26500000 4.82500000 -0.25100000 0.13630000 0.05780000 0.13630000 0.13840000 0.06320000 0.05780000 0.06320000 0.11260000 -0.00000000 0.19275731 0.08937830 0.13790627 0.08174154 -0.27534738 -Ti 2.95500000 3.41200000 9.65000000 0.05160000 -0.00480000 0.01240000 -0.00480000 -0.06320000 0.09580000 0.01240000 0.09580000 0.01150000 0.00005774 -0.00678823 0.13548166 0.01412539 0.01753625 0.08117586 -Ti 1.47700000 0.85300000 9.65000000 -0.04110000 -0.05810000 -0.10470000 -0.05810000 0.02520000 -0.05920000 -0.10470000 -0.05920000 0.01590000 -0.00000000 -0.08216581 -0.08372144 0.01947344 -0.14806816 -0.04688118 -Ti 4.43200000 0.85300000 9.65000000 -0.03800000 0.04880000 0.08820000 0.04880000 0.02870000 -0.03490000 0.08820000 -0.03490000 0.00930000 -0.00000000 0.06901362 -0.04935605 0.01139013 0.12473364 -0.04716402 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00200000 -0.00030000 0.00010000 -0.00030000 0.00860000 -0.00150000 0.00010000 -0.00150000 -0.01050000 -0.00005774 -0.00042426 -0.00212132 -0.01290065 0.00014142 -0.00466690 -Li 0.00000000 0.00000000 12.66500000 -0.02180000 0.00000000 -0.00000000 0.00000000 0.03170000 -0.01880000 -0.00000000 -0.01880000 -0.00980000 -0.00005774 0.00000000 -0.02658721 -0.01204332 0.00000000 -0.03783021 -Li 2.95500000 1.70600000 6.63400000 0.00150000 0.00240000 0.00110000 0.00240000 -0.00130000 0.00060000 0.00110000 0.00060000 -0.00020000 -0.00000000 0.00339411 0.00084853 -0.00024495 0.00155563 0.00197990 -Li 2.95500000 1.70600000 3.01600000 0.00750000 0.00270000 0.00110000 0.00270000 0.00360000 0.00060000 0.00110000 0.00060000 -0.01110000 0.00000000 0.00381838 0.00084853 -0.01359467 0.00155563 0.00275772 -Li 0.00000000 1.70600000 12.06200000 -0.01080000 -0.00000000 0.00000000 -0.00000000 0.02560000 -0.01260000 0.00000000 -0.01260000 -0.01480000 0.00000000 0.00000000 -0.01781909 -0.01812622 0.00000000 -0.02573869 -Li 0.00000000 3.41200000 7.84000000 0.01190000 -0.00070000 -0.00030000 -0.00070000 0.01260000 -0.00010000 -0.00030000 -0.00010000 -0.02440000 -0.00005774 -0.00098995 -0.00014142 -0.02992460 -0.00042426 -0.00049497 -Li 2.95500000 0.00000000 0.00000000 0.00620000 -0.00010000 0.00000000 -0.00010000 0.00560000 -0.00280000 0.00000000 -0.00280000 -0.01180000 0.00000000 -0.00014142 -0.00395980 -0.01445199 0.00000000 0.00042426 -Li 1.47700000 4.26500000 4.82500000 0.00580000 0.00010000 0.00100000 0.00010000 0.00540000 0.00050000 0.00100000 0.00050000 -0.01120000 0.00000000 0.00014142 0.00070711 -0.01371714 0.00141421 0.00028284 -O 0.00000000 0.00000000 3.80700000 0.07290000 0.02920000 0.06110000 0.02920000 0.02700000 0.02950000 0.06110000 0.02950000 -0.09990000 0.00000000 0.04129504 0.04171930 -0.12235201 0.08640845 0.03245620 -O 0.00000000 0.00000000 10.66800000 -0.04310000 -0.00080000 0.00100000 -0.00080000 0.09220000 0.08830000 0.00100000 0.08830000 -0.04900000 -0.00005774 -0.00113137 0.12487506 -0.06005332 0.00141421 -0.09567155 -O 2.95500000 1.70600000 8.63200000 0.04430000 0.00070000 0.00400000 0.00070000 0.04920000 0.01840000 0.00400000 0.01840000 -0.09350000 0.00000000 0.00098995 0.02602153 -0.11451365 0.00565685 -0.00346482 -O 2.95500000 1.70600000 1.01800000 0.02470000 -0.00710000 0.00540000 -0.00710000 0.06700000 -0.08650000 0.00540000 -0.08650000 -0.09170000 0.00000000 -0.01004092 -0.12232947 -0.11230910 0.00763675 -0.02991062 -O 0.00000000 3.41200000 13.45700000 0.05590000 0.00460000 0.01180000 0.00460000 0.03210000 -0.04850000 0.01180000 -0.04850000 -0.08790000 -0.00005774 0.00650538 -0.06858936 -0.10769590 0.01668772 0.01682914 -O 0.00000000 3.41200000 5.84300000 -0.01980000 -0.07520000 0.11240000 -0.07520000 0.08580000 0.05240000 0.11240000 0.05240000 -0.06590000 -0.00005774 -0.10634886 0.07410479 -0.08075151 0.15895760 -0.07467048 -O -1.32400000 4.17600000 1.14400000 -0.02590000 0.00700000 0.12870000 0.00700000 0.05180000 -0.02290000 0.12870000 -0.02290000 -0.02600000 0.00005774 0.00989949 -0.03238549 -0.03180254 0.18200929 -0.05494220 -O 0.00000000 1.88300000 1.14400000 0.04570000 0.00200000 -0.01700000 0.00200000 -0.10400000 0.06610000 -0.01700000 0.06610000 0.05830000 -0.00000000 0.00282843 0.09347952 0.07140263 -0.02404163 0.10585389 -O 1.32400000 4.17600000 1.14400000 -0.02310000 -0.00280000 -0.07820000 -0.00280000 0.04980000 -0.02730000 -0.07820000 -0.02730000 -0.02670000 0.00000000 -0.00395980 -0.03860803 -0.03270069 -0.11059150 -0.05154808 -O 4.27800000 0.94200000 13.33100000 -0.06830000 -0.03250000 0.02530000 -0.03250000 -0.00840000 -0.05630000 0.02530000 -0.05630000 0.07670000 -0.00000000 -0.04596194 -0.07962022 0.09393793 0.03577960 -0.04235570 -O 1.63100000 0.94200000 13.33100000 -0.06750000 0.03460000 -0.02420000 0.03460000 -0.01400000 -0.05770000 -0.02420000 -0.05770000 0.08150000 -0.00000000 0.04893179 -0.08160012 0.09981671 -0.03422397 -0.03783021 -O 2.95500000 3.23400000 13.33100000 0.00220000 -0.00120000 -0.00030000 -0.00120000 -0.05650000 0.07680000 -0.00030000 0.07680000 0.05430000 -0.00000000 -0.00169706 0.10861160 0.06650365 -0.00042426 0.04150717 -O 4.58600000 0.76400000 5.96800000 0.07890000 0.07400000 0.06630000 0.07400000 -0.13430000 -0.11930000 0.06630000 -0.11930000 0.05540000 0.00000000 0.10465180 -0.16871568 0.06785087 0.09376236 0.15075517 -O 2.95500000 3.58900000 5.96800000 -0.03000000 0.12910000 -0.06570000 0.12910000 -0.03400000 0.11700000 -0.06570000 0.11700000 0.06400000 -0.00000000 0.18257497 0.16546299 0.07838367 -0.09291383 0.00282843 -O 1.32400000 0.76400000 5.96800000 -0.05970000 -0.04130000 -0.06360000 -0.04130000 -0.01390000 -0.03720000 -0.06360000 -0.03720000 0.07360000 -0.00000000 -0.05840702 -0.05260874 0.09014122 -0.08994398 -0.03238549 -O 1.32400000 2.64800000 3.68100000 -0.00040000 0.03370000 0.07220000 0.03370000 0.04150000 -0.07770000 0.07220000 -0.07770000 -0.04110000 0.00000000 0.04765900 -0.10988439 -0.05033701 0.10210622 -0.02962777 -O -1.32400000 2.64800000 3.68100000 -0.06350000 -0.06180000 -0.05100000 -0.06180000 0.01380000 -0.00790000 -0.05100000 -0.00790000 0.04980000 -0.00005774 -0.08739840 -0.01117229 0.06095147 -0.07212489 -0.05465935 -O 0.00000000 4.94000000 3.68100000 0.05190000 -0.00130000 0.00260000 -0.00130000 -0.00920000 0.05740000 0.00260000 0.05740000 -0.04270000 0.00000000 -0.00183848 0.08117586 -0.05229661 0.00367696 0.04320422 -O 1.63100000 2.47000000 10.79300000 -0.00330000 0.11530000 -0.04360000 0.11530000 0.02160000 -0.02630000 -0.04360000 -0.02630000 -0.01830000 0.00000000 0.16305882 -0.03719382 -0.02241283 -0.06165971 -0.01760696 -O 2.95500000 0.17700000 10.79300000 0.07040000 -0.00270000 -0.00270000 -0.00270000 -0.04980000 -0.00700000 -0.00270000 -0.00700000 -0.02060000 -0.00000000 -0.00381838 -0.00989949 -0.02522974 -0.00381838 0.08499424 -O 4.27800000 2.47000000 10.79300000 -0.00310000 -0.11760000 0.04150000 -0.11760000 0.01870000 -0.02480000 0.04150000 -0.02480000 -0.01550000 -0.00005774 -0.16631151 -0.03507250 -0.01902437 0.05868986 -0.01541493 -O -1.63100000 4.35300000 8.50600000 -0.00200000 0.05080000 -0.04040000 0.05080000 0.03210000 -0.00550000 -0.04040000 -0.00550000 -0.03010000 0.00000000 0.07184205 -0.00777817 -0.03686482 -0.05713423 -0.02411234 -O 1.63100000 4.35300000 8.50600000 -0.00040000 -0.04530000 0.02610000 -0.04530000 0.03600000 0.00730000 0.02610000 0.00730000 -0.03560000 0.00000000 -0.06406387 0.01032376 -0.04360092 0.03691097 -0.02573869 -O 0.00000000 1.52800000 8.50600000 0.05690000 0.00420000 -0.01840000 0.00420000 -0.04120000 -0.01270000 -0.01840000 -0.01270000 -0.01570000 0.00000000 0.00593970 -0.01796051 -0.01922849 -0.02602153 0.06936718 -Ti 0.00000000 0.00000000 7.23700000 0.13420000 0.03960000 -0.06450000 0.03960000 0.09240000 -0.03680000 -0.06450000 -0.03680000 -0.22650000 -0.00005774 0.05600286 -0.05204306 -0.27744554 -0.09121677 0.02955706 -Ti 2.95500000 1.70600000 12.06200000 0.10850000 0.00030000 -0.00050000 0.00030000 0.12590000 0.03190000 -0.00050000 0.03190000 -0.23440000 0.00000000 0.00042426 0.04511341 -0.28708020 -0.00070711 -0.01230366 -Ti 0.00000000 3.41200000 2.41200000 0.11270000 0.02270000 -0.06460000 0.02270000 0.14070000 0.04300000 -0.06460000 0.04300000 -0.25340000 0.00000000 0.03210265 0.06081118 -0.31035035 -0.09135820 -0.01979899 -Ti -1.47700000 2.55900000 0.00000000 -0.09650000 -0.24850000 0.02760000 -0.24850000 -0.01640000 -0.05810000 0.02760000 -0.05810000 0.11290000 -0.00000000 -0.35143207 -0.08216581 0.13827370 0.03903229 -0.05663925 -Ti 1.47700000 2.55900000 0.00000000 -0.09490000 0.24890000 -0.02880000 0.24890000 -0.01940000 -0.06050000 -0.02880000 -0.06050000 0.11430000 -0.00000000 0.35199776 -0.08555992 0.13998834 -0.04072935 -0.05338656 -Ti 0.00000000 1.70600000 4.82500000 0.16090000 -0.09700000 0.08070000 -0.09700000 -0.27170000 0.01780000 0.08070000 0.01780000 0.11070000 0.00005774 -0.13717872 0.02517300 0.13562008 0.11412703 0.30589439 -Ti -1.47700000 4.26500000 4.82500000 -0.25130000 0.13630000 0.05780000 0.13630000 0.13850000 0.06360000 0.05780000 0.06360000 0.11280000 0.00000000 0.19275731 0.08994398 0.13815122 0.08174154 -0.27563022 -Ti 2.95500000 3.41200000 9.65000000 0.04450000 -0.00010000 -0.00110000 -0.00010000 -0.05980000 0.10800000 -0.00110000 0.10800000 0.01530000 0.00000000 -0.00014142 0.15273506 0.01873860 -0.00155563 0.07375124 -Ti 1.47700000 0.85300000 9.65000000 -0.04660000 -0.05160000 -0.11980000 -0.05160000 0.02580000 -0.05140000 -0.11980000 -0.05140000 0.02090000 -0.00005774 -0.07297342 -0.07269058 0.02555634 -0.16942278 -0.05119453 -Ti 4.43200000 0.85300000 9.65000000 -0.04680000 0.05340000 0.11840000 0.05340000 0.02730000 -0.05200000 0.11840000 -0.05200000 0.01940000 0.00005774 0.07551900 -0.07353911 0.02380088 0.16744289 -0.05239661 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00220000 -0.00040000 0.00010000 -0.00040000 0.00890000 -0.00130000 0.00010000 -0.00130000 -0.01110000 0.00000000 -0.00056569 -0.00183848 -0.01359467 0.00014142 -0.00473762 -Li 0.00000000 0.00000000 12.66500000 -0.00270000 0.00000000 0.00000000 0.00000000 0.00290000 -0.00120000 0.00000000 -0.00120000 -0.00020000 0.00000000 0.00000000 -0.00169706 -0.00024495 0.00000000 -0.00395980 -Li 2.95500000 1.70600000 6.63400000 0.01830000 0.02320000 0.01620000 0.02320000 -0.00840000 0.00940000 0.01620000 0.00940000 -0.00990000 0.00000000 0.03280975 0.01329361 -0.01212497 0.02291026 0.01887975 -Li 2.95500000 1.70600000 3.01600000 0.00720000 0.00270000 0.00130000 0.00270000 0.00330000 0.00070000 0.00130000 0.00070000 -0.01050000 0.00000000 0.00381838 0.00098995 -0.01285982 0.00183848 0.00275772 -Li 0.00000000 3.41200000 11.45900000 0.01300000 -0.00000000 -0.00010000 -0.00000000 0.01150000 0.00030000 -0.00010000 0.00030000 -0.02440000 -0.00005774 0.00000000 0.00042426 -0.02992460 -0.00014142 0.00106066 -Li -1.47700000 2.55900000 7.23700000 0.01650000 0.01570000 0.01090000 0.01570000 -0.00170000 0.00630000 0.01090000 0.00630000 -0.01480000 0.00000000 0.02220315 0.00890955 -0.01812622 0.01541493 0.01286934 -Li 2.95500000 0.00000000 0.00000000 0.00540000 -0.00010000 0.00000000 -0.00010000 0.00580000 -0.00110000 0.00000000 -0.00110000 -0.01120000 0.00000000 -0.00014142 -0.00155563 -0.01371714 0.00000000 -0.00028284 -Li 1.47700000 4.26500000 4.82500000 0.00590000 -0.00030000 0.00240000 -0.00030000 0.00590000 0.00140000 0.00240000 0.00140000 -0.01180000 0.00000000 -0.00042426 0.00197990 -0.01445199 0.00339411 0.00000000 -O 0.00000000 0.00000000 3.80700000 0.06250000 0.01470000 0.07770000 0.01470000 0.02910000 0.03860000 0.07770000 0.03860000 -0.09170000 0.00005774 0.02078894 0.05458864 -0.11226828 0.10988439 0.02361737 -O 0.00000000 0.00000000 10.66800000 0.04740000 0.00240000 -0.01390000 0.00240000 0.04610000 -0.01270000 -0.01390000 -0.01270000 -0.09350000 0.00000000 0.00339411 -0.01796051 -0.11451365 -0.01965757 0.00091924 -O 2.95500000 1.70600000 8.63200000 0.05900000 0.05820000 -0.07600000 0.05820000 -0.01000000 -0.04500000 -0.07600000 -0.04500000 -0.04900000 0.00000000 0.08230723 -0.06363961 -0.06001250 -0.10748023 0.04879037 -O 2.95500000 1.70600000 1.01800000 0.01310000 -0.00530000 0.00500000 -0.00530000 0.08680000 -0.06760000 0.00500000 -0.06760000 -0.09990000 0.00000000 -0.00749533 -0.09560084 -0.12235201 0.00707107 -0.05211377 -O 0.00000000 3.41200000 13.45700000 0.12450000 0.00820000 0.01080000 0.00820000 -0.05850000 -0.12360000 0.01080000 -0.12360000 -0.06590000 -0.00005774 0.01159655 -0.17479680 -0.08075151 0.01527351 0.12940054 -O 0.00000000 3.41200000 5.84300000 0.03400000 -0.00810000 0.04790000 -0.00810000 0.05390000 0.01400000 0.04790000 0.01400000 -0.08790000 0.00000000 -0.01145513 0.01979899 -0.10765507 0.06774083 -0.01407142 -O -1.32400000 4.17600000 1.14400000 0.00190000 0.03500000 0.10340000 0.03500000 0.03920000 -0.02380000 0.10340000 -0.02380000 -0.04110000 0.00000000 0.04949747 -0.03365828 -0.05033701 0.14622968 -0.02637508 -O 0.00000000 1.88300000 1.14400000 0.04790000 0.00260000 -0.01870000 0.00260000 -0.09770000 0.04820000 -0.01870000 0.04820000 0.04970000 0.00005774 0.00367696 0.06816509 0.06091064 -0.02644579 0.10295475 -O 1.32400000 4.17600000 1.14400000 0.00720000 -0.02720000 -0.04830000 -0.02720000 0.03550000 -0.03100000 -0.04830000 -0.03100000 -0.04270000 0.00000000 -0.03846661 -0.04384062 -0.05229661 -0.06830652 -0.02001112 -O 4.27800000 0.94200000 13.33100000 -0.14510000 -0.05530000 0.13650000 -0.05530000 0.08960000 0.00230000 0.13650000 0.00230000 0.05550000 0.00000000 -0.07820601 0.00325269 0.06797334 0.19304015 -0.16595796 -O 1.63100000 0.94200000 13.33100000 -0.14490000 0.06280000 -0.13410000 0.06280000 0.08090000 -0.00160000 -0.13410000 -0.00160000 0.06400000 -0.00000000 0.08881261 -0.00226274 0.07838367 -0.18964604 -0.15966471 -O 2.95500000 3.23400000 13.33100000 0.01030000 -0.00080000 0.00050000 -0.00080000 -0.08400000 0.07370000 0.00050000 0.07370000 0.07360000 0.00005774 -0.00113137 0.10422754 0.09018205 0.00070711 0.06668017 -O 4.58600000 0.76400000 5.96800000 0.00480000 0.00970000 0.06140000 0.00970000 -0.08140000 0.00620000 0.06140000 0.00620000 0.07660000 -0.00000000 0.01371787 0.00876812 0.09381546 0.08683271 0.06095260 -O 2.95500000 3.58900000 5.96800000 -0.05740000 0.04050000 0.03780000 0.04050000 -0.02410000 0.04980000 0.03780000 0.04980000 0.08150000 -0.00000000 0.05727565 0.07042784 0.09981671 0.05345727 -0.02354666 -O 1.32400000 0.76400000 5.96800000 -0.04080000 -0.02600000 -0.06660000 -0.02600000 -0.01360000 -0.03810000 -0.06660000 -0.03810000 0.05430000 0.00005774 -0.03676955 -0.05388154 0.06654447 -0.09418662 -0.01923330 -O 1.32400000 2.64800000 3.68100000 0.02640000 0.03720000 0.08410000 0.03720000 -0.00040000 -0.10000000 0.08410000 -0.10000000 -0.02600000 0.00000000 0.05260874 -0.14142136 -0.03184337 0.11893536 0.01895046 -O -1.32400000 2.64800000 3.68100000 -0.06830000 -0.06380000 -0.06570000 -0.06380000 0.01000000 -0.01830000 -0.06570000 -0.01830000 0.05830000 0.00000000 -0.09022683 -0.02588011 0.07140263 -0.09291383 -0.05536646 -O 0.00000000 4.94000000 3.68100000 0.03390000 0.03020000 -0.01550000 0.03020000 -0.00720000 0.08140000 -0.01550000 0.08140000 -0.02670000 0.00000000 0.04270925 0.11511698 -0.03270069 -0.02192031 0.02906209 -O 1.63100000 2.47000000 10.79300000 -0.02050000 0.04010000 -0.01540000 0.04010000 0.05050000 0.03780000 -0.01540000 0.03780000 -0.03000000 -0.00000000 0.05670996 0.05345727 -0.03674235 -0.02177889 -0.05020458 -O 2.95500000 0.17700000 10.79300000 0.06610000 -0.00690000 0.00680000 -0.00690000 -0.03060000 -0.02630000 0.00680000 -0.02630000 -0.03550000 -0.00000000 -0.00975807 -0.03719382 -0.04347844 0.00961665 0.06837723 -O 4.27800000 2.47000000 10.79300000 -0.02030000 -0.04040000 0.00180000 -0.04040000 0.03600000 0.02230000 0.00180000 0.02230000 -0.01560000 -0.00005774 -0.05713423 0.03153696 -0.01914684 0.00254558 -0.03981011 -O -1.63100000 4.35300000 8.50600000 -0.08450000 0.06850000 0.00100000 0.06850000 0.10290000 0.05100000 0.00100000 0.05100000 -0.01840000 0.00000000 0.09687363 0.07212489 -0.02253531 0.00141421 -0.13251181 -O 1.63100000 4.35300000 8.50600000 -0.01730000 -0.05340000 0.00470000 -0.05340000 0.03800000 0.00580000 0.00470000 0.00580000 -0.02070000 0.00000000 -0.07551900 0.00820244 -0.02535222 0.00664680 -0.03910300 -O 0.00000000 1.52800000 8.50600000 0.11500000 -0.04930000 0.04230000 -0.04930000 -0.09940000 -0.02360000 0.04230000 -0.02360000 -0.01560000 -0.00000000 -0.06972073 -0.03337544 -0.01910602 0.05982123 0.15160369 -Ti 0.00000000 0.00000000 7.23700000 0.12130000 0.00770000 -0.02780000 0.00770000 0.11310000 -0.01550000 -0.02780000 -0.01550000 -0.23440000 0.00000000 0.01088944 -0.02192031 -0.28708020 -0.03931514 0.00579828 -Ti 2.95500000 1.70600000 12.06200000 0.06850000 0.00170000 -0.00040000 0.00170000 0.15800000 0.07420000 -0.00040000 0.07420000 -0.22650000 0.00000000 0.00240416 0.10493465 -0.27740471 -0.00056569 -0.06328606 -Ti 0.00000000 3.41200000 2.41200000 0.11410000 0.02350000 -0.06950000 0.02350000 0.13930000 0.03440000 -0.06950000 0.03440000 -0.25340000 0.00000000 0.03323402 0.04864895 -0.31035035 -0.09828784 -0.01781909 -Ti -1.47700000 2.55900000 0.00000000 -0.07950000 -0.23590000 0.02490000 -0.23590000 -0.03130000 -0.07880000 0.02490000 -0.07880000 0.11080000 0.00000000 -0.33361298 -0.11144003 0.13570173 0.03521392 -0.03408255 -Ti 1.47700000 2.55900000 0.00000000 -0.07700000 0.23690000 -0.02620000 0.23690000 -0.03580000 -0.08190000 -0.02620000 -0.08190000 0.11280000 -0.00000000 0.33502719 -0.11582409 0.13815122 -0.03705240 -0.02913280 -Ti 0.00000000 1.70600000 4.82500000 0.17880000 -0.08960000 0.06410000 -0.08960000 -0.29170000 0.00520000 0.06410000 0.00520000 0.11290000 0.00000000 -0.12671354 0.00735391 0.13827370 0.09065109 0.33269374 -Ti -1.47700000 4.26500000 4.82500000 -0.25380000 0.15710000 0.03800000 0.15710000 0.13950000 0.05520000 0.03800000 0.05520000 0.11430000 0.00000000 0.22217295 0.07806459 0.13998834 0.05374012 -0.27810510 -Ti 2.95500000 3.41200000 9.65000000 0.05240000 0.00560000 -0.01530000 0.00560000 -0.07320000 0.12950000 -0.01530000 0.12950000 0.02090000 -0.00005774 0.00791960 0.18314066 0.02555634 -0.02163747 0.08881261 -Ti 1.47700000 0.85300000 9.65000000 -0.03370000 -0.04520000 -0.09410000 -0.04520000 0.01840000 -0.05300000 -0.09410000 -0.05300000 0.01530000 0.00000000 -0.06392245 -0.07495332 0.01873860 -0.13307750 -0.03684026 -Ti 4.43200000 0.85300000 9.65000000 -0.03740000 0.05880000 0.10420000 0.05880000 0.01800000 -0.07660000 0.10420000 -0.07660000 0.01940000 0.00000000 0.08315576 -0.10832876 0.02376005 0.14736105 -0.03917372 -42 -Lattice="5.9093 0.0 0.0 -2.95465 5.117604 0.0 0.0 0.0 14.4747" Properties=species:S:1:pos:R:3:efg:R:9:efg_L0:R:1:efg_L2:R:5 pbc="T T T" -Li 0.00000000 0.00000000 1.80900000 0.00220000 -0.00030000 0.00010000 -0.00030000 0.00880000 -0.00130000 0.00010000 -0.00130000 -0.01100000 0.00000000 -0.00042426 -0.00183848 -0.01347219 0.00014142 -0.00466690 -Li 0.00000000 0.00000000 12.66500000 -0.00280000 0.00000000 -0.00000000 0.00000000 0.00270000 -0.00120000 -0.00000000 -0.00120000 0.00010000 -0.00000000 0.00000000 -0.00169706 0.00012247 0.00000000 -0.00388909 -Li 2.95500000 1.70600000 6.63400000 0.00130000 0.00240000 0.00110000 0.00240000 -0.00140000 0.00060000 0.00110000 0.00060000 0.00010000 -0.00000000 0.00339411 0.00084853 0.00012247 0.00155563 0.00190919 -Li 2.95500000 1.70600000 3.01600000 0.00750000 0.00270000 0.00120000 0.00270000 0.00360000 0.00060000 0.00120000 0.00060000 -0.01100000 -0.00005774 0.00381838 0.00084853 -0.01351302 0.00169706 0.00275772 -Li 0.00000000 3.41200000 11.45900000 0.01240000 0.00000000 -0.00000000 0.00000000 0.01090000 0.00030000 -0.00000000 0.00030000 -0.02330000 0.00000000 0.00000000 0.00042426 -0.02853656 0.00000000 0.00106066 -Li 0.00000000 3.41200000 7.84000000 0.01120000 -0.00070000 -0.00030000 -0.00070000 0.01210000 -0.00010000 -0.00030000 -0.00010000 -0.02330000 0.00000000 -0.00098995 -0.00014142 -0.02853656 -0.00042426 -0.00063640 -Li 2.95500000 0.00000000 0.00000000 0.00540000 -0.00010000 0.00000000 -0.00010000 0.00580000 -0.00110000 0.00000000 -0.00110000 -0.01110000 -0.00005774 -0.00014142 -0.00155563 -0.01363549 0.00000000 -0.00028284 -Li 1.47700000 4.26500000 4.82500000 0.00580000 0.00010000 0.00100000 0.00010000 0.00540000 0.00060000 0.00100000 0.00060000 -0.01110000 -0.00005774 0.00014142 0.00084853 -0.01363549 0.00141421 0.00028284 -O 0.00000000 0.00000000 3.80700000 0.06960000 0.02760000 0.06340000 0.02760000 0.02540000 0.03140000 0.06340000 0.03140000 -0.09500000 0.00000000 0.03903229 0.04440631 -0.11635076 0.08966114 0.03125412 -O 0.00000000 0.00000000 10.66800000 0.03320000 -0.00070000 0.00110000 -0.00070000 0.03590000 -0.00410000 0.00110000 -0.00410000 -0.06910000 0.00000000 -0.00098995 -0.00579828 -0.08462987 0.00155563 -0.00190919 -O 2.95500000 1.70600000 8.63200000 0.03580000 0.00080000 0.00410000 0.00080000 0.03330000 0.00110000 0.00410000 0.00110000 -0.06910000 0.00000000 0.00113137 0.00155563 -0.08462987 0.00579828 0.00176777 -O 2.95500000 1.70600000 1.01800000 0.01260000 -0.00540000 0.00450000 -0.00540000 0.08240000 -0.07060000 0.00450000 -0.07060000 -0.09500000 0.00000000 -0.00763675 -0.09984348 -0.11635076 0.00636396 -0.04935605 -O 0.00000000 3.41200000 13.45700000 0.12430000 0.00800000 0.01070000 0.00800000 -0.06140000 -0.12620000 0.01070000 -0.12620000 -0.06280000 -0.00005774 0.01131371 -0.17847375 -0.07695480 0.01513209 0.13130973 -O 0.00000000 3.41200000 5.84300000 -0.02190000 -0.07640000 0.11460000 -0.07640000 0.08480000 0.05380000 0.11460000 0.05380000 -0.06280000 -0.00005774 -0.10804592 0.07608469 -0.07695480 0.16206887 -0.07544829 -O -1.32400000 4.17600000 1.14400000 0.00090000 0.03300000 0.10590000 0.03300000 0.04060000 -0.02200000 0.10590000 -0.02200000 -0.04150000 0.00000000 0.04666905 -0.03111270 -0.05082691 0.14976522 -0.02807214 -O 0.00000000 1.88300000 1.14400000 0.04800000 0.00270000 -0.01790000 0.00270000 -0.09840000 0.04810000 -0.01790000 0.04810000 0.05040000 -0.00000000 0.00381838 0.06802367 0.06172714 -0.02531442 0.10352043 -O 1.32400000 4.17600000 1.14400000 0.00290000 -0.02460000 -0.05270000 -0.02460000 0.03600000 -0.02930000 -0.05270000 -0.02930000 -0.03900000 0.00005774 -0.03478965 -0.04143646 -0.04772423 -0.07452905 -0.02340523 -O 4.27800000 0.94200000 13.33100000 -0.14650000 -0.05980000 0.13880000 -0.05980000 0.08730000 -0.00050000 0.13880000 -0.00050000 0.05920000 -0.00000000 -0.08456997 -0.00070711 0.07250490 0.19629284 -0.16532157 -O 1.63100000 0.94200000 13.33100000 -0.14660000 0.06540000 -0.13620000 0.06540000 0.07990000 -0.00180000 -0.13620000 -0.00180000 0.06660000 0.00005774 0.09248957 -0.00254558 0.08160883 -0.19261589 -0.16015969 -O 2.95500000 3.23400000 13.33100000 0.00750000 -0.00120000 -0.00050000 -0.00120000 -0.08250000 0.07590000 -0.00050000 0.07590000 0.07500000 0.00000000 -0.00169706 0.10733881 0.09185587 -0.00070711 0.06363961 -O 4.58600000 0.76400000 5.96800000 0.08070000 0.07130000 0.06980000 0.07130000 -0.13990000 -0.12000000 0.06980000 -0.12000000 0.05920000 -0.00000000 0.10083343 -0.16970563 0.07250490 0.09871211 0.15598776 -O 2.95500000 3.58900000 5.96800000 -0.03330000 0.13080000 -0.06650000 0.13080000 -0.03330000 0.11880000 -0.06650000 0.11880000 0.06660000 -0.00000000 0.18497913 0.16800857 0.08156801 -0.09404520 0.00000000 -O 1.32400000 0.76400000 5.96800000 -0.05890000 -0.03960000 -0.06600000 -0.03960000 -0.01600000 -0.03760000 -0.06600000 -0.03760000 0.07500000 -0.00005774 -0.05600286 -0.05317443 0.09181504 -0.09333810 -0.03033488 -O 1.32400000 2.64800000 3.68100000 0.00210000 0.03370000 0.07200000 0.03370000 0.03940000 -0.08070000 0.07200000 -0.08070000 -0.04150000 0.00000000 0.04765900 -0.11412703 -0.05082691 0.10182338 -0.02637508 -O -1.32400000 2.64800000 3.68100000 -0.06420000 -0.06210000 -0.05060000 -0.06210000 0.01370000 -0.00860000 -0.05060000 -0.00860000 0.05040000 0.00005774 -0.08782266 -0.01216224 0.06176797 -0.07155921 -0.05508362 -O 0.00000000 4.94000000 3.68100000 0.04900000 0.00210000 -0.00100000 0.00210000 -0.01000000 0.06030000 -0.00100000 0.06030000 -0.03900000 0.00000000 0.00296985 0.08527708 -0.04776505 -0.00141421 0.04171930 -O 1.63100000 2.47000000 10.79300000 -0.00840000 0.03020000 0.00200000 0.03020000 0.04270000 0.01720000 0.00200000 0.01720000 -0.03430000 0.00000000 0.04270925 0.02432447 -0.04200875 0.00282843 -0.03613316 -O 2.95500000 0.17700000 10.79300000 0.05480000 -0.00280000 -0.00260000 -0.00280000 -0.01500000 -0.00150000 -0.00260000 -0.00150000 -0.03990000 0.00005774 -0.00395980 -0.00212132 -0.04882650 -0.00367696 0.04935605 -O 4.27800000 2.47000000 10.79300000 -0.00820000 -0.03260000 -0.00420000 -0.03260000 0.03930000 0.01890000 -0.00420000 0.01890000 -0.03110000 0.00000000 -0.04610336 0.02672864 -0.03808957 -0.00593970 -0.03358757 -O -1.63100000 4.35300000 8.50600000 0.00370000 0.03720000 -0.01390000 0.03720000 0.03060000 -0.01030000 -0.01390000 -0.01030000 -0.03430000 0.00000000 0.05260874 -0.01456640 -0.04200875 -0.01965757 -0.01902117 -O 1.63100000 4.35300000 8.50600000 0.00490000 -0.03160000 -0.00000000 -0.03160000 0.03490000 0.00300000 -0.00000000 0.00300000 -0.03990000 0.00005774 -0.04468915 0.00424264 -0.04882650 0.00000000 -0.02121320 -O 0.00000000 1.52800000 8.50600000 0.05570000 0.00420000 -0.01850000 0.00420000 -0.02460000 -0.00580000 -0.01850000 -0.00580000 -0.03110000 0.00000000 0.00593970 -0.00820244 -0.03808957 -0.02616295 0.05678067 -Ti 0.00000000 0.00000000 7.23700000 0.13420000 0.03990000 -0.06390000 0.03990000 0.08870000 -0.03650000 -0.06390000 -0.03650000 -0.22290000 0.00000000 0.05642712 -0.05161880 -0.27299563 -0.09036825 0.03217336 -Ti 2.95500000 1.70600000 12.06200000 0.06550000 0.00020000 -0.00030000 0.00020000 0.15730000 0.07350000 -0.00030000 0.07350000 -0.22290000 0.00005774 0.00028284 0.10394470 -0.27295481 -0.00042426 -0.06491240 -Ti 0.00000000 3.41200000 2.41200000 0.11380000 0.02260000 -0.06520000 0.02260000 0.13990000 0.03760000 -0.06520000 0.03760000 -0.25360000 -0.00005774 0.03196123 0.05317443 -0.31063612 -0.09220672 -0.01845549 -Ti -1.47700000 2.55900000 0.00000000 -0.07950000 -0.23640000 0.02580000 -0.23640000 -0.03160000 -0.07830000 0.02580000 -0.07830000 0.11110000 -0.00000000 -0.33432009 -0.11073292 0.13606916 0.03648671 -0.03387041 -Ti 1.47700000 2.55900000 0.00000000 -0.07730000 0.23740000 -0.02720000 0.23740000 -0.03590000 -0.08120000 -0.02720000 -0.08120000 0.11320000 -0.00000000 0.33573430 -0.11483414 0.13864112 -0.03846661 -0.02927422 -Ti 0.00000000 1.70600000 4.82500000 0.16120000 -0.09750000 0.08070000 -0.09750000 -0.27230000 0.01680000 0.08070000 0.01680000 0.11110000 -0.00000000 -0.13788582 0.02375879 0.13606916 0.11412703 0.30653079 -Ti -1.47700000 4.26500000 4.82500000 -0.25180000 0.13660000 0.05670000 0.13660000 0.13870000 0.06410000 0.05670000 0.06410000 0.11320000 -0.00005774 0.19318157 0.09065109 0.13860029 0.08018591 -0.27612520 -Ti 2.95500000 3.41200000 9.65000000 0.05700000 -0.00020000 -0.00130000 -0.00020000 -0.06260000 0.08400000 -0.00130000 0.08400000 0.00560000 0.00000000 -0.00028284 0.11879394 0.00685857 -0.00183848 0.08456997 -Ti 1.47700000 0.85300000 9.65000000 -0.03250000 -0.05190000 -0.07340000 -0.05190000 0.02690000 -0.04090000 -0.07340000 -0.04090000 0.00560000 -0.00000000 -0.07339768 -0.05784133 0.00685857 -0.10380328 -0.04200214 -Ti 4.43200000 0.85300000 9.65000000 -0.03240000 0.05320000 0.07190000 0.05320000 0.02900000 -0.04150000 0.07190000 -0.04150000 0.00340000 -0.00000000 0.07523616 -0.05868986 0.00416413 0.10168196 -0.04341636 diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame copy.xyz b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame copy.xyz deleted file mode 100644 index 2fd5805d2..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame copy.xyz +++ /dev/null @@ -1,4 +0,0 @@ -2 -Properties=species:S:1:pos:R:3 pbc="F F F" -H -0.52638300 -0.76932700 -0.02936600 -H -0.52638300 0.76932700 -0.02936600 diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame.xyz b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame.xyz deleted file mode 100644 index 1fa42c1e3..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/frame.xyz +++ /dev/null @@ -1,5 +0,0 @@ -3 -Properties=species:S:1:pos:R:3 pbc="F F F" -O 0.06633400 0.00000000 0.00370100 -H -0.52638300 -0.76932700 -0.02936600 -H -0.52638300 0.76932700 -0.02936600 diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/lsoap_tutorial.ipynb b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/lsoap_tutorial.ipynb deleted file mode 100644 index ee869f1ce..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/lsoap_tutorial.ipynb +++ /dev/null @@ -1,452 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Installation\n", - "\n", - "In a new conda environment, install the following packages in the following\n", - "order:\n", - "\n", - "1. `pip install git+https://github.com/luthaf/rascaline.git@clebsch_gordan`\n", - "2. `pip install git+https://github.com/lab-cosmo/metatensor.git`\n", - "3. Optional but nice: `pip install chemiscope` (allows you to visualize the\n", - " dataset)\n", - "\n", - "\n", - "Also required: `ase` and `numpy`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "\n", - "import os\n", - "import ase.io\n", - "import numpy as np\n", - "\n", - "import chemiscope\n", - "import metatensor\n", - "from metatensor import Labels, TensorBlock, TensorMap\n", - "\n", - "import rascaline\n", - "import clebsch_gordan\n", - "# from rascaline.utils import clebsch_gordan" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Read frames and visualize with `chemiscope`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "frames = ase.io.read(\"combined_magres_spherical.xyz\", \":\")\n", - "chemiscope.show(frames, mode=\"structure\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Convert target property to `metatensor` format" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# We will create a TensorMap for each frame, then combine them into a single\n", - "# TensorMap at the end\n", - "structure_tms = []\n", - "for frame_i, frame in enumerate(frames):\n", - " # Get the number of atoms in the frame\n", - " n_atoms = frame.get_global_number_of_atoms()\n", - "\n", - " # Store the target data by l value and chemical species (i.e. atomic number)\n", - " data_dict = {}\n", - " for atom_i, atomic_number in enumerate(frame.get_atomic_numbers()):\n", - " for l, data in zip(\n", - " [0, 2], [frames[0].arrays[\"efg_L0\"], frames[0].arrays[\"efg_L2\"]]\n", - " ):\n", - " key = (l, atomic_number)\n", - " data_arr = data[atom_i]\n", - " if isinstance(data_arr, float):\n", - " data_arr = np.array([data_arr])\n", - " # Store the data array\n", - " if data_dict.get(key) is None:\n", - " data_dict[key] = {atom_i: data_arr}\n", - " else:\n", - " data_dict[key][atom_i] = data_arr\n", - "\n", - " # Build the keys of the resulting TensorMap\n", - " keys = Labels(\n", - " names=[\"spherical_harmonics_l\", \"species_center\"],\n", - " values=np.array([[l, species_center] for l, species_center in data_dict.keys()]),\n", - " )\n", - "\n", - " # Construct the TensorMap blocks for each of these keys\n", - " blocks = []\n", - " for l, species_center in keys.values:\n", - " # Retrive the raw block data\n", - " data = data_dict[(l, species_center)]\n", - "\n", - " # Get a list of sorted samples (i.e. atom indices) for the block \n", - " n_atoms_block = len(data)\n", - " ordered_atom_idxs = sorted(data.keys())\n", - "\n", - " # Sort the raw block data\n", - " block_data = np.array([data[atom_i] for atom_i in ordered_atom_idxs]).reshape(\n", - " n_atoms_block, 2 * l + 1, 1\n", - " )\n", - "\n", - " # Construct a TensorBlock, where the raw data is labelled with metadata\n", - " # Note here that we keep track of the structure index - this is\n", - " # important for later when we join the TensorMaps\n", - " block = TensorBlock(\n", - " values=block_data,\n", - " samples=Labels(\n", - " names=[\"structure\", \"center\"],\n", - " values=np.array([[frame_i, atom_i] for atom_i in ordered_atom_idxs]),\n", - " ),\n", - " components=[\n", - " Labels(\n", - " names=[\"spherical_harmonics_m\"],\n", - " values=np.arange(-l, l + 1).reshape(-1, 1),\n", - " )\n", - " ],\n", - " properties=Labels(\n", - " names=[\"efg\"],\n", - " values=np.array([[0]]).reshape(-1, 1),\n", - " ),\n", - " )\n", - " # Store the block\n", - " blocks.append(block)\n", - "\n", - " # Construct a TensorMap for this structure from the keys and blocks\n", - " structure_tms.append(TensorMap(keys=keys, blocks=blocks))\n", - "\n", - "# Now join the stucture-based TensorMaps into a single TensorMap. We want to\n", - "# join along the \"samples\" axis\n", - "efg = metatensor.join(structure_tms, axis=\"samples\", remove_tensor_name=True)\n", - "\n", - "# Save the TensorMap to file\n", - "metatensor.save(\"efg.npz\", efg)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The TensorMap is comprised of 6 blocks, each corresponding to a different\n", - "combination of l channel and chemical species:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "efg" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "efg.keys" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can pick out all the invariant blocks using `TensorMap.blocks()`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "efg.blocks(spherical_harmonics_l=0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or just a single block using `TensorMap.block()`. i.e. for l = 2, titanium:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "block = efg.block(spherical_harmonics_l=2, species_center=22)\n", - "block" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each TensorBlock is a tensor of values, wrapped with metadata. The \"samples\" are\n", - "always the first axis, the \"properties\" always the last axis, and all intermediate\n", - "axes are \"components\". Here the samples track the atom indices and which\n", - "structure they belong to. The components track the symmetry of the target\n", - "property. In this case we're representing the data in the spherical basis, so\n", - "there is a single component axis that tracks the $m$ component of the\n", - "irreducible spherical component (ISC) vector. As we only have a single property\n", - "per atom the properties axis has size one, and is just labelled with \"efg\" here. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "block.values.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The raw data is stored in this case as a numpy array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "type(block.values)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But we can also convert, for instance, to a torch backend. (Commented out in\n", - "case you don't have torch installed)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# import torch\n", - "\n", - "# efg_torch = metatensor.to(efg, backend=\"torch\")\n", - "# type(efg_torch.block(0).values)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Generate $\\lambda$-SOAP descriptor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First we need to generate a $\\nu=1$ order spherical expansion (an\n", - "atom-centered density correlation) using the `SphericalExpansion` calculator in\n", - "rascaline. From there we combine it with itself using Clebsch-Gordan iterations\n", - "to generate the $\\nu=2$ order descriptor, i.e. $\\lambda$-SOAP." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define hyperparameters for generating the rascaline SphericalExpansion\n", - "rascal_hypers = {\n", - " \"cutoff\": 3.0, # Angstrom\n", - " \"max_radial\": 6, # Exclusive\n", - " \"max_angular\": 5, # Inclusive\n", - " \"atomic_gaussian_width\": 0.2,\n", - " \"radial_basis\": {\"Gto\": {}},\n", - " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", - " \"center_atom_weight\": 1.0,\n", - "}\n", - "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", - "nu_1_tensor = calculator.compute(frames)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define target lambda channels - we only want invariant l=0 and l=2 channels to\n", - "# match the target property EFG\n", - "angular_selection = [0, 2]\n", - "\n", - "# Now generate the lambda-SOAP vector\n", - "lsoap = clebsch_gordan.lambda_soap_vector(\n", - " nu_1_tensor=nu_1_tensor,\n", - " angular_selection=angular_selection,\n", - " parity_selection=+1,\n", - ")\n", - "\n", - "# Save\n", - "metatensor.save(\"lsoap.npz\", lsoap)\n", - "\n", - "lsoap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some notes on the argument `angular_cutoff`, which can be set but isn't used\n", - "above. This sets the maximum intermediate value of lambda used when performing\n", - "CG iterations. The maximum value corresponding to non-zero combinations is given\n", - "by `target_body_order * rascal_hypers[\"max_angular\"]`.\n", - "\n", - "`target_body_order` is the target body-order of the descriptor. When using the\n", - "publci function `clebsch_gordan.lambda_soap_vector` as above,\n", - "`target_body_order` is by definition 2 so the specifying this isn't required.\n", - "For generating descriptors of higher body order, using the public function\n", - "`clebsch_gordan.combine_single_center_to_body_order`, this argumetn can be\n", - "specified. \n", - "\n", - "In some cases, particular for combinations to high body order (beyond\n", - "lambda-SOAP), combining at each iteration to the theoretical maximum can lead to\n", - "memory blow-up, so the `angular_cutoff` needs to be tailored in each case.\n", - "Setting this cutoff to less than theoretical maximum will lead to some\n", - "information loss. In the above function call I didn't specify `angular_cutoff`\n", - "so it just takes the maximum by default. \n", - "\n", - "The same logic also applies to the use of `angular_selection` and\n", - "`parity_selection`. Applying selection filters on intermediate iterations (i.e.\n", - "not the final one) can lead to some information loss, but reduce memory\n", - "consumption particularly at high body orders. `angular_cutoff` is essentially a\n", - "way of applying a global maximum cutoff to all iterations, whereas\n", - "`angular_selection` allows control of which specific angular channels are\n", - "constructed at each iteration. In our case, we only perform one iteration to\n", - "form our lambda-SOAP descriptor. As there are techincally no intermediate\n", - "iterations (only the case for num. iterations > 1), applying angular and parity\n", - "selections on the final (and only) iteration results in no information loss.\n", - "\n", - "Inspect the $\\lambda$-SOAP descriptor:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lsoap.block(spherical_harmonics_l=2, species_center=22)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The samples and components metadata are equivalent when compared to\n", - "the corresponding block of the EFG tensor, but the properties aren't, as the\n", - "relationship from descriptor properties -> target properties is the thing we wa\n", - "machine learn.\n", - "\n", - "It is important that all other metadata, except for the properties of each\n", - "block, agrees. More specifically, there should be a one-to-one mapping of\n", - "blocks, indexed by the same set of keys. For each of these blocks, the samples\n", - "and components should be the same and in the same order.\n", - "\n", - "This can be checked with the following function in `metatensor`. Here we toggle a\n", - "check for samples and components:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "assert metatensor.equal_metadata(lsoap, efg, check=[\"samples\", \"components\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But checking for properties too fails the test, as expected:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "assert metatensor.equal_metadata(lsoap, efg, check=[\"samples\", \"components\", \"properties\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are loads of other operations and convenience functions for checking and\n", - "manipulating both the data and metadata of TensorMaps. Check out the\n", - "[docs](https://lab-cosmo.github.io/metatensor/latest/) for more info! If there's\n", - "an operation we don't have but you think would be useful, please [open an\n", - "issue](https://github.com/lab-cosmo/metatensor/issues/new/choose) :)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "rho", - "language": "python", - "name": "python3" - }, - "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.10.9" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py deleted file mode 100644 index f94c6b1a0..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/old_clebsch_gordan.py +++ /dev/null @@ -1,760 +0,0 @@ -""" -Module to compute Clebsh-Gordan coefficients and perform CG iterations. Also -contains a wrapper function for computing lambda-SOAP. - -Note: this is legacy code and only used as reference. -""" - -import re -from typing import Optional, Sequence, Union - -import metatensor -import numpy as np -import wigners -from metatensor import Labels, TensorBlock, TensorMap - -import rascaline - - -class ClebschGordanReal: - """ - Class for generating CG coefficients. - """ - - def __init__(self, l_max): - self._l_max = l_max - self._cg = {} - - # real-to-complex and complex-to-real transformations as matrices - r2c = {} - c2r = {} - for L in range(0, self._l_max + 1): - r2c[L] = _real2complex(L) - c2r[L] = np.conjugate(r2c[L]).T - - for l1 in range(self._l_max + 1): - for l2 in range(self._l_max + 1): - for L in range( - max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1 - ): - complex_cg = _complex_clebsch_gordan_matrix(l1, l2, L) - - real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( - complex_cg.shape - ) - - real_cg = real_cg.swapaxes(0, 1) - real_cg = (r2c[l2].T @ real_cg.reshape(2 * l2 + 1, -1)).reshape( - real_cg.shape - ) - real_cg = real_cg.swapaxes(0, 1) - - real_cg = real_cg @ c2r[L].T - - if (l1 + l2 + L) % 2 == 0: - rcg = np.real(real_cg) - else: - rcg = np.imag(real_cg) - - new_cg = [] - for M in range(2 * L + 1): - cg_nonzero = np.where(np.abs(rcg[:, :, M]) > 1e-15) - cg_M = np.zeros( - len(cg_nonzero[0]), - dtype=[("m1", ">i4"), ("m2", ">i4"), ("cg", ">f8")], - ) - cg_M["m1"] = cg_nonzero[0] - cg_M["m2"] = cg_nonzero[1] - cg_M["cg"] = rcg[cg_nonzero[0], cg_nonzero[1], M] - new_cg.append(cg_M) - - self._cg[(l1, l2, L)] = new_cg - - def combine(self, rho1, rho2, L): - # automatically infer l1 and l2 from the size of the coefficients vectors - l1 = (rho1.shape[1] - 1) // 2 - l2 = (rho2.shape[1] - 1) // 2 - if L > self._l_max or l1 > self._l_max or l2 > self._l_max: - raise ValueError("Requested CG entry has not been precomputed") - - n_items = rho1.shape[0] - n_features = rho1.shape[2] - if rho1.shape[0] != rho2.shape[0] or rho1.shape[2] != rho2.shape[2]: - raise IndexError("Cannot combine differently-shaped feature blocks") - - rho = np.zeros((n_items, 2 * L + 1, n_features)) - if (l1, l2, L) in self._cg: - for M in range(2 * L + 1): - for m1, m2, cg in self._cg[(l1, l2, L)][M]: - rho[:, M] += rho1[:, m1, :] * rho2[:, m2, :] * cg - - return rho - - def combine_einsum(self, rho1, rho2, L, combination_string): - # automatically infer l1 and l2 from the size of the coefficients vectors - l1 = (rho1.shape[1] - 1) // 2 - l2 = (rho2.shape[1] - 1) // 2 - if L > self._l_max or l1 > self._l_max or l2 > self._l_max: - raise ValueError( - "Requested CG entry ", (l1, l2, L), " has not been precomputed" - ) - - n_items = rho1.shape[0] - if rho1.shape[0] != rho2.shape[0]: - raise IndexError( - "Cannot combine feature blocks with different number of items" - ) - - # infers the shape of the output using the einsum internals - features = np.einsum(combination_string, rho1[:, 0, ...], rho2[:, 0, ...]).shape - rho = np.zeros((n_items, 2 * L + 1) + features[1:]) - - if (l1, l2, L) in self._cg: - for M in range(2 * L + 1): - for m1, m2, cg in self._cg[(l1, l2, L)][M]: - rho[:, M, ...] += np.einsum( - combination_string, rho1[:, m1, ...], rho2[:, m2, ...] * cg - ) - - return rho - - def couple(self, decoupled, iterate=0): - """ - Goes from an uncoupled product basis to a coupled basis. A - (2l1+1)x(2l2+1) matrix transforming like the outer product of Y^m1_l1 - Y^m2_l2 can be rewritten as a list of coupled vectors, each transforming - like a Y^L irrep. - - The process can be iterated: a D dimensional array that is the product - of D Y^m_l can be turned into a set of multiple terms transforming as a - single Y^M_L. - - decoupled: array or dict - (...)x(2l1+1)x(2l2+1) array containing coefficients that transform - like products of Y^l1 and Y^l2 harmonics. can also be called on a - array of higher dimensionality, in which case the result will - contain matrices of entries. If the further index also correspond to - spherical harmonics, the process can be iterated, and couple() can - be called onto its output, in which case the decoupling is applied - to each entry. - - iterate: int - calls couple iteratively the given number of times. equivalent to - couple(couple(... couple(decoupled))) - - Returns: - -------- - A dictionary tracking the nature of the coupled objects. When called one - time, it returns a dictionary containing (l1, l2) [the coefficients of - the parent Ylm] which in turns is a dictionary of coupled terms, in the - form L:(...)x(2L+1)x(...) array. When called multiple times, it applies - the coupling to each term, and keeps track of the additional l terms, so - that e.g. when called with iterate=1 the return dictionary contains - terms of the form (l3,l4,l1,l2) : { L: array } - - - Note that this coupling scheme is different from the NICE-coupling where - angular momenta are coupled from left to right as (((l1 l2) l3) l4)... ) - Thus results may differ when combining more than two angular channels. - """ - - coupled = {} - - # when called on a matrix, turns it into a dict form to which we can - # apply the generic algorithm - if not isinstance(decoupled, dict): - l2 = (decoupled.shape[-1] - 1) // 2 - decoupled = {(): {l2: decoupled}} - - # runs over the tuple of (partly) decoupled terms - for ltuple, lcomponents in decoupled.items(): - # each is a list of L terms - for lc in lcomponents.keys(): - # this is the actual matrix-valued coupled term, - # of shape (..., 2l1+1, 2l2+1), transforming as Y^m1_l1 Y^m2_l2 - dec_term = lcomponents[lc] - l1 = (dec_term.shape[-2] - 1) // 2 - l2 = (dec_term.shape[-1] - 1) // 2 - - # there is a certain redundance: the L value is also the last entry - # in ltuple - if lc != l2: - raise ValueError( - "Inconsistent shape for coupled angular momentum block." - ) - - # in the new coupled term, prepend (l1,l2) to the existing label - coupled[(l1, l2) + ltuple] = {} - for L in range( - max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1 - ): - Lterm = np.zeros(shape=dec_term.shape[:-2] + (2 * L + 1,)) - for M in range(2 * L + 1): - for m1, m2, cg in self._cg[(l1, l2, L)][M]: - Lterm[..., M] += dec_term[..., m1, m2] * cg - coupled[(l1, l2) + ltuple][L] = Lterm - - # repeat if required - if iterate > 0: - coupled = self.couple(coupled, iterate - 1) - return coupled - - def decouple(self, coupled, iterate=0): - """ - Undoes the transformation enacted by couple. - """ - - decoupled = {} - # applies the decoupling to each entry in the dictionary - for ltuple, lcomponents in coupled.items(): - # the initial pair in the key indicates the decoupled terms that generated - # the L entries - l1, l2 = ltuple[:2] - - # shape of the coupled matrix (last entry is the 2L+1 M terms) - shape = next(iter(lcomponents.values())).shape[:-1] - - dec_term = np.zeros( - shape - + ( - 2 * l1 + 1, - 2 * l2 + 1, - ) - ) - for L in range(max(l1, l2) - min(l1, l2), min(self._l_max, (l1 + l2)) + 1): - # supports missing L components, e.g. if they are zero because of symmetry - if not L in lcomponents: - continue - for M in range(2 * L + 1): - for m1, m2, cg in self._cg[(l1, l2, L)][M]: - dec_term[..., m1, m2] += cg * lcomponents[L][..., M] - # stores the result with a key that drops the l's we have just decoupled - if not ltuple[2:] in decoupled: - decoupled[ltuple[2:]] = {} - decoupled[ltuple[2:]][l2] = dec_term - - # rinse, repeat - if iterate > 0: - decoupled = self.decouple(decoupled, iterate - 1) - - # if we got a fully decoupled state, just return an array - if ltuple[2:] == (): - decoupled = next(iter(decoupled[()].values())) - return decoupled - - -# ===== helper functions for ClebschGordanReal - - -def _real2complex(L): - """ - Computes a matrix that can be used to convert from real to complex-valued - spherical harmonics(coefficients) of order L. - - It's meant to be applied to the left, ``real2complex @ [-L..L]``. - """ - result = np.zeros((2 * L + 1, 2 * L + 1), dtype=np.complex128) - - i_sqrt_2 = 1.0 / np.sqrt(2) - - for m in range(-L, L + 1): - if m < 0: - result[L - m, L + m] = i_sqrt_2 * 1j * (-1) ** m - result[L + m, L + m] = -i_sqrt_2 * 1j - - if m == 0: - result[L, L] = 1.0 - - if m > 0: - result[L + m, L + m] = i_sqrt_2 * (-1) ** m - result[L - m, L + m] = i_sqrt_2 - - return result - - -def _complex_clebsch_gordan_matrix(l1, l2, L): - if np.abs(l1 - l2) > L or np.abs(l1 + l2) < L: - return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * L + 1), dtype=np.double) - else: - return wigners.clebsch_gordan_array(l1, l2, L) - - -# ======== Fxns used to perform CG iterations - - -def acdc_standardize_keys(descriptor): - """ - Standardize the naming scheme of density expansion coefficient blocks - (nu=1) - """ - - key_names = descriptor.keys.names - if not "spherical_harmonics_l" in key_names: - raise ValueError( - "Descriptor missing spherical harmonics channel key `spherical_harmonics_l`" - ) - blocks = [] - keys = [] - for key, block in descriptor.items(): - key = tuple(key) - if not "inversion_sigma" in key_names: - key = (1,) + key - if not "order_nu" in key_names: - key = (1,) + key - keys.append(key) - property_names = _remove_suffix(block.properties.names, "_1") - blocks.append( - TensorBlock( - values=block.values, - samples=block.samples, - components=block.components, - properties=Labels(property_names, block.properties.values), - ) - ) - - if not "inversion_sigma" in key_names: - key_names = ["inversion_sigma"] + key_names - if not "order_nu" in key_names: - key_names = ["order_nu"] + key_names - - return TensorMap( - keys=Labels(names=key_names, values=np.asarray(keys, dtype=np.int32)), - blocks=blocks, - ) - - -def cg_combine( - x_a, - x_b, - feature_names=None, - clebsch_gordan=None, - lcut=None, - other_keys_match=None, -): - """ - Performs a CG product of two sets of equivariants. Only requirement is that - sparse indices are labeled as ("inversion_sigma", "spherical_harmonics_l", - "order_nu"). The automatically-determined naming of output features can be - overridden by giving a list of "feature_names". By defaults, all other key - labels are combined in an "outer product" mode, i.e. if there is a key-side - neighbor_species in both x_a and x_b, the returned keys will have two - neighbor_species labels, corresponding to the parent features. By providing - a list `other_keys_match` of keys that should match, these are not - outer-producted, but combined together. for instance, passing `["species - center"]` means that the keys with the same species center will be combined - together, but yield a single key with the same species_center in the - results. - """ - - # determines the cutoff in the new features - lmax_a = max(x_a.keys["spherical_harmonics_l"]) - lmax_b = max(x_b.keys["spherical_harmonics_l"]) - if lcut is None: - lcut = lmax_a + lmax_b - - # creates a CG object, if needed - if clebsch_gordan is None: - clebsch_gordan = ClebschGordanReal(lcut) - - other_keys_a = tuple( - name - for name in x_a.keys.names - if name not in ["spherical_harmonics_l", "order_nu", "inversion_sigma"] - ) - other_keys_b = tuple( - name - for name in x_b.keys.names - if name not in ["spherical_harmonics_l", "order_nu", "inversion_sigma"] - ) - - if other_keys_match is None: - OTHER_KEYS = [k + "_a" for k in other_keys_a] + [k + "_b" for k in other_keys_b] - else: - OTHER_KEYS = ( - other_keys_match - + [ - k + ("_a" if k in other_keys_b else "") - for k in other_keys_a - if k not in other_keys_match - ] - + [ - k + ("_b" if k in other_keys_a else "") - for k in other_keys_b - if k not in other_keys_match - ] - ) - - # we assume grad components are all the same - if x_a.block(0).has_gradient("positions"): - grad_components = x_a.block(0).gradient("positions").components - else: - grad_components = None - - # automatic generation of the output features names - # "x1 x2 x3 ; x1 x2 -> x1_a x2_a x3_a k_nu x1_b x2_b l_nu" - if feature_names is None: - NU = x_a.keys[0]["order_nu"] + x_b.keys[0]["order_nu"] - feature_names = ( - tuple(n + "_a" for n in x_a.block(0).properties.names) - + ("k_" + str(NU),) - + tuple(n + "_b" for n in x_b.block(0).properties.names) - + ("l_" + str(NU),) - ) - - X_idx = {} - X_blocks = {} - X_samples = {} - X_grad_samples = {} - X_grads = {} - - # loops over sparse blocks of x_a - for index_a, block_a in x_a.items(): - lam_a = index_a["spherical_harmonics_l"] - sigma_a = index_a["inversion_sigma"] - order_a = index_a["order_nu"] - properties_a = ( - block_a.properties - ) # pre-extract this block as accessing a c property has a non-zero cost - samples_a = block_a.samples - - # and x_b - for index_b, block_b in x_b.items(): - lam_b = index_b["spherical_harmonics_l"] - sigma_b = index_b["inversion_sigma"] - order_b = index_b["order_nu"] - properties_b = block_b.properties - samples_b = block_b.samples - - if other_keys_match is None: - OTHERS = tuple(index_a[name] for name in other_keys_a) + tuple( - index_b[name] for name in other_keys_b - ) - else: - OTHERS = tuple( - index_a[k] for k in other_keys_match if index_a[k] == index_b[k] - ) - # skip combinations without matching key - if len(OTHERS) < len(other_keys_match): - continue - # adds non-matching keys to build outer product - OTHERS = OTHERS + tuple( - index_a[k] for k in other_keys_a if k not in other_keys_match - ) - OTHERS = OTHERS + tuple( - index_b[k] for k in other_keys_b if k not in other_keys_match - ) - - if "neighbor" in samples_b.names and "neighbor" not in samples_a.names: - # we hard-code a combination method where b can be a pair descriptor. this needs some work to be general and robust - # note also that this assumes that structure, center are ordered in the same way in the centred and neighbor descriptors - neighbor_slice = [] - smp_a, smp_b = 0, 0 - while smp_b < samples_b.shape[0]: - if samples_b[smp_b][["structure", "center"]] != samples_a[smp_a]: - smp_a += 1 - neighbor_slice.append(smp_a) - smp_b += 1 - neighbor_slice = np.asarray(neighbor_slice) - else: - neighbor_slice = slice(None) - - # determines the properties that are in the select list - sel_feats = [] - sel_idx = [] - sel_feats = ( - np.indices((len(properties_a), len(properties_b))).reshape(2, -1).T - ) - - prop_ids_a = [] - prop_ids_b = [] - for n_a, f_a in enumerate(properties_a): - prop_ids_a.append(tuple(f_a) + (lam_a,)) - for n_b, f_b in enumerate(properties_b): - prop_ids_b.append(tuple(f_b) + (lam_b,)) - prop_ids_a = np.asarray(prop_ids_a) - prop_ids_b = np.asarray(prop_ids_b) - sel_idx = np.hstack( - [prop_ids_a[sel_feats[:, 0]], prop_ids_b[sel_feats[:, 1]]] - ) - if len(sel_feats) == 0: - continue - # loops over all permissible output blocks. note that blocks will - # be filled from different la, lb - for L in range(np.abs(lam_a - lam_b), 1 + min(lam_a + lam_b, lcut)): - # determines parity of the block - S = sigma_a * sigma_b * (-1) ** (lam_a + lam_b + L) - NU = order_a + order_b - KEY = ( - NU, - S, - L, - ) + OTHERS - if not KEY in X_idx: - X_idx[KEY] = [] - X_blocks[KEY] = [] - X_samples[KEY] = block_b.samples - if grad_components is not None: - X_grads[KEY] = [] - X_grad_samples[KEY] = block_b.gradient("positions").samples - - # builds all products in one go - one_shot_blocks = clebsch_gordan.combine_einsum( - block_a.values[neighbor_slice][:, :, sel_feats[:, 0]], - block_b.values[:, :, sel_feats[:, 1]], - L, - combination_string="iq,iq->iq", - ) - # do gradients, if they are present... - if grad_components is not None: - grad_a = block_a.gradient("positions") - grad_b = block_b.gradient("positions") - grad_a_data = np.swapaxes(grad_a.data, 1, 2) - grad_b_data = np.swapaxes(grad_b.data, 1, 2) - one_shot_grads = clebsch_gordan.combine_einsum( - block_a.values[grad_a.samples["sample"]][ - neighbor_slice, :, sel_feats[:, 0] - ], - grad_b_data[..., sel_feats[:, 1]], - L=L, - combination_string="iq,iaq->iaq", - ) + clebsch_gordan.combine_einsum( - block_b.values[grad_b.samples["sample"]][:, :, sel_feats[:, 1]], - grad_a_data[neighbor_slice, ..., sel_feats[:, 0]], - L=L, - combination_string="iq,iaq->iaq", - ) - - # now loop over the selected features to build the blocks - - X_idx[KEY].append(sel_idx) - X_blocks[KEY].append(one_shot_blocks) - if grad_components is not None: - X_grads[KEY].append(one_shot_grads) - - # turns data into sparse storage format (and dumps any empty block in the - # process) - nz_idx = [] - nz_blk = [] - for KEY in X_blocks: - L = KEY[2] - # create blocks - if len(X_blocks[KEY]) == 0: - continue # skips empty blocks - nz_idx.append(KEY) - block_data = np.concatenate(X_blocks[KEY], axis=-1) - sph_components = Labels( - ["spherical_harmonics_m"], - np.asarray(range(-L, L + 1), dtype=np.int32).reshape(-1, 1), - ) - newblock = TensorBlock( - # feature index must be last - values=block_data, - samples=X_samples[KEY], - components=[sph_components], - properties=Labels( - feature_names, np.asarray(np.vstack(X_idx[KEY]), dtype=np.int32) - ), - ) - if grad_components is not None: - grad_data = np.swapaxes(np.concatenate(X_grads[KEY], axis=-1), 2, 1) - newblock.add_gradient( - "positions", - data=grad_data, - samples=X_grad_samples[KEY], - components=[grad_components[0], sph_components], - ) - nz_blk.append(newblock) - X = TensorMap( - Labels( - ["order_nu", "inversion_sigma", "spherical_harmonics_l"] + OTHER_KEYS, - np.asarray(nz_idx, dtype=np.int32), - ), - nz_blk, - ) - return X - - -def cg_increment( - x_nu, - x_1, - clebsch_gordan=None, - lcut=None, - other_keys_match=None, -): - """Specialized version of the CG product to perform iterations with nu=1 features""" - - nu = x_nu.keys["order_nu"][0] - - feature_roots = _remove_suffix(x_1.block(0).properties.names) - - if nu == 1: - feature_names = ( - [root + "_1" for root in feature_roots] - + ["l_1"] - + [root + "_2" for root in feature_roots] - + ["l_2"] - ) - else: - feature_names = ( - [x_nu.block(0).properties.names] - + ["k_" + str(nu + 1)] - + [root + "_" + str(nu + 1) for root in feature_roots] - + ["l_" + str(nu + 1)] - ) - - return cg_combine( - x_nu, - x_1, - feature_names=feature_names, - clebsch_gordan=clebsch_gordan, - lcut=lcut, - other_keys_match=other_keys_match, - ) - - -def _remove_suffix(names, new_suffix=""): - suffix = re.compile("_[0-9]?$") - rname = [] - for name in names: - match = suffix.search(name) - if match is None: - rname.append(name + new_suffix) - else: - rname.append(name[: match.start()] + new_suffix) - return rname - - -def lambda_soap_vector( - frames: list, - rascal_hypers: dict, - lambda_filter: Optional[Union[None, Sequence[int]]] = None, - sigma_filter: Optional[Union[None, Sequence[int]]] = None, - lambda_cut: Optional[int] = None, - selected_samples: Optional[Labels] = None, - neighbor_species: Optional[Sequence[int]] = None, -) -> TensorMap: - """ - Takes a list of frames of ASE loaded frames and a dict of Rascaline - hyperparameters and generates a lambda-SOAP (i.e. nu=2) representation. - - Passing a subset of samples in `selected_samples` can be used to, for - instance, only calculate the features for a subset of the strutcures passed - in `frames`. For instance: `selected_samples = Labels(names=["structure"], - values[4, 5, 6])` will only calculate the lambda-features for structures - indexed by 4, 5, 6. - - :param frames: a list of structures generated by the ase.io function. - :param rascal_hypers: a dict of hyperparameters used to calculate the atom - density correlation calculated with rascaline SphericalExpansion - :param lambda_cut: an int of the maximum lambda value to compute - combinations for. If none, the 'max_angular' value in `rascal_hypers` - will be used instead. - :param selected_samples: a Labels object that defines which samples, as a - subset of the total samples in `frames` (i.e. atomic environments or - structures) to perform the calculation on. - :param neighbor_species: a list of int that correspond to the atomic charges - of all the neighbour species that you want to be in your properties (or - features) dimension. This list may contain charges for atoms that don't - appear in ``frames``, but are included anyway so that the one can - enforce consistent properties dimension size with other lambda-feature - vectors. - :param even_parity_only: a bool that determines whether to only include the - key/block pairs with even parity under rotation, i.e. sigma = +1. - Defaults to false, where both parities are included. - :param save_dir: a str of the absolute path to the directory where the - TensorMap of the calculated lambda-SOAP representation and pickled - ``rascal_hypers`` dict should be written. If none, the TensorMap will not be - saved. - - :return: a TensorMap of the lambda-SOAP feature vector for the selected - samples of the input frames. - """ - # Generate Rascaline spherical expansion - if lambda_cut is None: - lambda_cut = 2 * rascal_hypers["max_angular"] - else: - if lambda_cut > 2 * rascal_hypers["max_angular"]: - raise ValueError( - "As this function generates 2-body features (nu=2), " - "`lambda_cut` cannot be more than 2 x rascal_hypers['max_angular'] " - f"or less than rascal_hypers['max_angular']. Received {lambda_cut}." - ) - # Pre-calculate ClebschGordan coefficients - cg = ClebschGordanReal(l_max=lambda_cut) - - # Generate descriptor via Spherical Expansion - calculator = rascaline.SphericalExpansion(**rascal_hypers) - nu1 = calculator.compute(frames, selected_samples=selected_samples) - - # nu=1 features - nu1 = acdc_standardize_keys(nu1) - - # Move "species_neighbor" sparse keys to properties with enforced atom - # charges if ``neighbor_species`` is specified. This is required as the CG - # iteration code currently does not handle neighbour species padding - # automatically. - keys_to_move = "species_neighbor" - if neighbor_species is not None: - keys_to_move = Labels( - names=(keys_to_move,), - values=np.array(neighbor_species).reshape(-1, 1), - ) - nu1 = nu1.keys_to_properties(keys_to_move=keys_to_move) - - # Combined nu=1 features to generate nu=2 features. lambda-SOAP is defined - # as just the nu=2 features. - lsoap = cg_increment( - nu1, - nu1, - clebsch_gordan=cg, - lcut=lambda_cut, - other_keys_match=["species_center"], - ) - - # Clean the lambda-SOAP TensorMap. Drop the order_nu key name as this is by - # definition 2 for all keys. - lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="order_nu") - - # # Drop all odd parity keys/blocks - # if even_parity_only: - # keys_to_drop = Labels( - # names=lsoap.keys.names, - # values=lsoap.keys.values[lsoap.keys.column("inversion_sigma") == -1], - # ) - # lsoap = metatensor.drop_blocks(lsoap, keys=keys_to_drop) - - # # Drop the inversion_sigma key name as this is now +1 for all blocks - # lsoap = metatensor.remove_dimension(lsoap, axis="keys", name="inversion_sigma") - - # Drop all blocks that don't correspond to the target lambdas - if lambda_filter is not None: - keys_to_drop = Labels( - names=lsoap.keys.names, - values=lsoap.keys.values[ - [ - lam not in lambda_filter - for lam in lsoap.keys.column("spherical_harmonics_l") - ] - ], - ) - lsoap = metatensor.drop_blocks(lsoap, keys=keys_to_drop) - - if sigma_filter is not None: - if len(sigma_filter) < 2: - keys_to_drop = Labels( - names=lsoap.keys.names, - values=lsoap.keys.values[ - [ - s not in sigma_filter - for s in lsoap.keys.column("inversion_sigma") - ] - ], - ) - lsoap = metatensor.drop_blocks(lsoap, keys=keys_to_drop) - - if len(np.unique(lsoap.keys.column("inversion_sigma"))) == 1: - lsoap = metatensor.remove_dimension( - lsoap, axis="keys", name="inversion_sigma" - ) - - return lsoap diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb b/python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb deleted file mode 100644 index 1aa9d85de..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_archive/tmp.ipynb +++ /dev/null @@ -1,806 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "\n", - "import ase.io\n", - "import numpy as np\n", - "\n", - "import rascaline\n", - "import metatensor\n", - "from metatensor import Labels, TensorBlock, TensorMap\n", - "import chemiscope\n", - "\n", - "from rascaline.utils import clebsch_gordan, PowerSpectrum\n", - "# import clebsch_gordan\n", - "# import rotations" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from rascaline.utils import clebsch_gordan, PowerSpectrum\n", - "\n", - "rascal_hypers = {\n", - " \"cutoff\": 3.0, # Angstrom\n", - " \"max_radial\": 2, # Exclusive\n", - " \"max_angular\": 3, # Inclusive\n", - " \"atomic_gaussian_width\": 0.2,\n", - " \"radial_basis\": {\"Gto\": {}},\n", - " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", - " \"center_atom_weight\": 1.0,\n", - "}\n", - "\n", - "frames = ase.io.read(\"frame.xyz\", \":\")\n", - "sphex = rascaline.SphericalExpansion(**rascal_hypers)\n", - "ps = PowerSpectrum(sphex).compute(frames)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import ase.io\n", - "import matplotlib.pyplot as plt\n", - "from rascaline.utils import clebsch_gordan, PowerSpectrum\n", - "\n", - "rascal_hypers = {\n", - " \"cutoff\": 3.0, # Angstrom\n", - " \"max_radial\": 2, # Exclusive\n", - " \"max_angular\": 3, # Inclusive\n", - " \"atomic_gaussian_width\": 0.2,\n", - " \"radial_basis\": {\"Gto\": {}},\n", - " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", - " \"center_atom_weight\": 1.0,\n", - "}\n", - "\n", - "frames = ase.io.read(\"frame.xyz\", \":\") # single water monomer\n", - "\n", - "# PowerSpectrum (python implementation)\n", - "sphex = rascaline.SphericalExpansion(**rascal_hypers)\n", - "ps = PowerSpectrum(sphex).compute(frames)\n", - "\n", - "# LSOAP\n", - "nu_1_tensor = sphex.compute(frames)\n", - "lsoap = clebsch_gordan.lambda_soap_vector(\n", - " nu_1_tensor=nu_1_tensor,\n", - " angular_selection=[0],\n", - ")\n", - "# Some metadata manipulation to get it to match PowerSpectrum\n", - "# ...\n", - "keys = lsoap.keys.remove(name=\"spherical_harmonics_l\")\n", - "lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()])\n", - "\n", - "# Remove components axis\n", - "blocks = []\n", - "for block in lsoap.blocks():\n", - " n_samples, n_props = block.values.shape[0], block.values.shape[2]\n", - " new_props = block.properties\n", - " new_props = new_props.remove(name=\"l1\")\n", - " new_props = new_props.rename(old=\"l2\", new=\"l\")\n", - " blocks.append(\n", - " TensorBlock(\n", - " values=block.values.reshape((n_samples, n_props)),\n", - " samples=block.samples,\n", - " components=[],\n", - " properties=new_props,\n", - " )\n", - " )\n", - "lsoap = TensorMap(keys=lsoap.keys, blocks=blocks)\n", - "\n", - "# Parity plot\n", - "fig, ax = plt.subplots()\n", - "for key in lsoap.keys:\n", - " ax.scatter(ps[key].values, lsoap[key].values)\n", - "ax.axline((0, 0), slope=1)\n", - "ax.axline((0, 0), slope=-1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nu_1_tensor = sphex.compute(frames)\n", - "lsoap = clebsch_gordan.lambda_soap_vector(\n", - " nu_1_tensor=nu_1_tensor,\n", - " angular_selection=[0],\n", - ")\n", - "keys = lsoap.keys.remove(name=\"spherical_harmonics_l\")\n", - "lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()])\n", - "\n", - "# Remove components axis\n", - "blocks = []\n", - "for block in lsoap.blocks():\n", - " n_samples, n_props = block.values.shape[0], block.values.shape[2]\n", - " new_props = block.properties\n", - " new_props = new_props.remove(name=\"l1\")\n", - " new_props = new_props.rename(old=\"l2\", new=\"l\")\n", - " blocks.append(\n", - " TensorBlock(\n", - " values=block.values.reshape((n_samples, n_props)),\n", - " samples=block.samples,\n", - " components=[],\n", - " properties=new_props,\n", - " )\n", - " )\n", - "\n", - "lsoap = TensorMap(keys=lsoap.keys, blocks=blocks)\n", - "lsoap" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# metatensor.equal_metadata(metatensor.sort(lsoap), metatensor.sort(ps))\n", - "assert metatensor.equal_metadata(lsoap, ps)\n", - "assert metatensor.allclose(metatensor.abs(lsoap), metatensor.abs(ps))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "diff = lsoap.block(0).values - ps.block(0).values\n", - "diff" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mask = abs(diff[0]) > 1e-10\n", - "mask" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lsoap.block(0).properties.names" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lsoap.block(0).properties.values[mask]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mask[0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lsoap.block(0).properties" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ps.block(0).values" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "metatensor.subtract(lsoap, ps).block(0).values" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "fig, ax = plt.subplots()\n", - "for key in lsoap.keys:\n", - " ax.scatter(ps[key].values, lsoap[key].values)\n", - " ax.axline((0, 0), slope=1)\n", - " ax.axline((0, 0), slope=-1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate a rascaline SphericalExpansion calculator" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "frames = ase.io.read(\"frame copy.xyz\", \":\")\n", - "# frames = ase.io.read(\"combined_magres_spherical.xyz\", \":1\")\n", - "\n", - "# Define hyperparameters for generating the rascaline SphericalExpansion\n", - "rascal_hypers = {\n", - " \"cutoff\": 3.0, # Angstrom\n", - " \"max_radial\": 4, # Exclusive\n", - " \"max_angular\": 5, # Inclusive\n", - " \"atomic_gaussian_width\": 0.2,\n", - " \"radial_basis\": {\"Gto\": {}},\n", - " \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", - " \"center_atom_weight\": 1.0,\n", - "}\n", - "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", - "nu_1 = calculator.compute(frames)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate output TensorMaps with:\n", - "### a) only metadata and b) actually doing the CG combinations" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit\n", - "nu_3 = clebsch_gordan.combine_single_center_to_body_order_metadata_only(\n", - " nu_1_tensor=nu_1,\n", - " target_body_order=3,\n", - " # angular_cutoff=6,\n", - " # angular_selection=[0, 1, 2],\n", - " # parity_selection=[+1],\n", - ")\n", - "# [np.unique(i[0].column(\"spherical_harmonics_l\")) for i in nu_3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit\n", - "nu_3 = clebsch_gordan.combine_single_center_to_body_order(\n", - " nu_1_tensor=nu_1,\n", - " target_body_order=3,\n", - " # angular_cutoff=6,\n", - " # angular_selection=[0, 1, 2],\n", - " # parity_selection=[+1],\n", - ")\n", - "# [np.unique(i[0].column(\"spherical_harmonics_l\")) for i in nu_3]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Equivariance Test - SO(3) for $\\nu=3$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define target angular channels\n", - "angular_selection = [0, 2]\n", - "\n", - "# Generate Wigner-D matrices, initialized with random angles\n", - "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", - "print(\"Random rotation angles (rad):\", wig.angles)\n", - "\n", - "# Randomly rigidly rotate the frame\n", - "frames_so3 = [rotations.transform_frame_so3(frame, wig.angles) for frame in frames]\n", - "assert not np.allclose(frames[-1].positions, frames_so3[-1].positions)\n", - "\n", - "# Generate nu=3 descriptor for both frames\n", - "print(\"Computing nu = 3 descriptor for unrotated frames...\")\n", - "nu_1 = calculator.compute(frames)\n", - "nu_3 = clebsch_gordan.combine_single_center_to_body_order(\n", - " nu_1_tensor=nu_1,\n", - " target_body_order=3,\n", - " angular_selection=angular_selection,\n", - " parity_selection=[+1],\n", - ")\n", - "print(\"Computing nu = 3 descriptor for rotated frames...\")\n", - "nu_1_rot = calculator.compute(frames_so3)\n", - "nu_3_rot = clebsch_gordan.combine_single_center_to_body_order(\n", - " nu_1_tensor=nu_1_rot,\n", - " target_body_order=3,\n", - " angular_selection=angular_selection,\n", - " parity_selection=[+1],\n", - ")\n", - "\n", - "# Rotate the lambda-SOAP descriptor of the unrotated frame\n", - "nu_3_transf = wig.transform_tensormap_so3(nu_3)\n", - "\n", - "# Check for equivariance!\n", - "assert metatensor.equal_metadata(nu_3_transf, nu_3_rot)\n", - "assert metatensor.allclose(nu_3_transf, nu_3_rot)\n", - "print(\"SO(3) EQUIVARIANT!\")\n", - "\n", - "# chemiscope.show(frames + frames_so3, mode=\"structure\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Equivariance Test - O(3) on $\\nu=2$ (i.e. $\\lambda$-SOAP)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define target lambda channels\n", - "angular_selection = [0, 1, 2, 3, 4, 5]\n", - "\n", - "# Generate Wigner-D matrices, initialized with random angles\n", - "wig = rotations.WignerDReal(lmax=rascal_hypers[\"max_angular\"])\n", - "print(\"Random rotation angles (rad):\", wig.angles)\n", - "\n", - "# Apply an O(3) transformation to the frame\n", - "frames_o3 = [rotations.transform_frame_o3(frame, wig.angles) for frame in frames]\n", - "assert not np.allclose(frames[-1].positions, frames_o3[-1].positions)\n", - "\n", - "# Generate lambda-SOAP for both frames\n", - "print(\"Computing lambda-SOAP descriptor for unrotated frames...\")\n", - "nu_1 = calculator.compute(frames)\n", - "lsoap = clebsch_gordan.lambda_soap_vector(\n", - " nu_1_tensor=nu_1,\n", - " angular_selection=angular_selection,\n", - ")\n", - "print(\"Computing lambda-SOAP descriptor for rotated frames...\")\n", - "nu_1_rot = calculator.compute(frames_o3)\n", - "lsoap_o3 = clebsch_gordan.lambda_soap_vector(\n", - " nu_1_tensor=nu_1_rot,\n", - " angular_selection=angular_selection,\n", - ")\n", - "\n", - "# Apply the O(3) transformation to the TensorMap\n", - "lsoap_transf = wig.transform_tensormap_o3(lsoap)\n", - "\n", - "# Check for equivariance!\n", - "assert metatensor.equal_metadata(lsoap_transf, lsoap_o3)\n", - "assert metatensor.allclose(lsoap_transf, lsoap_o3)\n", - "print(\"O(3) EQUIVARIANT!\")\n", - "\n", - "# chemiscope.show(frames + frames_o3, mode=\"structure\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Old v new lambda-SOAP" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit\n", - "lsoap_old = old_clebsch_gordan.lambda_soap_vector(frames, rascal_hypers, lambda_cut=5)\n", - "\n", - "size = 0\n", - "for block in lsoap_old:\n", - " size += np.prod(block.values.shape)\n", - "print(\"Size:\", size)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit\n", - "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", - "nu_1 = calculator.compute(frames)\n", - "lsoap_new = clebsch_gordan.lambda_soap_vector(nu_1, angular_cutoff=5)\n", - "\n", - "size = 0\n", - "for block in lsoap_new:\n", - " size += np.prod(block.values.shape)\n", - "print(\"Size:\", size)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Dense v Sparse" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Dense\n", - "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", - "nu_1 = calculator.compute(frames[:2])\n", - "nu_2_dense = clebsch_gordan.combine_single_center_to_body_order(\n", - " nu_1,\n", - " target_body_order=3,\n", - " angular_cutoff=5,\n", - " use_sparse=False,\n", - ")\n", - "nu_2_dense" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Sparse\n", - "calculator = rascaline.SphericalExpansion(**rascal_hypers)\n", - "nu_1 = calculator.compute(frames[:2])\n", - "nu_2_sparse = clebsch_gordan.combine_single_center_to_body_order(\n", - " nu_1,\n", - " target_body_order=3,\n", - " angular_cutoff=5,\n", - " use_sparse=True,\n", - ")\n", - "nu_2_sparse" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check sparse == dense\n", - "metatensor.allclose(nu_2_dense, nu_2_sparse)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Test system 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# %%timeit\n", - "\n", - "# use_sparse = False\n", - "\n", - "# tensor_dense = clebsch_gordan.n_body_iteration_single_center(\n", - "# frames,\n", - "# rascal_hypers=rascal_hypers,\n", - "# nu_target=3,\n", - "# lambdas=lambdas,\n", - "# use_sparse=use_sparse,\n", - "# )\n", - "# tensor_dense\n", - "\n", - "# # timeit comes out at about 22 seconds for nu_target = 3" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# %%timeit\n", - "\n", - "# use_sparse = True\n", - "\n", - "# tensor_sparse = clebsch_gordan.n_body_iteration_single_center(\n", - "# frames,\n", - "# rascal_hypers=rascal_hypers,\n", - "# nu_target=3,\n", - "# lambdas=lambdas,\n", - "# use_sparse=use_sparse,\n", - "# )\n", - "# tensor_sparse\n", - "\n", - "# # timeit comes out at about 13 seconds for nu_target = 3" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# tensor_sparse = clebsch_gordan.n_body_iteration_single_center(\n", - "# frames,\n", - "# rascal_hypers=rascal_hypers,\n", - "# nu_target=3,\n", - "# lambdas=lambdas,\n", - "# use_sparse=True,\n", - "# )\n", - "\n", - "# tensor_dense = clebsch_gordan.n_body_iteration_single_center(\n", - "# frames,\n", - "# rascal_hypers=rascal_hypers,\n", - "# nu_target=3,\n", - "# lambdas=lambdas,\n", - "# use_sparse=False,\n", - "# )\n", - "\n", - "# assert metatensor.allclose(tensor_dense, tensor_sparse)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Test system 2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# frames = [ase.io.read(\"frame.xyz\")]\n", - "\n", - "# lambdas = np.array([0, 2])\n", - "\n", - "# rascal_hypers = {\n", - "# \"cutoff\": 3.0, # Angstrom\n", - "# \"max_radial\": 6, # Exclusive\n", - "# \"max_angular\": 5, # Inclusive\n", - "# \"atomic_gaussian_width\": 0.2,\n", - "# \"radial_basis\": {\"Gto\": {}},\n", - "# \"cutoff_function\": {\"ShiftedCosine\": {\"width\": 0.5}},\n", - "# \"center_atom_weight\": 1.0,\n", - "# }\n", - "\n", - "# n_body = clebsch_gordan.n_body_iteration_single_center(\n", - "# frames,\n", - "# rascal_hypers=rascal_hypers,\n", - "# nu_target=3,\n", - "# lambdas=lambdas,\n", - "# )\n", - "# n_body" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# def sort_tm(tm):\n", - "# blocks = []\n", - "# for _, block in tm.items():\n", - "# values = block.values\n", - "\n", - "# samples_values = block.samples.values\n", - "# sorted_idx = native_list_argsort([tuple(row.tolist()) for row in block.samples.values])\n", - "# samples_values = samples_values[sorted_idx]\n", - "# values = values[sorted_idx]\n", - "\n", - "# components_values = []\n", - "# for i, component in enumerate(block.components):\n", - "# component_values = component.values\n", - "# sorted_idx = native_list_argsort([tuple(row.tolist()) for row in component.values])\n", - "# components_values.append( component_values[sorted_idx] )\n", - "# values = np.take(values, sorted_idx, axis=i+1)\n", - "\n", - "# properties_values = block.properties.values\n", - "# sorted_idx = native_list_argsort([tuple(row.tolist()) for row in block.properties.values])\n", - "# properties_values = properties_values[sorted_idx]\n", - "# values = values[..., sorted_idx]\n", - "\n", - "# blocks.append(\n", - "# TensorBlock(\n", - "# values=values,\n", - "# samples=Labels(values=samples_values, names=block.samples.names),\n", - "# components=[Labels(values=components_values[i], names=component.names) for i, component in enumerate(block.components)],\n", - "# properties=Labels(values=properties_values, names=block.properties.names)\n", - "# )\n", - "# )\n", - "# return TensorMap(keys=tm.keys, blocks=blocks)\n", - "\n", - "\n", - "# def native_list_argsort(native_list):\n", - "# return sorted(range(len(native_list)), key=native_list.__getitem__)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "# # Manipulate metadata from old LSOAP\n", - "# lsoap_old = metatensor.permute_dimensions(lsoap_old0, axis=\"properties\", dimensions_indexes=[2, 5, 0, 1, 3, 4])\n", - "# lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_1\", new=\"l1\")\n", - "# lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"l_2\", new=\"l2\")\n", - "# lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_1\", new=\"n1\")\n", - "# lsoap_old = metatensor.rename_dimension(lsoap_old, axis=\"properties\", old=\"n_2\", new=\"n2\")\n", - "\n", - "# # Slice TM to symmetrize l-values\n", - "# sliced_blocks = []\n", - "# for key, block in lsoap_old.items():\n", - "# # Filter properties l1 <= l2\n", - "# mask = [entry[0] <= entry[1] for entry in block.properties]\n", - "# new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", - "\n", - "# # Slice block\n", - "# sliced_block = metatensor.slice_block(block, axis=\"properties\", labels=new_labels)\n", - "# sliced_blocks.append(sliced_block)\n", - "\n", - "# # Check equal metadata\n", - "# lsoap_old = sort_tm(TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks))\n", - "# lsoap_new = sort_tm(lsoap_new0)\n", - "# assert metatensor.equal_metadata(lsoap_old, lsoap_new)\n", - "\n", - "# # Check equal values\n", - "# metatensor.allclose_raise(lsoap_old, lsoap_new)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# for key in lsoap_old.keys:\n", - "# b1 = lsoap_old[key]\n", - "# b2 = lsoap_new[key]\n", - "\n", - "# print(key, metatensor.allclose_block(b1, b2))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# np.linalg.norm(\n", - "# lsoap_old.block(inversion_sigma=1, spherical_harmonics_l=1, species_center=3).values\n", - "# - lsoap_new.block(\n", - "# inversion_sigma=1, spherical_harmonics_l=1, species_center=3\n", - "# ).values\n", - "# )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# # NOW SLICE TO l1 == l2\n", - "\n", - "# # Slice TM\n", - "# sliced_blocks = []\n", - "# for key, block in lsoap_old.items():\n", - "# # Filter properties\n", - "# mask = [entry[0] == entry[1] for entry in block.properties]\n", - "# new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", - "\n", - "# # Slice block\n", - "# sliced_blocks.append(metatensor.slice_block(block, axis=\"properties\", labels=new_labels))\n", - "\n", - "# lsoap_old_sliced = TensorMap(keys=lsoap_old.keys, blocks=sliced_blocks)\n", - "\n", - "# # Slice TM\n", - "# sliced_blocks = []\n", - "# for key, block in lsoap_new.items():\n", - "# # Filter properties\n", - "# mask = [entry[0] == entry[1] for entry in block.properties]\n", - "# new_labels = Labels(names=block.properties.names, values=block.properties.values[mask])\n", - "\n", - "# # Slice block\n", - "# sliced_blocks.append(metatensor.slice_block(block, axis=\"properties\", labels=new_labels))\n", - "\n", - "# lsoap_new_sliced = TensorMap(keys=lsoap_new.keys, blocks=sliced_blocks)\n", - "\n", - "# assert metatensor.equal_metadata(lsoap_old_sliced, lsoap_new_sliced)\n", - "# assert metatensor.allclose_raise(lsoap_old_sliced, lsoap_new_sliced)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "dev", - "language": "python", - "name": "python3" - }, - "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.10.12" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} From ad882209c1b67b265be1c020138b57e1df9ccaf3 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 15 Nov 2023 14:22:20 +0100 Subject: [PATCH 78/96] Add some docs on rotation-adapted features, authored by @ecignoni --- docs/src/explanations/index.rst | 1 + docs/src/explanations/rotation_adapted.rst | 88 ++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 docs/src/explanations/rotation_adapted.rst diff --git a/docs/src/explanations/index.rst b/docs/src/explanations/index.rst index 8643a7235..77eb9ffa0 100644 --- a/docs/src/explanations/index.rst +++ b/docs/src/explanations/index.rst @@ -13,3 +13,4 @@ all about. concepts soap + rotation_adapted \ No newline at end of file diff --git a/docs/src/explanations/rotation_adapted.rst b/docs/src/explanations/rotation_adapted.rst new file mode 100644 index 000000000..e98bb332d --- /dev/null +++ b/docs/src/explanations/rotation_adapted.rst @@ -0,0 +1,88 @@ +Rotation-Adapted Features +========================= + +Equivariance +------------ + +Descriptors like SOAP are translation, rotation, and permutation invariant. +Indeed, such invariances are extremely useful if one wants to learn an invariant target (e.g., the energy). +Being already encoded in the descriptor, the learning algorithm does not have to learn such a physical requirement. + +The situation is different if the target is not invariant. For example, one may want to learn a dipole. The dipole rotates with a rotation of the molecule, and as such, invariant descriptors do not have the required symmetries for this task. + +Instead, one would need a rotation equivariant descriptor. +Rotation equivariance means that, if I first rotate the structure and compute the descriptor, I obtain the same result as first computing the descriptor and then applying the rotation, i.e., the descriptor behaves correctly upon rotation operations. +Denoting a structure as :math:`A`, the function computing the descriptor as :math:`f(\cdot)`, and the rotation operator as :math:`\hat{R}`, rotation equivariance can be expressed as: + +.. math:: + :name: eq:equivariance + + f(\hat{R} A) = \hat{R} f(A) + +Of course, invariance is a special case of equivariance. + + +Rotation Equivariance of the Spherical Expansion +------------------------------------------------ + +The spherical expansion is a rotation equivariant descriptor. +Let's consider the expansion coefficients of :math:`\rho_i(\mathbf{r})`. +We have: + +.. math:: + + \hat{R} \rho_i(\mathbf{r}) &= \sum_{nlm} c_{nlm}^{i} R_n(r) \hat{R} Y_l^m(\hat{\mathbf{r}}) \nonumber \\ + &= \sum_{nlmm'} c_{nlm}^{i} R_n(r) D_{m,m'}^{l}(\hat{R}) Y_l^{m'}(\hat{\mathbf{r}}) \nonumber \\ + &= \sum_{nlm} \left( \sum_{m'} D_{m',m}^l(\hat{R}) c_{nlm'}^{i}\right) B_{nlm}(\mathbf{r}) \nonumber + +and noting that :math:`Y_l^m(\hat{R} \hat{\mathbf{r}}) = \hat{R} Y_l^m(\hat{\mathbf{r}})` and :math:`\hat{R}r = r`, equation :ref:`(1) ` is satisfied and we conclude that the expansion coefficients :math:`c_{nlm}^{i}` are rotation equivariant. +Indeed, each :math:`c_{nlm}^{i}` transforms under rotation as the spherical harmonics :math:`Y_l^m(\hat{\mathbf{r}})`. + +Using the Dirac notation, the coefficient :math:`c_{nlm}^{i}` can be expressed as :math:`\braket{nlm\vert\rho_i}`. +Equivalently, and to stress the fact that this coefficient describes something that transforms under rotation as a spherical harmonics :math:`Y_l^m(\hat{\mathbf{r}})`, it is sometimes written as :math:`\braket{n\vert\rho_i;lm}`, i.e., the atomic density is "tagged" with a label that tells how it transforms under rotations. + + +Completeness Relations of Spherical Harmonics +--------------------------------------------- + +Spherical harmonics can be combined together using rules coming from standard theory of angular momentum: + +.. math:: + :name: eq:cg_coupling + + \ket{lm} \propto \ket{l_1 l_2 l m} = \sum_{m_1 m_2} C_{m_1 m_2 m}^{l_1 l_2 l} \ket{l_1 m_1} \ket{l_2 m_2} + +where :math:`C_{m_1 m_2 m}^{l_1 l_2 l}` is a Clebsch-Gordan (CG) coefficient. + +Thanks to the one-to-one correspondence (under rotation) between :math:`c_{nlm}^{i}` and :math:`Y_l^m`, +:ref:`(2) ` means that one can take products of two spherical expansion coefficients (which amounts to considering density correlations), and combine them with CG coefficients to get new coefficients that transform as a single spherical harmonics. +This process is known as coupling, from the uncoupled basis of angular momentum (formed by the product of rotation eigenstates) to a coupled basis (a single rotation eigenstate). + +One can also write the inverse of :ref:`(2) `: + +.. math:: + :name: eq:cg_decoupling + + \ket{l_1 m_1} \ket{l_2 m_2} = \sum_{l m} C_{m_1 m_2 m}^{l_1 l_2 l m} \ket{l_1 l_2 l m} + +that express the product of two rotation eigenstates in terms of one. This process is known as decoupling. + +Example: :math:`\lambda`-SOAP +----------------------------- + +A straightforward application of :ref:`(2) ` is the construction of :math:`\lambda`-SOAP features. +Indeed, :math:`\lambda`-SOAP was created in order to have a rotation and inversion equivariant version of the 3-body density correlations. +The :math:`\lambda` represents the degree of a spherical harmonics, :math:`Y_{\lambda}^{\mu}(\hat{\mathbf{r}})`, +and it indicates that this descriptor can transform under rotations as a spherical harmonics, i.e., it is rotation equivariant. + +It is then obtained by considering two expansion coefficients of the atomic density, and combining them with a CG iteration to a coupled basis, +as in :ref:`(2) `. +The :math:`\lambda`-SOAP descriptor is then: + +.. math:: + + \braket{n_1 l_1 n_2 l_2\vert\overline{\rho_i^{\otimes 2}, \sigma, \lambda \mu}} = + \frac{\delta_{\sigma, (-1)^{l_1 + l_2 + \lambda}}}{\sqrt{2 \lambda + 1}} + \sum_{m} C_{m (\mu-m) \mu}^{l_1 l_2 \lambda} c_{n_1 l_1 m}^{i} c_{n_2 l_2 (\mu - m)}^{i} + +where we have assumed real spherical harmonics coefficients. \ No newline at end of file From 6b843a82b459706499e352894cd82ee5b383cdd1 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 15 Nov 2023 17:22:15 +0100 Subject: [PATCH 79/96] Set up dispatch, remove unnecessary lsoap wrapper --- .../utils/clebsch_gordan/__init__.py | 2 - .../utils/clebsch_gordan/_dispatch.py | 147 +++++++++++-- .../utils/clebsch_gordan/clebsch_gordan.py | 174 ++++++++------- .../rascaline/tests/utils/clebsch_gordan.py | 206 ++++++------------ 4 files changed, 293 insertions(+), 236 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py index 4f1a1aaa1..0bca78755 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py @@ -2,7 +2,6 @@ from .clebsch_gordan import ( # noqa combine_single_center_to_body_order, combine_single_center_to_body_order_metadata_only, - lambda_soap_vector, ) from .rotations import ( # noqa cartesian_rotation, @@ -17,7 +16,6 @@ "ClebschGordanReal", "combine_single_center_to_body_order", "combine_single_center_to_body_order_metadata_only", - "lambda_soap_vector", "transform_frame_so3", "transform_frame_o3", "WignerDReal", diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index adedfe5a5..c56779e6d 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -1,7 +1,7 @@ """ Module containing dispatch functions for numpy/torch CG combination operations. """ -from typing import Union +from typing import List, Optional, Union import numpy as np try: @@ -18,7 +18,10 @@ class TorchTensor: ) -def _combine_arrays( +# ============ CG combinations ============ + + +def combine_arrays( arr_1: Union[np.ndarray, TorchTensor], arr_2: Union[np.ndarray, TorchTensor], lam: int, @@ -67,18 +70,18 @@ def _combine_arrays( """ # If just precomputing metadata, return an empty array if return_empty_array: - return _sparse_combine(arr_1, arr_2, lam, cg_cache, True) + return sparse_combine(arr_1, arr_2, lam, cg_cache, True) # Otherwise, perform the CG combination # Spare CG cache if cg_cache.sparse: - return _sparse_combine(arr_1, arr_2, lam, cg_cache, False) + return sparse_combine(arr_1, arr_2, lam, cg_cache, False) # Dense CG cache - return _dense_combine(arr_1, arr_2, lam, cg_cache) + return dense_combine(arr_1, arr_2, lam, cg_cache) -def _sparse_combine( +def sparse_combine( arr_1: Union[np.ndarray, TorchTensor], arr_2: Union[np.ndarray, TorchTensor], lam: int, @@ -101,7 +104,7 @@ def _sparse_combine( :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] """ - if isinstance(arr_1, np.ndarray): + if isinstance(arr_1, np.ndarray) or isinstance(arr_1, TorchTensor): # Samples dimensions must be the same assert arr_1.shape[0] == arr_2.shape[0] @@ -115,7 +118,7 @@ def _sparse_combine( l2 = (arr_2.shape[1] - 1) // 2 # Initialise output array - arr_out = np.zeros((n_i, 2 * lam + 1, n_p * n_q)) + arr_out = zeros_like((n_i, 2 * lam + 1, n_p * n_q), like=arr_1) if return_empty_array: return arr_out @@ -132,14 +135,11 @@ def _sparse_combine( return arr_out - elif isinstance(arr_1, TorchTensor): - pass - else: raise TypeError(UNKNOWN_ARRAY_TYPE) -def _dense_combine( +def dense_combine( arr_1: Union[np.ndarray, TorchTensor], arr_2: Union[np.ndarray, TorchTensor], lam: int, @@ -161,7 +161,7 @@ def _dense_combine( :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] """ - if isinstance(arr_1, np.ndarray): + if isinstance(arr_1, np.ndarray) or isinstance(arr_1, TorchTensor): # Infer l1 and l2 from the len of the length of axis 1 of each tensor l1 = (arr_1.shape[1] - 1) // 2 l2 = (arr_2.shape[1] - 1) // 2 @@ -172,7 +172,7 @@ def _dense_combine( arr_out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] # (samples l2_mu p l1_mu q) -> (samples q p l1_mu l2_mu) - arr_out = arr_out.swapaxes(1, 4) + arr_out = swapaxes(arr_out, 1, 4) # samples (q p l1_mu l2_mu) -> (samples (q p) (l1_mu l2_mu)) arr_out = arr_out.reshape( @@ -186,10 +186,123 @@ def _dense_combine( arr_out = arr_out @ cg_coeffs # (samples (q p) lam_mu) -> (samples lam_mu (q p)) - return arr_out.swapaxes(1, 2) + return swapaxes(arr_out, 1, 2) + + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) + + +# ============ Other functions ============ + + +def unique(array, axis: Optional[int] = None): + """Find the unique elements of an array.""" + if isinstance(array, TorchTensor): + return torch.unique(array, dim=axis) + elif isinstance(array, np.ndarray): + return np.unique(array, axis=axis) + + +def int_range_like(min_val, max_val, like): + """Returns an array of integers from min to max, non-inclusive, based on the + type of `like`""" + if isinstance(like, TorchTensor): + return torch.arange(int_list, dtype=torch.int64, device=like.device) + elif isinstance(like, np.ndarray): + return np.arange(min_val, max_val).astype(np.int64) + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) + + +def int_array_like(int_list: List[int], like): + """ + Converts the input list of int to a numpy array or torch tensor + based on the type of `like`. + """ + if isinstance(like, TorchTensor): + return torch.tensor(int_list, dtype=torch.int64, device=like.device) + elif isinstance(like, np.ndarray): + return np.array(int_list).astype(np.int64) + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) - elif isinstance(arr_1, TorchTensor): - pass +def concatenate(arrays, axis: Optional[int] = 0): + """Concatenate arrays along an axis.""" + if isinstance(arrays[0], TorchTensor): + return torch.cat(arrays, dim=axis) + elif isinstance(arrays[0], np.ndarray): + return np.concatenate(arrays, axis=axis) else: raise TypeError(UNKNOWN_ARRAY_TYPE) + + +def all(array, axis: Optional[int] = None): + """Test whether all array elements along a given axis evaluate to True. + + This function has the same behavior as + ``np.all(array,axis=axis)``. + """ + if isinstance(array, bool): + array = np.array(array) + if isinstance(array, list): + array = np.array(array) + + if isinstance(array, TorchTensor): + # torch.all has two implementation, and picks one depending if more than one + # parameter is given. The second one does not supports setting dim to `None` + if axis is None: + return torch.all(input=array) + else: + return torch.all(input=array, dim=axis) + elif isinstance(array, np.ndarray): + return np.all(a=array, axis=axis) + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) + + +def any(array): + """Test whether any array elements along a given axis evaluate to True. + + This function has the same behavior as + ``np.any(array)``. + """ + if isinstance(array, bool): + array = np.array(array) + if isinstance(array, list): + array = np.array(array) + if isinstance(array, TorchTensor): + return torch.all(array) + elif isinstance(array, np.ndarray): + return np.all(array) + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) + + +def zeros_like(shape, like): + """Return an array of zeros with the same shape and type as a given array. + + This function has the same behavior as + ``np.zeros_like(array)``. + """ + if isinstance(like, TorchTensor): + return torch.zeros( + shape, + requires_grad=like.requires_grad, + dtype=like.dtype, + device=like.device, + ) + elif isinstance(like, np.ndarray): + return np.zeros(shape, dtype=like.dtype) + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) + + +def swapaxes(array, axis0: int, axis1: int): + """Swaps axes of an array.""" + if isinstance(array, TorchTensor): + return torch.swapaxes(array, axis0, axis1) + elif isinstance(array, np.ndarray): + return np.swapaxes(array, axis0, axis1) + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) \ No newline at end of file diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 431e83339..850b03c86 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -5,7 +5,6 @@ from typing import List, Optional, Tuple, Union import metatensor -import numpy as np from metatensor import Labels, TensorBlock, TensorMap from .cg_coefficients import ClebschGordanReal @@ -17,59 +16,6 @@ # ====================================================================== -def lambda_soap_vector( - nu_1_tensor: TensorMap, - angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[None, int, List[int]]] = None, - parity_selection: Optional[Union[None, int, List[int]]] = None, -) -> TensorMap: - """ - A higher-level wrapper for the :py:func:`n_body_iteration_single_center` - function specifically for generating a lambda-SOAP vector in the metatensor - format. - - A nu = 1 (i.e. 2-body) single-center descriptor is taken as input and - conbined with itself in a single CG combination step to form a nu = 2 - (3-body) single-center descriptor, i.e. lambda-SOAP. Only the target angular - channels given in `angular_selection` and target parities given in - `parity_selection` are returned. - - The input `nu_1_tensor` may be, for instance, a rascaline.SphericalExpansion - or rascaline.LodeSphericalExpansion. - - This function differs from :py:func`combine_single_center_to_body_order` in - that the returned TensorMap has the redundant "order_nu" key dimension - removed (which is by definition 2 for lambda-SOAP), and if a single parity - is selected, the redundant "inversion_sigma" key dimension too. - """ - if np.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): - raise NotImplementedError( - "CG combinations of gradients not currently supported. Check back soon." - ) - # Generate lambda-SOAP - lsoap = combine_single_center_to_body_order( - nu_1_tensor=nu_1_tensor, - target_body_order=2, - angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, - use_sparse=True, - ) - - # Drop the redundant key name "order_nu". This is by definition 2 for all - # lambda-SOAP blocks. - keys = lsoap.keys.remove(name="order_nu") - lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()]) - - # If a single parity is requested, drop the now redundant "inversion_sigma" - # key name - if len(np.unique(lsoap.keys.column("inversion_sigma"))) == 1: - keys = lsoap.keys.remove(name="inversion_sigma") - lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()]) - - return lsoap - - def combine_single_center_to_body_order( nu_1_tensor: TensorMap, target_body_order: int, @@ -83,11 +29,41 @@ def combine_single_center_to_body_order( Takes a nu = 1 (i.e. 2-body) single-center descriptor and combines it iteratively with itself to generate a descriptor of order ``target_body_order``. + + ``nu_1_tensor`` may be, for instance, a rascaline.SphericalExpansion or + rascaline.LodeSphericalExpansion. In the first iteration, ``nu_1_tensor`` is + combined with itself to form a nu = 2 (3-body) descriptor. In each following + iterations the nu = x (x+1)-body descriptor is combined with the + ``nu_1_tensor`` nu = 1 descriptor until the ``target_body_order`` is + reached. + + With no other specification of input args, the full extent of non-zero CG + combinations will be taken between blocks at every step. However, there are + selections that can be made to reduce the computational cost. + ``angular_cutoff`` applies a global cutoff to the maximum angular channel + that is computed at each iteration. + + ``angular_selection`` and ``parity_selection`` can be used to explicitly + control the angular and parity channels that are output at each step. + Passing a list of int in each will apply that selection on the final CG + combination step. Passing a list of list of int (of length + ``target_body_order`` - 1) will apply the specified selection at each step. + + Cost can also be reduced with the ``sort_l_list`` argument. By default, all + combinations of blocks are performed. Some well-defined sets of these + combinations are redundant, and can be skipped by setting + ``sort_l_list=True`. This is done by sorting the l list (i.e. the angular + channels of the original nu = 1 blocks previously combined) of the blocks to + be combined, and only operating on blocks where l1 <= l2 <= ... <= ln. + + Finally, the ``use_sparse`` argument can be used to control whether a sparse + or dense cache of CG coefficients is used, which depending on the use case + can affect the performance. """ if target_body_order < 1: raise ValueError("`target_body_order` must be > 0") - if np.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): + if _dispatch.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): raise NotImplementedError( "CG combinations of gradients not currently supported. Check back soon." ) @@ -129,8 +105,8 @@ def combine_single_center_to_body_order( # constructed CG cache could be used to reduce memory overhead - i.e. we # don't necessarily need *all* CG coeffs up to `angular_max`, just the ones # that are actually used. - angular_max = np.max( - np.concatenate( + angular_max = max( + _dispatch.concatenate( [nu_1_tensor.keys.column("spherical_harmonics_l")] + [ metadata[0].column("spherical_harmonics_l") @@ -167,7 +143,14 @@ def combine_single_center_to_body_order( + [f"k{i}" for i in range(2, target_body_order)] ) - return nu_x_tensor + # Drop the redundant key name "order_nu", and "inversion_sigma" if also + # redundant. TODO: these should be part of the global matadata associated + # with the TensorMap. Awaiting this functionality in metatensor. + keys = nu_x_tensor.keys.remove(name="order_nu") + if len(_dispatch.unique(nu_x_tensor.keys.column("inversion_sigma"))) == 1: + keys = keys.remove(name="inversion_sigma") + + return TensorMap(keys=keys, blocks=[b.copy() for b in nu_x_tensor.blocks()]) def combine_single_center_to_body_order_metadata_only( @@ -197,7 +180,7 @@ def combine_single_center_to_body_order_metadata_only( if target_body_order <= 1: raise ValueError("`target_body_order` must be > 1") - if np.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): + if _dispatch.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): raise NotImplementedError( "CG combinations of gradients not currently supported. Check back soon." ) @@ -256,7 +239,7 @@ def combine_single_center_to_body_order_metadata_only( nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) nu_x_tensors.append(nu_x_tensor) - return [ + nu_x_tensors = [ tensor.keys_to_properties( [f"l{i}" for i in range(1, tmp_bo + 1)] + [f"k{i}" for i in range(2, tmp_bo)] @@ -264,6 +247,17 @@ def combine_single_center_to_body_order_metadata_only( for tmp_bo, tensor in enumerate(nu_x_tensors, start=2) ] + return_tensors = [] + for tensor in nu_x_tensors: + keys = tensor.keys.remove(name="order_nu") + if len(_dispatch.unique(tensor.keys.column("inversion_sigma"))) == 1: + keys = keys.remove(name="inversion_sigma") + return_tensors.append( + TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) + ) + + return return_tensors + def combine_single_center_one_iteration( tensor_1: TensorMap, @@ -302,8 +296,16 @@ def _standardize_tensor_metadata(tensor: TensorMap) -> TensorMap: """ if "species_neighbor" in tensor.keys.names: tensor = tensor.keys_to_properties(keys_to_move="species_neighbor") - keys = tensor.keys.insert(name="order_nu", values=np.array([1]), index=0) - keys = keys.insert(name="inversion_sigma", values=np.array([1]), index=1) + keys = tensor.keys.insert( + name="order_nu", + values=_dispatch.int_array_like([1], like=tensor.keys.values), + index=0, + ) + keys = keys.insert( + name="inversion_sigma", + values=_dispatch.int_array_like([1], like=tensor.keys.values), + index=1, + ) return TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) @@ -361,29 +363,29 @@ def _parse_selection_filters( raise TypeError("`selection` must be an int, List[int], or List[List[int]]") for slct in selection: if slct is not None: - if not np.all([isinstance(val, int) for val in slct]): + if not _dispatch.all([isinstance(val, int) for val in slct]): raise TypeError( "`selection` must be an int, List[int], or List[List[int]]" ) if selection_type == "parity": - if not np.all([val in [-1, +1] for val in slct]): + if not _dispatch.all([val in [-1, +1] for val in slct]): raise ValueError( "specified layers in `selection` must only contain valid" " parity values of -1 or +1" ) - if not np.all([0 < len(slct) <= 2]): + if not _dispatch.all([0 < len(slct) <= 2]): raise ValueError( "each parity filter must be a list of length 1 or 2," " with vals +1 and/or -1" ) elif selection_type == "angular": - if not np.all([val >= 0 for val in slct]): + if not _dispatch.all([val >= 0 for val in slct]): raise ValueError( "specified layers in `selection` must only contain valid" " angular channels >= 0" ) if angular_cutoff is not None: - if not np.all([val <= angular_cutoff for val in slct]): + if not _dispatch.all([val <= angular_cutoff for val in slct]): raise ValueError( "specified layers in `selection` must only contain valid" " angular channels <= the specified `angular_cutoff`" @@ -522,7 +524,7 @@ def _precompute_metadata_one_iteration( parities. This must be passed as a list with elements +1 and/or -1. """ # Get the body order of the first TensorMap. - unique_nu = np.unique(keys_1.column("order_nu")) + unique_nu = _dispatch.unique(keys_1.column("order_nu")) if len(unique_nu) > 1: raise ValueError( "keys_1 must correspond to a tensor of a single body order." @@ -534,7 +536,7 @@ def _precompute_metadata_one_iteration( nu = nu1 + 1 # The body order of the second TensorMap should be nu = 1. - assert np.all(keys_2.column("order_nu") == 1) + assert _dispatch.all(keys_2.column("order_nu") == 1) # If nu1 = 1, the key names don't yet have any "lx" columns if nu1 == 1: @@ -545,13 +547,13 @@ def _precompute_metadata_one_iteration( new_l_list_names = l_list_names + [f"l{nu}"] # Check key names - assert np.all( + assert _dispatch.all( keys_1.names == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + l_list_names + [f"k{k}" for k in range(2, nu1)] ) - assert np.all( + assert _dispatch.all( keys_2.names == ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] ) @@ -579,7 +581,9 @@ def _precompute_metadata_one_iteration( # formed from combination of blocks of order `lam1` and `lam2`. This # corresponds to values in the inclusive range { |lam1 - lam2|, ..., # |lam1 + lam2| } - nonzero_lams = np.arange(abs(lam1 - lam2), abs(lam1 + lam2) + 1) + nonzero_lams = _dispatch.int_range_like( + abs(lam1 - lam2), abs(lam1 + lam2) + 1, like=key_1.values + ) # Now iterate over the non-zero angular channels and apply the custom # selections @@ -618,7 +622,10 @@ def _precompute_metadata_one_iteration( keys_2_entries.append(key_2) # Define new keys as the full product of keys_1 and keys_2 - nu_x_keys = Labels(names=new_names, values=np.array(new_key_values)) + nu_x_keys = Labels( + names=new_names, + values=_dispatch.int_array_like(new_key_values, like=keys_1.values), + ) # If we want to sort the l list to save computations (i.e. not calculate ) if not sort_l_list: @@ -640,13 +647,15 @@ def _precompute_metadata_one_iteration( # want to compute a CG combination for. key_slice_tuple = tuple(first_part + l_list) key_slice_sorted_tuple = tuple(first_part + l_list_sorted) - if np.all(key_slice_tuple == key_slice_sorted_tuple): + if _dispatch.all(key_slice_tuple == key_slice_sorted_tuple): key_idxs_to_keep.append(key_idx) # Build a reduced Labels object for the combined keys, with redundancies removed combined_keys_red = Labels( names=new_names, - values=np.array([nu_x_keys[idx].values for idx in key_idxs_to_keep]), + values=_dispatch.int_array_like( + [nu_x_keys[idx].values for idx in key_idxs_to_keep], like=keys_1.values + ), ) # Create a of LabelsEntry objects that correspond to the original keys in @@ -676,11 +685,11 @@ def _combine_single_center_blocks( # Do the CG combination - single center so no shape pre-processing required if return_metadata_only: - combined_values = _dispatch._combine_arrays( + combined_values = _dispatch.combine_arrays( block_1.values, block_2.values, lam, cg_cache, return_empty_array=True ) else: - combined_values = _dispatch._combine_arrays( + combined_values = _dispatch.combine_arrays( block_1.values, block_2.values, lam, cg_cache, return_empty_array=False ) @@ -700,17 +709,20 @@ def _combine_single_center_blocks( components=[ Labels( names=["spherical_harmonics_m"], - values=np.arange(-lam, lam + 1).reshape(-1, 1), + values=_dispatch.int_range_like( + min_val=-lam, max_val=lam + 1, like=block_1.values + ).reshape(-1, 1), ), ], properties=Labels( names=prop_names, - values=np.array( + values=_dispatch.int_array_like( [ - np.concatenate((b2, b1)) + _dispatch.concatenate((b2, b1)) for b2 in block_2.properties.values for b1 in block_1.properties.values - ] + ], + like=block_1.values, ), ), ) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 99411d045..f98cfe4a7 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -15,17 +15,17 @@ DATA_ROOT = os.path.join(os.path.dirname(__file__), "data") -RASCAL_HYPERS = { +SPHEX_HYPERS = { "cutoff": 3.0, # Angstrom "max_radial": 6, # Exclusive - "max_angular": 5, # Inclusive + "max_angular": 4, # Inclusive "atomic_gaussian_width": 0.2, "radial_basis": {"Gto": {}}, "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, "center_atom_weight": 1.0, } -RASCAL_HYPERS_SMALL = { +SPHEX_HYPERS_SMALL = { "cutoff": 3.0, # Angstrom "max_radial": 1, # Exclusive "max_angular": 2, # Inclusive @@ -35,6 +35,9 @@ "center_atom_weight": 1.0, } +# TODO: test a CG combination with LODE +LODE_HYPERS_SMALL = {} + # ============ Pytest fixtures ============ @@ -70,30 +73,35 @@ def wigners(lmax: int): def sphex(frames: List[ase.Atoms]): """Returns a rascaline SphericalExpansion""" - calculator = rascaline.SphericalExpansion(**RASCAL_HYPERS) + calculator = rascaline.SphericalExpansion(**SPHEX_HYPERS) return calculator.compute(frames) def sphex_small_features(frames: List[ase.Atoms]): """Returns a rascaline SphericalExpansion""" - calculator = rascaline.SphericalExpansion(**RASCAL_HYPERS_SMALL) + calculator = rascaline.SphericalExpansion(**SPHEX_HYPERS_SMALL) return calculator.compute(frames) def powspec(frames: List[ase.Atoms]): """Returns a rascaline PowerSpectrum constructed from a SphericalExpansion""" - return PowerSpectrum(rascaline.SphericalExpansion(**RASCAL_HYPERS)).compute(frames) + return PowerSpectrum(rascaline.SphericalExpansion(**SPHEX_HYPERS)).compute(frames) def powspec_small_features(frames: List[ase.Atoms]): """Returns a rascaline PowerSpectrum constructed from a SphericalExpansion""" - return PowerSpectrum(rascaline.SphericalExpansion(**RASCAL_HYPERS_SMALL)).compute( + return PowerSpectrum(rascaline.SphericalExpansion(**SPHEX_HYPERS_SMALL)).compute( frames ) +def lode_small_features(frames: List[ase.Atoms]): + """Returns a rascaline LODE SphericalExpansion""" + return rascaline.LodeSphericalExpansion(**LODE_HYPERS_SMALL).compute(frames) + + def get_norm(tensor: TensorMap): """ Calculates the norm used in CG iteration tests. Assumes standardized @@ -130,7 +138,7 @@ def test_so3_equivariance( angular_selection: List[List[int]], parity_selection: List[List[int]], ): - wig = wigners(nu_target * RASCAL_HYPERS["max_angular"]) + wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) frames_so3 = [ clebsch_gordan.transform_frame_so3(frame, wig.angles) for frame in frames ] @@ -172,7 +180,7 @@ def test_o3_equivariance( angular_selection: List[List[int]], parity_selection: List[List[int]], ): - wig = wigners(nu_target * RASCAL_HYPERS["max_angular"]) + wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) frames_o3 = [ clebsch_gordan.transform_frame_o3(frame, wig.angles) for frame in frames ] @@ -214,8 +222,9 @@ def test_lambda_soap_vs_powerspectrum(frames): # Build a lambda-SOAP nu_1_tensor = sphex_small_features(frames) - lsoap = clebsch_gordan.lambda_soap_vector( + lsoap = clebsch_gordan.combine_single_center_to_body_order( nu_1_tensor=nu_1_tensor, + target_body_order=2, angular_selection=[0], ) keys = lsoap.keys.remove(name="spherical_harmonics_l") @@ -398,7 +407,7 @@ def test_combine_single_center_to_body_order_dense_sparse_agree(frames): ) -# ============ Test metadata precomputation ============ +# ============ Test metadata ============ @pytest.mark.parametrize("frames", [h2o_isolated()]) @@ -452,131 +461,56 @@ def test_combine_single_center_to_body_order_metadata_only( TODO: finish! """ - for nu1 in [sphex_small_features(frames), sphex(frames)]: - # Build higher body order tensor without CG computation - i.e. metadata - # only. This returns a list of the TensorMaps formed at each CG - # iteration. - nux_metadata_only = ( - clebsch_gordan.combine_single_center_to_body_order_metadata_only( - nu1, - target_body_order=target_body_order, - angular_cutoff=None, - angular_selection=None, - parity_selection=None, - sort_l_list=sort_l_list, - ) + # for nu1 in [sphex_small_features(frames), sphex(frames)]: + # # Build higher body order tensor without CG computation - i.e. metadata + # # only. This returns a list of the TensorMaps formed at each CG + # # iteration. + # nux_metadata_only = ( + # clebsch_gordan.combine_single_center_to_body_order_metadata_only( + # nu1, + # target_body_order=target_body_order, + # angular_cutoff=None, + # angular_selection=None, + # parity_selection=None, + # sort_l_list=sort_l_list, + # ) + # ) + + +@pytest.mark.parametrize("frames", [h2o_isolated()]) +@pytest.mark.parametrize("angular_selection", [None, [1, 2, 4]]) +@pytest.mark.parametrize("sort_l_list", [True, False]) +def test_single_center_combine_angular_selection( + frames: List[ase.Atoms], + angular_selection: List[List[int]], + sort_l_list: bool, +): + """Tests that the correct angular channels are outputted based on the + specified ``angular_cutoff`` and ``angular_selection``.""" + nu_1 = sphex(frames) + + nu_2 = clebsch_gordan.combine_single_center_to_body_order( + nu_1_tensor=nu_1, + target_body_order=2, + angular_cutoff=None, + angular_selection=angular_selection, + parity_selection=None, + sort_l_list=sort_l_list, + ) + + if angular_selection is None: + assert np.all( + [ + l in np.arange(SPHEX_HYPERS["max_angular"] * 2 + 1) + for l in np.unique(nu_2.keys.column("spherical_harmonics_l")) + ] + ) + + else: + assert np.all( + np.sort(np.unique(nu_2.keys.column("spherical_harmonics_l"))) + == np.sort(angular_selection) ) -# ============ Test kernel construction ============ - -# TODO: if we want this test, the below code will need updating -# def test_soap_kernel(): -# """ -# Tests if we get the same result computing SOAP from spherical expansion coefficients using GC utils -# as when using SoapPowerSpectrum. -# -# """ -# frames = ase.Atoms('HO', -# positions=[[0., 0., 0.], [1., 1., 1.]], -# pbc=[False, False, False]) -# -# -# rascal_hypers = { -# "cutoff": 3.0, # Angstrom -# "max_radial": 2, # Exclusive -# "max_angular": 3, # Inclusive -# "atomic_gaussian_width": 0.2, -# "radial_basis": {"Gto": {}}, -# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, -# "center_atom_weight": 1.0, -# } -# -# calculator = rascaline.SphericalExpansion(**rascal_hypers) -# nu1 = calculator.compute(frames) -# nu1 = nu1.keys_to_properties("species_neighbor") -# -# lmax = 1 -# lambdas = np.arange(lmax) -# -# clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) -# cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) -# soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) # -> needs -# soap_cg = soap_cg.keys_to_properties(["spherical_harmonics_l"]).keys_to_samples(["species_center"]) -# n_samples = len(soap_cg[0].samples) -# kernel_cg = np.zeros((n_samples, n_samples)) -# for key, block in soap_cg.items(): -# kernel_cg += block.values.squeeze() @ block.values.squeeze().T -# -# calculator = rascaline.SoapPowerSpectrum(**rascal_hypers) -# nu2 = calculator.compute(frames) -# soap_rascaline = nu2.keys_to_properties(["species_neighbor_1", "species_neighbor_2"]).keys_to_samples(["species_center"]) -# kernel_rascaline = np.zeros((n_samples, n_samples)) -# for key, block in soap_rascaline.items(): -# kernel_rascaline += block.values.squeeze() @ block.values.squeeze().T -# -# # worries me a bit that the rtol is shit, might be missing multiplicity? -# assert np.allclose(kernel_cg, kernel_rascaline, atol=1e-9, rtol=1e-1) -# -# def test_soap_zeros(): -# """ -# Tests if the l1!=l2 values are zero when computing the 3-body invariant (SOAP) -# """ -# frames = ase.Atoms('HO', -# positions=[[0., 0., 0.], [1., 1., 1.]], -# pbc=[False, False, False]) -# -# -# rascal_hypers = { -# "cutoff": 3.0, # Angstrom -# "max_radial": 2, # Exclusive -# "max_angular": 3, # Inclusive -# "atomic_gaussian_width": 0.2, -# "radial_basis": {"Gto": {}}, -# "cutoff_function": {"ShiftedCosine": {"width": 0.5}}, -# "center_atom_weight": 1.0, -# } -# -# calculator = rascaline.SphericalExpansion(**rascal_hypers) -# nu1 = calculator.compute(frames) -# nu1 = nu1.keys_to_properties("species_neighbor") -# -# lmax = 1 -# lambdas = np.arange(lmax) -# -# clebsch_gordan._create_combined_keys(nu1.keys, nu1.keys, lambdas) -# cg_cache = clebsch_gordan.ClebschGordanReal(l_max=rascal_hypers['max_angular']) -# soap_cg = clebsch_gordan._combine_single_center(nu1, nu1, lambdas, cg_cache) -# soap_cg.keys_to_properties("spherical_harmonics_l") -# sliced_blocks = [] -# for key, block in soap_cg.items(): -# idx = block.properties.values[:, block.properties.names.index("l1")] != block.properties.values[:, block.properties.names.index("l2")] -# sliced_block = metatensor.slice_block(block, "properties", Labels(names=block.properties.names, values=block.properties.values[idx])) -# sliced_blocks.append(sliced_block) -# -# assert np.allclose(sliced_block.values, np.zeros(sliced_block.values.shape)) - - -# ============ - - -# ======= Docstring example from _combine_arrays_sparse. Not sure if we need it. -# >>> N_SAMPLES = 30 -# >>> N_Q_PROPERTIES = 10 -# >>> N_P_PROPERTIES = 8 -# >>> L1 = 2 -# >>> L2 = 3 -# >>> LAM = 2 -# >>> arr_1 = np.random.rand(N_SAMPLES, 2 * L1 + 1, N_Q_PROPERTIES) -# >>> arr_2 = np.random.rand(N_SAMPLES, 2 * L2 + 1, N_P_PROPERTIES) -# >>> cg_cache = {(L1, L2, LAM): np.random.rand(2 * L1 + 1, 2 * L2 + 1, 2 * LAM + 1)} -# >>> out1 = _clebsch_gordan_dense(arr_1, arr_2, LAM, cg_cache) -# >>> # (samples l1_m q_features) (samples l2_m p_features), -# >>> # (l1_m l2_m lambda_mu) -# >>> # --> (samples, lambda_mu q_features p_features) -# >>> # in einsum l1_m is l, l2_m is k, lambda_mu is L -# >>> out2 = np.einsum("slq, skp, lkL -> sLqp", arr_1, arr_2, cg_cache[(L1, L2, LAM)]) -# >>> # --> (samples lambda_mu (q_features p_features)) -# >>> out2 = out2.reshape(arr_1.shape[0], 2 * LAM + 1, -1) -# >>> print(np.allclose(out1, out2)) -# True +# ============ Test dispatch to torch/numpy ============ From fc984f21529bbc9414cc0c7f012cc4e196f8ad52 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 16 Nov 2023 10:52:10 +0100 Subject: [PATCH 80/96] Better naming --- .../utils/clebsch_gordan/__init__.py | 8 +- .../utils/clebsch_gordan/clebsch_gordan.py | 197 ++++++++++-------- .../rascaline/tests/utils/clebsch_gordan.py | 130 ++++++------ 3 files changed, 174 insertions(+), 161 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py index 0bca78755..befafb963 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py @@ -1,7 +1,7 @@ from .cg_coefficients import ClebschGordanReal from .clebsch_gordan import ( # noqa - combine_single_center_to_body_order, - combine_single_center_to_body_order_metadata_only, + single_center_combine_to_order, + single_center_combine_metadata_to_order, ) from .rotations import ( # noqa cartesian_rotation, @@ -14,8 +14,8 @@ __all__ = [ "cartesian_rotation", "ClebschGordanReal", - "combine_single_center_to_body_order", - "combine_single_center_to_body_order_metadata_only", + "single_center_combine_metadata_to_order", + "single_center_combine_metadata_to_order", "transform_frame_so3", "transform_frame_o3", "WignerDReal", diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 850b03c86..386c0a372 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -11,30 +11,31 @@ from . import _dispatch + # ====================================================================== # ===== Functions to do CG combinations on single-center descriptors # ====================================================================== -def combine_single_center_to_body_order( - nu_1_tensor: TensorMap, - target_body_order: int, +def single_center_combine_to_order( + nu1_tensor: TensorMap, + correlation_order: int, angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - sort_l_list: Optional[bool] = False, + skip_redundant: Optional[Union[bool, List[bool]]] = False, use_sparse: bool = True, ) -> TensorMap: """ - Takes a nu = 1 (i.e. 2-body) single-center descriptor and combines it - iteratively with itself to generate a descriptor of order - ``target_body_order``. + Takes a correlation order nu = 1 (i.e. 2-body) single-center descriptor and + combines it iteratively with itself to generate a descriptor of correlation + order ``correlation_order``. - ``nu_1_tensor`` may be, for instance, a rascaline.SphericalExpansion or - rascaline.LodeSphericalExpansion. In the first iteration, ``nu_1_tensor`` is + ``nu1_tensor`` may be, for instance, a rascaline.SphericalExpansion or + rascaline.LodeSphericalExpansion. In the first iteration, ``nu1_tensor`` is combined with itself to form a nu = 2 (3-body) descriptor. In each following - iterations the nu = x (x+1)-body descriptor is combined with the - ``nu_1_tensor`` nu = 1 descriptor until the ``target_body_order`` is + iteration the nu = x (x+1)-body descriptor is combined with the + ``nu1_tensor`` nu = 1 descriptor until the target ``correlation_order`` is reached. With no other specification of input args, the full extent of non-zero CG @@ -47,12 +48,12 @@ def combine_single_center_to_body_order( control the angular and parity channels that are output at each step. Passing a list of int in each will apply that selection on the final CG combination step. Passing a list of list of int (of length - ``target_body_order`` - 1) will apply the specified selection at each step. + ``correlation_order`` - 1) will apply the specified selection at each step. - Cost can also be reduced with the ``sort_l_list`` argument. By default, all - combinations of blocks are performed. Some well-defined sets of these + Cost can also be reduced with the ``skip_redundant`` argument. By default, + all combinations of blocks are performed. Some well-defined sets of these combinations are redundant, and can be skipped by setting - ``sort_l_list=True`. This is done by sorting the l list (i.e. the angular + ``skip_redundant=True`. This is done by sorting the l list (i.e. the angular channels of the original nu = 1 blocks previously combined) of the blocks to be combined, and only operating on blocks where l1 <= l2 <= ... <= ln. @@ -60,44 +61,53 @@ def combine_single_center_to_body_order( or dense cache of CG coefficients is used, which depending on the use case can affect the performance. """ - if target_body_order < 1: - raise ValueError("`target_body_order` must be > 0") + if correlation_order < 1: + raise ValueError("`correlation_order` must be > 0") - if _dispatch.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): + if _dispatch.any([len(list(block.gradients())) > 0 for block in nu1_tensor]): raise NotImplementedError( "CG combinations of gradients not currently supported. Check back soon." ) # Standardize the metadata of the input tensor - nu_1_tensor = _standardize_tensor_metadata(nu_1_tensor) + nu1_tensor = _standardize_tensor_metadata(nu1_tensor) # If the desired body order is 1, return the input tensor with standardized # metadata. - if target_body_order == 1: - return nu_1_tensor + if correlation_order == 1: + return nu1_tensor # Pre-compute the metadata needed to perform each CG iteration # Current design choice: only combine a nu = 1 tensor iteratively with # itself, i.e. nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. parity_selection = _parse_selection_filters( - n_iterations=target_body_order - 1, + n_iterations=correlation_order - 1, selection_type="parity", selection=parity_selection, ) angular_selection = _parse_selection_filters( - n_iterations=target_body_order - 1, + n_iterations=correlation_order - 1, selection_type="angular", selection=angular_selection, angular_cutoff=angular_cutoff, ) + if isinstance(skip_redundant, bool): + skip_redundant = [skip_redundant] * (correlation_order - 1) + if not _dispatch.all([isinstance(val, bool) for val in skip_redundant]): + raise TypeError("`skip_redundant` must be a bool or list of bools") + if not len(skip_redundant) == correlation_order - 1: + raise ValueError( + "`skip_redundant` must be a bool or list of bools of length" + " `correlation_order` - 1" + ) combination_metadata = _precompute_metadata( - nu_1_tensor.keys, - nu_1_tensor.keys, - n_iterations=target_body_order - 1, + nu1_tensor.keys, + nu1_tensor.keys, + n_iterations=correlation_order - 1, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, - sort_l_list=sort_l_list, + skip_redundant=skip_redundant, ) # Define the cached CG coefficients, either as sparse dicts or dense arrays. @@ -107,7 +117,7 @@ def combine_single_center_to_body_order( # that are actually used. angular_max = max( _dispatch.concatenate( - [nu_1_tensor.keys.column("spherical_harmonics_l")] + [nu1_tensor.keys.column("spherical_harmonics_l")] + [ metadata[0].column("spherical_harmonics_l") for metadata in combination_metadata @@ -117,10 +127,10 @@ def combine_single_center_to_body_order( cg_cache = ClebschGordanReal(angular_max, use_sparse) # Create a copy of the nu = 1 tensor to combine with itself - nu_x_tensor = nu_1_tensor + nu_x_tensor = nu1_tensor # Iteratively combine block values - for iteration in range(target_body_order - 1): + for iteration in range(correlation_order - 1): # Combine blocks nu_x_blocks = [] # TODO: is there a faster way of iterating over keys/blocks here? @@ -128,7 +138,7 @@ def combine_single_center_to_body_order( # Combine the pair of block values nu_x_block = _combine_single_center_blocks( nu_x_tensor[key_1], - nu_1_tensor[key_2], + nu1_tensor[key_2], nu_x_key["spherical_harmonics_l"], cg_cache, ) @@ -137,10 +147,10 @@ def combine_single_center_to_body_order( nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) # Move the [l1, l2, ...] keys to the properties - if target_body_order > 1: + if correlation_order > 1: nu_x_tensor = nu_x_tensor.keys_to_properties( - [f"l{i}" for i in range(1, target_body_order + 1)] - + [f"k{i}" for i in range(2, target_body_order)] + [f"l{i}" for i in range(1, correlation_order + 1)] + + [f"k{i}" for i in range(2, correlation_order)] ) # Drop the redundant key name "order_nu", and "inversion_sigma" if also @@ -153,83 +163,93 @@ def combine_single_center_to_body_order( return TensorMap(keys=keys, blocks=[b.copy() for b in nu_x_tensor.blocks()]) -def combine_single_center_to_body_order_metadata_only( - nu_1_tensor: TensorMap, - target_body_order: int, +def single_center_combine_metadata_to_order( + nu1_tensor: TensorMap, + correlation_order: int, angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - sort_l_list: Optional[bool] = False, + skip_redundant: Optional[bool] = False, ) -> List[TensorMap]: """ - Performs a pseudo-CG combination of a nu = 1 (i.e. 2-body) single-center - descriptor with itself to generate a descriptor of order - ``target_body_order``. + Performs a pseudo-CG combination of a correlation order nu = 1 (i.e. 2-body) + single-center descriptor with itself to generate a descriptor of order + ``correlation_order``. - A list of TensorMaps is returned, where each has the complete * metadata * - of the TensorMap that would be created by a full CG combination. No actual - CG combinations of block values arrays are performed, instead arrays of - zeros are returned in the output TensorMaps. + A TensorMap with complete metadata is returned, where all block values are + zero. This function is useful for producing pseudo-outputs of a CG iteration calculation with all the correct metadata, but far cheaper than if CG combinations were actually performed. This can help to quantify the size of descriptors produced, and observe the effect of selection filters on the expansion of features at each iteration. + + See :py:func:`single_center_combine_to_order` for documentation on args. """ - if target_body_order <= 1: - raise ValueError("`target_body_order` must be > 1") + if correlation_order <= 1: + raise ValueError("`correlation_order` must be > 1") - if _dispatch.any([len(list(block.gradients())) > 0 for block in nu_1_tensor]): + if _dispatch.any([len(list(block.gradients())) > 0 for block in nu1_tensor]): raise NotImplementedError( "CG combinations of gradients not currently supported. Check back soon." ) # Standardize the metadata of the input tensor - nu_1_tensor = _standardize_tensor_metadata(nu_1_tensor) + nu1_tensor = _standardize_tensor_metadata(nu1_tensor) # If the desired body order is 1, return the input tensor with standardized # metadata. - if target_body_order == 1: - return nu_1_tensor + if correlation_order == 1: + return nu1_tensor # Pre-compute the metadata needed to perform each CG iteration # Current design choice: only combine a nu = 1 tensor iteratively with # itself, i.e. nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. parity_selection = _parse_selection_filters( - n_iterations=target_body_order - 1, + n_iterations=correlation_order - 1, selection_type="parity", selection=parity_selection, ) angular_selection = _parse_selection_filters( - n_iterations=target_body_order - 1, + n_iterations=correlation_order - 1, selection_type="angular", selection=angular_selection, angular_cutoff=angular_cutoff, ) + # Parse the skip_redundant selection filter + if isinstance(skip_redundant, bool): + skip_redundant = [skip_redundant] * (correlation_order - 1) + if not _dispatch.all([isinstance(val, bool) for val in skip_redundant]): + raise TypeError("`skip_redundant` must be a bool or list of bools") + if not len(skip_redundant) == correlation_order - 1: + raise ValueError( + "`skip_redundant` must be a bool or list of bools of length" + " `correlation_order` - 1" + ) + combination_metadata = _precompute_metadata( - nu_1_tensor.keys, - nu_1_tensor.keys, - n_iterations=target_body_order - 1, + nu1_tensor.keys, + nu1_tensor.keys, + n_iterations=correlation_order - 1, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, - sort_l_list=sort_l_list, + skip_redundant=skip_redundant, ) # Create a copy of the nu = 1 tensor to combine with itself - nu_x_tensor = nu_1_tensor + nu_x_tensor = nu1_tensor # Iteratively combine block values - nu_x_tensors = [] - for iteration in range(target_body_order - 1): + for iteration in range(correlation_order - 1): # Combine blocks nu_x_blocks = [] # TODO: is there a faster way of iterating over keys/blocks here? for nu_x_key, key_1, key_2 in zip(*combination_metadata[iteration]): nu_x_block = _combine_single_center_blocks( nu_x_tensor[key_1], - nu_1_tensor[key_2], + nu1_tensor[key_2], nu_x_key["spherical_harmonics_l"], cg_cache=None, return_metadata_only=True, @@ -237,26 +257,19 @@ def combine_single_center_to_body_order_metadata_only( nu_x_blocks.append(nu_x_block) nu_x_keys = combination_metadata[iteration][0] nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) - nu_x_tensors.append(nu_x_tensor) - nu_x_tensors = [ - tensor.keys_to_properties( - [f"l{i}" for i in range(1, tmp_bo + 1)] - + [f"k{i}" for i in range(2, tmp_bo)] - ) - for tmp_bo, tensor in enumerate(nu_x_tensors, start=2) - ] - - return_tensors = [] - for tensor in nu_x_tensors: - keys = tensor.keys.remove(name="order_nu") - if len(_dispatch.unique(tensor.keys.column("inversion_sigma"))) == 1: - keys = keys.remove(name="inversion_sigma") - return_tensors.append( - TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) - ) + nu_x_tensor = nu_x_tensor.keys_to_properties( + [f"l{i}" for i in range(1, correlation_order + 1)] + + [f"k{i}" for i in range(2, correlation_order)] + ) + + # Remove redundant key names + keys = nu_x_tensor.keys.remove(name="order_nu") + if len(_dispatch.unique(nu_x_tensor.keys.column("inversion_sigma"))) == 1: + keys = keys.remove(name="inversion_sigma") + nu_x_tensor = TensorMap(keys=keys, blocks=[b.copy() for b in nu_x_tensor.blocks()]) - return return_tensors + return nu_x_tensor def combine_single_center_one_iteration( @@ -402,10 +415,10 @@ def _precompute_metadata( keys_1: Labels, keys_2: Labels, n_iterations: int, - angular_cutoff: Optional[int] = None, - angular_selection: Optional[List[Union[None, List[int]]]] = None, - parity_selection: Optional[List[Union[None, List[int]]]] = None, - sort_l_list: Optional[bool] = False, + angular_cutoff: int, + angular_selection: List[Union[None, List[int]]], + parity_selection: List[Union[None, List[int]]], + skip_redundant: List[bool], ) -> List[Tuple[Labels, List[List[int]]]]: """ Computes all the metadata needed to perform `n_iterations` of CG combination @@ -424,7 +437,7 @@ def _precompute_metadata( angular_cutoff=angular_cutoff, angular_selection=angular_selection[iteration], parity_selection=parity_selection[iteration], - sort_l_list=sort_l_list, + skip_redundant=skip_redundant[iteration], ) new_keys = i_comb_metadata[0] @@ -471,7 +484,7 @@ def _precompute_metadata_one_iteration( angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, List[int]]] = None, parity_selection: Optional[Union[None, List[int]]] = None, - sort_l_list: Optional[bool] = False, + skip_redundant: bool = False, ) -> Tuple[Labels, List[List[int]]]: """ Given the keys of 2 TensorMaps, returns the keys that would be present after @@ -509,10 +522,8 @@ def _precompute_metadata_one_iteration( ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - Returned is a tuple. - - The first element in the tuple is a Labels object corresponding to the keys - created by a CG combination step. + Returned is a tuple. The first element in the tuple is a Labels object + corresponding to the keys created by a CG combination step. The second element is a list of list of ints. Each sublist corresponds to [lam1, lam2, correction_factor] terms. lam1 and lam2 tracks the lambda @@ -522,6 +533,10 @@ def _precompute_metadata_one_iteration( The `parity_selection` argument can be used to return only keys with certain parities. This must be passed as a list with elements +1 and/or -1. + + The `skip_redundant` arg can be used to skip the calculation of redundant + block combinations - i.e. those that have equivalent sorted l lists. Only + the one for which l1 <= l2 <= ... <= ln is calculated. """ # Get the body order of the first TensorMap. unique_nu = _dispatch.unique(keys_1.column("order_nu")) @@ -627,8 +642,8 @@ def _precompute_metadata_one_iteration( values=_dispatch.int_array_like(new_key_values, like=keys_1.values), ) - # If we want to sort the l list to save computations (i.e. not calculate ) - if not sort_l_list: + # Don't skip the calculation of redundant blocks + if skip_redundant is False: return nu_x_keys, keys_1_entries, keys_2_entries # Now account for multiplicty diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index f98cfe4a7..f3754cd75 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -146,16 +146,16 @@ def test_so3_equivariance( nu_1 = sphex(frames) nu_1_so3 = sphex(frames_so3) - nu_3 = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor=nu_1, - target_body_order=nu_target, + nu_3 = clebsch_gordan.single_center_combine_to_order( + nu1_tensor=nu_1, + correlation_order=nu_target, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, ) - nu_3_so3 = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor=nu_1_so3, - target_body_order=nu_target, + nu_3_so3 = clebsch_gordan.single_center_combine_to_order( + nu1_tensor=nu_1_so3, + correlation_order=nu_target, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, @@ -188,16 +188,16 @@ def test_o3_equivariance( nu_1 = sphex(frames) nu_1_o3 = sphex(frames_o3) - nu_3 = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor=nu_1, - target_body_order=nu_target, + nu_3 = clebsch_gordan.single_center_combine_to_order( + nu1_tensor=nu_1, + correlation_order=nu_target, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, ) - nu_3_o3 = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor=nu_1_o3, - target_body_order=nu_target, + nu_3_o3 = clebsch_gordan.single_center_combine_to_order( + nu1_tensor=nu_1_o3, + correlation_order=nu_target, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, @@ -221,10 +221,10 @@ def test_lambda_soap_vs_powerspectrum(frames): ps = powspec_small_features(frames) # Build a lambda-SOAP - nu_1_tensor = sphex_small_features(frames) - lsoap = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor=nu_1_tensor, - target_body_order=2, + nu1_tensor = sphex_small_features(frames) + lsoap = clebsch_gordan.single_center_combine_to_order( + nu1_tensor=nu1_tensor, + correlation_order=2, angular_selection=[0], ) keys = lsoap.keys.remove(name="spherical_harmonics_l") @@ -260,8 +260,8 @@ def test_lambda_soap_vs_powerspectrum(frames): @pytest.mark.parametrize("frames", [h2_isolated(), h2o_periodic()]) -@pytest.mark.parametrize("target_body_order", [2, 3, 4]) -def test_combine_single_center_norm(frames, target_body_order): +@pytest.mark.parametrize("correlation_order", [2, 3, 4]) +def test_combine_single_center_norm(frames, correlation_order): """ Checks \|ρ^\\nu\| = \|ρ\|^\\nu in the case where l lists are not sorted. If l lists are sorted, thus saving computation of redundant block combinations, @@ -272,35 +272,35 @@ def test_combine_single_center_norm(frames, target_body_order): nu1 = sphex_small_features(frames) # Build higher body order tensor without sorting the l lists - nux = clebsch_gordan.combine_single_center_to_body_order( + nux = clebsch_gordan.single_center_combine_to_order( nu1, - target_body_order=target_body_order, + correlation_order=correlation_order, angular_cutoff=None, angular_selection=None, parity_selection=None, - sort_l_list=False, + skip_redundant=False, use_sparse=True, ) # Build higher body order tensor *with* sorting the l lists - nux_sorted_l = clebsch_gordan.combine_single_center_to_body_order( + nux_sorted_l = clebsch_gordan.single_center_combine_to_order( nu1, - target_body_order=target_body_order, + correlation_order=correlation_order, angular_cutoff=None, angular_selection=None, parity_selection=None, - sort_l_list=True, + skip_redundant=True, use_sparse=True, ) # Standardize the features by passing through the CG combination code but with # no iterations (i.e. body order 1 -> 1) - nu1 = clebsch_gordan.combine_single_center_to_body_order( + nu1 = clebsch_gordan.single_center_combine_to_order( nu1, - target_body_order=1, + correlation_order=1, angular_cutoff=None, angular_selection=None, parity_selection=None, - sort_l_list=False, + skip_redundant=False, use_sparse=True, ) @@ -330,7 +330,7 @@ def test_combine_single_center_norm(frames, target_body_order): nux_sorted_sliced = metatensor.slice(nux_sorted_l, "samples", labels=sample) # Calculate norms - norm_nu1 += get_norm(nu1) ** target_body_order + norm_nu1 += get_norm(nu1) ** correlation_order norm_nux += get_norm(nux) norm_nux_sorted_l += get_norm(nux_sorted_l) @@ -385,20 +385,20 @@ def test_clebsch_gordan_orthogonality(cg_cache_dense, l1, l2): @pytest.mark.parametrize("frames", [h2_isolated(), h2o_isolated()]) -def test_combine_single_center_to_body_order_dense_sparse_agree(frames): +def test_single_center_combine_to_correlation_order_dense_sparse_agree(frames): """ Tests for agreement between nu=3 tensors built using both sparse and dense CG coefficient caches. """ - nu_1_tensor = sphex_small_features(frames) - n_body_sparse = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor, - target_body_order=3, + nu1_tensor = sphex_small_features(frames) + n_body_sparse = clebsch_gordan.single_center_combine_to_order( + nu1_tensor, + correlation_order=3, use_sparse=True, ) - n_body_dense = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor, - target_body_order=3, + n_body_dense = clebsch_gordan.single_center_combine_to_order( + nu1_tensor, + correlation_order=3, use_sparse=False, ) @@ -411,53 +411,51 @@ def test_combine_single_center_to_body_order_dense_sparse_agree(frames): @pytest.mark.parametrize("frames", [h2o_isolated()]) -@pytest.mark.parametrize("target_body_order", [2, 3]) -@pytest.mark.parametrize("sort_l_list", [True, False]) -def test_combine_single_center_to_body_order_metadata_only_metadata_agree( - frames, target_body_order, sort_l_list +@pytest.mark.parametrize("correlation_order", [2, 3]) +@pytest.mark.parametrize("skip_redundant", [True, False]) +def test_single_center_combine_to_correlation_order_metadata_agree( + frames, correlation_order, skip_redundant ): """ Tests that the metadata output from - combine_single_center_to_body_order_metadata_only agrees with the metadata - of the full tensor built using combine_single_center_to_body_order. + single_center_combine_metadata_to_order agrees with the metadata + of the full tensor built using single_center_combine_to_order. """ for nu1 in [sphex_small_features(frames), sphex(frames)]: # Build higher body order tensor with CG computation - nux = clebsch_gordan.combine_single_center_to_body_order( + nux = clebsch_gordan.single_center_combine_to_order( nu1, - target_body_order=target_body_order, + correlation_order=correlation_order, angular_cutoff=None, angular_selection=None, parity_selection=None, - sort_l_list=sort_l_list, + skip_redundant=skip_redundant, use_sparse=True, ) # Build higher body order tensor without CG computation - i.e. metadata - # only. This returns a list of the TensorMaps formed at each CG - # iteration. In this case we only want to compare the metadata of the - # putput after the final iteration. + # only nux_metadata_only = ( - clebsch_gordan.combine_single_center_to_body_order_metadata_only( + clebsch_gordan.single_center_combine_metadata_to_order( nu1, - target_body_order=target_body_order, + correlation_order=correlation_order, angular_cutoff=None, angular_selection=None, parity_selection=None, - sort_l_list=sort_l_list, + skip_redundant=skip_redundant, ) ) - assert metatensor.equal_metadata(nux, nux_metadata_only[-1]) + assert metatensor.equal_metadata(nux, nux_metadata_only) @pytest.mark.parametrize("frames", [h2o_isolated()]) -@pytest.mark.parametrize("target_body_order", [2, 3]) -@pytest.mark.parametrize("sort_l_list", [True, False]) -def test_combine_single_center_to_body_order_metadata_only( - frames, target_body_order, sort_l_list +@pytest.mark.parametrize("correlation_order", [2, 3]) +@pytest.mark.parametrize("skip_redundant", [True, False]) +def test_single_center_combine_to_correlation_order_metadata( + frames, correlation_order, skip_redundant ): """ Performs hard-coded tests on the metadata outputted from - combine_single_center_to_body_order_metadata_only. + single_center_combine_metadata_to_order. TODO: finish! """ @@ -466,36 +464,36 @@ def test_combine_single_center_to_body_order_metadata_only( # # only. This returns a list of the TensorMaps formed at each CG # # iteration. # nux_metadata_only = ( - # clebsch_gordan.combine_single_center_to_body_order_metadata_only( + # clebsch_gordan.single_center_combine_metadata_to_order( # nu1, - # target_body_order=target_body_order, + # correlation_order=correlation_order, # angular_cutoff=None, # angular_selection=None, # parity_selection=None, - # sort_l_list=sort_l_list, + # skip_redundant=skip_redundant, # ) # ) @pytest.mark.parametrize("frames", [h2o_isolated()]) @pytest.mark.parametrize("angular_selection", [None, [1, 2, 4]]) -@pytest.mark.parametrize("sort_l_list", [True, False]) +@pytest.mark.parametrize("skip_redundant", [True, False]) def test_single_center_combine_angular_selection( frames: List[ase.Atoms], angular_selection: List[List[int]], - sort_l_list: bool, + skip_redundant: bool, ): """Tests that the correct angular channels are outputted based on the specified ``angular_cutoff`` and ``angular_selection``.""" nu_1 = sphex(frames) - nu_2 = clebsch_gordan.combine_single_center_to_body_order( - nu_1_tensor=nu_1, - target_body_order=2, + nu_2 = clebsch_gordan.single_center_combine_to_order( + nu1_tensor=nu_1, + correlation_order=2, angular_cutoff=None, angular_selection=angular_selection, parity_selection=None, - sort_l_list=sort_l_list, + skip_redundant=skip_redundant, ) if angular_selection is None: From 27108d085584d93ac0993d927425491b292643ce Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 16 Nov 2023 11:31:16 +0100 Subject: [PATCH 81/96] Match with master --- rascaline-c-api/include/rascaline.h | 11 ++++++++--- rascaline/src/calculators/soap/power_spectrum.rs | 14 +++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/rascaline-c-api/include/rascaline.h b/rascaline-c-api/include/rascaline.h index e063501d0..61077639a 100644 --- a/rascaline-c-api/include/rascaline.h +++ b/rascaline-c-api/include/rascaline.h @@ -217,7 +217,9 @@ typedef struct rascal_system_t { * cutoff passed in the last call to `compute_neighbors`. This function is * only valid to call after a call to `compute_neighbors`. */ - rascal_status_t (*pairs)(const void *user_data, const struct rascal_pair_t **pairs, uintptr_t *count); + rascal_status_t (*pairs)(const void *user_data, + const struct rascal_pair_t **pairs, + uintptr_t *count); /** * This function should set `*pairs` to a pointer to the first element of a * contiguous array containing all pairs in this system containing the atom @@ -229,7 +231,10 @@ typedef struct rascal_system_t { * included both in the return of `pairs_containing(i)` and * `pairs_containing(j)`. */ - rascal_status_t (*pairs_containing)(const void *user_data, uintptr_t center, const struct rascal_pair_t **pairs, uintptr_t *count); + rascal_status_t (*pairs_containing)(const void *user_data, + uintptr_t center, + const struct rascal_pair_t **pairs, + uintptr_t *count); } rascal_system_t; /** @@ -579,4 +584,4 @@ rascal_status_t rascal_profiling_get(const char *format, char *buffer, uintptr_t } // extern "C" #endif // __cplusplus -#endif /* RASCALINE_H */ +#endif /* RASCALINE_H */ \ No newline at end of file diff --git a/rascaline/src/calculators/soap/power_spectrum.rs b/rascaline/src/calculators/soap/power_spectrum.rs index e2a006d0e..879307388 100644 --- a/rascaline/src/calculators/soap/power_spectrum.rs +++ b/rascaline/src/calculators/soap/power_spectrum.rs @@ -368,16 +368,6 @@ impl SoapPowerSpectrum { -f64::sqrt((2 * spherical_harmonics_l + 1) as f64) }; - let spherical_harmonics_l = l.usize(); - - // For consistency with a full Clebsch-Gordan product we need to add - // a `-1^l / sqrt(2 l + 1)` factor to the power spectrum invariants - let normalization = if spherical_harmonics_l % 2 == 0 { - f64::sqrt((2 * spherical_harmonics_l + 1) as f64) - } else { - -f64::sqrt((2 * spherical_harmonics_l + 1) as f64) - }; - SpxPropertiesToCombine { spherical_harmonics_l, normalization, @@ -396,6 +386,8 @@ impl SoapPowerSpectrum { struct SpxPropertiesToCombine<'a> { /// value of l spherical_harmonics_l: usize, + /// normalization factor $-1^l * \sqrt{2 l + 1}$ + normalization: f64, /// position of n1 in the first spherical expansion properties property_1: usize, /// position of n2 in the second spherical expansion properties @@ -974,4 +966,4 @@ mod tests { assert_eq!(block.values().as_array(), 4.0 * block_scaled.values().as_array()); } } -} +} \ No newline at end of file From c5057210f7ffc946a7b2ec5ef1fa8b2da64a5530 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 16 Nov 2023 11:32:35 +0100 Subject: [PATCH 82/96] File endings --- rascaline-c-api/include/rascaline.h | 2 +- rascaline/src/calculators/soap/power_spectrum.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rascaline-c-api/include/rascaline.h b/rascaline-c-api/include/rascaline.h index 61077639a..efcbfbf5a 100644 --- a/rascaline-c-api/include/rascaline.h +++ b/rascaline-c-api/include/rascaline.h @@ -584,4 +584,4 @@ rascal_status_t rascal_profiling_get(const char *format, char *buffer, uintptr_t } // extern "C" #endif // __cplusplus -#endif /* RASCALINE_H */ \ No newline at end of file +#endif /* RASCALINE_H */ diff --git a/rascaline/src/calculators/soap/power_spectrum.rs b/rascaline/src/calculators/soap/power_spectrum.rs index 879307388..911eaaed1 100644 --- a/rascaline/src/calculators/soap/power_spectrum.rs +++ b/rascaline/src/calculators/soap/power_spectrum.rs @@ -966,4 +966,4 @@ mod tests { assert_eq!(block.values().as_array(), 4.0 * block_scaled.values().as_array()); } } -} \ No newline at end of file +} From 6a32b2020ed1691f4b74f9428c6e87482f7ee921 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 20 Nov 2023 15:57:55 +0100 Subject: [PATCH 83/96] Move rotations to test dir --- .../utils/clebsch_gordan/__init__.py | 12 +- .../utils/clebsch_gordan/clebsch_gordan.py | 320 ++++++++++------- .../rascaline/tests/utils/clebsch_gordan.py | 8 +- python/rascaline/tests/utils/rotations.py | 322 ++++++++++++++++++ 4 files changed, 516 insertions(+), 146 deletions(-) create mode 100644 python/rascaline/tests/utils/rotations.py diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py index befafb963..b4bf38509 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py @@ -3,20 +3,10 @@ single_center_combine_to_order, single_center_combine_metadata_to_order, ) -from .rotations import ( # noqa - cartesian_rotation, - transform_frame_so3, - transform_frame_o3, - WignerDReal, -) __all__ = [ - "cartesian_rotation", "ClebschGordanReal", + "single_center_combine_to_order", "single_center_combine_metadata_to_order", - "single_center_combine_metadata_to_order", - "transform_frame_so3", - "transform_frame_o3", - "WignerDReal", ] diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 386c0a372..82c35a557 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -24,8 +24,9 @@ def single_center_combine_to_order( angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, + output_selection: Optional[Union[bool, List[bool]]] = None, use_sparse: bool = True, -) -> TensorMap: +) -> Union[TensorMap, List[TensorMap]]: """ Takes a correlation order nu = 1 (i.e. 2-body) single-center descriptor and combines it iteratively with itself to generate a descriptor of correlation @@ -57,6 +58,10 @@ def single_center_combine_to_order( channels of the original nu = 1 blocks previously combined) of the blocks to be combined, and only operating on blocks where l1 <= l2 <= ... <= ln. + The ``output_selection`` argument can be used to control which CG iteration + steps a TensorMap is output for. By default (i.e. `output_selection=None`), + only the TensorMap from the final CG combination will be output. + Finally, the ``use_sparse`` argument can be used to control whether a sparse or dense cache of CG coefficients is used, which depending on the use case can affect the performance. @@ -77,33 +82,28 @@ def single_center_combine_to_order( if correlation_order == 1: return nu1_tensor + n_iterations = correlation_order - 1 + + # Parse the various selection filters + angular_selection, parity_selection = _parse_int_selections( + n_iterations=n_iterations, + angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + ) + skip_redundant, output_selection = _parse_bool_selections( + n_iterations, + skip_redundant=skip_redundant, + output_selection=output_selection, + ) + # Pre-compute the metadata needed to perform each CG iteration # Current design choice: only combine a nu = 1 tensor iteratively with # itself, i.e. nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. - parity_selection = _parse_selection_filters( - n_iterations=correlation_order - 1, - selection_type="parity", - selection=parity_selection, - ) - angular_selection = _parse_selection_filters( - n_iterations=correlation_order - 1, - selection_type="angular", - selection=angular_selection, - angular_cutoff=angular_cutoff, - ) - if isinstance(skip_redundant, bool): - skip_redundant = [skip_redundant] * (correlation_order - 1) - if not _dispatch.all([isinstance(val, bool) for val in skip_redundant]): - raise TypeError("`skip_redundant` must be a bool or list of bools") - if not len(skip_redundant) == correlation_order - 1: - raise ValueError( - "`skip_redundant` must be a bool or list of bools of length" - " `correlation_order` - 1" - ) combination_metadata = _precompute_metadata( nu1_tensor.keys, nu1_tensor.keys, - n_iterations=correlation_order - 1, + n_iterations=n_iterations, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, @@ -130,10 +130,11 @@ def single_center_combine_to_order( nu_x_tensor = nu1_tensor # Iteratively combine block values - for iteration in range(correlation_order - 1): - # Combine blocks - nu_x_blocks = [] + output_tensors = [] + for iteration, output_tensor in zip(range(n_iterations), output_selection): + tmp_correlation_order = iteration + 2 # TODO: is there a faster way of iterating over keys/blocks here? + nu_x_blocks = [] for nu_x_key, key_1, key_2 in zip(*combination_metadata[iteration]): # Combine the pair of block values nu_x_block = _combine_single_center_blocks( @@ -146,31 +147,42 @@ def single_center_combine_to_order( nu_x_keys = combination_metadata[iteration][0] nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) - # Move the [l1, l2, ...] keys to the properties - if correlation_order > 1: - nu_x_tensor = nu_x_tensor.keys_to_properties( - [f"l{i}" for i in range(1, correlation_order + 1)] - + [f"k{i}" for i in range(2, correlation_order)] - ) + # If this tensor is to be included in the output, move the keys to + # properties and store + if output_tensor is True: + # Move the [l1, l2, ...] keys to the properties + output_tensors.append( + nu_x_tensor.keys_to_properties( + [f"l{i}" for i in range(1, tmp_correlation_order + 1)] + + [f"k{i}" for i in range(2, tmp_correlation_order)] + ) + ) - # Drop the redundant key name "order_nu", and "inversion_sigma" if also - # redundant. TODO: these should be part of the global matadata associated - # with the TensorMap. Awaiting this functionality in metatensor. - keys = nu_x_tensor.keys.remove(name="order_nu") - if len(_dispatch.unique(nu_x_tensor.keys.column("inversion_sigma"))) == 1: - keys = keys.remove(name="inversion_sigma") + # Drop redundant key names. TODO: these should be part of the global + # matadata associated with the TensorMap. Awaiting this functionality in + # metatensor. + for i, tensor in enumerate(output_tensors): + keys = tensor.keys + if len(_dispatch.unique(tensor.keys.column("order_nu"))) == 1: + keys = keys.remove(name="order_nu") + if len(_dispatch.unique(tensor.keys.column("inversion_sigma"))) == 1: + keys = keys.remove(name="inversion_sigma") + output_tensors[i] = TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) - return TensorMap(keys=keys, blocks=[b.copy() for b in nu_x_tensor.blocks()]) + if len(output_tensors) == 1: + return output_tensors[0] + return output_tensors def single_center_combine_metadata_to_order( nu1_tensor: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - skip_redundant: Optional[bool] = False, -) -> List[TensorMap]: + angular_selection: Optional[Union[int, List[int], List[List[int]]]] = None, + parity_selection: Optional[Union[int, List[int], List[List[int]]]] = None, + skip_redundant: Optional[Union[bool, List[bool]]] = False, + output_selection: Optional[Union[bool, List[bool]]] = None, +) -> Union[TensorMap, List[TensorMap]]: """ Performs a pseudo-CG combination of a correlation order nu = 1 (i.e. 2-body) single-center descriptor with itself to generate a descriptor of order @@ -203,35 +215,28 @@ def single_center_combine_metadata_to_order( if correlation_order == 1: return nu1_tensor - # Pre-compute the metadata needed to perform each CG iteration - # Current design choice: only combine a nu = 1 tensor iteratively with - # itself, i.e. nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. - parity_selection = _parse_selection_filters( - n_iterations=correlation_order - 1, - selection_type="parity", - selection=parity_selection, - ) - angular_selection = _parse_selection_filters( - n_iterations=correlation_order - 1, - selection_type="angular", - selection=angular_selection, + n_iterations = correlation_order - 1 + + # Parse the various selection filters + angular_selection, parity_selection = _parse_int_selections( + n_iterations=n_iterations, angular_cutoff=angular_cutoff, + angular_selection=angular_selection, + parity_selection=parity_selection, + ) + skip_redundant, output_selection = _parse_bool_selections( + n_iterations, + skip_redundant=skip_redundant, + output_selection=output_selection, ) - # Parse the skip_redundant selection filter - if isinstance(skip_redundant, bool): - skip_redundant = [skip_redundant] * (correlation_order - 1) - if not _dispatch.all([isinstance(val, bool) for val in skip_redundant]): - raise TypeError("`skip_redundant` must be a bool or list of bools") - if not len(skip_redundant) == correlation_order - 1: - raise ValueError( - "`skip_redundant` must be a bool or list of bools of length" - " `correlation_order` - 1" - ) + # Pre-compute the metadata needed to perform each CG iteration. Current + # design choice: only combine a nu = 1 tensor iteratively with itself, i.e. + # nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. combination_metadata = _precompute_metadata( nu1_tensor.keys, nu1_tensor.keys, - n_iterations=correlation_order - 1, + n_iterations=n_iterations, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, @@ -242,10 +247,11 @@ def single_center_combine_metadata_to_order( nu_x_tensor = nu1_tensor # Iteratively combine block values - for iteration in range(correlation_order - 1): - # Combine blocks - nu_x_blocks = [] + output_tensors = [] + for iteration, output_tensor in zip(range(n_iterations), output_selection): + tmp_correlation_order = iteration + 2 # TODO: is there a faster way of iterating over keys/blocks here? + nu_x_blocks = [] for nu_x_key, key_1, key_2 in zip(*combination_metadata[iteration]): nu_x_block = _combine_single_center_blocks( nu_x_tensor[key_1], @@ -258,21 +264,32 @@ def single_center_combine_metadata_to_order( nu_x_keys = combination_metadata[iteration][0] nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) - nu_x_tensor = nu_x_tensor.keys_to_properties( - [f"l{i}" for i in range(1, correlation_order + 1)] - + [f"k{i}" for i in range(2, correlation_order)] - ) + # If this tensor is to be included in the output, move the keys to + # properties and store + if output_tensor is True: + # Move the [l1, l2, ...] keys to the properties + output_tensors.append( + nu_x_tensor.keys_to_properties( + [f"l{i}" for i in range(1, tmp_correlation_order + 1)] + + [f"k{i}" for i in range(2, tmp_correlation_order)] + ) + ) # Remove redundant key names - keys = nu_x_tensor.keys.remove(name="order_nu") - if len(_dispatch.unique(nu_x_tensor.keys.column("inversion_sigma"))) == 1: - keys = keys.remove(name="inversion_sigma") - nu_x_tensor = TensorMap(keys=keys, blocks=[b.copy() for b in nu_x_tensor.blocks()]) + for i, tensor in enumerate(output_tensors): + keys = tensor.keys + if len(_dispatch.unique(tensor.keys.column("order_nu"))) == 1: + keys = keys.remove(name="order_nu") + if len(_dispatch.unique(tensor.keys.column("inversion_sigma"))) == 1: + keys = keys.remove(name="inversion_sigma") + output_tensors[i] = TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) - return nu_x_tensor + if len(output_tensors) == 1: + return output_tensors[0] + return output_tensors -def combine_single_center_one_iteration( +def single_center_combine( tensor_1: TensorMap, tensor_2: TensorMap, angular_cutoff: Optional[int] = None, @@ -282,7 +299,8 @@ def combine_single_center_one_iteration( ) -> TensorMap: """ Takes two single-center descriptors of arbitrary body order and combines - them in a single CG combination step. + them in a single CG combination step. Tensors could be of different + provenance, i.e. a SphericalExpansion and a LodeSphericalExpansion. """ # TODO: implement! raise NotImplementedError @@ -322,12 +340,12 @@ def _standardize_tensor_metadata(tensor: TensorMap) -> TensorMap: return TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) -def _parse_selection_filters( +def _parse_int_selections( n_iterations: int, - selection_type: str = "parity", - selection: Union[None, int, List[int], List[List[int]]] = None, angular_cutoff: Optional[int] = None, -) -> List[Union[None, List[int]]]: + angular_selection: Optional[Union[int, List[int], List[List[int]]]] = None, + parity_selection: Optional[Union[int, List[int], List[List[int]]]] = None, +) -> List[List[List[int]]]: """ Returns a list of length `n_iterations` with selection filters for each CG combination step, for either `selection_type` "parity" or `selection_type` @@ -351,64 +369,102 @@ def _parse_selection_filters( >= 0. """ if angular_cutoff is not None: - if selection_type != "angular": - raise ValueError( - "`selection_type` must be 'angular' if specifying `angular_cutoff`" - ) if angular_cutoff < 1: raise ValueError("`angular_cutoff` must be >= 1") - if selection is None: - selection = [None] * n_iterations - else: - # If passed as int, use this for the last iteration only - if isinstance(selection, int): - selection = [None] * (n_iterations - 1) + [[selection]] + + selections = [] + for selection_type, selection in zip(["angular", "parity"], [angular_selection, parity_selection]): + if selection is None: + selection = [None] * n_iterations else: - if not isinstance(selection, List): - raise TypeError( - "`selection` must be an int, List[int], or List[List[int]]" - ) - if isinstance(selection[0], int): - selection = [None] * (n_iterations - 1) + [selection] - - # Basic checks - if not isinstance(selection, List): - raise TypeError("`selection` must be an int, List[int], or List[List[int]]") - for slct in selection: - if slct is not None: - if not _dispatch.all([isinstance(val, int) for val in slct]): - raise TypeError( - "`selection` must be an int, List[int], or List[List[int]]" - ) - if selection_type == "parity": - if not _dispatch.all([val in [-1, +1] for val in slct]): - raise ValueError( - "specified layers in `selection` must only contain valid" - " parity values of -1 or +1" - ) - if not _dispatch.all([0 < len(slct) <= 2]): - raise ValueError( - "each parity filter must be a list of length 1 or 2," - " with vals +1 and/or -1" + # If passed as int, use this for the last iteration only + if isinstance(selection, int): + selection = [None] * (n_iterations - 1) + [[selection]] + else: + if not isinstance(selection, List): + raise TypeError( + "`selection` must be an int, List[int], or List[List[int]]" ) - elif selection_type == "angular": - if not _dispatch.all([val >= 0 for val in slct]): - raise ValueError( - "specified layers in `selection` must only contain valid" - " angular channels >= 0" + if isinstance(selection[0], int): + selection = [None] * (n_iterations - 1) + [selection] + + # Basic checks + if not isinstance(selection, List): + raise TypeError("`selection` must be an int, List[int], or List[List[int]]") + for slct in selection: + if slct is not None: + if not _dispatch.all([isinstance(val, int) for val in slct]): + raise TypeError( + "`selection` must be an int, List[int], or List[List[int]]" ) - if angular_cutoff is not None: - if not _dispatch.all([val <= angular_cutoff for val in slct]): + if selection_type == "parity": + if not _dispatch.all([val in [-1, +1] for val in slct]): raise ValueError( "specified layers in `selection` must only contain valid" - " angular channels <= the specified `angular_cutoff`" + " parity values of -1 or +1" ) - else: - raise ValueError( - "`selection_type` must be either 'parity' or 'angular'" - ) + if not _dispatch.all([0 < len(slct) <= 2]): + raise ValueError( + "each parity filter must be a list of length 1 or 2," + " with vals +1 and/or -1" + ) + elif selection_type == "angular": + if not _dispatch.all([val >= 0 for val in slct]): + raise ValueError( + "specified layers in `selection` must only contain valid" + " angular channels >= 0" + ) + if angular_cutoff is not None: + if not _dispatch.all([val <= angular_cutoff for val in slct]): + raise ValueError( + "specified layers in `selection` must only contain valid" + " angular channels <= the specified `angular_cutoff`" + ) + else: + raise ValueError( + "`selection_type` must be either 'parity' or 'angular'" + ) + selections.append(selection) + + return selections + +def _parse_bool_selections( + n_iterations: int, + skip_redundant: Optional[Union[bool, List[bool]]] = False, + output_selection: Optional[Union[bool, List[bool]]] = None, +) -> List[List[bool]]: + """ + Parses the `skip_redundant` and `output_selection` arguments passed to + public functions. + """ + if isinstance(skip_redundant, bool): + skip_redundant = [skip_redundant] * n_iterations + if not _dispatch.all([isinstance(val, bool) for val in skip_redundant]): + raise TypeError("`skip_redundant` must be a bool or list of bools") + if not len(skip_redundant) == n_iterations: + raise ValueError( + "`skip_redundant` must be a bool or list of bools of length" + " `correlation_order` - 1" + ) + if output_selection is None: + output_selection = [False] * (n_iterations - 1) + [True] + else: + if isinstance(output_selection, bool): + output_selection = [True] * n_iterations + if not isinstance(output_selection, List): + raise TypeError("`output_selection` must be passed as a list of bools") + + if not len(output_selection) == n_iterations: + raise ValueError( + "`output_selection` must be a list of bools of length" + " corresponding to the number of CG iterations" + ) + if not _dispatch.all([isinstance(v, bool) for v in output_selection]): + raise TypeError("`output_selection` must be passed as a list of bools") + if not _dispatch.all([isinstance(v, bool) for v in output_selection]): + raise TypeError("`output_selection` must be passed as a list of bools") - return selection + return skip_redundant, output_selection def _precompute_metadata( diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index f3754cd75..9d871d257 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -12,6 +12,8 @@ from metatensor import Labels, TensorBlock, TensorMap from rascaline.utils import clebsch_gordan, PowerSpectrum +from .rotations import WignerDReal, transform_frame_so3, transform_frame_o3 + DATA_ROOT = os.path.join(os.path.dirname(__file__), "data") @@ -68,7 +70,7 @@ def h2o_periodic(): def wigners(lmax: int): - return clebsch_gordan.WignerDReal(lmax=lmax) + return WignerDReal(lmax=lmax) def sphex(frames: List[ase.Atoms]): @@ -140,7 +142,7 @@ def test_so3_equivariance( ): wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) frames_so3 = [ - clebsch_gordan.transform_frame_so3(frame, wig.angles) for frame in frames + transform_frame_so3(frame, wig.angles) for frame in frames ] nu_1 = sphex(frames) @@ -182,7 +184,7 @@ def test_o3_equivariance( ): wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) frames_o3 = [ - clebsch_gordan.transform_frame_o3(frame, wig.angles) for frame in frames + transform_frame_o3(frame, wig.angles) for frame in frames ] nu_1 = sphex(frames) diff --git a/python/rascaline/tests/utils/rotations.py b/python/rascaline/tests/utils/rotations.py new file mode 100644 index 000000000..edf90b3d6 --- /dev/null +++ b/python/rascaline/tests/utils/rotations.py @@ -0,0 +1,322 @@ +""" +Class for generating real Wigner-D matrices, and using them to rotate ASE frames +and TensorMaps of density coefficients in the spherical basis. +""" +from typing import Sequence + +import ase +import numpy as np +from metatensor import TensorBlock, TensorMap +from scipy.spatial.transform import Rotation + +try: + import torch + from torch import Tensor as TorchTensor +except ImportError: + + class TorchTensor: + pass + + +# ===== Functions for transformations in the Cartesian basis ===== + + +def cartesian_rotation(angles: Sequence[float]): + """ + Returns a Cartesian rotation matrix in the appropriate convention (ZYZ, + implicit rotations) to be consistent with the common Wigner D definition. + + `angles` correspond to the alpha, beta, gamma Euler angles in the ZYZ + convention, in radians. + """ + return Rotation.from_euler("ZYZ", angles).as_matrix() + + +def transform_frame_so3(frame: ase.Atoms, angles: Sequence[float]) -> ase.Atoms: + """ + Transforms the positions and cell coordinates of an ASE frame by a SO(3) + rigid rotation. + """ + new_frame = frame.copy() + + # Build cartesian rotation matrix + R = cartesian_rotation(angles) + + # Rotate its positions and cell + new_frame.positions = new_frame.positions @ R.T + new_frame.cell = new_frame.cell @ R.T + + return new_frame + + +def transform_frame_o3(frame: ase.Atoms, angles: Sequence[float]) -> ase.Atoms: + """ + Transforms the positions and cell coordinates of an ASE frame by an O(3) + rotation. This involves a rigid SO(3) rotation of the positions and cell + according to the Euler `angles`, then an inversion by multiplying just the + positions by -1. + """ + new_frame = frame.copy() + + # Build cartesian rotation matrix + R = cartesian_rotation(angles) + + # Rotate its positions and cell + new_frame.positions = new_frame.positions @ R.T + new_frame.cell = new_frame.cell @ R.T + + # Invert the atom positions + new_frame.positions *= -1 + + return new_frame + + +# ===== WignerDReal for transformations in the spherical basis ===== + +class WignerDReal: + """ + A helper class to compute Wigner D matrices given the Euler angles of a rotation, + and apply them to spherical harmonics (or coefficients). Built to function with + real-valued coefficients. + """ + + def __init__(self, lmax: int, angles: Sequence[float] = None): + """ + Initialize the WignerDReal class. + + :param lmax: int, the maximum angular momentum channel for which the + Wigner D matrices are computed + :param angles: Sequence[float], the alpha, beta, gamma Euler angles, in + radians. + """ + self.lmax = lmax + # Randomly generate Euler angles between 0 and 2 pi if none are provided + if angles is None: + angles = np.random.uniform(size=(3)) * 2 * np.pi + self.angles = angles + self.rotation = cartesian_rotation(angles) + + r2c_mats = {} + c2r_mats = {} + for L in range(0, self.lmax + 1): + r2c_mats[L] = np.hstack( + [_r2c(np.eye(2 * L + 1)[i])[:, np.newaxis] for i in range(2 * L + 1)] + ) + c2r_mats[L] = np.conjugate(r2c_mats[L]).T + self.matrices = {} + for L in range(0, self.lmax + 1): + wig = _wigner_d(L, self.angles) + self.matrices[L] = np.real(c2r_mats[L] @ np.conjugate(wig) @ r2c_mats[L]) + + def rotate_coeff_vector( + self, + frame: ase.Atoms, + coeffs: np.ndarray, + lmax: dict, + nmax: dict, + ) -> np.ndarray: + """ + Rotates the irreducible spherical components (ISCs) of basis set + coefficients in the spherical basis passed in as a flat vector. + + Required is the basis set definition specified by ``lmax`` and ``nmax``. + This are dicts of the form: + + lmax = {symbol: lmax_value, ...} + nmax = {(symbol, l): nmax_value, ...} + + where ``symbol`` is the chemical symbol of the atom, ``lmax_value`` is + its corresponding max l channel value. For each combination of species + symbol and lmax, there exists a max radial channel value ``nmax_value``. + + Then, the assumed ordering of basis function coefficients follows a + hierarchy, which can be read as nested loops over the various indices. + Be mindful that some indices range are from 0 to x (exclusive) and + others from 0 to x + 1 (exclusive). The ranges reported below are + ordered. + + 1. Loop over atoms (index ``i``, of chemical species ``a``) in the + structure. ``i`` takes values 0 to N (** exclusive **), where N is the + number of atoms in the structure. + + 2. Loop over spherical harmonics channel (index ``l``) for each atom. + ``l`` takes values from 0 to ``lmax[a] + 1`` (** exclusive **), where + ``a`` is the chemical species of atom ``i``, given by the chemical + symbol at the ``i``th position of ``symbol_list``. + + 3. Loop over radial channel (index ``n``) for each atom ``i`` and + spherical harmonics channel ``l`` combination. ``n`` takes values from 0 + to ``nmax[(a, l)]`` (** exclusive **). + + 4. Loop over spherical harmonics component (index ``m``) for each atom. + ``m`` takes values from ``-l`` to ``l`` (** inclusive **). + + :param frame: the atomic structure in ASE format for which the + coefficients are defined. + :param coeffs: the coefficients in the spherical basis, as a flat + vector. + :param lmax: dict containing the maximum spherical harmonics (l) value + for each atom type. + :param nmax: dict containing the maximum radial channel (n) value for + each combination of atom type and l. + + :return: the rotated coefficients in the spherical basis, as a flat + vector with the same order as the input vector. + """ + # Initialize empty vector for storing the rotated ISCs + rot_vect = np.empty_like(coeffs) + + # Iterate over atomic species of the atoms in the frame + curr_idx = 0 + for symbol in frame.get_chemical_symbols(): + # Get the basis set lmax value for this species + sym_lmax = lmax[symbol] + for l in range(sym_lmax + 1): + # Get the number of radial functions for this species and l value + sym_l_nmax = nmax[(symbol, l)] + # Get the Wigner D Matrix for this l value + wig_mat = self.matrices[l].T + for n in range(sym_l_nmax): + # Retrieve the irreducible spherical component + isc = coeffs[curr_idx : curr_idx + (2 * l + 1)] + # Rotate the ISC and store + rot_isc = isc @ wig_mat + rot_vect[curr_idx : curr_idx + (2 * l + 1)][:] = rot_isc[:] + # Update the start index for the next ISC + curr_idx += 2 * l + 1 + + return rot_vect + + def rotate_tensorblock(self, l: int, block: TensorBlock) -> TensorBlock: + """ + Rotates a TensorBlock ``block``, represented in the spherical basis, + according to the Wigner D Real matrices for the given ``l`` value. + Assumes the components of the block are [("spherical_harmonics_m",),]. + """ + # Get the Wigner matrix for this l value + wig = self.matrices[l].T + + # Copy the block + block_rotated = block.copy() + vals = block_rotated.values + + # Perform the rotation, either with numpy or torch, by taking the + # tensordot product of the irreducible spherical components. Modify + # in-place the values of the copied TensorBlock. + if isinstance(vals, TorchTensor): + wig = torch.tensor(wig) + block_rotated.values[:] = torch.tensordot( + vals.swapaxes(1, 2), wig, dims=1 + ).swapaxes(1, 2) + elif isinstance(block.values, np.ndarray): + block_rotated.values[:] = np.tensordot( + vals.swapaxes(1, 2), wig, axes=1 + ).swapaxes(1, 2) + else: + raise TypeError("TensorBlock values must be a numpy array or torch tensor.") + + return block_rotated + + def transform_tensormap_so3(self, tensor: TensorMap) -> TensorMap: + """ + Transforms a TensorMap by a by an SO(3) rigid rotation using Wigner-D + matrices. + + Assumes the input tensor follows the metadata structure consistent with + those produce by rascaline. + """ + # Retrieve the key and the position of the l value in the key names + keys = tensor.keys + idx_l_value = keys.names.index("spherical_harmonics_l") + + # Iterate over the blocks and rotate + rotated_blocks = [] + for key in keys: + # Retrieve the l value + l = key[idx_l_value] + + # Rotate the block and store + rotated_blocks.append(self.rotate_tensorblock(l, tensor[key])) + + return TensorMap(keys, rotated_blocks) + + def transform_tensormap_o3(self, tensor: TensorMap) -> TensorMap: + """ + Transforms a TensorMap by a by an O(3) transformation: this involves an + SO(3) rigid rotation using Wigner-D Matrices followed by an inversion. + + Assumes the input tensor follows the metadata structure consistent with + those produce by rascaline. + """ + # Retrieve the key and the position of the l value in the key names + keys = tensor.keys + idx_l_value = keys.names.index("spherical_harmonics_l") + + # Iterate over the blocks and rotate + new_blocks = [] + for key in keys: + # Retrieve the l value + l = key[idx_l_value] + + # Rotate the block + new_block = self.rotate_tensorblock(l, tensor[key]) + + # Work out the inversion multiplier according to the convention + inversion_multiplier = 1 + if key["spherical_harmonics_l"] % 2 == 1: + inversion_multiplier *= -1 + + # "inversion_sigma" may not be present if CG iterations haven't been + # performed (i.e. nu=1 rascaline SphericalExpansion) + if "inversion_sigma" in keys.names: + if key["inversion_sigma"] == -1: + inversion_multiplier *= -1 + + # Invert the block by applying the inversion multiplier + new_block = TensorBlock( + values=new_block.values * inversion_multiplier, + samples=new_block.samples, + components=new_block.components, + properties=new_block.properties, + ) + new_blocks.append(new_block) + + return TensorMap(keys, new_blocks) + + +# ===== Helper functions for WignerDReal + + +def _wigner_d(l: int, angles: Sequence[float]) -> np.ndarray: + """ + Computes the Wigner D matrix: + D^l_{mm'}(alpha, beta, gamma) + from sympy and converts it to numerical values. + + `angles` are the alpha, beta, gamma Euler angles (radians, ZYZ convention) + and l the irrep. + """ + try: + from sympy.physics.wigner import wigner_d + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Calculation of Wigner D matrices requires a sympy installation" + ) + return np.complex128(wigner_d(l, *angles)) + + +def _r2c(sp): + """ + Real to complex SPH. Assumes a block with 2l+1 reals corresponding + to real SPH with m indices from -l to +l + """ + + i_sqrt_2 = 1.0 / np.sqrt(2) + + l = (len(sp) - 1) // 2 # infers l from the vector size + rc = np.zeros(len(sp), dtype=np.complex128) + rc[l] = sp[l] + for m in range(1, l + 1): + rc[l + m] = (sp[l + m] + 1j * sp[l - m]) * i_sqrt_2 * (-1) ** m + rc[l - m] = (sp[l + m] - 1j * sp[l - m]) * i_sqrt_2 + return rc From 7fc8c6d80c6ace8f12c34f32b7cc24544ed71034 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 20 Nov 2023 16:09:27 +0100 Subject: [PATCH 84/96] Remove rotations --- .../utils/clebsch_gordan/rotations.py | 325 ------------------ 1 file changed, 325 deletions(-) delete mode 100644 python/rascaline/rascaline/utils/clebsch_gordan/rotations.py diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py b/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py deleted file mode 100644 index b3794e119..000000000 --- a/python/rascaline/rascaline/utils/clebsch_gordan/rotations.py +++ /dev/null @@ -1,325 +0,0 @@ -""" -Class for generating real Wigner-D matrices, and using them to rotate ASE frames -and TensorMaps of density coefficients in the spherical basis. -""" -from typing import Sequence - -import ase -import numpy as np -from metatensor import TensorBlock, TensorMap -from scipy.spatial.transform import Rotation - -try: - import torch - from torch import Tensor as TorchTensor -except ImportError: - - class TorchTensor: - pass - - -# TODO: move to test suite, don't provide as public functions? - - -# ===== Functions for transformations in the Cartesian basis ===== - - -def cartesian_rotation(angles: Sequence[float]): - """ - Returns a Cartesian rotation matrix in the appropriate convention (ZYZ, - implicit rotations) to be consistent with the common Wigner D definition. - - `angles` correspond to the alpha, beta, gamma Euler angles in the ZYZ - convention, in radians. - """ - return Rotation.from_euler("ZYZ", angles).as_matrix() - - -def transform_frame_so3(frame: ase.Atoms, angles: Sequence[float]) -> ase.Atoms: - """ - Transforms the positions and cell coordinates of an ASE frame by a SO(3) - rigid rotation. - """ - new_frame = frame.copy() - - # Build cartesian rotation matrix - R = cartesian_rotation(angles) - - # Rotate its positions and cell - new_frame.positions = new_frame.positions @ R.T - new_frame.cell = new_frame.cell @ R.T - - return new_frame - - -def transform_frame_o3(frame: ase.Atoms, angles: Sequence[float]) -> ase.Atoms: - """ - Transforms the positions and cell coordinates of an ASE frame by an O(3) - rotation. This involves a rigid SO(3) rotation of the positions and cell - according to the Euler `angles`, then an inversion by multiplying just the - positions by -1. - """ - new_frame = frame.copy() - - # Build cartesian rotation matrix - R = cartesian_rotation(angles) - - # Rotate its positions and cell - new_frame.positions = new_frame.positions @ R.T - new_frame.cell = new_frame.cell @ R.T - - # Invert the atom positions - new_frame.positions *= -1 - - return new_frame - - -# ===== WignerDReal for transformations in the spherical basis ===== - -class WignerDReal: - """ - A helper class to compute Wigner D matrices given the Euler angles of a rotation, - and apply them to spherical harmonics (or coefficients). Built to function with - real-valued coefficients. - """ - - def __init__(self, lmax: int, angles: Sequence[float] = None): - """ - Initialize the WignerDReal class. - - :param lmax: int, the maximum angular momentum channel for which the - Wigner D matrices are computed - :param angles: Sequence[float], the alpha, beta, gamma Euler angles, in - radians. - """ - self.lmax = lmax - # Randomly generate Euler angles between 0 and 2 pi if none are provided - if angles is None: - angles = np.random.uniform(size=(3)) * 2 * np.pi - self.angles = angles - self.rotation = cartesian_rotation(angles) - - r2c_mats = {} - c2r_mats = {} - for L in range(0, self.lmax + 1): - r2c_mats[L] = np.hstack( - [_r2c(np.eye(2 * L + 1)[i])[:, np.newaxis] for i in range(2 * L + 1)] - ) - c2r_mats[L] = np.conjugate(r2c_mats[L]).T - self.matrices = {} - for L in range(0, self.lmax + 1): - wig = _wigner_d(L, self.angles) - self.matrices[L] = np.real(c2r_mats[L] @ np.conjugate(wig) @ r2c_mats[L]) - - def rotate_coeff_vector( - self, - frame: ase.Atoms, - coeffs: np.ndarray, - lmax: dict, - nmax: dict, - ) -> np.ndarray: - """ - Rotates the irreducible spherical components (ISCs) of basis set - coefficients in the spherical basis passed in as a flat vector. - - Required is the basis set definition specified by ``lmax`` and ``nmax``. - This are dicts of the form: - - lmax = {symbol: lmax_value, ...} - nmax = {(symbol, l): nmax_value, ...} - - where ``symbol`` is the chemical symbol of the atom, ``lmax_value`` is - its corresponding max l channel value. For each combination of species - symbol and lmax, there exists a max radial channel value ``nmax_value``. - - Then, the assumed ordering of basis function coefficients follows a - hierarchy, which can be read as nested loops over the various indices. - Be mindful that some indices range are from 0 to x (exclusive) and - others from 0 to x + 1 (exclusive). The ranges reported below are - ordered. - - 1. Loop over atoms (index ``i``, of chemical species ``a``) in the - structure. ``i`` takes values 0 to N (** exclusive **), where N is the - number of atoms in the structure. - - 2. Loop over spherical harmonics channel (index ``l``) for each atom. - ``l`` takes values from 0 to ``lmax[a] + 1`` (** exclusive **), where - ``a`` is the chemical species of atom ``i``, given by the chemical - symbol at the ``i``th position of ``symbol_list``. - - 3. Loop over radial channel (index ``n``) for each atom ``i`` and - spherical harmonics channel ``l`` combination. ``n`` takes values from 0 - to ``nmax[(a, l)]`` (** exclusive **). - - 4. Loop over spherical harmonics component (index ``m``) for each atom. - ``m`` takes values from ``-l`` to ``l`` (** inclusive **). - - :param frame: the atomic structure in ASE format for which the - coefficients are defined. - :param coeffs: the coefficients in the spherical basis, as a flat - vector. - :param lmax: dict containing the maximum spherical harmonics (l) value - for each atom type. - :param nmax: dict containing the maximum radial channel (n) value for - each combination of atom type and l. - - :return: the rotated coefficients in the spherical basis, as a flat - vector with the same order as the input vector. - """ - # Initialize empty vector for storing the rotated ISCs - rot_vect = np.empty_like(coeffs) - - # Iterate over atomic species of the atoms in the frame - curr_idx = 0 - for symbol in frame.get_chemical_symbols(): - # Get the basis set lmax value for this species - sym_lmax = lmax[symbol] - for l in range(sym_lmax + 1): - # Get the number of radial functions for this species and l value - sym_l_nmax = nmax[(symbol, l)] - # Get the Wigner D Matrix for this l value - wig_mat = self.matrices[l].T - for n in range(sym_l_nmax): - # Retrieve the irreducible spherical component - isc = coeffs[curr_idx : curr_idx + (2 * l + 1)] - # Rotate the ISC and store - rot_isc = isc @ wig_mat - rot_vect[curr_idx : curr_idx + (2 * l + 1)][:] = rot_isc[:] - # Update the start index for the next ISC - curr_idx += 2 * l + 1 - - return rot_vect - - def rotate_tensorblock(self, l: int, block: TensorBlock) -> TensorBlock: - """ - Rotates a TensorBlock ``block``, represented in the spherical basis, - according to the Wigner D Real matrices for the given ``l`` value. - Assumes the components of the block are [("spherical_harmonics_m",),]. - """ - # Get the Wigner matrix for this l value - wig = self.matrices[l].T - - # Copy the block - block_rotated = block.copy() - vals = block_rotated.values - - # Perform the rotation, either with numpy or torch, by taking the - # tensordot product of the irreducible spherical components. Modify - # in-place the values of the copied TensorBlock. - if isinstance(vals, TorchTensor): - wig = torch.tensor(wig) - block_rotated.values[:] = torch.tensordot( - vals.swapaxes(1, 2), wig, dims=1 - ).swapaxes(1, 2) - elif isinstance(block.values, np.ndarray): - block_rotated.values[:] = np.tensordot( - vals.swapaxes(1, 2), wig, axes=1 - ).swapaxes(1, 2) - else: - raise TypeError("TensorBlock values must be a numpy array or torch tensor.") - - return block_rotated - - def transform_tensormap_so3(self, tensor: TensorMap) -> TensorMap: - """ - Transforms a TensorMap by a by an SO(3) rigid rotation using Wigner-D - matrices. - - Assumes the input tensor follows the metadata structure consistent with - those produce by rascaline. - """ - # Retrieve the key and the position of the l value in the key names - keys = tensor.keys - idx_l_value = keys.names.index("spherical_harmonics_l") - - # Iterate over the blocks and rotate - rotated_blocks = [] - for key in keys: - # Retrieve the l value - l = key[idx_l_value] - - # Rotate the block and store - rotated_blocks.append(self.rotate_tensorblock(l, tensor[key])) - - return TensorMap(keys, rotated_blocks) - - def transform_tensormap_o3(self, tensor: TensorMap) -> TensorMap: - """ - Transforms a TensorMap by a by an O(3) transformation: this involves an - SO(3) rigid rotation using Wigner-D Matrices followed by an inversion. - - Assumes the input tensor follows the metadata structure consistent with - those produce by rascaline. - """ - # Retrieve the key and the position of the l value in the key names - keys = tensor.keys - idx_l_value = keys.names.index("spherical_harmonics_l") - - # Iterate over the blocks and rotate - new_blocks = [] - for key in keys: - # Retrieve the l value - l = key[idx_l_value] - - # Rotate the block - new_block = self.rotate_tensorblock(l, tensor[key]) - - # Work out the inversion multiplier according to the convention - inversion_multiplier = 1 - if key["spherical_harmonics_l"] % 2 == 1: - inversion_multiplier *= -1 - - # "inversion_sigma" may not be present if CG iterations haven't been - # performed (i.e. nu=1 rascaline SphericalExpansion) - if "inversion_sigma" in keys.names: - if key["inversion_sigma"] == -1: - inversion_multiplier *= -1 - - # Invert the block by applying the inversion multiplier - new_block = TensorBlock( - values=new_block.values * inversion_multiplier, - samples=new_block.samples, - components=new_block.components, - properties=new_block.properties, - ) - new_blocks.append(new_block) - - return TensorMap(keys, new_blocks) - - -# ===== Helper functions for WignerDReal - - -def _wigner_d(l: int, angles: Sequence[float]) -> np.ndarray: - """ - Computes the Wigner D matrix: - D^l_{mm'}(alpha, beta, gamma) - from sympy and converts it to numerical values. - - `angles` are the alpha, beta, gamma Euler angles (radians, ZYZ convention) - and l the irrep. - """ - try: - from sympy.physics.wigner import wigner_d - except ModuleNotFoundError: - raise ModuleNotFoundError( - "Calculation of Wigner D matrices requires a sympy installation" - ) - return np.complex128(wigner_d(l, *angles)) - - -def _r2c(sp): - """ - Real to complex SPH. Assumes a block with 2l+1 reals corresponding - to real SPH with m indices from -l to +l - """ - - i_sqrt_2 = 1.0 / np.sqrt(2) - - l = (len(sp) - 1) // 2 # infers l from the vector size - rc = np.zeros(len(sp), dtype=np.complex128) - rc[l] = sp[l] - for m in range(1, l + 1): - rc[l + m] = (sp[l + m] + 1j * sp[l - m]) * i_sqrt_2 * (-1) ** m - rc[l - m] = (sp[l + m] - 1j * sp[l - m]) * i_sqrt_2 - return rc From c143657c121e35948c56f582bd9a6a6cf9543d42 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 20 Nov 2023 20:57:13 +0100 Subject: [PATCH 85/96] First attempt sparse accumulation of products --- .../utils/clebsch_gordan/_dispatch.py | 59 ++++++++++++---- .../utils/clebsch_gordan/cg_coefficients.py | 70 ++++++++++++++++--- .../utils/clebsch_gordan/clebsch_gordan.py | 25 +++---- 3 files changed, 114 insertions(+), 40 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index c56779e6d..37c1850f9 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -4,6 +4,12 @@ from typing import List, Optional, Union import numpy as np +from cg_coefficients import HAS_MOPS + +if HAS_MOPS: + from mops import sparse_accumulation_of_products as sap + + try: import torch from torch import Tensor as TorchTensor @@ -20,7 +26,7 @@ class TorchTensor: # ============ CG combinations ============ - +@profile def combine_arrays( arr_1: Union[np.ndarray, TorchTensor], arr_2: Union[np.ndarray, TorchTensor], @@ -81,6 +87,7 @@ def combine_arrays( return dense_combine(arr_1, arr_2, lam, cg_cache) +# @profile def sparse_combine( arr_1: Union[np.ndarray, TorchTensor], arr_2: Union[np.ndarray, TorchTensor], @@ -104,25 +111,47 @@ def sparse_combine( :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] """ - if isinstance(arr_1, np.ndarray) or isinstance(arr_1, TorchTensor): - # Samples dimensions must be the same - assert arr_1.shape[0] == arr_2.shape[0] + # Samples dimensions must be the same + assert arr_1.shape[0] == arr_2.shape[0] - # Define other useful dimensions - n_i = arr_1.shape[0] # number of samples - n_p = arr_1.shape[2] # number of properties in arr_1 - n_q = arr_2.shape[2] # number of properties in arr_2 + # Infer l1 and l2 from the len of the length of axis 1 of each tensor + l1 = (arr_1.shape[1] - 1) // 2 + l2 = (arr_2.shape[1] - 1) // 2 - # Infer l1 and l2 from the len of the length of axis 1 of each tensor - l1 = (arr_1.shape[1] - 1) // 2 - l2 = (arr_2.shape[1] - 1) // 2 + # Define other useful dimensions + n_i = arr_1.shape[0] # number of samples + n_p = arr_1.shape[2] # number of properties in arr_1 + n_q = arr_2.shape[2] # number of properties in arr_2 + if return_empty_array: + return zeros_like((n_i, 2 * lam + 1, n_p * n_q), like=arr_1) + + if HAS_MOPS: + # Reshape + arr_1 = np.repeat(arr_1[:, :, :, None], n_q, axis=3).reshape( + n_i, 2 * l1 + 1, n_p * n_q + ) + arr_2 = np.repeat(arr_2[:, :, None, :], n_p, axis=2).reshape( + n_i, 2 * l2 + 1, n_p * n_q + ) + + arr_1 = swapaxes(arr_1, 1, 2).reshape(n_i * n_p * n_q, 2 * l1 + 1) + arr_2 = swapaxes(arr_2, 1, 2).reshape(n_i * n_p * n_q, 2 * l2 + 1) + + # Do SAP + arr_out = sap(arr_1, arr_2, *cg_cache._coeffs[(l1, l2, lam)], n_O=2 * lam + 1) + assert arr_out.shape == (n_i * n_p * n_q, 2 * lam + 1) + + # Reshape back + arr_out = arr_out.reshape(n_i, n_p * n_q, 2 * lam + 1) + arr_out = swapaxes(arr_out, 1, 2) + + return arr_out + + if isinstance(arr_1, np.ndarray) or isinstance(arr_1, TorchTensor): # Initialise output array arr_out = zeros_like((n_i, 2 * lam + 1, n_p * n_q), like=arr_1) - if return_empty_array: - return arr_out - # Get the corresponding Clebsch-Gordan coefficients cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] @@ -305,4 +334,4 @@ def swapaxes(array, axis0: int, axis1: int): elif isinstance(array, np.ndarray): return np.swapaxes(array, axis0, axis1) else: - raise TypeError(UNKNOWN_ARRAY_TYPE) \ No newline at end of file + raise TypeError(UNKNOWN_ARRAY_TYPE) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py index 7127464e7..8e2a4dcb7 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py @@ -5,6 +5,21 @@ import wigners +try: + from mops import sparse_accumulation_of_products as sap + HAS_MOPS = False +except ImportError: + import warnings + warnings.warn( + "MOPS not installed. For performance it is recommended to install" + " from https://github.com/lab-cosmo/mops . Falling back to numpy." + ) + HAS_MOPS = False + +if HAS_MOPS: + print("USING MOPS") + + class ClebschGordanReal: """ Class for computing Clebsch-Gordan coefficients for real spherical @@ -30,11 +45,19 @@ class ClebschGordanReal: the `l2` angular channel into the irreducible tensor of order `lambda`. """ - def __init__(self, lambda_max: int, sparse: bool = True): + def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MOPS): + if use_mops is True: + if sparse is False: + raise ValueError( + "use_mops=True requires sparse=True, but sparse=False was passed." + ) self._lambda_max = lambda_max self._sparse = sparse + self._use_mops = use_mops self._coeffs = ClebschGordanReal.build_coeff_dict( - self._lambda_max, self._sparse + self._lambda_max, + self._sparse, + self._use_mops, ) @property @@ -45,12 +68,16 @@ def lambda_max(self): def sparse(self): return self._sparse + @property + def use_mops(self): + return self._use_mops + @property def coeffs(self): return self._coeffs @staticmethod - def build_coeff_dict(lambda_max: int, sparse: bool): + def build_coeff_dict(lambda_max: int, sparse: bool, use_mops: bool): """ Builds a dictionary of Clebsch-Gordan coefficients for all possible combination of l1 and l2, up to lambda_max. @@ -88,12 +115,37 @@ def build_coeff_dict(lambda_max: int, sparse: bool): cg_l1l2lam = np.imag(real_cg) if sparse: - # if sparse we make a dictionary out of the matrix - nonzeros_cg_coeffs_idx = np.where(np.abs(cg_l1l2lam) > 1e-15) - cg_l1l2lam = { - (m1, m2, mu): cg_l1l2lam[m1, m2, mu] - for m1, m2, mu in zip(*nonzeros_cg_coeffs_idx) - } + # Find the m1, m2, mu idxs of the nonzero CG coeffs + nonzeros_cg_coeffs_idx = np.where( + np.abs(cg_l1l2lam) > 1e-15 + ) + if use_mops: + # Store CG coeffs in a specific format for use in + # MOPS. Here we need the m1, m2, mu, and CG coeffs + # to be stored as separate 1D arrays. + m1_arr, m2_arr, mu_arr, C_arr = [], [], [], [] + for m1, m2, mu in zip(*nonzeros_cg_coeffs_idx): + m1_arr.append(m1) + m2_arr.append(m2) + mu_arr.append(mu) + C_arr.append(cg_l1l2lam[m1, m2, mu]) + + # Reorder the arrays based on sorted mu values + mu_idxs = np.argsort(mu_arr) + m1_arr = np.array(m1_arr)[mu_idxs] + m2_arr = np.array(m2_arr)[mu_idxs] + mu_arr = np.array(mu_arr)[mu_idxs] + C_arr = np.array(C_arr)[mu_idxs] + cg_l1l2lam = (C_arr, m1_arr, m2_arr, mu_arr) + else: + # Otherwise fall back to torch/numpy and store as + # sparse dicts. + cg_l1l2lam = { + (m1, m2, mu): cg_l1l2lam[m1, m2, mu] + for m1, m2, mu in zip(*nonzeros_cg_coeffs_idx) + } + + # Store coeff_dict[(l1, l2, lam)] = cg_l1l2lam return coeff_dict diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 82c35a557..70bbbeb96 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -9,14 +9,14 @@ from .cg_coefficients import ClebschGordanReal from . import _dispatch - +# from cg_coefficients import ClebschGordanReal +# import _dispatch # ====================================================================== # ===== Functions to do CG combinations on single-center descriptors # ====================================================================== - def single_center_combine_to_order( nu1_tensor: TensorMap, correlation_order: int, @@ -25,7 +25,7 @@ def single_center_combine_to_order( parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, - use_sparse: bool = True, + use_sparse: bool = True, # remove ) -> Union[TensorMap, List[TensorMap]]: """ Takes a correlation order nu = 1 (i.e. 2-body) single-center descriptor and @@ -71,7 +71,8 @@ def single_center_combine_to_order( if _dispatch.any([len(list(block.gradients())) > 0 for block in nu1_tensor]): raise NotImplementedError( - "CG combinations of gradients not currently supported. Check back soon." + "Clebsch Gordan combinations with gradients not yet implemented." + " Use metatensor.remove_gradients to remove gradients from the input." ) # Standardize the metadata of the input tensor @@ -186,18 +187,11 @@ def single_center_combine_metadata_to_order( """ Performs a pseudo-CG combination of a correlation order nu = 1 (i.e. 2-body) single-center descriptor with itself to generate a descriptor of order - ``correlation_order``. + ``correlation_order``. Obnly - A TensorMap with complete metadata is returned, where all block values are - zero. - - This function is useful for producing pseudo-outputs of a CG iteration - calculation with all the correct metadata, but far cheaper than if CG - combinations were actually performed. This can help to quantify the size of - descriptors produced, and observe the effect of selection filters on the - expansion of features at each iteration. - - See :py:func:`single_center_combine_to_order` for documentation on args. + TensorMap(s) with complete metadata is returned, where all block values are + zero. See :py:func:`single_center_combine_to_order` for documentation on + args. """ if correlation_order <= 1: raise ValueError("`correlation_order` must be > 1") @@ -741,7 +735,6 @@ def _precompute_metadata_one_iteration( # ===== Functions to perform the CG combinations of blocks # ================================================================== - def _combine_single_center_blocks( block_1: TensorBlock, block_2: TensorBlock, From 995ca0db890817a53e4ad5bceda332e592798a10 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 20 Nov 2023 21:42:48 +0100 Subject: [PATCH 86/96] Remove profile --- .../utils/clebsch_gordan/_dispatch.py | 3 +- .../utils/clebsch_gordan/cg_coefficients.py | 45 +++++++++++-------- .../utils/clebsch_gordan/clebsch_gordan.py | 2 - 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index 37c1850f9..f7a0a7024 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -26,7 +26,7 @@ class TorchTensor: # ============ CG combinations ============ -@profile + def combine_arrays( arr_1: Union[np.ndarray, TorchTensor], arr_2: Union[np.ndarray, TorchTensor], @@ -87,7 +87,6 @@ def combine_arrays( return dense_combine(arr_1, arr_2, lam, cg_cache) -# @profile def sparse_combine( arr_1: Union[np.ndarray, TorchTensor], arr_2: Union[np.ndarray, TorchTensor], diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py index 8e2a4dcb7..e03cd1c93 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py @@ -7,18 +7,11 @@ try: from mops import sparse_accumulation_of_products as sap - HAS_MOPS = False + + HAS_MOPS = True except ImportError: - import warnings - warnings.warn( - "MOPS not installed. For performance it is recommended to install" - " from https://github.com/lab-cosmo/mops . Falling back to numpy." - ) HAS_MOPS = False -if HAS_MOPS: - print("USING MOPS") - class ClebschGordanReal: """ @@ -46,14 +39,32 @@ class ClebschGordanReal: """ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MOPS): - if use_mops is True: - if sparse is False: - raise ValueError( - "use_mops=True requires sparse=True, but sparse=False was passed." - ) self._lambda_max = lambda_max self._sparse = sparse - self._use_mops = use_mops + + if sparse: + if not HAS_MOPS: + import warnings + + warnings.warn( + "It is recommended to use MOPS for sparse accumulation. " + " This can be installed from https://github.com/lab-cosmo/mops ." + " Falling back to numpy for now." + ) + self._use_mops = False + else: + print("USING MOPS") + self._use_mops = True + + else: + if HAS_MOPS: + import warnings + + warnings.warn( + "MOPS is installed, but not being used as dense operations chosen." + ) + self._use_mops = False + self._coeffs = ClebschGordanReal.build_coeff_dict( self._lambda_max, self._sparse, @@ -116,9 +127,7 @@ def build_coeff_dict(lambda_max: int, sparse: bool, use_mops: bool): if sparse: # Find the m1, m2, mu idxs of the nonzero CG coeffs - nonzeros_cg_coeffs_idx = np.where( - np.abs(cg_l1l2lam) > 1e-15 - ) + nonzeros_cg_coeffs_idx = np.where(np.abs(cg_l1l2lam) > 1e-15) if use_mops: # Store CG coeffs in a specific format for use in # MOPS. Here we need the m1, m2, mu, and CG coeffs diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 70bbbeb96..664007bdf 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -9,8 +9,6 @@ from .cg_coefficients import ClebschGordanReal from . import _dispatch -# from cg_coefficients import ClebschGordanReal -# import _dispatch # ====================================================================== From a36c3fde02d6c3af1cc9cd131e8e3bfd35c72f0d Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Tue, 21 Nov 2023 15:08:03 +0100 Subject: [PATCH 87/96] Make the CG cache module private --- .../utils/clebsch_gordan/__init__.py | 2 -- .../{cg_coefficients.py => _cg_cache.py} | 13 +++++++++++- .../utils/clebsch_gordan/_dispatch.py | 2 +- .../utils/clebsch_gordan/clebsch_gordan.py | 20 +++++++++++++------ 4 files changed, 27 insertions(+), 10 deletions(-) rename python/rascaline/rascaline/utils/clebsch_gordan/{cg_coefficients.py => _cg_cache.py} (96%) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py index b4bf38509..c8192d454 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py @@ -1,4 +1,3 @@ -from .cg_coefficients import ClebschGordanReal from .clebsch_gordan import ( # noqa single_center_combine_to_order, single_center_combine_metadata_to_order, @@ -6,7 +5,6 @@ __all__ = [ - "ClebschGordanReal", "single_center_combine_to_order", "single_center_combine_metadata_to_order", ] diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py similarity index 96% rename from python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py rename to python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py index e03cd1c93..eec6c8ebe 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/cg_coefficients.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py @@ -1,5 +1,6 @@ """ -Module that stores the ClebschGordanReal class for computing CG coefficients. +Module that stores the ClebschGordanReal class for computing and caching Clebsch +Gordan coefficients for use in CG combinations. """ import numpy as np import wigners @@ -13,6 +14,11 @@ HAS_MOPS = False +# ================================= +# ===== ClebschGordanReal class +# ================================= + + class ClebschGordanReal: """ Class for computing Clebsch-Gordan coefficients for real spherical @@ -160,6 +166,11 @@ def build_coeff_dict(lambda_max: int, sparse: bool, use_mops: bool): return coeff_dict +# ============================ +# ===== Helper functions +# ============================ + + def _real2complex(lam: int) -> np.ndarray: """ Computes a matrix that can be used to convert from real to complex-valued diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index f7a0a7024..0ce9c45de 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -4,7 +4,7 @@ from typing import List, Optional, Union import numpy as np -from cg_coefficients import HAS_MOPS +from _cg_cache import HAS_MOPS if HAS_MOPS: from mops import sparse_accumulation_of_products as sap diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 664007bdf..68adb0b4a 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -7,8 +7,8 @@ import metatensor from metatensor import Labels, TensorBlock, TensorMap -from .cg_coefficients import ClebschGordanReal from . import _dispatch +from ._cg_cache import ClebschGordanReal # ====================================================================== @@ -166,7 +166,9 @@ def single_center_combine_to_order( keys = keys.remove(name="order_nu") if len(_dispatch.unique(tensor.keys.column("inversion_sigma"))) == 1: keys = keys.remove(name="inversion_sigma") - output_tensors[i] = TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) + output_tensors[i] = TensorMap( + keys=keys, blocks=[b.copy() for b in tensor.blocks()] + ) if len(output_tensors) == 1: return output_tensors[0] @@ -274,7 +276,9 @@ def single_center_combine_metadata_to_order( keys = keys.remove(name="order_nu") if len(_dispatch.unique(tensor.keys.column("inversion_sigma"))) == 1: keys = keys.remove(name="inversion_sigma") - output_tensors[i] = TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) + output_tensors[i] = TensorMap( + keys=keys, blocks=[b.copy() for b in tensor.blocks()] + ) if len(output_tensors) == 1: return output_tensors[0] @@ -365,7 +369,9 @@ def _parse_int_selections( raise ValueError("`angular_cutoff` must be >= 1") selections = [] - for selection_type, selection in zip(["angular", "parity"], [angular_selection, parity_selection]): + for selection_type, selection in zip( + ["angular", "parity"], [angular_selection, parity_selection] + ): if selection is None: selection = [None] * n_iterations else: @@ -420,6 +426,7 @@ def _parse_int_selections( return selections + def _parse_bool_selections( n_iterations: int, skip_redundant: Optional[Union[bool, List[bool]]] = False, @@ -442,10 +449,10 @@ def _parse_bool_selections( output_selection = [False] * (n_iterations - 1) + [True] else: if isinstance(output_selection, bool): - output_selection = [True] * n_iterations + output_selection = [output_selection] * n_iterations if not isinstance(output_selection, List): raise TypeError("`output_selection` must be passed as a list of bools") - + if not len(output_selection) == n_iterations: raise ValueError( "`output_selection` must be a list of bools of length" @@ -733,6 +740,7 @@ def _precompute_metadata_one_iteration( # ===== Functions to perform the CG combinations of blocks # ================================================================== + def _combine_single_center_blocks( block_1: TensorBlock, block_2: TensorBlock, From 0521d44431c5aa781392d7738eaf99a729b32972 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 22 Nov 2023 09:15:40 +0100 Subject: [PATCH 88/96] Add mops to the dependencies --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 6e2fe8700..ff608e664 100644 --- a/tox.ini +++ b/tox.ini @@ -62,6 +62,7 @@ deps = scipy sympy wigners + git+https://github.com/lab-cosmo/mops commands = pytest {[testenv]test_options} {posargs} From 80ef84d5c789cf320d32435007796c2c0f87c91f Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 22 Nov 2023 09:33:56 +0100 Subject: [PATCH 89/96] New public API function names --- .../utils/clebsch_gordan/__init__.py | 8 +- .../utils/clebsch_gordan/_cg_cache.py | 95 +++- .../utils/clebsch_gordan/_dispatch.py | 14 +- .../utils/clebsch_gordan/clebsch_gordan.py | 453 +++++++++--------- .../rascaline/tests/utils/clebsch_gordan.py | 138 +++--- 5 files changed, 381 insertions(+), 327 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py index c8192d454..935cb51ae 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py @@ -1,10 +1,10 @@ from .clebsch_gordan import ( # noqa - single_center_combine_to_order, - single_center_combine_metadata_to_order, + correlate_density, + correlate_density_metadata, ) __all__ = [ - "single_center_combine_to_order", - "single_center_combine_metadata_to_order", + "correlate_density", + "correlate_density_metadata", ] diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py index eec6c8ebe..0ed6c687f 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py @@ -25,23 +25,89 @@ class ClebschGordanReal: harmonics. Stores the coefficients in a dictionary in the `self.coeffs` attribute, - which is built at initialization. This dictionary has the form: + which is built at initialization. There are 3 current use cases for the + format of these coefficients. + + Case 1: standard sparse format. + + Each dictionary entry is a dictionary with entries for each (m1, m2, mu) + combination. { - (l1, l2, lambda): [ - np.ndarray([m1, m2, cg]), - ... + (l1, l2, lambda): { + (m1, m2, mu) : cg_{m1, m2, mu}^{l1, l2, lambda} for m1 in range(-l1, l1 + 1), for m2 in range(-l2, l2 + 1), - ], + }, ... - for lambda in range(0, l) + for l1 in range(0, l1_list) + for l2 in range(0, l2_list) + for lambda in range(0, range(|l1 - l2|, ..., |l1 + l2|)) } - where `cg`, i.e. the third value in each array, is the Clebsch-Gordan - coefficient that describes the combination of the `m1` irreducible - component of the `l1` angular channel and the `m2` irreducible component of - the `l2` angular channel into the irreducible tensor of order `lambda`. + Case 2: standard dense format. + + Each dictionary entry is a dense array with shape (2 * l1 + 1, 2 * l2 + 1, 2 + * lambda + 1). + + { + (l1, l2, lambda): + array( + cg_{m1, m2, mu}^{l1, l2, lambda} + ... + for m1 in range(-l1, l1 + 1), + for m2 in range(-l2, l2 + 1), + for mu in range(-lambda, lambda + 1), + + shape=(2 * l1 + 1, 2 * l2 + 1, 2 * lambda + 1), + ) + ... + for l1 in range(0, l1_list) + for l2 in range(0, l2_list) + for lambda in range(0, range(|l1 - l2|, ..., |l1 + l2|)) + } + + Case 3: MOPS sparse format. + + Each dictionary entry contains a tuple with four 1D arrays, corresponding to + the CG coeffs and m1, m2, mu indices respectively. All of these arrays are + sorted according to the mu index. This format is used for Sparse + Accumulation of Products (SAP) as implemented in MOPS. + + { + (l1, l2, lambda): + ( + [ + cg_{m1, m2, mu}^{l1, l2, lambda} + ... + for m1 in range(-l1, l1 + 1), + for m2 in range(-l2, l2 + 1), + for mu in range(-lambda, lambda + 1) + ], + [ + m1 for m1 in range(-l1, l1 + 1), + ], + [ + m2 for m2 in range(-l2, l2 + 1), + ], + [ + mu for mu in range(-lambda, lambda + 1), + ], + ) + + + } + + where `cg_{m1, m2, mu}^{l1, l2, lambda}` is the Clebsch-Gordan coefficient + that describes the combination of the `m1` irreducible component of the `l1` + angular channel and the `m2` irreducible component of the `l2` angular + channel into the irreducible tensor of order `lambda`. + + :param lambda_max: maximum lambda value to compute CG coefficients for. + :param sparse: whether to store the CG coefficients in sparse format. + :param use_mops: whether to store the CG coefficients in MOPS sparse format. + This is recommended as the default for sparse accumulation, but can only + be used if Mops is installed. """ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MOPS): @@ -54,12 +120,15 @@ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MO warnings.warn( "It is recommended to use MOPS for sparse accumulation. " - " This can be installed from https://github.com/lab-cosmo/mops ." + " This can be installed with ``pip install" + " git+https://github.com/lab-cosmo/mops`." " Falling back to numpy for now." ) self._use_mops = False else: - print("USING MOPS") + print( + "Backend Clebsch Gordan tensor products will be performed with Mops." + ) self._use_mops = True else: @@ -67,7 +136,7 @@ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MO import warnings warnings.warn( - "MOPS is installed, but not being used as dense operations chosen." + "Mops is installed, but not being used as dense operations chosen." ) self._use_mops = False diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index 0ce9c45de..9cb75627f 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -4,7 +4,7 @@ from typing import List, Optional, Union import numpy as np -from _cg_cache import HAS_MOPS +from ._cg_cache import HAS_MOPS if HAS_MOPS: from mops import sparse_accumulation_of_products as sap @@ -76,12 +76,12 @@ def combine_arrays( """ # If just precomputing metadata, return an empty array if return_empty_array: - return sparse_combine(arr_1, arr_2, lam, cg_cache, True) + return sparse_combine(arr_1, arr_2, lam, cg_cache, return_empty_array=True) # Otherwise, perform the CG combination # Spare CG cache if cg_cache.sparse: - return sparse_combine(arr_1, arr_2, lam, cg_cache, False) + return sparse_combine(arr_1, arr_2, lam, cg_cache, return_empty_array=False) # Dense CG cache return dense_combine(arr_1, arr_2, lam, cg_cache) @@ -122,10 +122,10 @@ def sparse_combine( n_p = arr_1.shape[2] # number of properties in arr_1 n_q = arr_2.shape[2] # number of properties in arr_2 - if return_empty_array: + if return_empty_array: # used when only computing metadata return zeros_like((n_i, 2 * lam + 1, n_p * n_q), like=arr_1) - if HAS_MOPS: + if isinstance(arr_1, np.ndarray) and HAS_MOPS: # Reshape arr_1 = np.repeat(arr_1[:, :, :, None], n_q, axis=3).reshape( n_i, 2 * l1 + 1, n_p * n_q @@ -138,7 +138,9 @@ def sparse_combine( arr_2 = swapaxes(arr_2, 1, 2).reshape(n_i * n_p * n_q, 2 * l2 + 1) # Do SAP - arr_out = sap(arr_1, arr_2, *cg_cache._coeffs[(l1, l2, lam)], n_O=2 * lam + 1) + arr_out = sap( + arr_1, arr_2, *cg_cache._coeffs[(l1, l2, lam)], output_size=2 * lam + 1 + ) assert arr_out.shape == (n_i * n_p * n_q, 2 * lam + 1) # Reshape back diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 68adb0b4a..2390f2d84 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -12,204 +12,177 @@ # ====================================================================== -# ===== Functions to do CG combinations on single-center descriptors +# ===== Public API functions # ====================================================================== -def single_center_combine_to_order( - nu1_tensor: TensorMap, + +def correlate_density( + density: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, - use_sparse: bool = True, # remove -) -> Union[TensorMap, List[TensorMap]]: +) -> List[TensorMap]: """ - Takes a correlation order nu = 1 (i.e. 2-body) single-center descriptor and - combines it iteratively with itself to generate a descriptor of correlation - order ``correlation_order``. - - ``nu1_tensor`` may be, for instance, a rascaline.SphericalExpansion or - rascaline.LodeSphericalExpansion. In the first iteration, ``nu1_tensor`` is - combined with itself to form a nu = 2 (3-body) descriptor. In each following - iteration the nu = x (x+1)-body descriptor is combined with the - ``nu1_tensor`` nu = 1 descriptor until the target ``correlation_order`` is - reached. - - With no other specification of input args, the full extent of non-zero CG - combinations will be taken between blocks at every step. However, there are - selections that can be made to reduce the computational cost. - ``angular_cutoff`` applies a global cutoff to the maximum angular channel - that is computed at each iteration. - - ``angular_selection`` and ``parity_selection`` can be used to explicitly - control the angular and parity channels that are output at each step. - Passing a list of int in each will apply that selection on the final CG - combination step. Passing a list of list of int (of length - ``correlation_order`` - 1) will apply the specified selection at each step. - - Cost can also be reduced with the ``skip_redundant`` argument. By default, - all combinations of blocks are performed. Some well-defined sets of these - combinations are redundant, and can be skipped by setting - ``skip_redundant=True`. This is done by sorting the l list (i.e. the angular - channels of the original nu = 1 blocks previously combined) of the blocks to - be combined, and only operating on blocks where l1 <= l2 <= ... <= ln. - - The ``output_selection`` argument can be used to control which CG iteration - steps a TensorMap is output for. By default (i.e. `output_selection=None`), - only the TensorMap from the final CG combination will be output. - - Finally, the ``use_sparse`` argument can be used to control whether a sparse - or dense cache of CG coefficients is used, which depending on the use case - can affect the performance. + Takes iterative Clebsch-Gordan (CG) tensor products of a density descriptor + with itself to the desired correlation order. Returns a list of TensorMaps + corresponding to the density correlations output from the specified + iterations. + + A density descriptor necessarily is body order 2 (i.e. correlation order 1), + but can be single- or multi-center. The output is a list of density + correlations for each iteration specified in `output_selection`, up to the + target order passed in `correlation_order`. + + This function is an iterative special case of the more general + :py:func:`correlate_tensors`. As a density is being correlated with itself, + some redundant CG tensor products can be skipped with the `skip_redundant` + keyword. + + Selections on the angular and parity channels at each iteration can also be + controlled with arguments `angular_cutoff`, `angular_selection` and + `parity_selection`. + + :param density: A density descriptor of body order 2 (correlation order 1), + in metatensor.TensorMap format. This may be, for example, a rascaline + :py:class:`SphericalExpansion` or :py:class:`LodeSphericalExpansion`. + Alternatively, this could be multi-center descriptor, such as a pair + density. + :param correlation_order: The desired correlation order of the output + descriptor. Must be >= 1. + :param angular_cutoff: The maximum angular channel to compute at any given + CG iteration, applied globally to all iterations until the target + correlation order is reached. + :param angular_selection: A list of angular channels to output at each + iteration. If a single list is passed, this is applied to the final + iteration only. If a list of lists is passed, this is applied to each + iteration. If None is passed, all angular channels are output at each + iteration. + :param parity_selection: A list of parity channels to output at each + iteration. If a single list is passed, this is applied to the final + iteration only. If a list of lists is passed, this is applied to each + iteration. If None is passed, all parity channels are output at each + iteration. + :param skip_redundant: Whether to skip redundant CG combinations. Defaults + to False, which means all combinations are performed. If a list of bool + is passed, this is applied to each iteration. If a single bool is + passed, this is applied to all iterations. + :param output_selection: A list of bools specifying whether to output a + TensorMap for each iteration. If a single bool is passed as True, + outputs from all iterations will be returned. If a list of bools is + passed, this controls the output at each corresponding iteration. If + None is passed, only the final iteration is output. + + :return List[TensorMap]: A list of TensorMaps corresponding to the density + correlations output from the specified iterations. """ - if correlation_order < 1: - raise ValueError("`correlation_order` must be > 0") - - if _dispatch.any([len(list(block.gradients())) > 0 for block in nu1_tensor]): - raise NotImplementedError( - "Clebsch Gordan combinations with gradients not yet implemented." - " Use metatensor.remove_gradients to remove gradients from the input." - ) - - # Standardize the metadata of the input tensor - nu1_tensor = _standardize_tensor_metadata(nu1_tensor) - - # If the desired body order is 1, return the input tensor with standardized - # metadata. - if correlation_order == 1: - return nu1_tensor - - n_iterations = correlation_order - 1 - - # Parse the various selection filters - angular_selection, parity_selection = _parse_int_selections( - n_iterations=n_iterations, - angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, - ) - skip_redundant, output_selection = _parse_bool_selections( - n_iterations, - skip_redundant=skip_redundant, - output_selection=output_selection, + return _correlate_density( + density, + correlation_order, + angular_cutoff, + angular_selection, + parity_selection, + skip_redundant, + output_selection, + compute_metadata_only=False, + sparse=True, # sparse CG cache by default ) - # Pre-compute the metadata needed to perform each CG iteration - # Current design choice: only combine a nu = 1 tensor iteratively with - # itself, i.e. nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. - combination_metadata = _precompute_metadata( - nu1_tensor.keys, - nu1_tensor.keys, - n_iterations=n_iterations, - angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, - skip_redundant=skip_redundant, - ) - # Define the cached CG coefficients, either as sparse dicts or dense arrays. - # TODO: we pre-computed the combination metadata, so a more cleverly - # constructed CG cache could be used to reduce memory overhead - i.e. we - # don't necessarily need *all* CG coeffs up to `angular_max`, just the ones - # that are actually used. - angular_max = max( - _dispatch.concatenate( - [nu1_tensor.keys.column("spherical_harmonics_l")] - + [ - metadata[0].column("spherical_harmonics_l") - for metadata in combination_metadata - ] - ) - ) - cg_cache = ClebschGordanReal(angular_max, use_sparse) - - # Create a copy of the nu = 1 tensor to combine with itself - nu_x_tensor = nu1_tensor +def correlate_density_metadata( + density: TensorMap, + correlation_order: int, + angular_cutoff: Optional[int] = None, + angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + skip_redundant: Optional[Union[bool, List[bool]]] = False, + output_selection: Optional[Union[bool, List[bool]]] = None, +) -> List[TensorMap]: + """ + Returns the metadata-only TensorMaps that would be output by the function + :py:func:`correlate_density` under the same settings, without perfoming the + actual Clebsch-Gordan tensor products. See this function for full + documentation. + + :param density: A density descriptor of body order 2 (correlation order 1), + in metatensor.TensorMap format. This may be, for example, a rascaline + :py:class:`SphericalExpansion` or :py:class:`LodeSphericalExpansion`. + Alternatively, this could be multi-center descriptor, such as a pair + density. + :param correlation_order: The desired correlation order of the output + descriptor. Must be >= 1. + :param angular_cutoff: The maximum angular channel to compute at any given + CG iteration, applied globally to all iterations until the target + correlation order is reached. + :param angular_selection: A list of angular channels to output at each + iteration. If a single list is passed, this is applied to the final + iteration only. If a list of lists is passed, this is applied to each + iteration. If None is passed, all angular channels are output at each + iteration. + :param parity_selection: A list of parity channels to output at each + iteration. If a single list is passed, this is applied to the final + iteration only. If a list of lists is passed, this is applied to each + iteration. If None is passed, all parity channels are output at each + iteration. + :param skip_redundant: Whether to skip redundant CG combinations. Defaults + to False, which means all combinations are performed. If a list of bool + is passed, this is applied to each iteration. If a single bool is + passed, this is applied to all iterations. + :param output_selection: A list of bools specifying whether to output a + TensorMap for each iteration. If a single bool is passed as True, + outputs from all iterations will be returned. If a list of bools is + passed, this controls the output at each corresponding iteration. If + None is passed, only the final iteration is output. + + :return List[TensorMap]: A list of TensorMaps corresponding to the metadata + that would be output by :py:func:`correlate_density` under the same + settings. + """ - # Iteratively combine block values - output_tensors = [] - for iteration, output_tensor in zip(range(n_iterations), output_selection): - tmp_correlation_order = iteration + 2 - # TODO: is there a faster way of iterating over keys/blocks here? - nu_x_blocks = [] - for nu_x_key, key_1, key_2 in zip(*combination_metadata[iteration]): - # Combine the pair of block values - nu_x_block = _combine_single_center_blocks( - nu_x_tensor[key_1], - nu1_tensor[key_2], - nu_x_key["spherical_harmonics_l"], - cg_cache, - ) - nu_x_blocks.append(nu_x_block) - nu_x_keys = combination_metadata[iteration][0] - nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) - - # If this tensor is to be included in the output, move the keys to - # properties and store - if output_tensor is True: - # Move the [l1, l2, ...] keys to the properties - output_tensors.append( - nu_x_tensor.keys_to_properties( - [f"l{i}" for i in range(1, tmp_correlation_order + 1)] - + [f"k{i}" for i in range(2, tmp_correlation_order)] - ) - ) + return _correlate_density( + density, + correlation_order, + angular_cutoff, + angular_selection, + parity_selection, + skip_redundant, + output_selection, + compute_metadata_only=True, + ) - # Drop redundant key names. TODO: these should be part of the global - # matadata associated with the TensorMap. Awaiting this functionality in - # metatensor. - for i, tensor in enumerate(output_tensors): - keys = tensor.keys - if len(_dispatch.unique(tensor.keys.column("order_nu"))) == 1: - keys = keys.remove(name="order_nu") - if len(_dispatch.unique(tensor.keys.column("inversion_sigma"))) == 1: - keys = keys.remove(name="inversion_sigma") - output_tensors[i] = TensorMap( - keys=keys, blocks=[b.copy() for b in tensor.blocks()] - ) - if len(output_tensors) == 1: - return output_tensors[0] - return output_tensors +# ==================================================================== +# ===== Private functions that do the work on the TensorMap level +# ==================================================================== -def single_center_combine_metadata_to_order( - nu1_tensor: TensorMap, +def _correlate_density( + density: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[int, List[int], List[List[int]]]] = None, - parity_selection: Optional[Union[int, List[int], List[List[int]]]] = None, + angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, -) -> Union[TensorMap, List[TensorMap]]: + compute_metadata_only: bool = False, + sparse: bool = True, +) -> List[TensorMap]: """ - Performs a pseudo-CG combination of a correlation order nu = 1 (i.e. 2-body) - single-center descriptor with itself to generate a descriptor of order - ``correlation_order``. Obnly - - TensorMap(s) with complete metadata is returned, where all block values are - zero. See :py:func:`single_center_combine_to_order` for documentation on - args. + Performs the density correlations for public functions + :py:func:`correlate_density` and :py:func:`correlate_density_metadata`. """ if correlation_order <= 1: raise ValueError("`correlation_order` must be > 1") - - if _dispatch.any([len(list(block.gradients())) > 0 for block in nu1_tensor]): + if _dispatch.any([len(list(block.gradients())) > 0 for block in density]): raise NotImplementedError( - "CG combinations of gradients not currently supported. Check back soon." + "Clebsch Gordan combinations with gradients not yet implemented." + " Use metatensor.remove_gradients to remove gradients from the input." ) - - # Standardize the metadata of the input tensor - nu1_tensor = _standardize_tensor_metadata(nu1_tensor) - - # If the desired body order is 1, return the input tensor with standardized - # metadata. - if correlation_order == 1: - return nu1_tensor - - n_iterations = correlation_order - 1 + n_iterations = correlation_order - 1 # num iterations + density = _standardize_metadata(density) # standardize metadata + density_correlation = density # create a copy to combine with itself # Parse the various selection filters angular_selection, parity_selection = _parse_int_selections( @@ -224,81 +197,93 @@ def single_center_combine_metadata_to_order( output_selection=output_selection, ) - # Pre-compute the metadata needed to perform each CG iteration. Current - # design choice: only combine a nu = 1 tensor iteratively with itself, i.e. - # nu=1 + nu=1 --> nu=2. nu=2 + nu=1 --> nu=3, etc. - combination_metadata = _precompute_metadata( - nu1_tensor.keys, - nu1_tensor.keys, + # Pre-compute the metadata needed to perform each CG iteration + metadata = _precompute_metadata( + density.keys, + density.keys, n_iterations=n_iterations, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, skip_redundant=skip_redundant, ) + # Compute CG coefficient cache + if compute_metadata_only: + cg_cache = None + else: + angular_max = max( + _dispatch.concatenate( + [density.keys.column("spherical_harmonics_l")] + + [mdata[2].column("spherical_harmonics_l") for mdata in metadata] + ) + ) + # TODO: metadata has been precomputed, so perhaps we don't need to + # compute all CG coefficients up to angular_max here. + # TODO: use sparse cache by default until we understamd under which + # circumstances (and if) dense is faster. + cg_cache = ClebschGordanReal(angular_max, sparse=sparse) + + # Perform iterative CG tensor products + density_correlations = [] + for iteration in range(n_iterations): + # Define the correlation order of the current iteration + correlation_order_it = iteration + 2 - # Create a copy of the nu = 1 tensor to combine with itself - nu_x_tensor = nu1_tensor - - # Iteratively combine block values - output_tensors = [] - for iteration, output_tensor in zip(range(n_iterations), output_selection): - tmp_correlation_order = iteration + 2 + blocks_out = [] # TODO: is there a faster way of iterating over keys/blocks here? - nu_x_blocks = [] - for nu_x_key, key_1, key_2 in zip(*combination_metadata[iteration]): - nu_x_block = _combine_single_center_blocks( - nu_x_tensor[key_1], - nu1_tensor[key_2], - nu_x_key["spherical_harmonics_l"], - cg_cache=None, - return_metadata_only=True, + for key_1, key_2, key_out in zip(*metadata[iteration]): + block_out = _combine_single_center_blocks( + density_correlation[key_1], + density[key_2], + key_out["spherical_harmonics_l"], + cg_cache, + compute_metadata_only=compute_metadata_only, ) - nu_x_blocks.append(nu_x_block) - nu_x_keys = combination_metadata[iteration][0] - nu_x_tensor = TensorMap(keys=nu_x_keys, blocks=nu_x_blocks) - - # If this tensor is to be included in the output, move the keys to - # properties and store - if output_tensor is True: - # Move the [l1, l2, ...] keys to the properties - output_tensors.append( - nu_x_tensor.keys_to_properties( - [f"l{i}" for i in range(1, tmp_correlation_order + 1)] - + [f"k{i}" for i in range(2, tmp_correlation_order)] + blocks_out.append(block_out) + keys_out = metadata[iteration][2] + density_correlation = TensorMap(keys=keys_out, blocks=blocks_out) + + # If this tensor is to be included in the output, move the [l1, l2, ...] + # keys to properties and store + if output_selection[iteration]: + density_correlations.append( + density_correlation.keys_to_properties( + [f"l{i}" for i in range(1, correlation_order_it + 1)] + + [f"k{i}" for i in range(2, correlation_order_it)] ) ) - # Remove redundant key names - for i, tensor in enumerate(output_tensors): + # Drop redundant key names. TODO: these should be part of the global + # matadata associated with the TensorMap. Awaiting this functionality in + # metatensor. + for i, tensor in enumerate(density_correlations): keys = tensor.keys if len(_dispatch.unique(tensor.keys.column("order_nu"))) == 1: keys = keys.remove(name="order_nu") if len(_dispatch.unique(tensor.keys.column("inversion_sigma"))) == 1: keys = keys.remove(name="inversion_sigma") - output_tensors[i] = TensorMap( + density_correlations[i] = TensorMap( keys=keys, blocks=[b.copy() for b in tensor.blocks()] ) - if len(output_tensors) == 1: - return output_tensors[0] - return output_tensors + return density_correlations -def single_center_combine( +def correlate_tensors( tensor_1: TensorMap, tensor_2: TensorMap, angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - use_sparse: bool = True, ) -> TensorMap: """ - Takes two single-center descriptors of arbitrary body order and combines - them in a single CG combination step. Tensors could be of different - provenance, i.e. a SphericalExpansion and a LodeSphericalExpansion. + Performs the Clebsch Gordan tensor product of two TensorMaps that correspond + to densities or density correlations. Returns a new TensorMap corresponding + to a higher correlation-order descriptor. + + The two input tensors can be single- or multi-center, and of arbitrary (and + different) correlation order, but must contain the same samples. """ - # TODO: implement! raise NotImplementedError @@ -307,7 +292,7 @@ def single_center_combine( # ================================================================== -def _standardize_tensor_metadata(tensor: TensorMap) -> TensorMap: +def _standardize_metadata(tensor: TensorMap) -> TensorMap: """ Takes a nu=1 tensor and standardizes its metadata. This involves: 1) moving the "species_neighbor" key to properties, if present as a dimension in the @@ -482,22 +467,22 @@ def _precompute_metadata( angular (`angular_selection`) and parity (`parity_selection`) selections to be applied at each iteration. """ - comb_metadata = [] - new_keys = keys_1 + metadata = [] + keys_out = keys_1 for iteration in range(n_iterations): # Get the metadata for the combination of the 2 tensors - i_comb_metadata = _precompute_metadata_one_iteration( - keys_1=new_keys, + i_metadata = _precompute_metadata_one_iteration( + keys_1=keys_out, keys_2=keys_2, angular_cutoff=angular_cutoff, angular_selection=angular_selection[iteration], parity_selection=parity_selection[iteration], skip_redundant=skip_redundant[iteration], ) - new_keys = i_comb_metadata[0] + keys_out = i_metadata[2] # Check that some keys are produced as a result of the combination - if len(new_keys) == 0: + if len(keys_out) == 0: raise ValueError( f"invalid selections: iteration {iteration + 1} produces no" " valid combinations. Check the `angular_selection` and" @@ -508,7 +493,7 @@ def _precompute_metadata( if angular_selection is not None: if angular_selection[iteration] is not None: for lam in angular_selection[iteration]: - if lam not in new_keys.column("spherical_harmonics_l"): + if lam not in keys_out.column("spherical_harmonics_l"): raise ValueError( f"lambda = {lam} specified in `angular_selection`" f" for iteration {iteration + 1}, but this is not a" @@ -519,7 +504,7 @@ def _precompute_metadata( if parity_selection is not None: if parity_selection[iteration] is not None: for sig in parity_selection[iteration]: - if sig not in new_keys.column("inversion_sigma"): + if sig not in keys_out.column("inversion_sigma"): raise ValueError( f"sigma = {sig} specified in `parity_selection`" f" for iteration {iteration + 1}, but this is not" @@ -528,9 +513,9 @@ def _precompute_metadata( " `parity_selection` and try again." ) - comb_metadata.append(i_comb_metadata) + metadata.append(i_metadata) - return comb_metadata + return metadata def _precompute_metadata_one_iteration( @@ -692,18 +677,18 @@ def _precompute_metadata_one_iteration( keys_2_entries.append(key_2) # Define new keys as the full product of keys_1 and keys_2 - nu_x_keys = Labels( + keys_out = Labels( names=new_names, values=_dispatch.int_array_like(new_key_values, like=keys_1.values), ) # Don't skip the calculation of redundant blocks if skip_redundant is False: - return nu_x_keys, keys_1_entries, keys_2_entries + return keys_1_entries, keys_2_entries, keys_out # Now account for multiplicty key_idxs_to_keep = [] - for key_idx, key in enumerate(nu_x_keys): + for key_idx, key in enumerate(keys_out): # Get the important key values. This is all of the keys, excpet the k # list key_vals_slice = key.values[: 4 + (nu + 1)].tolist() @@ -721,10 +706,10 @@ def _precompute_metadata_one_iteration( key_idxs_to_keep.append(key_idx) # Build a reduced Labels object for the combined keys, with redundancies removed - combined_keys_red = Labels( + keys_out_red = Labels( names=new_names, values=_dispatch.int_array_like( - [nu_x_keys[idx].values for idx in key_idxs_to_keep], like=keys_1.values + [keys_out[idx].values for idx in key_idxs_to_keep], like=keys_1.values ), ) @@ -733,7 +718,7 @@ def _precompute_metadata_one_iteration( keys_1_entries_red = [keys_1_entries[idx] for idx in key_idxs_to_keep] keys_2_entries_red = [keys_2_entries[idx] for idx in key_idxs_to_keep] - return combined_keys_red, keys_1_entries_red, keys_2_entries_red + return keys_1_entries_red, keys_2_entries_red, keys_out_red # ================================================================== @@ -746,7 +731,7 @@ def _combine_single_center_blocks( block_2: TensorBlock, lam: int, cg_cache, - return_metadata_only: bool = False, + compute_metadata_only: bool = False, ) -> TensorBlock: """ For a given pair of TensorBlocks and desired angular channel, combines the @@ -754,7 +739,7 @@ def _combine_single_center_blocks( """ # Do the CG combination - single center so no shape pre-processing required - if return_metadata_only: + if compute_metadata_only: combined_values = _dispatch.combine_arrays( block_1.values, block_2.values, lam, cg_cache, return_empty_array=True ) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 9d871d257..654d65500 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -11,9 +11,10 @@ import rascaline from metatensor import Labels, TensorBlock, TensorMap from rascaline.utils import clebsch_gordan, PowerSpectrum +from rascaline.utils.clebsch_gordan.clebsch_gordan import _correlate_density, _standardize_metadata from .rotations import WignerDReal, transform_frame_so3, transform_frame_o3 - +from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal DATA_ROOT = os.path.join(os.path.dirname(__file__), "data") @@ -46,12 +47,12 @@ @pytest.fixture(scope="module") def cg_cache_sparse(): - return clebsch_gordan.ClebschGordanReal(lambda_max=5, sparse=True) + return ClebschGordanReal(lambda_max=5, sparse=True) @pytest.fixture(scope="module") def cg_cache_dense(): - return clebsch_gordan.ClebschGordanReal(lambda_max=5, sparse=False) + return ClebschGordanReal(lambda_max=5, sparse=False) # ============ Helper functions ============ @@ -141,27 +142,27 @@ def test_so3_equivariance( parity_selection: List[List[int]], ): wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) - frames_so3 = [ - transform_frame_so3(frame, wig.angles) for frame in frames - ] + frames_so3 = [transform_frame_so3(frame, wig.angles) for frame in frames] nu_1 = sphex(frames) nu_1_so3 = sphex(frames_so3) - nu_3 = clebsch_gordan.single_center_combine_to_order( - nu1_tensor=nu_1, + nu_3 = _correlate_density( + density=nu_1, correlation_order=nu_target, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, - ) - nu_3_so3 = clebsch_gordan.single_center_combine_to_order( - nu1_tensor=nu_1_so3, + compute_metadata_only=False, + )[0] + nu_3_so3 = _correlate_density( + density=nu_1_so3, correlation_order=nu_target, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, - ) + compute_metadata_only=False, + )[0] nu_3_transf = wig.transform_tensormap_so3(nu_3) assert metatensor.allclose(nu_3_transf, nu_3_so3) @@ -183,27 +184,27 @@ def test_o3_equivariance( parity_selection: List[List[int]], ): wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) - frames_o3 = [ - transform_frame_o3(frame, wig.angles) for frame in frames - ] + frames_o3 = [transform_frame_o3(frame, wig.angles) for frame in frames] nu_1 = sphex(frames) nu_1_o3 = sphex(frames_o3) - nu_3 = clebsch_gordan.single_center_combine_to_order( - nu1_tensor=nu_1, + nu_3 = _correlate_density( + density=nu_1, correlation_order=nu_target, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, - ) - nu_3_o3 = clebsch_gordan.single_center_combine_to_order( - nu1_tensor=nu_1_o3, + compute_metadata_only=False, + )[0] + nu_3_o3 = _correlate_density( + density=nu_1_o3, correlation_order=nu_target, angular_cutoff=angular_cutoff, angular_selection=angular_selection, parity_selection=parity_selection, - ) + compute_metadata_only=False, + )[0] nu_3_transf = wig.transform_tensormap_o3(nu_3) assert metatensor.allclose(nu_3_transf, nu_3_o3) @@ -223,12 +224,13 @@ def test_lambda_soap_vs_powerspectrum(frames): ps = powspec_small_features(frames) # Build a lambda-SOAP - nu1_tensor = sphex_small_features(frames) - lsoap = clebsch_gordan.single_center_combine_to_order( - nu1_tensor=nu1_tensor, + density = sphex_small_features(frames) + lsoap = _correlate_density( + density=density, correlation_order=2, angular_selection=[0], - ) + compute_metadata_only=False, + )[0] keys = lsoap.keys.remove(name="spherical_harmonics_l") lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()]) @@ -274,37 +276,31 @@ def test_combine_single_center_norm(frames, correlation_order): nu1 = sphex_small_features(frames) # Build higher body order tensor without sorting the l lists - nux = clebsch_gordan.single_center_combine_to_order( + nux = _correlate_density( nu1, correlation_order=correlation_order, angular_cutoff=None, angular_selection=None, parity_selection=None, skip_redundant=False, - use_sparse=True, - ) + compute_metadata_only=False, + sparse=True, + )[0] # Build higher body order tensor *with* sorting the l lists - nux_sorted_l = clebsch_gordan.single_center_combine_to_order( + nux_sorted_l = _correlate_density( nu1, correlation_order=correlation_order, angular_cutoff=None, angular_selection=None, parity_selection=None, skip_redundant=True, - use_sparse=True, - ) + compute_metadata_only=False, + sparse=True, + )[0] # Standardize the features by passing through the CG combination code but with # no iterations (i.e. body order 1 -> 1) - nu1 = clebsch_gordan.single_center_combine_to_order( - nu1, - correlation_order=1, - angular_cutoff=None, - angular_selection=None, - parity_selection=None, - skip_redundant=False, - use_sparse=True, - ) + nu1 = _standardize_metadata(nu1) # Make only lambda and sigma part of keys nu1 = nu1.keys_to_samples(["species_center"]) @@ -392,17 +388,19 @@ def test_single_center_combine_to_correlation_order_dense_sparse_agree(frames): Tests for agreement between nu=3 tensors built using both sparse and dense CG coefficient caches. """ - nu1_tensor = sphex_small_features(frames) - n_body_sparse = clebsch_gordan.single_center_combine_to_order( - nu1_tensor, + density = sphex_small_features(frames) + n_body_sparse = _correlate_density( + density, correlation_order=3, - use_sparse=True, - ) - n_body_dense = clebsch_gordan.single_center_combine_to_order( - nu1_tensor, + sparse=True, + compute_metadata_only=False, + )[0] + n_body_dense = _correlate_density( + density, correlation_order=3, - use_sparse=False, - ) + sparse=False, + compute_metadata_only=False, + )[0] assert metatensor.operations.allclose( n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8 @@ -419,33 +417,32 @@ def test_single_center_combine_to_correlation_order_metadata_agree( frames, correlation_order, skip_redundant ): """ - Tests that the metadata output from - single_center_combine_metadata_to_order agrees with the metadata - of the full tensor built using single_center_combine_to_order. + Tests that the outputs from `_correlate_density` agrees when switching the + `compute_metadata_only` flag on and off. """ for nu1 in [sphex_small_features(frames), sphex(frames)]: # Build higher body order tensor with CG computation - nux = clebsch_gordan.single_center_combine_to_order( + nux = _correlate_density( nu1, correlation_order=correlation_order, angular_cutoff=None, angular_selection=None, parity_selection=None, skip_redundant=skip_redundant, - use_sparse=True, - ) + compute_metadata_only=False, + sparse=True, + )[0] # Build higher body order tensor without CG computation - i.e. metadata # only - nux_metadata_only = ( - clebsch_gordan.single_center_combine_metadata_to_order( - nu1, - correlation_order=correlation_order, - angular_cutoff=None, - angular_selection=None, - parity_selection=None, - skip_redundant=skip_redundant, - ) - ) + nux_metadata_only = _correlate_density( + nu1, + correlation_order=correlation_order, + angular_cutoff=None, + angular_selection=None, + parity_selection=None, + skip_redundant=skip_redundant, + compute_metadata_only=True, + )[0] assert metatensor.equal_metadata(nux, nux_metadata_only) @@ -457,7 +454,7 @@ def test_single_center_combine_to_correlation_order_metadata( ): """ Performs hard-coded tests on the metadata outputted from - single_center_combine_metadata_to_order. + _correlate_density. TODO: finish! """ @@ -466,7 +463,7 @@ def test_single_center_combine_to_correlation_order_metadata( # # only. This returns a list of the TensorMaps formed at each CG # # iteration. # nux_metadata_only = ( - # clebsch_gordan.single_center_combine_metadata_to_order( + # clebsch_gordan.correlate_density_metadata( # nu1, # correlation_order=correlation_order, # angular_cutoff=None, @@ -489,14 +486,15 @@ def test_single_center_combine_angular_selection( specified ``angular_cutoff`` and ``angular_selection``.""" nu_1 = sphex(frames) - nu_2 = clebsch_gordan.single_center_combine_to_order( - nu1_tensor=nu_1, + nu_2 = _correlate_density( + density=nu_1, correlation_order=2, angular_cutoff=None, angular_selection=angular_selection, parity_selection=None, skip_redundant=skip_redundant, - ) + compute_metadata_only=False, + )[0] if angular_selection is None: assert np.all( From 88f4ac4989f65eec6eed8ac342f4154383b7e44b Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 22 Nov 2023 15:18:50 +0100 Subject: [PATCH 90/96] Separate out removal of redundant keys. Some renaming. --- .../utils/clebsch_gordan/clebsch_gordan.py | 116 ++++++++++-------- .../rascaline/tests/utils/clebsch_gordan.py | 4 +- 2 files changed, 68 insertions(+), 52 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 2390f2d84..c1f9cfb23 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -181,7 +181,7 @@ def _correlate_density( " Use metatensor.remove_gradients to remove gradients from the input." ) n_iterations = correlation_order - 1 # num iterations - density = _standardize_metadata(density) # standardize metadata + density = _standardize_keys(density) # standardize metadata density_correlation = density # create a copy to combine with itself # Parse the various selection filters @@ -197,8 +197,8 @@ def _correlate_density( output_selection=output_selection, ) - # Pre-compute the metadata needed to perform each CG iteration - metadata = _precompute_metadata( + # Pre-compute the keys needed to perform each CG iteration + key_metadata = _precompute_keys( density.keys, density.keys, n_iterations=n_iterations, @@ -214,10 +214,10 @@ def _correlate_density( angular_max = max( _dispatch.concatenate( [density.keys.column("spherical_harmonics_l")] - + [mdata[2].column("spherical_harmonics_l") for mdata in metadata] + + [mdata[2].column("spherical_harmonics_l") for mdata in key_metadata] ) ) - # TODO: metadata has been precomputed, so perhaps we don't need to + # TODO: keys have been precomputed, so perhaps we don't need to # compute all CG coefficients up to angular_max here. # TODO: use sparse cache by default until we understamd under which # circumstances (and if) dense is faster. @@ -231,8 +231,8 @@ def _correlate_density( blocks_out = [] # TODO: is there a faster way of iterating over keys/blocks here? - for key_1, key_2, key_out in zip(*metadata[iteration]): - block_out = _combine_single_center_blocks( + for key_1, key_2, key_out in zip(*key_metadata[iteration]): + block_out = _combine_blocks_same_samples( density_correlation[key_1], density[key_2], key_out["spherical_harmonics_l"], @@ -240,7 +240,7 @@ def _correlate_density( compute_metadata_only=compute_metadata_only, ) blocks_out.append(block_out) - keys_out = metadata[iteration][2] + keys_out = key_metadata[iteration][2] density_correlation = TensorMap(keys=keys_out, blocks=blocks_out) # If this tensor is to be included in the output, move the [l1, l2, ...] @@ -292,7 +292,7 @@ def correlate_tensors( # ================================================================== -def _standardize_metadata(tensor: TensorMap) -> TensorMap: +def _standardize_keys(tensor: TensorMap) -> TensorMap: """ Takes a nu=1 tensor and standardizes its metadata. This involves: 1) moving the "species_neighbor" key to properties, if present as a dimension in the @@ -451,7 +451,7 @@ def _parse_bool_selections( return skip_redundant, output_selection -def _precompute_metadata( +def _precompute_keys( keys_1: Labels, keys_2: Labels, n_iterations: int, @@ -461,25 +461,30 @@ def _precompute_metadata( skip_redundant: List[bool], ) -> List[Tuple[Labels, List[List[int]]]]: """ - Computes all the metadata needed to perform `n_iterations` of CG combination - steps, based on the keys of the 2 tensors being combined (`keys_1` and - `keys_2`), the maximum angular channel cutoff (`angular_cutoff`), and the - angular (`angular_selection`) and parity (`parity_selection`) selections to - be applied at each iteration. + Computes all the keys metadata needed to perform `n_iterations` of CG + combination steps, based on the keys of the 2 tensors being combined + (`keys_1` and `keys_2`), the maximum angular channel cutoff + (`angular_cutoff`), and the angular (`angular_selection`) and parity + (`parity_selection`) selections to be applied at each iteration. + + If `skip_redundant` is True, then keys that represent redundant CG + operations are not included in the output metadata. """ - metadata = [] + keys_metadata = [] keys_out = keys_1 for iteration in range(n_iterations): - # Get the metadata for the combination of the 2 tensors - i_metadata = _precompute_metadata_one_iteration( + # Get the keys metadata for the combination of the 2 tensors + keys_1_entries, keys_2_entries, keys_out = _precompute_keys_one_iteration( keys_1=keys_out, keys_2=keys_2, angular_cutoff=angular_cutoff, angular_selection=angular_selection[iteration], parity_selection=parity_selection[iteration], - skip_redundant=skip_redundant[iteration], ) - keys_out = i_metadata[2] + if skip_redundant[iteration]: + keys_1_entries, keys_2_entries, keys_out = _remove_redundant_keys( + keys_1_entries, keys_2_entries, keys_out + ) # Check that some keys are produced as a result of the combination if len(keys_out) == 0: @@ -513,22 +518,21 @@ def _precompute_metadata( " `parity_selection` and try again." ) - metadata.append(i_metadata) + keys_metadata.append((keys_1_entries, keys_2_entries, keys_out)) - return metadata + return keys_metadata -def _precompute_metadata_one_iteration( +def _precompute_keys_one_iteration( keys_1: Labels, keys_2: Labels, angular_cutoff: Optional[int] = None, angular_selection: Optional[Union[None, List[int]]] = None, parity_selection: Optional[Union[None, List[int]]] = None, - skip_redundant: bool = False, ) -> Tuple[Labels, List[List[int]]]: """ Given the keys of 2 TensorMaps, returns the keys that would be present after - a CG combination of these TensorMaps. + a full CG product of these TensorMaps. Any angular or parity channel selections passed in `angular_selection` and `parity_selection` are applied such that only specified channels are present @@ -562,35 +566,28 @@ def _precompute_metadata_one_iteration( ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] - Returned is a tuple. The first element in the tuple is a Labels object - corresponding to the keys created by a CG combination step. - - The second element is a list of list of ints. Each sublist corresponds to - [lam1, lam2, correction_factor] terms. lam1 and lam2 tracks the lambda - values of the blocks that combine to form the block indexed by the - corresponding key. The correction_factor terms are the prefactors that - account for the redundancy in the CG combination. + Returned is Tuple[List, List, Labels]. The first two lists correspond to the + LabelsEntry objects of the keys being combined. The third element is a + Labels object corresponding to the keys of the output TensorMap. Each entry + in this Labels object corresponds to the keys is formed by combination of + the pair of blocks indexed by correspoding key pairs in the first two lists. The `parity_selection` argument can be used to return only keys with certain parities. This must be passed as a list with elements +1 and/or -1. - - The `skip_redundant` arg can be used to skip the calculation of redundant - block combinations - i.e. those that have equivalent sorted l lists. Only - the one for which l1 <= l2 <= ... <= ln is calculated. """ - # Get the body order of the first TensorMap. + # Get the correlation order of the first TensorMap. unique_nu = _dispatch.unique(keys_1.column("order_nu")) if len(unique_nu) > 1: raise ValueError( - "keys_1 must correspond to a tensor of a single body order." + "keys_1 must correspond to a tensor of a single correlation order." f" Found {len(unique_nu)} body orders: {unique_nu}" ) nu1 = unique_nu[0] - # Define nu value of output TensorMap + # Define new correlation order of output TensorMap nu = nu1 + 1 - # The body order of the second TensorMap should be nu = 1. + # The correlation order of the second TensorMap should be nu = 1. assert _dispatch.all(keys_2.column("order_nu") == 1) # If nu1 = 1, the key names don't yet have any "lx" columns @@ -682,11 +679,30 @@ def _precompute_metadata_one_iteration( values=_dispatch.int_array_like(new_key_values, like=keys_1.values), ) - # Don't skip the calculation of redundant blocks - if skip_redundant is False: - return keys_1_entries, keys_2_entries, keys_out + return keys_1_entries, keys_2_entries, keys_out + + +def _remove_redundant_keys( + keys_1_entries: List, keys_2_entries: List, keys_out: Labels +) -> Tuple[List, List, Labels]: + """ + For a Labels object `keys_out` that corresponds to the keys of a TensorMap + formed by combined of the blocks described by the entries in the lists + `keys_1_entries` and `keys_2_entries`, removes redundant keys. - # Now account for multiplicty + These are the keys that correspond to blocks that have the same sorted l + list. The block where the l values are already sorted (i.e. l1 <= l2 <= ... + <= ln) is kept. + """ + # Get and check the correlation order of the input keys + nu1 = keys_1_entries[0]["order_nu"] + nu2 = keys_2_entries[0]["order_nu"] + assert nu2 == 1 + + # Get the correlation order of the output TensorMap + nu = nu1 + 1 + + # Identify keys of redundant blocks and remove them key_idxs_to_keep = [] for key_idx, key in enumerate(keys_out): # Get the important key values. This is all of the keys, excpet the k @@ -707,14 +723,14 @@ def _precompute_metadata_one_iteration( # Build a reduced Labels object for the combined keys, with redundancies removed keys_out_red = Labels( - names=new_names, + names=keys_out.names, values=_dispatch.int_array_like( - [keys_out[idx].values for idx in key_idxs_to_keep], like=keys_1.values + [keys_out[idx].values for idx in key_idxs_to_keep], + like=keys_1_entries[0].values, ), ) - # Create a of LabelsEntry objects that correspond to the original keys in - # `keys_1` and `keys_2` that combined to form the combined key + # Store the list of reduced entries that combine to form the reduced output keys keys_1_entries_red = [keys_1_entries[idx] for idx in key_idxs_to_keep] keys_2_entries_red = [keys_2_entries[idx] for idx in key_idxs_to_keep] @@ -726,7 +742,7 @@ def _precompute_metadata_one_iteration( # ================================================================== -def _combine_single_center_blocks( +def _combine_blocks_same_samples( block_1: TensorBlock, block_2: TensorBlock, lam: int, diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 654d65500..1d4970b4e 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -11,7 +11,7 @@ import rascaline from metatensor import Labels, TensorBlock, TensorMap from rascaline.utils import clebsch_gordan, PowerSpectrum -from rascaline.utils.clebsch_gordan.clebsch_gordan import _correlate_density, _standardize_metadata +from rascaline.utils.clebsch_gordan.clebsch_gordan import _correlate_density, _standardize_keys from .rotations import WignerDReal, transform_frame_so3, transform_frame_o3 from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal @@ -300,7 +300,7 @@ def test_combine_single_center_norm(frames, correlation_order): # Standardize the features by passing through the CG combination code but with # no iterations (i.e. body order 1 -> 1) - nu1 = _standardize_metadata(nu1) + nu1 = _standardize_keys(nu1) # Make only lambda and sigma part of keys nu1 = nu1.keys_to_samples(["species_center"]) From 9ad960e68f0a410aed6731c7f4600d812b48e0af Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 22 Nov 2023 15:28:51 +0100 Subject: [PATCH 91/96] Change fxn signatures --- .../utils/clebsch_gordan/clebsch_gordan.py | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index c1f9cfb23..7d0d5cf9f 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -20,8 +20,7 @@ def correlate_density( density: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + selection: Optional[Union[Labels, List[Labels]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, ) -> List[TensorMap]: @@ -82,8 +81,7 @@ def correlate_density( density, correlation_order, angular_cutoff, - angular_selection, - parity_selection, + selection, skip_redundant, output_selection, compute_metadata_only=False, @@ -95,8 +93,7 @@ def correlate_density_metadata( density: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + selection: Optional[Union[Labels, List[Labels]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, ) -> List[TensorMap]: @@ -145,8 +142,7 @@ def correlate_density_metadata( density, correlation_order, angular_cutoff, - angular_selection, - parity_selection, + selection, skip_redundant, output_selection, compute_metadata_only=True, @@ -162,8 +158,7 @@ def _correlate_density( density: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + selection: Optional[Union[Labels, List[Labels]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, compute_metadata_only: bool = False, @@ -188,8 +183,7 @@ def _correlate_density( angular_selection, parity_selection = _parse_int_selections( n_iterations=n_iterations, angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, + selection=selection, ) skip_redundant, output_selection = _parse_bool_selections( n_iterations, @@ -273,8 +267,7 @@ def correlate_tensors( tensor_1: TensorMap, tensor_2: TensorMap, angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, - parity_selection: Optional[Union[None, int, List[int], List[List[int]]]] = None, + selection: Optional[Labels] = None, ) -> TensorMap: """ Performs the Clebsch Gordan tensor product of two TensorMaps that correspond From 8d11a8bf8ade2e84ae33ac9afd37d6935ecd9f50 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Wed, 22 Nov 2023 18:27:52 +0100 Subject: [PATCH 92/96] Update API to take Labels selection --- .../utils/clebsch_gordan/_cg_cache.py | 28 +- .../utils/clebsch_gordan/_dispatch.py | 4 +- .../utils/clebsch_gordan/clebsch_gordan.py | 360 +++++++++--------- .../rascaline/tests/utils/clebsch_gordan.py | 127 +++--- 4 files changed, 247 insertions(+), 272 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py index 0ed6c687f..a4e989897 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py @@ -116,14 +116,14 @@ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MO if sparse: if not HAS_MOPS: - import warnings - - warnings.warn( - "It is recommended to use MOPS for sparse accumulation. " - " This can be installed with ``pip install" - " git+https://github.com/lab-cosmo/mops`." - " Falling back to numpy for now." - ) + # TODO: provide a warning once Mops is fully ready + # import warnings + # warnings.warn( + # "It is recommended to use MOPS for sparse accumulation. " + # " This can be installed with ``pip install" + # " git+https://github.com/lab-cosmo/mops`." + # " Falling back to numpy for now." + # ) self._use_mops = False else: print( @@ -132,12 +132,12 @@ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MO self._use_mops = True else: - if HAS_MOPS: - import warnings - - warnings.warn( - "Mops is installed, but not being used as dense operations chosen." - ) + # TODO: provide a warning once Mops is fully ready + # if HAS_MOPS: + # import warnings + # warnings.warn( + # "Mops is installed, but not being used as dense operations chosen." + # ) self._use_mops = False self._coeffs = ClebschGordanReal.build_coeff_dict( diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index 9cb75627f..774b09c42 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -302,9 +302,9 @@ def any(array): if isinstance(array, list): array = np.array(array) if isinstance(array, TorchTensor): - return torch.all(array) + return torch.any(array) elif isinstance(array, np.ndarray): - return np.all(array) + return np.any(array) else: raise TypeError(UNKNOWN_ARRAY_TYPE) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 7d0d5cf9f..caa7cba4e 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -20,7 +20,7 @@ def correlate_density( density: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, - selection: Optional[Union[Labels, List[Labels]]] = None, + selected_keys: Optional[Union[Labels, List[Labels]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, ) -> List[TensorMap]: @@ -54,16 +54,14 @@ def correlate_density( :param angular_cutoff: The maximum angular channel to compute at any given CG iteration, applied globally to all iterations until the target correlation order is reached. - :param angular_selection: A list of angular channels to output at each - iteration. If a single list is passed, this is applied to the final - iteration only. If a list of lists is passed, this is applied to each - iteration. If None is passed, all angular channels are output at each - iteration. - :param parity_selection: A list of parity channels to output at each - iteration. If a single list is passed, this is applied to the final - iteration only. If a list of lists is passed, this is applied to each - iteration. If None is passed, all parity channels are output at each - iteration. + :param selected_keys: Labels or List[Labels] specifying the angular and/or + parity channels to output at each iteration. All Labels objects passed + here must only contain key names "spherical_harmonics_l" and + "inversion_sigma". If a single Labels object is passed, this is applied + to the final iteration only. If a list of Labels objects is passed, + each is applied to its corresponding iteration. If None is passed, all + angular and parity channels are output at each iteration, with the + global `angular_cutoff` applied if specified. :param skip_redundant: Whether to skip redundant CG combinations. Defaults to False, which means all combinations are performed. If a list of bool is passed, this is applied to each iteration. If a single bool is @@ -81,7 +79,7 @@ def correlate_density( density, correlation_order, angular_cutoff, - selection, + selected_keys, skip_redundant, output_selection, compute_metadata_only=False, @@ -93,7 +91,7 @@ def correlate_density_metadata( density: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, - selection: Optional[Union[Labels, List[Labels]]] = None, + selected_keys: Optional[Union[Labels, List[Labels]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, ) -> List[TensorMap]: @@ -113,16 +111,14 @@ def correlate_density_metadata( :param angular_cutoff: The maximum angular channel to compute at any given CG iteration, applied globally to all iterations until the target correlation order is reached. - :param angular_selection: A list of angular channels to output at each - iteration. If a single list is passed, this is applied to the final - iteration only. If a list of lists is passed, this is applied to each - iteration. If None is passed, all angular channels are output at each - iteration. - :param parity_selection: A list of parity channels to output at each - iteration. If a single list is passed, this is applied to the final - iteration only. If a list of lists is passed, this is applied to each - iteration. If None is passed, all parity channels are output at each - iteration. + :param selected_keys: Labels or List[Labels] specifying the angular and/or + parity channels to output at each iteration. All Labels objects passed + here must only contain key names "spherical_harmonics_l" and + "inversion_sigma". If a single Labels object is passed, this is applied + to the final iteration only. If a list of Labels objects is passed, + each is applied to its corresponding iteration. If None is passed, all + angular and parity channels are output at each iteration, with the + global `angular_cutoff` applied if specified. :param skip_redundant: Whether to skip redundant CG combinations. Defaults to False, which means all combinations are performed. If a list of bool is passed, this is applied to each iteration. If a single bool is @@ -142,7 +138,7 @@ def correlate_density_metadata( density, correlation_order, angular_cutoff, - selection, + selected_keys, skip_redundant, output_selection, compute_metadata_only=True, @@ -158,7 +154,7 @@ def _correlate_density( density: TensorMap, correlation_order: int, angular_cutoff: Optional[int] = None, - selection: Optional[Union[Labels, List[Labels]]] = None, + selected_keys: Optional[Union[Labels, List[Labels]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, compute_metadata_only: bool = False, @@ -179,13 +175,16 @@ def _correlate_density( density = _standardize_keys(density) # standardize metadata density_correlation = density # create a copy to combine with itself - # Parse the various selection filters - angular_selection, parity_selection = _parse_int_selections( + # Parse the selected keys + selected_keys = _parse_selected_keys( n_iterations=n_iterations, angular_cutoff=angular_cutoff, - selection=selection, + selected_keys=selected_keys, + like=density.keys.values, ) - skip_redundant, output_selection = _parse_bool_selections( + # Parse the bool flags that control skipping of redundant CG combinations + # and TensorMap output from each iteration + skip_redundant, output_selection = _parse_bool_iteration_filters( n_iterations, skip_redundant=skip_redundant, output_selection=output_selection, @@ -196,9 +195,7 @@ def _correlate_density( density.keys, density.keys, n_iterations=n_iterations, - angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, + selected_keys=selected_keys, skip_redundant=skip_redundant, ) # Compute CG coefficient cache @@ -267,7 +264,7 @@ def correlate_tensors( tensor_1: TensorMap, tensor_2: TensorMap, angular_cutoff: Optional[int] = None, - selection: Optional[Labels] = None, + selected_keys: Optional[Labels] = None, ) -> TensorMap: """ Performs the Clebsch Gordan tensor product of two TensorMaps that correspond @@ -314,98 +311,99 @@ def _standardize_keys(tensor: TensorMap) -> TensorMap: return TensorMap(keys=keys, blocks=[b.copy() for b in tensor.blocks()]) -def _parse_int_selections( +def _parse_selected_keys( n_iterations: int, angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[int, List[int], List[List[int]]]] = None, - parity_selection: Optional[Union[int, List[int], List[List[int]]]] = None, -) -> List[List[List[int]]]: + selected_keys: Optional[Union[Labels, List[Labels]]] = None, + like=None, +) -> List[Union[None, Labels]]: """ - Returns a list of length `n_iterations` with selection filters for each CG - combination step, for either `selection_type` "parity" or `selection_type` - "angular". For a given iteration, if no selection is to be applied, the - element of the returned list will be None. - - The input argument `selection` will be parsed in the following ways. - - If `selection=None` is passed, then no filter is applied at any iteration. A - list of [None, None, ...] is returned. - - If an `int` or single List[int] is specified, then this is used for the last - iteration only. For example, if `n_iterations=3` and `selection=[+1]`, then - the filter [None, None, [+1]] is returned. + Parses the `selected_keys` argument passed to public functions. Checks the + values and returns a list of Labels objects, one for each iteration of CG + combination. - If a List[List[int]] is passed, then this is assumed to be the desired - filter for each iteration, and is not modified. - - Basic checks are performed. ValueError is raised if specified parity - selections are not in [-1, +1], or if specified angular selections are not - >= 0. + `like` is required if a new Labels object is to be created by + :py:mod:`_dispatch`. """ + # Check angular_cutoff arg if angular_cutoff is not None: + if not isinstance(angular_cutoff, int): + raise TypeError("`angular_cutoff` must be passed as an int") if angular_cutoff < 1: raise ValueError("`angular_cutoff` must be >= 1") - selections = [] - for selection_type, selection in zip( - ["angular", "parity"], [angular_selection, parity_selection] - ): - if selection is None: - selection = [None] * n_iterations + if selected_keys is None: + if angular_cutoff is None: # no selections at all + selected_keys = [None] * n_iterations else: - # If passed as int, use this for the last iteration only - if isinstance(selection, int): - selection = [None] * (n_iterations - 1) + [[selection]] - else: - if not isinstance(selection, List): - raise TypeError( - "`selection` must be an int, List[int], or List[List[int]]" - ) - if isinstance(selection[0], int): - selection = [None] * (n_iterations - 1) + [selection] - - # Basic checks - if not isinstance(selection, List): - raise TypeError("`selection` must be an int, List[int], or List[List[int]]") - for slct in selection: - if slct is not None: - if not _dispatch.all([isinstance(val, int) for val in slct]): - raise TypeError( - "`selection` must be an int, List[int], or List[List[int]]" - ) - if selection_type == "parity": - if not _dispatch.all([val in [-1, +1] for val in slct]): - raise ValueError( - "specified layers in `selection` must only contain valid" - " parity values of -1 or +1" - ) - if not _dispatch.all([0 < len(slct) <= 2]): - raise ValueError( - "each parity filter must be a list of length 1 or 2," - " with vals +1 and/or -1" - ) - elif selection_type == "angular": - if not _dispatch.all([val >= 0 for val in slct]): - raise ValueError( - "specified layers in `selection` must only contain valid" - " angular channels >= 0" - ) - if angular_cutoff is not None: - if not _dispatch.all([val <= angular_cutoff for val in slct]): - raise ValueError( - "specified layers in `selection` must only contain valid" - " angular channels <= the specified `angular_cutoff`" - ) - else: + # Create a key selection with all angular channels <= the specified + # angular cutoff + selected_keys = [ + Labels( + names=["spherical_harmonics_l"], + values=_dispatch.int_range_like(0, angular_cutoff, like=like).reshape(-1, 1), + ) + ] * n_iterations + + if isinstance(selected_keys, Labels): + # Create a list, but only apply a key selection at the final iteration + selected_keys = [None] * (n_iterations - 1) + [selected_keys] + + # Check the selected_keys + if not isinstance(selected_keys, List): + raise TypeError("`selected_keys` must be a Labels or List[Union[None, Labels]]") + if not len(selected_keys) == n_iterations: + raise ValueError( + "`selected_keys` must be a List[Union[None, Labels]] of length" + " `correlation_order` - 1" + ) + if not _dispatch.all( + [isinstance(val, (Labels, type(None))) for val in selected_keys] + ): + raise TypeError("`selected_keys` must be a Labels or List[Union[None, Labels]]") + + # Now iterate over each of the Labels (or None) in the list and check + for slct in selected_keys: + if slct is None: + continue + assert isinstance(slct, Labels) + if not _dispatch.all( + [ + name in ["spherical_harmonics_l", "inversion_sigma"] + for name in slct.names + ] + ): + raise ValueError( + "specified key names in `selected_keys` must be either" + " 'spherical_harmonics_l' or 'inversion_sigma'" + ) + if "spherical_harmonics_l" in slct.names: + if angular_cutoff is not None: + if not _dispatch.all( + slct.column("spherical_harmonics_l") <= angular_cutoff + ): raise ValueError( - "`selection_type` must be either 'parity' or 'angular'" + "specified angular channels in `selected_keys` must be <= the" + " specified `angular_cutoff`" ) - selections.append(selection) + if not _dispatch.all( + [l >= 0 for l in slct.column("spherical_harmonics_l")] + ): + raise ValueError( + "specified angular channels in `selected_keys` must be >= 0" + ) + if "inversion_sigma" in slct.names: + if not _dispatch.all( + [s in [-1, +1] for s in slct.column("inversion_sigma")] + ): + raise ValueError( + "specified parities in `selected_keys` must be -1 or +1" + ) - return selections + return selected_keys -def _parse_bool_selections( +def _parse_bool_iteration_filters( n_iterations: int, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, @@ -448,9 +446,7 @@ def _precompute_keys( keys_1: Labels, keys_2: Labels, n_iterations: int, - angular_cutoff: int, - angular_selection: List[Union[None, List[int]]], - parity_selection: List[Union[None, List[int]]], + selected_keys: List[Union[None, Labels]], skip_redundant: List[bool], ) -> List[Tuple[Labels, List[List[int]]]]: """ @@ -467,74 +463,38 @@ def _precompute_keys( keys_out = keys_1 for iteration in range(n_iterations): # Get the keys metadata for the combination of the 2 tensors - keys_1_entries, keys_2_entries, keys_out = _precompute_keys_one_iteration( + keys_1_entries, keys_2_entries, keys_out = _precompute_keys_full_product( keys_1=keys_out, keys_2=keys_2, - angular_cutoff=angular_cutoff, - angular_selection=angular_selection[iteration], - parity_selection=parity_selection[iteration], ) + if selected_keys[iteration] is not None: + keys_1_entries, keys_2_entries, keys_out = _apply_key_selection( + keys_1_entries, + keys_2_entries, + keys_out, + selected_keys=selected_keys[iteration], + ) + if skip_redundant[iteration]: keys_1_entries, keys_2_entries, keys_out = _remove_redundant_keys( keys_1_entries, keys_2_entries, keys_out ) - # Check that some keys are produced as a result of the combination - if len(keys_out) == 0: - raise ValueError( - f"invalid selections: iteration {iteration + 1} produces no" - " valid combinations. Check the `angular_selection` and" - " `parity_selection` arguments." - ) - - # Now check the angular and parity selections are present in the new keys - if angular_selection is not None: - if angular_selection[iteration] is not None: - for lam in angular_selection[iteration]: - if lam not in keys_out.column("spherical_harmonics_l"): - raise ValueError( - f"lambda = {lam} specified in `angular_selection`" - f" for iteration {iteration + 1}, but this is not a" - " valid angular channel based on the combination of" - " lower body-order tensors. Check the passed" - " `angular_selection` and try again." - ) - if parity_selection is not None: - if parity_selection[iteration] is not None: - for sig in parity_selection[iteration]: - if sig not in keys_out.column("inversion_sigma"): - raise ValueError( - f"sigma = {sig} specified in `parity_selection`" - f" for iteration {iteration + 1}, but this is not" - " a valid parity based on the combination of lower" - " body-order tensors. Check the passed" - " `parity_selection` and try again." - ) - keys_metadata.append((keys_1_entries, keys_2_entries, keys_out)) return keys_metadata -def _precompute_keys_one_iteration( - keys_1: Labels, - keys_2: Labels, - angular_cutoff: Optional[int] = None, - angular_selection: Optional[Union[None, List[int]]] = None, - parity_selection: Optional[Union[None, List[int]]] = None, -) -> Tuple[Labels, List[List[int]]]: +def _precompute_keys_full_product( + keys_1: Labels, keys_2: Labels +) -> Tuple[List, List, Labels]: """ Given the keys of 2 TensorMaps, returns the keys that would be present after a full CG product of these TensorMaps. - Any angular or parity channel selections passed in `angular_selection` and - `parity_selection` are applied such that only specified channels are present - in the returned combined keys. - Assumes that `keys_1` corresponds to a TensorMap with arbitrary body order, - while `keys_2` corresponds to a TensorMap with body order 1. - - `keys_1` must follow the key name convention: + while `keys_2` corresponds to a TensorMap with body order 1. `keys_1` must + follow the key name convention: ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center", "l1", "l2", ..., f"l{`nu`}", "k2", ..., f"k{`nu`-1}"]. The "lx" columns @@ -555,18 +515,14 @@ def _precompute_keys_one_iteration( \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n_{\nu-1} l_{\nu-1} k_{\nu-1} ; n_{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } - `keys_2` must follow the key name convention: - - ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] + `keys_2` must follow the key name convention: ["order_nu", + "inversion_sigma", "spherical_harmonics_l", "species_center"] Returned is Tuple[List, List, Labels]. The first two lists correspond to the LabelsEntry objects of the keys being combined. The third element is a Labels object corresponding to the keys of the output TensorMap. Each entry in this Labels object corresponds to the keys is formed by combination of the pair of blocks indexed by correspoding key pairs in the first two lists. - - The `parity_selection` argument can be used to return only keys with certain - parities. This must be passed as a list with elements +1 and/or -1. """ # Get the correlation order of the first TensorMap. unique_nu = _dispatch.unique(keys_1.column("order_nu")) @@ -633,27 +589,9 @@ def _precompute_keys_one_iteration( # Now iterate over the non-zero angular channels and apply the custom # selections for lam in nonzero_lams: - # Skip combination if it forms an angular channel of order greater - # than the specified maximum cutoff `angular_cutoff`. - if angular_cutoff is not None: - if lam > angular_cutoff: - continue - - # Skip combination if it creates an angular channel that has not - # been explicitly selected - if angular_selection is not None: - if lam not in angular_selection: - continue - # Calculate new sigma sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) - # Skip combination if it creates a parity that has not been - # explicitly selected - if parity_selection is not None: - if sig not in parity_selection: - continue - # Extract the l and k lists from keys_1 l_list = key_1.values[4 : 4 + nu1].tolist() k_list = key_1.values[4 + nu1 :].tolist() @@ -675,6 +613,54 @@ def _precompute_keys_one_iteration( return keys_1_entries, keys_2_entries, keys_out +def _apply_key_selection( + keys_1_entries: List, keys_2_entries: List, keys_out: Labels, selected_keys: Labels +) -> Tuple[List, List, Labels]: + """ + Applies a selection according to `selected_keys` to the keys of an output + TensorMap `keys_out` produced by combination of blocks indexed by keys + entries in `keys_1_entries` and `keys_2_entries` lists. + + After application of the selections, returned is a reduced set of keys and + set of corresponding parents key entries. + + If a selection in `selected_keys` is not valid based on the keys in + `keys_out`, an error is raised. + """ + # Extract the relevant columns from `selected_keys` that the selection will + # be performed on + keys_out_vals = [[k[name] for name in selected_keys.names] for k in keys_out] + + # First check that all of the selected keys exist in the output keys + for slct in selected_keys.values: + if not _dispatch.any([_dispatch.all(slct == k) for k in keys_out_vals]): + raise ValueError( + f"selected key {selected_keys.names} = {slct} not found" + " in the output keys. Check the `selected_keys` argument." + ) + + # Build a mask of the selected keys + mask = [ + _dispatch.any([_dispatch.all(i == j) for j in selected_keys.values]) + for i in keys_out_vals + ] + + # Apply the mask to key entries and keys and return + keys_1_entries = [k for k, isin in zip(keys_1_entries, mask) if isin] + keys_2_entries = [k for k, isin in zip(keys_2_entries, mask) if isin] + keys_out = Labels(names=keys_out.names, values=keys_out.values[mask]) + + # Check that some keys are produced as a result of the combination + if len(keys_out) == 0: + raise ValueError( + f"invalid selections: iteration {iteration + 1} produces no" + " valid combinations. Check the `angular_selection` and" + " `parity_selection` arguments." + ) + + return keys_1_entries, keys_2_entries, keys_out + + def _remove_redundant_keys( keys_1_entries: List, keys_2_entries: List, keys_out: Labels ) -> Tuple[List, List, Labels]: diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 1d4970b4e..d008be88a 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -11,7 +11,10 @@ import rascaline from metatensor import Labels, TensorBlock, TensorMap from rascaline.utils import clebsch_gordan, PowerSpectrum -from rascaline.utils.clebsch_gordan.clebsch_gordan import _correlate_density, _standardize_keys +from rascaline.utils.clebsch_gordan.clebsch_gordan import ( + _correlate_density, + _standardize_keys, +) from .rotations import WignerDReal, transform_frame_so3, transform_frame_o3 from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal @@ -127,19 +130,26 @@ def get_norm(tensor: TensorMap): @pytest.mark.parametrize( - "frames, nu_target, angular_cutoff, angular_selection, parity_selection", + "frames, nu_target, angular_cutoff, selected_keys", [ - (h2_isolated(), 3, None, [0, 4, 5], [+1]), - (h2o_isolated(), 2, 5, None, None), - (h2o_periodic(), 2, 5, None, None), + ( + h2_isolated(), + 3, + None, + Labels( + names=["spherical_harmonics_l", "inversion_sigma"], + values=np.array([[0, 1], [4, 1], [5, 1]]), + ), + ), + (h2o_isolated(), 2, 5, None), + (h2o_periodic(), 2, 5, None), ], ) def test_so3_equivariance( frames: List[ase.Atoms], nu_target: int, angular_cutoff: int, - angular_selection: List[List[int]], - parity_selection: List[List[int]], + selected_keys: Labels, ): wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) frames_so3 = [transform_frame_so3(frame, wig.angles) for frame in frames] @@ -151,16 +161,14 @@ def test_so3_equivariance( density=nu_1, correlation_order=nu_target, angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, + selected_keys=selected_keys, compute_metadata_only=False, )[0] nu_3_so3 = _correlate_density( density=nu_1_so3, correlation_order=nu_target, angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, + selected_keys=selected_keys, compute_metadata_only=False, )[0] @@ -169,19 +177,26 @@ def test_so3_equivariance( @pytest.mark.parametrize( - "frames, nu_target, angular_cutoff, angular_selection, parity_selection", + "frames, nu_target, angular_cutoff, selected_keys", [ - (h2_isolated(), 3, None, [0, 4, 5], None), - (h2o_isolated(), 2, 5, None, None), - (h2o_periodic(), 2, 5, None, None), + ( + h2_isolated(), + 3, + None, + Labels( + names=["spherical_harmonics_l"], + values=np.array([0, 4, 5]).reshape(-1, 1), + ), + ), + (h2o_isolated(), 2, 5, None), + (h2o_periodic(), 2, 5, None), ], ) def test_o3_equivariance( frames: List[ase.Atoms], nu_target: int, angular_cutoff: int, - angular_selection: List[List[int]], - parity_selection: List[List[int]], + selected_keys: Labels, ): wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) frames_o3 = [transform_frame_o3(frame, wig.angles) for frame in frames] @@ -193,16 +208,14 @@ def test_o3_equivariance( density=nu_1, correlation_order=nu_target, angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, + selected_keys=selected_keys, compute_metadata_only=False, )[0] nu_3_o3 = _correlate_density( density=nu_1_o3, correlation_order=nu_target, angular_cutoff=angular_cutoff, - angular_selection=angular_selection, - parity_selection=parity_selection, + selected_keys=selected_keys, compute_metadata_only=False, )[0] @@ -228,7 +241,9 @@ def test_lambda_soap_vs_powerspectrum(frames): lsoap = _correlate_density( density=density, correlation_order=2, - angular_selection=[0], + selected_keys=Labels( + names=["spherical_harmonics_l"], values=np.array([0]).reshape(-1, 1) + ), compute_metadata_only=False, )[0] keys = lsoap.keys.remove(name="spherical_harmonics_l") @@ -280,8 +295,7 @@ def test_combine_single_center_norm(frames, correlation_order): nu1, correlation_order=correlation_order, angular_cutoff=None, - angular_selection=None, - parity_selection=None, + selected_keys=None, skip_redundant=False, compute_metadata_only=False, sparse=True, @@ -291,8 +305,7 @@ def test_combine_single_center_norm(frames, correlation_order): nu1, correlation_order=correlation_order, angular_cutoff=None, - angular_selection=None, - parity_selection=None, + selected_keys=None, skip_redundant=True, compute_metadata_only=False, sparse=True, @@ -426,8 +439,7 @@ def test_single_center_combine_to_correlation_order_metadata_agree( nu1, correlation_order=correlation_order, angular_cutoff=None, - angular_selection=None, - parity_selection=None, + selected_keys=None, skip_redundant=skip_redundant, compute_metadata_only=False, sparse=True, @@ -438,8 +450,7 @@ def test_single_center_combine_to_correlation_order_metadata_agree( nu1, correlation_order=correlation_order, angular_cutoff=None, - angular_selection=None, - parity_selection=None, + selected_keys=None, skip_redundant=skip_redundant, compute_metadata_only=True, )[0] @@ -447,56 +458,37 @@ def test_single_center_combine_to_correlation_order_metadata_agree( @pytest.mark.parametrize("frames", [h2o_isolated()]) -@pytest.mark.parametrize("correlation_order", [2, 3]) -@pytest.mark.parametrize("skip_redundant", [True, False]) -def test_single_center_combine_to_correlation_order_metadata( - frames, correlation_order, skip_redundant -): - """ - Performs hard-coded tests on the metadata outputted from - _correlate_density. - - TODO: finish! - """ - # for nu1 in [sphex_small_features(frames), sphex(frames)]: - # # Build higher body order tensor without CG computation - i.e. metadata - # # only. This returns a list of the TensorMaps formed at each CG - # # iteration. - # nux_metadata_only = ( - # clebsch_gordan.correlate_density_metadata( - # nu1, - # correlation_order=correlation_order, - # angular_cutoff=None, - # angular_selection=None, - # parity_selection=None, - # skip_redundant=skip_redundant, - # ) - # ) - - -@pytest.mark.parametrize("frames", [h2o_isolated()]) -@pytest.mark.parametrize("angular_selection", [None, [1, 2, 4]]) +@pytest.mark.parametrize( + "selected_keys", + [ + None, + Labels( + names=["spherical_harmonics_l"], values=np.array([1, 2, 4]).reshape(-1, 1) + ), + ], +) @pytest.mark.parametrize("skip_redundant", [True, False]) def test_single_center_combine_angular_selection( frames: List[ase.Atoms], - angular_selection: List[List[int]], + selected_keys: Labels, skip_redundant: bool, ): - """Tests that the correct angular channels are outputted based on the - specified ``angular_cutoff`` and ``angular_selection``.""" + """ + Tests that the correct angular channels are outputted based on the + specified ``selected_keys``. + """ nu_1 = sphex(frames) nu_2 = _correlate_density( density=nu_1, correlation_order=2, angular_cutoff=None, - angular_selection=angular_selection, - parity_selection=None, + selected_keys=selected_keys, skip_redundant=skip_redundant, compute_metadata_only=False, )[0] - if angular_selection is None: + if selected_keys is None: assert np.all( [ l in np.arange(SPHEX_HYPERS["max_angular"] * 2 + 1) @@ -507,8 +499,5 @@ def test_single_center_combine_angular_selection( else: assert np.all( np.sort(np.unique(nu_2.keys.column("spherical_harmonics_l"))) - == np.sort(angular_selection) + == np.sort(selected_keys.column("spherical_harmonics_l")) ) - - -# ============ Test dispatch to torch/numpy ============ From f589abe4640bb270d1c9df3c79d9d9b79db93203 Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 23 Nov 2023 09:59:29 +0100 Subject: [PATCH 93/96] Remove not implemented function --- .../utils/clebsch_gordan/clebsch_gordan.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index caa7cba4e..e91dd1c77 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -260,23 +260,6 @@ def _correlate_density( return density_correlations -def correlate_tensors( - tensor_1: TensorMap, - tensor_2: TensorMap, - angular_cutoff: Optional[int] = None, - selected_keys: Optional[Labels] = None, -) -> TensorMap: - """ - Performs the Clebsch Gordan tensor product of two TensorMaps that correspond - to densities or density correlations. Returns a new TensorMap corresponding - to a higher correlation-order descriptor. - - The two input tensors can be single- or multi-center, and of arbitrary (and - different) correlation order, but must contain the same samples. - """ - raise NotImplementedError - - # ================================================================== # ===== Functions to handle metadata # ================================================================== From 6dc73889a6adcf2e34fb24967cbd039f45055bbe Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 23 Nov 2023 10:55:43 +0100 Subject: [PATCH 94/96] Make the CI happy --- python/rascaline/rascaline/utils/__init__.py | 2 - .../utils/clebsch_gordan/__init__.py | 5 +-- .../utils/clebsch_gordan/_cg_cache.py | 8 ++-- .../utils/clebsch_gordan/_dispatch.py | 16 ++++--- .../utils/clebsch_gordan/clebsch_gordan.py | 35 ++++++++------- .../rascaline/tests/utils/clebsch_gordan.py | 36 ++++++++------- python/rascaline/tests/utils/rotations.py | 44 ++++++++++--------- tox.ini | 3 ++ 8 files changed, 84 insertions(+), 65 deletions(-) diff --git a/python/rascaline/rascaline/utils/__init__.py b/python/rascaline/rascaline/utils/__init__.py index 0d3c5d748..8309ac44c 100644 --- a/python/rascaline/rascaline/utils/__init__.py +++ b/python/rascaline/rascaline/utils/__init__.py @@ -1,7 +1,5 @@ import os -import metatensor - from .clebsch_gordan import * # noqa from .power_spectrum import PowerSpectrum # noqa from .splines import ( # noqa diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py index 935cb51ae..38777d18e 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/__init__.py @@ -1,7 +1,4 @@ -from .clebsch_gordan import ( # noqa - correlate_density, - correlate_density_metadata, -) +from .clebsch_gordan import correlate_density, correlate_density_metadata # noqa __all__ = [ diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py index a4e989897..1e5305c57 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py @@ -7,7 +7,7 @@ try: - from mops import sparse_accumulation_of_products as sap + import mops # noqa F401 HAS_MOPS = True except ImportError: @@ -127,7 +127,8 @@ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MO self._use_mops = False else: print( - "Backend Clebsch Gordan tensor products will be performed with Mops." + "Backend Clebsch Gordan tensor products" + " will be performed with Mops." ) self._use_mops = True @@ -136,7 +137,8 @@ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MO # if HAS_MOPS: # import warnings # warnings.warn( - # "Mops is installed, but not being used as dense operations chosen." + # "Mops is installed, but not being used" + # " as dense operations chosen." # ) self._use_mops = False diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index 774b09c42..0af5d99e1 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -2,10 +2,12 @@ Module containing dispatch functions for numpy/torch CG combination operations. """ from typing import List, Optional, Union + import numpy as np from ._cg_cache import HAS_MOPS + if HAS_MOPS: from mops import sparse_accumulation_of_products as sap @@ -197,8 +199,9 @@ def dense_combine( l2 = (arr_2.shape[1] - 1) // 2 cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] - # (samples None None l1_mu q) * (samples l2_mu p None None) -> (samples l2_mu p l1_mu q) - # we broadcast it in this way so we only need to do one swapaxes in the next step + # (samples None None l1_mu q) * (samples l2_mu p None None) + # -> (samples l2_mu p l1_mu q) we broadcast it in this way + # so we only need to do one swapaxes in the next step arr_out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] # (samples l2_mu p l1_mu q) -> (samples q p l1_mu l2_mu) @@ -206,13 +209,16 @@ def dense_combine( # samples (q p l1_mu l2_mu) -> (samples (q p) (l1_mu l2_mu)) arr_out = arr_out.reshape( - -1, arr_1.shape[2] * arr_2.shape[2], arr_1.shape[1] * arr_2.shape[1] + -1, + arr_1.shape[2] * arr_2.shape[2], + arr_1.shape[1] * arr_2.shape[1], ) # (l1_mu l2_mu lam_mu) -> ((l1_mu l2_mu) lam_mu) cg_coeffs = cg_coeffs.reshape(-1, 2 * lam + 1) - # (samples (q p) (l1_mu l2_mu)) @ ((l1_mu l2_mu) lam_mu) -> samples (q p) lam_mu + # (samples (q p) (l1_mu l2_mu)) @ ((l1_mu l2_mu) lam_mu) + # -> samples (q p) lam_mu arr_out = arr_out @ cg_coeffs # (samples (q p) lam_mu) -> (samples lam_mu (q p)) @@ -237,7 +243,7 @@ def int_range_like(min_val, max_val, like): """Returns an array of integers from min to max, non-inclusive, based on the type of `like`""" if isinstance(like, TorchTensor): - return torch.arange(int_list, dtype=torch.int64, device=like.device) + return torch.arange(min_val, max_val, dtype=torch.int64, device=like.device) elif isinstance(like, np.ndarray): return np.arange(min_val, max_val).astype(np.int64) else: diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index e91dd1c77..96d265f80 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -4,7 +4,6 @@ import itertools from typing import List, Optional, Tuple, Union -import metatensor from metatensor import Labels, TensorBlock, TensorMap from . import _dispatch @@ -324,7 +323,9 @@ def _parse_selected_keys( selected_keys = [ Labels( names=["spherical_harmonics_l"], - values=_dispatch.int_range_like(0, angular_cutoff, like=like).reshape(-1, 1), + values=_dispatch.int_range_like( + 0, angular_cutoff, like=like + ).reshape(-1, 1), ) ] * n_iterations @@ -370,14 +371,14 @@ def _parse_selected_keys( " specified `angular_cutoff`" ) if not _dispatch.all( - [l >= 0 for l in slct.column("spherical_harmonics_l")] + [angular_l >= 0 for angular_l in slct.column("spherical_harmonics_l")] ): raise ValueError( "specified angular channels in `selected_keys` must be >= 0" ) if "inversion_sigma" in slct.names: if not _dispatch.all( - [s in [-1, +1] for s in slct.column("inversion_sigma")] + [parity_s in [-1, +1] for parity_s in slct.column("inversion_sigma")] ): raise ValueError( "specified parities in `selected_keys` must be -1 or +1" @@ -463,6 +464,14 @@ def _precompute_keys( keys_1_entries, keys_2_entries, keys_out ) + # Check that some keys are produced as a result of the combination + if len(keys_out) == 0: + raise ValueError( + f"invalid selections: iteration {iteration + 1} produces no" + " valid combinations. Check the `angular_cutoff` and" + " `selected_keys` args and try again." + ) + keys_metadata.append((keys_1_entries, keys_2_entries, keys_out)) return keys_metadata @@ -495,8 +504,12 @@ def _precompute_keys_full_product( .. math :: - \bra{ n_1 l_1 ; n_2 l_2 k_2 ; ... ; n_{\nu-1} l_{\nu-1} k_{\nu-1} ; - n_{\nu} l_{\nu} k_{\nu}; \lambda } \ket{ \rho^{\otimes \nu}; \lambda M } + \\bra{ + n_1 l_1 ; n_2 l_2 k_2 ; ... ; + n_{\nu-1} l_{\\nu-1} k_{\\nu-1} ; + n_{\\nu} l_{\\nu} k_{\\nu}; \\lambda + } + \\ket{ \\rho^{\\otimes \\nu}; \\lambda M } `keys_2` must follow the key name convention: ["order_nu", "inversion_sigma", "spherical_harmonics_l", "species_center"] @@ -527,7 +540,7 @@ def _precompute_keys_full_product( l_list_names = [] new_l_list_names = ["l1", "l2"] else: - l_list_names = [f"l{l}" for l in range(1, nu1 + 1)] + l_list_names = [f"l{angular_l}" for angular_l in range(1, nu1 + 1)] new_l_list_names = l_list_names + [f"l{nu}"] # Check key names @@ -633,14 +646,6 @@ def _apply_key_selection( keys_2_entries = [k for k, isin in zip(keys_2_entries, mask) if isin] keys_out = Labels(names=keys_out.names, values=keys_out.values[mask]) - # Check that some keys are produced as a result of the combination - if len(keys_out) == 0: - raise ValueError( - f"invalid selections: iteration {iteration + 1} produces no" - " valid combinations. Check the `angular_selection` and" - " `parity_selection` arguments." - ) - return keys_1_entries, keys_2_entries, keys_out diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index d008be88a..b3bd3ae1a 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -3,21 +3,21 @@ from typing import List import ase.io +import metatensor import numpy as np import pytest -from numpy.testing import assert_allclose +from metatensor import Labels, TensorBlock, TensorMap -import metatensor import rascaline -from metatensor import Labels, TensorBlock, TensorMap -from rascaline.utils import clebsch_gordan, PowerSpectrum +from rascaline.utils import PowerSpectrum +from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal from rascaline.utils.clebsch_gordan.clebsch_gordan import ( _correlate_density, _standardize_keys, ) -from .rotations import WignerDReal, transform_frame_so3, transform_frame_o3 -from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal +from .rotations import WignerDReal, transform_frame_o3, transform_frame_so3 + DATA_ROOT = os.path.join(os.path.dirname(__file__), "data") @@ -118,9 +118,12 @@ def get_norm(tensor: TensorMap): """ norm = 0.0 for key, block in tensor.items(): # Sum over lambda and sigma - l = key["spherical_harmonics_l"] + angular_l = key["spherical_harmonics_l"] norm += np.sum( - [np.linalg.norm(block.values[0, m, :]) ** 2 for m in range(-l, l + 1)] + [ + np.linalg.norm(block.values[0, m, :]) ** 2 + for m in range(-angular_l, angular_l + 1) + ] ) return norm @@ -282,9 +285,10 @@ def test_lambda_soap_vs_powerspectrum(frames): @pytest.mark.parametrize("correlation_order", [2, 3, 4]) def test_combine_single_center_norm(frames, correlation_order): """ - Checks \|ρ^\\nu\| = \|ρ\|^\\nu in the case where l lists are not sorted. If - l lists are sorted, thus saving computation of redundant block combinations, - the norm check will not hold for target body order greater than 2. + Checks \\|ρ^\\nu\\| = \\|ρ\\|^\\nu in the case where l lists are not + sorted. If l lists are sorted, thus saving computation of redundant block + combinations, the norm check will not hold for target body order greater + than 2. """ # Build nu=1 SphericalExpansion @@ -341,9 +345,9 @@ def test_combine_single_center_norm(frames, correlation_order): nux_sorted_sliced = metatensor.slice(nux_sorted_l, "samples", labels=sample) # Calculate norms - norm_nu1 += get_norm(nu1) ** correlation_order - norm_nux += get_norm(nux) - norm_nux_sorted_l += get_norm(nux_sorted_l) + norm_nu1 += get_norm(nu1_sliced) ** correlation_order + norm_nux += get_norm(nux_sliced) + norm_nux_sorted_l += get_norm(nux_sorted_sliced) # Without sorting the l list we should get the same norm assert np.allclose(norm_nu1, norm_nux) @@ -491,8 +495,8 @@ def test_single_center_combine_angular_selection( if selected_keys is None: assert np.all( [ - l in np.arange(SPHEX_HYPERS["max_angular"] * 2 + 1) - for l in np.unique(nu_2.keys.column("spherical_harmonics_l")) + angular in np.arange(SPHEX_HYPERS["max_angular"] * 2 + 1) + for angular in np.unique(nu_2.keys.column("spherical_harmonics_l")) ] ) diff --git a/python/rascaline/tests/utils/rotations.py b/python/rascaline/tests/utils/rotations.py index edf90b3d6..f4a2c9b5f 100644 --- a/python/rascaline/tests/utils/rotations.py +++ b/python/rascaline/tests/utils/rotations.py @@ -9,6 +9,7 @@ from metatensor import TensorBlock, TensorMap from scipy.spatial.transform import Rotation + try: import torch from torch import Tensor as TorchTensor @@ -73,6 +74,7 @@ def transform_frame_o3(frame: ase.Atoms, angles: Sequence[float]) -> ase.Atoms: # ===== WignerDReal for transformations in the spherical basis ===== + class WignerDReal: """ A helper class to compute Wigner D matrices given the Euler angles of a rotation, @@ -171,30 +173,30 @@ def rotate_coeff_vector( for symbol in frame.get_chemical_symbols(): # Get the basis set lmax value for this species sym_lmax = lmax[symbol] - for l in range(sym_lmax + 1): + for angular_l in range(sym_lmax + 1): # Get the number of radial functions for this species and l value - sym_l_nmax = nmax[(symbol, l)] + sym_l_nmax = nmax[(symbol, angular_l)] # Get the Wigner D Matrix for this l value - wig_mat = self.matrices[l].T - for n in range(sym_l_nmax): + wig_mat = self.matrices[angular_l].T + for _n in range(sym_l_nmax): # Retrieve the irreducible spherical component - isc = coeffs[curr_idx : curr_idx + (2 * l + 1)] + isc = coeffs[curr_idx : curr_idx + (2 * angular_l + 1)] # Rotate the ISC and store rot_isc = isc @ wig_mat - rot_vect[curr_idx : curr_idx + (2 * l + 1)][:] = rot_isc[:] + rot_vect[curr_idx : curr_idx + (2 * angular_l + 1)][:] = rot_isc[:] # Update the start index for the next ISC - curr_idx += 2 * l + 1 + curr_idx += 2 * angular_l + 1 return rot_vect - def rotate_tensorblock(self, l: int, block: TensorBlock) -> TensorBlock: + def rotate_tensorblock(self, angular_l: int, block: TensorBlock) -> TensorBlock: """ Rotates a TensorBlock ``block``, represented in the spherical basis, according to the Wigner D Real matrices for the given ``l`` value. Assumes the components of the block are [("spherical_harmonics_m",),]. """ # Get the Wigner matrix for this l value - wig = self.matrices[l].T + wig = self.matrices[angular_l].T # Copy the block block_rotated = block.copy() @@ -233,10 +235,10 @@ def transform_tensormap_so3(self, tensor: TensorMap) -> TensorMap: rotated_blocks = [] for key in keys: # Retrieve the l value - l = key[idx_l_value] + angular_l = key[idx_l_value] # Rotate the block and store - rotated_blocks.append(self.rotate_tensorblock(l, tensor[key])) + rotated_blocks.append(self.rotate_tensorblock(angular_l, tensor[key])) return TensorMap(keys, rotated_blocks) @@ -256,10 +258,10 @@ def transform_tensormap_o3(self, tensor: TensorMap) -> TensorMap: new_blocks = [] for key in keys: # Retrieve the l value - l = key[idx_l_value] + angular_l = key[idx_l_value] # Rotate the block - new_block = self.rotate_tensorblock(l, tensor[key]) + new_block = self.rotate_tensorblock(angular_l, tensor[key]) # Work out the inversion multiplier according to the convention inversion_multiplier = 1 @@ -287,7 +289,7 @@ def transform_tensormap_o3(self, tensor: TensorMap) -> TensorMap: # ===== Helper functions for WignerDReal -def _wigner_d(l: int, angles: Sequence[float]) -> np.ndarray: +def _wigner_d(angular_l: int, angles: Sequence[float]) -> np.ndarray: """ Computes the Wigner D matrix: D^l_{mm'}(alpha, beta, gamma) @@ -302,7 +304,7 @@ def _wigner_d(l: int, angles: Sequence[float]) -> np.ndarray: raise ModuleNotFoundError( "Calculation of Wigner D matrices requires a sympy installation" ) - return np.complex128(wigner_d(l, *angles)) + return np.complex128(wigner_d(angular_l, *angles)) def _r2c(sp): @@ -313,10 +315,12 @@ def _r2c(sp): i_sqrt_2 = 1.0 / np.sqrt(2) - l = (len(sp) - 1) // 2 # infers l from the vector size + angular_l = (len(sp) - 1) // 2 # infers l from the vector size rc = np.zeros(len(sp), dtype=np.complex128) - rc[l] = sp[l] - for m in range(1, l + 1): - rc[l + m] = (sp[l + m] + 1j * sp[l - m]) * i_sqrt_2 * (-1) ** m - rc[l - m] = (sp[l + m] - 1j * sp[l - m]) * i_sqrt_2 + rc[angular_l] = sp[angular_l] + for m in range(1, angular_l + 1): + rc[angular_l + m] = ( + (sp[angular_l + m] + 1j * sp[angular_l - m]) * i_sqrt_2 * (-1) ** m + ) + rc[angular_l - m] = (sp[angular_l + m] - 1j * sp[angular_l - m]) * i_sqrt_2 return rc diff --git a/tox.ini b/tox.ini index ff608e664..ce2f37be7 100644 --- a/tox.ini +++ b/tox.ini @@ -74,6 +74,9 @@ deps = {[testenv]metatensor-core-requirement} pytest pytest-cov + ase + metatensor-operations + sympy commands = pytest {[testenv]test_options} {posargs} From 148ed002074de6c1cd2130fbbb1bbe0ddd43ed4d Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Thu, 23 Nov 2023 17:29:31 +0100 Subject: [PATCH 95/96] Ensure the CI truly has absolutely nothing to complain about --- .../rascaline/tests/utils/clebsch_gordan.py | 62 +++++++++++++++---- python/rascaline/tests/utils/rotations.py | 16 +++-- tox.ini | 6 +- 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index b3bd3ae1a..61809f115 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -2,21 +2,43 @@ import os from typing import List -import ase.io -import metatensor import numpy as np import pytest -from metatensor import Labels, TensorBlock, TensorMap -import rascaline -from rascaline.utils import PowerSpectrum -from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal -from rascaline.utils.clebsch_gordan.clebsch_gordan import ( + +# Try to import some modules +ase = pytest.importorskip("ase") +import ase.io # noqa: E402 + + +try: + import metatensor.operations + + HAS_METATENSOR_OPERATIONS = True +except ImportError: + HAS_METATENSOR_OPERATIONS = False +try: + import sympy # noqa F401 + + HAS_SYMPY = True +except ImportError: + HAS_SYMPY = False + + +import metatensor # noqa: E402 +from metatensor import Labels, TensorBlock, TensorMap # noqa: E402 + +import rascaline # noqa: E402 +from rascaline.utils import PowerSpectrum # noqa: E402 +from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal # noqa: E402 +from rascaline.utils.clebsch_gordan.clebsch_gordan import ( # noqa: E402 _correlate_density, _standardize_keys, ) -from .rotations import WignerDReal, transform_frame_o3, transform_frame_so3 + +if HAS_SYMPY: + from .rotations import WignerDReal, transform_frame_o3, transform_frame_so3 DATA_ROOT = os.path.join(os.path.dirname(__file__), "data") @@ -132,6 +154,10 @@ def get_norm(tensor: TensorMap): # ============ Test equivariance ============ +@pytest.mark.skipif( + HAS_SYMPY is False and HAS_METATENSOR_OPERATIONS is False, + reason="SymPy and metatensor-operations are not installed", +) @pytest.mark.parametrize( "frames, nu_target, angular_cutoff, selected_keys", [ @@ -179,6 +205,10 @@ def test_so3_equivariance( assert metatensor.allclose(nu_3_transf, nu_3_so3) +@pytest.mark.skipif( + HAS_SYMPY is False and HAS_METATENSOR_OPERATIONS is False, + reason="SymPy and metatensor-operations are not installed", +) @pytest.mark.parametrize( "frames, nu_target, angular_cutoff, selected_keys", [ @@ -229,6 +259,9 @@ def test_o3_equivariance( # ============ Test lambda-SOAP vs PowerSpectrum ============ +@pytest.mark.skipif( + HAS_METATENSOR_OPERATIONS is False, reason="metatensor-operations is not installed" +) @pytest.mark.parametrize("frames", [h2_isolated()]) def test_lambda_soap_vs_powerspectrum(frames): """ @@ -281,6 +314,9 @@ def test_lambda_soap_vs_powerspectrum(frames): # ============ Test norm preservation ============ +@pytest.mark.skipif( + HAS_METATENSOR_OPERATIONS is False, reason="metatensor-operations is not installed" +) @pytest.mark.parametrize("frames", [h2_isolated(), h2o_periodic()]) @pytest.mark.parametrize("correlation_order", [2, 3, 4]) def test_combine_single_center_norm(frames, correlation_order): @@ -399,6 +435,9 @@ def test_clebsch_gordan_orthogonality(cg_cache_dense, l1, l2): assert np.allclose(dot_product[diag_mask], dot_product[diag_mask][0]) +@pytest.mark.skipif( + HAS_METATENSOR_OPERATIONS is False, reason="metatensor-operations is not installed" +) @pytest.mark.parametrize("frames", [h2_isolated(), h2o_isolated()]) def test_single_center_combine_to_correlation_order_dense_sparse_agree(frames): """ @@ -419,14 +458,15 @@ def test_single_center_combine_to_correlation_order_dense_sparse_agree(frames): compute_metadata_only=False, )[0] - assert metatensor.operations.allclose( - n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8 - ) + assert metatensor.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) # ============ Test metadata ============ +@pytest.mark.skipif( + HAS_METATENSOR_OPERATIONS is False, reason="metatensor-operations is not installed" +) @pytest.mark.parametrize("frames", [h2o_isolated()]) @pytest.mark.parametrize("correlation_order", [2, 3]) @pytest.mark.parametrize("skip_redundant", [True, False]) diff --git a/python/rascaline/tests/utils/rotations.py b/python/rascaline/tests/utils/rotations.py index f4a2c9b5f..2abe22714 100644 --- a/python/rascaline/tests/utils/rotations.py +++ b/python/rascaline/tests/utils/rotations.py @@ -4,15 +4,19 @@ """ from typing import Sequence -import ase -import numpy as np -from metatensor import TensorBlock, TensorMap -from scipy.spatial.transform import Rotation +import pytest + + +ase = pytest.importorskip("ase") + +import numpy as np # noqa: E402 +from metatensor import TensorBlock, TensorMap # noqa: E402 +from scipy.spatial.transform import Rotation # noqa: E402 try: - import torch - from torch import Tensor as TorchTensor + import torch # noqa: E402 + from torch import Tensor as TorchTensor # noqa: E402 except ImportError: class TorchTensor: diff --git a/tox.ini b/tox.ini index ce2f37be7..ed7ce7872 100644 --- a/tox.ini +++ b/tox.ini @@ -62,7 +62,8 @@ deps = scipy sympy wigners - git+https://github.com/lab-cosmo/mops + # TODO: add mops once support for windows available + # git+https://github.com/lab-cosmo/mops commands = pytest {[testenv]test_options} {posargs} @@ -74,9 +75,6 @@ deps = {[testenv]metatensor-core-requirement} pytest pytest-cov - ase - metatensor-operations - sympy commands = pytest {[testenv]test_options} {posargs} From 67878335651e6543974353c9a492ad88c2a302bb Mon Sep 17 00:00:00 2001 From: Joseph Abbott Date: Mon, 27 Nov 2023 17:43:36 +0100 Subject: [PATCH 96/96] Review edits --- .../utils/clebsch_gordan/_cg_cache.py | 301 +++++++++++++++--- .../utils/clebsch_gordan/_dispatch.py | 213 +------------ .../utils/clebsch_gordan/clebsch_gordan.py | 192 +++++------ .../rascaline/tests/utils/clebsch_gordan.py | 211 ++++++------ 4 files changed, 464 insertions(+), 453 deletions(-) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py index 1e5305c57..8ad082fb8 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_cg_cache.py @@ -2,17 +2,33 @@ Module that stores the ClebschGordanReal class for computing and caching Clebsch Gordan coefficients for use in CG combinations. """ +from typing import Union + import numpy as np import wigners +from . import _dispatch + try: - import mops # noqa F401 + from mops import sparse_accumulation_of_products as sap # noqa F401 HAS_MOPS = True except ImportError: HAS_MOPS = False +try: + from torch import Tensor as TorchTensor +except ImportError: + + class TorchTensor: + pass + + +UNKNOWN_ARRAY_TYPE = ( + "unknown array type, only numpy arrays and torch tensors are supported" +) + # ================================= # ===== ClebschGordanReal class @@ -26,7 +42,8 @@ class ClebschGordanReal: Stores the coefficients in a dictionary in the `self.coeffs` attribute, which is built at initialization. There are 3 current use cases for the - format of these coefficients. + format of these coefficients. By default, sparse accumulation of products is + performed, whether or not Mops is installed. Case 1: standard sparse format. @@ -72,7 +89,8 @@ class ClebschGordanReal: Each dictionary entry contains a tuple with four 1D arrays, corresponding to the CG coeffs and m1, m2, mu indices respectively. All of these arrays are sorted according to the mu index. This format is used for Sparse - Accumulation of Products (SAP) as implemented in MOPS. + Accumulation of Products (SAP) as implemented in MOPS. See + https://github.com/lab-cosmo/mops . { (l1, l2, lambda): @@ -101,7 +119,9 @@ class ClebschGordanReal: where `cg_{m1, m2, mu}^{l1, l2, lambda}` is the Clebsch-Gordan coefficient that describes the combination of the `m1` irreducible component of the `l1` angular channel and the `m2` irreducible component of the `l2` angular - channel into the irreducible tensor of order `lambda`. + channel into the irreducible tensor of order `lambda`. In all cases, these + correspond to the non-zero CG coefficients, i.e. those in the range |-l, + ..., +l| for each angular order l in {l1, l2, lambda}. :param lambda_max: maximum lambda value to compute CG coefficients for. :param sparse: whether to store the CG coefficients in sparse format. @@ -110,7 +130,7 @@ class ClebschGordanReal: be used if Mops is installed. """ - def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MOPS): + def __init__(self, lambda_max: int, sparse: bool = True, use_mops: bool = HAS_MOPS): self._lambda_max = lambda_max self._sparse = sparse @@ -126,10 +146,6 @@ def __init__(self, lambda_max: int, sparse: bool = None, use_mops: bool = HAS_MO # ) self._use_mops = False else: - print( - "Backend Clebsch Gordan tensor products" - " will be performed with Mops." - ) self._use_mops = True else: @@ -174,16 +190,16 @@ def build_coeff_dict(lambda_max: int, sparse: bool, use_mops: bool): r2c = {} c2r = {} coeff_dict = {} - for lam in range(0, lambda_max + 1): - c2r[lam] = _complex2real(lam) - r2c[lam] = _real2complex(lam) + for lambda_ in range(0, lambda_max + 1): + c2r[lambda_] = _complex2real(lambda_) + r2c[lambda_] = _real2complex(lambda_) for l1 in range(lambda_max + 1): for l2 in range(lambda_max + 1): - for lam in range( + for lambda_ in range( max(l1, l2) - min(l1, l2), min(lambda_max, (l1 + l2)) + 1 ): - complex_cg = _complex_clebsch_gordan_matrix(l1, l2, lam) + complex_cg = _complex_clebsch_gordan_matrix(l1, l2, lambda_) real_cg = (r2c[l1].T @ complex_cg.reshape(2 * l1 + 1, -1)).reshape( complex_cg.shape @@ -195,9 +211,9 @@ def build_coeff_dict(lambda_max: int, sparse: bool, use_mops: bool): ) real_cg = real_cg.swapaxes(0, 1) - real_cg = real_cg @ c2r[lam].T + real_cg = real_cg @ c2r[lambda_].T - if (l1 + l2 + lam) % 2 == 0: + if (l1 + l2 + lambda_) % 2 == 0: cg_l1l2lam = np.real(real_cg) else: cg_l1l2lam = np.imag(real_cg) @@ -232,7 +248,7 @@ def build_coeff_dict(lambda_max: int, sparse: bool, use_mops: bool): } # Store - coeff_dict[(l1, l2, lam)] = cg_l1l2lam + coeff_dict[(l1, l2, lambda_)] = cg_l1l2lam return coeff_dict @@ -242,66 +258,68 @@ def build_coeff_dict(lambda_max: int, sparse: bool, use_mops: bool): # ============================ -def _real2complex(lam: int) -> np.ndarray: +def _real2complex(lambda_: int) -> np.ndarray: """ Computes a matrix that can be used to convert from real to complex-valued - spherical harmonics(coefficients) of order ``lam``. + spherical harmonics(coefficients) of order ``lambda_``. - This is meant to be applied to the left: ``real2complex @ [-lam, ..., - +lam]``. + This is meant to be applied to the left: ``real2complex @ [-lambda_, ..., + +lambda_]``. See https://en.wikipedia.org/wiki/Spherical_harmonics#Real_form for details on the convention for how these tranformations are defined. """ - result = np.zeros((2 * lam + 1, 2 * lam + 1), dtype=np.complex128) + result = np.zeros((2 * lambda_ + 1, 2 * lambda_ + 1), dtype=np.complex128) inv_sqrt_2 = 1.0 / np.sqrt(2) i_sqrt_2 = 1j / np.sqrt(2) - for m in range(-lam, lam + 1): + for m in range(-lambda_, lambda_ + 1): if m < 0: # Positve part - result[lam + m, lam + m] = +i_sqrt_2 + result[lambda_ + m, lambda_ + m] = +i_sqrt_2 # Negative part - result[lam - m, lam + m] = -i_sqrt_2 * ((-1) ** m) + result[lambda_ - m, lambda_ + m] = -i_sqrt_2 * ((-1) ** m) if m == 0: - result[lam, lam] = +1.0 + result[lambda_, lambda_] = +1.0 if m > 0: # Negative part - result[lam - m, lam + m] = +inv_sqrt_2 + result[lambda_ - m, lambda_ + m] = +inv_sqrt_2 # Positive part - result[lam + m, lam + m] = +inv_sqrt_2 * ((-1) ** m) + result[lambda_ + m, lambda_ + m] = +inv_sqrt_2 * ((-1) ** m) return result -def _complex2real(lam: int) -> np.ndarray: +def _complex2real(lambda_: int) -> np.ndarray: """ Converts from complex to real spherical harmonics. This is just given by the conjugate tranpose of the real->complex transformation matrices. """ - return np.conjugate(_real2complex(lam)).T + return np.conjugate(_real2complex(lambda_)).T -def _complex_clebsch_gordan_matrix(l1, l2, lam): +def _complex_clebsch_gordan_matrix(l1, l2, lambda_): r"""clebsch-gordan matrix Computes the Clebsch-Gordan (CG) matrix for transforming complex-valued spherical harmonics. The CG matrix is computed as a 3D array of elements - < l1 m1 l2 m2 | lam mu > + < l1 m1 l2 m2 | lambda_ mu > where the first axis loops over m1, the second loops over m2, and the third one loops over mu. The matrix is real. For example, using the relation: - | l1 l2 lam mu > = \sum_{m1, m2} | l1 m1 > | l2 m2 > + | l1 l2 lambda_ mu > = + \sum_{m1, m2} + | l1 m1 > | l2 m2 > (https://en.wikipedia.org/wiki/Clebsch–Gordan_coefficients, section "Formal definition of Clebsch-Gordan coefficients", eq 2) - one can obtain the spherical harmonics lam from two sets of + one can obtain the spherical harmonics lambda_ from two sets of spherical harmonics with l1 and l2 (up to a normalization factor). E.g.: Args: l1: l number for the first set of spherical harmonics l2: l number for the second set of spherical harmonics - lam: l number For the third set of spherical harmonics + lambda_: l number For the third set of spherical harmonics Returns: cg: CG matrix for transforming complex-valued spherical harmonics >>> from scipy.special import sph_harm @@ -320,7 +338,214 @@ def _complex_clebsch_gordan_matrix(l1, l2, lam): >>> np.allclose(ratio[0], ratio) True """ - if np.abs(l1 - l2) > lam or np.abs(l1 + l2) < lam: - return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * lam + 1), dtype=np.double) + if np.abs(l1 - l2) > lambda_ or np.abs(l1 + l2) < lambda_: + return np.zeros((2 * l1 + 1, 2 * l2 + 1, 2 * lambda_ + 1), dtype=np.double) + else: + return wigners.clebsch_gordan_array(l1, l2, lambda_) + + +# ================================================= +# ===== Functions for performing CG combinations +# ================================================= + + +def combine_arrays( + arr_1: Union[np.ndarray, TorchTensor], + arr_2: Union[np.ndarray, TorchTensor], + lambda_: int, + cg_cache, + return_empty_array: bool = False, +) -> Union[np.ndarray, TorchTensor]: + """ + Couples arrays `arr_1` and `arr_2` corresponding to the irreducible + spherical components of 2 angular channels l1 and l2 using the appropriate + Clebsch-Gordan coefficients. As l1 and l2 can be combined to form multiple + lambda channels, this function returns the coupling to a single specified + channel `lambda`. The angular channels l1 and l2 are inferred from the size + of the components axis (axis 1) of the input arrays. + + `arr_1` has shape (n_i, 2 * l1 + 1, n_p) and `arr_2` has shape (n_i, 2 * l2 + + 1, n_q). n_i is the number of samples, n_p and n_q are the number of + properties in each array. The number of samples in each array must be the + same. + + The ouput array has shape (n_i, 2 * lambda + 1, n_p * n_q), where lambda is + the input parameter `lambda_`. + + The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these + must be produced by the ClebschGordanReal class in this module. These + coefficients can be stored in either sparse dictionaries or dense arrays. + + The combination operation is dispatched such that numpy arrays or torch + tensors are automatically handled. + + `return_empty_array` can be used to return an empty array of the correct + shape, without performing the CG combination step. This can be useful for + probing the outputs of CG iterations in terms of metadata without the + computational cost of performing the CG combinations - i.e. using the + function :py:func:`combine_single_center_to_body_order_metadata_only`. + + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + + 1, n_p_properties] + :param lambda_: int value of the resulting coupled channel + :param cg_cache: either a sparse dictionary with keys (m1, m2, mu) and array + values being sparse blocks of shape , or a dense array + of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lambda_ + 1)]. + + :returns: array of shape [n_samples, (2*lambda_+1), q_properties * p_properties] + """ + # If just precomputing metadata, return an empty array + if return_empty_array: + return sparse_combine(arr_1, arr_2, lambda_, cg_cache, return_empty_array=True) + + # Otherwise, perform the CG combination + # Spare CG cache + if cg_cache.sparse: + return sparse_combine(arr_1, arr_2, lambda_, cg_cache, return_empty_array=False) + + # Dense CG cache + return dense_combine(arr_1, arr_2, lambda_, cg_cache) + + +def sparse_combine( + arr_1: Union[np.ndarray, TorchTensor], + arr_2: Union[np.ndarray, TorchTensor], + lambda_: int, + cg_cache, + return_empty_array: bool = False, +) -> Union[np.ndarray, TorchTensor]: + """ + Performs a Clebsch-Gordan combination step on 2 arrays using sparse + operations. The angular channel of each block is inferred from the size of + its component axis, and the blocks are combined to the desired output + angular channel `lambda_` using the appropriate Clebsch-Gordan coefficients. + + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + + 1, n_p_properties] + :param lambda_: int value of the resulting coupled channel + :param cg_cache: sparse dictionary with keys (m1, m2, mu) and array values + being sparse blocks of shape + + :returns: array of shape [n_samples, (2*lambda_+1), q_properties * p_properties] + """ + # Samples dimensions must be the same + assert arr_1.shape[0] == arr_2.shape[0] + + # Infer l1 and l2 from the len of the length of axis 1 of each tensor + l1 = (arr_1.shape[1] - 1) // 2 + l2 = (arr_2.shape[1] - 1) // 2 + + # Define other useful dimensions + n_i = arr_1.shape[0] # number of samples + n_p = arr_1.shape[2] # number of properties in arr_1 + n_q = arr_2.shape[2] # number of properties in arr_2 + + if return_empty_array: # used when only computing metadata + return _dispatch.zeros_like((n_i, 2 * lambda_ + 1, n_p * n_q), like=arr_1) + + if isinstance(arr_1, np.ndarray) and HAS_MOPS: + # Reshape + arr_1 = np.repeat(arr_1[:, :, :, None], n_q, axis=3).reshape( + n_i, 2 * l1 + 1, n_p * n_q + ) + arr_2 = np.repeat(arr_2[:, :, None, :], n_p, axis=2).reshape( + n_i, 2 * l2 + 1, n_p * n_q + ) + + arr_1 = _dispatch.swapaxes(arr_1, 1, 2).reshape(n_i * n_p * n_q, 2 * l1 + 1) + arr_2 = _dispatch.swapaxes(arr_2, 1, 2).reshape(n_i * n_p * n_q, 2 * l2 + 1) + + # Do SAP + arr_out = sap( + arr_1, + arr_2, + *cg_cache._coeffs[(l1, l2, lambda_)], + output_size=2 * lambda_ + 1, + ) + assert arr_out.shape == (n_i * n_p * n_q, 2 * lambda_ + 1) + + # Reshape back + arr_out = arr_out.reshape(n_i, n_p * n_q, 2 * lambda_ + 1) + arr_out = _dispatch.swapaxes(arr_out, 1, 2) + + return arr_out + + if isinstance(arr_1, np.ndarray) or isinstance(arr_1, TorchTensor): + # Initialise output array + arr_out = _dispatch.zeros_like((n_i, 2 * lambda_ + 1, n_p * n_q), like=arr_1) + + # Get the corresponding Clebsch-Gordan coefficients + cg_coeffs = cg_cache.coeffs[(l1, l2, lambda_)] + + # Fill in each mu component of the output array in turn + for m1, m2, mu in cg_coeffs.keys(): + # Broadcast arrays, multiply together and with CG coeff + arr_out[:, mu, :] += ( + arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeffs[(m1, m2, mu)] + ).reshape(n_i, n_p * n_q) + + return arr_out + + else: + raise TypeError(UNKNOWN_ARRAY_TYPE) + + +def dense_combine( + arr_1: Union[np.ndarray, TorchTensor], + arr_2: Union[np.ndarray, TorchTensor], + lambda_: int, + cg_cache, +) -> Union[np.ndarray, TorchTensor]: + """ + Performs a Clebsch-Gordan combination step on 2 arrays using a dense + operation. The angular channel of each block is inferred from the size of + its component axis, and the blocks are combined to the desired output + angular channel `lambda_` using the appropriate Clebsch-Gordan coefficients. + + :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + + 1, n_q_properties] + :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + + 1, n_p_properties] + :param lambda_: int value of the resulting coupled channel + :param cg_cache: dense array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lambda_ + + 1)] + + :returns: array of shape [n_samples, (2*lambda_+1), q_properties * p_properties] + """ + if isinstance(arr_1, np.ndarray) or isinstance(arr_1, TorchTensor): + # Infer l1 and l2 from the len of the length of axis 1 of each tensor + l1 = (arr_1.shape[1] - 1) // 2 + l2 = (arr_2.shape[1] - 1) // 2 + cg_coeffs = cg_cache.coeffs[(l1, l2, lambda_)] + + # (samples None None l1_mu q) * (samples l2_mu p None None) + # -> (samples l2_mu p l1_mu q) we broadcast it in this way + # so we only need to do one swapaxes in the next step + arr_out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] + + # (samples l2_mu p l1_mu q) -> (samples q p l1_mu l2_mu) + arr_out = _dispatch.swapaxes(arr_out, 1, 4) + + # samples (q p l1_mu l2_mu) -> (samples (q p) (l1_mu l2_mu)) + arr_out = arr_out.reshape( + -1, + arr_1.shape[2] * arr_2.shape[2], + arr_1.shape[1] * arr_2.shape[1], + ) + + # (l1_mu l2_mu lam_mu) -> ((l1_mu l2_mu) lam_mu) + cg_coeffs = cg_coeffs.reshape(-1, 2 * lambda_ + 1) + + # (samples (q p) (l1_mu l2_mu)) @ ((l1_mu l2_mu) lam_mu) + # -> samples (q p) lam_mu + arr_out = arr_out @ cg_coeffs + + # (samples (q p) lam_mu) -> (samples lam_mu (q p)) + return _dispatch.swapaxes(arr_out, 1, 2) + else: - return wigners.clebsch_gordan_array(l1, l2, lam) + raise TypeError(UNKNOWN_ARRAY_TYPE) diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py index 0af5d99e1..f44aa3713 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/_dispatch.py @@ -1,16 +1,10 @@ """ Module containing dispatch functions for numpy/torch CG combination operations. """ -from typing import List, Optional, Union +from typing import List, Optional import numpy as np -from ._cg_cache import HAS_MOPS - - -if HAS_MOPS: - from mops import sparse_accumulation_of_products as sap - try: import torch @@ -26,211 +20,6 @@ class TorchTensor: ) -# ============ CG combinations ============ - - -def combine_arrays( - arr_1: Union[np.ndarray, TorchTensor], - arr_2: Union[np.ndarray, TorchTensor], - lam: int, - cg_cache, - return_empty_array: bool = False, -) -> Union[np.ndarray, TorchTensor]: - """ - Couples arrays `arr_1` and `arr_2` corresponding to the irreducible - spherical components of 2 angular channels l1 and l2 using the appropriate - Clebsch-Gordan coefficients. As l1 and l2 can be combined to form multiple - lambda channels, this function returns the coupling to a single specified - channel `lambda`. The angular channels l1 and l2 are inferred from the size - of the components axis (axis 1) of the input arrays. - - `arr_1` has shape (n_i, 2 * l1 + 1, n_p) and `arr_2` has shape (n_i, 2 * l2 - + 1, n_q). n_i is the number of samples, n_p and n_q are the number of - properties in each array. The number of samples in each array must be the - same. - - The ouput array has shape (n_i, 2 * lambda + 1, n_p * n_q), where lambda is - the input parameter `lam`. - - The Clebsch-Gordan coefficients are cached in `cg_cache`. Currently, these - must be produced by the ClebschGordanReal class in this module. These - coefficients can be stored in either sparse dictionaries or dense arrays. - - The combination operation is dispatched such that numpy arrays or torch - tensors are automatically handled. - - `return_empty_array` can be used to return an empty array of the correct - shape, without performing the CG combination step. This can be useful for - probing the outputs of CG iterations in terms of metadata without the - computational cost of performing the CG combinations - i.e. using the - function :py:func:`combine_single_center_to_body_order_metadata_only`. - - :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + - 1, n_q_properties] - :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + - 1, n_p_properties] - :param lam: int value of the resulting coupled channel - :param cg_cache: either a sparse dictionary with keys (m1, m2, mu) and array - values being sparse blocks of shape , or a dense array - of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + 1)]. - - :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] - """ - # If just precomputing metadata, return an empty array - if return_empty_array: - return sparse_combine(arr_1, arr_2, lam, cg_cache, return_empty_array=True) - - # Otherwise, perform the CG combination - # Spare CG cache - if cg_cache.sparse: - return sparse_combine(arr_1, arr_2, lam, cg_cache, return_empty_array=False) - - # Dense CG cache - return dense_combine(arr_1, arr_2, lam, cg_cache) - - -def sparse_combine( - arr_1: Union[np.ndarray, TorchTensor], - arr_2: Union[np.ndarray, TorchTensor], - lam: int, - cg_cache, - return_empty_array: bool = False, -) -> Union[np.ndarray, TorchTensor]: - """ - Performs a Clebsch-Gordan combination step on 2 arrays using sparse - operations. The angular channel of each block is inferred from the size of - its component axis, and the blocks are combined to the desired output - angular channel `lam` using the appropriate Clebsch-Gordan coefficients. - - :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + - 1, n_q_properties] - :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + - 1, n_p_properties] - :param lam: int value of the resulting coupled channel - :param cg_cache: sparse dictionary with keys (m1, m2, mu) and array values - being sparse blocks of shape - - :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] - """ - # Samples dimensions must be the same - assert arr_1.shape[0] == arr_2.shape[0] - - # Infer l1 and l2 from the len of the length of axis 1 of each tensor - l1 = (arr_1.shape[1] - 1) // 2 - l2 = (arr_2.shape[1] - 1) // 2 - - # Define other useful dimensions - n_i = arr_1.shape[0] # number of samples - n_p = arr_1.shape[2] # number of properties in arr_1 - n_q = arr_2.shape[2] # number of properties in arr_2 - - if return_empty_array: # used when only computing metadata - return zeros_like((n_i, 2 * lam + 1, n_p * n_q), like=arr_1) - - if isinstance(arr_1, np.ndarray) and HAS_MOPS: - # Reshape - arr_1 = np.repeat(arr_1[:, :, :, None], n_q, axis=3).reshape( - n_i, 2 * l1 + 1, n_p * n_q - ) - arr_2 = np.repeat(arr_2[:, :, None, :], n_p, axis=2).reshape( - n_i, 2 * l2 + 1, n_p * n_q - ) - - arr_1 = swapaxes(arr_1, 1, 2).reshape(n_i * n_p * n_q, 2 * l1 + 1) - arr_2 = swapaxes(arr_2, 1, 2).reshape(n_i * n_p * n_q, 2 * l2 + 1) - - # Do SAP - arr_out = sap( - arr_1, arr_2, *cg_cache._coeffs[(l1, l2, lam)], output_size=2 * lam + 1 - ) - assert arr_out.shape == (n_i * n_p * n_q, 2 * lam + 1) - - # Reshape back - arr_out = arr_out.reshape(n_i, n_p * n_q, 2 * lam + 1) - arr_out = swapaxes(arr_out, 1, 2) - - return arr_out - - if isinstance(arr_1, np.ndarray) or isinstance(arr_1, TorchTensor): - # Initialise output array - arr_out = zeros_like((n_i, 2 * lam + 1, n_p * n_q), like=arr_1) - - # Get the corresponding Clebsch-Gordan coefficients - cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] - - # Fill in each mu component of the output array in turn - for m1, m2, mu in cg_coeffs.keys(): - # Broadcast arrays, multiply together and with CG coeff - arr_out[:, mu, :] += ( - arr_1[:, m1, :, None] * arr_2[:, m2, None, :] * cg_coeffs[(m1, m2, mu)] - ).reshape(n_i, n_p * n_q) - - return arr_out - - else: - raise TypeError(UNKNOWN_ARRAY_TYPE) - - -def dense_combine( - arr_1: Union[np.ndarray, TorchTensor], - arr_2: Union[np.ndarray, TorchTensor], - lam: int, - cg_cache, -) -> Union[np.ndarray, TorchTensor]: - """ - Performs a Clebsch-Gordan combination step on 2 arrays using a dense - operation. The angular channel of each block is inferred from the size of - its component axis, and the blocks are combined to the desired output - angular channel `lam` using the appropriate Clebsch-Gordan coefficients. - - :param arr_1: array with the m values for l1 with shape [n_samples, 2 * l1 + - 1, n_q_properties] - :param arr_2: array with the m values for l2 with shape [n_samples, 2 * l2 + - 1, n_p_properties] - :param lam: int value of the resulting coupled channel - :param cg_cache: dense array of shape [(2 * l1 +1) * (2 * l2 +1), (2 * lam + - 1)] - - :returns: array of shape [n_samples, (2*lam+1), q_properties * p_properties] - """ - if isinstance(arr_1, np.ndarray) or isinstance(arr_1, TorchTensor): - # Infer l1 and l2 from the len of the length of axis 1 of each tensor - l1 = (arr_1.shape[1] - 1) // 2 - l2 = (arr_2.shape[1] - 1) // 2 - cg_coeffs = cg_cache.coeffs[(l1, l2, lam)] - - # (samples None None l1_mu q) * (samples l2_mu p None None) - # -> (samples l2_mu p l1_mu q) we broadcast it in this way - # so we only need to do one swapaxes in the next step - arr_out = arr_1[:, None, None, :, :] * arr_2[:, :, :, None, None] - - # (samples l2_mu p l1_mu q) -> (samples q p l1_mu l2_mu) - arr_out = swapaxes(arr_out, 1, 4) - - # samples (q p l1_mu l2_mu) -> (samples (q p) (l1_mu l2_mu)) - arr_out = arr_out.reshape( - -1, - arr_1.shape[2] * arr_2.shape[2], - arr_1.shape[1] * arr_2.shape[1], - ) - - # (l1_mu l2_mu lam_mu) -> ((l1_mu l2_mu) lam_mu) - cg_coeffs = cg_coeffs.reshape(-1, 2 * lam + 1) - - # (samples (q p) (l1_mu l2_mu)) @ ((l1_mu l2_mu) lam_mu) - # -> samples (q p) lam_mu - arr_out = arr_out @ cg_coeffs - - # (samples (q p) lam_mu) -> (samples lam_mu (q p)) - return swapaxes(arr_out, 1, 2) - - else: - raise TypeError(UNKNOWN_ARRAY_TYPE) - - -# ============ Other functions ============ - - def unique(array, axis: Optional[int] = None): """Find the unique elements of an array.""" if isinstance(array, TorchTensor): diff --git a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py index 96d265f80..5572c7598 100644 --- a/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py +++ b/python/rascaline/rascaline/utils/clebsch_gordan/clebsch_gordan.py @@ -6,8 +6,7 @@ from metatensor import Labels, TensorBlock, TensorMap -from . import _dispatch -from ._cg_cache import ClebschGordanReal +from . import _cg_cache, _dispatch # ====================================================================== @@ -22,17 +21,19 @@ def correlate_density( selected_keys: Optional[Union[Labels, List[Labels]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, -) -> List[TensorMap]: +) -> Union[TensorMap, List[TensorMap]]: """ Takes iterative Clebsch-Gordan (CG) tensor products of a density descriptor - with itself to the desired correlation order. Returns a list of TensorMaps - corresponding to the density correlations output from the specified - iterations. + with itself up to the desired correlation order. Returns + :py:class:`TensorMap`(s) corresponding to the density correlations output + from the specified iteration(s). A density descriptor necessarily is body order 2 (i.e. correlation order 1), - but can be single- or multi-center. The output is a list of density - correlations for each iteration specified in `output_selection`, up to the - target order passed in `correlation_order`. + but can be single- or multi-center. The output is a :py:class:`list` of + density correlations for each iteration specified in `output_selection`, up + to the target order passed in `correlation_order`. By default only the last + correlation (i.e. the correlation of order ``correlation_order``) is + returned. This function is an iterative special case of the more general :py:func:`correlate_tensors`. As a density is being correlated with itself, @@ -44,7 +45,7 @@ def correlate_density( `parity_selection`. :param density: A density descriptor of body order 2 (correlation order 1), - in metatensor.TensorMap format. This may be, for example, a rascaline + in :py:class:`TensorMap` format. This may be, for example, a rascaline :py:class:`SphericalExpansion` or :py:class:`LodeSphericalExpansion`. Alternatively, this could be multi-center descriptor, such as a pair density. @@ -53,26 +54,31 @@ def correlate_density( :param angular_cutoff: The maximum angular channel to compute at any given CG iteration, applied globally to all iterations until the target correlation order is reached. - :param selected_keys: Labels or List[Labels] specifying the angular and/or - parity channels to output at each iteration. All Labels objects passed - here must only contain key names "spherical_harmonics_l" and - "inversion_sigma". If a single Labels object is passed, this is applied - to the final iteration only. If a list of Labels objects is passed, - each is applied to its corresponding iteration. If None is passed, all - angular and parity channels are output at each iteration, with the - global `angular_cutoff` applied if specified. + :param selected_keys: :py:class:`Labels` or `List[:py:class:`Labels`]` + specifying the angular and/or parity channels to output at each + iteration. All :py:class:`Labels` objects passed here must only contain + key names "spherical_harmonics_l" and "inversion_sigma". If a single + :py:class:`Labels` object is passed, this is applied to the final + iteration only. If a :py:class:`list` of :py:class:`Labels` objects is + passed, each is applied to its corresponding iteration. If None is + passed, all angular and parity channels are output at each iteration, + with the global `angular_cutoff` applied if specified. :param skip_redundant: Whether to skip redundant CG combinations. Defaults - to False, which means all combinations are performed. If a list of bool - is passed, this is applied to each iteration. If a single bool is - passed, this is applied to all iterations. - :param output_selection: A list of bools specifying whether to output a - TensorMap for each iteration. If a single bool is passed as True, - outputs from all iterations will be returned. If a list of bools is - passed, this controls the output at each corresponding iteration. If - None is passed, only the final iteration is output. - - :return List[TensorMap]: A list of TensorMaps corresponding to the density - correlations output from the specified iterations. + to False, which means all combinations are performed. If a + :py:class:`list` of :py:class:`bool` is passed, this is applied to each + iteration. If a single :py:class:`bool` is passed, this is applied to + all iterations. + :param output_selection: A :py:class:`list` of :py:class:`bool` specifying + whether to output a :py:class:`TensorMap` for each iteration. If a + single :py:class:`bool` is passed as True, outputs from all iterations + will be returned. If a :py:class:`list` of :py:class:`bool` is passed, + this controls the output at each corresponding iteration. If None is + passed, only the final iteration is output. + + :return: A :py:class:`list` of :py:class:`TensorMap` corresponding to the + density correlations output from the specified iterations. If the output + from a single iteration is requested, a :py:class:`TensorMap` is + returned instead. """ return _correlate_density( density, @@ -93,44 +99,12 @@ def correlate_density_metadata( selected_keys: Optional[Union[Labels, List[Labels]]] = None, skip_redundant: Optional[Union[bool, List[bool]]] = False, output_selection: Optional[Union[bool, List[bool]]] = None, -) -> List[TensorMap]: +) -> Union[TensorMap, List[TensorMap]]: """ - Returns the metadata-only TensorMaps that would be output by the function - :py:func:`correlate_density` under the same settings, without perfoming the - actual Clebsch-Gordan tensor products. See this function for full - documentation. - - :param density: A density descriptor of body order 2 (correlation order 1), - in metatensor.TensorMap format. This may be, for example, a rascaline - :py:class:`SphericalExpansion` or :py:class:`LodeSphericalExpansion`. - Alternatively, this could be multi-center descriptor, such as a pair - density. - :param correlation_order: The desired correlation order of the output - descriptor. Must be >= 1. - :param angular_cutoff: The maximum angular channel to compute at any given - CG iteration, applied globally to all iterations until the target - correlation order is reached. - :param selected_keys: Labels or List[Labels] specifying the angular and/or - parity channels to output at each iteration. All Labels objects passed - here must only contain key names "spherical_harmonics_l" and - "inversion_sigma". If a single Labels object is passed, this is applied - to the final iteration only. If a list of Labels objects is passed, - each is applied to its corresponding iteration. If None is passed, all - angular and parity channels are output at each iteration, with the - global `angular_cutoff` applied if specified. - :param skip_redundant: Whether to skip redundant CG combinations. Defaults - to False, which means all combinations are performed. If a list of bool - is passed, this is applied to each iteration. If a single bool is - passed, this is applied to all iterations. - :param output_selection: A list of bools specifying whether to output a - TensorMap for each iteration. If a single bool is passed as True, - outputs from all iterations will be returned. If a list of bools is - passed, this controls the output at each corresponding iteration. If - None is passed, only the final iteration is output. - - :return List[TensorMap]: A list of TensorMaps corresponding to the metadata - that would be output by :py:func:`correlate_density` under the same - settings. + Returns the metadata-only :py:class:`TensorMap`(s) that would be output by + the function :py:func:`correlate_density` under the same settings, without + perfoming the actual Clebsch-Gordan tensor products. See this function for + full documentation. """ return _correlate_density( @@ -158,18 +132,38 @@ def _correlate_density( output_selection: Optional[Union[bool, List[bool]]] = None, compute_metadata_only: bool = False, sparse: bool = True, -) -> List[TensorMap]: +) -> Union[TensorMap, List[TensorMap]]: """ Performs the density correlations for public functions :py:func:`correlate_density` and :py:func:`correlate_density_metadata`. """ + # Check inputs if correlation_order <= 1: raise ValueError("`correlation_order` must be > 1") + # TODO: implement combinations of gradients too if _dispatch.any([len(list(block.gradients())) > 0 for block in density]): raise NotImplementedError( "Clebsch Gordan combinations with gradients not yet implemented." " Use metatensor.remove_gradients to remove gradients from the input." ) + # Check metadata + if not ( + _dispatch.all(density.keys.names == ["spherical_harmonics_l", "species_center"]) + or _dispatch.all( + density.keys.names + == ["spherical_harmonics_l", "species_center", "species_neighbor"] + ) + ): + raise ValueError( + "input `density` must have key names" + ' ["spherical_harmonics_l", "species_center"] or' + ' ["spherical_harmonics_l", "species_center", "species_neighbor"]' + ) + if not _dispatch.all(density.component_names == ["spherical_harmonics_m"]): + raise ValueError( + "input `density` must have a single component" + " axis with name `spherical_harmonics_m`" + ) n_iterations = correlation_order - 1 # num iterations density = _standardize_keys(density) # standardize metadata density_correlation = density # create a copy to combine with itself @@ -209,9 +203,9 @@ def _correlate_density( ) # TODO: keys have been precomputed, so perhaps we don't need to # compute all CG coefficients up to angular_max here. - # TODO: use sparse cache by default until we understamd under which + # TODO: use sparse cache by default until we understand under which # circumstances (and if) dense is faster. - cg_cache = ClebschGordanReal(angular_max, sparse=sparse) + cg_cache = _cg_cache.ClebschGordanReal(angular_max, sparse=sparse) # Perform iterative CG tensor products density_correlations = [] @@ -219,8 +213,8 @@ def _correlate_density( # Define the correlation order of the current iteration correlation_order_it = iteration + 2 + # Combine block pairs blocks_out = [] - # TODO: is there a faster way of iterating over keys/blocks here? for key_1, key_2, key_out in zip(*key_metadata[iteration]): block_out = _combine_blocks_same_samples( density_correlation[key_1], @@ -256,6 +250,11 @@ def _correlate_density( keys=keys, blocks=[b.copy() for b in tensor.blocks()] ) + # Return a single TensorMap in the simple case + if len(density_correlations) == 1: + return density_correlations[0] + + # Otherwise return a list of TensorMaps return density_correlations @@ -301,10 +300,10 @@ def _parse_selected_keys( ) -> List[Union[None, Labels]]: """ Parses the `selected_keys` argument passed to public functions. Checks the - values and returns a list of Labels objects, one for each iteration of CG - combination. + values and returns a :py:class:`list` of :py:class:`Labels` objects, one for + each iteration of CG combination. - `like` is required if a new Labels object is to be created by + `like` is required if a new :py:class:`Labels` object is to be created by :py:mod:`_dispatch`. """ # Check angular_cutoff arg @@ -335,7 +334,9 @@ def _parse_selected_keys( # Check the selected_keys if not isinstance(selected_keys, List): - raise TypeError("`selected_keys` must be a Labels or List[Union[None, Labels]]") + raise TypeError( + "`selected_keys` must be a `Labels` or List[Union[None, `Labels`]]" + ) if not len(selected_keys) == n_iterations: raise ValueError( "`selected_keys` must be a List[Union[None, Labels]] of length" @@ -399,10 +400,10 @@ def _parse_bool_iteration_filters( if isinstance(skip_redundant, bool): skip_redundant = [skip_redundant] * n_iterations if not _dispatch.all([isinstance(val, bool) for val in skip_redundant]): - raise TypeError("`skip_redundant` must be a bool or list of bools") + raise TypeError("`skip_redundant` must be a `bool` or `list` of `bool`") if not len(skip_redundant) == n_iterations: raise ValueError( - "`skip_redundant` must be a bool or list of bools of length" + "`skip_redundant` must be a bool or `list` of `bool` of length" " `correlation_order` - 1" ) if output_selection is None: @@ -411,17 +412,17 @@ def _parse_bool_iteration_filters( if isinstance(output_selection, bool): output_selection = [output_selection] * n_iterations if not isinstance(output_selection, List): - raise TypeError("`output_selection` must be passed as a list of bools") + raise TypeError("`output_selection` must be passed as `list` of `bool`") if not len(output_selection) == n_iterations: raise ValueError( - "`output_selection` must be a list of bools of length" + "`output_selection` must be a ``list`` of ``bool`` of length" " corresponding to the number of CG iterations" ) if not _dispatch.all([isinstance(v, bool) for v in output_selection]): - raise TypeError("`output_selection` must be passed as a list of bools") + raise TypeError("`output_selection` must be passed as a `list` of `bool`") if not _dispatch.all([isinstance(v, bool) for v in output_selection]): - raise TypeError("`output_selection` must be passed as a list of bools") + raise TypeError("`output_selection` must be passed as a `list` of `bool`") return skip_redundant, output_selection @@ -435,13 +436,16 @@ def _precompute_keys( ) -> List[Tuple[Labels, List[List[int]]]]: """ Computes all the keys metadata needed to perform `n_iterations` of CG - combination steps, based on the keys of the 2 tensors being combined - (`keys_1` and `keys_2`), the maximum angular channel cutoff - (`angular_cutoff`), and the angular (`angular_selection`) and parity - (`parity_selection`) selections to be applied at each iteration. + combination steps. + + At each iteration, a full product of the keys of two tensors, i.e. `keys_1` + and `keys_2` is computed. Then, key selections are applied according to the + user-defined settings: the maximum angular channel cutoff + (`angular_cutoff`), and angular and/or parity selections specified in + `selected_keys`. If `skip_redundant` is True, then keys that represent redundant CG - operations are not included in the output metadata. + operations are not included in the output keys at each step. """ keys_metadata = [] keys_out = keys_1 @@ -584,9 +588,9 @@ def _precompute_keys_full_product( # Now iterate over the non-zero angular channels and apply the custom # selections - for lam in nonzero_lams: + for lambda_ in nonzero_lams: # Calculate new sigma - sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lam) + sig = sig1 * sig2 * (-1) ** (lam1 + lam2 + lambda_) # Extract the l and k lists from keys_1 l_list = key_1.values[4 : 4 + nu1].tolist() @@ -595,7 +599,7 @@ def _precompute_keys_full_product( # Build the new keys values. l{nu} is `lam2`` (i.e. # "spherical_harmonics_l" of the key from `keys_2`. k{nu-1} is # `lam1` (i.e. "spherical_harmonics_l" of the key from `keys_1`). - new_vals = [nu, sig, lam, a] + l_list + [lam2] + k_list + [lam1] + new_vals = [nu, sig, lambda_, a] + l_list + [lam2] + k_list + [lam1] new_key_values.append(new_vals) keys_1_entries.append(key_1) keys_2_entries.append(key_2) @@ -712,7 +716,7 @@ def _remove_redundant_keys( def _combine_blocks_same_samples( block_1: TensorBlock, block_2: TensorBlock, - lam: int, + lambda_: int, cg_cache, compute_metadata_only: bool = False, ) -> TensorBlock: @@ -723,12 +727,12 @@ def _combine_blocks_same_samples( # Do the CG combination - single center so no shape pre-processing required if compute_metadata_only: - combined_values = _dispatch.combine_arrays( - block_1.values, block_2.values, lam, cg_cache, return_empty_array=True + combined_values = _cg_cache.combine_arrays( + block_1.values, block_2.values, lambda_, cg_cache, return_empty_array=True ) else: - combined_values = _dispatch.combine_arrays( - block_1.values, block_2.values, lam, cg_cache, return_empty_array=False + combined_values = _cg_cache.combine_arrays( + block_1.values, block_2.values, lambda_, cg_cache, return_empty_array=False ) # Infer the new nu value: block 1's properties are nu pairs of @@ -748,7 +752,7 @@ def _combine_blocks_same_samples( Labels( names=["spherical_harmonics_m"], values=_dispatch.int_range_like( - min_val=-lam, max_val=lam + 1, like=block_1.values + min_val=-lambda_, max_val=lambda_ + 1, like=block_1.values ).reshape(-1, 1), ), ], diff --git a/python/rascaline/tests/utils/clebsch_gordan.py b/python/rascaline/tests/utils/clebsch_gordan.py index 61809f115..38b7fab32 100644 --- a/python/rascaline/tests/utils/clebsch_gordan.py +++ b/python/rascaline/tests/utils/clebsch_gordan.py @@ -2,8 +2,20 @@ import os from typing import List +import metatensor import numpy as np import pytest +from metatensor import Labels, TensorBlock, TensorMap + +import rascaline +from rascaline.utils import PowerSpectrum +from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal +from rascaline.utils.clebsch_gordan.clebsch_gordan import ( + _correlate_density, + _standardize_keys, + correlate_density, + correlate_density_metadata, +) # Try to import some modules @@ -18,25 +30,12 @@ except ImportError: HAS_METATENSOR_OPERATIONS = False try: - import sympy # noqa F401 + import sympy # noqa: F401 HAS_SYMPY = True except ImportError: HAS_SYMPY = False - -import metatensor # noqa: E402 -from metatensor import Labels, TensorBlock, TensorMap # noqa: E402 - -import rascaline # noqa: E402 -from rascaline.utils import PowerSpectrum # noqa: E402 -from rascaline.utils.clebsch_gordan._cg_cache import ClebschGordanReal # noqa: E402 -from rascaline.utils.clebsch_gordan.clebsch_gordan import ( # noqa: E402 - _correlate_density, - _standardize_keys, -) - - if HAS_SYMPY: from .rotations import WignerDReal, transform_frame_o3, transform_frame_so3 @@ -63,19 +62,16 @@ "center_atom_weight": 1.0, } -# TODO: test a CG combination with LODE -LODE_HYPERS_SMALL = {} - # ============ Pytest fixtures ============ -@pytest.fixture(scope="module") +@pytest.fixture() def cg_cache_sparse(): return ClebschGordanReal(lambda_max=5, sparse=True) -@pytest.fixture(scope="module") +@pytest.fixture() def cg_cache_dense(): return ClebschGordanReal(lambda_max=5, sparse=False) @@ -95,29 +91,29 @@ def h2o_periodic(): return ase.io.read(os.path.join(DATA_ROOT, "h2o_periodic.xyz"), ":") -def wigners(lmax: int): +def wigner_d_matrices(lmax: int): return WignerDReal(lmax=lmax) -def sphex(frames: List[ase.Atoms]): +def spherical_expansion(frames: List[ase.Atoms]): """Returns a rascaline SphericalExpansion""" calculator = rascaline.SphericalExpansion(**SPHEX_HYPERS) return calculator.compute(frames) -def sphex_small_features(frames: List[ase.Atoms]): +def spherical_expansion_small(frames: List[ase.Atoms]): """Returns a rascaline SphericalExpansion""" calculator = rascaline.SphericalExpansion(**SPHEX_HYPERS_SMALL) return calculator.compute(frames) -def powspec(frames: List[ase.Atoms]): +def power_spectrum(frames: List[ase.Atoms]): """Returns a rascaline PowerSpectrum constructed from a SphericalExpansion""" return PowerSpectrum(rascaline.SphericalExpansion(**SPHEX_HYPERS)).compute(frames) -def powspec_small_features(frames: List[ase.Atoms]): +def power_spectrum_small(frames: List[ase.Atoms]): """Returns a rascaline PowerSpectrum constructed from a SphericalExpansion""" return PowerSpectrum(rascaline.SphericalExpansion(**SPHEX_HYPERS_SMALL)).compute( @@ -125,19 +121,23 @@ def powspec_small_features(frames: List[ase.Atoms]): ) -def lode_small_features(frames: List[ase.Atoms]): - """Returns a rascaline LODE SphericalExpansion""" - return rascaline.LodeSphericalExpansion(**LODE_HYPERS_SMALL).compute(frames) - - def get_norm(tensor: TensorMap): """ - Calculates the norm used in CG iteration tests. Assumes standardized - metadata and that the TensorMap is sliced to a single sample. + Calculates the norm used in CG iteration tests. Assumes that the TensorMap + is sliced to a single sample. For a given atomic sample, the norm is calculated for each feature vector, as a sum over lambda, sigma, and m. """ + # Check that there is only one sample + assert ( + len( + metatensor.unique_metadata( + tensor, "samples", ["structure", "center", "species_center"] + ).values + ) + == 1 + ) norm = 0.0 for key, block in tensor.items(): # Sum over lambda and sigma angular_l = key["spherical_harmonics_l"] @@ -155,8 +155,8 @@ def get_norm(tensor: TensorMap): @pytest.mark.skipif( - HAS_SYMPY is False and HAS_METATENSOR_OPERATIONS is False, - reason="SymPy and metatensor-operations are not installed", + not HAS_SYMPY or not HAS_METATENSOR_OPERATIONS, + reason="SymPy or metatensor-operations are not installed", ) @pytest.mark.parametrize( "frames, nu_target, angular_cutoff, selected_keys", @@ -174,40 +174,37 @@ def get_norm(tensor: TensorMap): (h2o_periodic(), 2, 5, None), ], ) -def test_so3_equivariance( - frames: List[ase.Atoms], - nu_target: int, - angular_cutoff: int, - selected_keys: Labels, -): - wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) +def test_so3_equivariance(frames, nu_target, angular_cutoff, selected_keys): + """ + Tests that the output of :py:func:`correlate_density` is equivariant under + SO(3) transformations. + """ + wig = wigner_d_matrices(nu_target * SPHEX_HYPERS["max_angular"]) frames_so3 = [transform_frame_so3(frame, wig.angles) for frame in frames] - nu_1 = sphex(frames) - nu_1_so3 = sphex(frames_so3) + nu_1 = spherical_expansion(frames) + nu_1_so3 = spherical_expansion(frames_so3) - nu_3 = _correlate_density( + nu_3 = correlate_density( density=nu_1, correlation_order=nu_target, angular_cutoff=angular_cutoff, selected_keys=selected_keys, - compute_metadata_only=False, - )[0] - nu_3_so3 = _correlate_density( + ) + nu_3_so3 = correlate_density( density=nu_1_so3, correlation_order=nu_target, angular_cutoff=angular_cutoff, selected_keys=selected_keys, - compute_metadata_only=False, - )[0] + ) nu_3_transf = wig.transform_tensormap_so3(nu_3) assert metatensor.allclose(nu_3_transf, nu_3_so3) @pytest.mark.skipif( - HAS_SYMPY is False and HAS_METATENSOR_OPERATIONS is False, - reason="SymPy and metatensor-operations are not installed", + not HAS_SYMPY or not HAS_METATENSOR_OPERATIONS, + reason="SymPy or metatensor-operations are not installed", ) @pytest.mark.parametrize( "frames, nu_target, angular_cutoff, selected_keys", @@ -225,32 +222,29 @@ def test_so3_equivariance( (h2o_periodic(), 2, 5, None), ], ) -def test_o3_equivariance( - frames: List[ase.Atoms], - nu_target: int, - angular_cutoff: int, - selected_keys: Labels, -): - wig = wigners(nu_target * SPHEX_HYPERS["max_angular"]) +def test_o3_equivariance(frames, nu_target, angular_cutoff, selected_keys): + """ + Tests that the output of :py:func:`correlate_density` is equivariant under + O(3) transformations. + """ + wig = wigner_d_matrices(nu_target * SPHEX_HYPERS["max_angular"]) frames_o3 = [transform_frame_o3(frame, wig.angles) for frame in frames] - nu_1 = sphex(frames) - nu_1_o3 = sphex(frames_o3) + nu_1 = spherical_expansion(frames) + nu_1_o3 = spherical_expansion(frames_o3) - nu_3 = _correlate_density( + nu_3 = correlate_density( density=nu_1, correlation_order=nu_target, angular_cutoff=angular_cutoff, selected_keys=selected_keys, - compute_metadata_only=False, - )[0] - nu_3_o3 = _correlate_density( + ) + nu_3_o3 = correlate_density( density=nu_1_o3, correlation_order=nu_target, angular_cutoff=angular_cutoff, selected_keys=selected_keys, - compute_metadata_only=False, - )[0] + ) nu_3_transf = wig.transform_tensormap_o3(nu_3) assert metatensor.allclose(nu_3_transf, nu_3_o3) @@ -260,28 +254,34 @@ def test_o3_equivariance( @pytest.mark.skipif( - HAS_METATENSOR_OPERATIONS is False, reason="metatensor-operations is not installed" + not HAS_METATENSOR_OPERATIONS, reason="metatensor-operations is not installed" ) @pytest.mark.parametrize("frames", [h2_isolated()]) -def test_lambda_soap_vs_powerspectrum(frames): +@pytest.mark.parametrize( + "sphex_powspec", + [ + (spherical_expansion, power_spectrum), + (spherical_expansion_small, power_spectrum_small), + ], +) +def test_lambda_soap_vs_powerspectrum(frames, sphex_powspec): """ Tests for exact equivalence between the invariant block of a generated lambda-SOAP equivariant and the Python implementation of PowerSpectrum in rascaline utils. """ # Build a PowerSpectrum - ps = powspec_small_features(frames) + ps = sphex_powspec[1](frames) # Build a lambda-SOAP - density = sphex_small_features(frames) - lsoap = _correlate_density( + density = sphex_powspec[0](frames) + lsoap = correlate_density( density=density, correlation_order=2, selected_keys=Labels( names=["spherical_harmonics_l"], values=np.array([0]).reshape(-1, 1) ), - compute_metadata_only=False, - )[0] + ) keys = lsoap.keys.remove(name="spherical_harmonics_l") lsoap = TensorMap(keys=keys, blocks=[b.copy() for b in lsoap.blocks()]) @@ -315,11 +315,11 @@ def test_lambda_soap_vs_powerspectrum(frames): @pytest.mark.skipif( - HAS_METATENSOR_OPERATIONS is False, reason="metatensor-operations is not installed" + not HAS_METATENSOR_OPERATIONS, reason="metatensor-operations is not installed" ) @pytest.mark.parametrize("frames", [h2_isolated(), h2o_periodic()]) @pytest.mark.parametrize("correlation_order", [2, 3, 4]) -def test_combine_single_center_norm(frames, correlation_order): +def test_correlate_density_norm(frames, correlation_order): """ Checks \\|ρ^\\nu\\| = \\|ρ\\|^\\nu in the case where l lists are not sorted. If l lists are sorted, thus saving computation of redundant block @@ -328,28 +328,24 @@ def test_combine_single_center_norm(frames, correlation_order): """ # Build nu=1 SphericalExpansion - nu1 = sphex_small_features(frames) + nu1 = spherical_expansion_small(frames) # Build higher body order tensor without sorting the l lists - nux = _correlate_density( + nux = correlate_density( nu1, correlation_order=correlation_order, angular_cutoff=None, selected_keys=None, skip_redundant=False, - compute_metadata_only=False, - sparse=True, - )[0] + ) # Build higher body order tensor *with* sorting the l lists - nux_sorted_l = _correlate_density( + nux_sorted_l = correlate_density( nu1, correlation_order=correlation_order, angular_cutoff=None, selected_keys=None, skip_redundant=True, - compute_metadata_only=False, - sparse=True, - )[0] + ) # Standardize the features by passing through the CG combination code but with # no iterations (i.e. body order 1 -> 1) @@ -436,27 +432,30 @@ def test_clebsch_gordan_orthogonality(cg_cache_dense, l1, l2): @pytest.mark.skipif( - HAS_METATENSOR_OPERATIONS is False, reason="metatensor-operations is not installed" + not HAS_METATENSOR_OPERATIONS, reason="metatensor-operations is not installed" ) @pytest.mark.parametrize("frames", [h2_isolated(), h2o_isolated()]) -def test_single_center_combine_to_correlation_order_dense_sparse_agree(frames): +def test_correlate_density_dense_sparse_agree(frames): """ Tests for agreement between nu=3 tensors built using both sparse and dense CG coefficient caches. """ - density = sphex_small_features(frames) + density = spherical_expansion_small(frames) + + # NOTE: testing the private function here so we can control the use of + # sparse v dense CG cache n_body_sparse = _correlate_density( density, correlation_order=3, - sparse=True, compute_metadata_only=False, - )[0] + sparse=True, + ) n_body_dense = _correlate_density( density, correlation_order=3, - sparse=False, compute_metadata_only=False, - )[0] + sparse=False, + ) assert metatensor.allclose(n_body_sparse, n_body_dense, atol=1e-8, rtol=1e-8) @@ -465,39 +464,34 @@ def test_single_center_combine_to_correlation_order_dense_sparse_agree(frames): @pytest.mark.skipif( - HAS_METATENSOR_OPERATIONS is False, reason="metatensor-operations is not installed" + not HAS_METATENSOR_OPERATIONS, reason="metatensor-operations is not installed" ) @pytest.mark.parametrize("frames", [h2o_isolated()]) @pytest.mark.parametrize("correlation_order", [2, 3]) @pytest.mark.parametrize("skip_redundant", [True, False]) -def test_single_center_combine_to_correlation_order_metadata_agree( - frames, correlation_order, skip_redundant -): +def test_correlate_density_metadata_agree(frames, correlation_order, skip_redundant): """ - Tests that the outputs from `_correlate_density` agrees when switching the - `compute_metadata_only` flag on and off. + Tests that the metadata of outputs from :py:func:`correlate_density` and + :py:func:`correlate_density_metadata` agree. """ - for nu1 in [sphex_small_features(frames), sphex(frames)]: + for nu1 in [spherical_expansion_small(frames), spherical_expansion(frames)]: # Build higher body order tensor with CG computation - nux = _correlate_density( + nux = correlate_density( nu1, correlation_order=correlation_order, angular_cutoff=None, selected_keys=None, skip_redundant=skip_redundant, - compute_metadata_only=False, - sparse=True, - )[0] + ) # Build higher body order tensor without CG computation - i.e. metadata # only - nux_metadata_only = _correlate_density( + nux_metadata_only = correlate_density_metadata( nu1, correlation_order=correlation_order, angular_cutoff=None, selected_keys=None, skip_redundant=skip_redundant, - compute_metadata_only=True, - )[0] + ) assert metatensor.equal_metadata(nux, nux_metadata_only) @@ -512,7 +506,7 @@ def test_single_center_combine_to_correlation_order_metadata_agree( ], ) @pytest.mark.parametrize("skip_redundant", [True, False]) -def test_single_center_combine_angular_selection( +def test_correlate_density_angular_selection( frames: List[ase.Atoms], selected_keys: Labels, skip_redundant: bool, @@ -521,16 +515,15 @@ def test_single_center_combine_angular_selection( Tests that the correct angular channels are outputted based on the specified ``selected_keys``. """ - nu_1 = sphex(frames) + nu_1 = spherical_expansion(frames) - nu_2 = _correlate_density( + nu_2 = correlate_density( density=nu_1, correlation_order=2, angular_cutoff=None, selected_keys=selected_keys, skip_redundant=skip_redundant, - compute_metadata_only=False, - )[0] + ) if selected_keys is None: assert np.all(