Automatic restarts of namd2 on patch grid changes

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

You may also like...

Leave a Reply

Your email address will not be published.