Coverage for /builds/ase/ase/ase/calculators/amber.py : 13.15%

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 Amber16.
3Usage: (Tested only with Amber16, http://ambermd.org/)
5Before usage, input files (infile, topologyfile, incoordfile)
7"""
9import subprocess
10import numpy as np
12from ase.calculators.calculator import Calculator, FileIOCalculator
13import ase.units as units
14from scipy.io import netcdf
17class Amber(FileIOCalculator):
18 """Class for doing Amber classical MM calculations.
20 Example:
22 mm.in::
24 Minimization with Cartesian restraints
25 &cntrl
26 imin=1, maxcyc=200, (invoke minimization)
27 ntpr=5, (print frequency)
28 &end
29 """
31 implemented_properties = ['energy', 'forces']
32 discard_results_on_any_change = True
34 def __init__(self, restart=None,
35 ignore_bad_restart_file=FileIOCalculator._deprecated,
36 label='amber', atoms=None, command=None,
37 amber_exe='sander -O ',
38 infile='mm.in', outfile='mm.out',
39 topologyfile='mm.top', incoordfile='mm.crd',
40 outcoordfile='mm_dummy.crd', mdcoordfile=None,
41 **kwargs):
42 """Construct Amber-calculator object.
44 Parameters
45 ==========
46 label: str
47 Name used for all files. May contain a directory.
48 atoms: Atoms object
49 Optional Atoms object to which the calculator will be
50 attached. When restarting, atoms will get its positions and
51 unit-cell updated from file.
52 label: str
53 Prefix to use for filenames (label.in, label.txt, ...).
54 amber_exe: str
55 Name of the amber executable, one can add options like -O
56 and other parameters here
57 infile: str
58 Input filename for amber, contains instuctions about the run
59 outfile: str
60 Logfilename for amber
61 topologyfile: str
62 Name of the amber topology file
63 incoordfile: str
64 Name of the file containing the input coordinates of atoms
65 outcoordfile: str
66 Name of the file containing the output coordinates of atoms
67 this file is not used in case minisation/dynamics is done by ase.
68 It is only relevant
69 if you run MD/optimisation many steps with amber.
71 """
73 self.out = 'mm.log'
75 self.positions = None
76 self.atoms = None
78 self.set(**kwargs)
80 self.amber_exe = amber_exe
81 self.infile = infile
82 self.outfile = outfile
83 self.topologyfile = topologyfile
84 self.incoordfile = incoordfile
85 self.outcoordfile = outcoordfile
86 self.mdcoordfile = mdcoordfile
87 if command is not None:
88 self.command = command
89 else:
90 self.command = (self.amber_exe +
91 ' -i ' + self.infile +
92 ' -o ' + self.outfile +
93 ' -p ' + self.topologyfile +
94 ' -c ' + self.incoordfile +
95 ' -r ' + self.outcoordfile)
96 if self.mdcoordfile is not None:
97 self.command = self.command + ' -x ' + self.mdcoordfile
99 FileIOCalculator.__init__(self, restart, ignore_bad_restart_file,
100 label, atoms, **kwargs)
102 def write_input(self, atoms=None, properties=None, system_changes=None):
103 """Write updated coordinates to a file."""
105 FileIOCalculator.write_input(self, atoms, properties, system_changes)
106 self.write_coordinates(atoms)
108 def read_results(self):
109 """ read energy and forces """
110 self.read_energy()
111 self.read_forces()
113 def write_coordinates(self, atoms, filename=''):
114 """ write amber coordinates in netCDF format,
115 only rectangular unit cells are allowed"""
116 if filename == '':
117 filename = self.incoordfile
118 fout = netcdf.netcdf_file(filename, 'w')
119 # dimension
120 fout.Conventions = 'AMBERRESTART'
121 fout.ConventionVersion = "1.0"
122 fout.title = 'Ase-generated-amber-restart-file'
123 fout.application = "AMBER"
124 fout.program = "ASE"
125 fout.programVersion = "1.0"
126 fout.createDimension('cell_spatial', 3)
127 fout.createDimension('label', 5)
128 fout.createDimension('cell_angular', 3)
129 fout.createDimension('time', 1)
130 time = fout.createVariable('time', 'd', ('time',))
131 time.units = 'picosecond'
132 time[0] = 0
133 fout.createDimension('spatial', 3)
134 spatial = fout.createVariable('spatial', 'c', ('spatial',))
135 spatial[:] = np.asarray(list('xyz'))
136 # spatial = 'xyz'
138 natom = len(atoms)
139 fout.createDimension('atom', natom)
140 coordinates = fout.createVariable('coordinates', 'd',
141 ('atom', 'spatial'))
142 coordinates.units = 'angstrom'
143 coordinates[:] = atoms.get_positions()[:]
145 if atoms.get_velocities() is not None:
146 velocities = fout.createVariable('velocities', 'd',
147 ('atom', 'spatial'))
148 velocities.units = 'angstrom/picosecond'
149 velocities[:] = atoms.get_velocities()[:]
151 # title
152 cell_angular = fout.createVariable('cell_angular', 'c',
153 ('cell_angular', 'label'))
154 cell_angular[0] = np.asarray(list('alpha'))
155 cell_angular[1] = np.asarray(list('beta '))
156 cell_angular[2] = np.asarray(list('gamma'))
158 # title
159 cell_spatial = fout.createVariable('cell_spatial', 'c',
160 ('cell_spatial',))
161 cell_spatial[0], cell_spatial[1], cell_spatial[2] = 'a', 'b', 'c'
163 # data
164 cell_lengths = fout.createVariable('cell_lengths', 'd',
165 ('cell_spatial',))
166 cell_lengths.units = 'angstrom'
167 cell_lengths[0] = atoms.get_cell()[0, 0]
168 cell_lengths[1] = atoms.get_cell()[1, 1]
169 cell_lengths[2] = atoms.get_cell()[2, 2]
171 cell_angles = fout.createVariable('cell_angles', 'd',
172 ('cell_angular',))
173 box_alpha, box_beta, box_gamma = 90.0, 90.0, 90.0
174 cell_angles[0] = box_alpha
175 cell_angles[1] = box_beta
176 cell_angles[2] = box_gamma
178 cell_angles.units = 'degree'
179 fout.close()
181 def read_coordinates(self, atoms, filename=''):
182 """Import AMBER16 netCDF restart files.
184 Reads atom positions and
185 velocities (if available),
186 and unit cell (if available)
188 This may be useful if you have run amber many steps and
189 want to read new positions and velocities
190 """
192 if filename == '':
193 filename = self.outcoordfile
195 from scipy.io import netcdf
196 import numpy as np
197 import ase.units as units
199 fin = netcdf.netcdf_file(filename, 'r')
200 all_coordinates = fin.variables['coordinates'][:]
201 get_last_frame = False
202 if hasattr(all_coordinates, 'ndim'):
203 if all_coordinates.ndim == 3:
204 get_last_frame = True
205 elif hasattr(all_coordinates, 'shape'):
206 if len(all_coordinates.shape) == 3:
207 get_last_frame = True
208 if get_last_frame:
209 all_coordinates = all_coordinates[-1]
210 atoms.set_positions(all_coordinates)
211 if 'velocities' in fin.variables:
212 all_velocities = fin.variables['velocities'][:] / (1000 * units.fs)
213 if get_last_frame:
214 all_velocities = all_velocities[-1]
215 atoms.set_velocities(all_velocities)
216 if 'cell_lengths' in fin.variables:
217 all_abc = fin.variables['cell_lengths']
218 if get_last_frame:
219 all_abc = all_abc[-1]
220 a, b, c = all_abc
221 all_angles = fin.variables['cell_angles']
222 if get_last_frame:
223 all_angles = all_angles[-1]
224 alpha, beta, gamma = all_angles
226 if (all(angle > 89.99 for angle in [alpha, beta, gamma]) and
227 all(angle < 90.01 for angle in [alpha, beta, gamma])):
228 atoms.set_cell(
229 np.array([[a, 0, 0],
230 [0, b, 0],
231 [0, 0, c]]))
232 atoms.set_pbc(True)
233 else:
234 raise NotImplementedError('only rectangular cells are'
235 ' implemented in ASE-AMBER')
237 else:
238 atoms.set_pbc(False)
240 def read_energy(self, filename='mden'):
241 """ read total energy from amber file """
242 with open(filename, 'r') as fd:
243 lines = fd.readlines()
244 self.results['energy'] = \
245 float(lines[16].split()[2]) * units.kcal / units.mol
247 def read_forces(self, filename='mdfrc'):
248 """ read forces from amber file """
249 fd = netcdf.netcdf_file(filename, 'r')
250 try:
251 forces = fd.variables['forces']
252 self.results['forces'] = forces[-1, :, :] \
253 / units.Ang * units.kcal / units.mol
254 finally:
255 fd.close()
257 def set_charges(self, selection, charges, parmed_filename=None):
258 """ Modify amber topology charges to contain the updated
259 QM charges, needed in QM/MM.
260 Using amber's parmed program to change charges.
261 """
262 qm_list = list(selection)
263 with open(parmed_filename, 'w') as fout:
264 fout.write('# update the following QM charges \n')
265 for i, charge in zip(qm_list, charges):
266 fout.write('change charge @' + str(i + 1) + ' ' +
267 str(charge) + ' \n')
268 fout.write('# Output the topology file \n')
269 fout.write('outparm ' + self.topologyfile + ' \n')
270 parmed_command = ('parmed -O -i ' + parmed_filename +
271 ' -p ' + self.topologyfile +
272 ' > ' + self.topologyfile + '.log 2>&1')
273 subprocess.check_call(parmed_command, shell=True, cwd=self.directory)
275 def get_virtual_charges(self, atoms):
276 with open(self.topologyfile, 'r') as fd:
277 topology = fd.readlines()
278 for n, line in enumerate(topology):
279 if '%FLAG CHARGE' in line:
280 chargestart = n + 2
281 lines1 = topology[chargestart:(chargestart
282 + (len(atoms) - 1) // 5 + 1)]
283 mm_charges = []
284 for line in lines1:
285 for el in line.split():
286 mm_charges.append(float(el) / 18.2223)
287 charges = np.array(mm_charges)
288 return charges
290 def add_virtual_sites(self, positions):
291 return positions # no virtual sites
293 def redistribute_forces(self, forces):
294 return forces
297def map(atoms, top):
298 p = np.zeros((2, len(atoms)), dtype="int")
300 elements = atoms.get_chemical_symbols()
301 unique_elements = np.unique(atoms.get_chemical_symbols())
303 for i in range(len(unique_elements)):
304 idx = 0
305 for j in range(len(atoms)):
306 if elements[j] == unique_elements[i]:
307 idx += 1
308 symbol = unique_elements[i] + np.str(idx)
309 for k in range(len(atoms)):
310 if top.atoms[k].name == symbol:
311 p[0, k] = j
312 p[1, j] = k
313 break
314 return p
317try:
318 import sander
319 have_sander = True
320except ImportError:
321 have_sander = False
324class SANDER(Calculator):
325 """
326 Interface to SANDER using Python interface
328 Requires sander Python bindings from http://ambermd.org/
329 """
330 implemented_properties = ['energy', 'forces']
332 def __init__(self, atoms=None, label=None, top=None, crd=None,
333 mm_options=None, qm_options=None, permutation=None, **kwargs):
334 if not have_sander:
335 raise RuntimeError("sander Python module could not be imported!")
336 Calculator.__init__(self, label, atoms)
337 self.permutation = permutation
338 if qm_options is not None:
339 sander.setup(top, crd.coordinates, crd.box, mm_options, qm_options)
340 else:
341 sander.setup(top, crd.coordinates, crd.box, mm_options)
343 def calculate(self, atoms, properties, system_changes):
344 Calculator.calculate(self, atoms, properties, system_changes)
345 if system_changes:
346 if 'energy' in self.results:
347 del self.results['energy']
348 if 'forces' in self.results:
349 del self.results['forces']
350 if 'energy' not in self.results:
351 if self.permutation is None:
352 crd = np.reshape(atoms.get_positions(), (1, len(atoms), 3))
353 else:
354 crd = np.reshape(atoms.get_positions()
355 [self.permutation[0, :]], (1, len(atoms), 3))
356 sander.set_positions(crd)
357 e, f = sander.energy_forces()
358 self.results['energy'] = e.tot * units.kcal / units.mol
359 if self.permutation is None:
360 self.results['forces'] = (np.reshape(np.array(f),
361 (len(atoms), 3)) *
362 units.kcal / units.mol)
363 else:
364 ff = np.reshape(np.array(f), (len(atoms), 3)) * \
365 units.kcal / units.mol
366 self.results['forces'] = ff[self.permutation[1, :]]
367 if 'forces' not in self.results:
368 if self.permutation is None:
369 crd = np.reshape(atoms.get_positions(), (1, len(atoms), 3))
370 else:
371 crd = np.reshape(atoms.get_positions()[self.permutation[0, :]],
372 (1, len(atoms), 3))
373 sander.set_positions(crd)
374 e, f = sander.energy_forces()
375 self.results['energy'] = e.tot * units.kcal / units.mol
376 if self.permutation is None:
377 self.results['forces'] = (np.reshape(np.array(f),
378 (len(atoms), 3)) *
379 units.kcal / units.mol)
380 else:
381 ff = np.reshape(np.array(f), (len(atoms), 3)) * \
382 units.kcal / units.mol
383 self.results['forces'] = ff[self.permutation[1, :]]