Coverage for /builds/ase/ase/ase/calculators/gulp.py : 27.01%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""This module defines an ASE interface to GULP.
3Written by:
5Andy Cuko <andi.cuko@upmc.fr>
6Antoni Macia <tonimacia@gmail.com>
8EXPORT ASE_GULP_COMMAND="/path/to/gulp < PREFIX.gin > PREFIX.got"
10Keywords
11Options
13"""
14import os
15import re
16import numpy as np
17from ase.units import eV, Ang
18from ase.calculators.calculator import FileIOCalculator, ReadError
21class GULPOptimizer:
22 def __init__(self, atoms, calc):
23 self.atoms = atoms
24 self.calc = calc
26 def todict(self):
27 return {'type': 'optimization',
28 'optimizer': 'GULPOptimizer'}
30 def run(self, fmax=None, steps=None, **gulp_kwargs):
31 if fmax is not None:
32 gulp_kwargs['gmax'] = fmax
33 if steps is not None:
34 gulp_kwargs['maxcyc'] = steps
36 self.calc.set(**gulp_kwargs)
37 self.atoms.calc = self.calc
38 self.atoms.get_potential_energy()
39 self.atoms.cell = self.calc.get_atoms().cell
40 self.atoms.positions[:] = self.calc.get_atoms().positions
43class GULP(FileIOCalculator):
44 implemented_properties = ['energy', 'free_energy', 'forces', 'stress']
45 command = 'gulp < PREFIX.gin > PREFIX.got'
46 discard_results_on_any_change = True
47 default_parameters = dict(
48 keywords='conp gradients',
49 options=[],
50 shel=[],
51 library="ffsioh.lib",
52 conditions=None)
54 def get_optimizer(self, atoms):
55 gulp_keywords = self.parameters.keywords.split()
56 if 'opti' not in gulp_keywords:
57 raise ValueError('Can only create optimizer from GULP calculator '
58 'with "opti" keyword. Current keywords: {}'
59 .format(gulp_keywords))
61 opt = GULPOptimizer(atoms, self)
62 return opt
64 def __init__(self, restart=None,
65 ignore_bad_restart_file=FileIOCalculator._deprecated,
66 label='gulp', atoms=None, optimized=None,
67 Gnorm=1000.0, steps=1000, conditions=None, **kwargs):
68 """Construct GULP-calculator object."""
69 FileIOCalculator.__init__(self, restart, ignore_bad_restart_file,
70 label, atoms, **kwargs)
71 self.optimized = optimized
72 self.Gnorm = Gnorm
73 self.steps = steps
74 self.conditions = conditions
75 self.library_check()
76 self.atom_types = []
78 # GULP prints the fractional coordinates before the Final
79 # lattice vectors so they need to be stored and then atoms
80 # positions need to be set after we get the Final lattice
81 # vectors
82 self.fractional_coordinates = None
84 def write_input(self, atoms, properties=None, system_changes=None):
85 FileIOCalculator.write_input(self, atoms, properties, system_changes)
86 p = self.parameters
88 # Build string to hold .gin input file:
89 s = p.keywords
90 s += '\ntitle\nASE calculation\nend\n\n'
92 if all(self.atoms.pbc):
93 cell_params = self.atoms.cell.cellpar()
94 # Formating is necessary since Gulp max-line-length restriction
95 s += 'cell\n{0:9.6f} {1:9.6f} {2:9.6f} ' \
96 '{3:8.5f} {4:8.5f} {5:8.5f}\n'.format(*cell_params)
97 s += 'frac\n'
98 coords = self.atoms.get_scaled_positions()
99 else:
100 s += 'cart\n'
101 coords = self.atoms.get_positions()
103 if self.conditions is not None:
104 c = self.conditions
105 labels = c.get_atoms_labels()
106 self.atom_types = c.get_atom_types()
107 else:
108 labels = self.atoms.get_chemical_symbols()
110 for xyz, symbol in zip(coords, labels):
111 s += ' {0:2} core' \
112 ' {1:10.7f} {2:10.7f} {3:10.7f}\n' .format(symbol, *xyz)
113 if symbol in p.shel:
114 s += ' {0:2} shel' \
115 ' {1:10.7f} {2:10.7f} {3:10.7f}\n' .format(symbol, *xyz)
117 if p.library:
118 s += '\nlibrary {0}\n'.format(p.library)
120 if p.options:
121 for t in p.options:
122 s += '%s\n' % t
123 with open(self.prefix + '.gin', 'w') as fd:
124 fd.write(s)
126 def read_results(self):
127 FileIOCalculator.read(self, self.label)
128 if not os.path.isfile(self.label + '.got'):
129 raise ReadError
131 with open(self.label + '.got') as fd:
132 lines = fd.readlines()
134 cycles = -1
135 self.optimized = None
136 for i, line in enumerate(lines):
137 m = re.match(r'\s*Total lattice energy\s*=\s*(\S+)\s*eV', line)
138 if m:
139 energy = float(m.group(1))
140 self.results['energy'] = energy
141 self.results['free_energy'] = energy
143 elif line.find('Optimisation achieved') != -1:
144 self.optimized = True
146 elif line.find('Final Gnorm') != -1:
147 self.Gnorm = float(line.split()[-1])
149 elif line.find('Cycle:') != -1:
150 cycles += 1
152 elif line.find('Final Cartesian derivatives') != -1:
153 s = i + 5
154 forces = []
155 while True:
156 s = s + 1
157 if lines[s].find("------------") != -1:
158 break
159 if lines[s].find(" s ") != -1:
160 continue
161 g = lines[s].split()[3:6]
162 G = [-float(x) * eV / Ang for x in g]
163 forces.append(G)
164 forces = np.array(forces)
165 self.results['forces'] = forces
167 elif line.find('Final internal derivatives') != -1:
168 s = i + 5
169 forces = []
170 while True:
171 s = s + 1
172 if lines[s].find("------------") != -1:
173 break
174 g = lines[s].split()[3:6]
176 # Uncomment the section below to separate the numbers when
177 # there is no space between them, in the case of long
178 # numbers. This prevents the code to break if numbers are
179 # too big.
181 '''for t in range(3-len(g)):
182 g.append(' ')
183 for j in range(2):
184 min_index=[i+1 for i,e in enumerate(g[j][1:])
185 if e == '-']
186 if j==0 and len(min_index) != 0:
187 if len(min_index)==1:
188 g[2]=g[1]
189 g[1]=g[0][min_index[0]:]
190 g[0]=g[0][:min_index[0]]
191 else:
192 g[2]=g[0][min_index[1]:]
193 g[1]=g[0][min_index[0]:min_index[1]]
194 g[0]=g[0][:min_index[0]]
195 break
196 if j==1 and len(min_index) != 0:
197 g[2]=g[1][min_index[0]:]
198 g[1]=g[1][:min_index[0]]'''
200 G = [-float(x) * eV / Ang for x in g]
201 forces.append(G)
202 forces = np.array(forces)
203 self.results['forces'] = forces
205 elif line.find('Final cartesian coordinates of atoms') != -1:
206 s = i + 5
207 positions = []
208 while True:
209 s = s + 1
210 if lines[s].find("------------") != -1:
211 break
212 if lines[s].find(" s ") != -1:
213 continue
214 xyz = lines[s].split()[3:6]
215 XYZ = [float(x) * Ang for x in xyz]
216 positions.append(XYZ)
217 positions = np.array(positions)
218 self.atoms.set_positions(positions)
220 elif line.find('Final stress tensor components') != -1:
221 res = [0., 0., 0., 0., 0., 0.]
222 for j in range(3):
223 var = lines[i + j + 3].split()[1]
224 res[j] = float(var)
225 var = lines[i + j + 3].split()[3]
226 res[j + 3] = float(var)
227 stress = np.array(res)
228 self.results['stress'] = stress
230 elif line.find('Final Cartesian lattice vectors') != -1:
231 lattice_vectors = np.zeros((3, 3))
232 s = i + 2
233 for j in range(s, s + 3):
234 temp = lines[j].split()
235 for k in range(3):
236 lattice_vectors[j - s][k] = float(temp[k])
237 self.atoms.set_cell(lattice_vectors)
238 if self.fractional_coordinates is not None:
239 self.fractional_coordinates = np.array(
240 self.fractional_coordinates)
241 self.atoms.set_scaled_positions(
242 self.fractional_coordinates)
244 elif line.find('Final fractional coordinates of atoms') != -1:
245 s = i + 5
246 scaled_positions = []
247 while True:
248 s = s + 1
249 if lines[s].find("------------") != -1:
250 break
251 if lines[s].find(" s ") != -1:
252 continue
253 xyz = lines[s].split()[3:6]
254 XYZ = [float(x) for x in xyz]
255 scaled_positions.append(XYZ)
256 self.fractional_coordinates = scaled_positions
258 self.steps = cycles
260 def get_opt_state(self):
261 return self.optimized
263 def get_opt_steps(self):
264 return self.steps
266 def get_Gnorm(self):
267 return self.Gnorm
269 def library_check(self):
270 if self.parameters['library'] is not None:
271 if 'GULP_LIB' not in os.environ:
272 raise RuntimeError("Be sure to have set correctly $GULP_LIB "
273 "or to have the force field library.")
276class Conditions:
277 """Atomic labels for the GULP calculator.
279 This class manages an array similar to
280 atoms.get_chemical_symbols() via get_atoms_labels() method, but
281 with atomic labels in stead of atomic symbols. This is useful
282 when you need to use calculators like GULP or lammps that use
283 force fields. Some force fields can have different atom type for
284 the same element. In this class you can create a set_rule()
285 function that assigns labels according to structural criteria."""
287 def __init__(self, atoms):
288 self.atoms = atoms
289 self.atoms_symbols = atoms.get_chemical_symbols()
290 self.atoms_labels = atoms.get_chemical_symbols()
291 self.atom_types = []
293 def min_distance_rule(self, sym1, sym2,
294 ifcloselabel1=None, ifcloselabel2=None,
295 elselabel1=None, max_distance=3.0):
296 """Find pairs of atoms to label based on proximity.
298 This is for, e.g., the ffsioh or catlow force field, where we
299 would like to identify those O atoms that are close to H
300 atoms. For each H atoms, we must specially label one O atom.
302 This function is a rule that allows to define atom labels (like O1,
303 O2, O_H etc..) starting from element symbols of an Atoms
304 object that a force field can use and according to distance
305 parameters.
307 Example:
308 atoms = read('some_xyz_format.xyz')
309 a = Conditions(atoms)
310 a.set_min_distance_rule('O', 'H', ifcloselabel1='O2',
311 ifcloselabel2='H', elselabel1='O1')
312 new_atoms_labels = a.get_atom_labels()
314 In the example oxygens O are going to be labeled as O2 if they
315 are close to a hydrogen atom othewise are labeled O1.
317 """
319 if ifcloselabel1 is None:
320 ifcloselabel1 = sym1
321 if ifcloselabel2 is None:
322 ifcloselabel2 = sym2
323 if elselabel1 is None:
324 elselabel1 = sym1
326 # self.atom_types is a list of element types used instead of element
327 # symbols in orger to track the changes made. Take care of this because
328 # is very important.. gulp_read function that parse the output
329 # has to know which atom_type it has to associate with which
330 # atom_symbol
331 #
332 # Example: [['O','O1','O2'],['H', 'H_C', 'H_O']]
333 # this because Atoms oject accept only atoms symbols
334 self.atom_types.append([sym1, ifcloselabel1, elselabel1])
335 self.atom_types.append([sym2, ifcloselabel2])
337 dist_mat = self.atoms.get_all_distances()
338 index_assigned_sym1 = []
339 index_assigned_sym2 = []
341 for i in range(len(self.atoms_symbols)):
342 if self.atoms_symbols[i] == sym2:
343 dist_12 = 1000
344 index_assigned_sym2.append(i)
345 for t in range(len(self.atoms_symbols)):
346 if (self.atoms_symbols[t] == sym1
347 and dist_mat[i, t] < dist_12
348 and t not in index_assigned_sym1):
349 dist_12 = dist_mat[i, t]
350 closest_sym1_index = t
351 index_assigned_sym1.append(closest_sym1_index)
353 for i1, i2 in zip(index_assigned_sym1, index_assigned_sym2):
354 if dist_mat[i1, i2] > max_distance:
355 raise ValueError('Cannot unambiguously apply minimum-distance '
356 'rule because pairings are not obvious. '
357 'If you wish to ignore this, then increase '
358 'max_distance.')
360 for s in range(len(self.atoms_symbols)):
361 if s in index_assigned_sym1:
362 self.atoms_labels[s] = ifcloselabel1
363 elif s not in index_assigned_sym1 and self.atoms_symbols[s] == sym1:
364 self.atoms_labels[s] = elselabel1
365 elif s in index_assigned_sym2:
366 self.atoms_labels[s] = ifcloselabel2
368 def get_atom_types(self):
369 return self.atom_types
371 def get_atoms_labels(self):
372 labels = np.array(self.atoms_labels)
373 return labels