├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── Test ├── Dichromatic_pattern_CSL.ipynb └── Usage_of_GB_code.ipynb ├── __init__.py ├── exGB.png ├── gb_code ├── __init__.py ├── csl_generator.py └── gb_generator.py ├── paper ├── paper.bib └── paper.md ├── requirements.txt └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | .idea 4 | input* 5 | __pycache__ 6 | .vscode 7 | log.file 8 | io_file 9 | Readme 10 | .ipynb_checkpoints 11 | POS* 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 oekosheri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GB_code [![DOI](http://joss.theoj.org/papers/10.21105/joss.00900/status.svg)](https://doi.org/10.21105/joss.00900) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1433531.svg)](https://doi.org/10.5281/zenodo.1433531) 2 | This python package helps you create orthogonal grain boundary supercells for atomistic calculations. The code is based on the coincident site lattice (CSL) formulations for cubic materials (sc, bcc, fcc, diamond). I intend to extend it to hcp structures soon. The code produces a final structure to be read in [LAMMPS](https://lammps.sandia.gov/) or [VASP](https://www.vasp.at/). 3 | It has been designed to be simple to use and instructive with a special attention to GB plane orientation which is often lacking in other grain boundary creation codes. For more details please read the [paper](https://doi.org/10.21105/joss.00900). 4 | 5 | # Overview 6 | There are two main scripts: [_csl_generator.py_](./csl_generator.py) and [_gb_generator.py_](./csl_generator.py) which you need to use in this order to produce the final grain boundary (GB) structure. These scripts are both modules (a collection of functions/classes) and can be executed 7 | from the command line. 8 | In this README I will explain the steps to use the code in the Terminal and I have also attached two _jupyter notebooks_ ([Usage_of_GB_code.ipynb](./Usage_of_GB_code.ipynb), [Dichromatic_pattern_CSL.ipynb](./Dichromatic_pattern_CSL.ipynb)) in the [Test](./Test) directory which describe how the code can be accessed and used in the notebooks by various examples. These notebooks have extra functionality. The former is for the general usage of the code with some tips to locate GBs of interest, the latter depicts how CSL construction can be used for different purposes. 9 | You can use [this link](https://mybinder.org/v2/gh/oekosheri/GB_code/master) or [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/oekosheri/GB_code/master) for an interactive Jupyter notebook environment provided by Binder. 10 | To use it locally, you will need `python> 3.5.1` and `numpy > 1.14` for the main scripts and additionally matplotlib and pandas to use the auxilliary Jupyter notebooks. 11 | 12 | # Installation guide 13 | For installation simply clone or download the code in your terminal and in the main directory of the package type: 14 | ``` 15 | > pip install . 16 | ``` 17 | This will copy the modules to your active python site-packages, thereby making them importable in any python script and will put the scrpits in the python bin, thereby making them executable in the shell. 18 | 19 | # Usage 20 | To pick a grain boundary 5 degrees of freedom need to be fixed: rotation axis, rotation angle and GB plane orientation. 21 | We start by choosing only an axis, say a low index [1, 1, 1] and list the possibilities for the angle (sigma). Once you pick 22 | these and additionally a basis, a CSL minimal cell will be created and you can see a list of possible GB plane orientations within 23 | the CSL that you can pick from. In the jupyter notebooks, [Usage_of_GB_code.ipynb](./Usage_of_GB_code.ipynb), example criteria have been 24 | shown to help pinpoint the boundary plane of interest. 25 | 26 | The first code [_csl_generator.py_](./csl_generator.py) runs in two modes: 27 | 28 | _First mode: 29 | "python CSLgenerator.py u v w [limit]" -----> Where the u v w are the 30 | indices of the rotation axis such as 1 0 0, 1 1 1, 1 1 0 and so on. The limit 31 | is the maximum Sigma of interest. 32 | (the limit by default: 100)_ 33 | 34 | _ex:_ 35 | 36 | ``` 37 | > csl_generator.py 1 1 1 50 38 | 39 | List of possible CSLs for [1 1 1] axis sorted by Sigma 40 | Sigma: 1 Theta: 0.00 41 | Sigma: 3 Theta: 60.00 42 | Sigma: 7 Theta: 38.21 43 | Sigma: 13 Theta: 27.80 44 | Sigma: 19 Theta: 46.83 45 | Sigma: 21 Theta: 21.79 46 | Sigma: 31 Theta: 17.90 47 | Sigma: 37 Theta: 50.57 48 | Sigma: 39 Theta: 32.20 49 | Sigma: 43 Theta: 15.18 50 | Sigma: 49 Theta: 43.57 51 | 52 | Choose a basis, pick a sigma and use the second mode! 53 | ``` 54 | Once you pick one of these angles (sigma boundaries) you should use the second mode, decide on a basis, for ex: diamond, and list the GB planes of interest: 55 | 56 | _Second mode: 57 | "python CSLgenerator.py u v w basis sigma [limit]" -----> Where basis is 58 | either fcc, bcc, diamond or sc. You read the sigma of interest from the first 59 | mode run. The limit here refers to CSL GB inclinations. The bigger the limit, 60 | the higher the indices of CSL planes. 61 | (the limit by default: 2)_ 62 | 63 | _ex:_ 64 | 65 | ``` 66 | > csl_generator.py 1 1 1 diamond 13 67 | 68 | ----------List of possible CSL planes for Sigma 13--------- 69 | GB1-------------------GB2-------------------Type----------Number of Atoms 70 | [ 2 1 -2] [ 1 2 -2] Mixed 3744 71 | [-1 -1 -1] [-1 -1 -1] Twist 1248 72 | [1 1 1] [1 1 1] Twist 1248 73 | [-1 2 -2] [-2 2 -1] Mixed 3744 74 | [ 1 -2 2] [ 2 -2 1] Mixed 3744 75 | [-2 -1 2] [-1 -2 2] Mixed 3744 76 | [-3 -2 1] [-2 -3 1] Mixed 2912 77 | [ 0 -3 1] [ 1 -3 0] Mixed 2080 78 | [ 1 3 -4] [-1 4 -3] Symmetric Tilt 1248 79 | ... 80 | ``` 81 | Which you can write to a file if you wish. 82 | 83 | Your chosen axis, basis and sigma will be written to an io_file (a yaml file) which will be read by gb_generator.py. 84 | You must customize the io_file and then run the gb_generator.py code after to get the atomic structure of the GB. This is 85 | how the io_file looks like right now: 86 | 87 | ``` 88 | ## input parameters for gb_generator.py ### 89 | # CSL plane of interest that you read from the output of csl_generator as GB1 90 | GB_plane: [1, 1, 1] 91 | 92 | # lattice parameter in Angstrom 93 | lattice_parameter: 4 94 | 95 | # atoms that are closer than this fraction of the lattice parameter will be removed 96 | # either from grain1 (g1) or from grain2 (g2). If you choose 0 no atoms will be removed 97 | overlap_distance: 0.0 98 | 99 | # decide which grain the atoms should be removed from 100 | which_g: g1 101 | 102 | # decide whether you want rigid body translations to be done on the GB_plane or not (yes or no) 103 | # When yes, for any GB aside from twist GBs, the two inplane 104 | # CSL vectors will be divided by integers a and b to produce a*b initial 105 | # configurations. The default values produce 50 initial structures 106 | # if you choose no for rigid_trans, you do not need to care about a and b. 107 | # twist boundaries are handled internally 108 | rigid_trans: no 109 | a: 10 110 | b: 5 111 | 112 | # dimensions of the supercell in: [l1,l2,l3], where l1 isthe direction along the GB_plane normal 113 | # and l2 and l3 are inplane dimensions 114 | dimensions: [1,1,1] 115 | 116 | # File type, either VASP or LAMMPS input 117 | File_type: LAMMPS 118 | 119 | 120 | # The following is your csl_generator output. YOU DO NOT NEED TO CHANGE THEM! 121 | 122 | axis: [1, 1, 1] 123 | m: 7 124 | n: 1 125 | basis: diamond 126 | ``` 127 | - GB_plane: must be chosen from the list that was provided after running the second mode of csl_generator.py. Here for ex: [2, 1, -2]. 128 | Change the default plane to your chosen one. 129 | - lattice_parameter: is self-explanatory. 130 | 131 | To minimize the grain boundary energy, microscopic degrees of freedom must be taken into account. Therefore here we allow for 132 | (1) atom removal by finding atoms that are too close to one another and (2) rigid body translations on the GB plane. 133 | (1) will be achieved by 134 | 135 | - overlap_distance: a fraction of lattice parameter. See the description in io_file. By default 0.0. 136 | - which_g: g1 or g2. 137 | 138 | (2) will be achieved by 139 | 140 | - rigid_trans: yes or no. If no, the following a and b will be disregarded. For any grain boundary type except the twist boundary, the smallest inplane unit that must be scanned comprises the unitcell formed by the smallest orthogonal CSL vectors on the GB plane. These vectors will be divided by the following a and b integers to produce a grid upon which the boundary will be translated. Here a is used for the larger inplane vector and b for the smaller one. So by defalut I produce _5 * 10 = 50_ initial structures to be minimized for finding the lowest energy structure. The higher your chosen a and b, the denser the grid. 141 | For the twist grain boundary the smallest unit that needs to be scanned is the unitcell created by the DSC vectors (the smallest repeat vectors of the CSL) and the code will handle it internally. 142 | - a: 10 143 | - b: 5 144 | 145 | You can choose a combination of atom removal and rigid body translation for finding the minimum energy GB. 146 | 147 | - dimensions: The supercell dimensions according to the io_file. Make sure you always choose a large enough l1 dimension that the GB and its periodic image do not interact. By default [1,1,1]. 148 | 149 | - File_type: choose either LAMMPS or VASP. By default LAMMPS. 150 | 151 | As an example, we change the default gb_plane to [2, 1, -2], overlap_distance to 0.3 and rigid_trans to 'yes' in the io_file. 152 | To produce the GB of interest we go on with: [_gb_generator.py_](./gb_generator.py) 153 | ``` 154 | > gb_generator.py io_file 155 | <<------ 32 atoms are being removed! ------>> 156 | <<------ 50 GB structures are being created! ------>> 157 | ``` 158 | The following is one of these 50 GBs visualized by [Ovito](https://ovito.org/index.php/download): 159 | The color code distinguishes the diamond bulk structure as blue. The color changes gradually to green as we approach the GB atoms. In the middle lies the central GB and on both up and bottom of the cell you have halves of an equivalent GB (the periodic image). 160 | 161 | 162 | 163 | - _**A note on the microscopic degrees of freedom:**_ 164 | 165 | In the absence of a consensus on how to find the global minimum energy GB structure I have used atom removal and/or rigid body translations according to the description above to find the minimized structure. For rigid body translations, the smallest translation vector to guarantee the minimum energy structure is not well defined, therefore you can make the mesh as dense as you wish by choosing larger a and b values. By trial and error in fcc elemental cases (such as Al and Cu) I have come to the rule of thumb conclusion of 50 166 | to 100 initial structures that need to be minimized to find the minimum energy GB. 167 | In the end you must find a crossover between the accuracy you need and the computational cost of running minimization on several 168 | input structures. 169 | If you are a more rigorous user, you can just create the GB structure with no atom removal or rigid body translations and run a more involved minimum energy search routine. 170 | 171 | - _**A note on the minimization procedure in LAMMPS:**_ 172 | 173 | I often do a three stage minimization at 0K followed by an MD annealing simulation in [LAMMPS](https://lammps.sandia.gov/). 174 | The 0K miminimization is composed of: A conjugate gradient minimization of the energy of atoms, the simulation box and then atoms again; 175 | similar to a procedure explained [here](https://icme.hpc.msstate.edu/mediawiki/index.php/LAMMPS_Input_Deck_for_Grain_boundary_generation). 176 | For the annealing simulations I use an _nvt_ ensemble followed by damped dynamics. Depending on the GB structure and your final purpose you can run annealing simulations for different time spans. 177 | # Questions/Contributions 178 | If you have any questions, raise an issue or contact [me](mailto:shahrzadhadian@gmail.com). 179 | This project is currently under development and at the moment I am not accepting contributions from other users. 180 | Should this change in the future I'll provide detailed information on how to contribute to the project. 181 | 182 | # Citation 183 | Feel free to use the code anyway you like, if you find it useful please cite the paper: 184 | - Hadian et al., (2018). GB code: A grain boundary generation code. Journal of Open Source Software, 3(29), 900 [https://doi.org/10.21105/joss.00900](https://doi.org/10.21105/joss.00900) 185 | [![DOI](http://joss.theoj.org/papers/10.21105/joss.00900/status.svg)](https://doi.org/10.21105/joss.00900) 186 | 187 | # License 188 | [MIT](./LICENSE). 189 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This files marks the containing directory as Python package. -------------------------------------------------------------------------------- /exGB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oekosheri/GB_code/b1fca9b31f960c4d1af02bdb1ee4e64eb6a99dd1/exGB.png -------------------------------------------------------------------------------- /gb_code/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This files marks the containing directory as Python package. -------------------------------------------------------------------------------- /gb_code/csl_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | This module is a collection of functions that produce CSL properties. 5 | When run from the terminal, the code runs in two modes. 6 | 7 | First mode: 8 | 'csl_generator.py u v w [limit]' -----> Where the u v w are the 9 | indices of the rotation axis such as 1 0 0, 1 1 1, 1 1 0 and so on. The limit 10 | is the maximum Sigma of interest. 11 | (the limit by default: 100) 12 | 13 | Second mode: 14 | 'csl_generator.py u v w basis sigma [limit]' -----> Where basis is 15 | either fcc, bcc, diamond or sc. You read the sigma of interest from the first 16 | mode run. The limit here refers to CSL GB inclinations. The bigger the limit, 17 | the higher the indices of CSL planes. 18 | (the limit by default: 2) 19 | 20 | your chosen axis, basis and sigma will be written to an io_file which will 21 | later be read by gb_geberator.py. 22 | """ 23 | 24 | import sys 25 | import random 26 | from math import degrees, atan, sqrt, pi, ceil, cos, acos, sin, gcd, radians 27 | import numpy as np 28 | from numpy import dot, cross 29 | from numpy.linalg import det, norm, inv 30 | 31 | 32 | def get_cubic_sigma(uvw, m, n=1): 33 | """ 34 | CSL analytical formula based on the book: 35 | 'Interfaces in crystalline materials', 36 | Sutton and Balluffi, clarendon press, 1996. 37 | generates possible sigma values. 38 | arguments: 39 | uvw -- the axis 40 | m,n -- two integers (n by default 1) 41 | 42 | """ 43 | u, v, w = uvw 44 | sqsum = u*u + v*v + w*w 45 | sigma = m*m + n*n * sqsum 46 | while sigma != 0 and sigma % 2 == 0: 47 | sigma /= 2 48 | return sigma if sigma > 1 else None 49 | 50 | 51 | def get_cubic_theta(uvw, m, n=1): 52 | """ 53 | generates possible theta values. 54 | arguments: 55 | uvw -- the axis 56 | m,n -- two integers (n by default 1) 57 | """ 58 | u, v, w = uvw 59 | sqsum = u*u + v*v + w*w 60 | if m > 0: 61 | return 2 * atan(sqrt(sqsum) * n / m) 62 | else: 63 | return pi 64 | 65 | 66 | def get_theta_m_n_list(uvw, sigma): 67 | """ 68 | Finds integers m and n lists that match the input sigma. 69 | """ 70 | if sigma == 1: 71 | return [(0., 0., 0.)] 72 | thetas = [] 73 | max_m = int(ceil(sqrt(4*sigma))) 74 | 75 | for m in range(1, max_m): 76 | for n in range(1, max_m): 77 | if gcd(m, n) == 1: 78 | s = get_cubic_sigma(uvw, m, n) 79 | if s == sigma: 80 | theta = (get_cubic_theta(uvw, m, n)) 81 | thetas.append((theta, m, n)) 82 | thetas.sort(key=lambda x: x[0]) 83 | return thetas 84 | 85 | 86 | def print_list(uvw, limit): 87 | """ 88 | prints a list of smallest sigmas/angles for a given axis(uvw). 89 | """ 90 | for i in range(limit): 91 | tt = get_theta_m_n_list(uvw, i) 92 | if len(tt) > 0: 93 | theta, _, _ = tt[0] 94 | print("Sigma: {0:3d} Theta: {1:5.2f} " 95 | .format(i, degrees(theta))) 96 | 97 | 98 | def rot(a, Theta): 99 | """ 100 | produces a rotation matrix. 101 | arguments: 102 | a -- an axis 103 | Theta -- an angle 104 | """ 105 | c = cos(Theta) 106 | s = sin(Theta) 107 | a = a / norm(a) 108 | ax, ay, az = a 109 | return np.array([[c + ax * ax * (1 - c), ax * ay * (1 - c) - az * s, 110 | ax * az * (1 - c) + ay * s], 111 | [ay * ax * (1 - c) + az * s, c + ay * ay * (1 - c), 112 | ay * az * (1 - c) - ax * s], 113 | [az * ax * (1 - c) - ay * s, az * ay * (1 - c) + ax * s, 114 | c + az * az * (1 - c)]]) 115 | 116 | 117 | # Helpful Functions: 118 | # -------------------# 119 | 120 | def integer_array(A, tol=1e-7): 121 | """ 122 | returns True if an array is ineteger. 123 | """ 124 | return np.all(abs(np.round(A) - A) < tol) 125 | 126 | 127 | def angv(a, b): 128 | """ 129 | returns the angle between two vectors. 130 | """ 131 | ang = acos(np.round(dot(a, b)/norm(a)/norm(b), 8)) 132 | return round(degrees(ang), 7) 133 | 134 | 135 | def ang(a, b): 136 | """ 137 | returns the cos(angle) between two vectors. 138 | """ 139 | ang = np.round(dot(a, b)/norm(a)/norm(b), 7) 140 | return abs(ang) 141 | 142 | 143 | def CommonDivisor(a): 144 | """ 145 | returns the common factor of vector a and the reduced vector. 146 | """ 147 | CommFac = [] 148 | a = np.array(a) 149 | for i in range(2, 100): 150 | while (a[0] % i == 0 and a[1] % i == 0 and a[2] % i == 0): 151 | a = a / i 152 | CommFac.append(i) 153 | return(a.astype(int), (abs(np.prod(CommFac)))) 154 | 155 | 156 | def SmallestInteger(a): 157 | """ 158 | returns the smallest multiple integer to make an integer array. 159 | """ 160 | a = np.array(a) 161 | for i in range(1, 200): 162 | testV = i * a 163 | if integer_array(testV): 164 | break 165 | return (testV, i) if integer_array(testV) else None 166 | 167 | 168 | def integerMatrix(a): 169 | """ 170 | returns an integer matrix from row vectors. 171 | """ 172 | Found = True 173 | b = np.zeros((3, 3)) 174 | a = np.array(a) 175 | for i in range(3): 176 | for j in range(1, 2000): 177 | testV = j * a[i] 178 | if integer_array(testV): 179 | b[i] = testV 180 | break 181 | if all(b[i] == 0): 182 | Found = False 183 | print("Can not make integer matrix!") 184 | return (b) if Found else None 185 | 186 | 187 | def SymmEquivalent(arr): 188 | """ 189 | returns cubic symmetric eqivalents of the given 2 dimensional vector. 190 | """ 191 | Sym = np.zeros([24, 3, 3]) 192 | Sym[0, :] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] 193 | Sym[1, :] = [[1, 0, 0], [0, -1, 0], [0, 0, -1]] 194 | Sym[2, :] = [[-1, 0, 0], [0, 1, 0], [0, 0, -1]] 195 | Sym[3, :] = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]] 196 | Sym[4, :] = [[0, -1, 0], [-1, 0, 0], [0, 0, -1]] 197 | Sym[5, :] = [[0, -1, 0], [1, 0, 0], [0, 0, 1]] 198 | Sym[6, :] = [[0, 1, 0], [-1, 0, 0], [0, 0, 1]] 199 | Sym[7, :] = [[0, 1, 0], [1, 0, 0], [0, 0, -1]] 200 | Sym[8, :] = [[-1, 0, 0], [0, 0, -1], [0, -1, 0]] 201 | Sym[9, :] = [[-1, 0, 0], [0, 0, 1], [0, 1, 0]] 202 | Sym[10, :] = [[1, 0, 0], [0, 0, -1], [0, 1, 0]] 203 | Sym[11, :] = [[1, 0, 0], [0, 0, 1], [0, -1, 0]] 204 | Sym[12, :] = [[0, 1, 0], [0, 0, 1], [1, 0, 0]] 205 | Sym[13, :] = [[0, 1, 0], [0, 0, -1], [-1, 0, 0]] 206 | Sym[14, :] = [[0, -1, 0], [0, 0, 1], [-1, 0, 0]] 207 | Sym[15, :] = [[0, -1, 0], [0, 0, -1], [1, 0, 0]] 208 | Sym[16, :] = [[0, 0, 1], [1, 0, 0], [0, 1, 0]] 209 | Sym[17, :] = [[0, 0, 1], [-1, 0, 0], [0, -1, 0]] 210 | Sym[18, :] = [[0, 0, -1], [1, 0, 0], [0, -1, 0]] 211 | Sym[19, :] = [[0, 0, -1], [-1, 0, 0], [0, 1, 0]] 212 | Sym[20, :] = [[0, 0, -1], [0, -1, 0], [-1, 0, 0]] 213 | Sym[21, :] = [[0, 0, -1], [0, 1, 0], [1, 0, 0]] 214 | Sym[22, :] = [[0, 0, 1], [0, -1, 0], [1, 0, 0]] 215 | Sym[23, :] = [[0, 0, 1], [0, 1, 0], [-1, 0, 0]] 216 | 217 | arr = np.atleast_2d(arr) 218 | Result = [] 219 | for i in range(len(Sym)): 220 | for j in range(len(arr)): 221 | Result.append(dot(Sym[i, :], arr[j])) 222 | Result = np.array(Result) 223 | return np.unique(Result, axis=0) 224 | 225 | 226 | def Tilt_Twist_comp(v1, uvw, m, n): 227 | """ 228 | returns the tilt and twist components of a given GB plane. 229 | arguments: 230 | v1 -- given gb plane 231 | uvw -- axis of rotation 232 | m,n -- the two necessary integers 233 | """ 234 | theta = get_cubic_theta(uvw, m, n) 235 | R = rot(uvw, theta) 236 | v2 = np.round(dot(R, v1), 6).astype(int) 237 | tilt = angv(v1, v2) 238 | if abs(tilt - degrees(theta)) < 10e-5: 239 | print("Pure tilt boundary with a tilt component: {0:6.2f}" 240 | .format(tilt)) 241 | else: 242 | twist = 2 * acos(cos(theta / 2) / cos(radians(tilt / 2))) 243 | print("Tilt component: {0:<6.2f} Twist component: {1:6.2f}" 244 | .format(tilt, twist)) 245 | 246 | 247 | def Create_Possible_GB_Plane_List(uvw, m=5, n=1, lim=5): 248 | """ 249 | generates GB planes and specifies the character. 250 | 251 | arguments: 252 | uvw -- axis of rotation. 253 | m,n -- the two necessary integers 254 | lim -- upper limit for the plane indices 255 | 256 | """ 257 | uvw = np.array(uvw) 258 | Theta = get_cubic_theta(uvw, m, n) 259 | Sigma = get_cubic_sigma(uvw, m, n) 260 | R1 = rot(uvw, Theta) 261 | 262 | # List and character of possible GB planes: 263 | x = np.arange(-lim, lim + 1, 1) 264 | y = x 265 | z = x 266 | indice = (np.stack(np.meshgrid(x, y, z)).T).reshape(len(x) ** 3, 3) 267 | indice_0 = indice[np.where(np.sum(abs(indice), axis=1) != 0)] 268 | indice_0 = indice_0[np.argsort(norm(indice_0, axis=1))] 269 | 270 | # extract the minimal cell: 271 | Min_1, Min_2 = Create_minimal_cell_Method_1(Sigma, uvw, R1) 272 | V1 = np.zeros([len(indice_0), 3]) 273 | V2 = np.zeros([len(indice_0), 3]) 274 | GBtype = [] 275 | tol = 0.001 276 | # Mirrorplanes cubic symmetry 277 | MP = np.array([[1, 0, 0], 278 | [0, 1, 0], 279 | [0, 0, 1], 280 | [1, 1, 0], 281 | [0, 1, 1], 282 | [1, 0, 1], 283 | ], dtype='float') 284 | # Find GB plane coordinates: 285 | for i in range(len(indice_0)): 286 | if CommonDivisor(indice_0[i])[1] <= 1: 287 | V1[i, :] = (indice_0[i, 0] * Min_1[:, 0] + 288 | indice_0[i, 1] * Min_1[:, 1] + 289 | indice_0[i, 2] * Min_1[:, 2]) 290 | V2[i, :] = (indice_0[i, 0] * Min_2[:, 0] + 291 | indice_0[i, 1] * Min_2[:, 1] + 292 | indice_0[i, 2] * Min_2[:, 2]) 293 | 294 | V1 = (V1[~np.all(V1 == 0, axis=1)]).astype(int) 295 | V2 = (V2[~np.all(V2 == 0, axis=1)]).astype(int) 296 | MeanPlanes = (V1 + V2) / 2 297 | 298 | # Check the type of GB plane: Symmetric tilt, tilt, twist 299 | for i in range(len(V1)): 300 | if ang(V1[i], uvw) < tol: 301 | 302 | for j in range(len(SymmEquivalent(MP))): 303 | if 1 - ang(MeanPlanes[i], SymmEquivalent(MP)[j]) < tol: 304 | GBtype.append('Symmetric Tilt') 305 | break 306 | else: 307 | GBtype.append('Tilt') 308 | elif 1 - ang(V1[i], uvw) < tol: 309 | GBtype.append('Twist') 310 | else: 311 | GBtype.append('Mixed') 312 | 313 | return (V1, V2, MeanPlanes, GBtype) 314 | 315 | 316 | def Create_minimal_cell_Method_1(sigma, uvw, R): 317 | """ 318 | finds Minimal cell by means of a numerical search. 319 | (An alternative analytical method can be used too). 320 | arguments: 321 | sigma -- gb sigma 322 | uvw -- rotation axis 323 | R -- rotation matrix 324 | """ 325 | uvw = np.array(uvw) 326 | MiniCell_1 = np.zeros([3, 3]) 327 | MiniCell_1[:, 2] = uvw 328 | 329 | lim = 20 330 | x = np.arange(-lim, lim + 1, 1) 331 | y = x 332 | z = x 333 | indice = (np.stack(np.meshgrid(x, y, z)).T).reshape(len(x) ** 3, 3) 334 | 335 | # remove 0 vectors and uvw from the list 336 | indice_0 = indice[np.where(np.sum(abs(indice), axis=1) != 0)] 337 | condition1 = ((abs(dot(indice_0, uvw) / norm(indice_0, axis=1) / 338 | norm(uvw))).round(7)) 339 | indice_0 = indice_0[np.where(condition1 != 1)] 340 | 341 | if MiniCell_search(indice_0, MiniCell_1, R, sigma): 342 | 343 | M1, M2 = MiniCell_search(indice_0, MiniCell_1, R, sigma) 344 | return (M1, M2) 345 | else: 346 | return None 347 | 348 | 349 | def MiniCell_search(indices, MiniCell_1, R, sigma): 350 | 351 | tol = 0.001 352 | # norm1 = norm(indices, axis=1) 353 | newindices = dot(R, indices.T).T 354 | nn = indices[np.all(abs(np.round(newindices) - newindices) < 1e-6, axis=1)] 355 | TestVecs = nn[np.argsort(norm(nn, axis=1))] 356 | # print(len(indices), len(TestVecs),TestVecs[:20]) 357 | 358 | Found = False 359 | count = 0 360 | while (not Found) and count < len(TestVecs) - 1: 361 | if 1 - ang(TestVecs[count], MiniCell_1[:, 2]) > tol: 362 | # and (ang(TestVecs[i],uvw) > tol): 363 | MiniCell_1[:, 1] = (TestVecs[count]) 364 | count += 1 365 | for j in range(len(TestVecs)): 366 | if (1 - ang(TestVecs[j], MiniCell_1[:, 2]) > tol) and ( 367 | 1 - ang(TestVecs[j], MiniCell_1[:, 1]) > tol): 368 | if (ang(TestVecs[j], 369 | cross(MiniCell_1[:, 2], MiniCell_1[:, 1])) > tol): 370 | # The condition that the third vector can not be 371 | # normal to any other two. 372 | # and (ang(TestVecs[i],uvw)> tol) and 373 | # (ang(TestVecs[i],MiniCell[:,1])> tol)): 374 | MiniCell_1[:, 0] = (TestVecs[j]).astype(int) 375 | Det1 = abs(round(det(MiniCell_1), 5)) 376 | MiniCell_1 = (MiniCell_1).astype(int) 377 | MiniCell_2 = ((np.round(dot(R, MiniCell_1), 7)) 378 | .astype(int)) 379 | Det2 = abs(round(det(MiniCell_2), 5)) 380 | 381 | if ((abs(Det1 - sigma)) < tol and 382 | (abs(Det2 - sigma)) < tol): 383 | Found = True 384 | break 385 | if Found: 386 | return MiniCell_1, MiniCell_2 387 | else: 388 | return Found 389 | 390 | 391 | def Basis(basis): 392 | """ 393 | defines the basis. 394 | """ 395 | # Cubic basis 396 | if str(basis) == 'fcc': 397 | basis = np.array([[0, 0, 0], 398 | [0.5, 0, 0.5], 399 | [0.5, 0.5, 0], 400 | [0, 0.5, 0.5]], dtype=float) 401 | elif str(basis) == 'bcc': 402 | basis = np.array([[0, 0, 0], 403 | [0.5, 0.5, 0.5]], dtype=float) 404 | elif str(basis) == 'sc': 405 | basis = np.eye(3) 406 | 407 | elif str(basis) == 'diamond': 408 | basis = np.array([[0, 0, 0], 409 | [0.5, 0, 0.5], 410 | [0.5, 0.5, 0], 411 | [0, 0.5, 0.5], 412 | [0.25, 0.25, 0.25], 413 | [0.75, 0.25, 0.75], 414 | [0.75, 0.75, 0.25], 415 | [0.25, 0.75, 0.75]], dtype=float) 416 | else: 417 | print('Sorry! For now only works for cubic lattices ...') 418 | sys.exit() 419 | 420 | return basis 421 | 422 | 423 | def Find_Orthogonal_cell(basis, uvw, m, n, GB1): 424 | """ 425 | finds Orthogonal cells from the CSL minimal cells. 426 | arguments: 427 | basis -- lattice basis 428 | uvw -- rotation axis 429 | m,n -- two necessary integers 430 | GB1 -- input plane orientation 431 | """ 432 | # Changeable limit 433 | lim = 15 434 | uvw = np.array(uvw) 435 | Theta = get_cubic_theta(uvw, m, n) 436 | Sigma = get_cubic_sigma(uvw, m, n) 437 | R = rot(uvw, Theta) 438 | GB2 = np.round((dot(R, GB1)), 6) 439 | x = np.arange(-lim, lim + 1, 1) 440 | y = x 441 | z = x 442 | indice = (np.stack(np.meshgrid(x, y, z)).T).reshape(len(x) ** 3, 3) 443 | indice_0 = indice[np.where(np.sum(abs(indice), axis=1) != 0)] 444 | indice_0 = indice_0[np.argsort(norm(indice_0, axis=1))] 445 | OrthoCell_1 = np.zeros([3, 3]) 446 | OrthoCell_1[:, 0] = np.array(GB1) 447 | OrthoCell_2 = np.zeros([3, 3]) 448 | OrthoCell_2[:, 0] = np.array(GB2) 449 | # extract the minimal cells: 450 | Min_1, Min_2 = Create_minimal_cell_Method_1(Sigma, uvw, R) 451 | # Find Ortho vectors: 452 | tol = 0.001 453 | if ang(OrthoCell_1[:, 0], uvw) < tol: 454 | OrthoCell_1[:, 1] = uvw 455 | OrthoCell_2[:, 1] = uvw 456 | else: 457 | 458 | for i in range(len(indice_0)): 459 | 460 | v1 = (indice_0[i, 0] * Min_1[:, 0] + 461 | indice_0[i, 1] * Min_1[:, 1] + 462 | indice_0[i, 2] * Min_1[:, 2]) 463 | v2 = (indice_0[i, 0] * Min_2[:, 0] + 464 | indice_0[i, 1] * Min_2[:, 1] + 465 | indice_0[i, 2] * Min_2[:, 2]) 466 | if ang(v1, OrthoCell_1[:, 0]) < tol: 467 | OrthoCell_1[:, 1] = v1 468 | OrthoCell_2[:, 1] = v2 469 | break 470 | OrthoCell_1[:, 2] = np.cross(OrthoCell_1[:, 0], OrthoCell_1[:, 1]) 471 | OrthoCell_2[:, 2] = np.cross(OrthoCell_2[:, 0], OrthoCell_2[:, 1]) 472 | 473 | if (CommonDivisor(OrthoCell_1[:, 2])[1] == 474 | CommonDivisor(OrthoCell_2[:, 2])[1]): 475 | OrthoCell_1[:, 2] = CommonDivisor(OrthoCell_1[:, 2])[0] 476 | OrthoCell_2[:, 2] = CommonDivisor(OrthoCell_2[:, 2])[0] 477 | # OrthoCell_1 = OrthoCell_1 478 | # OrthoCell_2 = OrthoCell_2 479 | # Test 480 | # OrthoCell_3 = (dot(R, OrthoCell_1)) 481 | Volume_1 = (round(det(OrthoCell_1), 5)) 482 | Volume_2 = (round(det(OrthoCell_2), 5)) 483 | Num = Volume_1 * len(Basis(basis)) * 2 484 | 485 | if Volume_1 == Volume_2: 486 | OrthoCell_1 = OrthoCell_1.astype(float) 487 | OrthoCell_2 = OrthoCell_2.astype(float) 488 | 489 | if basis == 'sc' or basis == 'diamond': 490 | 491 | return ((OrthoCell_1.astype(float), 492 | OrthoCell_2.astype(float), Num.astype(int))) 493 | 494 | elif basis == 'fcc' or basis == 'bcc': 495 | ortho1, ortho2 = Ortho_fcc_bcc(basis, OrthoCell_1, OrthoCell_2) 496 | Volume_1 = (round(det(ortho1), 5)) 497 | Num = Volume_1 * len(Basis(basis)) * 2 498 | return (ortho1, ortho2, Num.astype(int)) 499 | 500 | else: 501 | return None 502 | 503 | 504 | def print_list_GB_Planes(uvw, basis, m, n, lim=3): 505 | """ 506 | prints lists of GB planes given an axis, basis, m and n. 507 | """ 508 | uvw = np.array(uvw) 509 | V1, V2, _, Type = Create_Possible_GB_Plane_List(uvw, m, n, lim) 510 | for i in range(len(V1)): 511 | Or = Find_Orthogonal_cell(basis, uvw, m, n, V1[i]) 512 | if Or: 513 | print("{0:<20s} {1:<20s} {2:<20s} {3:<10s}" 514 | .format(str(V1[i]), str(V2[i]), Type[i], str(Or[2]))) 515 | 516 | 517 | # ___CSL/DSC vector construction___# 518 | 519 | # According to Grimmer et al. (Acta Cryst. (1974). A30, 197-207) , 520 | # DSC and CSL lattices for bcc and fcc were made from the Sc lattice 521 | # via body_centering and face_centering: 522 | def odd_even(M1): 523 | """ 524 | finds odd and even elements of a matrix. 525 | """ 526 | d_e = np.array([['a', 'a', 'a'], 527 | ['a', 'a', 'a'], 528 | ['a', 'a', 'a']], dtype=str) 529 | for i in range(3): 530 | for j in range(3): 531 | if abs(M1[i][j]) % 2 == 0: 532 | d_e[i][j] = 'e' 533 | else: 534 | d_e[i][j] = 'd' 535 | return d_e 536 | 537 | 538 | def self_test_b(a): 539 | z_b = np.eye(3, 3) 540 | M = a.copy() 541 | for i in range(3): 542 | if np.all(odd_even(M)[:, i] == ['d', 'd', 'd']): 543 | z_b[i][i] = 0.5 544 | break 545 | 546 | return z_b 547 | 548 | 549 | def binary_test_b(a): 550 | count = 0 551 | z_b = np.eye(3, 3) 552 | for i in [0, 1]: 553 | for j in [1, 2]: 554 | if i != j and count < 1: 555 | M = a.copy() 556 | M[:, j] = M[:, i] + M[:, j] 557 | if np.all(odd_even(M)[:, j] == ['d', 'd', 'd']): 558 | count = count + 1 559 | z_b[i][j] = 0.5 560 | z_b[j][j] = 0.5 561 | return z_b 562 | 563 | 564 | def tertiary_test_b(a): 565 | z_b = np.eye(3, 3) 566 | M = a.copy() 567 | M[:, 2] = M[:, 0] + M[:, 1] + M[:, 2] 568 | for k in range(3): 569 | if np.all(odd_even(M)[:, k] == ['d', 'd', 'd']): 570 | z_b[0][k] = 0.5 571 | z_b[1][k] = 0.5 572 | z_b[2][k] = 0.5 573 | break 574 | return z_b 575 | 576 | 577 | def body_centering(b): 578 | """ 579 | converting a single crystal minimal cell to a bcc one. 580 | """ 581 | z_b = np.eye(3, 3) 582 | while det(z_b) != 0.5: 583 | if det(self_test_b(b)) == 0.5: 584 | z_b = self_test_b(b) 585 | break 586 | if det(binary_test_b(b)) == 0.5: 587 | z_b = binary_test_b(b) 588 | break 589 | if det(tertiary_test_b(b)) == 0.5: 590 | z_b = tertiary_test_b(b) 591 | break 592 | return z_b 593 | 594 | 595 | def face_centering(a): 596 | """ 597 | converting a single crystal minimal cell to an fcc one. 598 | """ 599 | z_f = np.eye(3, 3) 600 | M = a.copy() 601 | count = 0 602 | for i in range(3): 603 | if (np.all(odd_even(M)[:, i] == ['d', 'd', 'e']) or 604 | np.all(odd_even(M)[:, i] == ['e', 'd', 'd']) or 605 | np.all(odd_even(M)[:, i] == ['d', 'e', 'd'])): 606 | count = count + 1 607 | z_f[i][i] = 0.5 608 | if det(z_f) == 0.25: 609 | return (z_f) 610 | else: 611 | for i in [0, 1]: 612 | for j in [1, 2]: 613 | if i != j and count < 2: 614 | M = a.copy() 615 | M[:, j] = M[:, i] + M[:, j] 616 | if (np.all(odd_even(M)[:, j] == ['d', 'd', 'e']) or 617 | np.all(odd_even(M)[:, j] == ['e', 'd', 'd']) or 618 | np.all(odd_even(M)[:, j] == ['d', 'e', 'd'])): 619 | count = count + 1 620 | z_f[i][j] = 0.5 621 | z_f[j][j] = 0.5 622 | 623 | return z_f if det(z_f) == 0.25 else None 624 | 625 | 626 | def DSC_vec(basis, sigma, minicell): 627 | """ 628 | a discrete shift complete (DSC) 629 | network for the given sigma and minimal cell. 630 | arguments: 631 | basis -- a lattice basis(fcc or bcc) 632 | sigma -- gb sigma 633 | minicell -- gb minimal cell 634 | """ 635 | D_sc = np.round(sigma * (inv(minicell).T), 6).astype(int) 636 | if basis == 'sc': 637 | D = D_sc.copy() 638 | if basis == 'bcc': 639 | D = dot(D_sc, body_centering(D_sc)) 640 | if basis == 'fcc' or basis == 'diamond': 641 | D = dot(D_sc, face_centering(D_sc)) 642 | return D 643 | 644 | 645 | def CSL_vec(basis, minicell): 646 | """ 647 | CSL minimal cell for sc, fcc and bcc. 648 | arguments: 649 | basis -- a lattice basis(fcc or bcc) 650 | minicell -- gb minimal cell 651 | """ 652 | C_sc = minicell.copy() 653 | if basis == 'sc': 654 | C = C_sc.copy() 655 | if basis == 'bcc': 656 | C = dot(C_sc, body_centering(C_sc)) 657 | if basis == 'fcc': 658 | C = dot(C_sc, face_centering(C_sc)) 659 | return C 660 | 661 | 662 | def DSC_on_plane(D, Pnormal): 663 | """ 664 | projects the given DSC network on a given plane. 665 | """ 666 | D_proj = np.zeros((3, 3)) 667 | Pnormal = np.array(Pnormal) 668 | for i in range(3): 669 | D_proj[:, i] = (D[:, i] - (dot(D[:, i], Pnormal) / norm(Pnormal)) * 670 | Pnormal/norm(Pnormal)) 671 | return D_proj 672 | 673 | 674 | def CSL_density(basis, minicell, plane): 675 | """ 676 | returns the CSL density of a given plane and its d_spacing. 677 | """ 678 | plane = np.array(plane) 679 | C = CSL_vec(basis, minicell) 680 | h = dot(C.T, plane) 681 | h = SmallestInteger(h)[0] 682 | h = CommonDivisor(h)[0] 683 | G = inv(dot(C.T, C)) 684 | hnorm = np.sqrt(dot(h.T, dot(G, h))) 685 | density = 1/(hnorm * det(C)) 686 | return (abs(density), 1/hnorm) 687 | 688 | # An auxilary function to help reduce the size of the small orthogonal cell 689 | # that is decided otherwise based on Sc for fcc and bcc 690 | 691 | 692 | def Ortho_fcc_bcc(basis, O1, O2): 693 | ortho1 = np.zeros((3, 3)) 694 | ortho2 = np.zeros((3, 3)) 695 | if basis == 'fcc': 696 | base = np.delete(Basis('fcc').T, 0, 1) 697 | elif basis == 'bcc': 698 | base = ((np.array([[0.5, 0.5, 0.5], [0.5, 0.5, -0.5], 699 | [-0.5, 0.5, 0.5]])).T) 700 | 701 | for i in range(3): 702 | Min_d = min(CommonDivisor(dot(O1[:, i], inv(base)))[1], 703 | CommonDivisor(dot(O2[:, i], inv(base)))[1]) 704 | ortho1[:, i] = O1[:, i] / Min_d 705 | ortho2[:, i] = O2[:, i] / Min_d 706 | return (ortho1, ortho2) 707 | # Writing to a yaml file that will be read by gb_generator 708 | 709 | 710 | def Write_to_io(axis, m, n, basis): 711 | """ 712 | an input file for gb_generator.py that can be customized. 713 | It also contains the output from the usage of csl_generator.py. 714 | """ 715 | 716 | my_dict = {'GB_plane': str([axis[0], axis[1], axis[2]]), 717 | 'lattice_parameter': '4', 718 | 'overlap_distance': '0.0', 'which_g': 'g1', 719 | 'rigid_trans': 'no', 'a': '10', 'b': '5', 720 | 'dimensions': '[1,1,1]', 721 | 'File_type': 'LAMMPS'} 722 | 723 | with open('io_file', 'w') as f: 724 | f.write('### input parameters for gb_generator.py ### \n') 725 | f.write('# CSL plane of interest that you read from the output of ' 726 | 'csl_generator as GB1 \n') 727 | f.write(list(my_dict.keys())[0] + ': ' + list(my_dict.values())[0] + 728 | '\n\n') 729 | f.write('# lattice parameter in Angstrom \n') 730 | f.write(list(my_dict.keys())[1] + ': ' + list(my_dict.values())[1] + 731 | '\n\n') 732 | f.write('# atoms that are closer than this fraction of the lattice ' 733 | 'parameter will be removed \n') 734 | f.write('# either from grain1 (g1) or from grain2 (g2). If you choose ' 735 | '0 no atoms will be removed \n') 736 | f.write(list(my_dict.keys())[2] + ': ' + list(my_dict.values())[2] + 737 | '\n\n') 738 | f.write('# decide which grain the atoms should be removed from \n') 739 | f.write(list(my_dict.keys())[3]+': ' + str(list(my_dict.values())[3]) + 740 | '\n\n') 741 | f.write('# decide whether you want rigid body translations to be done ' 742 | 'on the GB_plane or not (yes or no)\n') 743 | 744 | f.write('# When yes, for any GB aside from twist GBs, the two inplane \n' 745 | '# CSL vectors will be divided by integers a and b to produce a*b initial \n' 746 | '# configurations. The default values produce 50 initial structures \n' 747 | '# if you choose no for rigid_trans, you do not need to care about a and b. \n' 748 | '# twist boundaries are handled internally \n') 749 | 750 | f.write(list(my_dict.keys())[4] + ': ' + 751 | str(list(my_dict.values())[4]) + '\n') 752 | 753 | f.write(list(my_dict.keys())[5] + ': ' + 754 | str(list(my_dict.values())[5]) + '\n') 755 | 756 | f.write(list(my_dict.keys())[6] + ': ' + 757 | str(list(my_dict.values())[6]) + '\n\n') 758 | 759 | f.write('# dimensions of the supercell in: [l1,l2,l3], where l1 is' 760 | 'the direction along the GB_plane normal\n') 761 | f.write('# and l2 and l3 are inplane dimensions \n') 762 | f.write(list(my_dict.keys())[7] + ': ' + list(my_dict.values())[7] + 763 | '\n\n') 764 | f.write('# File type, either VASP or LAMMPS input \n') 765 | f.write(list(my_dict.keys())[8] + ': ' + list(my_dict.values())[8] + 766 | '\n\n\n') 767 | f.write('# The following is your csl_generator output.' 768 | ' YOU DO NOT NEED TO CHANGE THEM! \n\n') 769 | f.write('axis'+': ' + str([axis[0], axis[1], axis[2]]) + '\n') 770 | f.write('m' + ': ' + str(m) + '\n') 771 | f.write('n' + ': ' + str(n) + '\n') 772 | f.write('basis' + ': ' + str(basis) + '\n') 773 | 774 | f.close() 775 | return 776 | 777 | 778 | def main(): 779 | 780 | if (len(sys.argv) != 4 and len(sys.argv) != 5 and len(sys.argv) != 6 and 781 | len(sys.argv) != 7): 782 | print(__doc__) 783 | else: 784 | uvw = np.array([int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3])]) 785 | uvw = CommonDivisor(uvw)[0] 786 | 787 | if len(sys.argv) == 4: 788 | limit = 100 789 | print(" List of possible CSLs for {} axis sorted by Sigma " 790 | .format(str(uvw))) 791 | print_list(uvw, limit) 792 | print("\n Choose a basis, pick a sigma and use the second mode!\n") 793 | 794 | if len(sys.argv) == 5: 795 | 796 | try: 797 | limit = int(sys.argv[4]) 798 | print(" List of possible CSLs for {} axis sorted by Sigma " 799 | .format(str(uvw))) 800 | print_list(uvw, limit) 801 | print("\n Choose a basis, pick a sigma and use the second mode!\n") 802 | 803 | except: 804 | print(""" 805 | Careful! limit must be an integer ... 806 | """, __doc__) 807 | 808 | if len(sys.argv) == 6: 809 | 810 | lim = 2 811 | basis = str(sys.argv[4]) 812 | sigma = int(sys.argv[5]) 813 | 814 | try: 815 | _, m, n = get_theta_m_n_list(uvw, sigma)[0] 816 | Write_to_io(uvw, m, n, basis) 817 | 818 | print("----------List of possible CSL planes for Sigma {}---------" 819 | .format(sigma)) 820 | print(" GB1-------------------GB2-------------------Type----------" 821 | "Number of Atoms ") 822 | print_list_GB_Planes(uvw, basis, m, n, lim) 823 | print(" \nPick a GB plane and customize the io_file! ") 824 | print(" then run : python gb_generator.py io_file\n ") 825 | 826 | except: 827 | print("Your input sigma is wrong!") 828 | sys.exit() 829 | 830 | if len(sys.argv) == 7: 831 | 832 | basis = str(sys.argv[4]) 833 | sigma = int(sys.argv[5]) 834 | 835 | try: 836 | 837 | _, m, n = get_theta_m_n_list(uvw, sigma)[0] 838 | lim = int(sys.argv[6]) 839 | 840 | if lim > 10: 841 | print(2*'\n') 842 | print('You have chosen a large limit! It may take a while ...') 843 | print(2*'\n') 844 | 845 | print("----------List of possible CSL planes for Sigma {}---------" 846 | .format(sigma)) 847 | print(" GB1-------------------GB2-------------------Type----------" 848 | "Number of Atoms ") 849 | 850 | print_list_GB_Planes(uvw, basis, m, n, lim) 851 | print(" \nPick a GB plane and customize the io_file! ") 852 | print(" then run : gb_generator io_file\n ") 853 | 854 | except: 855 | print("Your input sigma is wrong!") 856 | sys.exit() 857 | 858 | return 859 | 860 | if __name__ == '__main__': 861 | main() 862 | -------------------------------------------------------------------------------- /gb_code/gb_generator.py: -------------------------------------------------------------------------------- 1 | 2 | # !/usr/bin/env python 3 | """ 4 | This module produces GB structures. You need to run csl_generator first 5 | to get the info necessary for your grain boundary of interest. 6 | 7 | The code runs in one mode only and takes all the necessary options to write 8 | a final GB structure from the input io_file, which is written after you run 9 | csl_generator.py. You must customize the io_file that comes with some default 10 | values. For ex.: input the GB_plane of interest from running the 11 | CSl_generator in the second mode. Once you have completed customizing the 12 | io_file, run: 13 | 14 | 'gb_generator.py io_file' 15 | """ 16 | 17 | import sys 18 | import numpy as np 19 | from numpy import dot, cross 20 | from numpy.linalg import det, norm 21 | import csl_generator as cslgen 22 | import warnings 23 | 24 | 25 | class GB_character: 26 | """ 27 | a grain boundary class, encompassing all the characteristics of a GB. 28 | """ 29 | def __init__(self): 30 | self.axis = np.array([1, 0, 0]) 31 | self.sigma = 1 32 | self.theta = 0 33 | self.m = 1 34 | self.n = 1 35 | self.R = np.eye(1) 36 | self.basis = 'fcc' 37 | self.LatP = 4.05 38 | self.gbplane = np.array([1, 1, 1]) 39 | self.ortho1 = np.eye(3) 40 | self.ortho2 = np.eye(3) 41 | self.ortho = np.eye(3) 42 | self.atoms = np.eye(3) 43 | self.atoms1 = np.eye(3) 44 | self.atoms2 = np.eye(3) 45 | self.rot1 = np.eye(3) 46 | self.rot2 = np.eye(3) 47 | self.Num = 0 48 | self.dim = np.array([1, 1, 1]) 49 | self.overD = 0 50 | self.whichG = 'g1' 51 | self.trans = False 52 | self.File = 'LAMMPS' 53 | 54 | def ParseGB(self, axis, basis, LatP, m, n, gb): 55 | """ 56 | parses the GB input axis, basis, lattice parameter, 57 | m and n integers and gb plane. 58 | """ 59 | self.axis = np.array(axis) 60 | self.m = int(m) 61 | self.n = int(n) 62 | self.sigma = cslgen.get_cubic_sigma(self.axis, self.m, self.n) 63 | self.theta = cslgen.get_cubic_theta(self.axis, self.m, self.n) 64 | self.R = cslgen.rot(self.axis, self.theta) 65 | 66 | if (str(basis) == 'fcc' or str(basis) == 'bcc' or str(basis) == 'sc' or 67 | str(basis) == 'diamond'): 68 | 69 | self.basis = str(basis) 70 | 71 | self.LatP = float(LatP) 72 | self.gbplane = np.array(gb) 73 | 74 | try: 75 | self.ortho1, self.ortho2, self.Num = \ 76 | cslgen.Find_Orthogonal_cell(self.basis, 77 | self.axis, 78 | self.m, 79 | self.n, 80 | self.gbplane) 81 | 82 | except: 83 | print(""" 84 | Could not find the orthogonal cells.... Most likely the 85 | input GB_plane is "NOT" a CSL plane. Go back to the first 86 | script and double check! 87 | """) 88 | sys.exit() 89 | else: 90 | print("Sorry! For now only works for cubic lattices ... ") 91 | sys.exit() 92 | 93 | def WriteGB(self, overlap=0.0, rigid=False, 94 | dim1=1, dim2=1, dim3=1, file='LAMMPS', 95 | **kwargs): 96 | """ 97 | parses the arguments overlap distance, dimensions, rigid body translate 98 | and file type and writes the final structure to the file. 99 | Possible keys: 100 | (whichG, a, b) 101 | """ 102 | self.overD = float(overlap) 103 | self.trans = rigid 104 | self.dim = np.array([int(dim1), int(dim2), int(dim3)]) 105 | self.File = file 106 | if self.overD > 0: 107 | try: 108 | self.whichG = kwargs['whichG'] 109 | except: 110 | print('decide on whichG!') 111 | sys.exit() 112 | if self.trans: 113 | try: 114 | a = int(kwargs['a']) 115 | b = int(kwargs['b']) 116 | except: 117 | print('Make sure the a and b integers are there!') 118 | sys.exit() 119 | self.Expand_Super_cell() 120 | xdel, _, x_indice, y_indice = self.Find_overlapping_Atoms() 121 | print("<<------ {} atoms are being removed! ------>>" 122 | .format(len(xdel))) 123 | 124 | if self.whichG == "G1" or self.whichG == "g1": 125 | self.atoms1 = np.delete(self.atoms1, x_indice, axis=0) 126 | 127 | elif self.whichG == "G2" or self.whichG == "g2": 128 | self.atoms2 = np.delete(self.atoms2, y_indice, axis=0) 129 | 130 | else: 131 | print("You must choose either 'g1', 'g2' ") 132 | sys.exit() 133 | if not self.trans: 134 | count = 0 135 | print("<<------ 1 GB structure is being created! ------>>") 136 | if self.File == "LAMMPS": 137 | self.Write_to_Lammps(count) 138 | elif self.File == "VASP": 139 | self.Write_to_Vasp(count) 140 | else: 141 | print("The output file must be either LAMMPS or VASP!") 142 | elif self.trans: 143 | self.Translate(a, b) 144 | 145 | elif self.overD == 0: 146 | if self.trans: 147 | try: 148 | a = int(kwargs['a']) 149 | b = int(kwargs['b']) 150 | except: 151 | print('Make sure the a and b integers are there!') 152 | sys.exit() 153 | print("<<------ 0 atoms are being removed! ------>>") 154 | self.Expand_Super_cell() 155 | self.Translate(a, b) 156 | 157 | else: 158 | self.Expand_Super_cell() 159 | count = 0 160 | print("<<------ 1 GB structure is being created! ------>>") 161 | if self.File == "LAMMPS": 162 | self.Write_to_Lammps(count) 163 | elif self.File == "VASP": 164 | self.Write_to_Vasp(count) 165 | else: 166 | print("The output file must be either LAMMPS or VASP!") 167 | else: 168 | print('Overlap distance is not inputted incorrectly!') 169 | sys.exit() 170 | 171 | def CSL_Ortho_unitcell_atom_generator(self): 172 | 173 | """ 174 | populates a unitcell from the orthogonal vectors. 175 | """ 176 | Or = self.ortho.T 177 | Orint = cslgen.integerMatrix(Or) 178 | LoopBound = np.zeros((3, 2), dtype=float) 179 | transformed = [] 180 | CubeCoords = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], 181 | [0, 1, 1], [1, 0, 1], [1, 1, 1], [0, 0, 0]], 182 | dtype=float) 183 | for i in range(len(CubeCoords)): 184 | transformed.append(np.dot(Orint.T, CubeCoords[i])) 185 | 186 | # Finding bounds for atoms in a CSL unitcell: 187 | 188 | LoopBound[0, :] = [min(np.array(transformed)[:, 0]), 189 | max(np.array(transformed)[:, 0])] 190 | LoopBound[1, :] = [min(np.array(transformed)[:, 1]), 191 | max(np.array(transformed)[:, 1])] 192 | LoopBound[2, :] = [min(np.array(transformed)[:, 2]), 193 | max(np.array(transformed)[:, 2])] 194 | 195 | # Filling up the unitcell: 196 | 197 | Tol = 1 198 | x = np.arange(LoopBound[0, 0] - Tol, LoopBound[0, 1] + Tol + 1, 1) 199 | y = np.arange(LoopBound[1, 0] - Tol, LoopBound[1, 1] + Tol + 1, 1) 200 | z = np.arange(LoopBound[2, 0] - Tol, LoopBound[2, 1] + Tol + 1, 1) 201 | V = len(x) * len(y) * len(z) 202 | indice = (np.stack(np.meshgrid(x, y, z)).T).reshape(V, 3) 203 | Base = cslgen.Basis(str(self.basis)) 204 | Atoms = [] 205 | tol = 0.001 206 | if V > 5e6: 207 | print("Warning! It may take a very long time" 208 | "to produce this cell!") 209 | # produce Atoms: 210 | 211 | for i in range(V): 212 | for j in range(len(Base)): 213 | Atoms.append(indice[i, 0:3] + Base[j, 0:3]) 214 | Atoms = np.array(Atoms) 215 | 216 | # Cell conditions 217 | Con1 = dot(Atoms, Or[0]) / norm(Or[0]) + tol 218 | Con2 = dot(Atoms, Or[1]) / norm(Or[1]) + tol 219 | Con3 = dot(Atoms, Or[2]) / norm(Or[2]) + tol 220 | # Application of the conditions: 221 | Atoms = (Atoms[(Con1 >= 0) & (Con1 <= norm(Or[0])) & (Con2 >= 0) & 222 | (Con2 <= norm(Or[1])) & 223 | (Con3 >= 0) & (Con3 <= norm(Or[2]))]) 224 | 225 | if len(Atoms) == (round(det(Or) * len(Base), 7)).astype(int): 226 | self.Atoms = Atoms 227 | else: 228 | self.Atoms = None 229 | return 230 | 231 | def CSL_Bicrystal_Atom_generator(self): 232 | """ 233 | builds the unitcells for both grains g1 and g2. 234 | """ 235 | Or_1 = self.ortho1.T 236 | Or_2 = self.ortho2.T 237 | self.rot1 = np.array([Or_1[0, :] / norm(Or_1[0, :]), 238 | Or_1[1, :] / norm(Or_1[1, :]), 239 | Or_1[2, :] / norm(Or_1[2, :])]) 240 | self.rot2 = np.array([Or_2[0, :] / norm(Or_2[0, :]), 241 | Or_2[1, :] / norm(Or_2[1, :]), 242 | Or_2[2, :] / norm(Or_2[2, :])]) 243 | 244 | self.ortho = self.ortho1.copy() 245 | self.CSL_Ortho_unitcell_atom_generator() 246 | self.atoms1 = self.Atoms 247 | 248 | self.ortho = self.ortho2.copy() 249 | self.CSL_Ortho_unitcell_atom_generator() 250 | self.atoms2 = self.Atoms 251 | 252 | self.atoms1 = dot(self.rot1, self.atoms1.T).T 253 | self.atoms2 = dot(self.rot2, self.atoms2.T).T 254 | # tol = 0.01 255 | self.atoms2[:, 0] = self.atoms2[:, 0] - norm(Or_2[0, :]) # - tol 256 | # print(self.atoms2, norm(Or_2[0, :]) ) 257 | return 258 | 259 | def Expand_Super_cell(self): 260 | """ 261 | expands the smallest CSL unitcell to the given dimensions. 262 | """ 263 | a = norm(self.ortho1[:, 0]) 264 | b = norm(self.ortho1[:, 1]) 265 | c = norm(self.ortho1[:, 2]) 266 | dimX, dimY, dimZ = self.dim 267 | 268 | X = self.atoms1.copy() 269 | Y = self.atoms2.copy() 270 | 271 | X_new = [] 272 | Y_new = [] 273 | for i in range(dimX): 274 | for j in range(dimY): 275 | for k in range(dimZ): 276 | Position1 = [i * a, j * b, k * c] 277 | Position2 = [-i * a, j * b, k * c] 278 | for l in range(len(X)): 279 | X_new.append(Position1[0:3] + X[l, 0:3]) 280 | for m in range(len(Y)): 281 | Y_new.append(Position2[0:3] + Y[m, 0:3]) 282 | 283 | self.atoms1 = np.array(X_new) 284 | self.atoms2 = np.array(Y_new) 285 | 286 | return 287 | 288 | def Find_overlapping_Atoms(self): 289 | 290 | """ 291 | finds the overlapping atoms. 292 | """ 293 | periodic_length = norm(self.ortho1[:, 0]) * self.dim[0] 294 | periodic_image = self.atoms2 + [periodic_length * 2, 0, 0] 295 | # select atoms contained in a smaller window around the GB and its 296 | # periodic image 297 | IndX = np.where([(self.atoms1[:, 0] < 1) | 298 | (self.atoms1[:, 0] > (periodic_length - 1))])[1] 299 | IndY = np.where([self.atoms2[:, 0] > -1])[1] 300 | IndY_image = np.where([periodic_image[:, 0] < 301 | (periodic_length + 1)])[1] 302 | X_new = self.atoms1[IndX] 303 | Y_new = np.concatenate((self.atoms2[IndY], periodic_image[IndY_image])) 304 | IndY_new = np.concatenate((IndY, IndY_image)) 305 | # create a meshgrid search routine 306 | x = np.arange(0, len(X_new), 1) 307 | y = np.arange(0, len(Y_new), 1) 308 | indice = (np.stack(np.meshgrid(x, y)).T).reshape(len(x) * len(y), 2) 309 | norms = norm(X_new[indice[:, 0]] - Y_new[indice[:, 1]], axis=1) 310 | indice_x = indice[norms < self.overD][:, 0] 311 | indice_y = indice[norms < self.overD][:, 1] 312 | X_del = X_new[indice_x] 313 | Y_del = Y_new[indice_y] 314 | 315 | if (len(X_del) != len(Y_del)): 316 | print("Warning! the number of deleted atoms" 317 | "in the two grains are not equal!") 318 | # print(type(IndX), len(IndY), len(IndY_image)) 319 | return (X_del, Y_del, IndX[indice_x], IndY_new[indice_y]) 320 | 321 | def Translate(self, a, b): 322 | 323 | """ 324 | translates the GB on a mesh created by a, b integers and writes 325 | to LAMMPS or VASP. 326 | """ 327 | tol = 0.001 328 | if (1 - cslgen.ang(self.gbplane, self.axis) < tol): 329 | 330 | M1, _ = cslgen.Create_minimal_cell_Method_1( 331 | self.sigma, self.axis, self.R) 332 | D = (1 / self.sigma * cslgen.DSC_vec(self.basis, self.sigma, M1)) 333 | Dvecs = cslgen.DSC_on_plane(D, self.gbplane) 334 | TransDvecs = np.round(dot(self.rot1, Dvecs), 7) 335 | shift1 = TransDvecs[:, 0] / 2 336 | shift2 = TransDvecs[:, 1] / 2 337 | a = b = 3 338 | else: 339 | # a = 10 340 | # b = 5 341 | if norm(self.ortho1[:, 1]) > norm(self.ortho1[:, 2]): 342 | 343 | shift1 = (1 / a) * (norm(self.ortho1[:, 1]) * 344 | np.array([0, 1, 0])) 345 | shift2 = (1 / b) * (norm(self.ortho1[:, 2]) * 346 | np.array([0, 0, 1])) 347 | else: 348 | shift1 = (1 / a) * (norm(self.ortho1[:, 2]) * 349 | np.array([0, 0, 1])) 350 | shift2 = (1 / b) * (norm(self.ortho1[:, 1]) * 351 | np.array([0, 1, 0])) 352 | print("<<------ {} GB structures are being created! ------>>" 353 | .format(int(a*b))) 354 | 355 | XX = self.atoms1 356 | count = 0 357 | if self.File == 'LAMMPS': 358 | 359 | for i in range(a): 360 | for j in range(b): 361 | count += 1 362 | shift = i * shift1 + j * shift2 363 | atoms1_new = XX.copy() + shift 364 | self.atoms1 = atoms1_new 365 | self.Write_to_Lammps(count) 366 | elif self.File == 'VASP': 367 | 368 | for i in range(a): 369 | for j in range(b): 370 | count += 1 371 | shift = i * shift1 + j * shift2 372 | atoms1_new = XX.copy() + shift 373 | self.atoms1 = atoms1_new 374 | self.Write_to_Vasp(count) 375 | else: 376 | print("The output file must be either LAMMPS or VASP!") 377 | 378 | def Write_to_Vasp(self, trans): 379 | """ 380 | write a single GB without translations to POSCAR. 381 | """ 382 | name = 'POS_G' 383 | plane = str(self.gbplane[0])+str(self.gbplane[1])+str(self.gbplane[2]) 384 | if self.overD > 0: 385 | overD = str(self.overD) 386 | else: 387 | overD = str(None) 388 | Trans = str(trans) 389 | # tol = 0.001 390 | X = self.atoms1.copy() 391 | Y = self.atoms2.copy() 392 | X_new = X * self.LatP 393 | Y_new = Y * self.LatP 394 | dimx, dimy, dimz = self.dim 395 | 396 | xlo = -1 * np.round(norm(self.ortho1[:, 0]) * dimx * self.LatP, 8) 397 | xhi = np.round(norm(self.ortho1[:, 0]) * dimx * self.LatP, 8) 398 | LenX = xhi - xlo 399 | ylo = 0.0 400 | yhi = np.round(norm(self.ortho1[:, 1]) * dimy * self.LatP, 8) 401 | LenY = yhi - ylo 402 | zlo = 0.0 403 | zhi = np.round(norm(self.ortho1[:, 2]) * dimz * self.LatP, 8) 404 | LenZ = zhi - zlo 405 | 406 | Wf = np.concatenate((X_new, Y_new)) 407 | 408 | with open(name + plane + '_' + overD + '_' + Trans, 'w') as f: 409 | f.write('#POSCAR written by GB_code \n') 410 | f.write('1 \n') 411 | f.write('{0:.8f} 0.0 0.0 \n'.format(LenX)) 412 | f.write('0.0 {0:.8f} 0.0 \n'.format(LenY)) 413 | f.write('0.0 0.0 {0:.8f} \n'.format(LenZ)) 414 | f.write('{} {} \n'.format(len(X), len(Y))) 415 | f.write('Cartesian\n') 416 | np.savetxt(f, Wf, fmt='%.8f %.8f %.8f') 417 | f.close() 418 | 419 | def Write_to_Lammps(self, trans): 420 | """ 421 | write a single GB without translations to LAMMPS. 422 | """ 423 | name = 'input_G' 424 | plane = str(self.gbplane[0])+str(self.gbplane[1])+str(self.gbplane[2]) 425 | if self.overD > 0: 426 | overD = str(self.overD) 427 | else: 428 | overD = str(None) 429 | Trans = str(trans) 430 | # tol = 0.001 431 | X = self.atoms1.copy() 432 | Y = self.atoms2.copy() 433 | 434 | NumberAt = len(X) + len(Y) 435 | X_new = X * self.LatP 436 | Y_new = Y * self.LatP 437 | dimx, dimy, dimz = self.dim 438 | 439 | xlo = -1 * np.round(norm(self.ortho1[:, 0]) * dimx * self.LatP, 8) 440 | xhi = np.round(norm(self.ortho1[:, 0]) * dimx * self.LatP, 8) 441 | ylo = 0.0 442 | yhi = np.round(norm(self.ortho1[:, 1]) * dimy * self.LatP, 8) 443 | zlo = 0.0 444 | zhi = np.round(norm(self.ortho1[:, 2]) * dimz * self.LatP, 8) 445 | 446 | Type1 = np.ones(len(X_new), int).reshape(1, -1) 447 | Type2 = 2 * np.ones(len(Y_new), int).reshape(1, -1) 448 | # Type = np.concatenate((Type1, Type2), axis=1) 449 | 450 | Counter = np.arange(1, NumberAt + 1).reshape(1, -1) 451 | 452 | # data = np.concatenate((X_new, Y_new)) 453 | W1 = np.concatenate((Type1.T, X_new), axis=1) 454 | W2 = np.concatenate((Type2.T, Y_new), axis=1) 455 | Wf = np.concatenate((W1, W2)) 456 | FinalMat = np.concatenate((Counter.T, Wf), axis=1) 457 | 458 | with open(name + plane + '_' + overD + '_' + Trans, 'w') as f: 459 | f.write('#Header \n \n') 460 | f.write('{} atoms \n \n'.format(NumberAt)) 461 | f.write('2 atom types \n \n') 462 | f.write('{0:.8f} {1:.8f} xlo xhi \n'.format(xlo, xhi)) 463 | f.write('{0:.8f} {1:.8f} ylo yhi \n'.format(ylo, yhi)) 464 | f.write('{0:.8f} {1:.8f} zlo zhi \n\n'.format(zlo, zhi)) 465 | f.write('Atoms \n \n') 466 | np.savetxt(f, FinalMat, fmt='%i %i %.8f %.8f %.8f') 467 | f.close() 468 | 469 | def __str__(self): 470 | return "GB_character" 471 | 472 | 473 | def main(): 474 | import yaml 475 | if len(sys.argv) == 2: 476 | io_file = sys.argv[1] 477 | file = open(io_file, 'r') 478 | in_params = yaml.load(file) 479 | 480 | try: 481 | axis = np.array(in_params['axis']) 482 | m = int(in_params['m']) 483 | n = int(in_params['n']) 484 | basis = str(in_params['basis']) 485 | gbplane = np.array(in_params['GB_plane']) 486 | LatP = in_params['lattice_parameter'] 487 | overlap = in_params['overlap_distance'] 488 | whichG = in_params['which_g'] 489 | rigid = in_params['rigid_trans'] 490 | a = in_params['a'] 491 | b = in_params['b'] 492 | dim1, dim2, dim3 = in_params['dimensions'] 493 | file = in_params['File_type'] 494 | 495 | except: 496 | print('Make sure the input argumnets in io_file are' 497 | 'put in correctly!') 498 | sys.exit() 499 | 500 | ################### 501 | 502 | gbI = GB_character() 503 | gbI.ParseGB(axis, basis, LatP, m, n, gbplane) 504 | gbI.CSL_Bicrystal_Atom_generator() 505 | 506 | if overlap > 0 and rigid: 507 | gbI.WriteGB( 508 | overlap=overlap, whichG=whichG, rigid=rigid, a=a, 509 | b=b, dim1=dim1, dim2=dim2, dim3=dim3, file=file 510 | ) 511 | elif overlap > 0 and not rigid: 512 | gbI.WriteGB( 513 | overlap=overlap, whichG=whichG, rigid=rigid, 514 | dim1=dim1, dim2=dim2, dim3=dim3, file=file 515 | ) 516 | elif overlap == 0 and rigid: 517 | gbI.WriteGB( 518 | overlap=overlap, rigid=rigid, a=a, 519 | b=b, dim1=dim1, dim2=dim2, dim3=dim3, 520 | file=file 521 | ) 522 | elif overlap == 0 and not rigid: 523 | gbI.WriteGB( 524 | overlap=overlap, rigid=rigid, 525 | dim1=dim1, dim2=dim2, dim3=dim3, file=file 526 | ) 527 | else: 528 | print(__doc__) 529 | return 530 | 531 | 532 | if __name__ == '__main__': 533 | main() 534 | -------------------------------------------------------------------------------- /paper/paper.bib: -------------------------------------------------------------------------------- 1 | @book{Sutton:1996, 2 | title={Interfaces in Crystalline Materials}, 3 | author={Sutton, A.P. and Balluffi, R.W.}, 4 | isbn={9780198500612}, 5 | lccn={94024731}, 6 | url={https://books.google.de/books?id=DMafQgAACAAJ}, 7 | year={1996}, 8 | publisher={Clarendon Press} 9 | } 10 | @book{Bollmann:1982, 11 | title={Crystal Lattices, Interfaces, Matrices: An Extension of Crystallography}, 12 | author={Bollmann, W.}, 13 | isbn={9782881050008}, 14 | url={https://books.google.de/books?id=oBt0QgAACAAJ}, 15 | year={1982}, 16 | publisher={W. Bollmann} 17 | } 18 | @article{LAMMPS:1995, 19 | Author = {Plimpton, Steve}, 20 | Journal = {Journal of Computational Physics}, 21 | Number = {1}, 22 | Pages = {1-19}, 23 | Title = {Fast Parallel Algorithms for Short-Range Molecular Dynamics}, 24 | Volume = {117}, 25 | doi = {10.1006/jcph.1995.1039}, 26 | Year = {1995}} 27 | 28 | @article{VASP:1996, 29 | author = {Kresse, G. and Furthm\"uller, J.}, 30 | journal = {Phys. Rev. B}, 31 | month = {Oct}, 32 | pages = {11169--11186}, 33 | publisher = {American Physical Society}, 34 | title = {Efficient iterative schemes for \textit{ab initio} 35 | total-energy calculations using a plane-wave basis 36 | set}, 37 | volume = {54}, 38 | year = {1996}, 39 | doi = {10.1103/PhysRevB.54.11169}, 40 | url = {http://link.aps.org/doi/10.1103/PhysRevB.54.11169}, 41 | } 42 | @article{Grimmer:1974, 43 | author = {Grimmer, H. and Bollmann, W. and Warrington, D. H.}, 44 | title = {Coincidence-site lattices and complete pattern-shift in cubic crystals}, 45 | journal = {Acta Crystallographica Section A}, 46 | volume = {30}, 47 | number = {2}, 48 | pages = {197-207}, 49 | doi = {10.1107/S056773947400043X}, 50 | year = {1974} 51 | } 52 | @article{Pub1, 53 | author = {Hadian, R. and Grabowski, B. and Race, C. P. and Neugebauer, J.}, 54 | title = {Atomistic migration mechanisms of atomically flat, stepped, and kinked grain boundaries}, 55 | journal = {Physical Review B}, 56 | volume = {94}, 57 | number = {16}, 58 | doi = {10.1103/PhysRevB.94.165413}, 59 | year = {2016} 60 | } 61 | @article{Pub2, 62 | author = {Hadian, R. and Grabowski, B. and Finnis, M. W. and Neugebauer, J.}, 63 | title = {Migration mechanisms of a faceted grain boundary}, 64 | journal = {Physical Review Materials}, 65 | volume = {2}, 66 | number = {4}, 67 | doi = {10.1103/PhysRevMaterials.2.043601}, 68 | year = {2018} 69 | } 70 | @misc{GB_code, 71 | author = {R. Hadian}, 72 | title = {GB_code}, 73 | year = {2018}, 74 | publisher = {GitHub}, 75 | journal = {GitHub repository}, 76 | howpublished = {\url{https://github.com/oekosheri/GB_code}}, 77 | } 78 | @misc{Marcin, 79 | author = {Marcin Wojdyr}, 80 | title = {gosam}, 81 | year = {2013}, 82 | publisher = {GitHub}, 83 | journal = {GitHub repository}, 84 | howpublished = {\url{https://github.com/wojdyr/gosam/blob/master/csl.py}}, 85 | } 86 | -------------------------------------------------------------------------------- /paper/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'GB\_code: A grain boundary generation code' 3 | tags: 4 | - Python 5 | - grain boundary 6 | - crystallography 7 | - CSL 8 | - atomistic simulations (VASP, LAMMPS) 9 | authors: 10 | - name: R. Hadian 11 | orcid: 0000-0002-9616-4602 12 | affiliation: 1 13 | - name: B. Grabowski 14 | orcid: 0000-0003-4281-5665 15 | affiliation: 1 16 | - name: J. Neugebauer 17 | affiliation: 1 18 | affiliations: 19 | - name: Max-Planck-Institut fuer Eisenforschung, Duesseldorf, Germany 20 | index: 1 21 | date: 19 July 2018 22 | bibliography: paper.bib 23 | --- 24 | 25 | # Summary 26 | 27 | Grain boundaries (GBs) are crystalline borders between single crystals in materials microstructure. They play an important role in mechanical, chemical or electronic response of materials and are therefore essential to materials science and physics. 28 | 29 | GBs are geometrical entities with a large parameter space that has been well formulated within a coincident site lattice (CSL) mathematical framework [@Sutton:1996]. One important computational advantage of the CSL formalism is that it enables the construction of GBs in a periodic setup for atomistic simulations. ``GB_code`` [@GB_code] uses the CSL construction to generate GB atomic structures (currently for cubic materials) systematically. It provides input atomic structures for large-scale atomistic simulations with interatomic potentials (as implemented e.g. in ``LAMMPS`` [@LAMMPS:1995]) or _ab initio_, density-functional-theory (DFT) simulations (as implemented e.g. in ``VASP`` [@VASP:1996]). These atomistic codes can further calculate different properties of the GBs. In addition to providing the input structures, the ``csl_generator.py`` script and the attached Jupyter notebooks have extra functionality to show how the CSL properties can be used to locate, classify and categorize different GBs and to extract detailed information about them. 30 | 31 | ``GB_code`` is designed to be a command line tool as it is documented in detail in the README file of the repository, 32 | but the modules can also be accessed separately for example via the attached Jupyter notebooks. The code consists of two main scripts, ``csl_generator.py`` and ``gb_generator.py``, that should be used in this order to produce the final GB structures. The attached Jupyter notebooks in the Test directory, ``Usage_of_GB_code.ipynb`` and ``Dichromatic_pattern_CSL.ipynb``, input the two scripts as modules. The former addresses the general usage of the code with some extra tips and functions to locate GBs of interest, the latter depicts how CSL properties such as the overlapping patterns and displacement shift complete (DSC) vectors can be extracted and visualized. In the notebooks, two examples of the usage of the ``GB_code`` in our previous publications [@Pub1, @Pub2] have been shown as well. 33 | 34 | ``GB_code`` uses the analytical and mathematical formulations of the following works of @Sutton:1996, @Bollmann:1982, @Grimmer:1974. Some functionality from the code by @Marcin on CSL has been used in a modified form in the ``GB_code``. 35 | 36 | ### Statement of need: 37 | ``GB_code`` is an interactive toolbox to learn about grain boundaries and it is versatile for running high-throughput calculations. The target audience is students/scientists of materials science and physics at any level of familiarity with the topic. Extensive use of the NumPy library in ``GB_code`` results in faster execution, especially when computing large structures. The user will be guided through in a step-by-step manner on how to find and create the GB of interest. The code has been designed to be simple to use and instructive with a special attention to GB plane orientation, which is often lacking in other grain boundary creation codes. 38 | 39 | # Acknowledgements 40 | 41 | R. Hadian would like to thank professor Mike Finnis for fruitful discussions. Funding from the European Union’s Horizon 2020 research and innovation programme (Grant agreement No. 639211) is also gratefully acknowledged. 42 | 43 | # References 44 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | numpy==1.14.3 3 | matplotlib==2.2.2 4 | pandas==0.23.0 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | packages = [ 4 | 'gb_code', 5 | ] 6 | 7 | INSTALL_REQUIRES = ( 8 | ['numpy >= 1.14.0'] ) 9 | 10 | setup( 11 | name='GB_code', 12 | python_requires='>3.5.1', 13 | version='1.0.0', 14 | author='R.Hadian', 15 | author_email='shahrzadhadian@gmail.com', 16 | packages=packages, 17 | install_requires=INSTALL_REQUIRES, 18 | entry_points = { 19 | 'console_scripts': [ 20 | 'csl_generator = gb_code.csl_generator:main', 21 | 'gb_generator = gb_code.gb_generator:main', 22 | ], 23 | }, 24 | url='https://github.com/oekosheri/GB_code', 25 | license='LICENSE', 26 | description='A GB generation code', 27 | long_description=open('README.md').read(), 28 | ) 29 | --------------------------------------------------------------------------------