def get_basis_indices(self): """Returns the indices of the atoms that were found to belong to a unit cell basis in the LinkedUnits in this collection as a single list. Returns: np.ndarray: Indices of the atoms in the original system that belong to this collection of LinkedUnits. """ if self._basis_indices is None: with chronic.Timer("chemical_environment_translations"): translations, translations_reduced = self.get_chem_env_translations( ) # For each atom in the basis check the chemical environment with chronic.Timer("chemical_environment_basis"): neighbour_map = self.get_basis_atom_neighbourhood() with chronic.Timer("chemical_environment_units"): indices = set() for unit in self.values(): # Compare the chemical environment near this atom to the one # that is present in the prototype cell. If these # neighbourhoods are too different, then the atom is not # counted as being a part of the region. for i_index, index in enumerate(unit.basis_indices): if index is not None: real_environment = self.get_chemical_environment( self.system, index, self.disp_tensor_finite, translations, translations_reduced) ideal_environment = neighbour_map[i_index] chem_similarity = self.get_chemical_similarity( ideal_environment, real_environment) if chem_similarity >= self.chem_similarity_threshold: indices.add(index) # Ensure that all the basis atoms belong to the same cluster. # clusters = self.get_clusters() self._basis_indices = np.array(list(indices)) return self._basis_indices
def test_concurrent(self): w = GeventWorker(self.create_queue_with_two_jobs(), pool_size=2) with chronic.Timer('default'): w.work(burst=True) self.assertLess(chronic.timings['default']['total_elapsed'], 1.5)
def test_sequence(self): w = GeventWorker(self.create_queue_with_two_jobs(), pool_size=1) with chronic.Timer('default'): w.work(burst=True) self.assertGreater(chronic.timings['default']['total_elapsed'], 2.0)
def classify(self, input_system): """A function that analyzes the system and breaks it into different components. Args: system(ASE.Atoms or System): Atomic system to classify. Returns: Classification: One of the subclasses of the Classification base class that represents a classification. Raises: ValueError: If the system has more atoms than self.max_n_atoms """ # We wrap the positions to to be inside the cell. system = input_system.copy() system.wrap() self.system = system classification = None n_atoms = len(system) if n_atoms > self.max_n_atoms: raise ValueError( "The system contains more atoms ({}) than the current allowed " "limit of {}. If you wish you can increase this limit with the " "max_n_atoms attribute.".format(n_atoms, self.max_n_atoms) ) # Calculate the displacement tensor for the original system. It will be # reused in multiple sections. pos = system.get_positions() cell = system.get_cell() pbc = system.get_pbc() with chronic.Timer("displacement_tensor"): disp_tensor = matid.geometry.get_displacement_tensor(pos, pos) if pbc.any(): disp_tensor_pbc, disp_factors = matid.geometry.get_displacement_tensor( pos, pos, cell, pbc, mic=True, return_factors=True ) else: disp_tensor_pbc = disp_tensor disp_factors = np.zeros(disp_tensor.shape) dist_matrix_pbc = np.linalg.norm(disp_tensor_pbc, axis=2) # Calculate the distance matrix where the periodicity and the covalent # radii have been taken into account dist_matrix_radii_pbc = np.array(dist_matrix_pbc) num = system.get_atomic_numbers() radii = covalent_radii[num] radii_matrix = radii[:, None] + radii[None, :] dist_matrix_radii_pbc -= radii_matrix # If pos_tol_mode or delaunay_threshold_mode is relative, get the # average distance to closest neighbours if self.pos_tol_mode == "relative" or self.delaunay_threshold_mode == "relative": min_basis = np.linalg.norm(cell, axis=1).min() dist_matrix_mod = np.array(dist_matrix_pbc) np.fill_diagonal(dist_matrix_mod, min_basis) global_min_dist = dist_matrix_mod.min() min_dist = np.min(dist_matrix_mod, axis=1) mean_min_dist = min_dist.mean() if self.pos_tol_mode == "relative": self.abs_pos_tol = np.array(self.pos_tol)*global_min_dist elif self.pos_tol_mode == "absolute": self.abs_pos_tol = self.pos_tol if self.delaunay_threshold_mode == "relative": self.abs_delaunay_threshold = self.delaunay_threshold * mean_min_dist elif self.delaunay_threshold_mode == "absolute": self.abs_delaunay_threshold = self.delaunay_threshold # Get the system dimensionality with chronic.Timer("TSA"): dimensionality = matid.geometry.get_dimensionality( system, self.cluster_threshold, dist_matrix_radii_pbc ) if dimensionality is None: return Unknown(input_system) # 0D structures if dimensionality == 0: classification = Class0D(input_system) # Systems with one atom have their own classification. n_atoms = len(system) if n_atoms == 1: classification = Atom(input_system) # 1D structures elif dimensionality == 1: classification = Class1D(input_system) # 2D structures elif dimensionality == 2: classification = Class2D(input_system) # Get the indices of the used seed atoms seed_indices = [] test_sys = system.copy() cm = matid.geometry.get_center_of_mass(test_sys) # If center of mass defined, for each atomic element find the # occurrence closest to center of mass to use as seed point. num = self.system.get_atomic_numbers() elems = set(num) if self.seed_position == "cm": distances = np.linalg.norm(system.get_positions() - cm, axis=1) indices = np.argsort(distances) for i in indices: i_elem = num[i] if i_elem in elems: seed_indices.append(i) elems.remove(i_elem) if len(elems) == 0: break else: if type(self.seed_position) == int: seed_indices = [self.seed_position] elif isinstance(self.seed_position, (tuple, list, np.ndarray)): seed_indices = self.seed_position # Find the best region by trying out different parameters options with chronic.Timer("cross_validation"): best_region = self.cross_validate_region( system, seed_indices, disp_tensor_pbc, disp_factors, disp_tensor, dist_matrix_radii_pbc ) if best_region is not None: with chronic.Timer("region_analysis"): # Check that the region was connected cyclically in two # directions. This ensures that finite systems or systems # with a dislocation at the cell boundary are filtered. region_conn = best_region.get_connected_directions() n_region_conn = np.sum(region_conn) region_is_periodic = n_region_conn == 2 # cell_statistically_valid = best_region.get_cell_statistically_valid() # print(cell_statistically_valid) # This might be unnecessary because the connectivity of the # unit cell is already checked. clusters = best_region.get_clusters() basis_indices = set(list(best_region.get_basis_indices())) split = True for cluster in clusters: if basis_indices.issubset(cluster): split = False # Check that the found region covers enough of the entire # system. If not, then the region alone cannot be used to # classify the entire structure. This happens e.g. when one # 2D sheet is found from a 2D heterostructure, or a local # pattern is found inside a structure. n_atoms = len(system) n_basis_atoms = len(basis_indices) coverage = n_basis_atoms/n_atoms covered = coverage >= self.min_coverage if not split and covered and region_is_periodic: if best_region.is_2d: classification = Material2D(input_system, best_region) else: classification = Surface(input_system, best_region) # Bulk structures elif dimensionality == 3: classification = Class3D(input_system) # Check the number of symmetries # analyzer = SymmetryAnalyzer(system) # crystallinity = matid.geometry.get_crystallinity(analyzer) # is_crystal = crystallinity >= self.crystallinity_threshold # If the structure is connected but the symmetry criteria was # not fullfilled, check the number of atoms in the primitive # cell. If above a certain threshold, try to find periodic # region to see if it is a crystal containing a defect. # if not is_crystal: # pass # This section is currently disabled. Can be reenabled once # more extensive testing is carried out on the detection of # defects in crystals. # primitive_system = analyzer.get_primitive_system() # n_atoms_prim = len(primitive_system) # if n_atoms_prim >= 20: # periodicfinder = PeriodicFinder( # pos_tol=self.abs_pos_tol, # angle_tol=self.angle_tol, # max_cell_size=self.max_cell_size, # pos_tol_factor=self.pos_tol_factor, # cell_size_tol=self.cell_size_tol, # ) # # Get the index of the seed atom # if self.seed_position == "cm": # seed_vec = self.system.get_center_of_mass() # else: # seed_vec = self.seed_position # seed_index = matid.geometry.get_nearest_atom(self.system, seed_vec) # region = periodicfinder.get_region(system, seed_index, disp_tensor_pbc, disp_tensor, self.abs_delaunay_threshold) # if region is not None: # region = region[1] # # If all the regions cover at least 80% of the structure, # # then we consider it to be a defected crystal # n_region_atoms = len(region.get_basis_indices()) # n_atoms = len(system) # coverage = n_region_atoms/n_atoms # if coverage >= self.coverage_threshold: # classification = Crystal(analyzer, region=region) # elif is_crystal: # classification = Crystal(analyzer) return classification