def find_optimal_cell_shape_in_range( cell, target_size, target_shape, size_range=None, optimize_sc=False, lower_limit=-2, upper_limit=2, sc_tolerance=1e-5, verbose=False, ): """ Returns the transformation matrix that produces a supercell corresponding to *target_size* unit cells with metric *cell* that most closely approximates the shape defined by *target_shape*. Parameters: cell: 2D array of floats Metric given as a (3x3 matrix) of the input structure. target_size: integer Size of desired super cell in number of unit cells. target_shape: str Desired supercell shape. Can be 'sc' for simple cubic or 'fcc' for face-centered cubic. size_range: None/int/float/list/tuple The range of the target_size if None, 80%-120% target_size elif int/float < 1 (1-size_range, 1+size_range) * target_size = 1 == target_size > 1 (-int(size_range), int(size_range)) + target_size elif list/tuple (2 elements) [size_range(0), size_range(1)] optimize_sc: bool Optimize the super cell matrix (True) or not (False) If False, then use the closest integer transformation matrix of ideal matrix lower_limit: int Lower limit of search range. upper_limit: int Upper limit of search range. sc_tolerance: float The tolerance for the search. If the score is less than sc_tolerance, stop searching verbose: bool Set to True to obtain additional information regarding construction of transformation matrix. Return numpy.ndarray 2d array of a scaling matrix, e.g. [[3,0,0],[0,3,0],[0,0,3]] Note: this function taken from ase(ase.build.find_optimal_cell_shape), and we made some improvements (add sc_tolerance and set the ideal_matrix as the first loop, which will speed up the code for high symmetry add size_range, which will allow search in a range) """ # Set up target metric if target_shape in ["sc", "simple-cubic"]: target_metric = np.eye(3) elif target_shape in ["fcc", "face-centered cubic"]: target_metric = 0.5 * np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=float) if verbose: print("target metric (h_target):") print(target_metric) if size_range is None: min_size = int(target_size * 0.8) max_size = math.ceil(target_size * 1.2) elif isinstance(size_range, (float, int)): if size_range < 1: min_size = int(target_size * (1 - size_range)) max_size = math.ceil(target_size * (1 + size_range)) elif size_range == 1: min_size = target_size max_size = target_size else: min_size = target_size - int(size_range) max_size = target_size + math.ceil(size_range) elif isinstance(size_range, (list, tuple)): min_size = int(size_range[0]) max_size = math.ceil(size_range[1]) else: raise ValueError('Unsupported size range.') # Normalize cell metric to reduce computation time during looping norm = (target_size * np.linalg.det(cell) / np.linalg.det(target_metric))**(-1.0 / 3) norm_cell = norm * cell if verbose: print("normalization factor (Q): %g" % norm) # Approximate initial P matrix ideal_P = np.dot(target_metric, np.linalg.inv(norm_cell)) if verbose: print("idealized transformation matrix:") print(ideal_P) starting_P = np.array(np.around(ideal_P, 0), dtype=int) if verbose: print("closest integer transformation matrix (P_0):") print(starting_P) if optimize_sc: # Prepare run. best_score = 1e6 optimal_P = None #Set the starting_P as the first one dPlist = list( itertools.product(range(lower_limit, upper_limit + 1), repeat=9)) dP0 = (0, 0, 0, 0, 0, 0, 0, 0, 0) dPlist.pop(dPlist.index(dP0)) dPlist = [dP0] + dPlist for dP in dPlist: dP = np.array(dP, dtype=int).reshape(3, 3) P = starting_P + dP New_size = np.around(np.linalg.det(P)) if New_size < min_size or New_size > max_size: continue norm_new = get_norm_cell(cell, New_size, target_shape=target_shape) score = get_deviation_from_optimal_cell_shape( np.dot(P, norm_new), target_shape=target_shape, norm=1.0) if score < best_score: best_score = score optimal_P = P if best_score < sc_tolerance: break if optimal_P is None: optimal_P = starting_P print( "Failed to find a transformation matrix, using the ideal one.") # Finalize. if verbose: print("smallest score (|Q P h_p - h_target|_2): %f" % best_score) print("optimal transformation matrix (P_opt):") print(optimal_P) print("supercell metric:") print(np.round(np.dot(optimal_P, cell), 4)) print("determinant of optimal transformation matrix: %g" % np.linalg.det(optimal_P)) else: optimal_P = starting_P return optimal_P
def supercell_scaling_by_atom_lat_vol(structure, min_obj=60, max_obj=120, scale_object='atom', optimize_sc=False, target_shape='sc', lower_search_limit=-2, upper_search_limit=2, verbose=False, sc_tolerance=1e-5): """ Find a the supercell scaling matrix that gives the most cubic supercell for a structure, where the supercell has between the minimum and maximum nubmer of object(atom/lattice/volume). Parameters ---------- structure : pymatgen.Structure Unitcell of a structure scale_object: str control the scale object, atom or lattice or volume (only first letter matters) min_obj/max_obj : int/float minimum/maximum atoms/lattice/volume(controlled by scale_object) target_shape : str Target shape of supercell. Could choose 'sc' for simple cubic or 'fcc' for face centered cubic. Default is 'sc'. lower_search_limit : int How far to search below the 'ideal' cubic scaling. Default is -2. upper_search_limit : int How far to search below the 'ideal' cubic scaling. Default is 2. verbose : bool Whether to print extra details on the cell shapes and scores. Useful for debugging. sc_tolerance: float The tolerance for the search. If the score is less than sc_tolerance, stop searching Returns ------- numpy.ndarray 2d array of a scaling matrix, e.g. [[3,0,0],[0,3,0],[0,0,3]] Notes ----- The motiviation for this is for use in phonon calculations and defect calculations. It is important that defect atoms are far enough apart that they do not interact. Scaling unit cells that are not cubic by even dimensions might result in interacting defects. An example would be a tetragonal cell with 2x8x8 Ang lattice vectors being made into a 2x2x2 supercell. Atoms along the first dimension would not be very far apart. We are using a pure Python implementation from ASE, which is not very fast for a given supercell size. This allows for a variable supercell size, so it's going to be slow for a large range of atoms. (TODO: The performance is improved, but still can be faster) The search limits are passed directloy to ``find_optimal_cell_shape``. They define the search space for each individual supercell based on the "ideal" scaling. For example, a cell with 4 atoms and a target size of 110 atoms might have an ideal scaling of 3x3x3. The search space for a lower and upper limit of -2/+2 would be 1-5. Since the calculations are based on the cartesian product of 3x3 matrices, large search ranges are very expensive. """ #from ase.build import get_deviation_from_optimal_cell_shape # range of supercell sizes in number of unitcells scale_object = scale_object.lower() if scale_object.startswith('a'): unit_obj = len(structure) elif scale_object.startswith('l'): unit_obj = structure.volume min_obj = min_obj**3 max_obj = max_obj**3 elif scale_object.startswith('v'): unit_obj = structure.volume else: raise ValueError( 'Unsupported scale object, please choose atom or lattice or volume.' ) size_range = [int(min_obj / unit_obj), math.ceil(max_obj / unit_obj)] optimal_supercell_shapes = [] # numpy arrays of optimal shapes optimal_supercell_scores = [] # will correspond to supercell size supercell_sizes_out = [] # find the target shapes for sc_size in range(size_range[0], size_range[1]): optimal_shape = find_optimal_cell_shape_in_range( structure.lattice.matrix, sc_size, target_shape, size_range=size_range, upper_limit=upper_search_limit, lower_limit=lower_search_limit, verbose=True, sc_tolerance=sc_tolerance, optimize_sc=optimize_sc) optimal_supercell_shapes.append(optimal_shape) norm_cell = get_norm_cell(structure.lattice.matrix, sc_size, target_shape=target_shape) scores = get_deviation_from_optimal_cell_shape( np.dot(optimal_shape, norm_cell), target_shape) optimal_supercell_scores.append(scores) supercell_sizes_out.append(sc_size) if scores < sc_tolerance: break if verbose: for i in range(len(optimal_supercell_shapes)): print('{} {:0.4f} {}'.format(supercell_sizes_out[i], optimal_supercell_scores[i], optimal_supercell_shapes[i].tolist())) # find the most optimal cell shape along the range of sizes optimal_sc_shape = optimal_supercell_shapes[np.argmin( optimal_supercell_scores)] return optimal_sc_shape
def supercell_scaling_by_target_atoms(structure, min_atoms=60, max_atoms=120, target_shape='sc', lower_search_limit=-2, upper_search_limit=2, verbose=False): """ Find a the supercell scaling matrix that gives the most cubic supercell for a structure, where the supercell has between the minimum and maximum nubmer of atoms. Parameters ---------- structure : pymatgen.Structure Unitcell of a structure min_atoms : target number of atoms in the supercell, defaults to 5 max_atoms : int Maximum number of atoms allowed in the supercell target_shape : str Target shape of supercell. Could choose 'sc' for simple cubic or 'fcc' for face centered cubic. Default is 'sc'. lower_search_limit : int How far to search below the 'ideal' cubic scaling. Default is -2. upper_search_limit : int How far to search below the 'ideal' cubic scaling. Default is 2. verbose : bool Whether to print extra details on the cell shapes and scores. Useful for debugging. Returns ------- numpy.ndarray 2d array of a scaling matrix, e.g. [[3,0,0],[0,3,0],[0,0,3]] Notes ----- The motiviation for this is for use in phonon calculations and defect calculations. It is important that defect atoms are far enough apart that they do not interact. Scaling unit cells that are not cubic by even dimensions might result in interacting defects. An example would be a tetragonal cell with 2x8x8 Ang lattice vectors being made into a 2x2x2 supercell. Atoms along the first dimension would not be very far apart. We are using a pure Python implementation from ASE, which is not very fast for a given supercell size. This allows for a variable supercell size, so it's going to be slow for a large range of atoms. The search limits are passed directloy to ``find_optimal_cell_shape``. They define the search space for each individual supercell based on the "ideal" scaling. For example, a cell with 4 atoms and a target size of 110 atoms might have an ideal scaling of 3x3x3. The search space for a lower and upper limit of -2/+2 would be 1-5. Since the calculations are based on the cartesian product of 3x3 matrices, large search ranges are very expensive. """ from ase.build import get_deviation_from_optimal_cell_shape, find_optimal_cell_shape # range of supercell sizes in number of unitcells supercell_sizes = range(min_atoms // len(structure), max_atoms // len(structure) + 1) optimal_supercell_shapes = [] # numpy arrays of optimal shapes optimal_supercell_scores = [] # will correspond to supercell size # find the target shapes for sc_size in supercell_sizes: optimal_shape = find_optimal_cell_shape(structure.lattice.matrix, sc_size, target_shape, upper_limit=upper_search_limit, lower_limit=lower_search_limit, verbose=True) optimal_supercell_shapes.append(optimal_shape) optimal_supercell_scores.append( get_deviation_from_optimal_cell_shape(optimal_shape, target_shape)) if verbose: for i in range(len(supercell_sizes)): print('{} {:0.4f} {}'.format(supercell_sizes[i], optimal_supercell_scores[i], optimal_supercell_shapes[i].tolist())) # find the most optimal cell shape along the range of sizes optimal_sc_shape = optimal_supercell_shapes[np.argmin( optimal_supercell_scores)] return optimal_sc_shape