From eac950c872fd4028e1906da069d2f063556c8e1c Mon Sep 17 00:00:00 2001 From: Max Veit Date: Fri, 7 May 2021 19:46:16 +0200 Subject: [PATCH 1/4] Make 'global_species' work as expected and improve documentation (it was ignored before if 'expansion_by_species_method' was not set appropriately; this is now done automatically) Fix #350 --- .../representations/spherical_invariants.py | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/bindings/rascal/representations/spherical_invariants.py b/bindings/rascal/representations/spherical_invariants.py index 38c0429c1..ea674d419 100644 --- a/bindings/rascal/representations/spherical_invariants.py +++ b/bindings/rascal/representations/spherical_invariants.py @@ -41,16 +41,16 @@ class SphericalInvariants(BaseIO): max_angular : int Highest angular momentum number (l) in the expansion - gaussian_sigma_type : str + gaussian_sigma_type : str, default="Constant" How the Gaussian atom sigmas (smearing widths) are allowed to vary -- fixed ('Constant'), by species ('PerSpecies'), or by distance from the central atom ('Radial'). - gaussian_sigma_constant : float - Specifies the atomic Gaussian widths, in the case where they're - fixed. + gaussian_sigma_constant : float, default=0.3 + Specifies the atomic Gaussian widths when + gaussian_sigma_type=="Constant" - cutoff_function_type : string + cutoff_function_type : string, default="ShiftedCosine" Choose the type of smooth cutoff function used to define the local environment. Can be either 'ShiftedCosine' or 'RadialScaling'. @@ -87,15 +87,15 @@ class SphericalInvariants(BaseIO): where :math:`c` is the rate, :math:`r_0` is the scale, :math:`m` is the exponent. - soap_type : string + soap_type : string, default="PowerSpectrum" Specifies the type of representation to be computed ("RadialSpectrum", "PowerSpectrum" and "BiSpectrum"). - inversion_symmetry : boolean + inversion_symmetry : boolean, default True Specifies whether inversion invariance should be enforced. (Only relevant for BiSpectrum.) - radial_basis : string + radial_basis : string, default="GTO" Specifies the type of radial basis R_n to be computed ("GTO" for Gaussian typed orbitals and "DVR" discrete variable representation using Gaussian-Legendre quadrature rule) @@ -104,12 +104,12 @@ class SphericalInvariants(BaseIO): Whether to normalize so that the kernel between identical environments is 1. Default and highly recommended: True. - optimization_args : dict + optimization_args : dict, optional Additional arguments for optimization. Currently spline optimization for the radial basis function is available Recommended settings if used {"type":"Spline", "accuracy": 1e-5} - expansion_by_species_method : string + expansion_by_species_method : string, default="environment wise" Specifies the how the species key of the invariant are set-up. Possible values: 'environment wise', 'user defined', 'structure wise'. The descriptor is computed for each atomic enviroment and it is indexed @@ -135,16 +135,21 @@ class SphericalInvariants(BaseIO): build kernels does not allow for clear efficiency gains so their sparsity is kept irrespective of expansion_by_species_method. - global_species : list of int + Finally, note that this setting is overridden if 'global_species' + is provided (see below). + + global_species : list of int, optional list of species (specified with their atomic number) to use to set-up the species key of the invariant. It should contain all the species - present in the structure for which invariants will be computed + present in the structure for which invariants will be computed. + If provided, 'expansion_by_species_method' will be set to + "user defined", overriding the original value. - compute_gradients : bool + compute_gradients : bool, default False control the computation of the representation's gradients w.r.t. atomic positions. - cutoff_function_parameters : dict + cutoff_function_parameters : dict, optional Additional parameters for the cutoff function. if cutoff_function_type == 'RadialScaling' then it should have the form @@ -156,7 +161,7 @@ class SphericalInvariants(BaseIO): where :code:`...` should be replaced by the desired value. - coefficient_subselection : list or None + coefficient_subselection : list, optional if None then all the coefficients are computed following max_radial, max_angular and the atomic species present. if :code:`soap_type == 'PowerSpectrum'` and it has the form @@ -186,7 +191,7 @@ def __init__( cutoff_smooth_width, max_radial, max_angular, - gaussian_sigma_type, + gaussian_sigma_type="Constant", gaussian_sigma_constant=0.3, cutoff_function_type="ShiftedCosine", soap_type="PowerSpectrum", @@ -211,6 +216,8 @@ class documentation global_species = [] elif not isinstance(global_species, list): global_species = list(global_species) + if global_species: + expansion_by_species_method = "user defined" self.update_hyperparameters( max_radial=max_radial, From ae64d1f8ded3d0f42002024c52180055a80f2ed6 Mon Sep 17 00:00:00 2001 From: Max Veit Date: Mon, 10 May 2021 16:30:15 +0200 Subject: [PATCH 2/4] Merge Python species selection parameters into a single 'species_list' This replaces the originally separate 'expansion_by_species_method' and 'global_species' keys, which are kept in the C++ backend; the wrapper just provides a more intuitive interface now. Some backwards compatibility is provided; it should be removed after a few months. --- .../representations/spherical_expansion.py | 122 +++++++++++++----- .../representations/spherical_invariants.py | 85 +++++++----- 2 files changed, 143 insertions(+), 64 deletions(-) diff --git a/bindings/rascal/representations/spherical_expansion.py b/bindings/rascal/representations/spherical_expansion.py index e48092705..797d1b26c 100644 --- a/bindings/rascal/representations/spherical_expansion.py +++ b/bindings/rascal/representations/spherical_expansion.py @@ -1,4 +1,5 @@ import json +import logging from .base import CalculatorFactory, cutoff_function_dict_switch from ..neighbourlist import AtomsList @@ -6,6 +7,40 @@ from ..utils import BaseIO from copy import deepcopy +LOGGER = logging.getLogger(__name__) + + +def _parse_species_list(species_list): + """Parse the species_list keyword for initialization + + Return a hypers dictionary that can be used to initialize the + underlying C++ object, with expansion_by_species_method and + global_species keys set appropriately + """ + if isinstance(species_list, str): + if (species_list == "environment wise") or ( + species_list == "structure wise" + ): + expansion_by_species_method = species_list + global_species = [] + else: + raise ValueError( + "'species_list' must be one of: {'environment wise', " + "'structure_wise'}, or a list of int" + ) + else: + if isinstance(species_list, Iterable): + expansion_by_species_method = "user defined" + global_species = list(species_list) + else: + raise ValueError( + "'species_list' must be a list of int, if user-defined" + ) + return { + "expansion_by_species_method": expansion_by_species_method, + "global_species": global_species, + } + class SphericalExpansion(BaseIO): """ @@ -26,16 +61,16 @@ class SphericalExpansion(BaseIO): max_angular : int Highest angular momentum number (l) in the expansion - gaussian_sigma_type : str + gaussian_sigma_type : str, default="Constant" How the Gaussian atom sigmas (smearing widths) are allowed to vary -- fixed ('Constant'), by species ('PerSpecies'), or by distance from the central atom ('Radial'). - gaussian_sigma_constant : float - Specifies the atomic Gaussian widths, in the case where they're - fixed. + gaussian_sigma_constant : float, default=0.3 + Specifies the atomic Gaussian widths when + gaussian_sigma_type=="Constant" - cutoff_function_type : string + cutoff_function_type : string, default="ShiftedCosine" Choose the type of smooth cutoff function used to define the local environment. Can be either 'ShiftedCosine' or 'RadialScaling'. @@ -72,18 +107,20 @@ class SphericalExpansion(BaseIO): where :math:`c` is the rate, :math:`r_0` is the scale, :math:`m` is the exponent. - radial_basis : string + radial_basis : string, default="GTO" Specifies the type of radial basis R_n to be computed - ("GTO" for Gaussian typed orbitals and "DVR" discrete variable representation using Gaussian quadrature rule) + ("GTO" for Gaussian typed orbitals and "DVR" discrete variable + representation using Gaussian quadrature rule) - optimization_args : dict + optimization_args : dict, optional Additional arguments for optimization. Currently spline optimization for the radial basis function is available - Recommended settings if used {"type":"Spline", "accuracy": 1e-5} + Recommended settings if using: {"type":"Spline", "accuracy": 1e-5} - expansion_by_species_method : string + species_list : string or list(int), default="environment wise" Specifies the how the species key of the invariant are set-up. - Possible values: 'environment wise', 'user defined', 'structure wise'. + Possible values: 'environment wise', 'structure wise', or a user-defined + list of species. The descriptor is computed for each atomic enviroment and it is indexed using tuples of atomic species that are present within the environment. This index is by definition sparse since a species tuple will be non @@ -93,9 +130,16 @@ class SphericalExpansion(BaseIO): atomic environment. 'structure wise' means that within a structure the species tuples will be the same for each environment coefficients. - 'user defined' uses global_species to set-up the species tuples. - These different settings correspond to different trade-off between + The user-defined option means that all the atomic numbers contained + in the supplied list will be considered. The user _must_ ensure that + all species contained in the structure are represented in the list, + otherwise an error will be raised. They are free to specify species + that do not occur in any structure, however; a typical use case of this + is to compare SOAP vectors between structure sets of possibly different + species composition. + + These different settings correspond to different trade-offs between the memory efficiency of the invariants and the computational efficiency of the kernel computation. When computing a kernel using 'environment wise' setting does not allow @@ -105,18 +149,13 @@ class SphericalExpansion(BaseIO): Note that the sparsity of the gradient coefficients and their use to build kernels does not allow for clear efficiency gains so their - sparsity is kept irrespective of expansion_by_species_method. - - global_species : list - list of species to use to set-up the species key of the invariant. It - should contain all the species present in the structure for which - invariants will be computed + sparsity is kept irrespective of the value of this parameter. - compute_gradients : bool + compute_gradients : bool, default False control the computation of the representation's gradients w.r.t. atomic positions. - cutoff_function_parameters : dict + cutoff_function_parameters : dict, optional Additional parameters for the cutoff function. if cutoff_function_type == 'RadialScaling' then it should have the form @@ -146,15 +185,16 @@ def __init__( cutoff_smooth_width, max_radial, max_angular, - gaussian_sigma_type, + gaussian_sigma_type="Constant", gaussian_sigma_constant=0.3, cutoff_function_type="ShiftedCosine", radial_basis="GTO", optimization_args={}, - expansion_by_species_method="environment wise", - global_species=None, + species_list="environment wise", compute_gradients=False, cutoff_function_parameters=dict(), + expansion_by_species_method=None, + global_species=None, ): """Construct a SphericalExpansion representation @@ -165,11 +205,6 @@ class documentation self.name = "sphericalexpansion" self.hypers = dict() - if global_species is None: - global_species = [] - elif not isinstance(global_species, list): - global_species = list(global_species) - self.update_hyperparameters( max_radial=max_radial, max_angular=max_angular, @@ -185,6 +220,23 @@ class documentation cutoff_function = cutoff_function_dict_switch( cutoff_function_type, **cutoff_function_parameters ) + # Soft backwards compatibility (remove these two if-clauses after 01.11.2021) + if expansion_by_species_method is not None: + LOGGER.warn( + "The 'expansion_by_species_method' parameter is deprecated " + "(see 'species_list' parameter instead).\n" + "This message will become an error after 2021-11-01." + ) + species_list = expansion_by_species_method + if global_species is not None: + LOGGER.warn( + "The 'global_species' parameter is deprecated " + "(see 'species_list' parameter instead).\n" + "This message will become an error after 2021-11-01." + ) + species_list = global_species + species_list_hypers = _parse_species_list(species_list) + self.update_hyperparameters(**species_list_hypers) gaussian_density = dict( type=gaussian_sigma_type, @@ -198,7 +250,8 @@ class documentation else: accuracy = 1e-5 print( - "No accuracy for spline optimization was given. Switching to default accuracy {:.0e}.".format( + "No accuracy for spline optimization was given. " + "Switching to default accuracy {:.0e}.".format( accuracy ) ) @@ -301,14 +354,19 @@ def _get_init_params(self): gaussian_density = self.hypers["gaussian_density"] cutoff_function = self.hypers["cutoff_function"] radial_contribution = self.hypers["radial_contribution"] + global_species = self.hypers["global_species"] + species_list = ( + global_species + if global_species + else self.hypers["expansion_by_species_method"] + ) init_params = dict( interaction_cutoff=cutoff_function["cutoff"]["value"], cutoff_smooth_width=cutoff_function["smooth_width"]["value"], max_radial=self.hypers["max_radial"], max_angular=self.hypers["max_angular"], - expansion_by_species_method=self.hypers["expansion_by_species_method"], - global_species=self.hypers["global_species"], + species_list=species_list, compute_gradients=self.hypers["compute_gradients"], gaussian_sigma_type=gaussian_density["type"], gaussian_sigma_constant=gaussian_density["gaussian_sigma"]["value"], diff --git a/bindings/rascal/representations/spherical_invariants.py b/bindings/rascal/representations/spherical_invariants.py index ea674d419..e427e7223 100644 --- a/bindings/rascal/representations/spherical_invariants.py +++ b/bindings/rascal/representations/spherical_invariants.py @@ -1,13 +1,17 @@ -import json -from itertools import product import ase +from itertools import product +import json +import logging from .base import CalculatorFactory, cutoff_function_dict_switch +from .spherical_expansion import _parse_species_list from ..neighbourlist import AtomsList import numpy as np from copy import deepcopy from ..utils import BaseIO +LOGGER = logging.getLogger(__name__) + def get_power_spectrum_index_mapping(sp_pairs, n_max, l_max): feat_idx2coeff_idx = {} @@ -107,11 +111,15 @@ class SphericalInvariants(BaseIO): optimization_args : dict, optional Additional arguments for optimization. Currently spline optimization for the radial basis function is available - Recommended settings if used {"type":"Spline", "accuracy": 1e-5} + Recommended settings if using: {"type":"Spline", "accuracy": 1e-5} - expansion_by_species_method : string, default="environment wise" + species_list : string or list(int), default="environment wise" Specifies the how the species key of the invariant are set-up. - Possible values: 'environment wise', 'user defined', 'structure wise'. + Possible values: 'environment wise', 'structure wise', or a user-defined + list of species. + This parameter is passed directly to the SphericalExpansion used to + build this representation; its documentation is reproduced below. + The descriptor is computed for each atomic enviroment and it is indexed using tuples of atomic species that are present within the environment. This index is by definition sparse since a species tuple will be non @@ -121,29 +129,26 @@ class SphericalInvariants(BaseIO): atomic environment. 'structure wise' means that within a structure the species tuples will be the same for each environment coefficients. - 'user defined' uses global_species to set-up the species tuples. - These different settings correspond to different trade-off between + The user-defined option means that all the atomic numbers contained + in the supplied list will be considered. The user _must_ ensure that + all species contained in the structure are represented in the list, + otherwise an error will be raised. They are free to specify species + that do not occur in any structure, however; a typical use case of this + is to compare SOAP vectors between structure sets of possibly different + species composition. + + These different settings correspond to different trade-offs between the memory efficiency of the invariants and the computational efficiency of the kernel computation. When computing a kernel using 'environment wise' setting does not allow for efficent matrix matrix multiplications which is ensured when - 'user defined' is used. 'structure wise' is a balance between the + a user-defined list is used. 'structure wise' is a balance between the memory footprint and the use of matrix matrix products. Note that the sparsity of the gradient coefficients and their use to build kernels does not allow for clear efficiency gains so their - sparsity is kept irrespective of expansion_by_species_method. - - Finally, note that this setting is overridden if 'global_species' - is provided (see below). - - global_species : list of int, optional - list of species (specified with their atomic number) to use to set-up - the species key of the invariant. It should contain all the species - present in the structure for which invariants will be computed. - If provided, 'expansion_by_species_method' will be set to - "user defined", overriding the original value. + sparsity is kept irrespective of the value of this parameter. compute_gradients : bool, default False control the computation of the representation's gradients w.r.t. atomic @@ -199,11 +204,12 @@ def __init__( radial_basis="GTO", normalize=True, optimization_args={}, - expansion_by_species_method="environment wise", - global_species=None, + species_list="environment wise", compute_gradients=False, cutoff_function_parameters=dict(), coefficient_subselection=None, + expansion_by_species_method=None, + global_species=None, ): """Construct a SphericalExpansion representation @@ -212,12 +218,6 @@ class documentation """ self.name = "sphericalinvariants" self.hypers = dict() - if global_species is None: - global_species = [] - elif not isinstance(global_species, list): - global_species = list(global_species) - if global_species: - expansion_by_species_method = "user defined" self.update_hyperparameters( max_radial=max_radial, @@ -230,6 +230,23 @@ class documentation compute_gradients=compute_gradients, coefficient_subselection=coefficient_subselection, ) + # Soft backwards compatibility (remove these two if-clauses after 01.11.2021) + if expansion_by_species_method is not None: + LOGGER.warn( + "Warning: The 'expansion_by_species_method' parameter is deprecated " + "(see 'species_list' parameter instead).\n" + "This message will become an error after 2021-11-01." + ) + species_list = expansion_by_species_method + if global_species is not None: + LOGGER.warn( + "Warning: The 'global_species' parameter is deprecated " + "(see 'species_list' parameter instead).\n" + "This message will become an error after 2021-11-01." + ) + species_list = global_species + species_list_hypers = _parse_species_list(species_list) + self.update_hyperparameters(**species_list_hypers) if self.hypers["coefficient_subselection"] is None: del self.hypers["coefficient_subselection"] @@ -255,9 +272,8 @@ class documentation else: accuracy = 1e-5 print( - "No accuracy for spline optimization was given. Switching to default accuracy {:.0e}.".format( - accuracy - ) + "No accuracy for spline optimization was given. " + "Switching to default accuracy {:.0e}.".format(accuracy) ) optimization_args = {"type": "Spline", "accuracy": accuracy} elif optimization_args["type"] == "None": @@ -449,6 +465,12 @@ def _get_init_params(self): gaussian_density = self.hypers["gaussian_density"] cutoff_function = self.hypers["cutoff_function"] radial_contribution = self.hypers["radial_contribution"] + global_species = self.hypers["global_species"] + species_list = ( + global_species + if global_species + else self.hypers["expansion_by_species_method"] + ) init_params = dict( interaction_cutoff=cutoff_function["cutoff"]["value"], @@ -458,8 +480,7 @@ def _get_init_params(self): soap_type=self.hypers["soap_type"], inversion_symmetry=self.hypers["inversion_symmetry"], normalize=self.hypers["normalize"], - expansion_by_species_method=self.hypers["expansion_by_species_method"], - global_species=self.hypers["global_species"], + species_list=species_list, compute_gradients=self.hypers["compute_gradients"], gaussian_sigma_type=gaussian_density["type"], gaussian_sigma_constant=gaussian_density["gaussian_sigma"]["value"], From 7be23317455aad344733ed7688c298696310f2c5 Mon Sep 17 00:00:00 2001 From: Max Veit Date: Mon, 10 May 2021 17:49:24 +0200 Subject: [PATCH 3/4] Fix missing import --- bindings/rascal/representations/spherical_expansion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/rascal/representations/spherical_expansion.py b/bindings/rascal/representations/spherical_expansion.py index 797d1b26c..35180b6b4 100644 --- a/bindings/rascal/representations/spherical_expansion.py +++ b/bindings/rascal/representations/spherical_expansion.py @@ -1,3 +1,4 @@ +from collections.abc import Iterable import json import logging From dd16ac73c1521b950e89b4bcb256a68106b26ff6 Mon Sep 17 00:00:00 2001 From: Max Veit Date: Mon, 10 May 2021 18:55:29 +0200 Subject: [PATCH 4/4] Fix backwards compatibility Also, fix strangely-meta deprecated deprecation warnings --- .../representations/spherical_expansion.py | 23 ++++++++++++++----- .../representations/spherical_invariants.py | 20 ++++++++++++---- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/bindings/rascal/representations/spherical_expansion.py b/bindings/rascal/representations/spherical_expansion.py index 35180b6b4..952671172 100644 --- a/bindings/rascal/representations/spherical_expansion.py +++ b/bindings/rascal/representations/spherical_expansion.py @@ -222,18 +222,29 @@ class documentation cutoff_function_type, **cutoff_function_parameters ) # Soft backwards compatibility (remove these two if-clauses after 01.11.2021) + # Soft backwards compatibility (remove this whole if-statement after 01.11.2021) if expansion_by_species_method is not None: - LOGGER.warn( - "The 'expansion_by_species_method' parameter is deprecated " + LOGGER.warning( + "Warning: The 'expansion_by_species_method' parameter is deprecated " "(see 'species_list' parameter instead).\n" "This message will become an error after 2021-11-01." ) - species_list = expansion_by_species_method - if global_species is not None: - LOGGER.warn( - "The 'global_species' parameter is deprecated " + if expansion_by_species_method != "user defined": + species_list = expansion_by_species_method + elif global_species is not None: + species_list = global_species + else: + raise ValueError( + "Found deprecated 'expansion_by_species_method' parameter " + "set to 'user defined' without 'global_species' set") + elif global_species is not None: + LOGGER.warning( + "Warning: The 'global_species' parameter is deprecated " "(see 'species_list' parameter instead).\n" "This message will become an error after 2021-11-01." + "(Also, this needs to be set with " + "'expansion_by_species_method'=='user defined'; proceeding under " + "the assumption that this is what you wanted)" ) species_list = global_species species_list_hypers = _parse_species_list(species_list) diff --git a/bindings/rascal/representations/spherical_invariants.py b/bindings/rascal/representations/spherical_invariants.py index e427e7223..4da66f724 100644 --- a/bindings/rascal/representations/spherical_invariants.py +++ b/bindings/rascal/representations/spherical_invariants.py @@ -230,19 +230,29 @@ class documentation compute_gradients=compute_gradients, coefficient_subselection=coefficient_subselection, ) - # Soft backwards compatibility (remove these two if-clauses after 01.11.2021) + # Soft backwards compatibility (remove this whole if-statement after 01.11.2021) if expansion_by_species_method is not None: - LOGGER.warn( + LOGGER.warning( "Warning: The 'expansion_by_species_method' parameter is deprecated " "(see 'species_list' parameter instead).\n" "This message will become an error after 2021-11-01." ) - species_list = expansion_by_species_method - if global_species is not None: - LOGGER.warn( + if expansion_by_species_method != "user defined": + species_list = expansion_by_species_method + elif global_species is not None: + species_list = global_species + else: + raise ValueError( + "Found deprecated 'expansion_by_species_method' parameter " + "set to 'user defined' without 'global_species' set") + elif global_species is not None: + LOGGER.warning( "Warning: The 'global_species' parameter is deprecated " "(see 'species_list' parameter instead).\n" "This message will become an error after 2021-11-01." + "(Also, this needs to be set with " + "'expansion_by_species_method'=='user defined'; proceeding under " + "the assumption that this is what you wanted)" ) species_list = global_species species_list_hypers = _parse_species_list(species_list)