git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@10174 f3b2605a-c512-4ea7-a41b-209d697bcdaa
This commit is contained in:
119
tools/moltemplate/src/extract_lammps_data.py
Executable file
119
tools/moltemplate/src/extract_lammps_data.py
Executable file
@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
lammps_data_sections = set(['Atoms',
|
||||
'Masses',
|
||||
'Bonds',
|
||||
'Bond Coeffs',
|
||||
'Angles',
|
||||
'Angle Coeffs',
|
||||
'Dihedrals',
|
||||
'Dihedral Coeffs',
|
||||
'Impropers',
|
||||
'Improper Coeffs',
|
||||
'BondBond Coeffs', # class2 angles
|
||||
'BondAngle Coeffs', # class2 angles
|
||||
'MiddleBondTorsion Coeffs', # class2 dihedrals
|
||||
'EndBondTorsion Coeffs', # class2 dihedrals
|
||||
'AngleTorsion Coeffs', # class2 dihedrals
|
||||
'AngleAngleTorsion Coeffs', # class2 dihedrals
|
||||
'BondBond13 Coeffs', # class2 dihedrals
|
||||
'AngleAngle Coeffs', # class2 impropers
|
||||
'Angles By Type', # new. not standard LAMMPS
|
||||
'Dihedrals By Type',# new. not standard LAMMPS
|
||||
'Angles By Type']) # new. not standard LAMMPS
|
||||
|
||||
|
||||
def DeleteComments(string,
|
||||
escape='\\',
|
||||
comment_char='#'):
|
||||
escaped_state = False
|
||||
for i in range(0,len(string)):
|
||||
if string[i] in escape:
|
||||
if escaped_state:
|
||||
escaped_state = False
|
||||
else:
|
||||
escaped_state = True
|
||||
elif string[i] == comment_char:
|
||||
if not escaped_state:
|
||||
return string[0:i]
|
||||
return string
|
||||
|
||||
|
||||
|
||||
def ExtractDataSection(f,
|
||||
section_name,
|
||||
comment_char = '#',
|
||||
include_section_name = False,
|
||||
return_line_nums = False):
|
||||
|
||||
inside_section = False
|
||||
if section_name in ('header','Header'): #"Header" section includes beginning
|
||||
inside_section = True
|
||||
|
||||
nonblank_encountered = False
|
||||
nonheader_encountered = False
|
||||
|
||||
i = 0
|
||||
for line_orig in f:
|
||||
return_this_line = False
|
||||
line = DeleteComments(line_orig).strip()
|
||||
if line in lammps_data_sections:
|
||||
nonheader_encountered = True
|
||||
if section_name in ('header', 'Header'):
|
||||
# The "header" section includes all lines at the beginning of the
|
||||
# before any other section is encountered.
|
||||
if nonheader_encountered:
|
||||
return_this_line = False
|
||||
else:
|
||||
return_this_line = True
|
||||
elif line == section_name:
|
||||
inside_section = True
|
||||
nonblank_encountered = False
|
||||
if include_section_name:
|
||||
return_this_line = True
|
||||
# A block of blank lines (which dont immediately follow
|
||||
# the section_name) signal the end of a section:
|
||||
elif len(line) == 0:
|
||||
if inside_section and include_section_name:
|
||||
return_this_line = True
|
||||
if nonblank_encountered:
|
||||
inside_section = False
|
||||
elif line[0] != comment_char:
|
||||
if inside_section:
|
||||
nonblank_encountered = True
|
||||
return_this_line = True
|
||||
|
||||
if return_this_line:
|
||||
if return_line_nums:
|
||||
yield i
|
||||
else:
|
||||
yield line_orig
|
||||
|
||||
i += 1
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
import sys
|
||||
lines = sys.stdin.readlines()
|
||||
exclude_sections = False
|
||||
if sys.argv[1] == '-n':
|
||||
exclude_sections = True
|
||||
del sys.argv[1]
|
||||
|
||||
if not exclude_sections:
|
||||
for section_name in sys.argv[1:]:
|
||||
for line in ExtractDataSection(lines, section_name):
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
line_nums_exclude = set([])
|
||||
for section_name in sys.argv[1:]:
|
||||
for line_num in ExtractDataSection(lines,
|
||||
section_name,
|
||||
include_section_name=True,
|
||||
return_line_nums=True):
|
||||
line_nums_exclude.add(line_num)
|
||||
for i in range(0, len(lines)):
|
||||
if i not in line_nums_exclude:
|
||||
sys.stdout.write(lines[i])
|
||||
2036
tools/moltemplate/src/ltemplify.py
Executable file
2036
tools/moltemplate/src/ltemplify.py
Executable file
File diff suppressed because it is too large
Load Diff
745
tools/moltemplate/src/lttree.py
Executable file
745
tools/moltemplate/src/lttree.py
Executable file
@ -0,0 +1,745 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Author: Andrew Jewett (jewett.aij at g mail)
|
||||
# http://www.chem.ucsb.edu/~sheagroup
|
||||
# License: 3-clause BSD License (See LICENSE.TXT)
|
||||
# Copyright (c) 2011, Regents of the University of California
|
||||
# All rights reserved.
|
||||
|
||||
"""
|
||||
lttree.py
|
||||
|
||||
lttree.py is an extension of the generic ttree.py program.
|
||||
This version can understand and manipulate ttree-style templates which
|
||||
are specialized for storing molecule-specific data for use in LAMMPS.
|
||||
|
||||
The main difference between lttree.py and ttree.py is:
|
||||
Unlike ttree.py, lttree.py understands rigid-body movement commands like
|
||||
"rot()" and "move()" which allows it to reorient and move each copy
|
||||
of a molecule to a new location. (ttree.py just ignores these commands.
|
||||
Consequently LAMMPS input file (fragments) created with ttree.py have
|
||||
invalid (overlapping) atomic coordinates and must be modified or aguemted
|
||||
later (by loading atomic coordinates from a PDB file or an XYZ file).
|
||||
lttree.py understands the "Data Atoms" section of a LAMMPS
|
||||
data file (in addition to the various "atom_styles" which effect it).
|
||||
|
||||
Additional LAMMPS-specific features may be added in the future.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
from ttree import *
|
||||
from lttree_styles import *
|
||||
from ttree_matrix_stack import *
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
# Python 3
|
||||
basestring = unicode = str
|
||||
|
||||
|
||||
|
||||
|
||||
class LttreeSettings(BasicUISettings):
|
||||
def __init__(self,
|
||||
user_bindings_x=None,
|
||||
user_bindings=None,
|
||||
order_method='by_command'):
|
||||
|
||||
BasicUISettings.__init__(self,
|
||||
user_bindings_x,
|
||||
user_bindings,
|
||||
order_method)
|
||||
|
||||
# The following new member data indicate which columns store
|
||||
# LAMMPS-specific information.
|
||||
# The next 6 members store keep track of the different columns
|
||||
# of the "Data Atoms" section of a LAMMPS data file:
|
||||
self.column_names = [] #<--A list of column names (optional)
|
||||
self.ii_coords=[] #<--A list of triplets of column indexes storing coordinate data
|
||||
self.ii_vects=[] #<--A list of triplets of column indexes storing directional data
|
||||
# (such as dipole or ellipsoid orientations)
|
||||
self.i_atomid=None #<--An integer indicating which column has the atomid
|
||||
self.i_atomtype=None #<--An integer indicating which column has the atomtype
|
||||
self.i_molid=None #<--An integer indicating which column has the molid, if applicable
|
||||
self.infile=None # Name of the outermost file. This is the file
|
||||
# which was read at the moment parsing begins.
|
||||
|
||||
|
||||
|
||||
|
||||
def LttreeParseArgs(argv, settings):
|
||||
|
||||
BasicUIParseArgs(argv, settings)
|
||||
|
||||
# Loop over the remaining arguments not processed yet.
|
||||
# These arguments are specific to the lttree.py program
|
||||
# and are not understood by ttree.py:
|
||||
i = 1
|
||||
while i < len(argv):
|
||||
#sys.stderr.write('argv['+str(i)+'] = \"'+argv[i]+'\"\n')
|
||||
if ((argv[i].lower() == '-atomstyle') or
|
||||
(argv[i].lower() == '-atom-style') or
|
||||
(argv[i].lower() == '-atom_style')):
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error('+g_program_name+'): The '+argv[i]+' flag should be followed by a LAMMPS\n'
|
||||
' atom_style name (or single quoted string containing a space-separated\n'
|
||||
' list of column names such as: atom-ID atom-type q x y z molecule-ID.)\n')
|
||||
settings.column_names = AtomStyle2ColNames(argv[i+1])
|
||||
sys.stderr.write('\n \"'+data_atoms+'\" column format:\n')
|
||||
sys.stderr.write(' '+(' '.join(settings.column_names))+'\n\n')
|
||||
settings.ii_coords = ColNames2Coords(settings.column_names)
|
||||
settings.ii_vects = ColNames2Vects(settings.column_names)
|
||||
settings.i_atomid, settings.i_atomtype, settings.i_molid = ColNames2AidAtypeMolid(settings.column_names)
|
||||
del(argv[i:i+2])
|
||||
elif (argv[i].lower() == '-icoord'):
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by list of integers\n'
|
||||
' corresponding to column numbers for coordinates in\n'
|
||||
' the \"'+data_atoms+'\" section of a LAMMPS data file.\n')
|
||||
ilist = argv[i+1].split()
|
||||
if (len(ilist) % 3) != 0:
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by list of integers.\n'
|
||||
' This is usually a list of 3 integers, but it can contain more.\n'
|
||||
' The number of cooridnate columns must be divisible by 3,\n'
|
||||
' (even if the simulation is in 2 dimensions)\n')
|
||||
settings.iaffinevects = []
|
||||
for i in range(0, len(ilist)/3):
|
||||
cols = [int(ilist[3*i])+1,
|
||||
int(ilist[3*i+1])+1,
|
||||
int(ilist[3*i+2])+1]
|
||||
settings.iaffinevects.append(cols)
|
||||
del(argv[i:i+2])
|
||||
elif (argv[i].lower() == '-ivect'):
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by list of integers\n'
|
||||
' corresponding to column numbers for direction vectors in\n'
|
||||
' the \"'+data_atoms+'\" section of a LAMMPS data file.\n')
|
||||
ilist = argv[i+1].split()
|
||||
if (len(ilist) % 3) != 0:
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by list of integers.\n'
|
||||
' This is usually a list of 3 integers, but it can contain more.\n'
|
||||
' The number of cooridnate columns must be divisible by 3,\n'
|
||||
' (even if the simulation is in 2 dimensions)\n')
|
||||
settings.ivects = []
|
||||
for i in range(0, len(ilist)/3):
|
||||
cols = [int(ilist[3*i])+1,
|
||||
int(ilist[3*i+1])+1,
|
||||
int(ilist[3*i+2])+1]
|
||||
settings.ivects.append(cols)
|
||||
del(argv[i:i+2])
|
||||
elif ((argv[i].lower() == '-iatomid') or
|
||||
(argv[i].lower() == '-iid') or
|
||||
(argv[i].lower() == '-iatom-id')):
|
||||
if ((i+1 >= len(argv)) or (not str.isdigit(argv[i+1]))):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by an integer\n'
|
||||
' (>=1) indicating which column in the \"'+data_atoms+'\" section of a\n'
|
||||
' LAMMPS data file contains the atom id number (typically 1).\n'
|
||||
' (This argument is unnecessary if you use the -atomstyle argument.)\n')
|
||||
i_atomid = int(argv[i+1])-1
|
||||
del(argv[i:i+2])
|
||||
elif ((argv[i].lower() == '-iatomtype') or
|
||||
(argv[i].lower() == '-itype') or
|
||||
(argv[i].lower() == '-iatom-type')):
|
||||
if ((i+1 >= len(argv)) or (not str.isdigit(argv[i+1]))):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by an integer\n'
|
||||
' (>=1) indicating which column in the \"'+data_atoms+'\" section of a\n'
|
||||
' LAMMPS data file contains the atom type.\n'
|
||||
' (This argument is unnecessary if you use the -atomstyle argument.)\n')
|
||||
i_atomtype = int(argv[i+1])-1
|
||||
del(argv[i:i+2])
|
||||
elif ((argv[i].lower() == '-imolid') or
|
||||
(argv[i].lower() == '-imol') or
|
||||
(argv[i].lower() == '-imol-id') or
|
||||
(argv[i].lower() == '-imoleculeid') or
|
||||
(argv[i].lower() == '-imolecule-id')):
|
||||
if ((i+1 >= len(argv)) or (not str.isdigit(argv[i+1]))):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by an integer\n'
|
||||
' (>=1) indicating which column in the \"'+data_atoms+'\" section of a\n'
|
||||
' LAMMPS data file contains the molecule id number.\n'
|
||||
' (This argument is unnecessary if you use the -atomstyle argument.)\n')
|
||||
i_molid = int(argv[i+1])-1
|
||||
del(argv[i:i+2])
|
||||
|
||||
elif ((argv[i][0] == '-') and (__name__ == "__main__")):
|
||||
#elif (__name__ == "__main__"):
|
||||
raise InputError('Error('+g_program_name+'):\n'
|
||||
'Unrecogized command line argument \"'+argv[i]+'\"\n')
|
||||
else:
|
||||
i += 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Instantiate the lexer we will be using.
|
||||
# (The lexer's __init__() function requires an openned file.
|
||||
# Assuming __name__ == "__main__", then the name of that file should
|
||||
# be the last remaining (unprocessed) argument in the argument list.
|
||||
# Otherwise, then name of that file will be determined later by the
|
||||
# python script which imports this module, so we let them handle it.)
|
||||
|
||||
if len(argv) == 1:
|
||||
raise InputError('Error: This program requires at least one argument\n'
|
||||
' the name of a file containing ttree template commands\n')
|
||||
elif len(argv) == 2:
|
||||
try:
|
||||
settings.lex = TemplateLexer(open(argv[1], 'r'), argv[1]) # Parse text from file
|
||||
except IOError:
|
||||
sys.stderr.write('Error: unable to open file\n'
|
||||
' \"'+argv[1]+'\"\n'
|
||||
' for reading.\n')
|
||||
sys.exit(1)
|
||||
del(argv[1:2])
|
||||
|
||||
else:
|
||||
# if there are more than 2 remaining arguments,
|
||||
problem_args = ['\"'+arg+'\"' for arg in argv[1:]]
|
||||
raise InputError('Syntax Error('+g_program_name+'):\n\n'
|
||||
' Problem with argument list.\n'
|
||||
' The remaining arguments are:\n\n'
|
||||
' '+(' '.join(problem_args))+'\n\n'
|
||||
' (The actual problem may be earlier in the argument list.\n'
|
||||
' If these arguments are source files, then keep in mind\n'
|
||||
' that this program can not parse multiple source files.)\n'
|
||||
' Check the syntax of the entire argument list.\n')
|
||||
|
||||
|
||||
|
||||
if len(settings.ii_coords) == 0:
|
||||
sys.stderr.write('########################################################\n'
|
||||
'## WARNING: atom_style unspecified ##\n'
|
||||
'## --> \"'+data_atoms+'\" column data has an unknown format ##\n'
|
||||
'## Assuming atom_style = \"full\" ##\n'
|
||||
# '########################################################\n'
|
||||
# '## To specify the \"'+data_atoms+'\" column format you can: ##\n'
|
||||
# '## 1) Use the -atomstyle \"STYLE\" argument ##\n'
|
||||
# '## where \"STYLE\" is a string indicating a LAMMPS ##\n'
|
||||
# '## atom_style, including hybrid styles.(Standard ##\n'
|
||||
# '## atom styles defined in 2011 are supported.) ##\n'
|
||||
# '## 2) Use the -atomstyle \"COL_LIST\" argument ##\n'
|
||||
# '## where \"COL_LIST" is a quoted list of strings ##\n'
|
||||
# '## indicating the name of each column. ##\n'
|
||||
# '## Names \"x\",\"y\",\"z\" are interpreted as ##\n'
|
||||
# '## atomic coordinates. \"mux\",\"muy\",\"muz\" ##\n'
|
||||
# '## are interpreted as direction vectors. ##\n'
|
||||
# '## 3) Use the -icoord \"cx cy cz...\" argument ##\n'
|
||||
# '## where \"cx cy cz\" is a list of integers ##\n'
|
||||
# '## indicating the column numbers for the x,y,z ##\n'
|
||||
# '## coordinates of each atom. ##\n'
|
||||
# '## 4) Use the -ivect \"cmux cmuy cmuz...\" argument ##\n'
|
||||
# '## where \"cmux cmuy cmuz...\" is a list of ##\n'
|
||||
# '## integers indicating the column numbers for ##\n'
|
||||
# '## the vector that determines the direction of a ##\n'
|
||||
# '## dipole or ellipsoid (ie. a rotateable vector).##\n'
|
||||
# '## (More than one triplet can be specified. The ##\n'
|
||||
# '## number of entries must be divisible by 3.) ##\n'
|
||||
'########################################################\n')
|
||||
|
||||
# The default atom_style is "full"
|
||||
settings.column_names = AtomStyle2ColNames('full')
|
||||
settings.ii_coords = ColNames2Coords(settings.column_names)
|
||||
settings.ii_vects = ColNames2Vects(settings.column_names)
|
||||
settings.i_atomid, settings.i_atomtype, settings.i_molid = ColNames2AidAtypeMolid(settings.column_names)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def TransformAtomText(text, matrix):
|
||||
""" Apply transformations to the coordinates and other vector degrees
|
||||
of freedom stored in the \"Data Atoms\" section of a LAMMPS data file.
|
||||
This is the \"text\" argument.
|
||||
The \"matrix\" stores the aggregate sum of combined transformations
|
||||
to be applied.
|
||||
|
||||
"""
|
||||
|
||||
#sys.stderr.write('matrix_stack.M = \n'+ MatToStr(matrix) + '\n')
|
||||
|
||||
lines = text.split('\n')
|
||||
|
||||
for i in range(0, len(lines)):
|
||||
line_orig = lines[i]
|
||||
ic = line_orig.find('#')
|
||||
if ic != -1:
|
||||
line = line_orig[:ic]
|
||||
comment = ' '+line_orig[ic:].rstrip('\n')
|
||||
else:
|
||||
line = line_orig.rstrip('\n')
|
||||
comment = ''
|
||||
|
||||
columns = line.split()
|
||||
if len(columns) > 0:
|
||||
if len(columns) == len(settings.column_names)+3:
|
||||
raise InputError('Error: lttree.py does not yet support integer unit-cell counters \n'
|
||||
' within the \"'+data_atoms+'\" section of a LAMMPS data file.\n'
|
||||
' Instead please add the appropriate offsets (these offsets\n'
|
||||
' should be multiples of the cell size) to the atom coordinates\n'
|
||||
' in the data file, and eliminate the extra columns. Then try again.\n'
|
||||
' (If you get this message often, email me and I\'ll fix this limitation.)')
|
||||
if len(columns) < len(settings.column_names):
|
||||
raise InputError('Error: The number of columns in your data file does not\n'
|
||||
' match the LAMMPS atom_style you selected.\n'
|
||||
' Use the -atomstyle <style> command line argument.\n')
|
||||
x0 = [0.0, 0.0, 0.0]
|
||||
x = [0.0, 0.0, 0.0]
|
||||
# Atomic coordinates transform using "affine" transformations
|
||||
# (translations plus rotations [or other linear transformations])
|
||||
for cxcycz in settings.ii_coords:
|
||||
for d in range(0,3):
|
||||
x0[d] = float(columns[cxcycz[d]])
|
||||
AffineTransform(x, matrix, x0) # x = matrix * x0 + b
|
||||
for d in range(0,3): #("b" is part of "matrix")
|
||||
columns[cxcycz[d]] = str(x[d])
|
||||
# Dipole moments and other direction-vectors
|
||||
# are not effected by translational movement
|
||||
for cxcycz in settings.ii_vects:
|
||||
for d in range(0,3):
|
||||
x0[d] = float(columns[cxcycz[d]])
|
||||
LinearTransform(x, matrix, x0) # x = matrix * x0
|
||||
for d in range(0,3):
|
||||
columns[cxcycz[d]] = str(x[d])
|
||||
lines[i] = ' '.join(columns) + comment
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
|
||||
def CalcCM(text_Atoms,
|
||||
text_Masses=None,
|
||||
settings=None):
|
||||
types2masses = None
|
||||
# Loop through the "Masses" section: what is the mass of each atom type?
|
||||
if text_Masses != None:
|
||||
types2masses = {}
|
||||
lines = text_Masses.split('\n')
|
||||
for i in range(0, len(lines)):
|
||||
line = lines[i]
|
||||
columns = line.split()
|
||||
if len(columns) == 2:
|
||||
atomtype = columns[0]
|
||||
m = float(columns[1])
|
||||
types2masses[atomtype] = m
|
||||
|
||||
lines = text_Atoms.split('\n')
|
||||
# Pass 1 through the "Data Atoms" section: Determine each atom's mass
|
||||
if text_Masses != None:
|
||||
assert(settings != None)
|
||||
for i in range(0, len(lines)):
|
||||
line = lines[i]
|
||||
columns = line.split()
|
||||
atomid = columns[settings.i_atomid]
|
||||
atomtype = columns[settings.i_atomtype]
|
||||
if atomtype not in types2masses[atomtype]:
|
||||
raise InputError('Error(lttree): You have neglected to define the mass of atom type: \"'+atomtype+'\"\n'
|
||||
'Did you specify the mass of every atom type using write(\"Masses\"){}?')
|
||||
atomid2mass[atomid] = atomtype2mass[atomtype]
|
||||
|
||||
# Pass 2 through the "Data Atoms" section: Find the center of mass.
|
||||
for i in range(0, len(lines)):
|
||||
line = lines[i]
|
||||
columns = line.split()
|
||||
if len(columns) > 0:
|
||||
if len(columns) == len(settings.column_names)+3:
|
||||
raise InputError('Error: lttree.py does not yet support integer unit-cell counters (ix, iy, iz)\n'
|
||||
' within the \"'+data_atoms+'\" section of a LAMMPS data file.\n'
|
||||
' Instead please add the appropriate offsets (these offsets\n'
|
||||
' should be multiples of the cell size) to the atom coordinates\n'
|
||||
' in the data file, and eliminate the extra columns. Then try again.\n'
|
||||
' (If you get this message often, email me and I\'ll fix this limitation.)')
|
||||
if len(columns) != len(settings.column_names):
|
||||
raise InputError('Error: The number of columns in your data file does not\n'
|
||||
' match the LAMMPS atom_style you selected.\n'
|
||||
' Use the -atomstyle <style> command line argument.\n')
|
||||
x = [0.0, 0.0, 0.0]
|
||||
if atomids2masses != None:
|
||||
m = atomids2masses[atomid]
|
||||
else:
|
||||
m = 1.0
|
||||
tot_m += m
|
||||
for cxcycz in settings.ii_coords:
|
||||
for d in range(0,3):
|
||||
x[d] = float(columns[cxcycz[d]])
|
||||
tot_x[d] += x[d]
|
||||
# Note: dipole moments and other direction vectors don't effect
|
||||
# the center of mass. So I commented out the loop below.
|
||||
#for cxcycz in settings.ii_vects:
|
||||
# for d in range(0,3):
|
||||
# v[d] = float(columns[cxcycz[d]])
|
||||
lines[i] = ' '.join(columns)
|
||||
|
||||
xcm = [0.0, 0.0, 0.0]
|
||||
for d in range(0,3):
|
||||
xcm[d] = tot_x[d] / tot_m
|
||||
return xcm
|
||||
|
||||
|
||||
|
||||
def _ExecCommands(command_list,
|
||||
index,
|
||||
global_files_content,
|
||||
settings,
|
||||
matrix_stack,
|
||||
current_scope_id=None,
|
||||
substitute_vars=True):
|
||||
"""
|
||||
_ExecCommands():
|
||||
The argument "commands" is a nested list of lists of
|
||||
"Command" data structures (defined in ttree.py).
|
||||
|
||||
Carry out the write() and write_once() commands (which
|
||||
write out the contents of the templates contain inside them).
|
||||
Instead of writing the files, save their contents in a string.
|
||||
|
||||
The argument "global_files_content" should be of type defaultdict(list)
|
||||
It is an associative array whose key is a string (a filename)
|
||||
and whose value is a lists of strings (of rendered templates).
|
||||
|
||||
"""
|
||||
files_content = defaultdict(list)
|
||||
postprocessing_commands = []
|
||||
|
||||
while index < len(command_list):
|
||||
command = command_list[index]
|
||||
index += 1
|
||||
|
||||
# For debugging only
|
||||
if ((not isinstance(command, StackableCommand)) and
|
||||
(not isinstance(command, ScopeCommand)) and
|
||||
(not isinstance(command, WriteFileCommand))):
|
||||
sys.stderr.write(str(command)+'\n')
|
||||
|
||||
|
||||
if isinstance(command, PopCommand):
|
||||
assert(current_scope_id != None)
|
||||
if command.context_node == None:
|
||||
command.context_node = current_scope_id
|
||||
if isinstance(command, PopRightCommand):
|
||||
matrix_stack.PopRight(which_stack = command.context_node)
|
||||
elif isinstance(command, PopLeftCommand):
|
||||
matrix_stack.PopLeft(which_stack = command.context_node)
|
||||
else:
|
||||
assert(False)
|
||||
|
||||
elif isinstance(command, PushCommand):
|
||||
assert(current_scope_id != None)
|
||||
if command.context_node == None:
|
||||
command.context_node = current_scope_id
|
||||
# Some commands are post-processing commands, and must be
|
||||
# carried out AFTER all the text has been rendered. For example
|
||||
# the "movecm(0,0,0)" waits until all of the coordinates have
|
||||
# been rendered, calculates the center-of-mass, and then applies
|
||||
# a translation moving the center of mass to the origin (0,0,0).
|
||||
# We need to figure out which of these commands need to be
|
||||
# postponed, and which commands can be carried out now.
|
||||
# ("now"=pushing transformation matrices onto the matrix stack).
|
||||
# UNFORTUNATELY POSTPONING SOME COMMANDS MAKES THE CODE UGLY
|
||||
transform_list = command.contents.split('.')
|
||||
transform_blocks = []
|
||||
i_post_process = -1
|
||||
# Example: Suppose:
|
||||
#command.contents = '.rot(30,0,0,1).movecm(0,0,0).rot(45,1,0,0).scalecm(2.0).move(-2,1,0)'
|
||||
# then
|
||||
#transform_list = ['rot(30,0,0,1)', 'movecm(0,0,0)', 'rot(45,1,0,0)', 'scalecm(2.0)', 'move(-2,1,0)']
|
||||
# Note: the first command 'rot(30,0,0,1)' is carried out now.
|
||||
# The remaining commands are carried out during post-processing,
|
||||
# (when processing the "ScopeEnd" command.
|
||||
#
|
||||
# We break up the commands into "blocks" separated by center-
|
||||
# of-mass transformations ('movecm', 'rotcm', or 'scalecm')
|
||||
#
|
||||
# transform_blocks = ['.rot(30,0,0,1)',
|
||||
# '.movecm(0,0,0).rot(45,1,0,0)',
|
||||
# '.scalecm(2.0).move(-2,1,0)']
|
||||
|
||||
i = 0
|
||||
while i < len(transform_list):
|
||||
transform_block = ''
|
||||
while i < len(transform_list):
|
||||
transform = transform_list[i]
|
||||
i += 1
|
||||
if transform != '':
|
||||
transform_block += '.' + transform
|
||||
transform = transform.split('(')[0]
|
||||
if ((transform == 'movecm') or
|
||||
(transform == 'rotcm') or
|
||||
(transform == 'scalecm')):
|
||||
break
|
||||
transform_blocks.append(transform_block)
|
||||
|
||||
if len(postprocessing_commands) == 0:
|
||||
# The first block (before movecm, rotcm, or scalecm)
|
||||
# can be executed now by modifying the matrix stack.
|
||||
if isinstance(command, PushRightCommand):
|
||||
matrix_stack.PushCommandsRight(transform_blocks[0].strip('.'),
|
||||
command.srcloc,
|
||||
which_stack=command.context_node)
|
||||
elif isinstance(command, PushLeftCommand):
|
||||
matrix_stack.PushCommandsLeft(transform_blocks[0].strip('.'),
|
||||
command.srcloc,
|
||||
which_stack=command.context_node)
|
||||
# Everything else must be saved for later.
|
||||
postprocessing_blocks = transform_blocks[1:]
|
||||
else:
|
||||
# If we already encountered a "movecm" "rotcm" or "scalecm"
|
||||
# then all of the command blocks must be handled during
|
||||
# postprocessing.
|
||||
postprocessing_blocks = transform_blocks
|
||||
|
||||
for transform_block in postprocessing_blocks:
|
||||
assert(isinstance(block, basestring))
|
||||
if isinstance(command, PushRightCommand):
|
||||
postprocessing_commands.append(PushRightCommand(transform_block,
|
||||
command.srcloc,
|
||||
command.context_node))
|
||||
elif isinstance(command, PushLeftCommand):
|
||||
postprocessing_commands.append(PushLeftCommand(transform_block,
|
||||
command.srcloc,
|
||||
command.context_node))
|
||||
|
||||
|
||||
elif isinstance(command, WriteFileCommand):
|
||||
|
||||
# --- Throw away lines containin references to deleted variables:---
|
||||
|
||||
# First: To edit the content of a template,
|
||||
# you need to make a deep local copy of it
|
||||
tmpl_list = []
|
||||
for entry in command.tmpl_list:
|
||||
if isinstance(entry, TextBlock):
|
||||
tmpl_list.append(TextBlock(entry.text,
|
||||
entry.srcloc)) #, entry.srcloc_end))
|
||||
else:
|
||||
tmpl_list.append(entry)
|
||||
|
||||
|
||||
# Now throw away lines with deleted variables
|
||||
|
||||
DeleteLinesWithBadVars(tmpl_list)
|
||||
|
||||
# --- Now render the text ---
|
||||
text = Render(tmpl_list,
|
||||
substitute_vars)
|
||||
|
||||
# ---- Coordinates of the atoms, must be rotated
|
||||
# and translated after rendering.
|
||||
# In addition, other vectors (dipoles, ellipsoid orientations)
|
||||
# must be processed.
|
||||
# This requires us to re-parse the contents of this text
|
||||
# (after it has been rendered), and apply these transformations
|
||||
# before passing them on to the caller.
|
||||
if command.filename == data_atoms:
|
||||
text = TransformAtomText(text, matrix_stack.M)
|
||||
|
||||
files_content[command.filename].append(text)
|
||||
|
||||
|
||||
elif isinstance(command, ScopeBegin):
|
||||
|
||||
if isinstance(command.node, InstanceObj):
|
||||
if ((command.node.children != None) and
|
||||
(len(command.node.children) > 0)):
|
||||
matrix_stack.PushStack(command.node)
|
||||
|
||||
# "command_list" is a long list of commands.
|
||||
# ScopeBegin and ScopeEnd are (usually) used to demarcate/enclose
|
||||
# the commands which are issued for a single class or
|
||||
# class instance. _ExecCommands() carries out the commands for
|
||||
# a single class/instance. If we reach a ScopeBegin(),
|
||||
# then recursively process the commands belonging to the child.
|
||||
index = _ExecCommands(command_list,
|
||||
index,
|
||||
files_content,
|
||||
settings,
|
||||
matrix_stack,
|
||||
command.node,
|
||||
substitute_vars)
|
||||
|
||||
elif isinstance(command, ScopeEnd):
|
||||
if data_atoms in files_content:
|
||||
for ppcommand in postprocessing_commands:
|
||||
if data_masses in files_content:
|
||||
xcm = CalcCM(files_content[data_atoms],
|
||||
files_content[data_masses],
|
||||
settings)
|
||||
else:
|
||||
xcm = CalcCM(files_content[data_atoms])
|
||||
if isinstance(ppcommand, PushRightCommand):
|
||||
matrix_stack.PushCommandsRight(ppcommand.contents,
|
||||
ppcommand.srcloc,
|
||||
xcm,
|
||||
which_stack=command.context_node)
|
||||
elif isinstance(ppcommand, PushLeftCommand):
|
||||
matrix_stack.PushCommandsLeft(ppcommand.contents,
|
||||
ppcommand.srcloc,
|
||||
xcm,
|
||||
which_stack=command.context_node)
|
||||
files_content[data_atoms] = \
|
||||
TransformAtomText(files_content[data_atoms],
|
||||
matrix_stack.M)
|
||||
|
||||
for ppcommand in postprocessing_commands:
|
||||
matrix_stack.Pop(which_stack = command.context_node)
|
||||
#(same as PopRight())
|
||||
|
||||
if isinstance(command.node, InstanceObj):
|
||||
if ((command.node.children != None) and
|
||||
(len(command.node.children) > 0)):
|
||||
matrix_stack.PopStack()
|
||||
|
||||
# "ScopeEnd" means we're done with this class/instance.
|
||||
break
|
||||
|
||||
else:
|
||||
assert(False)
|
||||
# no other command types allowed at this point
|
||||
|
||||
|
||||
# After processing the commands in this list,
|
||||
# merge the templates with the callers template list
|
||||
for filename, tmpl_list in files_content.items():
|
||||
global_files_content[filename] += \
|
||||
files_content[filename]
|
||||
|
||||
return index
|
||||
|
||||
|
||||
|
||||
def ExecCommands(commands,
|
||||
files_content,
|
||||
settings,
|
||||
substitute_vars=True):
|
||||
|
||||
matrix_stack = MultiAffineStack()
|
||||
|
||||
index = _ExecCommands(commands,
|
||||
0,
|
||||
files_content,
|
||||
settings,
|
||||
matrix_stack,
|
||||
None,
|
||||
substitute_vars)
|
||||
assert(index == len(commands))
|
||||
|
||||
|
||||
|
||||
|
||||
def WriteFiles(files_content, suffix='', write_to_stdout=True):
|
||||
for filename, str_list in files_content.items():
|
||||
if filename != None:
|
||||
out_file = None
|
||||
if filename == '':
|
||||
if write_to_stdout:
|
||||
out_file = sys.stdout
|
||||
else:
|
||||
out_file = open(filename+suffix, 'a')
|
||||
if out_file != None:
|
||||
out_file.write(''.join(str_list))
|
||||
if filename != '':
|
||||
out_file.close()
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
"""
|
||||
This is is a "main module" wrapper for invoking lttree.py
|
||||
as a stand alone program. This program:
|
||||
|
||||
1)reads a ttree file,
|
||||
2)constructs a tree of class definitions (g_objectdefs)
|
||||
3)constructs a tree of instantiated class objects (g_objects),
|
||||
4)automatically assigns values to the variables,
|
||||
5)and carries out the "write" commands to write the templates a file(s).
|
||||
|
||||
"""
|
||||
g_program_name = __file__.split('/')[-1] # ='lttree.py'
|
||||
g_date_str = '2013-2-15'
|
||||
g_version_str = '0.71'
|
||||
|
||||
####### Main Code Below: #######
|
||||
sys.stderr.write(g_program_name+' v'+g_version_str+' '+g_date_str+' ')
|
||||
sys.stderr.write('\n(python version '+str(sys.version)+')\n')
|
||||
if sys.version < '2.7':
|
||||
raise InputError('Error: Alas, you must upgrade to a newever version of python.')
|
||||
|
||||
try:
|
||||
|
||||
#settings = BasicUISettings()
|
||||
#BasicUIParseArgs(sys.argv, settings)
|
||||
settings = LttreeSettings()
|
||||
LttreeParseArgs(sys.argv, settings)
|
||||
|
||||
# Data structures to store the class definitionss and instances
|
||||
g_objectdefs = StaticObj('', None) # The root of the static tree
|
||||
# has name '' (equivalent to '/')
|
||||
g_objects = InstanceObj('', None) # The root of the instance tree
|
||||
# has name '' (equivalent to '/')
|
||||
|
||||
# A list of commands to carry out
|
||||
g_static_commands = []
|
||||
g_instance_commands = []
|
||||
|
||||
|
||||
BasicUI(settings,
|
||||
g_objectdefs,
|
||||
g_objects,
|
||||
g_static_commands,
|
||||
g_instance_commands)
|
||||
|
||||
# Interpret the the commands. (These are typically write() or
|
||||
# write_once() commands, rendering templates into text.
|
||||
# This step also handles coordinate transformations and delete commands.
|
||||
# Coordinate transformations can be applied to the rendered text
|
||||
# as a post-processing step.
|
||||
|
||||
sys.stderr.write(' done\nbuilding templates...')
|
||||
|
||||
files_content = defaultdict(list)
|
||||
|
||||
ExecCommands(g_static_commands,
|
||||
files_content,
|
||||
settings,
|
||||
False)
|
||||
ExecCommands(g_instance_commands,
|
||||
files_content,
|
||||
settings,
|
||||
False)
|
||||
|
||||
# Finally: write the rendered text to actual files.
|
||||
|
||||
# Erase the files that will be written to:
|
||||
sys.stderr.write(' done\nwriting templates...')
|
||||
EraseTemplateFiles(g_static_commands)
|
||||
EraseTemplateFiles(g_instance_commands)
|
||||
|
||||
# Write the files as templates
|
||||
# (with the original variable names present)
|
||||
WriteFiles(files_content, suffix=".template", write_to_stdout=False)
|
||||
|
||||
# Write the files with the variables substituted by values
|
||||
sys.stderr.write(' done\nbuilding and rendering templates...')
|
||||
files_content = defaultdict(list)
|
||||
ExecCommands(g_static_commands, files_content, settings, True)
|
||||
ExecCommands(g_instance_commands, files_content, settings, True)
|
||||
sys.stderr.write(' done\nwriting rendered templates...\n')
|
||||
WriteFiles(files_content)
|
||||
sys.stderr.write(' done\n')
|
||||
|
||||
# Step 11: Now write the variable bindings/assignments table.
|
||||
# Now write the variable bindings/assignments table.
|
||||
sys.stderr.write('writing \"ttree_assignments.txt\" file...')
|
||||
open('ttree_assignments.txt', 'w').close() # <-- erase previous version.
|
||||
WriteVarBindingsFile(g_objectdefs)
|
||||
WriteVarBindingsFile(g_objects)
|
||||
sys.stderr.write(' done\n')
|
||||
|
||||
except (ValueError, InputError) as err:
|
||||
sys.stderr.write('\n\n'+str(err)+'\n')
|
||||
sys.exit(-1)
|
||||
|
||||
2099
tools/moltemplate/src/lttree_check.py
Executable file
2099
tools/moltemplate/src/lttree_check.py
Executable file
File diff suppressed because it is too large
Load Diff
335
tools/moltemplate/src/lttree_postprocess.py
Executable file
335
tools/moltemplate/src/lttree_postprocess.py
Executable file
@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
lttree_postprocess.py
|
||||
This is a stand-alone python script which checks the files created by
|
||||
lttree.py to insure that the standard instance-variables ($variables)
|
||||
have all been defined. This script performs a task which is very similar
|
||||
to the task performed by lttree_check.py. This script attempts to detect
|
||||
mistakes in the names of $atom, $bond, $angle, $dihedral, $improper, & $mol
|
||||
variables.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
from lttree_styles import *
|
||||
from ttree_lex import ExtractCatName
|
||||
|
||||
g_program_name = __file__.split('/')[-1] # = 'lttree_postprocess.py'
|
||||
g_version_str = '0.4'
|
||||
g_date_str = '2012-12-12'
|
||||
atom_style = 'full'
|
||||
ttree_assignments_fname = 'ttree_assignments.txt'
|
||||
defined_mols = set([])
|
||||
defined_atoms = set([])
|
||||
defined_bonds = set([])
|
||||
defined_angles = set([])
|
||||
defined_dihedrals = set([])
|
||||
defined_impropers = set([])
|
||||
|
||||
g_no_check_msg = \
|
||||
'(To override this error, run moltemplate using the \"-nocheck\" argument.)\n'
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
for i in range(0,len(sys.argv)):
|
||||
if ((sys.argv[i].lower() == '-atomstyle') or
|
||||
(sys.argv[i].lower() == '-atom-style') or
|
||||
(sys.argv[i].lower() == '-atom_style')):
|
||||
if i+1 >= len(sys.argv):
|
||||
raise InputError('Error('+g_program_name+'): The '+sys.argv[i]+' flag should be followed by a LAMMPS\n'
|
||||
' atom_style name (or single quoted string containing a space-separated\n'
|
||||
' list of column names such as: atom-ID atom-type q x y z molecule-ID.)\n')
|
||||
|
||||
atom_style = sys.argv[i+1]
|
||||
elif ((sys.argv[i].lower() == '-ttreeassignments') or
|
||||
(sys.argv[i].lower() == '-ttree-assignments') or
|
||||
(sys.argv[i].lower() == '-ttree_assignments')):
|
||||
if i+1 >= len(sys.argv):
|
||||
raise InputError('Error('+g_program_name+'): The '+sys.argv[i]+' flag should be followed by \n'
|
||||
' a file containing the variable bindings created by ttree/moltemplate.\n')
|
||||
ttree_assignments_fname = sys.argv[i+1]
|
||||
else:
|
||||
pass # ignore other arguments (they are intended for lttree.py)
|
||||
|
||||
|
||||
atom_column_names = AtomStyle2ColNames(atom_style)
|
||||
i_atomid = 0
|
||||
i_molid = -1
|
||||
for i in range(0,len(atom_column_names)):
|
||||
if atom_column_names[i].lower() == 'atom-id':
|
||||
i_atomid = i
|
||||
elif atom_column_names[i].lower() == 'molecule-id':
|
||||
i_molid = i
|
||||
i_max_column = max(i_atomid, i_molid)
|
||||
|
||||
|
||||
# The following variables are defined in "lttree_styles.py"
|
||||
#data_atoms="Data Atoms"
|
||||
#data_masses="Data Masses"
|
||||
#data_velocities="Data Velocities"
|
||||
#data_bonds="Data Bonds"
|
||||
#data_angles="Data Angles"
|
||||
#data_dihedrals="Data Dihedrals"
|
||||
#data_impropers="Data Impropers"
|
||||
|
||||
|
||||
sys.stderr.write(g_program_name+' v'+g_version_str+' '+g_date_str+'\n')
|
||||
|
||||
try:
|
||||
# ------------ defined_atoms ------------
|
||||
try:
|
||||
f = open(data_atoms+'.template', 'r')
|
||||
except:
|
||||
raise InputError('Error('+g_program_name+'): Unable to open file\n'+
|
||||
'\"'+data_atoms+'.template\"\n'
|
||||
' for reading. (Do your files lack a \"'+data_atoms+'\" section?)\n'
|
||||
+g_no_check_msg+'\n')
|
||||
|
||||
for line_orig in f:
|
||||
ic = line_orig.find('#')
|
||||
if ic != -1:
|
||||
line = line_orig[:ic]
|
||||
else:
|
||||
line = line_orig.rstrip('\n')
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) == 0:
|
||||
pass
|
||||
elif len(tokens) <= i_max_column:
|
||||
raise InputError('Error('+g_program_name+'): The following line from\n'
|
||||
' "\"'+data_atoms+'.template\" has bad format:\n\n'
|
||||
+line_orig+'\n'
|
||||
' This my probably an internal error. (Feel free to contact the developer.)\n'
|
||||
+g_no_check_msg+'\n')
|
||||
else:
|
||||
defined_atoms.add(tokens[i_atomid])
|
||||
if i_molid != -1:
|
||||
defined_mols.add(tokens[i_molid])
|
||||
|
||||
f.close()
|
||||
|
||||
|
||||
# ------------ defined_bonds ------------
|
||||
try:
|
||||
f = open(data_bonds+'.template', 'r')
|
||||
|
||||
for line_orig in f:
|
||||
ic = line_orig.find('#')
|
||||
if ic != -1:
|
||||
line = line_orig[:ic]
|
||||
else:
|
||||
line = line_orig.rstrip('\n')
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) == 0:
|
||||
pass
|
||||
elif len(tokens) < 4:
|
||||
raise InputError('Error('+g_program_name+'): The following line from\n'
|
||||
' "\"'+data_bonds+'.template\" has bad format:\n\n'
|
||||
+line_orig+'\n'
|
||||
' This my probably an internal error. (Feel free to contact the developer.)\n'
|
||||
+g_no_check_msg+'\n')
|
||||
else:
|
||||
defined_bonds.add(tokens[0])
|
||||
f.close()
|
||||
except:
|
||||
pass # Defining bonds (stored in the data_bonds file) is optional
|
||||
|
||||
|
||||
# ------------ defined_angles ------------
|
||||
try:
|
||||
f = open(data_angles+'.template', 'r')
|
||||
for line_orig in f:
|
||||
ic = line_orig.find('#')
|
||||
if ic != -1:
|
||||
line = line_orig[:ic]
|
||||
else:
|
||||
line = line_orig.rstrip('\n')
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) == 0:
|
||||
pass
|
||||
elif len(tokens) < 5:
|
||||
raise InputError('Error('+g_program_name+'): The following line from\n'
|
||||
' "\"'+data_angles+'.template\" has bad format:\n\n'
|
||||
+line_orig+'\n'
|
||||
' This my probably an internal error. (Feel free to contact the developer.)\n'
|
||||
+g_no_check_msg+'\n')
|
||||
else:
|
||||
defined_angles.add(tokens[0])
|
||||
f.close()
|
||||
except:
|
||||
pass # Defining angles (stored in the data_angles file) is optional
|
||||
|
||||
|
||||
# ------------ defined_dihedrals ------------
|
||||
try:
|
||||
f = open(data_dihedrals+'.template', 'r')
|
||||
for line_orig in f:
|
||||
ic = line_orig.find('#')
|
||||
if ic != -1:
|
||||
line = line_orig[:ic]
|
||||
else:
|
||||
line = line_orig.rstrip('\n')
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) == 0:
|
||||
pass
|
||||
elif len(tokens) < 6:
|
||||
raise InputError('Error('+g_program_name+'): The following line from\n'
|
||||
' "\"'+data_dihedrals+'.template\" has bad format:\n\n'
|
||||
+line_orig+'\n'
|
||||
' This my probably an internal error. (Feel free to contact the developer.)\n'
|
||||
+g_no_check_msg+'\n')
|
||||
else:
|
||||
defined_dihedrals.add(tokens[0])
|
||||
f.close()
|
||||
except:
|
||||
pass #Defining dihedrals (stored in the data_dihedrals file) is optional
|
||||
|
||||
|
||||
# ------------ defined_impropers ------------
|
||||
try:
|
||||
f = open(data_impropers+'.template', 'r')
|
||||
|
||||
for line_orig in f:
|
||||
ic = line_orig.find('#')
|
||||
if ic != -1:
|
||||
line = line_orig[:ic]
|
||||
else:
|
||||
line = line_orig.rstrip('\n')
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) == 0:
|
||||
pass
|
||||
elif len(tokens) < 6:
|
||||
raise InputError('Error('+g_program_name+'): The following line from\n'
|
||||
' "\"'+data_impropers+'.template\" has bad format:\n\n'
|
||||
+line_orig+'\n'
|
||||
' This my probably an internal error. (Feel free to contact the developer.)\n'
|
||||
+g_no_check_msg+'\n')
|
||||
else:
|
||||
defined_impropers.add(tokens[0])
|
||||
f.close()
|
||||
except:
|
||||
pass #Defining impropers (stored in the data_impropers file) is optional
|
||||
|
||||
|
||||
|
||||
|
||||
# ---- Check ttree_assignments to make sure variables are defined ----
|
||||
|
||||
try:
|
||||
f = open(ttree_assignments_fname, 'r')
|
||||
except:
|
||||
raise InputError('Error('+g_program_name+'): Unable to open file\n'+
|
||||
'\"'+ttree_assignments_fname+'\"\n'
|
||||
' for reading. (Do your files lack a \"'+data_atoms+'\" section?)\n'
|
||||
+g_no_check_msg+'\n')
|
||||
|
||||
for line_orig in f:
|
||||
|
||||
ic = line_orig.find('#')
|
||||
if ic != -1:
|
||||
line = line_orig[:ic]
|
||||
usage_location_str = 'near ' + line_orig[ic+1:]
|
||||
else:
|
||||
line = line_orig.rstrip('\n')
|
||||
usage_location_str = ''
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) == 0:
|
||||
pass
|
||||
if len(tokens) > 0:
|
||||
# This file contains a list of variables of the form:
|
||||
#
|
||||
# @/atom:MoleculeType1:C 1
|
||||
# @/atom:MoleculeType1:H 2
|
||||
# @/atom:MoleculeType2:N 3
|
||||
# $/atom:molecule1:N1 1
|
||||
# $/atom:molecule1:C1 2
|
||||
# :
|
||||
# $/atom:molecule1141:CH 13578
|
||||
# $/atom:molecule1142:N3 13579
|
||||
# :
|
||||
# We only care about instance variables (which use the '$' prefix)
|
||||
# Lines corresponding to static variables (which use the '@' prefix)
|
||||
# are ignored during this pass.
|
||||
i_prefix = tokens[0].find('$')
|
||||
if i_prefix != -1:
|
||||
descr_str = tokens[0][i_prefix+1:]
|
||||
cat_name = ExtractCatName(descr_str)
|
||||
|
||||
if ((cat_name == 'atom') and
|
||||
(tokens[0] not in defined_atoms)):
|
||||
raise InputError('Error('+g_program_name+'): '+usage_location_str+'\n'+
|
||||
' Reference to undefined $atom:\n\n'
|
||||
' '+tokens[0]+' (<--full name)\n\n'+
|
||||
' (If that atom belongs to a molecule (or other subunit), make sure that\n'+
|
||||
' you specified the correct path which leads to it (using / and ..))\n\n'+
|
||||
g_no_check_msg)
|
||||
|
||||
elif ((cat_name == 'bond') and
|
||||
(tokens[0] not in defined_bonds)):
|
||||
raise InputError('Error('+g_program_name+'): '+usage_location_str+'\n'+
|
||||
' Reference to undefined $bond:\n\n'
|
||||
' '+tokens[0]+' (<--full name)\n\n'+
|
||||
' (If that bond belongs to a molecule (or other subunit), make sure that\n'+
|
||||
' you specified the correct path which leads to it (using / and ..))\n\n'+
|
||||
g_no_check_msg)
|
||||
|
||||
elif ((cat_name == 'angle') and
|
||||
(tokens[0] not in defined_angles)):
|
||||
raise InputError('Error('+g_program_name+'): '+usage_location_str+'\n'+
|
||||
' Reference to undefined $angle:\n\n'+
|
||||
' '+tokens[0]+' (<--full name)\n\n'+
|
||||
' (If that angle belongs to a molecule (or other subunit), make sure that\n'+
|
||||
' you specified the correct path which leads to it (using / and ..))\n\n'+
|
||||
g_no_check_msg)
|
||||
|
||||
elif ((cat_name == 'dihedral') and
|
||||
(tokens[0] not in defined_dihedrals)):
|
||||
raise InputError('Error('+g_program_name+'): '+usage_location_str+'\n\n'+
|
||||
' Reference to undefined $dihedral:\n\n'
|
||||
' '+tokens[0]+' (<--full name)\n\n'+
|
||||
' (If that dihedral belongs to a molecule (or other subunit), make sure that\n'+
|
||||
' you specified the correct path which leads to it (using / and ..))\n\n'+
|
||||
g_no_check_msg)
|
||||
|
||||
elif ((cat_name == 'improper') and
|
||||
(tokens[0] not in defined_impropers)):
|
||||
raise InputError('Error('+g_program_name+'): '+usage_location_str+'\n'+
|
||||
' Reference to undefined $improper:\n\n'
|
||||
' '+tokens[0]+' (<--full name)\n\n'+
|
||||
' (If that improper belongs to a molecule (or other subunit), make sure that\n'+
|
||||
' you specified the correct path which leads to it (using / and ..))\n\n'+
|
||||
g_no_check_msg)
|
||||
|
||||
# I used to generate an error when a users defines a $mol
|
||||
# variable but does not associate any atoms with it (or if the
|
||||
# user systematically deletes all the atoms in that molecule),
|
||||
# but I stopped this practice.
|
||||
# I don't think there is any real need to complain if some
|
||||
# molecule id numbers are undefined. LAMMPS does not care.
|
||||
#
|
||||
#elif ((cat_name == 'mol') and
|
||||
# (tokens[0] not in defined_mols)):
|
||||
# raise InputError('Error('+g_program_name+'): '+usage_location_str+'\n'+
|
||||
# ' Reference to undefined $mol (molecule-ID) variable:\n\n'
|
||||
# ' '+tokens[0]+' (<--full name)\n\n'+
|
||||
# ' (If that molecule is part of a larger molecule, then make sure that\n'+
|
||||
# ' you specified the correct path which leads to it (using / and ..))\n\n'+
|
||||
# g_no_check_msg)
|
||||
|
||||
|
||||
f.close()
|
||||
|
||||
sys.stderr.write(g_program_name+': -- No errors detected. --\n')
|
||||
exit(0)
|
||||
|
||||
|
||||
|
||||
except (ValueError, InputError) as err:
|
||||
sys.stderr.write('\n'+str(err)+'\n')
|
||||
sys.exit(1)
|
||||
|
||||
235
tools/moltemplate/src/lttree_styles.py
Normal file
235
tools/moltemplate/src/lttree_styles.py
Normal file
@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Author: Andrew Jewett (jewett.aij at g mail)
|
||||
# http://www.chem.ucsb.edu/~sheagroup
|
||||
# License: 3-clause BSD License (See LICENSE.TXT)
|
||||
# Copyright (c) 2012, Regents of the University of California
|
||||
# All rights reserved.
|
||||
|
||||
from ttree_lex import InputError
|
||||
|
||||
|
||||
# Users of lttree typically generate the following files:
|
||||
# The variable below refer to file names generated by
|
||||
# write() and write_once() commands in a lttree-file.
|
||||
# (I keep changing my mind what I want these names to be.)
|
||||
data_prefix="Data "
|
||||
data_prefix_no_space="Data"
|
||||
data_atoms="Data Atoms"
|
||||
data_masses="Data Masses"
|
||||
data_velocities="Data Velocities"
|
||||
data_bonds="Data Bonds"
|
||||
data_angles="Data Angles"
|
||||
data_dihedrals="Data Dihedrals"
|
||||
data_impropers="Data Impropers"
|
||||
data_bond_coeffs="Data Bond Coeffs"
|
||||
data_angle_coeffs="Data Angle Coeffs"
|
||||
data_dihedral_coeffs="Data Dihedral Coeffs"
|
||||
data_improper_coeffs="Data Improper Coeffs"
|
||||
data_pair_coeffs="Data Pair Coeffs"
|
||||
|
||||
# interactions-by-type (not id. This is not part of the LAMMPS standard.)
|
||||
data_angles_by_type="Data Angles By Type"
|
||||
data_dihedrals_by_type="Data Dihedrals By Type"
|
||||
data_impropers_by_type="Data Impropers By Type"
|
||||
|
||||
# class2 data sections
|
||||
data_bondbond_coeffs="Data BondBond Coeffs"
|
||||
data_bondangle_coeffs="Data BondAngle Coeffs"
|
||||
data_middlebondtorsion_coeffs="Data MiddleBondTorsion Coeffs"
|
||||
data_endbondtorsion_coeffs="Data EndBondTorsion Coeffs"
|
||||
data_angletorsion_coeffs="Data AngleTorsion Coeffs"
|
||||
data_angleangletorsion_coeffs="Data AngleAngleTorsion Coeffs"
|
||||
data_bondbond13_coeffs="Data BondBond13 Coeffs"
|
||||
data_angleangle_coeffs="Data AngleAngle Coeffs"
|
||||
|
||||
# sections for non-point-like particles:
|
||||
data_ellipsoids="Data Ellipsoids"
|
||||
data_lines="Data Lines"
|
||||
data_triangles="Data Triangles"
|
||||
|
||||
# periodic boundary conditions
|
||||
data_boundary="Data Boundary"
|
||||
# (for backward compatibility), an older version of this file was named:
|
||||
data_pbc="Data PBC"
|
||||
|
||||
# The files are fragments of a LAMMPS data file (see "read_data").
|
||||
# In addition, moltemplate may also generate the following files:
|
||||
in_prefix="In "
|
||||
in_prefix_no_space="In"
|
||||
in_init="In Init"
|
||||
in_settings="In Settings"
|
||||
in_coords="In Coords"
|
||||
# These files represent different sections of the LAMMPS input script.
|
||||
|
||||
|
||||
|
||||
# Atom Styles in LAMMPS as of 2011-7-29
|
||||
g_style_map = {'angle': ['atom-ID','molecule-ID','atom-type','x','y','z'],
|
||||
'atomic': ['atom-ID','atom-type','x','y','z'],
|
||||
'bond': ['atom-ID','molecule-ID','atom-type','x','y','z'],
|
||||
'charge': ['atom-ID','atom-type','q','x','y','z'],
|
||||
'dipole': ['atom-ID','atom-type','q','x','y','z','mux','muy','muz'],
|
||||
'electron': ['atom-ID','atom-type','q','spin','eradius','x','y','z'],
|
||||
'ellipsoid': ['atom-ID','atom-type','x','y','z','quatw','quati','quatj','quatk'],
|
||||
'full': ['atom-ID','molecule-ID','atom-type','q','x','y','z'],
|
||||
'line': ['atom-ID','molecule-ID','atom-type','lineflag','density','x','y','z'],
|
||||
'meso': ['atom-ID','atom-type','rho','e','cv','x','y','z'],
|
||||
'molecular': ['atom-ID','molecule-ID','atom-type','x','y','z'],
|
||||
'peri': ['atom-ID','atom-type','volume','density','x','y','z'],
|
||||
'sphere': ['atom-ID','atom-type','diameter','density','x','y','z'],
|
||||
'tri': ['atom-ID','molecule-ID','atom-type','triangleflag','density','x','y','z'],
|
||||
'wavepacket':['atom-ID','atom-type','charge','spin','eradius','etag','cs_re','cs_im','x','y','z'],
|
||||
'hybrid': ['atom-ID','atom-type','x','y','z'],
|
||||
# The following styles were removed from LAMMPS as of 2012-3
|
||||
'colloid': ['atom-ID','atom-type','x','y','z'],
|
||||
'granular': ['atom-ID','atom-type','diameter','density','x','y','z']}
|
||||
|
||||
|
||||
|
||||
|
||||
def AtomStyle2ColNames(atom_style_string):
|
||||
|
||||
atom_style_string = atom_style_string.strip()
|
||||
if len(atom_style_string) == 0:
|
||||
raise InputError('Error: Invalid atom_style\n'
|
||||
' (The atom_style command was followed by an empty string.)\n')
|
||||
atom_style_args = atom_style_string.split()
|
||||
atom_style = atom_style_args[0]
|
||||
|
||||
hybrid_args = atom_style_args[1:]
|
||||
if (atom_style not in g_style_map):
|
||||
if (len(atom_style_args) >= 2):
|
||||
# If the atom_style_string includes at least 2 words, then we
|
||||
# interpret this as a list of the individual column names
|
||||
return atom_style_args
|
||||
else:
|
||||
raise InputError('Error: Unrecognized atom_style: \"'+atom_style+'\"\n')
|
||||
|
||||
if (atom_style != 'hybrid'):
|
||||
return g_style_map[atom_style]
|
||||
else:
|
||||
column_names = ['atom-ID','atom-type','x','y','z']
|
||||
if (len(hybrid_args)==0):
|
||||
raise InputError('Error: atom_style hybrid must be followed by a sub_style.\n')
|
||||
for sub_style in hybrid_args:
|
||||
if (sub_style not in g_style_map):
|
||||
raise InputError('Error: Unrecognized atom_style: \"'+sub_style+'\"\n')
|
||||
for cname in g_style_map[sub_style]:
|
||||
if cname not in column_names:
|
||||
column_names.append(cname)
|
||||
|
||||
return column_names
|
||||
|
||||
|
||||
def ColNames2AidAtypeMolid(column_names):
|
||||
# Because of the diversity of ways that these
|
||||
# numbers are referred to in the LAMMPS documentation,
|
||||
# we have to be flexible and allow the user to refer
|
||||
# to these quantities in a variety of ways.
|
||||
# Hopefully this covers everything:
|
||||
|
||||
if 'atom-ID' in column_names:
|
||||
i_atomid = column_names.index('atom-ID')
|
||||
elif 'atom−ID' in column_names: # (− is the character used in the manual)
|
||||
i_atomid = column_names.index('atom−ID')
|
||||
elif 'atomID' in column_names:
|
||||
i_atomid = column_names.index('atomID')
|
||||
elif 'atomid' in column_names:
|
||||
i_atomid = column_names.index('atomid')
|
||||
elif 'id' in column_names:
|
||||
i_atomid = column_names.index('id')
|
||||
elif 'atom' in column_names:
|
||||
i_atomid = column_names.index('atom')
|
||||
elif '$atom' in column_names:
|
||||
i_atomid = column_names.index('$atom')
|
||||
else:
|
||||
raise InputError('Error: List of column names lacks an \"atom-ID\"\n')
|
||||
|
||||
if 'atom-type' in column_names:
|
||||
i_atomtype = column_names.index('atom-type')
|
||||
elif 'atom−type' in column_names: # (− hyphen character used in manual)
|
||||
i_atomtype = column_names.index('atom−type')
|
||||
elif 'atomtype' in column_names:
|
||||
i_atomtype = column_names.index('atomtype')
|
||||
elif 'type' in column_names:
|
||||
i_atomtype = column_names.index('type')
|
||||
elif '@atom' in column_names:
|
||||
i_atomtype = column_names.index('@atom')
|
||||
else:
|
||||
raise InputError('Error: List of column names lacks an \"atom-type\"\n')
|
||||
|
||||
i_molid = None
|
||||
if 'molecule-ID' in column_names:
|
||||
i_molid = column_names.index('molecule-ID')
|
||||
elif 'molecule−ID' in column_names: # (− hyphen character used in manual)
|
||||
i_molid = column_names.index('molecule−ID')
|
||||
elif 'moleculeID' in column_names:
|
||||
i_molid = column_names.index('moleculeID')
|
||||
elif 'moleculeid' in column_names:
|
||||
i_molid = column_names.index('moleculeid')
|
||||
elif 'molecule' in column_names:
|
||||
i_molid = column_names.index('molecule')
|
||||
elif 'molID' in column_names:
|
||||
i_molid = column_names.index('molID')
|
||||
elif 'molid' in column_names:
|
||||
i_molid = column_names.index('molid')
|
||||
elif 'mol' in column_names:
|
||||
i_molid = column_names.index('mol')
|
||||
else:
|
||||
pass # some atom_types do not have a valid molecule-ID
|
||||
|
||||
return i_atomid, i_atomtype, i_molid
|
||||
|
||||
|
||||
|
||||
def ColNames2Coords(column_names):
|
||||
""" Which of the columns correspond to coordinates
|
||||
which must be transformed using rigid-body
|
||||
(affine: rotation + translation) transformations?
|
||||
This function outputs a list of lists of triplets of integers.
|
||||
|
||||
"""
|
||||
i_x = None
|
||||
i_y = None
|
||||
i_z = None
|
||||
if 'x' in column_names:
|
||||
i_x = column_names.index('x')
|
||||
if 'y' in column_names:
|
||||
i_y = column_names.index('y')
|
||||
if 'z' in column_names:
|
||||
i_z = column_names.index('z')
|
||||
if (((i_x != None) != (i_y != None)) or
|
||||
((i_y != None) != (i_z != None)) or
|
||||
((i_z != None) != (i_x != None))):
|
||||
raise InputError('Error: custom atom_style list must define x, y, and z.\n')
|
||||
return [[i_x, i_y, i_z]]
|
||||
|
||||
|
||||
def ColNames2Vects(column_names):
|
||||
""" Which of the columns correspond to coordinates
|
||||
which must be transformed using rotations?
|
||||
Some coordinates like dipole moments and
|
||||
ellipsoid orientations should only be rotated
|
||||
(not translated).
|
||||
This function outputs a list of lists of triplets of integers.
|
||||
|
||||
"""
|
||||
vects = []
|
||||
i_mux = None
|
||||
i_muy = None
|
||||
i_muz = None
|
||||
if 'mux' in column_names:
|
||||
i_mux = column_names.index('mux')
|
||||
if 'muy' in column_names:
|
||||
i_muy = column_names.index('muy')
|
||||
if 'muz' in column_names:
|
||||
i_muz = column_names.index('muz')
|
||||
if (((i_mux != None) != (i_muy != None)) or
|
||||
((i_muy != None) != (i_muz != None)) or
|
||||
((i_muz != None) != (i_mux != None))):
|
||||
raise InputError('Error: custom atom_style list must define mux, muy, and muz or none.\n')
|
||||
if i_mux != None:
|
||||
vects.append([i_mux, i_muy, i_muz])
|
||||
return vects
|
||||
1589
tools/moltemplate/src/moltemplate.sh
Executable file
1589
tools/moltemplate/src/moltemplate.sh
Executable file
File diff suppressed because it is too large
Load Diff
41
tools/moltemplate/src/nbody_Angles.py
Normal file
41
tools/moltemplate/src/nbody_Angles.py
Normal file
@ -0,0 +1,41 @@
|
||||
from nbody_graph_search import Ugraph
|
||||
|
||||
# To find 3-body "angle" interactions, we would use this subgraph:
|
||||
#
|
||||
#
|
||||
# *---*---* => 1st bond connects atoms 0 and 1
|
||||
# 0 1 2 2nd bond connects atoms 1 and 2
|
||||
#
|
||||
|
||||
bond_pattern = Ugraph([(0,1), (1,2)])
|
||||
# (Ugraph atom indices begin at 0, not 1)
|
||||
|
||||
|
||||
# The next function eliminates the redundancy between 0-1-2 and 2-1-0:
|
||||
def canonical_order(match):
|
||||
"""
|
||||
When searching for atoms with matching bond patterns GraphMatcher
|
||||
often returns redundant results. We must define a "canonical_order"
|
||||
function which sorts the atoms and bonds in a way which is consistent
|
||||
with the type of N-body interaction being considered.
|
||||
The atoms (and bonds) in a candidate match are rearranged by the
|
||||
canonical_order(). Then the re-ordered list of atom and bond ids is
|
||||
tested against the list of atom/bond ids in the matches-found-so-far,
|
||||
before it is added to the list of interactions found so far.
|
||||
(For example, it does not make sense to define a separate 3-body angle
|
||||
interaction between atoms 1, 2, 3 AND 3, 2, 1. This is the same triplet
|
||||
of atoms, and the angle between them is the same.)
|
||||
|
||||
"""
|
||||
# match[0][0:2] contains the ID numbers for the 3 atoms in the match
|
||||
atom0 = match[0][0]
|
||||
atom1 = match[0][1]
|
||||
atom2 = match[0][2]
|
||||
# match[1][0:1] contains the ID numbers for the 2 bonds
|
||||
bond0 = match[1][0]
|
||||
bond1 = match[1][1]
|
||||
if atom0 < atom2:
|
||||
#return ((atom0, atom1, atom2), (bond0, bond1)) same thing as:
|
||||
return match
|
||||
else:
|
||||
return ((atom2, atom1, atom0), (bond1, bond0))
|
||||
31
tools/moltemplate/src/nbody_Bonds.py
Normal file
31
tools/moltemplate/src/nbody_Bonds.py
Normal file
@ -0,0 +1,31 @@
|
||||
from nbody_graph_search import Ugraph
|
||||
|
||||
# To find 2-body "bond" interactions, we would use this subgraph:
|
||||
#
|
||||
#
|
||||
# *---* => one bond connects atoms 0 and 1
|
||||
# 0 1
|
||||
#
|
||||
|
||||
bond_pattern = Ugraph([(0,1)])
|
||||
# (Ugraph atom indices begin at 0, not 1)
|
||||
|
||||
|
||||
# The next function eliminates the redundancy between 0-1 and 1-0:
|
||||
def canonical_order(match):
|
||||
"""
|
||||
It does not make sense to define a separate bond between atoms 1 and 2,
|
||||
and between atoms 2 and 1. This function will swap the atoms in the bond
|
||||
if the first atom > second atom.
|
||||
|
||||
"""
|
||||
# match[0][0:2] contains the ID numbers for the 2 atoms in the match
|
||||
atom0 = match[0][0]
|
||||
atom1 = match[0][1]
|
||||
# match[1][0:1] contains the ID numbers for the 1 bond
|
||||
bond0 = match[1][0]
|
||||
if atom0 < atom1:
|
||||
#return ((atom0, atom1), (bond0)) same thing as:
|
||||
return match
|
||||
else:
|
||||
return ((atom1, atom0), (bond0))
|
||||
44
tools/moltemplate/src/nbody_Dihedrals.py
Normal file
44
tools/moltemplate/src/nbody_Dihedrals.py
Normal file
@ -0,0 +1,44 @@
|
||||
from nbody_graph_search import Ugraph
|
||||
|
||||
# To find 4-body "dihedral" interactions, we would use this subgraph:
|
||||
#
|
||||
# 1st bond connects atoms 0 and 1
|
||||
# *---*---*---* => 2nd bond connects atoms 1 and 2
|
||||
# 0 1 2 3 3rd bond connects atoms 2 and 3
|
||||
#
|
||||
|
||||
bond_pattern = Ugraph([(0,1), (1,2), (2,3)])
|
||||
# (Ugraph atom indices begin at 0, not 1)
|
||||
|
||||
|
||||
def canonical_order(match):
|
||||
"""
|
||||
When searching for atoms with matching bond patterns GraphMatcher
|
||||
often returns redundant results. We must define a "canonical_order"
|
||||
function which sorts the atoms and bonds in a way which is consistent
|
||||
with the type of N-body interaction being considered.
|
||||
The atoms (and bonds) in a candidate match are rearranged by the
|
||||
canonical_order(). Then the re-ordered list of atom and bond ids is
|
||||
tested against the list of atom/bond ids in the matches-found-so-far,
|
||||
before it is added to the list of interactions found so far.
|
||||
(For example, it does not make sense to define a separate 4-body dihedral-
|
||||
angle interaction between atoms 1, 2, 3, 4 AND 4, 3, 2, 1. The dihedral-
|
||||
angle is not altered when the order of atoms is reversed, so we arbitrarily
|
||||
choose whichever order causes the first atom to have the lower atomID.)
|
||||
|
||||
"""
|
||||
|
||||
# match[0][0:3] contains the ID numbers of the 4 atoms in the match
|
||||
atom0 = match[0][0]
|
||||
atom1 = match[0][1]
|
||||
atom2 = match[0][2]
|
||||
atom3 = match[0][3]
|
||||
# match[1][0:2] contains the ID numbers of the the 3 bonds
|
||||
bond0 = match[1][0]
|
||||
bond1 = match[1][1]
|
||||
bond2 = match[1][2]
|
||||
if atom0 < atom3:
|
||||
#return ((atom0, atom1, atom2, atom3), (bond0, bond1, bond2)) same as:
|
||||
return match
|
||||
else:
|
||||
return ((atom3, atom2, atom1, atom0), (bond2, bond1, bond0))
|
||||
41
tools/moltemplate/src/nbody_Impropers.py
Normal file
41
tools/moltemplate/src/nbody_Impropers.py
Normal file
@ -0,0 +1,41 @@
|
||||
from nbody_graph_search import Ugraph
|
||||
|
||||
# To find 4-body "improper" interactions, we would use this subgraph:
|
||||
# 3
|
||||
# * 1st bond connects atoms 0 and 1
|
||||
# | => 2nd bond connects atoms 0 and 2
|
||||
# _.*._ 3rd bond connects atoms 0 and 3
|
||||
# *' 0 `*
|
||||
# 1 2
|
||||
#
|
||||
|
||||
bond_pattern = Ugraph([(0,1), (0,2), (0,3)])
|
||||
# (Ugraph atom indices begin at 0, not 1)
|
||||
|
||||
|
||||
def canonical_order(match):
|
||||
"""
|
||||
When searching for atoms with matching bond patterns GraphMatcher
|
||||
often returns redundant results. We must define a "canonical_order"
|
||||
function which sorts the atoms and bonds in a way which is consistent
|
||||
with the type of N-body interaction being considered.
|
||||
The atoms (and bonds) in a candidate match are rearranged by the
|
||||
canonical_order(). Then the re-ordered list of atom and bond ids is
|
||||
tested against the list of atom/bond ids in the matches-found-so-far,
|
||||
before it is added to the list of interactions found so far.
|
||||
(For example, it does not make sense to define a separate 4-body improper-
|
||||
angle interaction between atoms 1, 2, 3, 4 AND 1, 3, 2, 4. The improper-
|
||||
angle is defined as the angle between planes formed by atoms 1,2,3 & 2,3,4.
|
||||
This is not effected by swapping the middle pair of atoms so we arbitrarily
|
||||
sort them so that the second atom has a lower atomID than the third atom.)
|
||||
|
||||
"""
|
||||
atom0 = match[0][0]
|
||||
atom1 = match[0][1]
|
||||
atom2 = match[0][2]
|
||||
atom3 = match[0][3]
|
||||
if atom1 <= atom2:
|
||||
#return ((atom0,atom1,atom2,atom3), match[1]) same thing as:
|
||||
return match
|
||||
else:
|
||||
return ((atom0,atom2,atom1,atom3), match[1])
|
||||
598
tools/moltemplate/src/nbody_by_type.py
Executable file
598
tools/moltemplate/src/nbody_by_type.py
Executable file
@ -0,0 +1,598 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Author: Andrew Jewett (jewett.aij at g mail)
|
||||
# http://www.chem.ucsb.edu/~sheagroup
|
||||
# License: 3-clause BSD License (See LICENSE.TXT)
|
||||
# Copyright (c) 2011, Regents of the University of California
|
||||
# All rights reserved.
|
||||
|
||||
man_page_text = """
|
||||
|
||||
nbody_by_type.py reads a LAMMPS data file (or an excerpt of a LAMMPS)
|
||||
data file containing bonded many-body interactions by atom type
|
||||
(and bond type), and generates a list of additional interactions
|
||||
in LAMMPS format consistent with those type (to the standard out).
|
||||
|
||||
Typical Usage:
|
||||
|
||||
nbody_by_type.py X < old.data > new.data
|
||||
|
||||
--or--
|
||||
|
||||
nbody_by_type.py X \\
|
||||
-atoms atoms.data \\
|
||||
-bonds bonds.data \\
|
||||
-nbody X.data \\
|
||||
-nbodybytype X_by_type.data \\
|
||||
> new_X.data
|
||||
|
||||
In both cases "X" denotes the interaction type, which
|
||||
is either "Angles", "Dihedrals", or "Impropers".
|
||||
(Support for other interaction types can be added by the user. See below.)
|
||||
|
||||
-------- Example 1 -------
|
||||
|
||||
nbody_by_type.py X < old.data > new.data
|
||||
|
||||
In this example, nbody_by_type.py reads a LAMMPS data file
|
||||
"orig.data", and extracts the relevant section ("Angles",
|
||||
"Dihedrals", or "Impropers"). It also looks a section named "X By Type",
|
||||
(eg. "Angles By type", "Impropers By type", "Impropers By type")
|
||||
which contains a list of criteria for automatically defining additional
|
||||
interactions of that type. For example, this file might contain:
|
||||
|
||||
Angle By Type
|
||||
|
||||
7 1 2 1 * *
|
||||
8 2 2 * * *
|
||||
9 3 4 3 * *
|
||||
|
||||
The first column is an interaction type ID.
|
||||
The next 3 columns are atom type identifiers.
|
||||
The final 2 columns are bond type identifiers.
|
||||
The * is a wildcard symbol indicating there is no preference for bond types
|
||||
in this example. (Optionally, regular expressions can also be used to
|
||||
define a type match, by enclosing the atom or bond type in / slashes.)
|
||||
|
||||
The first line tells us to that there should be a 3-body "Angle"
|
||||
interaction of type "7" whenever an atom of type 1 is bonded to an atom
|
||||
of type "2", which is bonded to another atom of type "1" again.
|
||||
The second line tells us that an angle is defined whenever three atoms
|
||||
are bonded together and the first two are of type "2".
|
||||
(Redundant angle interactions are filtered.)
|
||||
|
||||
New interactions are created for every group of bonded
|
||||
atoms which match these criteria if they are bonded together
|
||||
in the relevant way for that interaction type (as determined by
|
||||
nbody_X.py), and printed to the standard output. For example,
|
||||
suppose you are automatically generating 3-body "Angle" interactions using:
|
||||
|
||||
nbody_by_type Angles < old.data > new.data
|
||||
|
||||
The file "new.data" will be identical to "old.data", however the
|
||||
"Angles By Type" section will be deleted, and the following lines of
|
||||
text will be added to the "Angles" section:
|
||||
|
||||
394 7 5983 5894 5895
|
||||
395 7 5984 5895 5896
|
||||
396 7 5985 5896 5897
|
||||
: : : : :
|
||||
847 9 14827 14848 14849
|
||||
|
||||
The numbers in the first column are counters which assign a ID to
|
||||
every interaction of that type, and start where the original "Angles"
|
||||
data left off (New angle ID numbers do not overlap with old ID numbers).
|
||||
The text in the second column ("7", "9", ...) matches the text from the
|
||||
first column of the "Angle By Type" section of the input file.
|
||||
|
||||
-------- Example 2 -------
|
||||
|
||||
nbody_by_type.py X \\
|
||||
-atoms atoms.data \\
|
||||
-bonds bonds.data \\
|
||||
-nbody X.data \\
|
||||
-nbodybytype X_by_type.data \\
|
||||
-prefix "SOMESTRING" -suffix "ANOTHERSTRING" \\
|
||||
> new_X.data
|
||||
|
||||
In particular, for Angle interactions:
|
||||
|
||||
nbody_by_type.py Angles \\
|
||||
-atoms atoms.data \\
|
||||
-bonds bonds.data \\
|
||||
-nbody angles.data \\
|
||||
-nbodybytype angles_by_type.data \\
|
||||
> new_Angles.data
|
||||
|
||||
When run this way, nbody_by_type.py behaves exactly the same way
|
||||
as in Example 1, however only the lines of text corresponding to
|
||||
the new generated interactions are printed, (not the entire data file).
|
||||
Also note, that when run this way, nbody_by_type.py does not read the
|
||||
LAMMPS data from the standard input. Instead, it reads each section of
|
||||
the data file from a different file indicated by the arguments following
|
||||
the "-atoms", "-bonds", "-nbody", and "-nbodybytype" flags.
|
||||
|
||||
"Angles" is a 3-body interaction style. So when run this way,
|
||||
nbody_by_type.py will create a 5 (=3+2) column file (new_Angles.data).
|
||||
|
||||
Note: the atom, bond and other IDs/types in need not be integers.
|
||||
|
||||
Note: This program must be distributed with several python modules, including:
|
||||
nbody_Angles.py, nbody_Dihedrals.py, and nbody_Impropers.py. These
|
||||
contain bond definitions for angular, dihedral, and improper interactions.
|
||||
(In case any new interaction types are ever added to LAMMPS, with only
|
||||
a few lines of python it is easy to edit to define new bonded
|
||||
interaction types by supplying new "nbody_X.py" python module.
|
||||
Refer to the modules listed above for examples.)
|
||||
|
||||
Note: Optional "-prefix" and "-suffix" arguments can be included to decorate
|
||||
the interaction IDs (first column). For example, -prefix "auto_" and
|
||||
-suffix "_angle", causes "new_Angles.data" to contain the following text:
|
||||
|
||||
auto_394_angle 7 5983 5894 5895
|
||||
auto_395_angle 7 5984 5895 5896
|
||||
auto_396_angle 7 5985 5896 5897
|
||||
: : : : :
|
||||
auto_847_angle 9 14827 14848 14849
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
from extract_lammps_data import *
|
||||
from nbody_by_type_lib import GenInteractions_str
|
||||
from ttree_lex import *
|
||||
from lttree_styles import AtomStyle2ColNames, ColNames2AidAtypeMolid
|
||||
|
||||
|
||||
if sys.version < '2.6':
|
||||
raise InputError('Error: Using python '+sys.version+'\n'
|
||||
' Alas, you must upgrade to a newever version of python (2.7 or later).')
|
||||
elif sys.version < '2.7':
|
||||
sys.stderr.write('--------------------------------------------------------\n'
|
||||
'----------------- WARNING: OLD PYTHON VERSION ----------\n'
|
||||
' This program is untested on your python version ('+sys.version+').\n'
|
||||
' PLEASE LET ME KNOW IF THIS PROGRAM CRASHES (and upgrade python).\n'
|
||||
' -Andrew 2012-4-16\n'
|
||||
'--------------------------------------------------------\n'
|
||||
'--------------------------------------------------------\n')
|
||||
from ordereddict import OrderedDict
|
||||
else:
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
|
||||
def GenInteractions_lines(lines_atoms,
|
||||
lines_bonds,
|
||||
lines_nbody,
|
||||
lines_nbodybytype,
|
||||
atom_style,
|
||||
g_bond_pattern,
|
||||
canonical_order, #function to sort atoms and bonds
|
||||
prefix='',
|
||||
suffix='',
|
||||
report_progress = False):
|
||||
|
||||
column_names = AtomStyle2ColNames(atom_style)
|
||||
i_atomid, i_atomtype, i_molid = ColNames2AidAtypeMolid(column_names)
|
||||
|
||||
atomids_str = []
|
||||
atomtypes_str = []
|
||||
|
||||
for iv in range(0, len(lines_atoms)):
|
||||
line = lines_atoms[iv].strip()
|
||||
if '#' in line:
|
||||
icomment = line.find('#')
|
||||
line = (line[:icomment]).strip()
|
||||
if len(line) > 0:
|
||||
tokens = SplitQuotedString(line)
|
||||
if ((len(tokens) <= i_atomid) or (len(tokens) <= i_atomtype)):
|
||||
raise(InputError('Error not enough columns on line '+str(iv+1)+' of \"Atoms\" section.'))
|
||||
tokens = SplitQuotedString(line)
|
||||
atomids_str.append(EscCharStrToChar(tokens[i_atomid]))
|
||||
atomtypes_str.append(EscCharStrToChar(tokens[i_atomtype]))
|
||||
|
||||
bondids_str = []
|
||||
bondtypes_str = []
|
||||
bond_pairs = []
|
||||
for ie in range(0, len(lines_bonds)):
|
||||
line = lines_bonds[ie].strip()
|
||||
if '#' in line:
|
||||
icomment = line.find('#')
|
||||
line = (line[:icomment]).strip()
|
||||
if len(line) > 0:
|
||||
tokens = SplitQuotedString(line)
|
||||
if len(tokens) < 4:
|
||||
raise(InputError('Error not enough columns on line '+str(ie+1)+' of \"Bonds\" section.'))
|
||||
bondids_str.append(EscCharStrToChar(tokens[0]))
|
||||
bondtypes_str.append(EscCharStrToChar(tokens[1]))
|
||||
bond_pairs.append( (EscCharStrToChar(tokens[2]),
|
||||
EscCharStrToChar(tokens[3])) )
|
||||
|
||||
typepattern_to_coefftypes = []
|
||||
|
||||
for i in range(0, len(lines_nbodybytype)):
|
||||
line = lines_nbodybytype[i].strip()
|
||||
if '#' in line:
|
||||
icomment = line.find('#')
|
||||
line = (line[:icomment]).strip()
|
||||
if len(line) > 0:
|
||||
tokens = SplitQuotedString(line)
|
||||
|
||||
if ((len(tokens) != 1 + g_bond_pattern.GetNumVerts()) and
|
||||
(len(tokens) != 1 + g_bond_pattern.GetNumVerts()
|
||||
+ g_bond_pattern.GetNumEdges())):
|
||||
raise(InputError('Error: Wrong number of columns in \"By Type\" section of data file.\n'
|
||||
'Offending line:\n'+
|
||||
'\"'+line+'\"\n'
|
||||
'Expected either '+
|
||||
str(1 + g_bond_pattern.GetNumVerts()) + ' or ' +
|
||||
str(1 + g_bond_pattern.GetNumVerts() +
|
||||
g_bond_pattern.GetNumEdges())
|
||||
+ ' colunms.'))
|
||||
|
||||
coefftype = EscCharStrToChar(tokens[0])
|
||||
typepattern = []
|
||||
|
||||
for typestr in tokens[1:]:
|
||||
if ((len(typestr) >= 2) and
|
||||
(typestr[0] == '/') and (typestr[-1] == '/')):
|
||||
regex_str = typestr[1:-1]
|
||||
typepattern.append( re.compile(regex_str) )
|
||||
else:
|
||||
typepattern.append(EscCharStrToChar(typestr))
|
||||
|
||||
# If the user neglected to specify the bond types, assume '*'
|
||||
if len(tokens) == 1 + g_bond_pattern.GetNumVerts():
|
||||
typepattern += ['*'] * g_bond_pattern.GetNumEdges()
|
||||
|
||||
typepattern_to_coefftypes.append([typepattern, coefftype])
|
||||
|
||||
coefftype_to_atomids_str = GenInteractions_str(bond_pairs,
|
||||
g_bond_pattern,
|
||||
typepattern_to_coefftypes,
|
||||
canonical_order,
|
||||
atomids_str,
|
||||
atomtypes_str,
|
||||
bondids_str,
|
||||
bondtypes_str,
|
||||
report_progress)
|
||||
lines_nbody_new = []
|
||||
for coefftype, atomids_list in coefftype_to_atomids_str.items():
|
||||
for atomids_found in atomids_list:
|
||||
n = len(lines_nbody) + len(lines_nbody_new) + 1
|
||||
line = prefix+str(n)+suffix+' '+ \
|
||||
coefftype+' '+(' '.join(atomids_found))+'\n'
|
||||
lines_nbody_new.append(line)
|
||||
|
||||
return lines_nbody_new
|
||||
|
||||
|
||||
|
||||
def GenInteractions_files(lines_data,
|
||||
src_bond_pattern,
|
||||
fname_atoms,
|
||||
fname_bonds,
|
||||
fname_nbody,
|
||||
fname_nbodybytype,
|
||||
section_name,
|
||||
section_name_bytype,
|
||||
atom_style,
|
||||
prefix='',
|
||||
suffix='',
|
||||
report_progress = False):
|
||||
|
||||
if fname_atoms == None:
|
||||
lines_atoms = [line for line in ExtractDataSection(lines_data, 'Atoms')]
|
||||
else:
|
||||
try:
|
||||
f = open(fname_atoms, 'r')
|
||||
except:
|
||||
sys.stderr.write('Error: Unable to open file \"'+fname_atoms+'\" for reading.\n')
|
||||
sys.exit(-1)
|
||||
lines_atoms = [line for line in f.readlines()
|
||||
if ((len(line.strip())>0) and (line.strip()[0] != '#'))]
|
||||
f.close()
|
||||
|
||||
|
||||
if fname_bonds == None:
|
||||
lines_bonds = [line for line in ExtractDataSection(lines_data, 'Bonds')]
|
||||
else:
|
||||
try:
|
||||
f = open(fname_bonds, 'r')
|
||||
except IOError:
|
||||
sys.stderr.write('Error: Unable to open file \"'+fname_bonds+'\" for reading.\n')
|
||||
sys.exit(-1)
|
||||
lines_bonds = [line for line in f.readlines()
|
||||
if ((len(line.strip())>0) and (line.strip()[0] != '#'))]
|
||||
f.close()
|
||||
|
||||
|
||||
if fname_nbody == None:
|
||||
lines_nbody = [line for line in ExtractDataSection(lines_data, section_name)]
|
||||
else:
|
||||
try:
|
||||
f = open(fname_nbody, 'r')
|
||||
lines_nbody = [line for line in f.readlines()
|
||||
if ((len(line.strip())>0) and (line.strip()[0] != '#'))]
|
||||
f.close()
|
||||
except IOError:
|
||||
#sys.stderr.write(' (omitting optional file \"'+fname_nbody+'\")\n')
|
||||
lines_nbody = []
|
||||
|
||||
|
||||
if fname_nbodybytype == None:
|
||||
lines_nbodybytype=[line for
|
||||
line in ExtractDataSection(lines_data,
|
||||
section_name_bytype)]
|
||||
|
||||
else:
|
||||
try:
|
||||
f = open(fname_nbodybytype, 'r')
|
||||
except:
|
||||
sys.stderr.write('Error: Unable to open file \"'+fname_nbodybytype+'\" for reading.\n')
|
||||
sys.exit(-1)
|
||||
lines_nbodybytype = [line for line in f.readlines()
|
||||
if((len(line.strip())>0)and(line.strip()[0]!='#'))]
|
||||
f.close()
|
||||
|
||||
|
||||
g = __import__(src_bond_pattern) #defines g.bond_pattern, g.canonical_order
|
||||
|
||||
|
||||
return GenInteractions_lines(lines_atoms,
|
||||
lines_bonds,
|
||||
lines_nbody,
|
||||
lines_nbodybytype,
|
||||
atom_style,
|
||||
g.bond_pattern,
|
||||
g.canonical_order,
|
||||
prefix,
|
||||
suffix,
|
||||
report_progress)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
g_program_name = __file__.split('/')[-1] # = 'nbody_by_type.py'
|
||||
g_date_str = '2013-4-16'
|
||||
g_version_str = '0.11'
|
||||
|
||||
####### Main Code Below: #######
|
||||
sys.stderr.write(g_program_name+' v'+g_version_str+' '+g_date_str+' ')
|
||||
if sys.version < '3':
|
||||
sys.stderr.write(' (python version < 3)\n')
|
||||
else:
|
||||
sys.stderr.write('\n')
|
||||
|
||||
try:
|
||||
|
||||
fname_atoms = None
|
||||
fname_bonds = None
|
||||
fname_nbody = None
|
||||
fname_nbodybytype = None
|
||||
atom_style = 'full'
|
||||
prefix=''
|
||||
suffix=''
|
||||
|
||||
argv = [arg for arg in sys.argv]
|
||||
|
||||
if len(argv) == 1:
|
||||
raise InputError('Error: Missing argument required.\n'
|
||||
' The \"'+g_program_name+'\" program requires an argument containing the\n'
|
||||
' name of a section from a LAMMPS data file storing bonded interactions.\n'
|
||||
' (For example: "Angles", "Dihedrals", or "Impropers".)\n'
|
||||
#' Note: The first letter of each section is usually capitalized.)\n'
|
||||
'\n'
|
||||
'--------------- general documentation -------------\n'
|
||||
'\n' + man_page_text + '\n')
|
||||
|
||||
section_name = '' # (This will be replaced later.)
|
||||
|
||||
# Loop over the remaining arguments not processed yet.
|
||||
# These arguments are specific to the lttree.py program
|
||||
# and are not understood by ttree.py:
|
||||
i = 1
|
||||
while i < len(argv):
|
||||
#sys.stderr.write('argv['+str(i)+'] = \"'+argv[i]+'\"\n')
|
||||
if ((argv[i].lower() == '-?') or
|
||||
(argv[i].lower() == '--?') or
|
||||
(argv[i].lower() == '-help') or
|
||||
(argv[i].lower() == '-help')):
|
||||
if i+1 >= len(argv):
|
||||
sys.stdout.write(man_page_text+'\n')
|
||||
sys.exit(0)
|
||||
|
||||
elif argv[i].lower() == '-atoms':
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by a file name containing lines of\n'
|
||||
' text which might appear in the "Atoms" section of a LAMMPS data file.\n')
|
||||
fname_atoms = argv[i+1]
|
||||
del(argv[i:i+2])
|
||||
|
||||
elif argv[i].lower() == '-bonds':
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by a file name containing lines of\n'
|
||||
' text which might appear in the "Bonds" section of a LAMMPS data file.\n')
|
||||
fname_bonds = argv[i+1]
|
||||
del(argv[i:i+2])
|
||||
|
||||
elif argv[i].lower() == '-nbody':
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by a file name\n')
|
||||
|
||||
#raise InputError('Error: '+argv[i]+' flag should be followed by a file name containing lines of\n'
|
||||
# ' text which might appear in the "'+section_name+' section of a LAMMPS data file.\n')
|
||||
fname_nbody = argv[i+1]
|
||||
del(argv[i:i+2])
|
||||
|
||||
elif argv[i].lower() == '-nbodybytype':
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by a file name\n')
|
||||
|
||||
#raise InputError('Error: '+argv[i]+' flag should be followed by a file name containing\n'
|
||||
# ' text which might appear in the "'+section_name+' By Type" section\n'
|
||||
# ' of a LAMMPS data file.\n')
|
||||
fname_nbodybytype = argv[i+1]
|
||||
del(argv[i:i+2])
|
||||
|
||||
elif ((argv[i].lower() == '-atom-style') or
|
||||
(argv[i].lower() == '-atom_style')):
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by a an atom_style name.\n'
|
||||
' (Or single quoted string which includes a space-separated\n'
|
||||
' list of column names.)\n')
|
||||
atom_style = argv[i+1]
|
||||
del(argv[i:i+2])
|
||||
|
||||
elif argv[i].lower() == '-prefix':
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by a prefix string\n'
|
||||
' (a string you want to appear to the left of the integer\n'
|
||||
' which counts the bonded interactions you have generated.)\n')
|
||||
prefix = argv[i+1]
|
||||
del(argv[i:i+2])
|
||||
|
||||
elif argv[i].lower() == '-suffix':
|
||||
if i+1 >= len(argv):
|
||||
raise InputError('Error: '+argv[i]+' flag should be followed by a suffix string\n'
|
||||
' (a string you want to appear to the right of the integer\n'
|
||||
' which counts the bonded interactions you have generated.)\n')
|
||||
prefix = argv[i+1]
|
||||
del(argv[i:i+2])
|
||||
|
||||
elif argv[i][0] == '-':
|
||||
raise InputError('Error('+g_program_name+'):\n'
|
||||
'Unrecogized command line argument \"'+argv[i]+'\"\n')
|
||||
else:
|
||||
i += 1
|
||||
|
||||
|
||||
if len(argv) == 1:
|
||||
raise InputError('Error: Missing argument required.\n'
|
||||
' The \"'+g_program_name+'\" program requires an argument containing the\n'
|
||||
' name of a section from a LAMMPS data file storing bonded interactions.\n'
|
||||
' (For example: "Angles", "Dihedrals", or "Impropers".)\n')
|
||||
#' Note: The first letter of each section is usually capitalized.)\n'
|
||||
|
||||
elif len(argv) == 2:
|
||||
section_name = argv[1]
|
||||
bond_pattern_module_name = 'nbody_'+section_name
|
||||
del(argv[1:2])
|
||||
|
||||
else:
|
||||
# if there are more than 2 remaining arguments,
|
||||
problem_args = ['\"'+arg+'\"' for arg in argv[1:]]
|
||||
raise InputError('Syntax Error('+g_program_name+'):\n\n'
|
||||
' Problem with argument list.\n'
|
||||
' The remaining arguments are:\n\n'
|
||||
' '+(' '.join(problem_args))+'\n\n'
|
||||
' (The actual problem may be earlier in the argument list.)\n')
|
||||
|
||||
|
||||
# ------------ Done parsing argument list ----------
|
||||
|
||||
section_name_bytype = section_name + ' By Type'
|
||||
|
||||
if (fname_atoms or fname_bonds or fname_nbody or fname_nbodybytype):
|
||||
output_full_DATA_file = False
|
||||
lines_data = []
|
||||
else:
|
||||
output_full_DATA_file = True
|
||||
lines_data = sys.stdin.readlines()
|
||||
|
||||
# Calculate the interactions and generate a list of lines of text
|
||||
lines_new_interactions = \
|
||||
GenInteractions_files(lines_data,
|
||||
bond_pattern_module_name,
|
||||
fname_atoms,
|
||||
fname_bonds,
|
||||
fname_nbody,
|
||||
fname_nbodybytype,
|
||||
section_name,
|
||||
section_name_bytype,
|
||||
atom_style,
|
||||
prefix,
|
||||
suffix,
|
||||
report_progress=True)
|
||||
|
||||
# Print this text to the standard out.
|
||||
|
||||
# Question: Do we write out the entire DATA file,
|
||||
# or just the portion that was generated by this program?
|
||||
|
||||
if not output_full_DATA_file:
|
||||
# ...then only print out the interactions which were generated
|
||||
# by this program, omitting any lines from the original data file:
|
||||
|
||||
# (This is the way I usually run this program.)
|
||||
for line in lines_new_interactions:
|
||||
sys.stdout.write(line)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
|
||||
# ...then print out the entire data file, deleting the "By Type"
|
||||
# section, and adding the generated lines of text to the corresponding
|
||||
|
||||
# If present, update the interaction counter at the beginning
|
||||
# of the LAMMPS data file. (For example, if if 100 new "Angles"
|
||||
# interactions were generated, replace "2 Angles" with "102 Angles")
|
||||
#
|
||||
for i in range(0, len(lines_data)):
|
||||
line = lines_data[i].strip()
|
||||
tokens = SplitQuotedString(line)
|
||||
|
||||
# updating the interaction counter
|
||||
if ((len(tokens) == 2) and (tokens[1] == (section_name).lower())):
|
||||
tokens[0] = str(int(tokens[0]) + len(lines_new_interactions))
|
||||
lines_data[i] = ' '.join(tokens) + '\n'
|
||||
|
||||
# stop when you come to a section header
|
||||
elif line in lammps_data_sections:
|
||||
#"lammps_data_sections" is defined in "extract_lammps_data.py"
|
||||
break
|
||||
|
||||
|
||||
# locate the appropriate section of the data file
|
||||
# (storing the type of interactions we just created)
|
||||
i_nbody_a, i_nbody_b = \
|
||||
FindDataSection(lines_data, section_name)
|
||||
|
||||
if i_nbody_a == -1:
|
||||
if len(lines_new_interactions) > 0:
|
||||
# If not found, create a new section at the end of the file,
|
||||
# containing a section name followed by the list of lines
|
||||
lines_data += ['\n', section_name+'\n', '\n'] + \
|
||||
lines_new_interactions + ['\n']
|
||||
else:
|
||||
# Insert the new lines into the existing section
|
||||
lines_data[i_nbody_b:i_nbody_b] = lines_new_interactions
|
||||
|
||||
# Figure out where the "By Type" section is located
|
||||
# (so we skip over it)
|
||||
i_bytype_a, i_bytype_b = \
|
||||
FindDataSection(lines_data, section_name_bytype)
|
||||
|
||||
in_bytype_section = False
|
||||
for i in range(0, len(lines_data)):
|
||||
line = lines_data[i].strip()
|
||||
# Omit all lines of text in the 'By Type' section (including the
|
||||
# header and commments or blank lines which immediately follow it.)
|
||||
if line == section_name_bytype:
|
||||
in_bytype_section = True
|
||||
elif i == i_bytype_b:
|
||||
in_bytype_section = False
|
||||
|
||||
if not in_bytype_section:
|
||||
sys.stdout.write(lines_data[i])
|
||||
|
||||
except (ValueError, InputError) as err:
|
||||
sys.stderr.write('\n'+str(err)+'\n')
|
||||
sys.exit(-1)
|
||||
|
||||
312
tools/moltemplate/src/nbody_by_type_lib.py
Normal file
312
tools/moltemplate/src/nbody_by_type_lib.py
Normal file
@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Author: Andrew Jewett (jewett.aij at g mail)
|
||||
# http://www.chem.ucsb.edu/~sheagroup
|
||||
# License: 3-clause BSD License (See LICENSE.TXT)
|
||||
# Copyright (c) 2012, Regents of the University of California
|
||||
# All rights reserved.
|
||||
|
||||
|
||||
import sys
|
||||
from nbody_graph_search import *
|
||||
#from collections import namedtuple
|
||||
from collections import defaultdict, OrderedDict
|
||||
from ttree_lex import MatchesPattern, MatchesAll, InputError
|
||||
#import gc
|
||||
|
||||
|
||||
def GenInteractions_int(G_system,
|
||||
g_bond_pattern,
|
||||
typepattern_to_coefftype,
|
||||
canonical_order, #function to sort atoms and bonds
|
||||
atomtypes_int2str,
|
||||
bondtypes_int2str,
|
||||
report_progress = False): #print messages to sys.stderr?
|
||||
"""
|
||||
GenInteractions() automatically determines a list of interactions
|
||||
present in a system of bonded atoms (argument "G_system"),
|
||||
which satisfy the bond topology present in "g_bond_pattern", and
|
||||
satisfy the atom and bond type requirements in "typepattern_to_coefftype".
|
||||
|
||||
Whenever a set of atoms in "G_system" are bonded together in a way which
|
||||
matches "g_bond_pattern", and when the atom and bond types is consistent
|
||||
with one of the entries in "typepattern_to_coefftype", the corresponding
|
||||
list of atoms from G_system is appended to the list of results.
|
||||
|
||||
These results (the list of lists of atoms participating in an interaction)
|
||||
are organized according their corresponding "coefftype", a string
|
||||
which identifies the type of interaction they obey as explained above.
|
||||
results are returned as a dictionary using "coefftype" as the lookup key.
|
||||
|
||||
Arguments:
|
||||
|
||||
-- typepattern_to_coefftype is a list of 2-tuples --
|
||||
The first element of the 2-tuple is the "typepattern".
|
||||
It contains a string describing a list of atom types and bond types.
|
||||
The typepattern is associated with a "coefftype",
|
||||
which is the second element of the 2-tuple. This is a string
|
||||
which identifies the type of interaction between the atoms.
|
||||
Later on, this string can be used to lookup the force field
|
||||
parameters for this interaction elsewhere.)
|
||||
|
||||
-- Arguments: G_system, g_bond_pattern, atomtypes_int2str, bondtypes_int2str --
|
||||
|
||||
G_system stores a list of atoms and bonds, and their attributes in
|
||||
"Ugraph" format. In this format:
|
||||
Atom ID numbers are represented by indices into the G_system.verts[] list.
|
||||
Bond ID numbers are represented by indices into the G_system.edges[] list.
|
||||
Atom types are represented as integers in the G_system.verts[i].attr list.
|
||||
Bond types are represented as integers in the G_system.edges[i].attr list.
|
||||
They are converted into strings using
|
||||
atomtypes_int2str, and bondtypes_int2str.
|
||||
|
||||
g_bond_pattern is a graph which specifies the type of bonding between
|
||||
the atoms required for a match. It is in Ugraph format (however the
|
||||
atom and bond types are left blank.)
|
||||
|
||||
Atom and bond types are supplied by the user in string format. (These
|
||||
strings typically encode integers, but could be any string in principle.)
|
||||
The string-version of the ith atom type is stored in
|
||||
atomtypes_int2str[ G_system.verts[i].attr ]
|
||||
The string-version of the ith bond type is stored in
|
||||
bondtypes_int2str[ G_system.edges[i].attr ]
|
||||
|
||||
-- The "canonical_order" argument: --
|
||||
|
||||
The search for atoms with a given bond pattern often yields
|
||||
redundant matches. There is no difference for example
|
||||
between the angle formed between three consecutively
|
||||
bonded atoms (named, 1, 2, 3, for example), and the
|
||||
angle between the same atoms in reverse order (3, 2, 1).
|
||||
However both triplets of atoms will be returned by the subgraph-
|
||||
matching algorithm when searching for ALL 3-body interactions.)
|
||||
|
||||
To eliminate this redundancy, the caller must supply a "canonical_order"
|
||||
argument. This is a function which sorts the atoms and bonds in a way
|
||||
which is consistent with the type of N-body interaction being considered.
|
||||
The atoms (and bonds) in a candidate match are rearranged by the
|
||||
canonical_order(). Then the re-ordered list of atom and bond ids is
|
||||
tested against the list of atom/bond ids in the matches-found-so-far,
|
||||
before it is added.
|
||||
|
||||
"""
|
||||
|
||||
if report_progress:
|
||||
startatomid = 0
|
||||
sys.stderr.write(' Searching for matching bond patterns:\n')
|
||||
sys.stderr.write(' 0%')
|
||||
|
||||
# Figure out which atoms from "G_system" bond together in a way which
|
||||
# matches the "g_bond_pattern" argument. Organize these matches by
|
||||
# atom and bond types and store all of the non-redundant ones in
|
||||
# the "interactions_by_type" variable.
|
||||
|
||||
gm = GraphMatcher(G_system, g_bond_pattern)
|
||||
|
||||
interactions_by_type = defaultdict(list)
|
||||
|
||||
for atombondids in gm.Matches():
|
||||
# "atombondids" is a tuple.
|
||||
# atombondids[0] has atomIDs from G_system corresponding to g_bond_pattern
|
||||
# (These atomID numbers are indices into the G_system.verts[] list.)
|
||||
# atombondids[1] has bondIDs from G_system corresponding to g_bond_pattern
|
||||
# (These bondID numbers are indices into the G_system.edges[] list.)
|
||||
|
||||
# It's convenient to organize the list of interactions-between-
|
||||
# atoms in a dictionary indexed by atomtypes and bondtypes.
|
||||
# (Because many atoms and bonds typically share the same type,
|
||||
# organizing the results this way makes it faster to check
|
||||
# whether a given interaction matches a "typepattern" defined
|
||||
# by the user. We only have to check once for the whole group.)
|
||||
|
||||
atombondtypes = \
|
||||
(tuple([G_system.GetVert(Iv).attr for Iv in atombondids[0]]),
|
||||
tuple([G_system.GetEdge(Ie).attr for Ie in atombondids[1]]))
|
||||
|
||||
interactions_by_type[atombondtypes].append(atombondids)
|
||||
|
||||
if report_progress:
|
||||
# GraphMatcher.Matches() searches for matches in an order
|
||||
# that selects a different atomid number from G_system,
|
||||
# starting at 0, and continuing up to the number of atoms (-1)
|
||||
# in the system (G_system.nv-1), and using this as the first
|
||||
# atom in the match (ie match[0][0]). This number can be used
|
||||
# to guess much progress has been made so far.
|
||||
oldatomid = startatomid
|
||||
startatomid = atombondids[0][0]
|
||||
percent_complete = (100 * startatomid) // G_system.GetNumVerts()
|
||||
# report less often as more progress made
|
||||
if percent_complete <= 4:
|
||||
old_pc = (100 * oldatomid) // G_system.GetNumVerts()
|
||||
if percent_complete > old_pc:
|
||||
sys.stderr.write(' '+str(percent_complete)+'%')
|
||||
elif percent_complete <= 8:
|
||||
pc_d2 = (100 * startatomid) // (2*G_system.GetNumVerts())
|
||||
oldpc_d2 = (100 * oldatomid) // (2*G_system.GetNumVerts())
|
||||
if pc_d2 > oldpc_d2:
|
||||
sys.stderr.write(' '+str(percent_complete)+'%')
|
||||
elif percent_complete <= 20:
|
||||
pc_d4 = (100 * startatomid) // (4*G_system.GetNumVerts())
|
||||
oldpc_d4 = (100 * oldatomid) // (4*G_system.GetNumVerts())
|
||||
if pc_d4 > oldpc_d4:
|
||||
sys.stderr.write(' '+str(percent_complete)+'%')
|
||||
else:
|
||||
pc_d10 = (100 * startatomid) // (10*G_system.GetNumVerts())
|
||||
oldpc_d10 = (100 * oldatomid) // (10*G_system.GetNumVerts())
|
||||
if pc_d10 > oldpc_d10:
|
||||
sys.stderr.write(' '+str(percent_complete)+'%')
|
||||
|
||||
if report_progress:
|
||||
sys.stderr.write(' 100%\n')
|
||||
sys.stderr.write(' Looking up atom and bond types...')
|
||||
|
||||
# Now test each match to see if the types of atoms and bonds involved match
|
||||
# any of the type-patterns in the "typepattern_to_coefftype" argument.
|
||||
# If so, store them in the out_topo list
|
||||
|
||||
#coefftype_to_atomids = defaultdict(list)
|
||||
#abids_to_coefftypes = defaultdict(list)
|
||||
coefftype_to_atomids = OrderedDict()
|
||||
abids_to_coefftypes = OrderedDict()
|
||||
|
||||
count = 0
|
||||
|
||||
for typepattern, coefftype in typepattern_to_coefftype:
|
||||
if report_progress:
|
||||
sys.stderr.write(' Checking (atom-types,bond-types) against \n '+str(typepattern)+'-->'+coefftype+'\n')
|
||||
for atombondtypes, abidslist in interactions_by_type.items():
|
||||
# express atom & bond types in a tuple of the original string format
|
||||
types_atoms = [atomtypes_int2str[Iv] for Iv in atombondtypes[0]]
|
||||
types_bonds = [bondtypes_int2str[Ie] for Ie in atombondtypes[1]]
|
||||
type_strings = types_atoms + types_bonds
|
||||
# use string comparisons to check for a match with typepattern
|
||||
if MatchesAll(type_strings, typepattern): #<-see "ttree_lex.py"
|
||||
for abids in abidslist:
|
||||
|
||||
# Re-order the atoms (and bonds) in a "canonical" way.
|
||||
# Only add new interactions to the list after re-ordering
|
||||
# them and checking that they have not been added earlier.
|
||||
# (...well not when using the same coefftype at least.
|
||||
# This prevents the same triplet of atoms from
|
||||
# being used to calculate the bond-angle twice:
|
||||
# once for 1-2-3 and 3-2-1, for example.)
|
||||
abids = canonical_order(abids)
|
||||
redundant = False
|
||||
if abids in abids_to_coefftypes:
|
||||
coefftypes = abids_to_coefftypes[abids]
|
||||
if coefftype in coefftypes:
|
||||
redundant = True
|
||||
|
||||
if not redundant:
|
||||
# (It's too bad python does not
|
||||
# have an Ordered defaultdict)
|
||||
if coefftype in coefftype_to_atomids:
|
||||
coefftype_to_atomids[coefftype].append(abids[0])
|
||||
else:
|
||||
coefftype_to_atomids[coefftype]=[abids[0]]
|
||||
if abids in abids_to_coefftypes:
|
||||
abids_to_coefftypes[abids].append(coefftype)
|
||||
else:
|
||||
abids_to_coefftypes[abids] = [coefftype]
|
||||
count += 1
|
||||
|
||||
if report_progress:
|
||||
sys.stderr.write(' done\n (found '+
|
||||
str(count)+' non-redundant matches)\n')
|
||||
|
||||
return coefftype_to_atomids
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def GenInteractions_str(bond_pairs,
|
||||
g_bond_pattern,
|
||||
typepattern_to_coefftype,
|
||||
canonical_order, #function to sort atoms and bonds
|
||||
atomids_str,
|
||||
atomtypes_str,
|
||||
bondids_str,
|
||||
bondtypes_str,
|
||||
report_progress = False): #print messages to sys.stderr?
|
||||
|
||||
|
||||
assert(len(atomids_str) == len(atomtypes_str))
|
||||
assert(len(bondids_str) == len(bondtypes_str))
|
||||
# The atomids and atomtypes and bondtypes are strings.
|
||||
# First we assign a unique integer id to each string.
|
||||
|
||||
atomids_str2int = {}
|
||||
atomtypes_str2int = {}
|
||||
atomtypes_int2str = []
|
||||
atomtype_int = 0
|
||||
for i in range(0, len(atomids_str)):
|
||||
if atomids_str[i] in atomids_str2int:
|
||||
raise InputError('Error: multiple atoms have the same id ('+
|
||||
str(atomids_str[i])+')')
|
||||
atomids_str2int[atomids_str[i]] = i
|
||||
#atomtypes_int = len(atomtypes_int)+1
|
||||
if (not (atomtypes_str[i] in atomtypes_str2int)):
|
||||
atomtypes_str2int[atomtypes_str[i]] = atomtype_int
|
||||
atomtypes_int2str.append(atomtypes_str[i])
|
||||
atomtype_int += 1
|
||||
#atomtypes_int.append(atomtype_int)
|
||||
|
||||
bondids_str2int = {}
|
||||
bondtypes_str2int = {}
|
||||
bondtypes_int2str = []
|
||||
bondtype_int = 0
|
||||
for i in range(0, len(bondids_str)):
|
||||
if bondids_str[i] in bondids_str2int:
|
||||
raise InputError('Error: multiple bonds have the same id ('+
|
||||
str(bondids_str[i])+')')
|
||||
bondids_str2int[bondids_str[i]] = i
|
||||
#bondtype_int = len(bondtypes_int)+1
|
||||
if (not (bondtypes_str[i] in bondtypes_str2int)):
|
||||
bondtypes_str2int[bondtypes_str[i]] = bondtype_int
|
||||
bondtypes_int2str.append(bondtypes_str[i])
|
||||
bondtype_int += 1
|
||||
|
||||
# Now convert "bond_pairs" into the UGraph format
|
||||
G_system = Ugraph()
|
||||
for iv in range(0, len(atomtypes_str)):
|
||||
G_system.AddVertex(iv, atomtypes_str2int[atomtypes_str[iv]])
|
||||
|
||||
for ie in range(0, len(bond_pairs)):
|
||||
atomid1_str = bond_pairs[ie][0]
|
||||
atomid2_str = bond_pairs[ie][1]
|
||||
if (atomid1_str not in atomids_str2int):
|
||||
raise InputError('Error in Bonds Section:\n'
|
||||
' '+atomid1_str+' is not defined in Atoms section\n')
|
||||
if (atomid2_str not in atomids_str2int):
|
||||
raise InputError('Error in Bonds Section:\n'
|
||||
' '+atomid2_str+' is not defined in Atoms section\n')
|
||||
G_system.AddEdge(atomids_str2int[atomid1_str],
|
||||
atomids_str2int[atomid2_str],
|
||||
bondtypes_str2int[bondtypes_str[ie]])
|
||||
|
||||
coefftype_to_atomids_int = GenInteractions_int(G_system,
|
||||
g_bond_pattern,
|
||||
typepattern_to_coefftype,
|
||||
canonical_order,
|
||||
atomtypes_int2str,
|
||||
bondtypes_int2str,
|
||||
report_progress)
|
||||
coefftype_to_atomids_str = OrderedDict()
|
||||
for coefftype, atomidss_int in coefftype_to_atomids_int.items():
|
||||
sys.stderr.write(' processing coefftype='+str(coefftype)+'\n')
|
||||
for atomids_int in atomidss_int:
|
||||
if coefftype in coefftype_to_atomids_str:
|
||||
coefftype_to_atomids_str[coefftype].append(
|
||||
[atomids_str[iv] for iv in atomids_int])
|
||||
else:
|
||||
coefftype_to_atomids_str[coefftype] = \
|
||||
[[atomids_str[iv] for iv in atomids_int]]
|
||||
#gc.collect()
|
||||
|
||||
return coefftype_to_atomids_str
|
||||
|
||||
|
||||
149
tools/moltemplate/src/nbody_fix_ttree_assignments.py
Executable file
149
tools/moltemplate/src/nbody_fix_ttree_assignments.py
Executable file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
nbody_fix_ttree_assignments.py
|
||||
|
||||
This is an ugly little script which was not intended to be run by end users.
|
||||
|
||||
Typical usage:
|
||||
|
||||
nbody_fix_ttree_assignments.py "angles" new_Angles.template \
|
||||
< ttree_assignments.txt > ttree_assigmnents_new.txt
|
||||
|
||||
What it does:
|
||||
|
||||
In this example, this program extracts the first column from
|
||||
"new_Angles.template", and appends to it the first column from
|
||||
the lines in ttree_assignments.txt containing "@angle:".
|
||||
Then it adds a second column which is just a sequence of integers
|
||||
counting upwards.
|
||||
Finally it inserts this 2-column text into the appropriate place in a
|
||||
ttree_assignments.txt file, replacing the original @angle variables
|
||||
with the new ones (followed by the renumbered original).
|
||||
|
||||
|
||||
AWK/GREP equivalent
|
||||
This program is roughly equivalent to the following lines of awk/grep:
|
||||
|
||||
awk 'BEGIN{i=-1} {if(substr($0,0,8)=="$/angle") i=NR; if (i==-1){print $0}}'\
|
||||
< ttree_assignments.txt > ttree_assignments_new.txt
|
||||
|
||||
awk '{print $1}' < new_Angles.template > Angles_column1.txt
|
||||
grep '$/angle:' ttree_assignments.txt | awk '{print $1}' >> Angles_column1.txt
|
||||
awk '{print $1 " " NR}' < Angles_column1.txt >> ttree_assignments_new.txt
|
||||
|
||||
awk 'BEGIN{found=0;passed=0} {if(substr($0,0,8)=="$/angle") found=1;
|
||||
else {if (found) {passed=1}}
|
||||
if (passed) print $0}' \
|
||||
< ttree_assignments.txt >> ttree_assignments_new.txt
|
||||
|
||||
I wrote this python script (instead of using awk) just to handle quoted stings
|
||||
(and strings with other fancy characters and escape sequences).
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
from ttree_lex import SplitQuotedString, EscCharStrToChar, SafelyEncodeString, InputError
|
||||
|
||||
g_program_name = __file__.split('/')[-1]
|
||||
|
||||
try:
|
||||
if (len(sys.argv) != 3):
|
||||
raise InputError('Error running \"'+g_program_name+'\"\n'
|
||||
' Wrong number of arguments.\n'
|
||||
' (This is likely a programmer error.\n'
|
||||
' This script was not intended to be run by end users.)\n')
|
||||
|
||||
cat_name = sys.argv[1]
|
||||
f = open(sys.argv[2])
|
||||
lines_generated = f.readlines()
|
||||
f.close()
|
||||
|
||||
# Selections are simply lists of 2-tuples (pairs)
|
||||
#f = open('ttree_assignments.txt','r')
|
||||
#lines_bindings = f.readlines()
|
||||
#f.close()
|
||||
lines_bindings = sys.stdin.readlines()
|
||||
|
||||
# Figure out which lines in the 'ttree_assignments.txt' file
|
||||
# contain the variables of the type you are looking for.
|
||||
# Make note of the relevant line numbers
|
||||
i_preexisting_begin = -1
|
||||
i_preexisting_end = -1
|
||||
in_section = False
|
||||
possible_cat_names = set(['$'+cat_name, '$/'+cat_name, '${'+cat_name, '${/'+cat_name])
|
||||
|
||||
preexisting_interaction_list = []
|
||||
for i in range(0, len(lines_bindings)):
|
||||
line = lines_bindings[i].strip()
|
||||
tokens = SplitQuotedString(line) #strip comments, handle quotes
|
||||
if len(tokens) == 2:
|
||||
before_colon = tokens[0].split(':')[0]
|
||||
if before_colon in possible_cat_names:
|
||||
if i_preexisting_begin == -1:
|
||||
i_preexisting_begin = i
|
||||
in_section = True
|
||||
else:
|
||||
if in_section:
|
||||
i_preexisting_end = i
|
||||
in_section = False
|
||||
|
||||
if i_preexisting_end == -1:
|
||||
i_preexisting_end = len(lines_bindings)
|
||||
|
||||
if i_preexisting_begin == -1:
|
||||
for line in lines_bindings:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
# write out all the lines in the original file up until the point where
|
||||
# the variables in the category we are looking for were encountered
|
||||
for i in range(0, i_preexisting_begin):
|
||||
sys.stdout.write(lines_bindings[i])
|
||||
|
||||
sys.stderr.write(' (adding new lines)\n')
|
||||
|
||||
# Now add some new lines (2-column format).
|
||||
# As with any ttree_assignment.txt file:
|
||||
# The first column has our generated variable names
|
||||
# The second column has the counter assigned to that variable
|
||||
new_counter = 1
|
||||
for line_orig in lines_generated:
|
||||
line = line_orig.strip()
|
||||
if len(line) > 0:
|
||||
tokens = SplitQuotedString(line) #strip comments, handle quotes
|
||||
sys.stdout.write(tokens[0]+' '+str(new_counter)+'\n')
|
||||
new_counter += 1
|
||||
|
||||
sys.stderr.write(' (adding pre-exisiting lines)\n')
|
||||
if i_preexisting_begin != -1:
|
||||
# Append the original pre-existing interactions of that type, but assign
|
||||
# them to higher numbers. (Hopefully this helps to make sure that these
|
||||
# assignments will override any of the automatic/generated assignments.)
|
||||
# As with any ttree_assignment.txt file:
|
||||
# The first column has our generated variable names
|
||||
# The second column has the counter assigned to that variable
|
||||
|
||||
#sys.stderr.write(' i_preexisting_begin='+
|
||||
# str(i_preexisting_begin)+
|
||||
# ' i_preexisting_end='+str(i_preexisting_end)+'\n')
|
||||
|
||||
for i in range(i_preexisting_begin, i_preexisting_end):
|
||||
line = lines_bindings[i].strip()
|
||||
tokens = SplitQuotedString(line) #strip comments, handle quotes
|
||||
if len(tokens) == 2:
|
||||
sys.stdout.write(tokens[0]+' '+str(new_counter)+'\n')
|
||||
new_counter += 1
|
||||
|
||||
#sys.stderr.write(' (writing pre-exisiting lines)\n')
|
||||
|
||||
# write out all the lines in the original file after this point.
|
||||
for i in range(i_preexisting_end, len(lines_bindings)):
|
||||
sys.stdout.write(lines_bindings[i])
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
except (ValueError, InputError) as err:
|
||||
sys.stderr.write('\n'+str(err)+'\n')
|
||||
sys.exit(-1)
|
||||
|
||||
974
tools/moltemplate/src/nbody_graph_search.py
Normal file
974
tools/moltemplate/src/nbody_graph_search.py
Normal file
@ -0,0 +1,974 @@
|
||||
# Author: Andrew Jewett (jewett.aij at g mail)
|
||||
# http://www.chem.ucsb.edu/~sheagroup
|
||||
# License: 3-clause BSD License (See LICENSE.TXT)
|
||||
# Copyright (c) 2012, Regents of the University of California
|
||||
# All rights reserved.
|
||||
|
||||
|
||||
#__all__ = ['Ugraph', 'GraphMatcher', 'DFS', 'GenError', 'GraphError', 'Disconnected', 'NotUndirected']
|
||||
|
||||
|
||||
import sys
|
||||
import copy
|
||||
from operator import itemgetter
|
||||
|
||||
|
||||
class GenError(Exception):
|
||||
"""
|
||||
An exception class containing string for error reporting.
|
||||
|
||||
"""
|
||||
def __init__(self, err_msg):
|
||||
self.err_msg = err_msg
|
||||
def __str__(self):
|
||||
return self.err_msg
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
class GraphError(GenError):
|
||||
"""
|
||||
An exception class containing a graph and a string for error reporting.
|
||||
|
||||
"""
|
||||
def __init__(self, g, err_msg):
|
||||
GenError.__init__(self, err_msg)
|
||||
self.g = g
|
||||
def __str__(self):
|
||||
g_str = str(g)
|
||||
# If the string representation of the graph is too
|
||||
# large to fit in one screen, truncate it
|
||||
g_str_lines = g_str.split('\n')
|
||||
if (len(g_str_lines) > 12):
|
||||
g_str_lines = g_str_lines[0:12] + [' ...(additional lines not shown)]']
|
||||
g_str = '\n'.join(g_str_lines)
|
||||
return 'Problem with graph:\n'+g_str+'\n'+self.err_msg
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
class Disconnected(GraphError):
|
||||
def __init__(self, g, err_msg):
|
||||
GraphError.__init__(self, g, err_msg)
|
||||
|
||||
class NotUndirected(GraphError):
|
||||
def __init__(self, g, err_msg):
|
||||
GraphError.__init__(self, g, err_msg)
|
||||
|
||||
|
||||
|
||||
class Edge(object):
|
||||
def __init__(self,
|
||||
iv_start, # edge starts here (index into vertex list)
|
||||
iv_stop, # edge ends here (index into vertex list)
|
||||
attr=None): # edges have an optional type attribute
|
||||
self.start = iv_start
|
||||
self.stop = iv_stop
|
||||
self.attr = attr
|
||||
def __str__(self):
|
||||
return '('+str(self.start)+','+str(self.stop)+')'
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class Vertex(object):
|
||||
def __init__(self, attr=None):
|
||||
self.attr = attr
|
||||
|
||||
|
||||
|
||||
|
||||
class Dgraph(object):
|
||||
"""
|
||||
This class is a minimal implementation of a directed graph.
|
||||
Vertices and edges are accessed by integer index only (beginning at 0).
|
||||
Multiple edges connecting the same pair of vertices are allowed.
|
||||
(One would use the AddEdge() member function to accomplish this.)
|
||||
Both vertices and edges have an optional "attr" attribute.
|
||||
|
||||
"""
|
||||
|
||||
NULL = -1 # forbidden vertex id number (used several places)
|
||||
|
||||
def __init__(self, edgelist=None):
|
||||
"""
|
||||
The constructor accepts an optional neighborlist argument.
|
||||
This is a simple list of neighbors for every vertex in the graph
|
||||
and it completely defines the topology of the graph.
|
||||
(Vertex and edge attributes can be specified later.)
|
||||
|
||||
Alternatley, you can leave the neighborlist argument blank,
|
||||
and build the graph one vertex at a time later
|
||||
using the "AddVertex()" and "AddEdge()" commands.
|
||||
(AddEdge() commands must be issued strictly after
|
||||
all vertices have been defined.)
|
||||
|
||||
"""
|
||||
|
||||
if edgelist == None:
|
||||
self.verts = []
|
||||
self.edges = []
|
||||
self.nv = 0 #integer keeps track of # of vertices = len(self.verts)
|
||||
self.ne = 0 #integer keeps track of # of edges = len(self.edges)
|
||||
self.neighbors = [] # The adjacency list.
|
||||
|
||||
else:
|
||||
# Parse the edge-list format:
|
||||
iv_max = 0 # <-- what's the vertex with the maximum id number?
|
||||
for i in range(0, len(edgelist)):
|
||||
iv = edgelist[i][0]
|
||||
jv = edgelist[i][1]
|
||||
if ((iv < 0) or (jv < 0)):
|
||||
raise(GenError('Error in Dgraph.__init__: Negative vertex number pair encountered: ('+str(iv)+','+str(jv)+')'))
|
||||
if iv > iv_max:
|
||||
iv_max = iv
|
||||
if jv > iv_max:
|
||||
iv_max = jv
|
||||
|
||||
self.nv = iv_max+1
|
||||
self.verts = [Vertex() for iv in range(0, self.nv)]
|
||||
self.edges = []
|
||||
self.ne = 0
|
||||
self.neighbors = [[] for iv in range(0, self.nv)]
|
||||
|
||||
for i in range(0, len(edgelist)):
|
||||
iv = edgelist[i][0]
|
||||
jv = edgelist[i][1]
|
||||
self.neighbors[iv].append(self.ne)
|
||||
self.edges.append(Edge(iv, jv))
|
||||
self.ne += 1
|
||||
assert(self.ne == len(self.edges))
|
||||
|
||||
self.SortNeighborLists()
|
||||
|
||||
|
||||
def AddVertex(self, iv=-1, attr=None):
|
||||
"""
|
||||
Add a vertex to the graph.
|
||||
(Edges connected to this vertex must be added later using "AddEdge()"
|
||||
All vertices should be added before "AddEdge()" is ever invoked.)
|
||||
|
||||
Optional "attr" argument allows you to set the attribute of this vertex.
|
||||
(for example, in a molecule this might correspond to the type of atom
|
||||
in the molecule).
|
||||
|
||||
Optional "iv" argument allows you to specify the index of that vertex.
|
||||
Vertices can be added in any order, but thei vertex id numbers
|
||||
should eventually fill the range from 0 to self.nv-1.
|
||||
|
||||
"""
|
||||
if iv == -1: # if iv unspecified, put the vertex at the end of the list
|
||||
iv = self.nv
|
||||
|
||||
if iv < self.nv:
|
||||
self.verts[iv].attr = attr
|
||||
else:
|
||||
# In case there is a gap between iv and nv, fill it with blanks
|
||||
self.verts += ([Vertex()] * ((1 + iv) - self.nv))
|
||||
self.neighbors += ([[]] * ((1 + iv) - self.nv))
|
||||
self.verts[iv].attr = attr
|
||||
self.nv = iv+1
|
||||
assert(self.nv == len(self.verts))
|
||||
assert(self.nv == len(self.neighbors))
|
||||
|
||||
|
||||
def AddEdge(self, iv, jv, attr=None, remove_duplicates=False):
|
||||
"""
|
||||
Add an edge to graph connecting vertex iv to jv.
|
||||
(both are integers from 0 to self.nv-1)
|
||||
This function must not be called until all vertices have been added.
|
||||
If the edge is already present (and remove_duplicates==True),
|
||||
no new edge will be added.
|
||||
|
||||
"""
|
||||
if remove_duplicates:
|
||||
for je in self.neighbors[iv]:
|
||||
if jv == self.edges[je].stop:
|
||||
return # In that case, do nothing, the edge is already present
|
||||
self.edges.append(Edge(iv, jv, attr))
|
||||
self.neighbors[iv].append(self.ne)
|
||||
self.ne += 1
|
||||
assert(self.ne == len(self.edges))
|
||||
|
||||
|
||||
def ReorderVerts(self, vpermutation, invert=False):
|
||||
"""
|
||||
This function allows the user to re-order (relabel) the vertices
|
||||
in a graph, making the necessary changes to the
|
||||
self.verts, self.edges, and self.neighbors lists.
|
||||
By default (invert=False). The vpermutation is a list
|
||||
from 1 to self.nv which is interpreted this way:
|
||||
iv = vpermutation[iv_orig]
|
||||
where "iv" and "iv_orig" are the vertex id numbers before
|
||||
and after the mapping (which also corresponds to its
|
||||
position in the self.verts and self.neighbors arrays).
|
||||
|
||||
"""
|
||||
assert(len(self.verts) == self.nv)
|
||||
assert(len(self.edges) == self.ne)
|
||||
assert(len(vpermutation) == self.nv)
|
||||
|
||||
if (invert):
|
||||
vperm = [-1 for iv in vpermutation]
|
||||
for iv in range(0, self.nv):
|
||||
vperm[ vpermutation[iv] ] = iv
|
||||
else:
|
||||
vperm = vpermutation
|
||||
|
||||
orig_verts = [vert for vert in self.verts]
|
||||
for iv_old in range(0, self.nv):
|
||||
iv = vperm[iv_old]
|
||||
self.verts[iv] = orig_verts[iv_old]
|
||||
|
||||
for ie in range(0, self.ne):
|
||||
self.edges[ie].start = vperm[self.edges[ie].start]
|
||||
self.edges[ie].stop = vperm[self.edges[ie].stop]
|
||||
|
||||
orig_neighbors = [nlist for nlist in self.neighbors]
|
||||
# self.neighbors is a 2-d array.
|
||||
# We need to re-sort "self.neighbors" because the first index is
|
||||
# a vertex id number, and these id numbers have been permuted.
|
||||
# However, there's no need to sort the contents of each sub-array
|
||||
# (self.neighbors[iv]), because these are edge id numbers (indices into
|
||||
# the self.edges[] array). These edge index numbers are never altered.
|
||||
# (However the entries stored in self.edges were modified earlier.)
|
||||
for iv_old in range(0, self.nv):
|
||||
iv = vperm[iv_old]
|
||||
self.neighbors[iv] = orig_neighbors[iv_old]
|
||||
|
||||
# Optional:
|
||||
self.SortNeighborLists()
|
||||
|
||||
|
||||
def ReorderEdges(self, epermutation, invert=False):
|
||||
"""
|
||||
This function allows the user to re-order (relabel) the
|
||||
edges in a graph, making the necessary changes to the
|
||||
self.edges and self.neighbors lists.
|
||||
By default (invert=False). The epermutation is a list
|
||||
from 1 to self.ne which is interpreted this way:
|
||||
ie = epermutation[ie_orig]
|
||||
where "ie" and "ie_orig" are the edge id numbers before
|
||||
and after the mapping (which also corresponds to that edge's
|
||||
position in the self.edges array).
|
||||
(Minor detail: Recall that in this code, Ugraphs
|
||||
are implemented by placing two (directed) edges between each pair of
|
||||
connected, adjacent vertices, which point back-and-forth between them.
|
||||
Consequently the list of edges in self.edges is often typically
|
||||
twice as large you might expect.)
|
||||
|
||||
"""
|
||||
assert(len(self.verts) == self.nv)
|
||||
assert(len(self.edges) == self.ne)
|
||||
assert(len(epermutation) == self.ne)
|
||||
|
||||
if (invert):
|
||||
eperm = [-1 for ie in epermutation]
|
||||
for ie in range(0, self.ne):
|
||||
eperm[ epermutation[ie] ] = ie
|
||||
else:
|
||||
eperm = epermutation
|
||||
|
||||
orig_edges = [edge for edge in self.edges]
|
||||
for ie_old in range(0, self.ne):
|
||||
ie = eperm[ie_old]
|
||||
self.edges[ie] = orig_edges[ie_old]
|
||||
|
||||
for iv in range(0, self.nv):
|
||||
for j in range(0, len(self.neighbors[iv])):
|
||||
je_old = self.neighbors[iv][j]
|
||||
self.neighbors[iv][j] = eperm[je_old]
|
||||
|
||||
def SortNeighborLists(self):
|
||||
assert(self.nv == len(self.neighbors))
|
||||
for iv in range(0, self.nv):
|
||||
#Back when self.neighbors was just a 2-dimensional list of
|
||||
#vertex id numbers, then the following line would have worked:
|
||||
# self.neighbors[iv].sort()
|
||||
|
||||
# ugly python code alert:
|
||||
#Unfortunately, we had to change the format of self.neighbors. Now
|
||||
#it is a list of indices into the self.edges array ("ie" numbers).
|
||||
#We want to sort the "ie" numbers by the vertices they point to.
|
||||
#self.edge[ie].start should point to the current vertex (hopefully).
|
||||
#self.edge[ie].stop should point to the vertex it's attached to.
|
||||
#So we want to sort the ie's in self.neighbors by self.edge[ie].stop
|
||||
#Create a temporary array of 2-tuples (ie, jv)
|
||||
nlist = [(ie, self.edges[ie].stop)
|
||||
for ie in self.neighbors[iv]]
|
||||
self.neighbors[iv] = [ie for ie,jv in sorted(nlist,
|
||||
key=itemgetter(1))]
|
||||
|
||||
def FindEdge(self, istart, istop):
|
||||
"""
|
||||
A simple function looks up the edge id number
|
||||
corresponding to an edge connecting vertex istart to istop.
|
||||
If not present returns Dgraph.NULL.
|
||||
|
||||
"""
|
||||
iv = istart
|
||||
for je in self.neighbors[iv]:
|
||||
jv = self.edges[je].stop
|
||||
if jv == istop:
|
||||
return je
|
||||
return Dgraph.NULL
|
||||
|
||||
def GetVert(self, iv):
|
||||
return self.verts[iv]
|
||||
|
||||
def GetEdge(self, ie):
|
||||
return self.edges[ie]
|
||||
|
||||
def GetNumVerts(self):
|
||||
return self.nv
|
||||
|
||||
def GetNumEdges(self):
|
||||
return self.ne
|
||||
|
||||
# Commenting out. I think it's clearer to use python's deepcopy instead
|
||||
#def makecopy(self):
|
||||
# new_copy = Ugraph()
|
||||
# new_copy.verts = [vertex for vertex in self.verts]
|
||||
# new_copy.edges = [ edge for edge in self.edges]
|
||||
# new_copy.neighbors = [nlist for nlist in self.neighbors]
|
||||
# new_copy.nv = self.nv
|
||||
# new_copy.ne = self.ne
|
||||
# return new_copy
|
||||
|
||||
|
||||
def __str__(self):
|
||||
# Print the graph as a list of neighbor-lists.
|
||||
# (Note: This is the same format as the first argument to __init__().
|
||||
# The Vertex.attr and Edge.attr attributes are not printed.)
|
||||
l = ['([']
|
||||
for iv in range(0, self.nv):
|
||||
l.append('[')
|
||||
for j in range(0, len(self.neighbors[iv])):
|
||||
je = self.neighbors[iv][j]
|
||||
jv = self.edges[je].stop
|
||||
l.append(str(jv))
|
||||
if j < len(self.neighbors[iv])-1:
|
||||
l.append(', ')
|
||||
else:
|
||||
l.append(']')
|
||||
if iv < self.nv-1:
|
||||
l.append(',\n ')
|
||||
else:
|
||||
l.append(']')
|
||||
l.append(',\n [')
|
||||
for ie in range(0, self.ne):
|
||||
l.append(str(self.edges[ie]))
|
||||
if ie < self.ne-1:
|
||||
l.append(', ')
|
||||
else:
|
||||
l.append('])\n')
|
||||
return ''.join(l)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Ugraph(Dgraph):
|
||||
"""
|
||||
This class is a minimal implementation of an undirected graph.
|
||||
Vertices and edges are accessed by integer index only (beginning at 0).
|
||||
Multiple edges connecting the same pair of vertices are allowed.
|
||||
(One would use the AddEdge() member function to accomplish this.)
|
||||
Both vertices and edges have an optional "attr" attribute.
|
||||
|
||||
Undirected graphs (Ugraphs) are represented internally as
|
||||
directed graphs. This means that for every edge in the Ugraph,
|
||||
connecting vertex 2 to 3, for example, two edges are stored
|
||||
internally, (2 -> 3, and 3 -> 2),
|
||||
Edges which begin and end at the same vertex are stored only once.)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, edgelist=None):
|
||||
Dgraph.__init__(self, edgelist)
|
||||
|
||||
# Now add the extra edges which point in the reverse direction.
|
||||
neu = self.ne
|
||||
ned = self.ne
|
||||
for ieu in range(0, self.ne):
|
||||
iv = self.edges[ieu].start
|
||||
jv = self.edges[ieu].stop
|
||||
if iv != jv:
|
||||
ned += 1
|
||||
|
||||
self.ieu_to_ied = [Dgraph.NULL for ieu in range(0, neu)]
|
||||
self.ied_to_ieu = [Dgraph.NULL for ied in range(0, ned)]
|
||||
|
||||
ied_redundant = neu
|
||||
for ie in range(0, neu):
|
||||
iv = self.edges[ie].start
|
||||
jv = self.edges[ie].stop
|
||||
attr = self.edges[ie].attr
|
||||
self.ieu_to_ied[ie] = ie
|
||||
self.ied_to_ieu[ie] = ie
|
||||
|
||||
if iv != jv:
|
||||
# Then create another edge which points in the reverse direction
|
||||
Dgraph.AddEdge(self, jv, iv, attr) # <--this increments self.ne
|
||||
self.ied_to_ieu[ied_redundant] = ie
|
||||
ied_redundant += 1
|
||||
|
||||
self.neu = neu
|
||||
assert(self.ne == ned)
|
||||
|
||||
|
||||
def AddEdge(self, iv, jv, attr=None, remove_duplicates=False):
|
||||
"""
|
||||
Add an edge to an undirected graph connecting vertices iv and jv.
|
||||
If the edge is already present (and remove_duplicates==True),
|
||||
no new edge will be added.
|
||||
|
||||
Note: Undirected Ugraphs are implemented by creating two separate
|
||||
digraph edges that conect iv->jv and jv->iv.
|
||||
|
||||
"""
|
||||
|
||||
self.ieu_to_ied.append( len(self.edges) )
|
||||
Dgraph.AddEdge(self, iv, jv, attr, remove_duplicates)
|
||||
self.ied_to_ieu.append( self.neu )
|
||||
if jv != iv:
|
||||
Dgraph.AddEdge(self, jv, iv, attr, remove_duplicates)
|
||||
self.ied_to_ieu.append( self.neu )
|
||||
self.neu += 1
|
||||
|
||||
assert(len(self.ieu_to_ied) == self.neu)
|
||||
assert(len(self.ied_to_ieu) == len(self.edges))
|
||||
|
||||
|
||||
|
||||
def ReorderEdges(self, epermutation, invert=False):
|
||||
Dgraph.ReorderEdges(self, epermutation, invert)
|
||||
|
||||
# Now update the
|
||||
# self.ieu_to_ied and
|
||||
# self.ied_to_ieu lookup tables:
|
||||
|
||||
if (invert): # (first invert the permutation if necessary)
|
||||
eperm = [-1 for ie in epermutation]
|
||||
for ie in range(0, self.ne):
|
||||
eperm[ epermutation[ie] ] = ie
|
||||
else:
|
||||
eperm = epermutation
|
||||
#epermutation.reverse()
|
||||
|
||||
ieu_to_ied_orig = [ied for ied in self.ieu_to_ied]
|
||||
ied_to_ieu_orig = [ieu for ieu in self.ied_to_ieu]
|
||||
|
||||
for ieu in range(0, self.neu):
|
||||
ied_old = ieu_to_ied_orig[ieu]
|
||||
ied = eperm[ied_old]
|
||||
self.ieu_to_ied[ieu] = ied
|
||||
for ied_old in range(0, self.ne):
|
||||
ieu = ied_to_ieu_orig[ied_old]
|
||||
ied = eperm[ied_old]
|
||||
self.ied_to_ieu[ied] = ieu
|
||||
|
||||
eperm = epermutation
|
||||
|
||||
|
||||
def LookupDirectedEdgeIdx(self, ieu):
|
||||
return self.ieu_to_ied[ieu]
|
||||
|
||||
|
||||
def LookupUndirectedEdgeIdx(self, ied):
|
||||
return self.ied_to_ieu[ied]
|
||||
|
||||
#def GetVert(self, iv): <-- (inherited from parent)
|
||||
# return self.verts[iv]
|
||||
|
||||
def GetEdge(self, ieu):
|
||||
ied = self.ieu_to_ied[ieu]
|
||||
return self.edges[ied]
|
||||
|
||||
#def GetNumVerts(self): <-- (inherited from parent)
|
||||
# return self.nv
|
||||
|
||||
def GetNumEdges(self):
|
||||
return self.neu
|
||||
|
||||
def FindEdge(self, istart, istop):
|
||||
"""
|
||||
A simple function looks up the (undirected) edge id number
|
||||
corresponding to an edge connecting vertices istart and istop.
|
||||
If not present returns Dgraph.NULL.
|
||||
|
||||
To find the corresponding entry in the self.edges[] list,
|
||||
you can either:
|
||||
use the LookupDirectedEdge() lookup function
|
||||
or
|
||||
you can use the parent-class' version of this function
|
||||
Dgraph.FindEdge(self, istart, istop) which returns
|
||||
this number by default.
|
||||
|
||||
"""
|
||||
ied = Dgraph.FindEdge(self, istart, istop)
|
||||
ieu = self.LookupUndirectedEdgeIdx(ied)
|
||||
return ieu
|
||||
|
||||
|
||||
def CalcEdgeLookupTable(self):
|
||||
"""
|
||||
COMMENT: THIS NEXT FUNCTION IS PROBABLY NOT NECESSARY AND MIGHT BE
|
||||
REMOVED AT A LATER TIME WHEN I FIGURE OUT A BETTER WAY.
|
||||
|
||||
Because undirected graphs (Ugraphs) are implemented as directed graphs
|
||||
(Dgraphs) with redundant edges, they may have some extra edges which
|
||||
the user never explicitly asked for.
|
||||
There is some confusion about whether the i'th edge refers to
|
||||
the i'th undirected edge that the user explicitly added, or
|
||||
the i'th directed edge which is stored internally.
|
||||
|
||||
(The number of directed edges is usually twice the number of
|
||||
edges that the user asked for. But not always, because edges
|
||||
wich start and end at the same vertex are only represented once.)
|
||||
|
||||
This function calculates lookup tables to translate between
|
||||
the two edge numbering systems:
|
||||
|
||||
self.ieu_to_ied[ieu] returns a directed edge id number,
|
||||
(which is an index into the self.edges list)
|
||||
corresponding to the ieu'th undirected edge
|
||||
which was explicitly added by the caller.
|
||||
|
||||
self.ied_to_ieu[ied] takes a directed edge id number (ied,
|
||||
an index into the self.edges list)
|
||||
and returns the undirected edge number,
|
||||
which is allways <= ied
|
||||
|
||||
"""
|
||||
|
||||
self.ieu_to_ied = []
|
||||
self.ied_to_ieu = [Ugraph.NULL for ied in range(0, self.ne)]
|
||||
for ied in range(0, self.ne):
|
||||
iv = self.edges[ied].start
|
||||
jv = self.edges[ied].stop
|
||||
ieu = len(self.ieu_to_ied)
|
||||
self.ied_to_ieu[ied] = ieu
|
||||
if iv <= jv:
|
||||
self.ieu_to_ied.append(ied)
|
||||
|
||||
|
||||
|
||||
|
||||
def SortVertsByDegree(g):
|
||||
vert_numneighbors = [(iv, len(g.neighbors[iv])) for iv in range(0, g.nv)]
|
||||
vert_numneighbors.sort(key=itemgetter(1))
|
||||
order = [vert_numneighbors[iv][0] for iv in range(0, g.nv)]
|
||||
g.ReorderVerts(order, invert=True)
|
||||
|
||||
|
||||
|
||||
class DFS(object):
|
||||
"""
|
||||
This class contains a member function (Order()) calculates the order
|
||||
of vertices visited in a depth-first-search over a connected graph.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, g):
|
||||
self.g = g
|
||||
self.sv = 0 #integer sv keeps track of how many vertices visited so far
|
||||
self.se = 0 #integer se keeps track of how many edges visited so far
|
||||
self.vvisited=[False for iv in range(0, self.g.nv)] # verts visited
|
||||
self.vorder =[Dgraph.NULL for iv in range(0, self.g.nv)] #search order
|
||||
self.evisited=[False for ie in range(0, self.g.ne)] # edges visited
|
||||
self.eorder =[Dgraph.NULL for ie in range(0, self.g.ne)] #search order
|
||||
|
||||
def Reset(self):
|
||||
self.sv = 0
|
||||
self.se = 0
|
||||
for iv in range(0, self.g.nv):
|
||||
self.vvisited[iv] = False
|
||||
self.vorder[iv] = Dgraph.NULL
|
||||
for ie in range(0, self.g.ne):
|
||||
self.evisited[ie] = False
|
||||
self.eorder[ie] = Dgraph.NULL
|
||||
|
||||
def Order(self, starting_node=0):
|
||||
"""
|
||||
VisitOrder(starting_node)
|
||||
generates a list of integers from 0 to self.g.nv-1 (=#vertices minus 1)
|
||||
which represents the order in which the vertices would be visited
|
||||
during a Depth-First-Search.
|
||||
|
||||
The first vertex visited is specified by the "starting_node" argument
|
||||
(an integer (from 0 to g.nv-1)).
|
||||
|
||||
"""
|
||||
self.Reset()
|
||||
# The first vertex to be visited should be the starting_node
|
||||
self.vorder[0] = starting_node
|
||||
self.vvisited[starting_node] = True
|
||||
self.sv = 1
|
||||
self._Order(starting_node)
|
||||
if self.sv != self.g.nv:
|
||||
raise(Disconnected(self.g, "Error(Order): "+
|
||||
"The input graph is not connected."))
|
||||
assert(self.se == self.g.ne)
|
||||
return ([iv for iv in self.vorder], [ie for ie in self.eorder])
|
||||
#return self.order
|
||||
|
||||
def _Order(self, iv):
|
||||
"""
|
||||
_Order() is a recursive function which carries out a
|
||||
Depth-First-Search over the graph "self.g", starting with vertex iv.
|
||||
|
||||
"""
|
||||
for je in self.g.neighbors[iv]:
|
||||
jv = self.g.edges[je].stop
|
||||
if not self.evisited[je]:
|
||||
self.eorder[self.se] = je
|
||||
self.se += 1
|
||||
self.evisited[je] = True
|
||||
if not self.vvisited[jv]:
|
||||
self.vorder[self.sv] = jv
|
||||
self.sv += 1
|
||||
self.vvisited[jv] = True
|
||||
self._Order(jv)
|
||||
|
||||
def IsConnected(self):
|
||||
self.Reset()
|
||||
self._Order(0)
|
||||
return (self.sv == self.g.nv)
|
||||
|
||||
def IsCyclic(self):
|
||||
"""
|
||||
IsCyclic() returns True if the graph is cyclic (and connected).
|
||||
(An exception is raised on disconnected graphs.)
|
||||
This function quits early as soon as a cycle is found.
|
||||
|
||||
"""
|
||||
self.Reset()
|
||||
if (type(self.g) is Ugraph):
|
||||
is_cyclic = self._IsCyclicUgraph(0, Dgraph.NULL)
|
||||
else:
|
||||
is_cyclic = self._IsCyclic(0)
|
||||
if ((self.sv != self.g.nv) and (not is_cyclic)):
|
||||
raise(Disconnected(self.g, "Error(IsCyclic): "+
|
||||
"The input graph is not connected."))
|
||||
return is_cyclic
|
||||
|
||||
def _IsCyclicUgraph(self, iv, ivprev):
|
||||
"""
|
||||
_IsCyclicUgraph() is a recursive function which carries out a
|
||||
Depth-First-Search over the graph "self.g" to determine whether the
|
||||
graph is cyclic. This function works on undirected graphs (Ugraphs).
|
||||
|
||||
Indirected graphs (Ugraphs) are a special case.
|
||||
Ugraphs are implemented by using two (redundant) forward/backward edges
|
||||
connecting each pair of adjacent vertices. This creates trivial loops.
|
||||
This version of _IsCyclicUgraph() only counts loops between more
|
||||
distantly connected vertices.
|
||||
|
||||
"""
|
||||
self.sv += 1
|
||||
self.vvisited[iv] = True
|
||||
for je in self.g.neighbors[iv]:
|
||||
jv = self.g.edges[je].stop
|
||||
if self.vvisited[jv]:
|
||||
if jv != ivprev:
|
||||
return True
|
||||
elif self._IsCyclicUgraph(jv, iv):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _IsCyclic(self, iv):
|
||||
"""
|
||||
_IsCyclic() is a recursive function which carries out a
|
||||
Depth-First-Search over the graph "self.g" to determine whether
|
||||
the graph is cyclic.
|
||||
This function works on directed graphs.
|
||||
|
||||
"""
|
||||
self.sv += 1
|
||||
self.vvisited[iv] = True
|
||||
for je in self.g.neighbors[iv]:
|
||||
jv = self.g.edges[je].stop
|
||||
if self.vvisited[jv]:
|
||||
return True
|
||||
elif self._IsCyclic(jv):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
class GraphMatcher(object):
|
||||
"""
|
||||
This class is a variant of the VF2 algorithm for searching
|
||||
for small connected subgraphs (g) within a larger graph (G).
|
||||
GraphMatcher works on directed or underected graphs (Dgraph or Ugraph).
|
||||
This particular version is better optimized for detecting subgraph
|
||||
isomorphisms between two graphs of highly unequal size. It should be
|
||||
faster in these situations because, the computation required for
|
||||
each step is independent of the number of vertices in the larger graph
|
||||
In the original VF2 algorithm, the computation time for each step
|
||||
is proportional to the number of vertices in the larger graph.
|
||||
(The distinction matters when one graph is much smaller than the other.)
|
||||
|
||||
Limitations: At the moment, the matching process uses a simple
|
||||
depth-first-search to search the vertices of the small graph "g".
|
||||
Hence this approach fails when the smaller graph g is disconnected.
|
||||
(but it can probably be fixed by picking a different algorithm to search
|
||||
the small graph).
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
G, # The "big" graph
|
||||
g): # The little graph (number of vertices in g must be <= G)
|
||||
|
||||
self.G = G
|
||||
self.g = copy.deepcopy(g)
|
||||
|
||||
if (type(self.G) is Ugraph):
|
||||
assert(type(self.g) is Ugraph)
|
||||
# self.G.CalcEdgeLookupTable() <-- not needed anymore
|
||||
|
||||
self.sv = 0
|
||||
self.se = 0
|
||||
self.voccupiedG = [False for iv in range(0, G.nv)]
|
||||
self.eoccupiedG = [False for ie in range(0, G.ne)]
|
||||
self.G_is_too_small = False
|
||||
if ((g.nv > G.nv) or
|
||||
(g.ne > G.ne)):
|
||||
self.G_is_too_small = True
|
||||
#raise GenErr('Error: The first argument of GraphMatcher(G,g),\n'+
|
||||
# ' must be at least as large as the second.')
|
||||
|
||||
|
||||
# The list self.iv_to_Iv is the mapping between the graph vertices.
|
||||
# Iv is an index into the large graph's list of vertices.
|
||||
# iv is an index into the small graph's list of vertices.
|
||||
# The mapping is stored in the iv_to_Iv list.
|
||||
self.iv_to_Iv = [Dgraph.NULL for Iv in range(0, self.g.nv)]
|
||||
self.ie_to_Ie = [Dgraph.NULL for Ie in range(0, self.g.ne)]
|
||||
# (This used to be called "core_2" in the VF2 algorithm)
|
||||
|
||||
# Due to the large number of recursion limit
|
||||
self.old_recursion_limit = sys.getrecursionlimit()
|
||||
expected_max_recursion = self.g.nv
|
||||
|
||||
if self.old_recursion_limit < 1.5 * expected_max_recursion:
|
||||
# Give some breathing room.
|
||||
sys.setrecursionlimit(int(1.5 * expected_max_recursion))
|
||||
|
||||
subgraph_searcher = DFS(self.g)
|
||||
# Perform a Depth-First-Search on the small graph.
|
||||
self.vorder_g, self.eorder_g = subgraph_searcher.Order()
|
||||
# Then re-order the vertices and edgers to
|
||||
# match the order they were visited.
|
||||
|
||||
# Note on permutation order:
|
||||
# (The DFS.Order() function returns the permutation in this format
|
||||
# old_index[ new_index ]
|
||||
# where new_index is the DFS iteration when the vertex/edge was visited
|
||||
# and old_index is the original vertex/edge order.
|
||||
# However the ReorderVerts() and ReorderEdges() functions expect
|
||||
# the permutation to have the opposite order: new_index[ old_index ]
|
||||
# Hence we set "invert=True", when we invoke these functions.)
|
||||
self.g.ReorderVerts(self.vorder_g, invert=True)
|
||||
self.g.ReorderEdges(self.eorder_g, invert=True)
|
||||
|
||||
# Initialize state
|
||||
self.Reset()
|
||||
|
||||
|
||||
def Reset(self):
|
||||
"""Reinitializes the state of the match-search algorithm.
|
||||
|
||||
"""
|
||||
for iv in range(0, self.g.nv):
|
||||
self.iv_to_Iv[iv] = Dgraph.NULL
|
||||
for ie in range(0, self.g.ne):
|
||||
self.ie_to_Ie[ie] = Dgraph.NULL
|
||||
for Iv in range(0, self.G.nv):
|
||||
self.voccupiedG[Iv] = False
|
||||
for Ie in range(0, self.G.ne):
|
||||
self.eoccupiedG[Ie] = False
|
||||
|
||||
self.se = 0
|
||||
self.sv = 0
|
||||
|
||||
# OPTIONAL: First, do a partial sort for the vertices in the graphs
|
||||
# based on number of edges emanating from each vertex.
|
||||
# (This is probably unnecessary for small subgraphs.)
|
||||
#SortVertsByDegree(self.g)
|
||||
|
||||
|
||||
|
||||
def Matches(self):
|
||||
"""
|
||||
Iterator over all matches between G and g.
|
||||
Each "match" corresponds to a subgraph of G which is isomorphic to g.
|
||||
Matches is formatted as a 2-tuple of lists:
|
||||
(list of vertex ids from G, list of edge ids from G)
|
||||
The vertex ids in the list are a subset of the integers from 0 to G.nv.
|
||||
The edge ids in the list are a subset of the integers from 0 to G.ne.
|
||||
|
||||
(The corresponding vertices and edges from g are indicated by the order)
|
||||
|
||||
"""
|
||||
|
||||
self.Reset()
|
||||
if self.G_is_too_small:
|
||||
# Then there are fewer verts and edges in G than in g.
|
||||
# Thus it is impossible for a subgraph of G to be isomorphic to g.
|
||||
return # return no matches
|
||||
|
||||
for Iv in range(0, self.G.nv):
|
||||
|
||||
# match vertex Iv from G with vertex 0 from graph g
|
||||
self.iv_to_Iv[0] = Iv
|
||||
self.voccupiedG[Iv] = True
|
||||
|
||||
# Implementation:
|
||||
# In this loop we begin the search process
|
||||
# starting with a different vertex (Iv) from big graph G,
|
||||
# and matching it with the first vertex (iv=0) from small graph g.
|
||||
# In this way the match "begins" from vertex Iv in G.
|
||||
#
|
||||
# Any matches found which begin from vertex Iv are distinct
|
||||
# from matches beginning from any other vertex in G.
|
||||
# Looping over all Iv in G is necessary and sufficient
|
||||
# to insure that all possible subgraphs of G
|
||||
# (which are isomorphic to g) are considered.
|
||||
|
||||
self.sv = 1 # we have matched one vertex already
|
||||
self.se = 0 # we haven't matched any edges yet
|
||||
for match in self.Match():
|
||||
yield match
|
||||
self.voccupiedG[Iv] = False
|
||||
|
||||
def Match(self):
|
||||
|
||||
# self.se represents how many vertices have been matched so far.
|
||||
# We are done searching if all of the edges from 0 to self.se-1
|
||||
# from graph g have been selected (matched with edges from graph G).
|
||||
if self.se == self.g.ne:
|
||||
# Note: This also gaurantees that all vertices have been visited.
|
||||
assert(self.sv == self.g.nv)
|
||||
yield self.ReformatMatch()
|
||||
|
||||
else:
|
||||
# VF2-style recursive loop:
|
||||
|
||||
# We know the next edge to be matched is connected to at least
|
||||
# one previously visited vertex from g which has already been
|
||||
# been added to the the current match-in-progress.
|
||||
iv = self.g.edges[self.se].start
|
||||
Iv = self.iv_to_Iv[iv]
|
||||
assert(iv < self.sv) # <-- check to verify this is so
|
||||
|
||||
# The other vertex may or may not have been visited (matched) yet.
|
||||
iv_neighbor = self.g.edges[self.se].stop
|
||||
|
||||
# Two cases:
|
||||
# Case 1: edge self.se points to a previously visited vertex from g
|
||||
# This means we have a loop.
|
||||
if iv_neighbor < self.sv:
|
||||
# In that case, then the corresponding edge in G must
|
||||
# connect the corresponding pair of vertices from G.
|
||||
# (Which we know have already been assigned to vertices in g
|
||||
# because both iv and iv_neighbor are < self.sv)
|
||||
Iv_neighbor = self.iv_to_Iv[iv_neighbor]
|
||||
# Loop over all of the edges in G which connect this pair
|
||||
# of vertices (Iv --> Iv_neighbor)
|
||||
for Je in self.G.neighbors[Iv]:
|
||||
Jv = self.G.edges[Je].stop
|
||||
if ((Jv == Iv_neighbor) and
|
||||
(not self.eoccupiedG[Je])):
|
||||
|
||||
# Match edge Je from big graph G with
|
||||
# edge self.se from small graph g
|
||||
self.ie_to_Ie[self.se] = Je
|
||||
self.se += 1
|
||||
self.eoccupiedG[Je] = True
|
||||
for match in self.Match():
|
||||
yield match
|
||||
self.eoccupiedG[Je] = False
|
||||
self.se -= 1
|
||||
self.ie_to_Ie[self.se] = Dgraph.NULL
|
||||
|
||||
# Case 2:
|
||||
else: # this would mean that iv_neighbor >= self.sv
|
||||
|
||||
# If iv_neighbor>=self.sv, then this edge points to to a vertex
|
||||
# in g which has not yet been paired with a vertex from G.
|
||||
|
||||
# Loop over all of the edges in G which connect vertex
|
||||
# Iv from G to new (unvisited) vertices in G
|
||||
for Je in self.G.neighbors[Iv]:
|
||||
Jv = self.G.edges[Je].stop
|
||||
if (not self.voccupiedG[Jv]):
|
||||
|
||||
assert(not self.eoccupiedG[Je])
|
||||
# Match both edge Je with je
|
||||
# AND vertex Jv with jv
|
||||
self.ie_to_Ie[self.se] = Je
|
||||
self.se += 1
|
||||
self.eoccupiedG[Je] = True
|
||||
self.iv_to_Iv[self.sv] = Jv
|
||||
self.sv += 1
|
||||
self.voccupiedG[Jv] = True
|
||||
# Then continue the recursion
|
||||
for match in self.Match():
|
||||
yield match
|
||||
self.voccupiedG[Jv] = False
|
||||
self.sv -= 1
|
||||
self.iv_to_Iv[self.sv] = Dgraph.NULL
|
||||
self.eoccupiedG[Je] = False
|
||||
self.se -= 1
|
||||
self.ie_to_Ie[self.se] = Dgraph.NULL
|
||||
|
||||
def ReformatMatch(self):
|
||||
# (This is because we are assuming g is connected.
|
||||
# IT should not have any orphanned vertices.)
|
||||
# Now return the match:
|
||||
#
|
||||
# There are different ways of doing this
|
||||
# version 1:
|
||||
#match = (self.iv_to_Iv, self.ie_to_Ie) <-return a pointer to array
|
||||
# version 2:
|
||||
#match = ([Iv for Iv in self.iv_to_Iv], <-return a copy of the array
|
||||
# [Ie for Ie in self.ie_to_Ie])
|
||||
# version 3:
|
||||
# Recall that the vertices and edges and g have been re-ordered,
|
||||
# so sort the list of Iv indices in the order they would be
|
||||
# matched with the original vertices from the original graph g:
|
||||
#match = ([self.iv_to_Iv[self.vorder_g[iv]]
|
||||
# for iv in range(0,self.g.nv)],
|
||||
# [self.ie_to_Ie[self.eorder_g[ie]]
|
||||
# for ie in range(0,self.g.ne)])
|
||||
# version 4: Similar to version 3 above, but we also translate
|
||||
# the directed edge id list into a shorter undirected
|
||||
# edge id list.
|
||||
match_verts = [self.iv_to_Iv[self.vorder_g[iv]]
|
||||
for iv in range(0,self.g.nv)]
|
||||
|
||||
if type(self.g) is Dgraph:
|
||||
match_edges = [self.ie_to_Ie[self.eorder_g[ie]]
|
||||
for ie in range(0, self.g.ne)]
|
||||
else:
|
||||
#assert(atype(self.g) is Ugraph)
|
||||
match_edges = [Dgraph.NULL for ieu in range(0, self.g.neu)]
|
||||
|
||||
for ie in range(0, self.g.ne):
|
||||
iv = self.g.edges[ie].start
|
||||
jv = self.g.edges[ie].stop
|
||||
if iv <= jv: # <-- avoid duplicating edges (iv,jv) and (jv,iv)
|
||||
ieu = self.g.LookupUndirectedEdgeIdx(ie)
|
||||
Ie = self.ie_to_Ie[ie]
|
||||
Ieu = self.G.LookupUndirectedEdgeIdx(Ie)
|
||||
match_edges[ieu] = Ieu
|
||||
|
||||
return (tuple(match_verts), tuple(match_edges))
|
||||
74
tools/moltemplate/src/nbody_reorder_atoms.py
Executable file
74
tools/moltemplate/src/nbody_reorder_atoms.py
Executable file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Reorder the atoms in the Angles section of a data file to make sure that
|
||||
atoms have a "canonical order" (for example the first atom has a lower
|
||||
id than the last atom, for angle and dihedral interactions.
|
||||
(This helps us detect potential problems like dupicate Angle interactions.)
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
from operator import itemgetter
|
||||
|
||||
g_program_name = __file__.split('/')[-1]
|
||||
|
||||
in_stream = sys.stdin
|
||||
|
||||
|
||||
section_name = ''
|
||||
if len(sys.argv) == 2:
|
||||
section_name = sys.argv[1]
|
||||
else:
|
||||
sys.stderr.write('Usage Example:\n\n'
|
||||
' '+g_program_name+' Angles < angles.txt > new_angles.txt\n\n'
|
||||
' In this example \"angles.txt\" contains only the \"Angles\" section of\n'
|
||||
' a LAMMPS DATA file. (Either a text-editor, or the \n'
|
||||
' \"extract_lammps_data.py\" script can be used to select a section from\n'
|
||||
' a LAMMPS DATA file\n\n'
|
||||
'Error('+g_program_name+'): expected exactly one argument:\n'
|
||||
' \"Angles\", \"Dihedrals\", or \"Impropers\"\n')
|
||||
exit(-1)
|
||||
|
||||
# Ordering rules are defined in a seperate module named
|
||||
# nbody_Angles.py, nbody_Dihedrals.py, nbody_Impropers.py
|
||||
# Load that now.
|
||||
|
||||
module_name = 'nbody_'+section_name
|
||||
g = __import__(module_name) #defines g.bond_pattern, g.canonical_order
|
||||
|
||||
# This module defines the graph representing the bond pattern for this type
|
||||
# of interaction. (The number of vertices and edges for the graph corresponds
|
||||
# to the number of atoms and bonds in this type of interaction.)
|
||||
natoms = g.bond_pattern.GetNumVerts()
|
||||
nbonds = g.bond_pattern.GetNumEdges()
|
||||
|
||||
|
||||
for line_orig in in_stream:
|
||||
line = line_orig.rstrip('\n')
|
||||
comment = ''
|
||||
if '#' in line_orig:
|
||||
ic = line.find('#')
|
||||
line = line_orig[:ic]
|
||||
comment = ' '+line_orig[ic:].rstrip('\n')
|
||||
|
||||
tokens = line.strip().split()
|
||||
swapped = False
|
||||
if len(tokens) == 2+natoms:
|
||||
all_integers = True
|
||||
abids_l = [[0 for i in range(0, natoms)],
|
||||
[0 for i in range(0, nbonds)]]
|
||||
for i in range(0, natoms):
|
||||
if not tokens[2+i].isdigit():
|
||||
all_integers = False
|
||||
if all_integers:
|
||||
for i in range(0, natoms):
|
||||
abids_l[0][i] = int(tokens[2+i])
|
||||
else:
|
||||
for i in range(0, natoms):
|
||||
abids_l[0][i] = tokens[2+i]
|
||||
abids = g.canonical_order( (tuple(abids_l[0]), tuple(abids_l[1])) )
|
||||
for i in range(0, natoms):
|
||||
tokens[2+i] = str(abids[0][i])
|
||||
|
||||
sys.stdout.write(' '.join(tokens)+comment+'\n')
|
||||
258
tools/moltemplate/src/ordereddict.py
Normal file
258
tools/moltemplate/src/ordereddict.py
Normal file
@ -0,0 +1,258 @@
|
||||
# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
|
||||
# Passes Python2.7's test suite and incorporates all the latest updates.
|
||||
|
||||
try:
|
||||
from thread import get_ident as _get_ident
|
||||
except ImportError:
|
||||
from dummy_thread import get_ident as _get_ident
|
||||
|
||||
try:
|
||||
from _abcoll import KeysView, ValuesView, ItemsView
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class OrderedDict(dict):
|
||||
'Dictionary that remembers insertion order'
|
||||
# An inherited dict maps keys to values.
|
||||
# The inherited dict provides __getitem__, __len__, __contains__, and get.
|
||||
# The remaining methods are order-aware.
|
||||
# Big-O running times for all methods are the same as for regular dictionaries.
|
||||
|
||||
# The internal self.__map dictionary maps keys to links in a doubly linked list.
|
||||
# The circular doubly linked list starts and ends with a sentinel element.
|
||||
# The sentinel element never gets deleted (this simplifies the algorithm).
|
||||
# Each link is stored as a list of length three: [PREV, NEXT, KEY].
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
'''Initialize an ordered dictionary. Signature is the same as for
|
||||
regular dictionaries, but keyword arguments are not recommended
|
||||
because their insertion order is arbitrary.
|
||||
|
||||
'''
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
try:
|
||||
self.__root
|
||||
except AttributeError:
|
||||
self.__root = root = [] # sentinel node
|
||||
root[:] = [root, root, None]
|
||||
self.__map = {}
|
||||
self.__update(*args, **kwds)
|
||||
|
||||
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
|
||||
'od.__setitem__(i, y) <==> od[i]=y'
|
||||
# Setting a new item creates a new link which goes at the end of the linked
|
||||
# list, and the inherited dictionary is updated with the new key/value pair.
|
||||
if key not in self:
|
||||
root = self.__root
|
||||
last = root[0]
|
||||
last[1] = root[0] = self.__map[key] = [last, root, key]
|
||||
dict_setitem(self, key, value)
|
||||
|
||||
def __delitem__(self, key, dict_delitem=dict.__delitem__):
|
||||
'od.__delitem__(y) <==> del od[y]'
|
||||
# Deleting an existing item uses self.__map to find the link which is
|
||||
# then removed by updating the links in the predecessor and successor nodes.
|
||||
dict_delitem(self, key)
|
||||
link_prev, link_next, key = self.__map.pop(key)
|
||||
link_prev[1] = link_next
|
||||
link_next[0] = link_prev
|
||||
|
||||
def __iter__(self):
|
||||
'od.__iter__() <==> iter(od)'
|
||||
root = self.__root
|
||||
curr = root[1]
|
||||
while curr is not root:
|
||||
yield curr[2]
|
||||
curr = curr[1]
|
||||
|
||||
def __reversed__(self):
|
||||
'od.__reversed__() <==> reversed(od)'
|
||||
root = self.__root
|
||||
curr = root[0]
|
||||
while curr is not root:
|
||||
yield curr[2]
|
||||
curr = curr[0]
|
||||
|
||||
def clear(self):
|
||||
'od.clear() -> None. Remove all items from od.'
|
||||
try:
|
||||
for node in self.__map.itervalues():
|
||||
del node[:]
|
||||
root = self.__root
|
||||
root[:] = [root, root, None]
|
||||
self.__map.clear()
|
||||
except AttributeError:
|
||||
pass
|
||||
dict.clear(self)
|
||||
|
||||
def popitem(self, last=True):
|
||||
'''od.popitem() -> (k, v), return and remove a (key, value) pair.
|
||||
Pairs are returned in LIFO order if last is true or FIFO order if false.
|
||||
|
||||
'''
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
root = self.__root
|
||||
if last:
|
||||
link = root[0]
|
||||
link_prev = link[0]
|
||||
link_prev[1] = root
|
||||
root[0] = link_prev
|
||||
else:
|
||||
link = root[1]
|
||||
link_next = link[1]
|
||||
root[1] = link_next
|
||||
link_next[0] = root
|
||||
key = link[2]
|
||||
del self.__map[key]
|
||||
value = dict.pop(self, key)
|
||||
return key, value
|
||||
|
||||
# -- the following methods do not depend on the internal structure --
|
||||
|
||||
def keys(self):
|
||||
'od.keys() -> list of keys in od'
|
||||
return list(self)
|
||||
|
||||
def values(self):
|
||||
'od.values() -> list of values in od'
|
||||
return [self[key] for key in self]
|
||||
|
||||
def items(self):
|
||||
'od.items() -> list of (key, value) pairs in od'
|
||||
return [(key, self[key]) for key in self]
|
||||
|
||||
def iterkeys(self):
|
||||
'od.iterkeys() -> an iterator over the keys in od'
|
||||
return iter(self)
|
||||
|
||||
def itervalues(self):
|
||||
'od.itervalues -> an iterator over the values in od'
|
||||
for k in self:
|
||||
yield self[k]
|
||||
|
||||
def iteritems(self):
|
||||
'od.iteritems -> an iterator over the (key, value) items in od'
|
||||
for k in self:
|
||||
yield (k, self[k])
|
||||
|
||||
def update(*args, **kwds):
|
||||
'''od.update(E, **F) -> None. Update od from dict/iterable E and F.
|
||||
|
||||
If E is a dict instance, does: for k in E: od[k] = E[k]
|
||||
If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
|
||||
Or if E is an iterable of items, does: for k, v in E: od[k] = v
|
||||
In either case, this is followed by: for k, v in F.items(): od[k] = v
|
||||
|
||||
'''
|
||||
if len(args) > 2:
|
||||
raise TypeError('update() takes at most 2 positional '
|
||||
'arguments (%d given)' % (len(args),))
|
||||
elif not args:
|
||||
raise TypeError('update() takes at least 1 argument (0 given)')
|
||||
self = args[0]
|
||||
# Make progressively weaker assumptions about "other"
|
||||
other = ()
|
||||
if len(args) == 2:
|
||||
other = args[1]
|
||||
if isinstance(other, dict):
|
||||
for key in other:
|
||||
self[key] = other[key]
|
||||
elif hasattr(other, 'keys'):
|
||||
for key in other.keys():
|
||||
self[key] = other[key]
|
||||
else:
|
||||
for key, value in other:
|
||||
self[key] = value
|
||||
for key, value in kwds.items():
|
||||
self[key] = value
|
||||
|
||||
__update = update # let subclasses override update without breaking __init__
|
||||
|
||||
__marker = object()
|
||||
|
||||
def pop(self, key, default=__marker):
|
||||
'''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
||||
If key is not found, d is returned if given, otherwise KeyError is raised.
|
||||
|
||||
'''
|
||||
if key in self:
|
||||
result = self[key]
|
||||
del self[key]
|
||||
return result
|
||||
if default is self.__marker:
|
||||
raise KeyError(key)
|
||||
return default
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
|
||||
if key in self:
|
||||
return self[key]
|
||||
self[key] = default
|
||||
return default
|
||||
|
||||
def __repr__(self, _repr_running={}):
|
||||
'od.__repr__() <==> repr(od)'
|
||||
call_key = id(self), _get_ident()
|
||||
if call_key in _repr_running:
|
||||
return '...'
|
||||
_repr_running[call_key] = 1
|
||||
try:
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
finally:
|
||||
del _repr_running[call_key]
|
||||
|
||||
def __reduce__(self):
|
||||
'Return state information for pickling'
|
||||
items = [[k, self[k]] for k in self]
|
||||
inst_dict = vars(self).copy()
|
||||
for k in vars(OrderedDict()):
|
||||
inst_dict.pop(k, None)
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
|
||||
def copy(self):
|
||||
'od.copy() -> a shallow copy of od'
|
||||
return self.__class__(self)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
'''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
|
||||
and values equal to v (which defaults to None).
|
||||
|
||||
'''
|
||||
d = cls()
|
||||
for key in iterable:
|
||||
d[key] = value
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
'''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
|
||||
while comparison to a regular mapping is order-insensitive.
|
||||
|
||||
'''
|
||||
if isinstance(other, OrderedDict):
|
||||
return len(self)==len(other) and self.items() == other.items()
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
# -- the following methods are only used in Python 2.7 --
|
||||
|
||||
def viewkeys(self):
|
||||
"od.viewkeys() -> a set-like object providing a view on od's keys"
|
||||
return KeysView(self)
|
||||
|
||||
def viewvalues(self):
|
||||
"od.viewvalues() -> an object providing a view on od's values"
|
||||
return ValuesView(self)
|
||||
|
||||
def viewitems(self):
|
||||
"od.viewitems() -> a set-like object providing a view on od's items"
|
||||
return ItemsView(self)
|
||||
138
tools/moltemplate/src/pdbsort.py
Executable file
138
tools/moltemplate/src/pdbsort.py
Executable file
@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Unfortunately, the lines in a PDB files are not always listed in the
|
||||
correct order. Software that reads PDB files is expected to re-sort this
|
||||
data before interpreting it. (One reason I don't like them.)
|
||||
This script reads a PDB file from the standard input, sorts the lines
|
||||
according to ChainID, SeqNum (residue-number), Icode (insert-code),
|
||||
and AtomID (in that order) and prints the result to the standard-out.
|
||||
Only the ATOM and HETATM records are effected.
|
||||
All other lines in the PDB file are printed back to the user verbatim.
|
||||
Note: the "altLoc" column (character #17 on each line) is ignored
|
||||
and is not used for sorting purposes.
|
||||
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
# In order to specify any amino acid in a PDB file, you must provide 3
|
||||
# identifiers:
|
||||
# the ChainID a single letter specifying
|
||||
# the SeqNum an integer indicating the location within that chain
|
||||
# the ICode ("insert code" usually 0. I don't know why this number is
|
||||
# necessary. ..For the record, I never loved the PDB file format.)
|
||||
|
||||
|
||||
class AtomDescr:
|
||||
def __init__(self, setChainID, setSeqNum, setICode, setAtomID):
|
||||
self.chainID = setChainID
|
||||
self.seqNum = setSeqNum
|
||||
self.iCode = setICode
|
||||
self.atomID = setAtomID
|
||||
|
||||
#I plan to store this information in a python dictionary.
|
||||
#Unfortunately, in order to use such classes as keys in python dictionaries
|
||||
#I must define comparison operators, and a hash function.
|
||||
#In retrospect I figured out it would have been easier just to use tuples
|
||||
#as dictionary keys. I suppose it was a good excercise to try to do it
|
||||
#using python classes instead. I'm sorry it made the code so long.
|
||||
|
||||
def __le__(self, other):
|
||||
#return ((self.chainID < other.chainID) or ((self.chainID == other.chainID) and ((self.seqNum < other.seqNum) or ((self.seqNum == other.seqNum) and (self.iCode <= other.iCode)))))))
|
||||
# instead I'll exploit python's ability to compare tuples
|
||||
return (self.chainID, self.seqNum, self.iCode) <= (other.chainID, other.seqNum, other.iCode)
|
||||
|
||||
def __lt__(self, other):
|
||||
return (self.chainID, self.seqNum, self.iCode) < (other.chainID, other.seqNum, other.iCode)
|
||||
|
||||
def __eq__(self, other):
|
||||
#return ((self.chainID == x.chainID) and (self.seqNum == x.seqNum) and (self.iCode == x.iCode))
|
||||
return (self.chainID, self.seqNum, self.iCode) == (other.chainID, other.seqNum, other.iCode)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not __eq__(self, other)
|
||||
|
||||
def __gt__(self, other):
|
||||
return not __le__(self, other)
|
||||
|
||||
def __ge__(self, other):
|
||||
return not __lt__(self, other)
|
||||
|
||||
def __cmp__(self, other):
|
||||
if __lt__(self, other):
|
||||
return -1
|
||||
elif __gt__(self, other):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def __hash__(self):
|
||||
numChainIDs = 128
|
||||
numICodes = 128
|
||||
i = self.seqNum
|
||||
i *= numChainIDs
|
||||
i += ord(self.chainID)
|
||||
i *= numICodes
|
||||
i += ord(self.iCode)
|
||||
i *=10
|
||||
i += self.atomID
|
||||
return i
|
||||
|
||||
|
||||
|
||||
|
||||
import sys
|
||||
from operator import attrgetter
|
||||
g_program_name = __file__.split('/')[-1]
|
||||
g_version_str = 0.1
|
||||
g_date_str = 2012-5-06
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
use_all_residues = True
|
||||
elif len(sys.argv) == 7:
|
||||
use_all_residues = False
|
||||
first = AtomDescr(sys.argv[1], int(sys.argv[2]), sys.argv[3], 0)
|
||||
last = AtomDescr(sys.argv[4], int(sys.argv[5]), sys.argv[6], 2147483647)
|
||||
else:
|
||||
sys.stderr.write("Error("+g_program_name+"): This program requires either 0 or 6 arguments.\n"
|
||||
" By default, the the sequence is extracted from the entire PDB file.\n"
|
||||
" In that case, no arguments are required.\n"
|
||||
" Alternately, you can limit the selection to a single interval of\n"
|
||||
" residues from one of the chains in the PDB file.\n"
|
||||
" To specify an interval, you must passing 6 arguments to this program.\n"
|
||||
" This program requires a pair of residues to designate the first and\n"
|
||||
" last members of the interval. Each residue requires 3 identifiers.\n"
|
||||
" Consequently the six arguments needed are:\n"
|
||||
"ChainID_first SeqNum_first ICode_first ChainID_last SeqNum_last ICode_last\n")
|
||||
exit(-1)
|
||||
|
||||
|
||||
atoms2lines = defaultdict(list)
|
||||
|
||||
for line in sys.stdin:
|
||||
if (line[0:6] == "ATOM ") or (line[0:6] == "HETATM"):
|
||||
atomID = int(line[6:11])
|
||||
#atomType = line[12:16]
|
||||
#altLoc = line[16:17]
|
||||
iCode = line[26:27]
|
||||
#resType = line[17:20]
|
||||
chainID = line[21:22]
|
||||
seqNumStr = line[22:26]
|
||||
seqNum = int(seqNumStr)
|
||||
atomdescr = AtomDescr(chainID, int(seqNumStr), iCode, int(atomID))
|
||||
atoms2lines[atomdescr].append(line.rstrip('\n'))
|
||||
else:
|
||||
sys.stdout.write(line)
|
||||
|
||||
# Extract an (unordered) list of the atomdescrs of the atoms in the sequence
|
||||
atomdescrs = [atomdescr for atomdescr in atoms2lines]
|
||||
|
||||
# Residues in PDB files are often not listed in order.
|
||||
# Consequently, we must sort the list by chainID, seqNum, and finnaly iCode:
|
||||
sequence_of_atomdescrs = sorted(atomdescrs, key=attrgetter('chainID','seqNum','iCode','atomID'))
|
||||
|
||||
for atomdescr in sequence_of_atomdescrs:
|
||||
for line in atoms2lines[atomdescr]:
|
||||
sys.stdout.write(line+'\n')
|
||||
|
||||
144
tools/moltemplate/src/postprocess_input_script.py
Executable file
144
tools/moltemplate/src/postprocess_input_script.py
Executable file
@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Reorder the integer arguments to the commands in a LAMMPS input
|
||||
file if these arguments violate LAMMPS order requirements.
|
||||
We have to do this because the moltemplate.sh script will automatically
|
||||
assign these integers in a way which may violate these restrictions
|
||||
and the user has little control over this.
|
||||
|
||||
Right now, the only thing this script does is swap the I and J integers in
|
||||
"pair_coeff I J ..." commands. Later changes may be added.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
lines_orig = []
|
||||
in_stream = sys.stdin
|
||||
f = None
|
||||
fname = None
|
||||
num_lines_ignore = 0
|
||||
|
||||
# Lines from files passed as arguments are read and processed silently.
|
||||
|
||||
# (Why? Sometimes it's necessary to read the contents of previous input scripts
|
||||
# in order to be able to understand a script command which appears later.
|
||||
# I'm assuming these files will be processed by lammps in the same order. So I
|
||||
# must insure that moltemplate.sh passes them to this program in that order.
|
||||
# I'm too lazy to read the "include" commands in input scripts correctly.)
|
||||
if len(sys.argv) > 1:
|
||||
for fname in sys.argv[1:]:
|
||||
f = open(fname, 'r')
|
||||
in_stream = f
|
||||
lines_orig += in_stream.readlines()
|
||||
num_lines_ignore += len(lines_orig)
|
||||
f.close()
|
||||
|
||||
# Lines read from the standard input are read, processed, and printed to stdout
|
||||
in_stream = sys.stdin
|
||||
lines_orig += in_stream.readlines()
|
||||
|
||||
pair_style_list=[]
|
||||
swap_occured = False
|
||||
warn_wildcard = False
|
||||
|
||||
i=0
|
||||
while i < len(lines_orig):
|
||||
# Read the next logical line
|
||||
# Any lines ending in '&' should be merged with the next line before breaking
|
||||
line_orig = ''
|
||||
while i < len(lines_orig):
|
||||
line_counter = 1 + i - num_lines_ignore
|
||||
line_orig += lines_orig[i]
|
||||
if ((len(line_orig) < 2) or (line_orig[-2:] != '&\n')):
|
||||
break
|
||||
i += 1
|
||||
line = line_orig.replace('&\n','\n').rstrip('\n')
|
||||
|
||||
comment = ''
|
||||
if '#' in line_orig:
|
||||
ic = line.find('#')
|
||||
line = line_orig[:ic]
|
||||
comment = line_orig[ic:] # keep track of comments (put them back later)
|
||||
|
||||
tokens = line.strip().split()
|
||||
if ((len(tokens) >= 2) and (tokens[0] == 'pair_style')):
|
||||
pair_style_list = tokens[1:]
|
||||
|
||||
if ((len(tokens) >= 3) and (tokens[0] == 'pair_coeff')):
|
||||
|
||||
if ((tokens[1].isdigit() and (tokens[2].isdigit())) and
|
||||
(int(tokens[1]) > int(tokens[2]))):
|
||||
|
||||
swap_occured = True
|
||||
tmp = tokens[2]
|
||||
tokens[2] = tokens[1]
|
||||
tokens[1] = tmp
|
||||
|
||||
if i >= num_lines_ignore:
|
||||
|
||||
# polite warning:
|
||||
sys.stderr.write('swapped pair_coeff order on line '+str(line_counter))
|
||||
#if (fname != None):
|
||||
# sys.stderr.write(' of file \"'+fname+'\"')
|
||||
sys.stderr.write('\n')
|
||||
|
||||
# Deal with the "hbond/" pair coeffs.
|
||||
#
|
||||
# The hbond/dreiding pair style designates one of the two atom types
|
||||
# as a donor, and the other as an acceptor (using the 'i','j' flags)
|
||||
# If swapped atom types eariler, we also need to swap 'i' with 'j'.
|
||||
#
|
||||
# If "hbond/dreiding.." pair style is used with "hybrid" or
|
||||
# "hybrid/overlay" then tokens[3] is the name of the pair style
|
||||
# and tokens[5] is either 'i' or 'j'.
|
||||
if len(pair_style_list) > 0:
|
||||
if ((pair_style_list[0] == 'hybrid') or
|
||||
(pair_style_list[0] == 'hybrid/overlay')):
|
||||
if ((tokens[5] == 'i') and (tokens[3][0:6]=='hbond/')):
|
||||
tokens[5] = 'j'
|
||||
sys.stderr.write(' (and replaced \"i\" with \"j\")\n')
|
||||
elif ((tokens[5] == 'j') and (tokens[3][0:6]=='hbond/')):
|
||||
tokens[5] = 'i'
|
||||
sys.stderr.write(' (and replaced \"j\" with \"i\")\n')
|
||||
elif (pair_style_list[0][0:6] == 'hbond/'):
|
||||
if (tokens[4] == 'i'):
|
||||
tokens[4] = 'j'
|
||||
sys.stderr.write(' (and replaced \"i\" with \"j\")\n')
|
||||
elif (tokens[4] == 'j'):
|
||||
tokens[4] = 'i'
|
||||
sys.stderr.write(' (and replaced \"j\" with \"i\")\n')
|
||||
|
||||
sys.stdout.write((' '.join(tokens)+comment).replace('\n','&\n')+'\n')
|
||||
|
||||
else:
|
||||
if ((('*' in tokens[1]) or ('*' in tokens[2]))
|
||||
and
|
||||
(not (('*' == tokens[1]) and ('*' == tokens[2])))):
|
||||
warn_wildcard = True
|
||||
if i >= num_lines_ignore:
|
||||
sys.stdout.write(line_orig)
|
||||
else:
|
||||
if i >= num_lines_ignore:
|
||||
sys.stdout.write(line_orig)
|
||||
|
||||
i += 1
|
||||
|
||||
|
||||
|
||||
if swap_occured:
|
||||
sys.stderr.write('\n'
|
||||
' WARNING: Atom order in some pair_coeff commands was swapped to pacify LAMMPS.\n'
|
||||
' For some exotic pair_styles such as hbond/dreiding, this is not enough. If you\n'
|
||||
' use exotic pair_styles, please verify the \"pair_coeff\" commands are correct.\n')
|
||||
|
||||
if warn_wildcard:
|
||||
sys.stderr.write('\n'
|
||||
' WARNING: The use of wildcard characters (\"*\") in your \"pair_coeff\"\n'
|
||||
' commands is not recommended.\n'
|
||||
' (It is safer to specify each interaction pair manually.\n'
|
||||
' Check every pair_coeff command. Make sure that every atom type in\n'
|
||||
' the first group is <= atom types in the second group.\n'
|
||||
' Moltemplate does NOT do this when wildcards are used.)\n'
|
||||
' If you are using a many-body pair style then ignore this warning.\n')
|
||||
|
||||
53
tools/moltemplate/src/remove_duplicate_atoms.py
Executable file
53
tools/moltemplate/src/remove_duplicate_atoms.py
Executable file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Get rid of lines containing duplicate copies of the same atom in the "Atoms"
|
||||
section of a LAMMPS data file. Duplicate lines which occur later are
|
||||
preserved and the earlier lines are erased.
|
||||
The file is read from sys.stdin. This program does not parse the entire
|
||||
data file. The text from the "Atoms" section of the LAMMPS file must
|
||||
be extracted in advance before it is sent to this program.)
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
in_stream = sys.stdin
|
||||
f = None
|
||||
fname = None
|
||||
if len(sys.argv) == 2:
|
||||
fname = sys.argv[1]
|
||||
f = open(fname, 'r')
|
||||
in_stream = f
|
||||
|
||||
atom_ids_in_use = set([])
|
||||
|
||||
lines = in_stream.readlines()
|
||||
|
||||
# Start at the end of the file and read backwards.
|
||||
# If duplicate lines exist, eliminate the ones that occur earlier in the file.
|
||||
i = len(lines)
|
||||
while i > 0:
|
||||
i -= 1
|
||||
line_orig = lines[i]
|
||||
line = line_orig.rstrip('\n')
|
||||
if '#' in line_orig:
|
||||
ic = line.find('#')
|
||||
line = line_orig[:ic]
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) > 0:
|
||||
atom_id = tokens[0]
|
||||
if atom_id in atom_ids_in_use:
|
||||
del lines[i]
|
||||
else:
|
||||
atom_ids_in_use.add(atom_id)
|
||||
else:
|
||||
del lines[i]
|
||||
|
||||
|
||||
for line in lines:
|
||||
sys.stdout.write(line)
|
||||
|
||||
if f != None:
|
||||
f.close()
|
||||
|
||||
49
tools/moltemplate/src/remove_duplicates_nbody.py
Executable file
49
tools/moltemplate/src/remove_duplicates_nbody.py
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Get rid of lines containing duplicate bonded nbody interactions in the
|
||||
corresponding section of a LAMMPS data file (such as bonds, angles,
|
||||
dihedrals and impropers). Duplicate lines which occur later are
|
||||
preserved and the earlier lines are erased.
|
||||
(This program reads from sys.stdin. This program does not parse the entire
|
||||
data file. The text from the relevant section of the LAMMPS file should be
|
||||
extracted in advance before it is sent to this program.)
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
in_stream = sys.stdin
|
||||
|
||||
if len(sys.argv) == 2:
|
||||
n = int(sys.argv[1])
|
||||
if (len(sys.argv) != 2) or (n < 1):
|
||||
sys.stderr.write('Error (remove_duplicates_nbody.py): expected a positive integer argument.\n')
|
||||
sys.exit(-1)
|
||||
|
||||
atom_ids_in_use = set([])
|
||||
|
||||
lines = in_stream.readlines()
|
||||
|
||||
# Start at the end of the file and read backwards.
|
||||
# If duplicate lines exist, eliminate the ones that occur earlier in the file.
|
||||
i = len(lines)
|
||||
while i > 0:
|
||||
i -= 1
|
||||
line_orig = lines[i]
|
||||
line = line_orig.rstrip('\n')
|
||||
if '#' in line_orig:
|
||||
ic = line.find('#')
|
||||
line = line_orig[:ic]
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) == 2+n:
|
||||
atom_ids = tuple(tokens[2:2+n])
|
||||
if atom_ids in atom_ids_in_use:
|
||||
del lines[i]
|
||||
else:
|
||||
atom_ids_in_use.add(atom_ids)
|
||||
elif len(tokens) == 0:
|
||||
del lines[i]
|
||||
|
||||
for line in lines:
|
||||
sys.stdout.write(line)
|
||||
69
tools/moltemplate/src/renumber_DATA_first_column.py
Executable file
69
tools/moltemplate/src/renumber_DATA_first_column.py
Executable file
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
renumber the integers at the beginning of ever line in the file
|
||||
to make sure these numbers are contiguous.
|
||||
The file is read from sys.stdin.
|
||||
This program does not parse an entire LAMMPS data file.
|
||||
The text from the "Atoms" section
|
||||
(or "Bonds", or "Angles", or "Dihedrals", or "Impropers" sections)
|
||||
of the LAMMPS file must be extracted in advance.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
from operator import itemgetter
|
||||
|
||||
in_stream = sys.stdin
|
||||
f = None
|
||||
fname = None
|
||||
if len(sys.argv) == 2:
|
||||
fname = sys.argv[1]
|
||||
f = open(fname, 'r')
|
||||
in_stream = f
|
||||
|
||||
|
||||
lines = in_stream.readlines()
|
||||
column1_iorig_columnsAfter1 = []
|
||||
|
||||
# Start at the end of the file and read backwards.
|
||||
# If duplicate lines exist, eliminate the ones that occur earlier in the file.
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line_orig = lines[i]
|
||||
line = line_orig.rstrip('\n')
|
||||
comment = ''
|
||||
if '#' in line_orig:
|
||||
ic = line.find('#')
|
||||
line = line_orig[:ic]
|
||||
comment = ' '+line_orig[ic:].rstrip('\n')
|
||||
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) > 0:
|
||||
if str.isdigit(tokens[0]):
|
||||
column1 = int(tokens[0])
|
||||
else:
|
||||
column1 = tokens[0]
|
||||
column1_iorig_columnsAfter1.append([column1, i, ' '.join(tokens[1:])+comment])
|
||||
i += 1
|
||||
|
||||
# Sort the list of lines by the first number on each line
|
||||
|
||||
column1_iorig_columnsAfter1.sort(key=itemgetter(0))
|
||||
|
||||
# Change all of these numbers so that they are consecutive starting at 1
|
||||
for i in range(0, len(column1_iorig_columnsAfter1)):
|
||||
column1_iorig_columnsAfter1[i][0] = i+1
|
||||
|
||||
# Sort the list of lines so they are back in the original order
|
||||
|
||||
column1_iorig_columnsAfter1.sort(key=itemgetter(1))
|
||||
|
||||
for i in range(0, len(column1_iorig_columnsAfter1)):
|
||||
column1 = column1_iorig_columnsAfter1[i][0]
|
||||
columnsAfter1 = column1_iorig_columnsAfter1[i][2]
|
||||
sys.stdout.write(str(column1) +' ' + columnsAfter1 + '\n')
|
||||
|
||||
if f != None:
|
||||
f.close()
|
||||
|
||||
4717
tools/moltemplate/src/ttree.py
Executable file
4717
tools/moltemplate/src/ttree.py
Executable file
File diff suppressed because it is too large
Load Diff
2074
tools/moltemplate/src/ttree_lex.py
Normal file
2074
tools/moltemplate/src/ttree_lex.py
Normal file
File diff suppressed because it is too large
Load Diff
618
tools/moltemplate/src/ttree_matrix_stack.py
Normal file
618
tools/moltemplate/src/ttree_matrix_stack.py
Normal file
@ -0,0 +1,618 @@
|
||||
# Author: Andrew Jewett (jewett.aij at g mail)
|
||||
# http://www.chem.ucsb.edu/~sheagroup
|
||||
# License: 3-clause BSD License (See LICENSE.TXT)
|
||||
# Copyright (c) 2012, Regents of the University of California
|
||||
# All rights reserved.
|
||||
|
||||
from collections import deque
|
||||
from array import array
|
||||
from ttree_lex import *
|
||||
#import sys
|
||||
|
||||
def MultMat(dest, A, B):
|
||||
""" Multiply two matrices together. Store result in "dest".
|
||||
|
||||
"""
|
||||
I = len(A)
|
||||
J = len(B[0])
|
||||
K = len(B) # or len(A[0])
|
||||
for i in range(0,I):
|
||||
for j in range(0,J):
|
||||
dest[i][j] = 0.0
|
||||
for k in range(0,K):
|
||||
dest[i][j] += A[i][k] * B[k][j]
|
||||
|
||||
|
||||
def MatToStr(M):
|
||||
strs = []
|
||||
for i in range(0, len(M)):
|
||||
for j in range(0, len(M[i])):
|
||||
strs.append(str(M[i][j])+' ')
|
||||
strs.append('\n')
|
||||
return(''.join(strs))
|
||||
|
||||
|
||||
def LinTransform(dest, M, x):
|
||||
""" Multiply matrix M by 1-dimensioal array (vector) "x" (from the right).
|
||||
Store result in 1-dimensional array "dest".
|
||||
In this function, wetreat "x" and "dest" as a column vectors.
|
||||
(Not row vectors.)
|
||||
|
||||
"""
|
||||
I = len(A)
|
||||
J = len(x)
|
||||
for i in range(0,I):
|
||||
dest[i] = 0.0
|
||||
for j in range(0,J):
|
||||
dest[i] += M[i][j] * x[j]
|
||||
|
||||
|
||||
def AffineTransform(dest, M, x):
|
||||
""" This function performs an affine transformation on vector "x".
|
||||
Multiply 3-dimensional vector "x" by first three columns of 3x4
|
||||
matrix M. Add to this the final column of M. Store result in "dest":
|
||||
dest[0] = M[0][0]*x[0] + M[0][1]*x[1] + M[0][2]*x[2] + M[0][3]
|
||||
dest[1] = M[1][0]*x[0] + M[1][1]*x[1] + M[1][2]*x[2] + M[1][3]
|
||||
dest[2] = M[2][0]*x[0] + M[2][1]*x[1] + M[2][2]*x[2] + M[2][3]
|
||||
|
||||
"""
|
||||
D = len(M)
|
||||
#assert(len(M[0]) == D+1)
|
||||
for i in range(0,D):
|
||||
dest[i] = 0.0
|
||||
for j in range(0,D):
|
||||
dest[i] += M[i][j] * x[j]
|
||||
dest[i] += M[i][D] #(translation offset stored in final column)
|
||||
|
||||
|
||||
def AffineCompose(dest, M2, M1):
|
||||
"""
|
||||
Multiplication for pairs of 3x4 matrices is technically undefined.
|
||||
However what we want to do is compose two affine transformations: M1 and M2
|
||||
|
||||
3x4 matrices are used to define rotations/translations
|
||||
x' = M[0][0]*x + M[0][1]*y + M[0][2]*z + M[0][3]
|
||||
y' = M[1][0]*x + M[1][1]*y + M[1][2]*z + M[1][3]
|
||||
z' = M[2][0]*x + M[2][1]*y + M[2][2]*z + M[2][3]
|
||||
|
||||
We want to create a new 3x4 matrix representing an affine transformation
|
||||
(M2 M1), defined so that when (M2 M1) is applied to vector x, the result is
|
||||
M2 (M1 x). In other words:
|
||||
first, affine transformation M1 is applied to to x
|
||||
then, affine transformation M2 is applied to (M1 x)
|
||||
|
||||
"""
|
||||
|
||||
D = len(M1)
|
||||
#assert(len(M1[0]) == D+1)
|
||||
#assert(len(M2[0]) == D+1)
|
||||
for i in range(0, D):
|
||||
dest[i][D] = 0.0
|
||||
for j in range(0, D+1):
|
||||
dest[i][j] = 0.0
|
||||
for k in range(0, D):
|
||||
dest[i][j] += M2[i][k] * M1[k][j]
|
||||
dest[i][D] += M2[i][D]
|
||||
|
||||
def CopyMat(dest, source):
|
||||
for i in range(0, len(source)):
|
||||
for j in range(0, len(source[i])):
|
||||
dest[i][j] = source[i][j]
|
||||
|
||||
|
||||
class AffineStack(object):
|
||||
"""
|
||||
This class defines a matrix stack used to define compositions of affine
|
||||
transformations of 3 dimensional coordinates (rotation and translation).
|
||||
Affine transformations are represented using 3x4 matrices.
|
||||
(Coordinates of atoms are thought of as column vectors: [[x],[y],[z]],
|
||||
although they are represented internally in the more ordinary way [x,y,z].
|
||||
To aplly an affine transformation to a vector, multiply the vector
|
||||
by the matrix, from the left-hand side, as explained in the comments for:
|
||||
AffineTransform(dest, M, x)
|
||||
Note: The last column of the 3x4 matrix stores a translational offset.
|
||||
This bears similarity with the original OpenGL matrix stack
|
||||
http://content.gpwiki.org/index.php/OpenGL:Tutorials:Theory
|
||||
(OpenGL uses 4x4 matrices. We don't need the final row of these matrices,
|
||||
because in OpenGL, these rows are used for perspective transformations.)
|
||||
http://en.wikipedia.org/wiki/Homogeneous_coordinates#Use_in_computer_graphics
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.stack = None
|
||||
self.M = None
|
||||
self._tmp = None
|
||||
self.Clear()
|
||||
|
||||
def Clear(self):
|
||||
self.stack = deque([])
|
||||
self.M = [[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0]] # (identity, initially)
|
||||
self._tmp = [[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0]]
|
||||
|
||||
def PushRight(self, M):
|
||||
#Push a copy of matrix self.M onto the stack
|
||||
# We make no distinction between "right" and "left" here.
|
||||
# All transformations are pushed onto the stack in the same way.
|
||||
# (The "right" and "left" refer to whether the matrix is multiplied
|
||||
# on the right of left hand side. Because not all matrices need be
|
||||
# invertible, we require that matrices be popped from the stack
|
||||
# in the reverse order they were pushed. This prevents the ability
|
||||
# to push and pop matrices to either end of the stack in an arbitrary
|
||||
# order (like append(), appendleft(), pop(), popleft()).)
|
||||
self.stack.append([[self.M[i][j] for j in range(0,len(self.M[i]))]
|
||||
for i in range(0,len(self.M))])
|
||||
# The "Right" and "Left" refer to whether the new matrix is multiplied
|
||||
# on the right or left side of the culmulatie matrix product.
|
||||
AffineCompose(self._tmp, self.M, M) # Afterwards, self._tmp = self.M * M
|
||||
|
||||
#sys.stderr.write('DEBUG: PushLeft()\n' +
|
||||
# MatToStr(self._tmp) + '\n = \n' +
|
||||
# MatToStr(M) + '\n * \n' +
|
||||
# MatToStr(self.M) + '\n')
|
||||
|
||||
CopyMat(self.M, self._tmp) # Copy self._tmp into self.M
|
||||
|
||||
|
||||
def PushLeft(self, M):
|
||||
#Push a copy of matrix self.M onto the stack
|
||||
# We make no distinction between right and left here.
|
||||
# All transformations are pushed onto the stack in the same way.
|
||||
# (The "right" and "left" refer to whether the matrix is multiplied
|
||||
# on the right of left hand side. Because not all matrices need be
|
||||
# invertible, we require that matrices be popped from the stack
|
||||
# in the reverse order they were pushed. This prevents the ability
|
||||
# to push and pop matrices to either end of the stack in an arbitrary
|
||||
# order (like append(), appendleft(), pop(), popleft()).)
|
||||
self.stack.append([[self.M[i][j] for j in range(0,len(self.M[i]))]
|
||||
for i in range(0,len(self.M))])
|
||||
# The "Right" and "Left" refer to whether the new matrix is multiplied
|
||||
# on the right or left side of the culmulatie matrix product.
|
||||
AffineCompose(self._tmp, M, self.M) # Afterwards, self._tmp = M * self.M
|
||||
|
||||
#sys.stderr.write('DEBUG: PushLeft()\n' +
|
||||
# MatToStr(self._tmp) + '\n = \n' +
|
||||
# MatToStr(M) + '\n * \n' +
|
||||
# MatToStr(self.M) + '\n')
|
||||
|
||||
CopyMat(self.M, self._tmp) # Copy self.tmp into self.M
|
||||
|
||||
|
||||
def Pop(self):
|
||||
CopyMat(self.M, self.stack.pop())
|
||||
# (No need to return a matrix,"self.M",after popping.
|
||||
# The caller can directly access self.M later.)
|
||||
#return self.M
|
||||
|
||||
|
||||
def PopRight(self):
|
||||
self.Pop()
|
||||
|
||||
|
||||
def PopLeft(self):
|
||||
self.Pop()
|
||||
|
||||
def PushCommandsRight(self,
|
||||
text, # text containing affine transformation commands
|
||||
# The next two arguments are optional:
|
||||
src_loc = OSrcLoc(), # for debugging
|
||||
xcm = None): # position of center of object
|
||||
"""Generate affine transformation matrices from simple text commands
|
||||
(such as \"rotcm(90,0,0,1)\" and \"move(0,5.0,0)".
|
||||
Chains of "rotcm", "movecm", "rot", and "move" commands
|
||||
can also be strung together:
|
||||
\"rotcm(90,0,0,1).move(0,5.0,0)\"
|
||||
Commands ending in \"cm\" are carried out relative to center-of-mass
|
||||
(average position) of the object, and consequently require
|
||||
an additional argument (\"xcm\").
|
||||
|
||||
"""
|
||||
self.PushRight(AffineStack.CommandsToMatrix(text, src_loc, xcm))
|
||||
|
||||
def PushCommandsLeft(self,
|
||||
text, # text containing affine transformation commands
|
||||
# The next two arguments are optional:
|
||||
src_loc = OSrcLoc(), # for debugging
|
||||
xcm = None): # position of center of object
|
||||
self.PushLeft(AffineStack.CommandsToMatrix(text, src_loc, xcm))
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return 1 + len(self.stack)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def CommandsToMatrix(text, # text containing affine transformation commands
|
||||
src_loc = OSrcLoc(), # for debugging
|
||||
xcm = None): # position of center of object
|
||||
Mdest=[[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]]
|
||||
M =[[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]]
|
||||
Mtmp =[[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]]
|
||||
|
||||
transform_commands = text.split(').')
|
||||
for transform_str in transform_commands:
|
||||
if transform_str.find('move(') == 0:
|
||||
i_paren_close = transform_str.find(')')
|
||||
if i_paren_close == -1:
|
||||
i_paren_close = len(transform_str)
|
||||
args = transform_str[5:i_paren_close].split(',')
|
||||
if (len(args) != 3):
|
||||
raise InputError('Error near '+ErrorLeader(src_loc.infile, src_loc.lineno)+':\n'
|
||||
' Invalid command: \"'+transform_str+'\"\n'
|
||||
' This command requires 3 numerical arguments.')
|
||||
M = [[1.0, 0.0, 0.0, float(args[0])],
|
||||
[0.0, 1.0, 0.0, float(args[1])],
|
||||
[0.0, 0.0, 1.0, float(args[2])]]
|
||||
AffineCompose(Mtmp, M, Mdest)
|
||||
CopyMat(Mdest, Mtmp)
|
||||
|
||||
#if transform_str.find('movecm(') == 0:
|
||||
# # assert(xcm != None)
|
||||
# i_paren_close = transform_str.find(')')
|
||||
# if i_paren_close == -1:
|
||||
# i_paren_close = len(transform_str)
|
||||
# args = transform_str[8:i_paren_close].split(',')
|
||||
# if (len(args) != 3):
|
||||
# raise InputError('Error near '+ErrorLeader(src_loc.infile, src_loc.lineno)+':\n'
|
||||
# ' Invalid command: \"'+transform_str+'\"\n'
|
||||
# ' This command requires 3 numerical arguments.')
|
||||
# M = [[1.0, 0.0, 0.0, float(args[0])-(xcm[0])],
|
||||
# [0.0, 1.0, 0.0, float(args[1])-(xcm[1])],
|
||||
# [0.0, 0.0, 1.0, float(args[2])-(xcm[2])]]
|
||||
# AffineCompose(Mtmp, M, Mdest)
|
||||
# CopyMat(Mdest, Mtmp)
|
||||
|
||||
elif transform_str.find('rot(') == 0:
|
||||
i_paren_close = transform_str.find(')')
|
||||
if i_paren_close == -1:
|
||||
i_paren_close = len(transform_str)
|
||||
args = transform_str[4:i_paren_close].split(',')
|
||||
center_v = None
|
||||
if (len(args) == 7):
|
||||
center_v = [float(args[4]), float(args[5]), float(args[6])]
|
||||
elif (len(args) != 4):
|
||||
raise InputError('Error near '+ErrorLeader(src_loc.infile, src_loc.lineno)+':\n'
|
||||
' Invalid command: \"'+transform_str+'\"\n'
|
||||
' This command requires either 4 or 7 numerical arguments. Either:\n'
|
||||
' rot(angle, axisX, axisY, axiZ) or \n'
|
||||
' rot(angle, axisX, axisY, axiZ, centerX, centerY, centerZ)')
|
||||
M[0][3] = 0.0 #RotMatAXYZ() only modifies 3x3 submatrix of M
|
||||
M[1][3] = 0.0 #The remaining final column must be zeroed by hand
|
||||
M[2][3] = 0.0
|
||||
RotMatAXYZ(M,
|
||||
float(args[0])*math.pi/180.0,
|
||||
float(args[1]),
|
||||
float(args[2]),
|
||||
float(args[3]))
|
||||
if (center_v == None):
|
||||
AffineCompose(Mtmp, M, Mdest)
|
||||
CopyMat(Mdest, Mtmp)
|
||||
else:
|
||||
# Move "center_v" to the origin
|
||||
moveCentToOrig = [[1.0, 0.0, 0.0, -center_v[0]],
|
||||
[0.0, 1.0, 0.0, -center_v[1]],
|
||||
[0.0, 0.0, 1.0, -center_v[2]]]
|
||||
AffineCompose(Mtmp, moveCentToOrig, Mdest)
|
||||
CopyMat(Mdest, Mtmp)
|
||||
# Rotate the coordinates (relative to the origin)
|
||||
AffineCompose(Mtmp, M, Mdest) # M is the rotation matrix
|
||||
CopyMat(Mdest, Mtmp)
|
||||
# Move the origin back to center_v
|
||||
moveCentBack = [[1.0, 0.0, 0.0, center_v[0]],
|
||||
[0.0, 1.0, 0.0, center_v[1]],
|
||||
[0.0, 0.0, 1.0, center_v[2]]]
|
||||
AffineCompose(Mtmp, moveCentBack, Mdest)
|
||||
CopyMat(Mdest, Mtmp)
|
||||
|
||||
|
||||
# # elif transform_str.find('rotcm(') == 0:
|
||||
# # assert(xcm != None)
|
||||
# # i_paren_close = transform_str.find(')')
|
||||
# # if i_paren_close == -1:
|
||||
# # i_paren_close = len(transform_str)
|
||||
# # args = transform_str[6:i_paren_close].split(',')
|
||||
# # if (len(args) != 4):
|
||||
# # raise InputError('Error near '+ErrorLeader(src_loc.infile, src_loc.lineno)+':\n'
|
||||
# # ' Invalid command: \"'+transform_str+'\"\n'
|
||||
# # ' This command requires 4 numerical arguments.')
|
||||
# #
|
||||
# # moveCMtoOrig = [[1.0, 0.0, 0.0, -xcm[0]],
|
||||
# # [0.0, 1.0, 0.0, -xcm[1]],
|
||||
# # [0.0, 0.0, 1.0, -xcm[2]]]
|
||||
# # AffineCompose(Mtmp, moveCMtoOrig, Mdest)
|
||||
# # CopyMat(Mdest, Mtmp)
|
||||
# # M[0][3] = 0.0#RotMatAXYZ() only modifies 3x3 submatrix of M
|
||||
# # M[1][3] = 0.0#The remaining final column must be zeroed by hand
|
||||
# # M[2][3] = 0.0
|
||||
# # RotMatAXYZ(M,
|
||||
# # float(args[0])*math.pi/180.0,
|
||||
# # float(args[1]),
|
||||
# # float(args[2]),
|
||||
# # float(args[3]))
|
||||
# # AffineCompose(Mtmp, M, Mdest)
|
||||
# # CopyMat(Mdest, Mtmp)
|
||||
# # moveCmBack = [[1.0, 0.0, 0.0, xcm[0]],
|
||||
# # [0.0, 1.0, 0.0, xcm[1]],
|
||||
# # [0.0, 0.0, 1.0, xcm[2]]]
|
||||
# # AffineCompose(Mtmp, moveCmBack, Mdest)
|
||||
# # CopyMat(Mdest, Mtmp)
|
||||
|
||||
elif transform_str.find('scale(') == 0:
|
||||
i_paren_close = transform_str.find(')')
|
||||
if i_paren_close == -1:
|
||||
i_paren_close = len(transform_str)
|
||||
args = transform_str[6:i_paren_close].split(',')
|
||||
|
||||
if (len(args) == 1):
|
||||
scale_v = [float(args[0]), float(args[0]), float(args[0])]
|
||||
center_v = [0.0, 0.0, 0.0]
|
||||
elif (len(args) == 3):
|
||||
scale_v = [float(args[0]), float(args[1]), float(args[2])]
|
||||
center_v = [0.0, 0.0, 0.0]
|
||||
elif (len(args) == 4):
|
||||
scale_v = [float(args[0]), float(args[0]), float(args[0])]
|
||||
center_v = [float(args[1]), float(args[2]), float(args[3])]
|
||||
elif (len(args) == 6):
|
||||
scale_v = [float(args[0]), float(args[1]), float(args[2])]
|
||||
center_v = [float(args[3]), float(args[4]), float(args[5])]
|
||||
else:
|
||||
raise InputError('Error near '+ErrorLeader(src_loc.infile, src_loc.lineno)+':\n'
|
||||
' Invalid command: \"'+transform_str+'\"\n'
|
||||
' This command requires either 1, 3, 4, or 6 numerical arguments. Either:\n'
|
||||
' scale(ratio), or \n'
|
||||
' scale(ratioX, ratioY, ratioZ),\n'
|
||||
' scale(ratio, centerX, centerY, centerZ), or\n'
|
||||
' scale(ratioX, ratioY, ratioZ, centerX, centerY, centerZ)')
|
||||
|
||||
ScaleMat(M, scale_v)
|
||||
|
||||
# Now worry about translation:
|
||||
for d in range(0, 3):
|
||||
M[d][3] = center_v[d] * (1.0 - scale_v[d])
|
||||
|
||||
AffineCompose(Mtmp, M, Mdest)
|
||||
CopyMat(Mdest, Mtmp)
|
||||
|
||||
# # elif transform_str.find('scalecm(') == 0:
|
||||
# # assert(xcm != None)
|
||||
# # i_paren_close = transform_str.find(')')
|
||||
# # if i_paren_close == -1:
|
||||
# # i_paren_close = len(transform_str)
|
||||
# # args = transform_str[8:i_paren_close].split(',')
|
||||
# #
|
||||
# # moveCMtoOrig = [[1.0, 0.0, 0.0, -xcm[0]],
|
||||
# # [0.0, 1.0, 0.0, -xcm[1]],
|
||||
# # [0.0, 0.0, 1.0, -xcm[2]]]
|
||||
# # AffineCompose(Mtmp, moveCMtoOrig, Mdest)
|
||||
# # CopyMat(Mdest, Mtmp)
|
||||
# #
|
||||
# # M[0][3] = 0.0 #ScaleMat() only modifies 3x3 submatrix of M
|
||||
# # M[1][3] = 0.0 #The remaining final column must be zeroed by hand
|
||||
# # M[2][3] = 0.0
|
||||
# # if (len(args) == 1):
|
||||
# # ScaleMat(M, args[0])
|
||||
# # elif (len(args) == 3):
|
||||
# # ScaleMat(M, args)
|
||||
# # else:
|
||||
# # raise InputError('Error near '+ErrorLeader(src_loc.infile, src_loc.lineno)+':\n'
|
||||
# # ' Invalid command: \"'+transform_str+'\"\n'
|
||||
# # ' This command requires either 1 or 3 numerical arguments.')
|
||||
# #
|
||||
# # AffineCompose(Mtmp, M, Mdest)
|
||||
# # CopyMat(Mdest, Mtmp)
|
||||
# # moveCmBack = [[1.0, 0.0, 0.0, xcm[0]],
|
||||
# # [0.0, 1.0, 0.0, xcm[1]],
|
||||
# # [0.0, 0.0, 1.0, xcm[2]]]
|
||||
# # AffineCompose(Mtmp, moveCmBack, Mdest)
|
||||
# # CopyMat(Mdest, Mtmp)
|
||||
|
||||
else:
|
||||
raise InputError('Error near '+ErrorLeader(src_loc.infile, src_loc.lineno)+':\n'
|
||||
' Unknown transformation command: \"'+transform_str+'\"\n')
|
||||
|
||||
|
||||
return Mdest
|
||||
|
||||
|
||||
|
||||
|
||||
class MultiAffineStack(object):
|
||||
def __init__(self, which_stack=None):
|
||||
self.tot_stack = None
|
||||
self.stack_lookup = None
|
||||
self.stack_keys = None
|
||||
self.stacks = None
|
||||
self.M = None
|
||||
self.error_if_substack_empty = False
|
||||
self.Clear()
|
||||
|
||||
def Clear(self):
|
||||
self.tot_stack = AffineStack()
|
||||
self.stack_lookup = {}
|
||||
self.stack_keys = deque([])
|
||||
self.stacks = deque([])
|
||||
self.M = self.tot_stack.M
|
||||
self.error_if_substack_empty = False
|
||||
|
||||
def _Update(self):
|
||||
self.tot_stack.Clear()
|
||||
self.M = self.tot_stack.M
|
||||
assert(len(self.stacks) > 0)
|
||||
for stack in self.stacks:
|
||||
self.tot_stack.PushRight(stack.M)
|
||||
|
||||
def PushStack(self, which_stack):
|
||||
stack = AffineStack()
|
||||
self.stack_keys.append(which_stack)
|
||||
self.stack_lookup[which_stack] = stack
|
||||
self.stacks.append(stack)
|
||||
self.tot_stack.PushRight(stack.M)
|
||||
|
||||
def PopStack(self):
|
||||
assert(len(self.stacks) > 0)
|
||||
self.tot_stack.PopRight()
|
||||
which_stack = self.stack_keys.pop()
|
||||
del self.stack_lookup[which_stack]
|
||||
self.stacks.pop()
|
||||
|
||||
def Push(self, M, which_stack=None, right_not_left = True):
|
||||
if len(self.stacks) == 0:
|
||||
self.PushStack(which_stack)
|
||||
if which_stack == None:
|
||||
stack = self.stacks[-1]
|
||||
if right_not_left:
|
||||
stack.PushRight(M) # This should copy the matrix M into stack.M
|
||||
else:
|
||||
stack.PushLeft(M)
|
||||
else:
|
||||
stack = self.stack_lookup[which_stack]
|
||||
if right_not_left:
|
||||
stack.PushRight(M)
|
||||
else:
|
||||
stack.PushLeft(M)
|
||||
if stack == self.stacks[-1]:
|
||||
self.tot_stack.PopRight() #Replace the last matrix on self.tot_stack
|
||||
# Note: Always use tot_stack.PopRight (even if right_not_left=False)
|
||||
self.tot_stack.PushRight(stack.M) # with the the updated version.
|
||||
# Note: We could call self._Update(M) here, but that is slower.
|
||||
else:
|
||||
self._Update()
|
||||
|
||||
def PushRight(self, M, which_stack=None):
|
||||
self.Push(M, which_stack, right_not_left=True)
|
||||
|
||||
def PushLeft(self, M, which_stack=None):
|
||||
self.Push(M, which_stack, right_not_left=False)
|
||||
|
||||
def PushCommandsRight(self,
|
||||
text, # text containing affine transformation commands
|
||||
# The next two arguments are optional:
|
||||
src_loc = OSrcLoc(), # for debugging
|
||||
xcm = None,
|
||||
which_stack=None): # position of center of object
|
||||
"""Generate affine transformation matrices from simple text commands
|
||||
(such as \"rotcm(90,0,0,1)\" and \"move(0,5.0,0)".
|
||||
Chains of "rotcm", "movecm", "rot", and "move" commands
|
||||
can also be strung together:
|
||||
\"rotcm(90,0,0,1).move(0,5.0,0)\"
|
||||
Commands ending in \"cm\" are carried out relative to center-of-mass
|
||||
(average position) of the object, and consequently require
|
||||
an additional argument (\"xcm\").
|
||||
|
||||
"""
|
||||
self.PushRight(AffineStack.CommandsToMatrix(text, src_loc, xcm),
|
||||
which_stack)
|
||||
|
||||
def PushCommandsLeft(self,
|
||||
text, # text containing affine transformation commands
|
||||
# The next two arguments are optional:
|
||||
src_loc = OSrcLoc(), # for debugging
|
||||
xcm = None, # position of center of object
|
||||
which_stack = None):
|
||||
self.PushLeft(AffineStack.CommandsToMatrix(text, src_loc, xcm),
|
||||
which_stack)
|
||||
|
||||
|
||||
|
||||
def Pop(self, which_stack=None, right_not_left=True):
|
||||
#empty_stack_error = False
|
||||
|
||||
if which_stack == None:
|
||||
stack = self.stacks[-1]
|
||||
if len(stack) >= 1:
|
||||
if right_not_left:
|
||||
stack.PopRight()
|
||||
else:
|
||||
stack.PopLeft()
|
||||
# Note: We could call self._Update(M) here, but that is slower
|
||||
self.tot_stack.PopRight() #Replace the last matrix on self.tot_stack
|
||||
# Note: Always use tot_stack.PopRight (even if right_not_left=False)
|
||||
self.tot_stack.PushRight(stack.M) # with the the updated version.
|
||||
else:
|
||||
assert(False)
|
||||
# OPTIONAL CODE BELOW AUTOMATICALLY INVOKES self.PopStack() WHEN
|
||||
# THE stacks[-1].stack IS EMPTY. PROBABLY DOES NOT WORK. IGNORE
|
||||
# if (not self.error_if_substack_empty):
|
||||
# if right_not_left:
|
||||
# assert(len(self.stacks) > 0)
|
||||
# self.PopStack()
|
||||
# else:
|
||||
# assert(False)
|
||||
# else:
|
||||
# empty_stack_error = True
|
||||
|
||||
else:
|
||||
stack = self.stack_lookup[which_stack]
|
||||
if len(stack) > 1:
|
||||
if right_not_left:
|
||||
stack.PopRight()
|
||||
else:
|
||||
stack.PopLeft()
|
||||
self._Update()
|
||||
else:
|
||||
assert(False)
|
||||
#empty_stack_error = True
|
||||
|
||||
def PopRight(self, which_stack=None):
|
||||
self.Pop(which_stack, right_not_left=True)
|
||||
|
||||
def PopLeft(self, which_stack=None):
|
||||
self.Pop(which_stack, right_not_left=True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def ScaleMat(dest, scale):
|
||||
for i in range(0, len(dest)):
|
||||
for j in range(0, len(dest[i])):
|
||||
dest[i][j] = 0.0
|
||||
if ((type(scale) is float) or (type(scale) is int)):
|
||||
for i in range(0, len(dest)):
|
||||
dest[i][i] = scale
|
||||
else:
|
||||
for i in range(0, len(dest)):
|
||||
dest[i][i] = scale[i]
|
||||
|
||||
|
||||
def RotMatAXYZ(dest, angle, axis_x, axis_y, axis_z):
|
||||
r = math.sqrt(axis_x*axis_x + axis_y*axis_y + axis_z*axis_z)
|
||||
X = axis_x / r
|
||||
Y = axis_y / r
|
||||
Z = axis_z / r
|
||||
|
||||
#angle *= math.pi/180.0 # "angle" is assumed to be in degrees
|
||||
# on second thought, let the caller worry about angle units.
|
||||
c = math.cos(angle)
|
||||
s = math.sin(angle)
|
||||
|
||||
dest[0][0] = X*X*(1-c) + c
|
||||
dest[1][1] = Y*Y*(1-c) + c
|
||||
dest[2][2] = Z*Z*(1-c) + c
|
||||
|
||||
dest[0][1] = X*Y*(1-c) - Z*s
|
||||
dest[0][2] = X*Z*(1-c) + Y*s
|
||||
|
||||
dest[1][0] = Y*X*(1-c) + Z*s
|
||||
dest[2][0] = Z*X*(1-c) - Y*s
|
||||
|
||||
dest[1][2] = Y*Z*(1-c) - X*s
|
||||
dest[2][1] = Z*Y*(1-c) + X*s
|
||||
|
||||
# formula from these sources:
|
||||
# http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/
|
||||
# also check
|
||||
# http://www.manpagez.com/man/3/glRotate/
|
||||
# some pdb test commands:
|
||||
# from lttree_matrixstack import *
|
||||
# r = [[1.0,0.0,0.0], [0.0,1.0,0.0], [0.0,0.0,1.0]]
|
||||
# RotMatAXYZ(r, 90.0, 0.0, 0.0, 1.0)
|
||||
|
||||
|
||||
115
tools/moltemplate/src/ttree_render.py
Executable file
115
tools/moltemplate/src/ttree_render.py
Executable file
@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
man_page_text = """
|
||||
Usage (example):
|
||||
|
||||
ttree_render.py ttree_assignments.txt < file.template > file.rendered
|
||||
|
||||
The argument (ttree_assignments.txt) should be a 2-column file containing
|
||||
ttree-style variables (1st column), and their values (bindings, 2nd column).
|
||||
|
||||
This program reads a text file containing ttree-style variables,
|
||||
substitutes the corresponding values stored in ttree_assignments.txt,
|
||||
and prints out the new (rendered) text to the standard-out.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
from ttree_lex import *
|
||||
from ttree import *
|
||||
import gc
|
||||
|
||||
|
||||
g_filename = __file__.split('/')[-1]
|
||||
g_module_name = g_filename
|
||||
if g_filename.rfind('.py') != -1:
|
||||
g_module_name = g_filename[:g_filename.rfind('.py')]
|
||||
g_date_str = '2012-9-06'
|
||||
g_version_str = '0.1'
|
||||
g_program_name = g_filename
|
||||
#sys.stderr.write(g_program_name+' v'+g_version_str+' '+g_date_str+' ')
|
||||
|
||||
|
||||
|
||||
try:
|
||||
|
||||
if (len(sys.argv) != 2):
|
||||
raise InputError('Error running \"'+g_program_name+'\"\n'
|
||||
' Typical usage:\n'
|
||||
' ttree_render.py ttree_assignments.txt < file.template > file.rendered\n'
|
||||
'\n'
|
||||
' Missing argument.\n'
|
||||
' Expected the name of a 2-column file containing\n'
|
||||
' variable names and their bindings (values).\n'
|
||||
' (This is likely a programmer error.\n'
|
||||
' This script was not intended to be run by end users.)\n')
|
||||
|
||||
bindings_filename = sys.argv[1]
|
||||
f = open(bindings_filename)
|
||||
assignments = {}
|
||||
|
||||
#BasicUIReadBindingsStream(assignments, f, bindings_filename)
|
||||
|
||||
# The line above is robust but it uses far too much memory.
|
||||
# This for loop below works for most cases.
|
||||
for line in f:
|
||||
#tokens = lines.strip().split()
|
||||
tokens = SplitQuotedString(line.strip()) # like split but handles quotes
|
||||
if len(tokens) < 2:
|
||||
continue
|
||||
assignments[tokens[0]] = tokens[1]
|
||||
|
||||
f.close()
|
||||
gc.collect()
|
||||
|
||||
lex = TemplateLexer(sys.stdin, '__standard_input_for_ttree_render__')
|
||||
lex.var_delim = '$@'
|
||||
|
||||
text_block_list = lex.ReadTemplate(simplify_output=True)
|
||||
|
||||
output = []
|
||||
|
||||
for entry in text_block_list:
|
||||
assert(isinstance(entry, str))
|
||||
|
||||
if ((len(entry) > 1) and (entry[0] in lex.var_delim)):
|
||||
if '.' in entry:
|
||||
ic = entry.find('.')
|
||||
var_name = entry[:ic]
|
||||
var_suffix = entry[ic:]
|
||||
else:
|
||||
var_name = entry
|
||||
var_suffix = ''
|
||||
|
||||
var_name = entry
|
||||
if var_name not in assignments:
|
||||
raise(InputError('Error('+g_program_name+')'
|
||||
#' at '+ErrorLeader(var_ref.src_loc.infile,
|
||||
# var_ref.src_loc.lineno)+
|
||||
' unknown variable:\n'
|
||||
' \"'+var_name+'\"\n'))
|
||||
else:
|
||||
var_value = assignments[var_name]
|
||||
|
||||
format_fname, args = ExtractFormattingCommands(var_suffix)
|
||||
if format_fname == 'ljust':
|
||||
if len(args) == 1:
|
||||
var_value = var_value.ljust(int(args[0]))
|
||||
else:
|
||||
var_value = var_value.ljust(int(args[0]), args[1])
|
||||
elif format_fname == 'rjust':
|
||||
if len(args) == 1:
|
||||
var_value = var_value.rjust(int(args[0]))
|
||||
else:
|
||||
var_value = var_value.rjust(int(args[0]), args[1])
|
||||
output.append(var_value)
|
||||
else:
|
||||
output += entry
|
||||
|
||||
sys.stdout.write(''.join(output))
|
||||
|
||||
|
||||
except (ValueError, InputError) as err:
|
||||
sys.stderr.write('\n'+str(err)+'\n')
|
||||
sys.exit(-1)
|
||||
Reference in New Issue
Block a user