You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							148 lines
						
					
					
						
							6.2 KiB
						
					
					
				
			
		
		
	
	
							148 lines
						
					
					
						
							6.2 KiB
						
					
					
				| import numpy as np | |
| from ase.build.general_surface import surface | |
| from ase.geometry import get_layers | |
| from ase.symbols import string2symbols | |
| 
 | |
| 
 | |
| def surfaces_with_termination(lattice, indices, layers, vacuum=None, tol=1e-10, | |
|                               termination=None, return_all=False, verbose=False): | |
|     """Create surface from a given lattice and Miller indices with a given | |
|         termination | |
|  | |
|         Parameters | |
|         ========== | |
|         lattice: Atoms object or str | |
|             Bulk lattice structure of alloy or pure metal.  Note that the | |
|             unit-cell must be the conventional cell - not the primitive cell. | |
|             One can also give the chemical symbol as a string, in which case the | |
|             correct bulk lattice will be generated automatically. | |
|         indices: sequence of three int | |
|             Surface normal in Miller indices (h,k,l). | |
|         layers: int | |
|             Number of equivalent layers of the slab. (not the same as the layers | |
|             you choose from for terminations) | |
|         vacuum: float | |
|             Amount of vacuum added on both sides of the slab. | |
|         termination: str | |
|             the atoms you wish to be in the top layer. There may be many such | |
|             terminations, this function returns all terminations with the same | |
|             atomic composition. | |
|             e.g. 'O' will return oxygen terminated surfaces. | |
|             e.g.'TiO' will return surfaces terminated with layers containing both O | |
|             and Ti | |
|         Returns: | |
|         return_surfs: List | |
|             a list of surfaces that match the specifications given | |
|  | |
|     """ | |
|     lats = translate_lattice(lattice, indices) | |
|     return_surfs = [] | |
|     check = [] | |
|     check2 = [] | |
|     for item in lats: | |
|         too_similar = False | |
|         surf = surface(item, indices, layers, vacuum=vacuum, tol=tol) | |
|         surf.wrap(pbc=[True] * 3)  # standardize slabs | |
|          | |
|         positions = surf.get_scaled_positions().flatten() | |
|         for i, value in enumerate(positions): | |
|             if value >= 1 - tol:  # move things closer to zero within tol | |
|                 positions[i] -= 1 | |
|         surf.set_scaled_positions(np.reshape(positions, (len(surf), 3))) | |
|         #rep = find_z_layers(surf) | |
|         z_layers, hs = get_layers(surf, (0, 0, 1))  # just z layers matter | |
|         # get the indicies of the atoms in the highest layer | |
|         top_layer = [i for i, val in enumerate(z_layers == max(z_layers)) if val] | |
|           | |
|         if termination is not None: | |
|             comp = [surf.get_chemical_symbols()[a] for a in top_layer] | |
|             term = string2symbols(termination) | |
|             # list atoms in top layer and not in requested termination | |
|             check = [a for a in comp if a not in term] | |
|             # list of atoms in requested termination and not in top layer | |
|             check2 = [a for a in term if a not in comp] | |
|         if len(return_surfs) > 0: | |
|             pos_diff = [a.get_positions() - surf.get_positions() | |
|                         for a in return_surfs] | |
|             for i, su in enumerate(pos_diff): | |
|                 similarity_test = su.flatten() < tol * 1000 | |
|                 if similarity_test.all(): | |
|                     # checks if surface is too similar to another surface | |
|                     too_similar = True | |
|         if too_similar: | |
|             continue | |
|         if return_all is True: | |
|             pass | |
|         elif check != [] or check2 != []: | |
|             continue | |
|         return_surfs.append(surf) | |
|     return return_surfs | |
| 
 | |
|              | |
| def translate_lattice(lattice, indices, tol=10**-3): | |
|     """translates a bulk unit cell along a normal vector given by the a set of | |
|     miller indices to the next symetric position. This is used to control the | |
|     termination of the surface in the smart_surface command | |
|     Parameters: | |
|     ========== | |
|         lattice: Atoms object | |
|             atoms object of the bulk unit cell | |
|         indices: 1x3 list,tuple, or numpy array | |
|             the miller indices you wish to cut along. | |
|     returns: | |
|         lattice_list: list of Atoms objects | |
|             a list of all the different translations of the unit cell that will | |
|             yield different terminations of a surface cut along the miller | |
|             indices provided. | |
|     """ | |
|     lattice_list = [] | |
|     cell = lattice.get_cell() | |
|     pt = [0, 0, 0] | |
|     h, k, l = indices | |
|     millers = list(indices) | |
|     for index, item in enumerate(millers): | |
|         if item == 0: | |
|             millers[index] = 10**9  # make zeros large numbers | |
|         elif pt == [0, 0, 0]:  # for numerical stability | |
|             pt = list(cell[index] / float(item) / np.linalg.norm(cell[index])) | |
|     h1, k1, l1 = millers | |
|     N = np.array(cell[0] / h1 + cell[1] / k1 + cell[2] / l1) | |
|     n = N / np.linalg.norm(N)  # making a unit vector normal to cut plane | |
|     # finding distance from cut plan vector | |
|     d = [np.round(np.dot(n, (a - pt)) * n, 5) for | |
|          a in lattice.get_scaled_positions()] | |
|     duplicates = [] | |
|     for i, item in enumerate(d): | |
|         g = [True for a in d[i + 1:] if np.linalg.norm(a - item) < tol] | |
|         if g != []: | |
|             duplicates.append(i) | |
|     duplicates.reverse() | |
|     for i in duplicates: | |
|         del d[i] | |
|     # put distance to the plane at the end of the array | |
|     for i, item in enumerate(d): | |
|         d[i] = np.append(item, | |
|                          np.dot(n, (lattice.get_scaled_positions()[i] - pt))) | |
|     d = np.array(d) | |
|     d = d[d[:, 3].argsort()]  # sort by distance to the plane | |
|     d = [a[:3] for a in d]  # remove distance | |
|     d = list(d)  # make it a list again | |
|     for i in d: | |
|         """ | |
|         The above method gives you the boundries of between terminations that | |
|         will allow you to build a complete set of terminations. However, it | |
|         does not return all the boundries. Thus you must check both above and | |
|         below the boundary, and not stray too far from the boundary. If you move | |
|         too far away, you risk hitting another boundary you did not find. | |
|         """ | |
|         lattice1 = lattice.copy() | |
|         displacement = (h * cell[0] + k * cell[1] + l * cell[2]) \ | |
|             * (i + 10 ** -8) | |
|         lattice1.positions -= displacement | |
|         lattice_list.append(lattice1) | |
|         lattice1 = lattice.copy() | |
|         displacement = (h * cell[0] + k * cell[1] + l * cell[2]) \ | |
|             * (i - 10 ** -8) | |
|         lattice1.positions -= displacement | |
|         lattice_list.append(lattice1) | |
|     return lattice_list
 | |
| 
 |