11""" creation of the structure file content
2-
3- The code here is largely adapted from https://github.com/andeplane/cif2cell-lammps
4- ESPInterfaces.LAMMPSFile
52"""
6- from math import cos , sin , atan2
7- import ase
3+ # import ase
84import numpy as np
95
106
11- def get_vector (value ):
12- return [float (v ) for v in value ]
13-
14-
15- def get_matrix (value ):
16- return [[float (v ) for v in vec ] for vec in value ]
17-
18-
19- def mvmult3 (mat , vec ):
20- """ matrix-vector multiplication """
21- w = [0. , 0. , 0. ]
22- for i in range (3 ):
23- t = 0
24- for j in range (3 ):
25- t = t + mat [j ][i ] * vec [j ]
26- w [i ] = t
27- return w
28-
29-
30- def cartesian_to_frac (lattice , ccoords ):
31- """convert cartesian coordinate to fractional
7+ def transform_cell (cell ):
8+ """transform the cell to an orientation, compatible with LAMMPS
329
33- Parameters
34- ----------
35- lattice: list
36- 3x3 array of lattice vectors
37- ccoord: list
38- Nx3 cartesian coordinates
10+ LAMMPS requires the simulation cell to be in the format of a
11+ lower triangular matrix (right-handed basis).
12+ Therefore the cell and positions may require rotation and inversion.
13+ See https://lammps.sandia.gov/doc/Howto_triclinic.html
3914
40- Returns
41- -------
42- list:
43- Nx3 array of fractional coordinate
15+ :param cell: (3x3) lattice
16+ :returns: (new_cell, transform)
4417
4518 """
46- det3 = np .linalg .det
47- latt_tr = np .transpose (lattice )
48- det_latt_tr = np .linalg .det (latt_tr )
49-
50- fcoords = []
51- for ccoord in ccoords :
52- a = (det3 ([[ccoord [0 ], latt_tr [0 ][1 ], latt_tr [0 ][2 ]],
53- [ccoord [1 ], latt_tr [1 ][1 ], latt_tr [1 ][2 ]],
54- [ccoord [2 ], latt_tr [2 ][1 ], latt_tr [2 ][2 ]]])) / det_latt_tr
55- b = (det3 ([[latt_tr [0 ][0 ], ccoord [0 ], latt_tr [0 ][2 ]],
56- [latt_tr [1 ][0 ], ccoord [1 ], latt_tr [1 ][2 ]],
57- [latt_tr [2 ][0 ], ccoord [2 ], latt_tr [2 ][2 ]]])) / det_latt_tr
58- c = (det3 ([[latt_tr [0 ][0 ], latt_tr [0 ][1 ], ccoord [0 ]],
59- [latt_tr [1 ][0 ], latt_tr [1 ][1 ], ccoord [1 ]],
60- [latt_tr [2 ][0 ], latt_tr [2 ][1 ], ccoord [2 ]]])) / det_latt_tr
61-
62- fcoords .append ([a , b , c ])
63-
64- return fcoords
65-
66-
67- def is_not_zero (value ):
68- return not np .isclose (value , 0 )
69-
70-
71- def round_by (value , round_dp ):
72- if round_dp is None :
73- return value
74- return round (value , round_dp )
19+ cell = np .array (cell )
20+ transform , upper_tri = np .linalg .qr (cell .T , mode = "complete" )
21+ new_cell = np .transpose (upper_tri )
7522
23+ # LAMMPS also requires positive values on the diagonal of the,
24+ # so invert cell if necessary
25+ inversion = np .eye (3 )
26+ for i in range (3 ):
27+ if new_cell [i ][i ] < 0.0 :
28+ inversion [i ][i ] = - 1.0
29+ new_cell = np .dot (inversion , new_cell .T ).T
30+ transform = np .dot (transform , inversion .T ).T
7631
77- class AtomSite (object ):
78- def __init__ (self , kind_name , cartesian , fractional = None ):
79- self .kind_name = kind_name
80- self .cartesian = cartesian
81- self .fractional = fractional
32+ return new_cell , transform
8233
8334
8435def generate_lammps_structure (structure ,
@@ -100,98 +51,45 @@ def generate_lammps_structure(structure,
10051 docstring : str
10152 docstring to put at top of file
10253
54+ Returns
55+ -------
56+ str: content
57+ the structure file content
58+ numpy.array: transform
59+ the transformation matrix applied to the structure cell and coordinates
60+
10361 """
10462 if atom_style not in ['atomic' , 'charge' ]:
10563 raise ValueError ("atom_style must be in ['atomic', 'charge']" )
10664 if charge_dict is None :
10765 charge_dict = {}
10866
109- atom_sites = [AtomSite (site .kind_name , site .position )
110- for site in structure .sites ]
11167 # mapping of atom kind_name to id number
11268 kind_name_id_map = {}
113- for site in atom_sites :
69+ for site in structure . sites :
11470 if site .kind_name not in kind_name_id_map :
11571 kind_name_id_map [site .kind_name ] = len (kind_name_id_map ) + 1
11672 # mapping of atom kind_name to mass
11773 kind_mass_dict = {kind .name : kind .mass for kind in structure .kinds }
11874
11975 filestring = ""
12076 filestring += "# {}\n \n " .format (docstring )
121- filestring += "{0} atoms\n " .format (len (atom_sites ))
77+ filestring += "{0} atoms\n " .format (len (structure . sites ))
12278 filestring += "{0} atom types\n \n " .format (len (kind_name_id_map ))
12379
124- lattice = get_matrix (structure .cell )
125-
126- # As per https://lammps.sandia.gov/doc/Howto_triclinic.html,
127- # if the lattice does not conform to a regular parallelpiped
128- # then it must first be rotated
129-
130- if is_not_zero (lattice [0 ][1 ]) or is_not_zero (lattice [0 ][2 ]) or is_not_zero (lattice [1 ][2 ]):
131- rotated_cell = True
132- for site in atom_sites :
133- site .fractional = cartesian_to_frac (lattice , [site .cartesian ])[0 ]
134- # creating the cell from its lengths and angles,
135- # generally ensures that it is in a compatible orientation
136- atoms = ase .Atoms (cell = structure .cell_lengths + structure .cell_angles )
137- lattice = get_matrix (atoms .cell )
138- else :
139- rotated_cell = False
140-
141- if is_not_zero (lattice [0 ][1 ]):
142- theta = atan2 (- lattice [0 ][1 ], lattice [0 ][0 ])
143- rot_matrix = get_matrix ([
144- [cos (theta ), sin (theta ), 0 ],
145- [- sin (theta ), cos (theta ), 0 ],
146- [0 , 0 , 1 ]
147- ])
148- lattice [0 ] = get_vector (mvmult3 (rot_matrix , lattice [0 ]))
149- lattice [1 ] = get_vector (mvmult3 (rot_matrix , lattice [1 ]))
150- lattice [2 ] = get_vector (mvmult3 (rot_matrix , lattice [2 ]))
151-
152- if is_not_zero (lattice [0 ][2 ]):
153- theta = atan2 (- lattice [0 ][2 ], lattice [0 ][0 ])
154- rot_matrix = get_matrix ([
155- [cos (theta ), sin (theta ), 0 ],
156- [0 , 1 , 0 ],
157- [- sin (theta ), cos (theta ), 0 ]
158- ])
159- lattice [0 ] = get_vector (mvmult3 (rot_matrix , lattice [0 ]))
160- lattice [1 ] = get_vector (mvmult3 (rot_matrix , lattice [1 ]))
161- lattice [2 ] = get_vector (mvmult3 (rot_matrix , lattice [2 ]))
80+ atoms = structure .get_ase ()
81+ cell , coord_transform = transform_cell (atoms .cell )
82+ positions = np .transpose (np .dot (coord_transform , np .transpose (atoms .positions )))
16283
163- if is_not_zero (lattice [1 ][2 ]):
164- theta = atan2 (- lattice [1 ][2 ], lattice [1 ][1 ])
165- rot_matrix = get_matrix ([
166- [1 , 0 , 0 ],
167- [0 , cos (theta ), sin (theta )],
168- [0 , - sin (theta ), cos (theta )]
169- ])
170- lattice [0 ] = get_vector (mvmult3 (rot_matrix , lattice [0 ]))
171- lattice [1 ] = get_vector (mvmult3 (rot_matrix , lattice [1 ]))
172- lattice [2 ] = get_vector (mvmult3 (rot_matrix , lattice [2 ]))
84+ if round_dp :
85+ cell = np .round (cell , round_dp ) + 0.
86+ positions = np .round (positions , round_dp ) + 0.
17387
174- if is_not_zero (lattice [0 ][1 ]) or is_not_zero (lattice [0 ][2 ]) or is_not_zero (lattice [1 ][2 ]) or lattice [0 ][0 ] < 1e-9 or lattice [1 ][1 ] < 1e-9 or lattice [2 ][2 ] < 1e-9 :
175- raise ValueError (
176- "Error in triclinic box: {}\n "
177- "Vectors should follow these rules: "
178- "https://lammps.sandia.gov/doc/Howto_triclinic.html" .format (lattice ))
179-
180- a = round_by (lattice [0 ][0 ], round_dp )
181- b = round_by (lattice [1 ][1 ], round_dp )
182- c = round_by (lattice [2 ][2 ], round_dp )
183-
184- filestring += "0.0 {0:20.10f} xlo xhi\n " .format (a )
185- filestring += "0.0 {0:20.10f} ylo yhi\n " .format (b )
186- filestring += "0.0 {0:20.10f} zlo zhi\n " .format (c )
187-
188- xy = round_by (lattice [1 ][0 ], round_dp )
189- xz = round_by (lattice [2 ][0 ], round_dp )
190- yz = round_by (lattice [2 ][1 ], round_dp )
191-
192- if is_not_zero (xy ) or is_not_zero (xz ) or is_not_zero (yz ):
193- filestring += "{0:20.10f} {1:20.10f} {2:20.10f} xy xz yz\n \n " .format (
194- xy , xz , yz )
88+ filestring += "0.0 {0:20.10f} xlo xhi\n " .format (cell [0 ][0 ])
89+ filestring += "0.0 {0:20.10f} ylo yhi\n " .format (cell [1 ][1 ])
90+ filestring += "0.0 {0:20.10f} zlo zhi\n " .format (cell [2 ][2 ])
91+ filestring += "{0:20.10f} {1:20.10f} {2:20.10f} xy xz yz\n \n " .format (
92+ cell [1 ][0 ], cell [2 ][0 ], cell [2 ][1 ])
19593
19694 filestring += 'Masses\n \n '
19795 for kind_name in sorted (list (kind_name_id_map .keys ())):
@@ -201,12 +99,7 @@ def generate_lammps_structure(structure,
20199
202100 filestring += "Atoms\n \n "
203101
204- for site_index , site in enumerate (atom_sites ):
205- if rotated_cell :
206- pos = get_vector (mvmult3 (lattice , site .fractional ))
207- else :
208- pos = site .cartesian
209- pos = [round_by (v , round_dp ) for v in pos ]
102+ for site_index , (pos , site ) in enumerate (zip (positions , structure .sites )):
210103
211104 kind_id = kind_name_id_map [site .kind_name ]
212105
@@ -218,75 +111,6 @@ def generate_lammps_structure(structure,
218111 filestring += "{0} {1} {2} {3:20.10f} {4:20.10f} {5:20.10f}\n " .format (
219112 site_index + 1 , kind_id , charge , pos [0 ], pos [1 ], pos [2 ])
220113 else :
221- raise ValueError ('atom_style' )
222-
223- return filestring
224-
225-
226- def old_generate_lammps_structure (structure , atom_style ):
227- """ this is the deprecated method, used before 0.3.0b3,
228- stored here for prosperity.
229-
230- This method can create erroneous structures for triclinic cells
231- """
232- import numpy as np
233-
234- types = [site .kind_name for site in structure .sites ]
235-
236- type_index_unique = np .unique (types , return_index = True )[1 ]
237- count_index_unique = np .diff (np .append (type_index_unique , [len (types )]))
238-
239- atom_index = []
240- for i , index in enumerate (count_index_unique ):
241- atom_index += [i for j in range (index )]
242-
243- masses = [site .mass for site in structure .kinds ]
244- positions = [site .position for site in structure .sites ]
245-
246- number_of_atoms = len (positions )
247-
248- lammps_data_file = 'Generated using dynaphopy\n \n '
249- lammps_data_file += '{0} atoms\n \n ' .format (number_of_atoms )
250- lammps_data_file += '{0} atom types\n \n ' .format (len (masses ))
251-
252- cell = np .array (structure .cell )
253-
254- a = np .linalg .norm (cell [0 ])
255- b = np .linalg .norm (cell [1 ])
256- c = np .linalg .norm (cell [2 ])
257-
258- alpha = np .arccos (np .dot (cell [1 ], cell [2 ]) / (c * b ))
259- gamma = np .arccos (np .dot (cell [1 ], cell [0 ]) / (a * b ))
260- beta = np .arccos (np .dot (cell [2 ], cell [0 ]) / (a * c ))
261-
262- xhi = a
263- xy = b * np .cos (gamma )
264- xz = c * np .cos (beta )
265- yhi = np .sqrt (pow (b , 2 ) - pow (xy , 2 ))
266- yz = (b * c * np .cos (alpha ) - xy * xz ) / yhi
267- zhi = np .sqrt (pow (c , 2 ) - pow (xz , 2 ) - pow (yz , 2 ))
268-
269- xhi = xhi + max (0 , 0 , xy , xz , xy + xz )
270- yhi = yhi + max (0 , 0 , yz )
271-
272- lammps_data_file += '\n {0:20.10f} {1:20.10f} xlo xhi\n ' .format (0 , xhi )
273- lammps_data_file += '{0:20.10f} {1:20.10f} ylo yhi\n ' .format (0 , yhi )
274- lammps_data_file += '{0:20.10f} {1:20.10f} zlo zhi\n ' .format (0 , zhi )
275- lammps_data_file += '{0:20.10f} {1:20.10f} {2:20.10f} xy xz yz\n \n ' .format (
276- xy , xz , yz )
277-
278- lammps_data_file += 'Masses\n \n '
279-
280- for i , mass in enumerate (masses ):
281- lammps_data_file += '{0} {1:20.10f} \n ' .format (i + 1 , mass )
282-
283- lammps_data_file += '\n Atoms\n \n '
284- for i , row in enumerate (positions ):
285- if atom_style == 'charge' :
286- lammps_data_file += '{0} {1} 0.0 {2:20.10f} {3:20.10f} {4:20.10f}\n ' .format (
287- i + 1 , atom_index [i ] + 1 , row [0 ], row [1 ], row [2 ])
288- else :
289- lammps_data_file += '{0} {1} {2:20.10f} {3:20.10f} {4:20.10f}\n ' .format (
290- i + 1 , atom_index [i ] + 1 , row [0 ], row [1 ], row [2 ])
114+ raise ValueError ('atom_style unknown: {}' .format (atom_style ))
291115
292- return lammps_data_file
116+ return filestring , coord_transform
0 commit comments