| [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() | 
|---|
| [415ddd] | 153 | mol.wait() | 
|---|
| [cc6e5c] | 154 | mass_sum = mol.getSelectedMolarMass() | 
|---|
| [f894fe] | 155 | have = ("%f atomicmassunit" % mass_sum) | 
|---|
|  | 156 | want = ("%f*%s" % tuple(units['mass'])) | 
|---|
|  | 157 | return ConvertUnits(have, want)*avogadro | 
|---|
| [0c83d8] | 158 |  | 
|---|
|  | 159 |  | 
|---|
| [c0c85f] | 160 | def UpdateSettingsAndSource(opt): | 
|---|
| [0c83d8] | 161 | # Map boolean values | 
|---|
| [c0c85f] | 162 | boolmap = {'on': True, 'off': False} | 
|---|
|  | 163 |  | 
|---|
| [0ad49cc] | 164 | for name in ['cubicdomain', 'cubiccell', 'autorotate', 'autodim', 'postprocess', 'automass']: | 
|---|
| [0c83d8] | 165 | value = eval('opt.' + name) | 
|---|
| [5735ba] | 166 |  | 
|---|
| [c0c85f] | 167 | if value in boolmap: | 
|---|
|  | 168 | value = boolmap[value] | 
|---|
| [0c83d8] | 169 | else: | 
|---|
|  | 170 | print 'Not a boolean value:', value | 
|---|
|  | 171 | exit() | 
|---|
|  | 172 |  | 
|---|
|  | 173 | exec('opt.' + name + '= value') | 
|---|
|  | 174 |  | 
|---|
|  | 175 | # Convert dimensions | 
|---|
|  | 176 | if opt.autodim: | 
|---|
|  | 177 | units = ReadUnits(opt) | 
|---|
|  | 178 |  | 
|---|
| [0ad49cc] | 179 | if not opt.automass: | 
|---|
|  | 180 | have = opt.molarmass | 
|---|
|  | 181 | want = '%f*%s / mol' % tuple(units['mass']) | 
|---|
|  | 182 | opt.molarmass = ConvertUnits(have, want) | 
|---|
| [0c83d8] | 183 |  | 
|---|
|  | 184 | have = opt.density | 
|---|
| [39cbae] | 185 | want = '(%f*%s) ' % tuple(units['mass']) + '/ (%f*%s)**3' % tuple(units['length']) | 
|---|
| [0c83d8] | 186 | opt.density = ConvertUnits(have, want) | 
|---|
| [39cbae] | 187 |  | 
|---|
|  | 188 | if opt.temp: | 
|---|
|  | 189 | have = opt.temp | 
|---|
|  | 190 | want = '%f*%s' % tuple(units['temperature']) | 
|---|
|  | 191 | opt.temp = ConvertUnits(have, want) | 
|---|
| [0c83d8] | 192 | else: | 
|---|
| [0ad49cc] | 193 | if not opt.automass: | 
|---|
|  | 194 | opt.molarmass = float(opt.molarmass) | 
|---|
|  | 195 |  | 
|---|
| [0c83d8] | 196 | opt.density = float(opt.density) | 
|---|
| [0ad49cc] | 197 |  | 
|---|
|  | 198 | if opt.temp: | 
|---|
|  | 199 | opt.temp = float(opt.temp) | 
|---|
| [0c83d8] | 200 |  | 
|---|
|  | 201 | # Number might be an integer or a 3-vector | 
|---|
|  | 202 | nvec = opt.number.split() | 
|---|
|  | 203 | if len(nvec) == 3: | 
|---|
|  | 204 | opt.number = [0]*3 | 
|---|
|  | 205 |  | 
|---|
| [5735ba] | 206 | for i in range(3): | 
|---|
| [0c83d8] | 207 | opt.number[i] = int(nvec[i]) | 
|---|
| [5735ba] | 208 | else: | 
|---|
| [0c83d8] | 209 | opt.number = int(opt.number) | 
|---|
| [0ad49cc] | 210 |  | 
|---|
| [f894fe] | 211 | InitialiseSource(opt) | 
|---|
| [c0c85f] | 212 |  | 
|---|
| [0ad49cc] | 213 | # Automatic source mass | 
|---|
|  | 214 | if opt.automass: | 
|---|
| [f894fe] | 215 | opt.molarmass = GetSourceMolareMass(opt, units) | 
|---|
| [0ad49cc] | 216 | print '======== MOLAR MASS:', opt.molarmass | 
|---|
| [0c83d8] | 217 |  | 
|---|
|  | 218 |  | 
|---|
|  | 219 | def FindBestCube(opt): | 
|---|
|  | 220 | newroot = int( round(opt.number**(1./3)) ) | 
|---|
|  | 221 | newnumber = newroot**3 | 
|---|
|  | 222 |  | 
|---|
|  | 223 | if newnumber != opt.number: | 
|---|
|  | 224 | print 'Warning: Number changed to %d.' % newnumber | 
|---|
|  | 225 |  | 
|---|
|  | 226 | return [newroot] * 3 | 
|---|
|  | 227 |  | 
|---|
|  | 228 |  | 
|---|
|  | 229 | def FindBestCuboid(opt): | 
|---|
|  | 230 | n = opt.number | 
|---|
|  | 231 |  | 
|---|
|  | 232 | # Prime factors of n | 
|---|
|  | 233 | factors = [] | 
|---|
|  | 234 |  | 
|---|
|  | 235 | for i in [2, 3]: | 
|---|
|  | 236 | while n % i == 0: | 
|---|
|  | 237 | factors.append(i) | 
|---|
|  | 238 | n /= 2 | 
|---|
|  | 239 |  | 
|---|
| [5735ba] | 240 | t = 5 | 
|---|
|  | 241 | diff = 2 | 
|---|
| [0c83d8] | 242 |  | 
|---|
| [5735ba] | 243 | while t*t <= n: | 
|---|
| [0c83d8] | 244 | while n % t == 0: | 
|---|
|  | 245 | factors.append(t) | 
|---|
|  | 246 | n /= t | 
|---|
|  | 247 |  | 
|---|
| [5735ba] | 248 | t = t + diff | 
|---|
|  | 249 | diff = 6 - diff | 
|---|
| [0c83d8] | 250 |  | 
|---|
| [5735ba] | 251 | if n > 1: | 
|---|
| [0c83d8] | 252 | factors.append(n) | 
|---|
|  | 253 |  | 
|---|
|  | 254 | # Even distribution of current biggest prime to each vector -> similar sizes | 
|---|
|  | 255 | if len(factors) < 3: | 
|---|
|  | 256 | print 'Warning: Not enough prime factors - falling back to cubic placement' | 
|---|
|  | 257 | return FindBestCube(opt) | 
|---|
|  | 258 |  | 
|---|
|  | 259 | factors.sort() | 
|---|
| [5735ba] | 260 | distri = [[],[],[]] | 
|---|
|  | 261 | current = 0 | 
|---|
| [0c83d8] | 262 |  | 
|---|
|  | 263 | for factor in factors: | 
|---|
|  | 264 | distri[current].append(factor) | 
|---|
| [5735ba] | 265 | current += 1 | 
|---|
|  | 266 | if current == 3: | 
|---|
|  | 267 | current = 0 | 
|---|
| [0c83d8] | 268 |  | 
|---|
|  | 269 | result = [0]*3 | 
|---|
|  | 270 |  | 
|---|
|  | 271 | print '======== CUBOID USED:', | 
|---|
|  | 272 |  | 
|---|
| [5735ba] | 273 | for i in range(3): | 
|---|
| [0c83d8] | 274 | result[i] = int( reduce(operator.mul, distri[i]) ) | 
|---|
|  | 275 |  | 
|---|
|  | 276 | print result | 
|---|
| [5735ba] | 277 | return result | 
|---|
| [0c83d8] | 278 |  | 
|---|
|  | 279 |  | 
|---|
|  | 280 | def GetSourceBBabs(opt): | 
|---|
| [f894fe] | 281 | mol.WorldCenterOnEdge() | 
|---|
| [415ddd] | 282 | mol.wait() | 
|---|
| [cc6e5c] | 283 | bb = mol.getBoundingBox() | 
|---|
| [f894fe] | 284 | bbmin = [bb[0], bb[2], bb[4]] | 
|---|
|  | 285 | bbmax = [bb[1], bb[3], bb[5]] | 
|---|
| [5735ba] | 286 |  | 
|---|
|  | 287 | bb = [0.0]*3 | 
|---|
| [0c83d8] | 288 |  | 
|---|
| [5735ba] | 289 | for i in range(3): | 
|---|
| [0c83d8] | 290 | bb[i] = abs(bbmax[i] - bbmin[i]) | 
|---|
|  | 291 |  | 
|---|
| [5735ba] | 292 | return bb | 
|---|
| [c0c85f] | 293 |  | 
|---|
|  | 294 |  | 
|---|
| [f894fe] | 295 | def InitialiseSource(opt): | 
|---|
| [c0c85f] | 296 | potfilepath = opt.potentialsfiledir + opt.basename + '.potentials' | 
|---|
| [2fe4a5] | 297 | mol.PotentialParseParticleParameters(potfilepath) | 
|---|
| [2aa9ef] | 298 | mol.MoleculeLoad(opt.source) | 
|---|
| [c0c85f] | 299 |  | 
|---|
|  | 300 | if opt.autorotate: | 
|---|
| [2aa9ef] | 301 | mol.SelectAllAtoms() | 
|---|
|  | 302 | mol.RotateToPrincipalAxisSystem("0 1 0") | 
|---|
| [415ddd] | 303 | mol.wait() | 
|---|
| [5735ba] | 304 |  | 
|---|
| [0c83d8] | 305 | # Global options with sensible default parameters | 
|---|
|  | 306 | opt = c_opt() | 
|---|
| [5735ba] | 307 |  | 
|---|
| [0c83d8] | 308 | ReadSettings(opt) | 
|---|
| [c0c85f] | 309 | UpdateSettingsAndSource(opt) | 
|---|
| [5735ba] | 310 |  | 
|---|
| [0c83d8] | 311 | if type(opt.number) == type([]): | 
|---|
|  | 312 | # Number is a vector - use it without any modification | 
|---|
|  | 313 | nbox = opt.number | 
|---|
| [5735ba] | 314 | else: | 
|---|
| [0c83d8] | 315 | if opt.cubicdomain: | 
|---|
|  | 316 | nbox = FindBestCube(opt) | 
|---|
| [5735ba] | 317 | else: | 
|---|
| [0c83d8] | 318 | nbox = FindBestCuboid(opt) | 
|---|
| [5735ba] | 319 |  | 
|---|
| [0c83d8] | 320 | VolumePerMolecule = opt.molarmass / (avogadro * opt.density) | 
|---|
|  | 321 | cell = [VolumePerMolecule**(1./3)] * 3 | 
|---|
| [5735ba] | 322 |  | 
|---|
| [0c83d8] | 323 | if not opt.cubiccell: | 
|---|
|  | 324 | try: | 
|---|
|  | 325 | bb = GetSourceBBabs(opt) | 
|---|
|  | 326 | print '======== BBOX:', bb | 
|---|
|  | 327 | # Scaling factor - the molecules bounding box is scaled to fit the volume suiting the density | 
|---|
|  | 328 | s = (VolumePerMolecule / (bb[0]*bb[1]*bb[2])) ** (1./3) | 
|---|
|  | 329 |  | 
|---|
|  | 330 | if s < 1: | 
|---|
|  | 331 | print 'Warning: Molecular cells will overlap.' | 
|---|
|  | 332 |  | 
|---|
|  | 333 | for i in range(3): | 
|---|
|  | 334 | cell[i] = bb[i]*s | 
|---|
|  | 335 | except ZeroDivisionError: | 
|---|
|  | 336 | print 'Warning:  Singularity in bounding box, falling back to cubic cell.' | 
|---|
| [5735ba] | 337 |  | 
|---|
| [0c83d8] | 338 |  | 
|---|
| [5735ba] | 339 | print '======== CELL: ', cell | 
|---|
|  | 340 |  | 
|---|
|  | 341 | mol.CommandVerbose('0') | 
|---|
|  | 342 | mol.WorldCenterInBox('%f 0 0 %f 0 %f' % tuple(cell)) | 
|---|
|  | 343 | mol.WorldRepeatBox('%d %d %d' % tuple(nbox)) | 
|---|
| [072f0e] | 344 | mol.WorldOutputAs(opt.outfilename + '.data') | 
|---|
|  | 345 | mol.WorldOutputAs(opt.outfilename + '.xyz') | 
|---|
| [415ddd] | 346 | mol.wait() | 
|---|
| [5735ba] | 347 |  | 
|---|
|  | 348 | domain = [0.0]*3 | 
|---|
|  | 349 |  | 
|---|
|  | 350 | for i in range(3): | 
|---|
|  | 351 | domain[i] = cell[i]*nbox[i] | 
|---|
| [0c83d8] | 352 |  | 
|---|
| [5735ba] | 353 | print  '======== DOMAIN: ', domain | 
|---|
| [32bc47] | 354 |  | 
|---|
|  | 355 | # Postprocessing | 
|---|
|  | 356 |  | 
|---|
|  | 357 | if opt.postprocess: | 
|---|
|  | 358 | with open(opt.outfilename + '.data') as f: | 
|---|
|  | 359 | ofile = f.read() | 
|---|
|  | 360 |  | 
|---|
|  | 361 | with open(opt.outfilename + '.data', 'w') as f: | 
|---|
|  | 362 | f.write('# INPUTCONV shift center\n') | 
|---|
|  | 363 |  | 
|---|
|  | 364 | if opt.temp: | 
|---|
|  | 365 | f.write('# INPUTCONV temp %.4f\n' % opt.temp) | 
|---|
|  | 366 |  | 
|---|
|  | 367 | f.write(ofile) | 
|---|
|  | 368 |  | 
|---|
| [c0c85f] | 369 | os.system('rm temp_source.data temp_source.xyz') | 
|---|