| [bcfb77] | 1 | #!@PYTHON@ | 
|---|
| [bfbb62] | 2 |  | 
|---|
|  | 3 | # Boxmaker 1.0 | 
|---|
| [239cc5] | 4 | # Creates tremolo-datafiles with arbitrary size and specific density from a single input molecule, | 
|---|
|  | 5 | # supporting numerous pre- and postprocessing features such as unit conversion. | 
|---|
| [bfbb62] | 6 | # Gregor Bollerhey - bollerhe@ins.uni-bonn.de | 
|---|
|  | 7 |  | 
|---|
|  | 8 |  | 
|---|
| [5735ba] | 9 | import re, os, os.path, sys, operator | 
|---|
| [2aa9ef] | 10 | import pyMoleCuilder as mol | 
|---|
| [5735ba] | 11 |  | 
|---|
| [0ad49cc] | 12 | avogadro =  6.022143e23 | 
|---|
|  | 13 |  | 
|---|
| [0c83d8] | 14 | class c_opt(): | 
|---|
|  | 15 | basename = None | 
|---|
|  | 16 | tremofiledir = './' | 
|---|
|  | 17 | potentialsfiledir = './' | 
|---|
|  | 18 | outfilename = 'out' | 
|---|
|  | 19 |  | 
|---|
|  | 20 | source = None | 
|---|
|  | 21 | molarmass = None | 
|---|
|  | 22 | density = None | 
|---|
|  | 23 | temp = None | 
|---|
|  | 24 |  | 
|---|
|  | 25 | number = '1000' | 
|---|
|  | 26 |  | 
|---|
|  | 27 | cubicdomain = 'on' | 
|---|
|  | 28 | cubiccell = 'off' | 
|---|
| [32bc47] | 29 | autorotate = 'off' | 
|---|
| [0c83d8] | 30 | autodim = 'on' | 
|---|
| [32bc47] | 31 | postprocess = 'on' | 
|---|
| [0ad49cc] | 32 | automass = 'on' | 
|---|
| [0c83d8] | 33 |  | 
|---|
|  | 34 | def update(self, name, value): | 
|---|
| [0ad49cc] | 35 | shortcuts = {'tf': 'temofiledir', 'pf': 'potentialsfiledir', 'o': 'outfilename', | 
|---|
|  | 36 | 'i': 'source', 'm': 'molarmass', 'rho': 'density', | 
|---|
|  | 37 | 't': 'temp', 'n': 'number', 'cd': 'cubicdomain', | 
|---|
|  | 38 | 'cc': 'cubiccell', 'ar': 'autorotate', 'ad': 'autodim', | 
|---|
|  | 39 | 'pp': 'postprocess', 'am': 'automass'} | 
|---|
| [751d7f1] | 40 |  | 
|---|
|  | 41 | if name in shortcuts: | 
|---|
|  | 42 | name = shortcuts[name] | 
|---|
|  | 43 |  | 
|---|
| [0c83d8] | 44 | if name in dir(self): | 
|---|
|  | 45 | exec('self.%s = "%s"' % (name, value)) | 
|---|
|  | 46 | else: | 
|---|
|  | 47 | print 'Warning: Unknown option:', name | 
|---|
|  | 48 |  | 
|---|
|  | 49 |  | 
|---|
|  | 50 | def ReadSettings(opt): | 
|---|
|  | 51 | # Obtain basename | 
|---|
| [5735ba] | 52 | if len(sys.argv) >= 2: | 
|---|
| [0c83d8] | 53 | opt.basename = sys.argv[1] | 
|---|
| [5735ba] | 54 | else: | 
|---|
| [0c83d8] | 55 | print 'Usage: boxmaker.py <basename> [options]' | 
|---|
| [5735ba] | 56 | exit() | 
|---|
| [0c83d8] | 57 |  | 
|---|
|  | 58 | # Read settings file | 
|---|
| [751d7f1] | 59 | try: | 
|---|
|  | 60 | with open('boxmaker.' + opt.basename) as f: | 
|---|
|  | 61 | for line in f: | 
|---|
|  | 62 | if len(line) > 0 and line[0] != '#': | 
|---|
|  | 63 | L, S, R = line.partition('=') | 
|---|
|  | 64 | opt.update(L.strip(), R.strip()) | 
|---|
|  | 65 | except IOError: | 
|---|
|  | 66 | print 'Warning: Configuration file not readable, CLI only' | 
|---|
| [0c83d8] | 67 |  | 
|---|
|  | 68 | # Parse parameters | 
|---|
|  | 69 | i = 2 | 
|---|
|  | 70 | while i < len(sys.argv): | 
|---|
|  | 71 | L = sys.argv[i] | 
|---|
|  | 72 |  | 
|---|
|  | 73 | if L[0] in '+-': | 
|---|
|  | 74 | LN = L[1:] | 
|---|
|  | 75 |  | 
|---|
|  | 76 | if L[0] == '+': | 
|---|
|  | 77 | R = 'on' | 
|---|
|  | 78 | else: | 
|---|
|  | 79 | R = 'off' | 
|---|
|  | 80 | else: | 
|---|
|  | 81 | LN = L | 
|---|
|  | 82 | i += 1 | 
|---|
|  | 83 | R = sys.argv[i] | 
|---|
|  | 84 |  | 
|---|
|  | 85 | opt.update(LN, R) | 
|---|
|  | 86 | i += 1 | 
|---|
|  | 87 |  | 
|---|
|  | 88 |  | 
|---|
|  | 89 | def ReadUnits(opt): | 
|---|
|  | 90 | lines = [] # The file needs to be processed twice, so we save the lines in the first run | 
|---|
|  | 91 |  | 
|---|
|  | 92 | with open(opt.tremofiledir + opt.basename + '.tremolo') as f: | 
|---|
| [5735ba] | 93 | for line in f: | 
|---|
|  | 94 | if len(line) > 0 and line[0] != '#': | 
|---|
|  | 95 | line = line.strip() | 
|---|
| [0c83d8] | 96 | lines.append(line) | 
|---|
|  | 97 |  | 
|---|
| [5735ba] | 98 | if 'systemofunits' in line: | 
|---|
|  | 99 | L, S, SOU = line.partition('=') | 
|---|
| [0c83d8] | 100 | SOU = SOU.strip()[:-1] # Remove semicolon | 
|---|
|  | 101 |  | 
|---|
| [5735ba] | 102 | if SOU == 'custom': | 
|---|
|  | 103 | units = {} | 
|---|
| [39cbae] | 104 | quantities = ['length', 'mass', 'temperature'] | 
|---|
| [0c83d8] | 105 |  | 
|---|
| [5735ba] | 106 | for quantity in quantities: | 
|---|
| [0c83d8] | 107 | units[quantity] = [None, None] # Init with scaling factor and unit 'None'. | 
|---|
|  | 108 |  | 
|---|
| [5735ba] | 109 | for line in lines: | 
|---|
|  | 110 | for quantity in quantities: | 
|---|
|  | 111 | if quantity in line: | 
|---|
|  | 112 | L, S, R = line.partition('=') | 
|---|
| [0c83d8] | 113 | R = R.strip()[:-1] # Remove semicolon | 
|---|
|  | 114 |  | 
|---|
| [5735ba] | 115 | if 'scalingfactor' in line: | 
|---|
| [39cbae] | 116 | units[quantity][0] = float(R) | 
|---|
| [5735ba] | 117 | else: | 
|---|
|  | 118 | units[quantity][1] = R | 
|---|
| [0c83d8] | 119 |  | 
|---|
| [5735ba] | 120 | elif SOU == 'kcalpermole': | 
|---|
| [39cbae] | 121 | units = {'length': [1.0, 'angstrom'], 'mass': [1.0, 'u'], 'temperature': [503.556, 'K']} | 
|---|
| [0c83d8] | 122 |  | 
|---|
| [5735ba] | 123 | elif SOU == 'evolt': | 
|---|
| [39cbae] | 124 | units = {'length': [1.0, 'angstrom'], 'mass': [1.0, 'u'], 'temperature': [11604.0, 'K']} | 
|---|
| [0c83d8] | 125 |  | 
|---|
| [5735ba] | 126 | else: # SI | 
|---|
| [39cbae] | 127 | units = {'length': [1.0, 'm'], 'mass': [1.0, 'kg'], 'temperature': [1.0, 'K']} | 
|---|
| [0c83d8] | 128 |  | 
|---|
| [5735ba] | 129 | return units | 
|---|
| [0c83d8] | 130 |  | 
|---|
|  | 131 |  | 
|---|
| [5735ba] | 132 | def ConvertUnits(have, want): | 
|---|
| [39cbae] | 133 | if have[0] == '!': | 
|---|
|  | 134 | return float(have[1:]) | 
|---|
|  | 135 |  | 
|---|
| [0c83d8] | 136 | # Redo with pipes? | 
|---|
| [f894fe] | 137 | #sys.stdout.write("units '%s' '%s'\n" % (have, want)) | 
|---|
| [5735ba] | 138 | ret = os.system("units '%s' '%s' > temp_units_output" % (have, want)) | 
|---|
| [0c83d8] | 139 |  | 
|---|
| [5735ba] | 140 | if ret == 0: | 
|---|
|  | 141 | with open('temp_units_output') as f: | 
|---|
|  | 142 | line = f.readline() | 
|---|
| [0c83d8] | 143 |  | 
|---|
| [5735ba] | 144 | os.system('rm temp_units_output') | 
|---|
| [0c83d8] | 145 |  | 
|---|
| [5735ba] | 146 | return float(line[3:-1]) | 
|---|
|  | 147 | else: | 
|---|
|  | 148 | raise NameError('UnitError') | 
|---|
| [0ad49cc] | 149 |  | 
|---|
|  | 150 |  | 
|---|
| [f894fe] | 151 | def GetSourceMolareMass(opt, units): | 
|---|
|  | 152 | mol.SelectionAllAtoms() | 
|---|
|  | 153 | mass_sum = mol.AnalysisCalculateMolarMass() | 
|---|
|  | 154 | have = ("%f atomicmassunit" % mass_sum) | 
|---|
|  | 155 | want = ("%f*%s" % tuple(units['mass'])) | 
|---|
|  | 156 | return ConvertUnits(have, want)*avogadro | 
|---|
| [0c83d8] | 157 |  | 
|---|
|  | 158 |  | 
|---|
| [c0c85f] | 159 | def UpdateSettingsAndSource(opt): | 
|---|
| [0c83d8] | 160 | # Map boolean values | 
|---|
| [c0c85f] | 161 | boolmap = {'on': True, 'off': False} | 
|---|
|  | 162 |  | 
|---|
| [0ad49cc] | 163 | for name in ['cubicdomain', 'cubiccell', 'autorotate', 'autodim', 'postprocess', 'automass']: | 
|---|
| [0c83d8] | 164 | value = eval('opt.' + name) | 
|---|
| [5735ba] | 165 |  | 
|---|
| [c0c85f] | 166 | if value in boolmap: | 
|---|
|  | 167 | value = boolmap[value] | 
|---|
| [0c83d8] | 168 | else: | 
|---|
|  | 169 | print 'Not a boolean value:', value | 
|---|
|  | 170 | exit() | 
|---|
|  | 171 |  | 
|---|
|  | 172 | exec('opt.' + name + '= value') | 
|---|
|  | 173 |  | 
|---|
|  | 174 | # Convert dimensions | 
|---|
|  | 175 | if opt.autodim: | 
|---|
|  | 176 | units = ReadUnits(opt) | 
|---|
|  | 177 |  | 
|---|
| [0ad49cc] | 178 | if not opt.automass: | 
|---|
|  | 179 | have = opt.molarmass | 
|---|
|  | 180 | want = '%f*%s / mol' % tuple(units['mass']) | 
|---|
|  | 181 | opt.molarmass = ConvertUnits(have, want) | 
|---|
| [0c83d8] | 182 |  | 
|---|
|  | 183 | have = opt.density | 
|---|
| [39cbae] | 184 | want = '(%f*%s) ' % tuple(units['mass']) + '/ (%f*%s)**3' % tuple(units['length']) | 
|---|
| [0c83d8] | 185 | opt.density = ConvertUnits(have, want) | 
|---|
| [39cbae] | 186 |  | 
|---|
|  | 187 | if opt.temp: | 
|---|
|  | 188 | have = opt.temp | 
|---|
|  | 189 | want = '%f*%s' % tuple(units['temperature']) | 
|---|
|  | 190 | opt.temp = ConvertUnits(have, want) | 
|---|
| [0c83d8] | 191 | else: | 
|---|
| [0ad49cc] | 192 | if not opt.automass: | 
|---|
|  | 193 | opt.molarmass = float(opt.molarmass) | 
|---|
|  | 194 |  | 
|---|
| [0c83d8] | 195 | opt.density = float(opt.density) | 
|---|
| [0ad49cc] | 196 |  | 
|---|
|  | 197 | if opt.temp: | 
|---|
|  | 198 | opt.temp = float(opt.temp) | 
|---|
| [0c83d8] | 199 |  | 
|---|
|  | 200 | # Number might be an integer or a 3-vector | 
|---|
|  | 201 | nvec = opt.number.split() | 
|---|
|  | 202 | if len(nvec) == 3: | 
|---|
|  | 203 | opt.number = [0]*3 | 
|---|
|  | 204 |  | 
|---|
| [5735ba] | 205 | for i in range(3): | 
|---|
| [0c83d8] | 206 | opt.number[i] = int(nvec[i]) | 
|---|
| [5735ba] | 207 | else: | 
|---|
| [0c83d8] | 208 | opt.number = int(opt.number) | 
|---|
| [0ad49cc] | 209 |  | 
|---|
| [f894fe] | 210 | InitialiseSource(opt) | 
|---|
| [c0c85f] | 211 |  | 
|---|
| [0ad49cc] | 212 | # Automatic source mass | 
|---|
|  | 213 | if opt.automass: | 
|---|
| [f894fe] | 214 | opt.molarmass = GetSourceMolareMass(opt, units) | 
|---|
| [0ad49cc] | 215 | print '======== MOLAR MASS:', opt.molarmass | 
|---|
| [0c83d8] | 216 |  | 
|---|
|  | 217 |  | 
|---|
|  | 218 | def FindBestCube(opt): | 
|---|
|  | 219 | newroot = int( round(opt.number**(1./3)) ) | 
|---|
|  | 220 | newnumber = newroot**3 | 
|---|
|  | 221 |  | 
|---|
|  | 222 | if newnumber != opt.number: | 
|---|
|  | 223 | print 'Warning: Number changed to %d.' % newnumber | 
|---|
|  | 224 |  | 
|---|
|  | 225 | return [newroot] * 3 | 
|---|
|  | 226 |  | 
|---|
|  | 227 |  | 
|---|
|  | 228 | def FindBestCuboid(opt): | 
|---|
|  | 229 | n = opt.number | 
|---|
|  | 230 |  | 
|---|
|  | 231 | # Prime factors of n | 
|---|
|  | 232 | factors = [] | 
|---|
|  | 233 |  | 
|---|
|  | 234 | for i in [2, 3]: | 
|---|
|  | 235 | while n % i == 0: | 
|---|
|  | 236 | factors.append(i) | 
|---|
|  | 237 | n /= 2 | 
|---|
|  | 238 |  | 
|---|
| [5735ba] | 239 | t = 5 | 
|---|
|  | 240 | diff = 2 | 
|---|
| [0c83d8] | 241 |  | 
|---|
| [5735ba] | 242 | while t*t <= n: | 
|---|
| [0c83d8] | 243 | while n % t == 0: | 
|---|
|  | 244 | factors.append(t) | 
|---|
|  | 245 | n /= t | 
|---|
|  | 246 |  | 
|---|
| [5735ba] | 247 | t = t + diff | 
|---|
|  | 248 | diff = 6 - diff | 
|---|
| [0c83d8] | 249 |  | 
|---|
| [5735ba] | 250 | if n > 1: | 
|---|
| [0c83d8] | 251 | factors.append(n) | 
|---|
|  | 252 |  | 
|---|
|  | 253 | # Even distribution of current biggest prime to each vector -> similar sizes | 
|---|
|  | 254 | if len(factors) < 3: | 
|---|
|  | 255 | print 'Warning: Not enough prime factors - falling back to cubic placement' | 
|---|
|  | 256 | return FindBestCube(opt) | 
|---|
|  | 257 |  | 
|---|
|  | 258 | factors.sort() | 
|---|
| [5735ba] | 259 | distri = [[],[],[]] | 
|---|
|  | 260 | current = 0 | 
|---|
| [0c83d8] | 261 |  | 
|---|
|  | 262 | for factor in factors: | 
|---|
|  | 263 | distri[current].append(factor) | 
|---|
| [5735ba] | 264 | current += 1 | 
|---|
|  | 265 | if current == 3: | 
|---|
|  | 266 | current = 0 | 
|---|
| [0c83d8] | 267 |  | 
|---|
|  | 268 | result = [0]*3 | 
|---|
|  | 269 |  | 
|---|
|  | 270 | print '======== CUBOID USED:', | 
|---|
|  | 271 |  | 
|---|
| [5735ba] | 272 | for i in range(3): | 
|---|
| [0c83d8] | 273 | result[i] = int( reduce(operator.mul, distri[i]) ) | 
|---|
|  | 274 |  | 
|---|
|  | 275 | print result | 
|---|
| [5735ba] | 276 | return result | 
|---|
| [0c83d8] | 277 |  | 
|---|
|  | 278 |  | 
|---|
|  | 279 | def GetSourceBBabs(opt): | 
|---|
| [f894fe] | 280 | mol.WorldCenterOnEdge() | 
|---|
|  | 281 | bb = mol.AnalysisCalculateBoundingBox() | 
|---|
|  | 282 | bbmin = [bb[0], bb[2], bb[4]] | 
|---|
|  | 283 | bbmax = [bb[1], bb[3], bb[5]] | 
|---|
| [5735ba] | 284 |  | 
|---|
|  | 285 | bb = [0.0]*3 | 
|---|
| [0c83d8] | 286 |  | 
|---|
| [5735ba] | 287 | for i in range(3): | 
|---|
| [0c83d8] | 288 | bb[i] = abs(bbmax[i] - bbmin[i]) | 
|---|
|  | 289 |  | 
|---|
| [5735ba] | 290 | return bb | 
|---|
| [c0c85f] | 291 |  | 
|---|
|  | 292 |  | 
|---|
| [f894fe] | 293 | def InitialiseSource(opt): | 
|---|
| [c0c85f] | 294 | potfilepath = opt.potentialsfiledir + opt.basename + '.potentials' | 
|---|
| [2aa9ef] | 295 | mol.ParserParseTremoloPotentials(potfilepath) | 
|---|
|  | 296 | mol.MoleculeLoad(opt.source) | 
|---|
| [c0c85f] | 297 |  | 
|---|
|  | 298 | if opt.autorotate: | 
|---|
| [2aa9ef] | 299 | mol.SelectAllAtoms() | 
|---|
|  | 300 | mol.RotateToPrincipalAxisSystem("0 1 0") | 
|---|
| [5735ba] | 301 |  | 
|---|
| [0c83d8] | 302 | # Global options with sensible default parameters | 
|---|
|  | 303 | opt = c_opt() | 
|---|
| [5735ba] | 304 |  | 
|---|
| [0c83d8] | 305 | ReadSettings(opt) | 
|---|
| [c0c85f] | 306 | UpdateSettingsAndSource(opt) | 
|---|
| [5735ba] | 307 |  | 
|---|
| [0c83d8] | 308 | if type(opt.number) == type([]): | 
|---|
|  | 309 | # Number is a vector - use it without any modification | 
|---|
|  | 310 | nbox = opt.number | 
|---|
| [5735ba] | 311 | else: | 
|---|
| [0c83d8] | 312 | if opt.cubicdomain: | 
|---|
|  | 313 | nbox = FindBestCube(opt) | 
|---|
| [5735ba] | 314 | else: | 
|---|
| [0c83d8] | 315 | nbox = FindBestCuboid(opt) | 
|---|
| [5735ba] | 316 |  | 
|---|
| [0c83d8] | 317 | VolumePerMolecule = opt.molarmass / (avogadro * opt.density) | 
|---|
|  | 318 | cell = [VolumePerMolecule**(1./3)] * 3 | 
|---|
| [5735ba] | 319 |  | 
|---|
| [0c83d8] | 320 | if not opt.cubiccell: | 
|---|
|  | 321 | try: | 
|---|
|  | 322 | bb = GetSourceBBabs(opt) | 
|---|
|  | 323 | print '======== BBOX:', bb | 
|---|
|  | 324 | # Scaling factor - the molecules bounding box is scaled to fit the volume suiting the density | 
|---|
|  | 325 | s = (VolumePerMolecule / (bb[0]*bb[1]*bb[2])) ** (1./3) | 
|---|
|  | 326 |  | 
|---|
|  | 327 | if s < 1: | 
|---|
|  | 328 | print 'Warning: Molecular cells will overlap.' | 
|---|
|  | 329 |  | 
|---|
|  | 330 | for i in range(3): | 
|---|
|  | 331 | cell[i] = bb[i]*s | 
|---|
|  | 332 | except ZeroDivisionError: | 
|---|
|  | 333 | print 'Warning:  Singularity in bounding box, falling back to cubic cell.' | 
|---|
| [5735ba] | 334 |  | 
|---|
| [0c83d8] | 335 |  | 
|---|
| [5735ba] | 336 | print '======== CELL: ', cell | 
|---|
|  | 337 |  | 
|---|
|  | 338 | mol.CommandVerbose('0') | 
|---|
|  | 339 | mol.WorldCenterInBox('%f 0 0 %f 0 %f' % tuple(cell)) | 
|---|
|  | 340 | mol.WorldRepeatBox('%d %d %d' % tuple(nbox)) | 
|---|
| [0c83d8] | 341 | mol.WorldOutput(opt.outfilename + '.data') | 
|---|
|  | 342 | mol.WorldOutput(opt.outfilename + '.xyz') | 
|---|
| [5735ba] | 343 |  | 
|---|
|  | 344 | domain = [0.0]*3 | 
|---|
|  | 345 |  | 
|---|
|  | 346 | for i in range(3): | 
|---|
|  | 347 | domain[i] = cell[i]*nbox[i] | 
|---|
| [0c83d8] | 348 |  | 
|---|
| [5735ba] | 349 | print  '======== DOMAIN: ', domain | 
|---|
| [32bc47] | 350 |  | 
|---|
|  | 351 | # Postprocessing | 
|---|
|  | 352 |  | 
|---|
|  | 353 | if opt.postprocess: | 
|---|
|  | 354 | with open(opt.outfilename + '.data') as f: | 
|---|
|  | 355 | ofile = f.read() | 
|---|
|  | 356 |  | 
|---|
|  | 357 | with open(opt.outfilename + '.data', 'w') as f: | 
|---|
|  | 358 | f.write('# INPUTCONV shift center\n') | 
|---|
|  | 359 |  | 
|---|
|  | 360 | if opt.temp: | 
|---|
|  | 361 | f.write('# INPUTCONV temp %.4f\n' % opt.temp) | 
|---|
|  | 362 |  | 
|---|
|  | 363 | f.write(ofile) | 
|---|
|  | 364 |  | 
|---|
| [c0c85f] | 365 | os.system('rm temp_source.data temp_source.xyz') | 
|---|