From 8286039d4ae15ec132440ad01c768fb72263c922 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 12:52:32 -0400 Subject: [PATCH 01/15] add get_max_cutoff to struc --- flare/struc.py | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/flare/struc.py b/flare/struc.py index dc9d235a3..e8fded429 100644 --- a/flare/struc.py +++ b/flare/struc.py @@ -127,6 +127,39 @@ def get_cell_dot(self): return cell_dot + def get_max_cutoff(self): + # Retrieve the lattice vectors. + a_vec = self.cell[0] + b_vec = self.cell[1] + c_vec = self.cell[2] + + # Compute dot products and norms of lattice vectors. + a_dot_b = np.dot(a_vec, b_vec) + a_dot_c = np.dot(a_vec, c_vec) + b_dot_c = np.dot(b_vec, c_vec) + + a_norm = np.linalg.norm(a_vec) + b_norm = np.linalg.norm(b_vec) + c_norm = np.linalg.norm(c_vec) + + # Compute the six independent altitudes of the cell faces. + # The smallest is the maximum cutoff that can be used with sweep=1. + max_candidates = np.zeros(6) + max_candidates[0] = \ + a_norm * np.sqrt(1 - (a_dot_b / (a_norm * b_norm))**2) + max_candidates[1] = \ + b_norm * np.sqrt(1 - (a_dot_b / (a_norm * b_norm))**2) + max_candidates[2] = \ + a_norm * np.sqrt(1 - (a_dot_c / (a_norm * c_norm))**2) + max_candidates[3] = \ + c_norm * np.sqrt(1 - (a_dot_c / (a_norm * c_norm))**2) + max_candidates[4] = \ + b_norm * np.sqrt(1 - (b_dot_c / (b_norm * c_norm))**2) + max_candidates[5] = \ + c_norm * np.sqrt(1 - (b_dot_c / (b_norm * c_norm))**2) + + return np.min(max_candidates) + @staticmethod def raw_to_relative(positions: 'ndarray', cell_transpose: 'ndarray', cell_dot_inverse: 'ndarray')-> 'ndarray': @@ -175,10 +208,10 @@ def wrap_positions(self, in_place: bool = True)-> 'ndarray': into the unit cell. in_place flag controls if the wrapped positions are set in the class. - :param in_place: If true, set the current structure - positions to be the wrapped positions. + :param in_place: If true, set the current structure positions to be + the wrapped positions. :return: Cartesian coordinates of positions all in unit cell - :rtype: np.ndarray + :rtype: np.ndarray """ rel_pos = \ self.raw_to_relative(self.positions, self.cell_transpose, @@ -464,8 +497,6 @@ def from_file(file_name, format='') -> Union['flare.struc.Structure', raise ImportError("Pymatgen not imported; " \ "functionality requires pymatgen.") - - def get_unique_species(species: List[Any]) -> (List, List[int]): """ Returns a list of the unique species passed in, and a list of @@ -485,5 +516,3 @@ def get_unique_species(species: List[Any]) -> (List, List[int]): coded_species = np.array(coded_species) return unique_species, coded_species - - From ef76b1dd304cdf06b82bae82d859f6ac361cf3b7 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 13:01:37 -0400 Subject: [PATCH 02/15] pep8 clean up --- flare/struc.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/flare/struc.py b/flare/struc.py index e8fded429..17c0fee18 100644 --- a/flare/struc.py +++ b/flare/struc.py @@ -143,7 +143,8 @@ def get_max_cutoff(self): c_norm = np.linalg.norm(c_vec) # Compute the six independent altitudes of the cell faces. - # The smallest is the maximum cutoff that can be used with sweep=1. + # The smallest is the maximum atomic environment cutoff that can be + # used with sweep=1. max_candidates = np.zeros(6) max_candidates[0] = \ a_norm * np.sqrt(1 - (a_dot_b / (a_norm * b_norm))**2) @@ -162,7 +163,7 @@ def get_max_cutoff(self): @staticmethod def raw_to_relative(positions: 'ndarray', cell_transpose: 'ndarray', - cell_dot_inverse: 'ndarray')-> 'ndarray': + cell_dot_inverse: 'ndarray') -> 'ndarray': """Convert Cartesian coordinates to relative (fractional) coordinates, expressed in terms of the cell vectors set in self.cell. @@ -186,7 +187,7 @@ def raw_to_relative(positions: 'ndarray', cell_transpose: 'ndarray', @staticmethod def relative_to_raw(relative_positions: 'ndarray', cell_transpose_inverse: 'ndarray', - cell_dot: 'ndarray')-> 'ndarray': + cell_dot: 'ndarray') -> 'ndarray': """Convert fractional coordinates to raw (Cartesian) coordinates. :param relative_positions: fractional coordinates. @@ -202,7 +203,7 @@ def relative_to_raw(relative_positions: 'ndarray', return np.matmul(np.matmul(relative_positions, cell_dot), cell_transpose_inverse) - def wrap_positions(self, in_place: bool = True)-> 'ndarray': + def wrap_positions(self, in_place: bool = True) -> 'ndarray': """ Convenience function which folds atoms outside of the unit cell back into the unit cell. in_place flag controls if the wrapped positions @@ -211,7 +212,7 @@ def wrap_positions(self, in_place: bool = True)-> 'ndarray': :param in_place: If true, set the current structure positions to be the wrapped positions. :return: Cartesian coordinates of positions all in unit cell - :rtype: np.ndarray + :rtype: np.ndarray """ rel_pos = \ self.raw_to_relative(self.positions, self.cell_transpose, @@ -295,7 +296,7 @@ def as_str(self) -> str: return dumps(self.as_dict(), cls=NumpyEncoder) @staticmethod - def from_dict(dictionary: dict)-> 'flare.struc.Structure': + def from_dict(dictionary: dict) -> 'flare.struc.Structure': """ Assembles a Structure object from a dictionary parameterizing one. @@ -316,7 +317,7 @@ def from_dict(dictionary: dict)-> 'flare.struc.Structure': return struc @staticmethod - def from_ase_atoms(atoms: 'ase.Atoms')-> 'flare.struc.Structure': + def from_ase_atoms(atoms: 'ase.Atoms') -> 'flare.struc.Structure': """ From an ASE Atoms object, return a FLARE structure @@ -329,12 +330,10 @@ def from_ase_atoms(atoms: 'ase.Atoms')-> 'flare.struc.Structure': species=atoms.get_chemical_symbols()) return struc - def to_ase_atoms(self)-> 'ase.Atoms': + def to_ase_atoms(self) -> 'ase.Atoms': from ase import Atoms - return Atoms(self.species_labels, - positions=self.positions, - cell=self.cell, - pbc=True) + return Atoms(self.species_labels, positions=self.positions, + cell=self.cell, pbc=True) def to_pmg_structure(self): """ @@ -361,7 +360,7 @@ def to_pmg_structure(self): ) @staticmethod - def from_pmg_structure(structure: 'pymatgen Structure')-> \ + def from_pmg_structure(structure: 'pymatgen Structure') -> \ 'flare Structure': """ Returns Pymatgen structure as FLARE structure. @@ -390,11 +389,9 @@ def from_pmg_structure(structure: 'pymatgen Structure')-> \ return new_struc - def to_xyz(self, extended_xyz: bool = True, - print_stds: bool = False, - print_forces : bool = False, - print_max_stds: bool = False, - write_file: str = '')->str: + def to_xyz(self, extended_xyz: bool = True, print_stds: bool = False, + print_forces: bool = False, print_max_stds: bool = False, + write_file: str = '') -> str: """ Convenience function which turns a structure into an extended .xyz file; useful for further input into visualization programs like VESTA @@ -455,7 +452,8 @@ def to_xyz(self, extended_xyz: bool = True, @staticmethod def from_file(file_name, format='') -> Union['flare.struc.Structure', - List['flare.struc.Structure']]: + List['flare.struc.Structure'] + ]: """ Load a FLARE structure from a file or a series of FLARE structures :param file_name: @@ -466,7 +464,7 @@ def from_file(file_name, format='') -> Union['flare.struc.Structure', try: with open(file_name, 'r') as _: pass - except: + except FileNotFoundError: raise FileNotFoundError if 'xyz' in file_name or 'xyz' in format.lower(): @@ -489,14 +487,16 @@ def from_file(file_name, format='') -> Union['flare.struc.Structure', else: return structures - is_poscar = 'POSCAR' in file_name or 'CONTCAR' in file_name or 'vasp' in format.lower() + is_poscar = 'POSCAR' in file_name or 'CONTCAR' in file_name \ + or 'vasp' in format.lower() if is_poscar and _pmg_present: pmg_structure = pmgvaspio.Poscar.from_file(file_name).structure return Structure.from_pmg_structure(pmg_structure) elif is_poscar and not _pmg_present: - raise ImportError("Pymatgen not imported; " \ + raise ImportError("Pymatgen not imported; " "functionality requires pymatgen.") + def get_unique_species(species: List[Any]) -> (List, List[int]): """ Returns a list of the unique species passed in, and a list of From 49760627040beea4577ce1396b4557b05d7dcfa8 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 13:04:04 -0400 Subject: [PATCH 03/15] set max cutoff in struc constructor --- flare/struc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flare/struc.py b/flare/struc.py index 17c0fee18..7b4fadaf6 100644 --- a/flare/struc.py +++ b/flare/struc.py @@ -60,6 +60,9 @@ def __init__(self, cell: 'ndarray', species: Union[List[str], List[int]], self.vec2 = self.cell[1, :] self.vec3 = self.cell[2, :] + # Compute the max cutoff for sweep = 1. + self.max_cutoff = self.get_max_cutoff() + # get cell matrices for wrapping coordinates self.cell_transpose = self.cell.transpose() self.cell_transpose_inverse = np.linalg.inv(self.cell_transpose) @@ -127,7 +130,7 @@ def get_cell_dot(self): return cell_dot - def get_max_cutoff(self): + def get_max_cutoff(self) -> float: # Retrieve the lattice vectors. a_vec = self.cell[0] b_vec = self.cell[1] From d3dd42e868dae06d4709b0d2cf666117d71f7701 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 13:09:59 -0400 Subject: [PATCH 04/15] define sweep_array from max_cutoff --- flare/env.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/flare/env.py b/flare/env.py index 350f46bd0..4aa58007b 100644 --- a/flare/env.py +++ b/flare/env.py @@ -2,7 +2,7 @@ environment of an atom. :class:`AtomicEnvironment` objects are inputs to the 2-, 3-, and 2+3-body kernels.""" import numpy as np -from math import sqrt +from math import sqrt, ceil from numba import njit from flare.struc import Structure @@ -21,12 +21,15 @@ class AtomicEnvironment: :type cutoffs: np.ndarray """ - def __init__(self, structure: Structure, atom: int, cutoffs, sweep=1): + def __init__(self, structure: Structure, atom: int, cutoffs): self.structure = structure self.positions = structure.wrapped_positions self.cell = structure.cell self.species = structure.coded_species - self.sweep_array = np.arange(-sweep, sweep+1, 1) + + # Set the sweep array based on the 2-body cutoff. + sweep_val = ceil(cutoffs[0] / structure.max_cutoff) + self.sweep_array = np.arange(-sweep_val, sweep_val + 1, 1) self.atom = atom self.ctype = structure.coded_species[atom] From b3b2bb8224f195784784388aa521e06938a6f965 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 13:12:26 -0400 Subject: [PATCH 05/15] env clean up --- flare/env.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/flare/env.py b/flare/env.py index 4aa58007b..fc476fb21 100644 --- a/flare/env.py +++ b/flare/env.py @@ -110,7 +110,7 @@ def from_dict(dictionary): cutoffs = dictionary['cutoffs'] else: cutoffs = [] - for cutoff in ['cutoff_2','cutoff_3','cutoff_mb']: + for cutoff in ['cutoff_2', 'cutoff_3', 'cutoff_mb']: if dictionary.get(cutoff): cutoffs.append(dictionary[cutoff]) @@ -215,10 +215,12 @@ class to allow for njit acceleration with Numba. @njit -def get_2_body_arrays_ind(positions, atom: int, cell, cutoff_2: float, species): - """Returns distances, coordinates, species of atoms, and indexes of neighbors - in the 2-body local environment. This method is implemented outside - the AtomicEnvironment class to allow for njit acceleration with Numba. +def get_2_body_arrays_ind(positions, atom: int, cell, cutoff_2: float, + species: np.ndarray): + """Returns distances, coordinates, species of atoms, and indexes of + neighbors in the 2-body local environment. This method is implemented + outside the AtomicEnvironment class to allow for njit acceleration + with Numba. :param positions: Positions of atoms in the structure. :type positions: np.ndarray @@ -380,10 +382,11 @@ def get_3_body_arrays(bond_array_2, bond_positions_2, cutoff_3: float): @njit def get_m_body_arrays(positions, atom: int, cell, cutoff_mb: float, species, sweep: np.ndarray): - """Returns distances, and species of atoms in the many-body - local environment, and returns distances and numbers of neighbours for atoms in the one - many-body local environment. This method is implemented outside the AtomicEnvironment - class to allow for njit acceleration with Numba. + """Returns distances, and species of atoms in the many-body local + environment, and returns distances and numbers of neighbours for atoms + in the one many-body local environment. This method is implemented + outside the AtomicEnvironment class to allow for njit acceleration + with Numba. :param positions: Positions of atoms in the structure. :type positions: np.ndarray @@ -396,7 +399,8 @@ class to allow for njit acceleration with Numba. :type cutoff_mb: float :param species: Numpy array of species represented by their atomic numbers. :type species: np.ndarray - :param indexes: Boolean indicating whether indexes of neighbours are returned + :param indexes: Boolean indicating whether indexes of neighbours are + returned :type indexes: boolean :return: Tuple of arrays describing pairs of atoms in the 2-body local environment. @@ -415,11 +419,13 @@ class to allow for njit acceleration with Numba. num_neighs_mb: number of neighbours of each atom in the local environment - etypes_mb_array: species of neighbours of each atom in the local environment + etypes_mb_array: species of neighbours of each atom in the local + environment :rtype: np.ndarray, np.ndarray, np.ndarray, np.ndarray """ - # TODO: this can be probably improved using stored arrays, redundant calls to get_2_body_arrays + # TODO: this can be probably improved using stored arrays, redundant calls + # to get_2_body_arrays # Get distances, positions, species and indexes of neighbouring atoms bond_array_mb, __, etypes, bond_inds = get_2_body_arrays_ind( positions, atom, cell, cutoff_mb, species) @@ -446,7 +452,8 @@ class to allow for njit acceleration with Numba. neigh_dists_mb[i, :num_neighs_mb[i]] = neighbouring_dists[i] etypes_mb_array[i, :num_neighs_mb[i]] = neighbouring_etypes[i] - return bond_array_mb, neigh_dists_mb, num_neighs_mb, etypes_mb_array, etypes + return bond_array_mb, neigh_dists_mb, num_neighs_mb, etypes_mb_array, \ + etypes if __name__ == '__main__': From f186ed50a5a8f54878800d7207bc3a1203ec4279 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 13:15:13 -0400 Subject: [PATCH 06/15] add get_max_cutoff docstring --- flare/struc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/flare/struc.py b/flare/struc.py index 7b4fadaf6..988960b2e 100644 --- a/flare/struc.py +++ b/flare/struc.py @@ -131,6 +131,12 @@ def get_cell_dot(self): return cell_dot def get_max_cutoff(self) -> float: + """Compute the maximum cutoff compatible with a 3x3x3 supercell of the + structure. + + Returns: + float: maximum cutoff + """ # Retrieve the lattice vectors. a_vec = self.cell[0] b_vec = self.cell[1] From 2de0e8111726f0e66e1612d92b20d39ff43ee87f Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 13:23:57 -0400 Subject: [PATCH 07/15] hotfix the unit tests --- flare/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flare/env.py b/flare/env.py index fc476fb21..ad2464bdd 100644 --- a/flare/env.py +++ b/flare/env.py @@ -21,7 +21,7 @@ class AtomicEnvironment: :type cutoffs: np.ndarray """ - def __init__(self, structure: Structure, atom: int, cutoffs): + def __init__(self, structure: Structure, atom: int, cutoffs, sweep=1): self.structure = structure self.positions = structure.wrapped_positions self.cell = structure.cell From 76397b4fca1a9e239338f759e66b3bd3d93ec754 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 14:38:52 -0400 Subject: [PATCH 08/15] unit test the auto sweep feature --- flare/env.py | 1 + tests/test_env.py | 44 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/flare/env.py b/flare/env.py index ad2464bdd..18eb6697a 100644 --- a/flare/env.py +++ b/flare/env.py @@ -29,6 +29,7 @@ def __init__(self, structure: Structure, atom: int, cutoffs, sweep=1): # Set the sweep array based on the 2-body cutoff. sweep_val = ceil(cutoffs[0] / structure.max_cutoff) + self.sweep_val = sweep_val self.sweep_array = np.arange(-sweep_val, sweep_val + 1, 1) self.atom = atom diff --git a/tests/test_env.py b/tests/test_env.py index 70c76b7c9..1f89182af 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -11,13 +11,13 @@ def test_species_count(cutoff): species = [1, 2, 3] positions = np.array([[0, 0, 0], [0.5, 0.5, 0.5], [0.1, 0.1, 0.1]]) struc_test = Structure(cell, species, positions) - env_test = AtomicEnvironment(structure=struc_test, - atom=0, + env_test = AtomicEnvironment(structure=struc_test, atom=0, cutoffs=np.array([1, 1])) assert (len(struc_test.positions) == len(struc_test.coded_species)) assert (len(env_test.bond_array_2) == len(env_test.etypes)) assert (isinstance(env_test.etypes[0], np.int8)) + @pytest.mark.parametrize('cutoff', cutoff_list) def test_env_methods(cutoff): cell = np.eye(3) @@ -39,3 +39,43 @@ def test_env_methods(cutoff): assert np.array_equal(remade_env.bond_array_2, env_test.bond_array_2) assert np.array_equal(remade_env.bond_array_3, env_test.bond_array_3) assert np.array_equal(remade_env.bond_array_mb, env_test.bond_array_mb) + + +def test_auto_sweep(): + """Test that the number of neighbors inside the local environment is + correctly computed.""" + + # Make an arbitrary non-cubic structure. + cell = np.array([[1.3, 0.5, 0.8], + [-1.2, 1, 0.73], + [-0.8, 0.1, 0.9]]) + positions = np.array([[1.2, 0.7, 2.3], + [3.1, 2.5, 8.9], + [-1.8, -5.8, 3.0], + [0.2, 1.1, 2.1], + [3.2, 1.1, 3.3]]) + species = np.array([1, 2, 3, 4, 5]) + arbitrary_structure = Structure(cell, species, positions) + + # Construct an environment. + cutoffs = np.array([4., 3.]) + arbitrary_environment = \ + AtomicEnvironment(arbitrary_structure, 0, cutoffs) + + # Count the neighbors. + n_neighbors_1 = len(arbitrary_environment.etypes) + + # Reduce the sweep value, and check that neighbors are missing. + sweep_val = arbitrary_environment.sweep_val + arbitrary_environment.sweep_array = \ + np.arange(-sweep_val + 1, sweep_val, 1) + arbitrary_environment.compute_env() + n_neighbors_2 = len(arbitrary_environment.etypes) + assert(n_neighbors_1 > n_neighbors_2) + + # Increase the sweep value, and check that the count is the same. + arbitrary_environment.sweep_array = \ + np.arange(-sweep_val - 1, sweep_val + 2, 1) + arbitrary_environment.compute_env() + n_neighbors_3 = len(arbitrary_environment.etypes) + assert(n_neighbors_1 == n_neighbors_3) From 590913d8aef89f7d58c895795532de9fb33c3e8b Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 14:43:03 -0400 Subject: [PATCH 09/15] remove sweep as an argument to env --- flare/env.py | 2 +- flare/gp.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/flare/env.py b/flare/env.py index 18eb6697a..47fe4dcfc 100644 --- a/flare/env.py +++ b/flare/env.py @@ -21,7 +21,7 @@ class AtomicEnvironment: :type cutoffs: np.ndarray """ - def __init__(self, structure: Structure, atom: int, cutoffs, sweep=1): + def __init__(self, structure: Structure, atom: int, cutoffs): self.structure = structure self.positions = structure.wrapped_positions self.cell = structure.cell diff --git a/flare/gp.py b/flare/gp.py index 79c9d6c63..18a13a8c1 100644 --- a/flare/gp.py +++ b/flare/gp.py @@ -344,8 +344,7 @@ def check_instantiation(self): self.hyps_mask = None def update_db(self, struc: Structure, forces: List, - custom_range: List[int] = (), energy: float = None, - sweep: int = 1): + custom_range: List[int] = (), energy: float = None): """Given a structure and forces, add local environments from the structure to the training set of the GP. If energy is given, add the entire structure to the training set. @@ -370,7 +369,7 @@ def update_db(self, struc: Structure, forces: List, if forces is not None: for atom in update_indices: env_curr = \ - AtomicEnvironment(struc, atom, self.cutoffs, sweep=sweep) + AtomicEnvironment(struc, atom, self.cutoffs) forces_curr = np.array(forces[atom]) self.training_data.append(env_curr) @@ -386,7 +385,7 @@ def update_db(self, struc: Structure, forces: List, structure_list = [] # Populate with all environments of the struc for atom in range(noa): env_curr = \ - AtomicEnvironment(struc, atom, self.cutoffs, sweep=sweep) + AtomicEnvironment(struc, atom, self.cutoffs) structure_list.append(env_curr) self.energy_labels.append(energy) From 93f2af1c241766de99196f48848b26f1ae209618 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 19:25:25 -0400 Subject: [PATCH 10/15] clarify comment --- flare/struc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flare/struc.py b/flare/struc.py index 988960b2e..6a8326b87 100644 --- a/flare/struc.py +++ b/flare/struc.py @@ -60,7 +60,8 @@ def __init__(self, cell: 'ndarray', species: Union[List[str], List[int]], self.vec2 = self.cell[1, :] self.vec3 = self.cell[2, :] - # Compute the max cutoff for sweep = 1. + # Compute the max cutoff compatible with a 3x3x3 supercell of the + # structure. self.max_cutoff = self.get_max_cutoff() # get cell matrices for wrapping coordinates From 4dd8da8337fbecba0fc5dfe8dfa60ab779173353 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 19:27:04 -0400 Subject: [PATCH 11/15] change 2-body cutoff to max cutoff --- flare/env.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flare/env.py b/flare/env.py index 47fe4dcfc..58f06b02a 100644 --- a/flare/env.py +++ b/flare/env.py @@ -27,8 +27,8 @@ def __init__(self, structure: Structure, atom: int, cutoffs): self.cell = structure.cell self.species = structure.coded_species - # Set the sweep array based on the 2-body cutoff. - sweep_val = ceil(cutoffs[0] / structure.max_cutoff) + # Set the sweep array based on the max cutoff. + sweep_val = ceil(np.max(cutoffs) / structure.max_cutoff) self.sweep_val = sweep_val self.sweep_array = np.arange(-sweep_val, sweep_val + 1, 1) From 4005d2541f8bd3ccd9e159d180aefeec18de5907 Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 19:35:42 -0400 Subject: [PATCH 12/15] move get_max_cutoff to util --- flare/struc.py | 41 +------------------------------------- flare/util.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/flare/struc.py b/flare/struc.py index 6a8326b87..6461f82a8 100644 --- a/flare/struc.py +++ b/flare/struc.py @@ -10,6 +10,7 @@ import numpy as np from flare.util import element_to_Z, Z_to_element, NumpyEncoder from json import dumps, loads +from util import get_max_cutoff from typing import List, Union, Any @@ -131,46 +132,6 @@ def get_cell_dot(self): return cell_dot - def get_max_cutoff(self) -> float: - """Compute the maximum cutoff compatible with a 3x3x3 supercell of the - structure. - - Returns: - float: maximum cutoff - """ - # Retrieve the lattice vectors. - a_vec = self.cell[0] - b_vec = self.cell[1] - c_vec = self.cell[2] - - # Compute dot products and norms of lattice vectors. - a_dot_b = np.dot(a_vec, b_vec) - a_dot_c = np.dot(a_vec, c_vec) - b_dot_c = np.dot(b_vec, c_vec) - - a_norm = np.linalg.norm(a_vec) - b_norm = np.linalg.norm(b_vec) - c_norm = np.linalg.norm(c_vec) - - # Compute the six independent altitudes of the cell faces. - # The smallest is the maximum atomic environment cutoff that can be - # used with sweep=1. - max_candidates = np.zeros(6) - max_candidates[0] = \ - a_norm * np.sqrt(1 - (a_dot_b / (a_norm * b_norm))**2) - max_candidates[1] = \ - b_norm * np.sqrt(1 - (a_dot_b / (a_norm * b_norm))**2) - max_candidates[2] = \ - a_norm * np.sqrt(1 - (a_dot_c / (a_norm * c_norm))**2) - max_candidates[3] = \ - c_norm * np.sqrt(1 - (a_dot_c / (a_norm * c_norm))**2) - max_candidates[4] = \ - b_norm * np.sqrt(1 - (b_dot_c / (b_norm * c_norm))**2) - max_candidates[5] = \ - c_norm * np.sqrt(1 - (b_dot_c / (b_norm * c_norm))**2) - - return np.min(max_candidates) - @staticmethod def raw_to_relative(positions: 'ndarray', cell_transpose: 'ndarray', cell_dot_inverse: 'ndarray') -> 'ndarray': diff --git a/flare/util.py b/flare/util.py index f43b277e7..21a210ab9 100644 --- a/flare/util.py +++ b/flare/util.py @@ -584,7 +584,7 @@ def subset_of_frame_by_element(frame: 'flare.Structure', if len(matching_atoms) == 0: continue # Choose the atoms to add - to_add_atoms = np.random.choice(matching_atoms,replace=False, + to_add_atoms = np.random.choice(matching_atoms, replace=False, size=min(n, len(matching_atoms))) return_atoms += list(to_add_atoms) @@ -592,4 +592,53 @@ def subset_of_frame_by_element(frame: 'flare.Structure', return_atoms.sort() - return return_atoms \ No newline at end of file + return return_atoms + + +def get_max_cutoff(cell: np.ndarray) -> float: + """Compute the maximum cutoff compatible with a 3x3x3 supercell of a + structure. Called in the Structure constructor when + setting the max_cutoff attribute, which is used to create local + environments with arbitrarily large cutoff radii. + + Args: + cell (np.ndarray): Bravais lattice vectors of the structure stored as + rows of a 3x3 Numpy array. + + Returns: + float: Maximum cutoff compatible with a 3x3x3 supercell of the + structure. + """ + + # Retrieve the lattice vectors. + a_vec = cell[0] + b_vec = cell[1] + c_vec = cell[2] + + # Compute dot products and norms of lattice vectors. + a_dot_b = np.dot(a_vec, b_vec) + a_dot_c = np.dot(a_vec, c_vec) + b_dot_c = np.dot(b_vec, c_vec) + + a_norm = np.linalg.norm(a_vec) + b_norm = np.linalg.norm(b_vec) + c_norm = np.linalg.norm(c_vec) + + # Compute the six independent altitudes of the cell faces. + # The smallest is the maximum atomic environment cutoff that can be + # used with sweep=1. + max_candidates = np.zeros(6) + max_candidates[0] = \ + a_norm * np.sqrt(1 - (a_dot_b / (a_norm * b_norm))**2) + max_candidates[1] = \ + b_norm * np.sqrt(1 - (a_dot_b / (a_norm * b_norm))**2) + max_candidates[2] = \ + a_norm * np.sqrt(1 - (a_dot_c / (a_norm * c_norm))**2) + max_candidates[3] = \ + c_norm * np.sqrt(1 - (a_dot_c / (a_norm * c_norm))**2) + max_candidates[4] = \ + b_norm * np.sqrt(1 - (b_dot_c / (b_norm * c_norm))**2) + max_candidates[5] = \ + c_norm * np.sqrt(1 - (b_dot_c / (b_norm * c_norm))**2) + + return np.min(max_candidates) From d27cc5941340c382ae3b685bed89a6094c9e1cec Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 19:38:14 -0400 Subject: [PATCH 13/15] clean up util --- flare/util.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/flare/util.py b/flare/util.py index 21a210ab9..ce87bcaa5 100644 --- a/flare/util.py +++ b/flare/util.py @@ -19,15 +19,16 @@ def get_random_velocities(noa: int, temperature: float, mass: float): mass (float): Mass of each particle in amu. Returns: - np.ndarray: Particle velocities, corrected to give zero center of mass motion. + np.ndarray: Particle velocities, corrected to give zero center of mass + motion. """ - + # Use FLARE mass units (time = ps, length = A, energy = eV) mass_md = mass * 0.000103642695727 kb = 0.0000861733034 std = np.sqrt(kb * temperature / mass_md) velocities = np.random.normal(scale=std, size=(noa, 3)) - + # Remove center-of-mass motion vel_sum = np.sum(velocities, axis=0) corrected_velocities = velocities - vel_sum / noa @@ -44,7 +45,8 @@ def multicomponent_velocities(temperature: float, masses: List[float]): masses (List[float]): Particle masses in amu. Returns: - np.ndarray: Particle velocities, corrected to give zero center of mass motion. + np.ndarray: Particle velocities, corrected to give zero center of mass + motion. """ noa = len(masses) @@ -294,7 +296,8 @@ class NumpyEncoder(JSONEncoder): json.dumps(... cls = NumpyEncoder) - Thanks to StackOverflow users karlB and fnunnari, who contributed this from: + Thanks to StackOverflow users karlB and fnunnari, who contributed this + from: `https://stackoverflow.com/a/47626762` """ @@ -333,7 +336,7 @@ def Z_to_element(Z: int) -> str: def is_std_in_bound(std_tolerance: float, noise: float, structure: 'flare.struc.Structure', - max_atoms_added: int = inf)-> (bool, List[int]): + max_atoms_added: int = inf) -> (bool, List[int]): """ Given an uncertainty tolerance and a structure decorated with atoms, species, and associated uncertainties, return those which are above a @@ -383,7 +386,8 @@ def is_std_in_bound_per_species(rel_std_tolerance: float, abs_std_tolerance: float, noise: float, structure: 'flare.struc.Structure', max_atoms_added: int = inf, - max_by_species: dict = {})-> (bool, List[int]): + max_by_species: dict = {}) -> (bool, + List[int]): """ Checks the stds of GP prediction assigned to the structure, returns a list of atoms which either meet an absolute threshold or a relative @@ -473,9 +477,9 @@ def is_force_in_bound_per_species(abs_force_tolerance: float, label_forces: 'ndarray', structure, max_atoms_added: int = inf, - max_by_species: dict ={}, + max_by_species: dict = {}, max_force_error: float - = inf)-> (bool, List[int]): + = inf) -> (bool, List[int]): """ Checks the forces of GP prediction assigned to the structure against a DFT calculation, and return a list of atoms which meet an absolute @@ -532,7 +536,7 @@ def is_force_in_bound_per_species(abs_force_tolerance: float, # conclude if len(target_atoms) == max_atoms_added or \ (max_error_components[i] < abs_force_tolerance and - max_error_components[i] != np.nan): + max_error_components[i] != np.nan): break cur_spec = structure.species_labels[i] @@ -553,7 +557,7 @@ def is_force_in_bound_per_species(abs_force_tolerance: float, def subset_of_frame_by_element(frame: 'flare.Structure', - predict_atoms_per_element: dict)->List[int]: + predict_atoms_per_element: dict) -> List[int]: """ Given a structure and a dictionary formatted as {"Symbol":int, ..} describing a number of atoms per element, return a sorted list of From b17e5c971b46b48d5709159277405e8612e9fc7a Mon Sep 17 00:00:00 2001 From: Jonathan Vandermause Date: Tue, 19 May 2020 19:41:05 -0400 Subject: [PATCH 14/15] fix struc import --- flare/struc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flare/struc.py b/flare/struc.py index 6461f82a8..6f21b6b61 100644 --- a/flare/struc.py +++ b/flare/struc.py @@ -8,9 +8,9 @@ used to train ML models. """ import numpy as np -from flare.util import element_to_Z, Z_to_element, NumpyEncoder +from flare.util import element_to_Z, Z_to_element, NumpyEncoder, \ + get_max_cutoff from json import dumps, loads -from util import get_max_cutoff from typing import List, Union, Any @@ -63,7 +63,7 @@ def __init__(self, cell: 'ndarray', species: Union[List[str], List[int]], # Compute the max cutoff compatible with a 3x3x3 supercell of the # structure. - self.max_cutoff = self.get_max_cutoff() + self.max_cutoff = get_max_cutoff(self.cell) # get cell matrices for wrapping coordinates self.cell_transpose = self.cell.transpose() From 270012a29d41a5b700351ba52ae01c86af5fcea8 Mon Sep 17 00:00:00 2001 From: Steven Torrisi Date: Wed, 20 May 2020 09:19:45 -0400 Subject: [PATCH 15/15] Remove redundant try / except block --- flare/struc.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/flare/struc.py b/flare/struc.py index 6f21b6b61..2c27a189c 100644 --- a/flare/struc.py +++ b/flare/struc.py @@ -431,12 +431,11 @@ def from_file(file_name, format='') -> Union['flare.struc.Structure', :param format: :return: """ - - try: - with open(file_name, 'r') as _: - pass - except FileNotFoundError: - raise FileNotFoundError + + # Ensure the file specified exists. + with open(file_name, 'r') as _: + pass + if 'xyz' in file_name or 'xyz' in format.lower(): raise NotImplementedError