def _set_properties(self): """ Set properties. """ cry_lat = CrystalLattice(lattice=self._lattice) self._reciprocal_lattice = cry_lat.reciprocal_lattice recip_cry_lat = CrystalLattice(lattice=self._reciprocal_lattice) self._reciprocal_abc = recip_cry_lat.abc self._reciprocal_volume = recip_cry_lat.volume try: check_hexagonal_lattice(self._lattice) self._is_hexagonal = True except AssertionError: pass
def _test_expansion(): wyckoff = 'c' lattice, _, symbols = ti_cell_wyckoff_c shear_strain_ratio = 0.3 twinmode = '11-21' expansion_ratios = np.array([1.1, 0.9, 1.2]) shear_orig = ShearStructure( lattice=lattice, symbol=symbols[0], shear_strain_ratio=shear_strain_ratio, twinmode=twinmode, wyckoff=wyckoff, ) shear_expand = ShearStructure( lattice=lattice, symbol=symbols[0], shear_strain_ratio=shear_strain_ratio, twinmode=twinmode, wyckoff=wyckoff, ) shear_lattices = [] shear_expand.set_expansion_ratios(expansion_ratios=expansion_ratios) for shr in [shear_orig, shear_expand]: shr.run(is_primitive=False) shear_lattice = shr.get_cell_for_export( get_lattice=False, move_atoms_into_unitcell=True)[0] shear_lattices.append(shear_lattice) shear_lattice_expand = CrystalLattice( lattice=shear_lattices[0]).get_expanded_lattice( expansion_ratios=expansion_ratios) np.testing.assert_allclose(shear_lattice_expand, shear_lattices[1])
def get_neighbors(cell: tuple, idx: int, distance_cutoff: float, get_distances: bool = False) -> list: """ Get neighboring atoms from idx th atom. Args: cell (tuple): cell idx (int): index of specific atom distance_cutoff (float): distance cutoff get_distances (bool): if True, return also distances Returns: list: List of neighboring atoms. Each data contains atom_index in the first element and periorics in the remaining elements. If get_distances=True, this function also returns distances. """ lattice = CrystalLattice(cell[0]) periodics = np.floor(distance_cutoff / lattice.abc).astype(int) # round up neighbors = [] distances = [] for i, posi in enumerate(cell[1]): grids = [[i for i in range(-(x + 1), (x + 2))] for x in periodics] for l, m, n in itertools.product(*grids): if i == idx and [l, m, n] == [0, 0, 0]: continue distance = lattice.get_distance(cell[1][idx], posi + np.array([l, m, n])) if distance < distance_cutoff: neighbors.append([i, l, m, n]) distances.append(distance) # sort sort_idx = np.argsort(distances) distances = list(np.array(distances)[sort_idx]) neighbors = list(np.array(neighbors)[sort_idx]) neighbors = list(map(tuple, neighbors)) if get_distances: return (neighbors, distances) return neighbors
def get_relaxplot(self, start_step:int=1) -> RelaxPlot: """ Get RelaxPlot class object. Args: start_step: The step number of the first relax in this WorkChain. If you relax 20 steps in the privious RelaxWorkChain, for example, start_step becomes 21. Returns: RelaxPlot: RelaxPlot class object. """ relax_vasps, static_vasp = self.get_vasp_calculations() relax_data = {} relax_data['max_force'] = \ np.array([ relax_vasp.get_max_force() for relax_vasp in relax_vasps ]) # stress xx yy zz yz zx xy relax_data['stress'] = \ np.array([ relax_vasp.stress.flatten()[[0,4,8,5,6,1]] for relax_vasp in relax_vasps ]) relax_data['energy'] = \ np.array([ relax_vasp.energy for relax_vasp in relax_vasps ]) relax_data['abc'] = \ np.array([ CrystalLattice(relax_vasp.final_cell[0]).abc for relax_vasp in relax_vasps ]) relax_data['step_energies_collection'] = \ [ relax_vasp.step_energies for relax_vasp in relax_vasps ] if static_vasp is None: static_data = None else: static_data = { 'max_force': static_vasp.get_max_force(), 'stress': static_vasp.stress.flatten()[[0,4,8,5,6,1]] , 'energy': static_vasp.energy, 'abc': CrystalLattice(static_vasp.initial_cell[0]).abc, } relax_plot = RelaxPlot(relax_data=relax_data, static_data=static_data, start_step=start_step) return relax_plot
def _set_shear_strain_ratio_Rodney(): """ Set shear strain ratio based on Rodney. """ self._set_lattice(shear_strain_ratio=0.) # initialize crylat = CrystalLattice(lattice=self._lattice) _, b, c = crylat.abc l = b / self._b_replicate * step_range burg = self._burg_vec[1] s = burg * l / (2 * b * c) self._shear_strain_ratio = s self._set_lattice(self._shear_strain_ratio)
def _set_shear_strain_ratio_half(): """ Set shear strain ratio based on half. """ self._set_lattice(shear_strain_ratio=0.) # initialize crylat = CrystalLattice(lattice=self._lattice) _, b, c = crylat.abc interval = c / ((self._twinboundary.layers + 1) * 2) print(c) ratio = interval / c self._shear_strain_ratio = ratio print(ratio) self._set_lattice(ratio)
def write_thermal_ellipsoid(cell: tuple, matrices: np.array, temperatures: list, filetype: str = 'CrystalMaker', header: str = ''): """ Write thermal ellipsoid. Args: cell: (lattice, scaled_positions, symbols). matrices: Thermal ellipsoid. temperatures: Temperature list. filetype: Currently only 'CrystalMaker' is supported. header: Header of filename. """ if filetype != 'CrystalMaker': raise ValueError("Only filetype='CrystalMaker' is supported.") lines = [] lattice = CrystalLattice(cell[0]) abc = lattice.abc abc_str = ' '.join(map(str, list(abc))) angles = lattice.angles angles_str = ' '.join(map(str, list(angles))) cell_str = "CELL {} {}".format(abc_str, angles_str) lines.append(cell_str) lines.append("") lines.append("ATOM") # crystal maker => xx yy zz xy xz yz tensor_idx = [[0, 0], [1, 1], [2, 2], [0, 1], [0, 2], [1, 2]] for i in range(len(cell[2])): frac_str = ' '.join(map(str, list(cell[1][i]))) lines.append("{} {} {}".format(cell[2][i], cell[2][i] + str(i + 1), frac_str)) lines.append("") lines.append("UANI") for i, temp in enumerate(temperatures): temp_lines = deepcopy(lines) for j in range(len(cell[2])): mat = np.round(matrices[i, j], decimals=4) tensor = [str(mat.item(*idx)) for idx in tensor_idx] tensor_str = ' '.join(tensor) temp_lines.append("{} {}".format(cell[2][j] + str(1 + j), tensor_str)) strings = '\n'.join(temp_lines) filename = header + "temp{}K.cmtx".format(temp) with open(filename, 'w') as f: f.write(strings)
def __init__( self, lattice: np.array, symbol: str, twinmode: str, wyckoff: str = 'c', ): """ Setup. Args: lattice: Lattice. symbol: Element symbol. twinmode: Twin mode. wyckoff: No.194 Wycoff letter ('c' or 'd'). Todo: Check it is best to use 'deepcopy'. """ atoms_from_lp = get_hcp_atom_positions(wyckoff) symbols = [symbol] * 2 crylat = CrystalLattice(lattice=lattice) check_cell_is_hcp(cell=(lattice, atoms_from_lp, symbols)) self._hexagonal_lattice = lattice self._a, _, self._c = crylat.abc self._r = self._c / self._a self._symbol = symbol self._wyckoff = wyckoff self._atoms_from_lattice_points = \ get_hcp_atom_positions(wyckoff=self._wyckoff) self._natoms = 2 self._twinmode = None self._indices = None self._set_twinmode(twinmode=twinmode) self._xshift = None self._yshift = None self._expansion_ratios = np.ones(3) self._output_structure = \ {'lattice': self._hexagonal_lattice, 'lattice_points': { 'white': np.array([0.,0.,0.])}, 'atoms_from_lattice_points': { 'white': self._atoms_from_lattice_points}, 'symbols': [self._symbol] * 2}
def _set_shear_strain_ratios(self): """ Set shear strain ratios. """ hex_lat = \ self._aiida_twinboundary_relax.cells['hexagonal'][0] crylat = CrystalLattice(hex_lat) a, _, c = crylat.abc r = c / a twinmode = self._aiida_twinboundary_relax.twinboundary_structure.twinmode func = get_shear_strain_function(twinmode) gamma = func(r) conf = self._node.inputs.twinboundary_shear_conf.get_dict() if conf['is_ratio']: self._shear_strain_ratios = conf['shear_strain'] self._shear_strain = \ [ ratio * gamma for ratio in conf['shear_strain'] ] else: self._shear_strain = conf['shear_strain'] self._shear_strain_ratios = \ [ s / gamma for s in conf['shear_strain'] ]
def check_hexagonal_lattice(lattice: np.array): """ Check input lattice is hexagonal lattice. Args: lattice: Lattice matrix. Raises: AssertionError: The angles are not (90, 90, 120). Note: Check the angles of input lattice are (90, 90, 120). """ hexagonal = CrystalLattice(lattice) expected = np.array([90., 90., 120.]) actual = hexagonal.angles err_msg = "The angles of lattice was {}, \ which does not match [90., 90., 120.]".format(actual) np.testing.assert_allclose( actual, expected, err_msg=err_msg, )
def _get_atomic_environment(cell: tuple, layer_indices: list) -> tuple: """ Get plane coords from lower plane to upper plane. Return list of z coordinates of original cell frame. Plane coordinates (z coordinates) are fractional. Args: cell (np.array): (lattice, scaled_positions, symbols). layer_indices (list): List of layer indices. Returns: tuple: (planes, distances, angles) """ lattice = CrystalLattice(cell[0]) angles = lattice.angles np.testing.assert_allclose( angles[1:], [90., 90.], err_msg="Angles of lattice is {}. " "Angle beta and gamma must be 90 degree.".format(angles)) sine = lattice.sin_angles[0] plane_z_coords = [] distances = [] previous_z_coord = 0. angles = [] pair_distances = [] c_norm = np.linalg.norm(cell[0], axis=1)[2] for i, indices in enumerate(layer_indices): pair_atoms = cell[1][indices, :] pair_diff = lattice.get_diff(first_coord=pair_atoms[0], second_coord=pair_atoms[1], is_cartesian=False, with_periodic=True) pair_distance = lattice.get_norm(coord=pair_diff, is_cartesian=False, with_periodic=True) pair_distances.append(pair_distance) lattice_point = lattice.get_midpoint( first_coord=pair_atoms[0], second_coord=pair_atoms[1], with_periodic=True, ) if i == 0: if lattice_point[2] > 0.9: lattice_point[2] = lattice_point[2] - 1. elif i == len(layer_indices) - 1: if lattice_point[2] < 0.1: lattice_point[2] = lattice_point[2] + 1. plane_z_coord = c_norm * lattice_point[2] * sine plane_z_coords.append(plane_z_coord) if i > 0: d = plane_z_coord - previous_z_coord distances.append(d) previous_z_coord = plane_z_coord # # angles diff = lattice.get_diff( first_coord=pair_atoms[0], second_coord=pair_atoms[1], is_cartesian=False, with_periodic=True, ) diff_cart = np.dot(lattice.lattice.T, diff) cos = diff_cart[1] / np.linalg.norm(diff_cart) angle = np.arccos(cos) * 180 / np.pi % 180 angles.append(angle) angles = np.array(angles) angles = np.where(angles > 90., angles - 180, angles) angles = np.abs(angles) distances.append(lattice.abc[2] * sine - plane_z_coords[-1]) return (list(plane_z_coords), list(distances), list(angles), pair_distances)
def write_crystal_maker( modulation, multiply: float = 1., color_sets=[1, 0, 0], dirname='.', origin_shift=[0, 0, 0], is_minus=False, ): import os from twinpy.structure.lattice import CrystalLattice import itertools base_cell = get_cell_from_phonopy_structure( modulation._modulation._primitive) super_cell = \ get_cell_from_phonopy_structure(modulation._modulation._supercell) supercell_mat = modulation._modulation._supercell.supercell_matrix.diagonal( ) for j, disp in enumerate(modulation._displacements): filename = os.path.join(dirname, '%d.cmtx' % j) cell = super_cell lines = [] lattice = CrystalLattice(cell[0]) abc = lattice.abc abc_str = ' '.join(map(str, list(abc))) angles = lattice.angles angles_str = ' '.join(map(str, list(angles))) cell_str = "CELL {} {}".format(abc_str, angles_str) lines.append(cell_str) lines.append("") cell_range = [0, 1, 0, 1, 0, 1] cell_range_str = ' '.join(map(str, cell_range)) lines.append("XYZR %s" % cell_range_str) lines.append("") lines.append("ATOM") lines.append("") for i in range(len(cell[2])): frac_str = ' '.join( map(str, list((cell[1][i] + np.array(origin_shift)) % 1))) lines.append("{} {} {}".format(cell[2][i], cell[2][i] + str(i + 1), frac_str)) lines.append("") lines.append("! Atom vectors") lines.append("AVEC") color_str = ' '.join(map(str, list(color_sets))) norms = [] for i in range(len(cell[2])): amplitude = modulation._phonon_modes[j][2] frac_str = ' '.join( map(str, list((cell[1][i] + np.array(origin_shift)) % 1))) egn = np.linalg.norm(disp[i].real) eigen_str = ' '.join( map(str, list(np.round(disp[i].real / egn, decimals=6)))) norm = np.linalg.norm(disp[i].real) * multiply norms.append(norm) norm_str = str(norm) # 1 0 0 => red, 1 => vector style lines.append("{} {} {} {} {} 1".format(cell[2][i] + str(i + 1), frac_str, eigen_str, norm_str, color_str)) strings = '\n'.join(lines) print("Max norm: {}".format(max(norms))) with open(filename, 'w') as f: f.write(strings)
def __init__( self, lattice: np.array, twinmode: str, wyckoff: str, ): """ Get twin indices of input twinmode. Twinmode must be '10-11', '10-12', '11-21' or '11-22'. Args: lattice: Hexagonal lattice matrix. twinmode: Currently supported \ '10-11', '10-12', '11-22' and '11-21'. wyckoff: Wyckoff letter, 'c' or 'd'. Returns: dict: Twin indices. Note: You can find customs how to set the four indices in the paper 'DEFORMATION TWINNING' by Christian (1995). Abstruct is as bellow 1. Vector m normal to shear plane, eta1 and eta2 form right hand system. 2. The angle between eta1 and eta2 are abtuse. 3. The angle between eta1 and k2 are acute. 4. The angle between eta2 and k1 are acute. Additional rule: 5. K1 plane depends on wyckoff 'c' or 'd' because in the case of {10-12}, either (10-12) or (-1012) is chosen based on which plane are nearer to the neighbor atoms In this algorithm, indices are set in order of a. k1 condition(5) b. eta2 condition(4) c. eta1 condition(2) d. k2 condition(3) e. m condition(1) Todo: _twin_indices_orig is a little bit different from the ones 1981. Yoo especially 'S'. """ check_hexagonal_lattice(lattice) check_supported_twinmode(twinmode) self._lattice = lattice self._crylat = CrystalLattice(self._lattice) self._twinmode = twinmode self._layers = get_number_of_layers(twinmode) self._atom_num_per_layer = get_number_of_atoms_per_layer(twinmode) self._wyckoff = wyckoff self._indices_Yoo = self._get_indices_Yoo() self._indices = deepcopy(self._indices_Yoo) self._set_K1() self._set_k1_k2() self._reset_indices() self._set_shear_plane()
class TwinIndices(): """ Deals with twin indices. """ def __init__( self, lattice: np.array, twinmode: str, wyckoff: str, ): """ Get twin indices of input twinmode. Twinmode must be '10-11', '10-12', '11-21' or '11-22'. Args: lattice: Hexagonal lattice matrix. twinmode: Currently supported \ '10-11', '10-12', '11-22' and '11-21'. wyckoff: Wyckoff letter, 'c' or 'd'. Returns: dict: Twin indices. Note: You can find customs how to set the four indices in the paper 'DEFORMATION TWINNING' by Christian (1995). Abstruct is as bellow 1. Vector m normal to shear plane, eta1 and eta2 form right hand system. 2. The angle between eta1 and eta2 are abtuse. 3. The angle between eta1 and k2 are acute. 4. The angle between eta2 and k1 are acute. Additional rule: 5. K1 plane depends on wyckoff 'c' or 'd' because in the case of {10-12}, either (10-12) or (-1012) is chosen based on which plane are nearer to the neighbor atoms In this algorithm, indices are set in order of a. k1 condition(5) b. eta2 condition(4) c. eta1 condition(2) d. k2 condition(3) e. m condition(1) Todo: _twin_indices_orig is a little bit different from the ones 1981. Yoo especially 'S'. """ check_hexagonal_lattice(lattice) check_supported_twinmode(twinmode) self._lattice = lattice self._crylat = CrystalLattice(self._lattice) self._twinmode = twinmode self._layers = get_number_of_layers(twinmode) self._atom_num_per_layer = get_number_of_atoms_per_layer(twinmode) self._wyckoff = wyckoff self._indices_Yoo = self._get_indices_Yoo() self._indices = deepcopy(self._indices_Yoo) self._set_K1() self._set_k1_k2() self._reset_indices() self._set_shear_plane() @property def lattice(self): """ Lattice matrix. """ return self._lattice @property def twinmode(self): """ Twinmode. """ return self._twinmode @property def layers(self): """ Number of layers. """ return self._layers @property def atom_num_per_layer(self): """ Number of atoms per layer. """ return self._atom_num_per_layer @property def wyckoff(self): """ Wyckoff. """ return self._wyckoff @property def indices_Yoo(self): """ Indices Yoo showed. """ return self._indices_Yoo @property def indices(self): """ Indices. """ return self._indices def _get_indices_Yoo(self) -> dict: """ Get specific twinmode indices which are found in Yoo's paper. Returns: dict: indices by Yoo """ indices_ = get_twin_indices_by_Yoo()[self._twinmode] indices = {} for plane in ['S', 'K1', 'K2']: indices[plane] = HexagonalPlane(lattice=self._lattice, four=indices_[plane]) for direction in ['eta1', 'eta2']: indices[direction] = HexagonalDirection(lattice=self._lattice, four=indices_[direction]) return indices def _set_K1(self): """ Set K1. Note: There are two candidates for K1 plane. One is the K1 plane (defimed as (hkil) plane) which is found in Yoo's paper, and the other is (-h-k-il) plane. The plane which has shorter dictance from nearest atoms is set as K1 plane. If (-h-k-il) is determined, every other planes and directions are fixed by multiplied (-1, -1, -1, 1). """ arr = np.array([-1, -1, -1, 1]) atoms = get_hcp_atom_positions(wyckoff=self.wyckoff) K1_1 = self._indices['K1'] K1_2 = deepcopy(K1_1) K1_2.reset_indices(four=K1_2.four * arr) if K1_1.get_distance_from_plane(atoms[0]) > \ K1_2.get_distance_from_plane(atoms[0]): for key in ['S', 'K1', 'K2', 'eta1', 'eta2']: self._indices[key].reset_indices(four=self._indices[key].four * arr) def _set_k1_k2(self): """ Set k1 and k2. """ self._indices['k1'] = \ self._indices['K1'].get_direction_normal_to_plane( normalize=False) self._indices['k2'] = \ self._indices['K2'].get_direction_normal_to_plane( normalize=False) def _reset_indices(self): """ Reset indices. Raises: AssertionError: k1 is not orthogonal to eta1 AssertionError: k2 is not orthogonal to eta2 Note: conditions are as bellow - if the angle between k1 and eta2 is obtuse, eta2 -> -eta2 - if the angle between eta1 and eta2 is acute, eta1 -> -eta1 - if the angle between eta1 and k2 is obtuse, k2 -> -k2 - check k1 is orthogonal to eta1 - check k2 is orthogonal to eta2 """ # reset eta2 if self._crylat.dot(self._indices['k1'].three, self._indices['eta2'].three) < 0: self._indices['eta2'].inverse() # reset eta1 if self._crylat.dot(self._indices['eta1'].three, self._indices['eta2'].three) > 0: self._indices['eta1'].inverse() # reset K2 if self._crylat.dot(self._indices['eta1'].three, self._indices['k2'].three) < 0: self._indices['K2'].inverse() self._indices['k2'].inverse() # check k1 is orthogonal to eta1 np.testing.assert_allclose(actual=self._crylat.dot( self._indices['k1'].three, self._indices['eta1'].three), desired=0, atol=1e-6, err_msg="k1 is not orthogonal to eta1") # check k2 is orthogonal to eta2 np.testing.assert_allclose(actual=self._crylat.dot( self._indices['k2'].three, self._indices['eta2'].three), desired=0, atol=1e-6, err_msg="k2 is not orthogonal to eta2") def _set_shear_plane(self): """ Set shear plane. Raises: RuntimeError: Could not find shear plane. Note: Set shear plane which fulfill the following conditions from six candidates ((hkil), (hikl), (khil), (kihl), (ihkl), (ikhl)). """ S_four = self._indices['S'].four perms = map(list, permutations((0, 1, 2))) trial_S_fours = [[S_four[i] for i in lst + [3]] for lst in perms] normal_directions = ['k1', 'k2', 'eta1', 'eta2'] flag = 1 for trial_S_four in trial_S_fours: trial_S = HexagonalPlane(self._lattice, four=np.array(trial_S_four, dtype='intc')) trial_m = trial_S.get_direction_normal_to_plane(normalize=False) dots = [ self._crylat.dot(trial_m.three, self._indices[direction].three) for direction in normal_directions ] if np.allclose(np.array(dots), np.zeros(4), atol=1e-4): flag = 0 break if flag == 1: raise RuntimeError("could not find shear plane") triple_product = \ np.dot(trial_m.get_cartesian(), np.cross(self._indices['eta1'].get_cartesian(), self._indices['eta2'].get_cartesian())) if triple_product < 0: trial_S.inverse() trial_m.inverse() self._indices['S'] = trial_S self._indices['m'] = trial_m def get_supercell_matrix_for_parent(self) -> np.array: """ Get supercell matrix for creating parent matrix. Returns: np.array: Sueprcell matrix. Note: Create lattice basis with integerized m, eta1 and eta2. """ tf1 = np.array(get_ratio(self._indices['m'].three)) tf2 = np.array(get_ratio(self._indices['eta1'].three)) tf3 = np.array(get_ratio(self._indices['eta2'].three)) supercell_matrix = np.vstack([tf1, tf2, tf3]).T return supercell_matrix def get_shear_strain_function(self): """ Get shear strain function. """ return get_shear_strain_function(self._twinmode)
def get_structure_diff(cells: list, base_index: int = 0, include_base: bool = True) -> dict: """ Get structure diff with first cell in cells. Args: cells: List of cells. base_index: Base cell index. include_base: If True, include base cell in output dict. Returns: dict: Containing 'lattice_diffs', 'frac_posi_diffs', 'cart_posi_diffs' and 'cart_norm_diffs'. Note: Return diff compared with base cell. ex. lattice_diffs[i] = ith_lattice - base_lattice. The value 'frac_posi_diffs' are the difference between two fractional coordinates which is not consider lattice change, which can be defined as 'shuffle'. The value 'cart_posi_diffs' are the difference between two cartesian coordinates which is automatically consider lattice periodicity. """ cart_posis = [ np.dot(cells[i][0].T, cells[i][1].T).T for i in range(len(cells)) ] base_lat, base_frac_posi, _ = cells[base_index] base_cart_posi = cart_posis[base_index] lattice_diffs = [cell[0] - base_lat for cell in cells] _frac_posi_diffs = [ np.round(cell[1] - base_frac_posi, decimals=8) for cell in cells ] frac_posi_diffs = [ np.where(diff > 0.5, diff - 1, diff) for diff in _frac_posi_diffs ] cart_posi_diffs = [] for cell, cart_posi in zip(cells, cart_posis): lattice = CrystalLattice(lattice=cell[0]) diff = lattice.get_diff(first_coords=base_cart_posi, second_coords=cart_posi, is_cartesian=True, with_periodic=True) cart_posi_diffs.append(np.dot(lattice.lattice.T, diff.T).T) cart_norm_diffs = [ np.linalg.norm(cart_posi_diff, axis=1) for cart_posi_diff in cart_posi_diffs ] if not include_base: del lattice_diffs[base_index] del frac_posi_diffs[base_index] del cart_posi_diffs[base_index] del cart_norm_diffs[base_index] return { 'lattice_diffs': np.array(lattice_diffs), 'frac_posi_diffs': np.array(frac_posi_diffs), 'cart_posi_diffs': np.array(cart_posi_diffs), 'cart_norm_diffs': np.array(cart_norm_diffs), }
def test_lattice(ti_cell_wyckoff_c): """ Check Lattice. Todo: Write test for 'get_diff' and get_midpoint after refactering. """ def _test_reciprocal_lattice(crylat): _direct_lattice = crylat.lattice _recip_lattice_expected = crylat.reciprocal_lattice recip_bases = [] for i in range(3): j = (i + 1) % 3 k = (i + 2) % 3 recip_bases.append( np.cross(_direct_lattice[j], _direct_lattice[k]) / crylat.volume) _recip_lattice = np.array(recip_bases) np.testing.assert_allclose(_recip_lattice, _recip_lattice_expected) def _test_abc_angles_cos_angles_dot(crylat): """ check dot """ v1 = np.array([1. ,0., 0.]) v2 = np.array([0. ,1., 0.]) _abc = crylat.abc _cos_angles = crylat.cos_angles _inner_product = crylat.dot(first_coord=v1, second_coord=v2, is_cartesian=False) _inner_product_expected = \ _abc[0] * _abc[1] * _cos_angles[2] np.testing.assert_allclose(_inner_product, _inner_product_expected) def _test_convert_coordinate(crylat): """ check convert_fractional_to_cartesian check convert_cartesian_to_fractional """ a, _, c = crylat.abc v1_frac = np.array([0., 0., 0.5]) v2_frac = np.array([[0., 0., 0.5], [1., 0., 0. ]]) v1_cart_expected = np.array([0., 0., c/2]) v2_cart_expected = np.array([[0., 0., c/2], [a, 0., 0.]]) _v1_cart = crylat.convert_fractional_to_cartesian(frac_coords=v1_frac) _v2_cart = crylat.convert_fractional_to_cartesian(frac_coords=v2_frac) _v1_frac = crylat.convert_cartesian_to_fractional(cart_coords=_v1_cart) _v2_frac = crylat.convert_cartesian_to_fractional(cart_coords=_v2_cart) np.testing.assert_allclose(_v1_cart, v1_cart_expected) np.testing.assert_allclose(_v2_cart, v2_cart_expected) np.testing.assert_allclose(_v1_frac, v1_frac) np.testing.assert_allclose(_v2_frac, v2_frac) def _test_get_norm(crylat): _a_norm_expected = crylat.abc[0] _a_frac = np.array([1., 0., 0.]) _a_cart = np.array([_a_norm_expected,0.,0.]) _a_norm_from_frac = crylat.get_norm(coord=_a_frac, is_cartesian=False, with_periodic=False) _a_norm_from_cart = crylat.get_norm(coord=_a_cart, is_cartesian=True, with_periodic=False) np.testing.assert_allclose(_a_norm_from_frac, _a_norm_expected) np.testing.assert_allclose(_a_norm_from_cart, _a_norm_expected) def _test_expanded_lattice(crylat): _dim = np.array([2,3,4]) _sup_lat = crylat.get_expanded_lattice(expansion_ratios=_dim) _sup_lat_expected = crylat.lattice.copy() for i in range(3): _sup_lat_expected[i] *= _dim[i] np.testing.assert_allclose(_sup_lat, _sup_lat_expected) hex_lattice = ti_cell_wyckoff_c[0] hex_crylat = CrystalLattice(lattice=hex_lattice) _test_reciprocal_lattice(crylat=hex_crylat) _test_abc_angles_cos_angles_dot(crylat=hex_crylat) _test_convert_coordinate(crylat=hex_crylat) _test_get_norm(crylat=hex_crylat) _test_expanded_lattice(crylat=hex_crylat)
def plot_nearest_atomic_distance_of_twinboundary( lattice: np.array, symbol: str, twinmode: str, layers: int, wyckoff: str = 'c', delta: float = 0., twintype: int = 1, xshift: float = 0., yshift: float = 0., shear_strain_ratio: float = 0., expansion_ratios: np.array = np.ones(3), make_tb_flat: bool = True, ): """ Show nearest atomic distance of twinboundary by changing xshift and yshift. Args: lattice: Lattice matrix. symbol: Element symbol. twinmode: Twinmode. layers: The number of layers. wyckoff: No.194 Wycoff position ('c' or 'd'). delta: Additional interval both sites of twin boundary. twintype: Twintype, choose from 1 and 2. shear_strain_ratio (float): Shear twinboundary ratio. expansion_ratios: Expansion ratios. make_tb_flat: If True, atoms on the twin boundary plane are projected to twin boundary. """ import matplotlib.pyplot as plt from twinpy.structure.bonding import get_nearest_atomic_distance from twinpy.structure.lattice import CrystalLattice tb = TwinBoundaryStructure(lattice=lattice, symbol=symbol, twinmode=twinmode, twintype=twintype, wyckoff=wyckoff) tb.set_expansion_ratios(expansion_ratios) x = np.arange(0, 1.025, 0.025) y = np.arange(0, 1.025, 0.025) X, Y = np.meshgrid(x, y) shape = X.shape Z = np.zeros(shape) for i in range(shape[0]): for j in range(shape[1]): tb.run(layers=layers, delta=delta, xshift=X[i, j], yshift=Y[i, j], shear_strain_ratio=shear_strain_ratio, make_tb_flat=make_tb_flat) cell = tb.get_cell_for_export() Z[i, j] = get_nearest_atomic_distance(cell) a, b, _ = CrystalLattice(lattice=cell[0]).abc fig = plt.figure(figsize=(4, 4 * b / a)) cont = plt.contour(X, Y, Z, 5, vmin=0, vmax=4, colors=['black']) cont.clabel(fmt='%1.1f') plt.xlabel('xshift') plt.ylabel('yshift') plt.pcolormesh(X, Y, Z, cmap='cool') pp = plt.colorbar(orientation="vertical") pp.set_label("Nearest Atomic Distance") plt.show()