If you do NPT simulations with namd2, you may get the following message:
FATAL ERROR: Periodic cell has become too small for original patch grid!
Unfortunately, namd2 does not support automatic restarts in this case. Here is a small wrapper script, that automates this process. Just prepare your namd.conf as usual but omit the DCDfile and restartname keywords. Then call the wrapper
namd_keep_running.py template/namd.conf template/ /storage/somewhere/ --scratchdir='/local_scratch/somewhere/'
The configuration file should be suitable for simulations working only on a copy of the template directory. The simulation run will be split into a minimization and MD part. After completion, the data will be copied from the scratch directory to the storage directory given as third parameter. The results are collected in a folder named after the PBS job id or after the current process id (if invoked outside of the queueing system).
#!/usr/bin/env python
# system modules
import argparse
import os
import shutil
import subprocess
import sys
# argument parsing
parser = argparse.ArgumentParser(description='Automatically restarts namd2 on patch grid changes.')
parser.add_argument('configfile', nargs=1, help='namd config file')
parser.add_argument('templatedir', nargs=1, help='namd template folder according to relative filenames in configfile. Should be prepared to contain the configuration file.')
parser.add_argument('outputdir', nargs=1, help='Folder to save the results to.')
parser.add_argument('--scratchdir', nargs='?', default='/tmp/', help='Scratch directory.')
args = parser.parse_args()
# sanitize input
def checkfolder(value):
if value[-1] != '/':
value += '/'
if value[0] != '/':
value = os.getcwd() + '/' + value
return value
args.scratchdir = checkfolder(args.scratchdir)
args.outputdir = checkfolder(args.outputdir[0])
args.templatedir = args.templatedir[0]
args.configfile = args.configfile[0]
# config file input
def get_key_val(line):
parts = line.split('#')
line = parts[0].strip()
parts = line.split('=')
if len(parts) > 2:
raise ValueError('Malformatted config line')
if len(parts) == 2:
return tuple(parts)
parts = parts[0].split()
if len(parts) < 2:
return (None, None)
return (parts[0], ' '.join(parts[1:]))
try:
fh = open(args.configfile)
except:
print 'Unable to read config file %s.' % args.configfile
exit(1)
lines = fh.readlines()
# parse config file
firstline = len(lines)
basiclines = []
first_run_lines = []
due = dict([('minimize', 0), ('run', 0)])
for no, line in enumerate(lines):
try:
key, value = get_key_val(line)
except ValueError:
print 'Syntax error on line %d. Omitting line.' % (no+1)
if key is None:
continue
basicline = True
if key.lower() == 'binaryrestart':
if value.lower() != 'yes':
print 'Please use binary restart files. namd2 default is yes.'
exit(1)
if key.lower() == 'dcdfile':
print 'Please remove the DCDfile keyword. It will be activated automatically.'
exit(1)
if key.lower() == 'restartname':
print 'Please remove the restartname keyword. It will be activated automatically.'
exit(1)
if key.lower() in ('minimize', 'run'):
if due[key.lower()] != 0:
print 'Ignoring former %s step count. Taking the one from line %d.' % (key.lower(), no+1)
due[key.lower()] = int(value)
firstline = min(firstline, no)
basicline = False
if key.lower() == 'temperature':
basicline = False
first_run_lines.append(line)
if basicline:
basiclines.append(line)
# prepare folder structure
try:
folder = os.environ['PBS_JOBID']
except:
folder = str(os.getpid())
try:
os.mkdir(args.scratchdir + folder)
except OSError:
print 'Tempdir already exists.'
exit(1)
initial_cwd = os.getcwd()
try:
shutil.copytree(args.templatedir, args.scratchdir + folder + '/minimizedir')
shutil.copytree(args.templatedir, args.scratchdir + folder + '/rundir')
except:
pass
# prepare namd runs
def start_section(mode, minlastbasename=None):
mode_due = due[mode]
run = 0
steps = 0
while steps < mode_due:
print 'mode: %s, run %d' % (mode, run)
# build namd config
basename = '%s-%03d' % (mode, run)
prevbasename = '%s-%03d' % (mode, run-1)
wh = open(basename + '.conf', 'w')
for line in basiclines:
wh.write(line)
if run == 0:
if mode == 'run':
wh.write('bincoordinates ../minimizedir/%s.coor\n' % minlastbasename)
wh.write('binvelocities ../minimizedir/%s.vel\n' % minlastbasename)
wh.write('extendedSystem ../minimizedir/%s.xsc\n' % minlastbasename)
else:
for line in first_run_lines:
wh.write(line)
else:
wh.write('bincoordinates %s.coor\n' % prevbasename)
wh.write('binvelocities %s.vel\n' % prevbasename)
wh.write('extendedSystem %s.xsc\n' % prevbasename)
wh.write('DCDfile %s.dcd\n' % basename)
wh.write('restartname %s\n' % basename)
wh.write('%s %d\n' % (mode, mode_due-steps))
wh.close()
# start namd run
wlog = open(basename + '.log', 'w')
retval = subprocess.call(['namd2', basename + '.conf'], stdout=wlog, stderr=subprocess.STDOUT)
wlog.close()
if retval == 0:
return basename
# check whether the patch grid was reason for exit
rlog = open(basename + '.log', 'r')
patch_grid_is_reason = False
last_restart_step = 0
for line in rlog.readlines():
if line.startswith('FATAL ERROR: Periodic cell has become too small for original patch grid!'):
patch_grid_is_reason = True
if line.startswith('WRITING EXTENDED SYSTEM TO RESTART FILE AT STEP'):
last_restart_step = int(line[48:])
if not patch_grid_is_reason:
print 'Unable to complete %s.' % mode
print 'See %s/%sdir/%s.log for details.' % (args.scratchdir + folder, mode, basename)
exit(1)
# update counters
run += 1
steps += last_restart_step
return basename
# perform calculations
os.chdir(args.scratchdir + folder + '/minimizedir')
minlastbasename = start_section('minimize')
os.chdir(args.scratchdir + folder + '/rundir')
start_section('run', minlastbasename)
# retain data
try:
shutil.copytree(args.scratchdir + folder, args.outputdir + folder)
except:
print 'Error on copying results.'
pass