import os, sys import shutil import textwrap from DPpack.PTable import * from DPpack.Misc import * #### Global hashes that control the behaviour of Diceplayer player = {} dice = {} gaussian = {} molcas = {} internal = {} ####################################################################### #### Global parameters that MAY be given by the user #### #### (If not given by the user, default values will be used) #### ####################################################################### ## Diceplayer: player['maxcyc'] = 1 player['initcyc'] = 1 # May restart an optimization (append to geoms.xyz from start) player['nprocs'] = 1 player['switchcyc'] = 3 # At which step start doing only one QM calculation (geom & chg) player['altsteps'] = 20000 # Steps for thermalization when starting from previous step player['maxstep'] = 0.3 # Maxstep for geometry relaxation in Bohr player['qmprog'] = "g09" player['opt'] = "yes" player['freq'] = "no" player['readhessian'] = "no" player['lps'] = "no" player['ghosts'] = "no" player['vdwforces'] = "no" player['tol_factor'] = 1.2 # Factor to multiply the default tolerance values ## Dice: dice['title'] = "Diceplayer run" dice['progname'] = "dice" dice['temp'] = 300.0 dice['press'] = 1.0 dice['isave'] = 1000 # ASEC construction will take this into account dice['ncores'] = 1 ## Gaussian: gaussian['mem'] = None gaussian['keywords'] = None gaussian['chgmult'] = [0, 1] gaussian['gmiddle'] = None # In each case, if a filename is given, its content will be gaussian['gbottom'] = None # inserted in the gaussian input gaussian['pop'] = "chelpg" gaussian['chglevel'] = None ## Molcas: molcas['orbfile'] = "input.exporb" molcas['root'] = 1 ######################################################################## #### Global parameters that MUST be given by the user #### ######################################################################## ## Dice: dice['dens'] = None # Investigate the possibility of using 'box = Lx Ly Lz' instead. #dice['box'] = None # So 'geom' would be set by diceplayer and 'cutoff' would be # switched off. One of them must be given. dice['ljname'] = None dice['outname'] = None dice['nmol'] = [] # Up to 4 integer values related to up to 4 molecule types dice['nstep'] = [] # 2 or 3 integer values related to 2 or 3 simulations # (NVT th + NVT eq) or (NVT th + NPT th + NPT eq). # This will control the 'nstep' keyword of Dice ## Gaussian: gaussian['level'] = None ## Molcas: molcas['mbottom'] = None molcas['basis'] = None ## The following Dice keywords will be handled automatically by Diceplayer: ## * init ("yes" in the first thermalization and "yesreadxyz" for thermalizations ## starting from a previous step / "no" in subsequent simulations) ## * vstep ("0" for NVT simulations and 'nstep'/5 for NPT simulations) ## * nstep ('nstep' for NVT and "5" for NPT simulations ) ## * irdf ("0" for thermalizations and "10*nprocs" for equilibrium) ## * seed (will be generated randomly each time a Dice input is created) ## The following Dice keywords will be set constant by Diceplayer for all simulations ## * mstop = 1 (So to guarantee that the ASEC will be correctly built) ## * accum = no (There is never a simulation continuation in Diceplayer) ## * iprint = 1 (Print energy info every step in Dice output) ## All the other Dice keywords will not be altered from their default values ## and therefore are not mentioned in Diceplayer ##################################################################### #### Global parameters that are not accessible to the user #### #### (Intended to be used only internally by the program) #### ##################################################################### ## Diceplayer: internal['tol_rms_force'] = 3e-4 # Hartree/Bohr internal['tol_max_force'] = 4.5e-4 # Hartree/Bohr internal['tol_rms_step'] = 1.2e-3 # Bohr internal['tol_max_step'] = 1.8e-3 # Bohr internal['trust_radius'] = None ## Dice: internal['combrule'] = None internal['randominit'] = None ## Other global variables: molecules = [] # Armazena todas as informacoes sobre cada tipo de molecula # (lbl, na, rx, ry, rz, chg, eps, sig, mass) internal['ghost_types'] = [] internal['ghost_atoms'] = [] # Store the ghost atoms (off-atom charge sites) in the QM molecule # (rx, ry, rz, chg) internal['lp_types'] = [] internal['lp_atoms'] = [] # Store the lone pairs (special off-atom charge sites) in the QM molecule # (rx, ry, rz, chg) ## Numpy arrays: step = [] ## Values in Bohr internal['position'] = [] internal['energy'] = [] ## Values in Hartree internal['gradient'] = [] ## Values in Hartree/Bohr internal['hessian'] = [] ## Values in Hartree/Bohr^2 ## Conversion factors: bohr2ang = 0.52917721092 ang2bohr = 1/bohr2ang ###################################################################### #### Environment variables important for executing Diceplayer #### ###################################################################### env = ["OMP_STACKSIZE"] ####################################### functions ###################################### ## Functions to process the input files and store the values in the global variables ## ########################################################################################## # def read_keywords(infile): # try: # with open(infile) as fh: # controlfile = fh.readlines() # except EnvironmentError: # sys.exit("Error: cannot open file {}".format(infile)) # for line in controlfile: # key, value = line.partition("=")[::2] # Discards the '=' # key = key.strip().lower() # if key in ('title', 'keywords'): # value = value.strip() # else: # value = value.split() # #### Read the Diceplayer related keywords # if key in player and len(value) != 0: ## 'value' is not empty! # if key == 'qmprog' and value[0].lower() in ("g03", "g09", "g16", "molcas"): # player[key] = value[0].lower() # elif key == 'opt' and value[0].lower() in ("yes", "no", "ts"): # player[key] = value[0].lower() # #elif key == 'zipprog' and value[0].lower() in ("zip", "gzip", "bzip"): # #player[key] = value[0].lower() # elif key in ('lps', 'ghosts') and value[0].lower() in ("yes", "no"): # player[key] = value[0].lower() # elif key in ('readhessian', 'vdwforces') and value[0].lower() in ("yes", "no"): # player[key] = value[0].lower() # elif key in ('maxcyc', 'initcyc', 'nprocs', 'altsteps', 'switchcyc'): # err = "Error: expected a positive integer for keyword {} in file {}".format(key, infile) # try: # new_value = int(value[0]) # if new_value >= 1: # player[key] = new_value # elif key == 'altsteps' and new_value == 0: # player[key] = 0 # except ValueError: # sys.exit(err) # elif key == 'maxstep': # Cannot be less than 0.01 # err = "Error: expected a float greater than 0.01 for keyword {} in file {}".format(key, infile) # try: # new_value = float(value[0]) # if new_value < 0.01: # sys.exit(err) # else: # player[key] = new_value # except ValueError: # sys.exit(err) # #### Read the Dice related keywords # elif key in dice and len(value) != 0: ## 'value' is not empty! # if key == 'title': # dice[key] = value # elif key in ('ljname', 'outname', 'progname'): # dice[key] = value[0] # elif key in ('ncores', 'isave'): # err = "Error: expected a positive integer for keyword {} in file {}".format(key, infile) # if not value[0].isdigit(): # sys.exit(err) # new_value = int(value[0]) # if new_value >= 1: # dice[key] = new_value # elif key in ('temp', 'press', 'dens'): # Cannot be less than 1e-10 # err = "Error: expected a positive float for keyword {} in file {}".format(key, infile) # try: # new_value = float(value[0]) # if new_value < 1e-10: # sys.exit(err) # else: # dice[key] = new_value # except ValueError: # sys.exit(err) # elif key == 'nmol': # If defined, must be well defined (only positive integer values) # err = "Error: expected 1 to 4 positive integers for keyword {} in file {}".format(key, infile) # args = min(4, len(value)) # for i in range(args): # if value[i].isdigit(): # new_value = int(value[i]) # if new_value < 1: # sys.exit(err) # else: # dice[key].append(new_value) # elif i == 0: # sys.exit(err) # else: # break # elif key == 'nstep': # If defined, must be well defined (only positive integer values) # err = "Error: expected 2 or 3 positive integers for keyword {} in file {}".format(key, infile) # if len(value) < 2: # sys.exit(err) # args = min(3, len(value)) # for i in range(args): # if value[i].isdigit(): # new_value = int(value[i]) # if new_value < 1: # sys.exit(err) # else: # dice[key].append(new_value) # elif i < 2: # sys.exit(err) # else: # break # #### Read the Gaussian related keywords # elif key in gaussian and len(value) != 0: ## 'value' is not empty! # if key == 'mem': # Memory in MB (minimum of 100) # err = "Error: expected a positive integer for keyword {} in file {}".format(key, infile) # if not value[0].isdigit(): # sys.exit(err) # new_value = int(value[0]) # if new_value >= 100: # gaussian[key] = new_value # elif key == 'keywords': # gaussian[key] = value # elif key == 'chgmult': # If defined, must be well defined (2 integer values) # err = "Error: expected 2 integers for keyword {} in file {}".format(key, infile) # if len(value) < 2: # sys.exit(err) # for i in range (2): # try: # gaussian[key][i] = int(value[i]) # except ValueError: # sys.exit(err) # elif key in ('level', 'chglevel'): # gaussian[key] = value[0] # elif key in ('gmiddle', 'gbottom'): # gaussian[key] = value[0] # elif key == 'pop' and value[0].lower() in ("chelpg", "mk", "nbo"): # gaussian[key] = value[0].lower() # #### Read the Molcas related keywords # elif key in molcas and len(value) != 0: ## 'value' is not empty! # if key == 'root': # If defined, must be well defined (only positive integer values) # err = "Error: expected a positive integer for keyword {} in file {}".format(key, infile) # if not value[0].isdigit(): # sys.exit(err) # new_value = int(value[0]) # if new_value >= 1: # molcas[key] = new_value # elif key in ('mbottom', 'orbfile'): # molcas[key] = value[0] # elif key == 'basis': # molcas[key] = value[0] # #### End # return # def check_keywords(infile): # min_steps = 20000 # if dice['ljname'] == None: # sys.exit("Error: 'ljname' keyword not specified in file {}".format(infile)) # if dice['outname'] == None: # sys.exit("Error: 'outname' keyword not specified in file {}".format(infile)) # if dice['dens'] == None: # sys.exit("Error: 'dens' keyword not specified in file {}".format(infile)) # if len(dice['nmol']) == 0: # sys.exit("Error: 'nmol' keyword not defined appropriately in file {}".format(infile)) # if len(dice['nstep']) == 0: # sys.exit("Error: 'nstep' keyword not defined appropriately in file {}".format(infile)) # ## Check only if QM program is Gaussian: # if player['qmprog'] in ("g03", "g09", "g16"): # if gaussian['level'] == None: # sys.exit("Error: 'level' keyword not specified in file {}".format(infile)) # if gaussian['gmiddle'] != None: # if not os.path.isfile(gaussian['gmiddle']): # sys.exit("Error: file {} not found".format(gaussian['gmiddle'])) # if gaussian['gbottom'] != None: # if not os.path.isfile(gaussian['gbottom']): # sys.exit("Error: file {} not found".format(gaussian['gbottom'])) # if gaussian['pop'] != "chelpg" and (player['ghosts'] == "yes" or player['lps'] == "yes"): # sys.exit("Error: ghost atoms or lone pairs only available with 'pop = chelpg')") # if gaussian['chglevel'] == None: # gaussian['chglevel'] = gaussian['level'] # ## Check only if QM program is Molcas: # if player['qmprog'] == "molcas": # if molcas['mbottom'] == None: # sys.exit("Error: 'mbottom' keyword not specified in file {}".format(infile)) # else: # if not os.path.isfile(molcas['mbottom']): # sys.exit("Error: file {} not found".format(molcas['mbottom'])) # if molcas['basis'] == None: # sys.exit("Error: 'basis' keyword not specified in file {}".format(infile)) # if player['altsteps'] != 0: # ### Verifica se tem mais de 1 molecula QM # ### (No futuro usar o RMSD fit para poder substituir todas as moleculas QM # ### no arquivo outname.xy - Need to change the make_init_file!!) # if dice['nmol'][0] > 1: # sys.exit("Error: altsteps > 0 only possible with 1 QM molecule (nmol = 1 n2 n3 n4)") # # if not zero, altsteps cannot be less than min_steps # player['altsteps'] = max(min_steps, player['altsteps']) # # altsteps value is always the nearest multiple of 1000 # player['altsteps'] = round(player['altsteps'] / 1000) * 1000 # for i in range(len(dice['nstep'])): # # nstep can never be less than min_steps # dice['nstep'][i] = max(min_steps, dice['nstep'][i]) # # nstep values are always the nearest multiple of 1000 # dice['nstep'][i] = round(dice['nstep'][i] / 1000) * 1000 # # isave must be between 100 and 2000 # dice['isave'] = max(100, dice['isave']) # dice['isave'] = min(2000, dice['isave']) # # isave value is always the nearest multiple of 100 # dice['isave'] = round(dice['isave'] / 100) * 100 # return # def print_keywords(fh): # fh.write("##########################################################################################\n" # "############# Welcome to DICEPLAYER version 1.0 #############\n" # "##########################################################################################\n" # "\n") # fh.write("Your python version is {}\n".format(sys.version)) # fh.write("\n") # fh.write("Program started on {}\n".format(weekday_date_time())) # fh.write("\n") # fh.write("Environment variables:\n") # for var in env: # fh.write("{} = {}\n".format(var, # (os.environ[var] if var in os.environ else "Not set"))) # fh.write("\n==========================================================================================\n" # " CONTROL variables being used in this run:\n" # "------------------------------------------------------------------------------------------\n" # "\n") # for key in sorted(player): # if player[key] != None: # if isinstance(player[key], list): # string = " ".join(str(x) for x in player[key]) # fh.write("{} = {}\n".format(key, string)) # else: # fh.write("{} = {}\n".format(key, player[key])) # fh.write("\n") # fh.write("------------------------------------------------------------------------------------------\n" # " DICE variables being used in this run:\n" # "------------------------------------------------------------------------------------------\n" # "\n") # for key in sorted(dice): # if dice[key] != None: # if isinstance(dice[key], list): # string = " ".join(str(x) for x in dice[key]) # fh.write("{} = {}\n".format(key, string)) # else: # fh.write("{} = {}\n".format(key, dice[key])) # fh.write("\n") # if player['qmprog'] in ("g03", "g09", "g16"): # fh.write("------------------------------------------------------------------------------------------\n" # " GAUSSIAN variables being used in this run:\n" # "------------------------------------------------------------------------------------------\n" # "\n") # for key in sorted(gaussian): # if gaussian[key] != None: # if isinstance(gaussian[key], list): # string = " ".join(str(x) for x in gaussian[key]) # fh.write("{} = {}\n".format(key, string)) # else: # fh.write("{} = {}\n".format(key, gaussian[key])) # fh.write("\n") # elif player['qmprog'] == "molcas": # fh.write("------------------------------------------------------------------------------------------\n" # " MOLCAS variables being used in this run:\n" # "------------------------------------------------------------------------------------------\n" # "\n") # for key in sorted(molcas): # if molcas[key] != None: # if isinstance(molcas[key], list): # string = " ".join(str(x) for x in molcas[key]) # fh.write("{} = {}\n".format(key, string)) # else: # fh.write("{} = {}\n".format(key, molcas[key])) # fh.write("\n") # return # def read_potential(infile): # Deve ser atualizado para o uso de # try: # with open(dice['ljname']) as file: # ljfile = file.readlines() # except EnvironmentError as err: # sys.exit(err) # combrule = ljfile.pop(0).split()[0] # if combrule not in ("*", "+"): # sys.exit("Error: expected a '*' or a '+' sign in 1st line of file {}".format(dice['ljname'])) # dice['combrule'] = combrule # ntypes = ljfile.pop(0).split()[0] # if not ntypes.isdigit(): # sys.exit("Error: expected an integer in the 2nd line of file {}".format(dice['ljname'])) # ntypes = int(ntypes) # if ntypes != len(dice['nmol']): # sys.exit("Error: number of molecule types in file {} must match that of 'nmol' keyword in file {}".format( # dice['ljname'], infile)) # line = 2 # for i in range(ntypes): # line += 1 # nsites = ljfile.pop(0).split()[0] # if not nsites.isdigit(): # sys.exit("Error: expected an integer in line {} of file {}".format(line, dice['ljname'])) # nsites = int(nsites) # molecules.append([]) # for j in range(nsites): # line += 1 # new_atom = ljfile.pop(0).split() # if len(new_atom) < 8: # sys.exit("Error: expected at least 8 fields in line {} of file {}".format(line, dice['ljname'])) # molecules[i].append({}) # if not new_atom[0].isdigit(): # sys.exit("Error: expected an integer in field 1, line {} of file {}".format(line, dice['ljname'])) # molecules[i][j]['lbl'] = int(new_atom[0]) # if not new_atom[1].isdigit(): # sys.exit("Error: expected an integer in field 2, line {} of file {}".format(line, dice['ljname'])) # atnumber = int(new_atom[1]) # if atnumber == ghost_number and i == 0: # Ghost atom not allowed in the QM molecule # sys.exit("Error: found a ghost atom in line {} of file {}".format(line, dice['ljname'])) # molecules[i][j]['na'] = atnumber # try: # molecules[i][j]['rx'] = float(new_atom[2]) # except: # sys.exit("Error: expected a float in field 3, line {} of file {}".format(line, dice['ljname'])) # try: # molecules[i][j]['ry'] = float(new_atom[3]) # except: # sys.exit("Error: expected a float in field 4, line {} of file {}".format(line, dice['ljname'])) # try: # molecules[i][j]['rz'] = float(new_atom[4]) # except: # sys.exit("Error: expected a float in field 5, line {} of file {}".format(line, dice['ljname'])) # try: # molecules[i][j]['chg'] = float(new_atom[5]) # except: # sys.exit("Error: expected a float in field 6, line {} of file {}".format(line, dice['ljname'])) # try: # molecules[i][j]['eps'] = float(new_atom[6]) # except: # sys.exit("Error: expected a float in field 7, line {} of file {}".format(line, dice['ljname'])) # try: # molecules[i][j]['sig'] = float(new_atom[7]) # except: # sys.exit("Error: expected a float in field 8, line {} of file {}".format(line, dice['ljname'])) # molecules[i][j]['mass'] = atommass[molecules[i][j]['na']] # if len(new_atom) > 8: # masskey, mass = new_atom[8].partition("=")[::2] # if masskey.lower() == 'mass' and len(mass) !=0: # try: # new_mass = float(mass) # if new_mass > 0: # molecules[i][j]['mass'] = new_mass # except: # sys.exit( # "Error: expected a positive float after 'mass=' in field 9, line {} of file {}".format( # line, dice['ljname'])) # return def read_ghosts(): max_atom_number = len(molecules[0]) try: with open("ghosts.in") as fh: ghostfile = fh.readlines() except EnvironmentError: sys.exit("Error: cannot open file ghosts.in") for line in ghostfile: if len(line.split()) > 1: # Discard lines with less than 2 fields key, *atom_numbers = line.split() key = key.lower() if key in ("g", "m", "z"): # Discard lines that do not start with g|m|z ghost_types.append({}) ghost_types[-1]['type'] = key ghost_types[-1]['numbers'] = [] for num in atom_numbers: if not num.isdigit(): sys.exit("Error: in file ghosts.in: only positive integers allowed after letter g|m|z") new_num = int(num) if new_num > max_atom_number: sys.exit("Error: in file ghosts.in: there is no atom number {}".format(new_num)) else: ghost_types[-1]['numbers'].append(new_num) if len(ghost_types[-1]['numbers']) < 2: sys.exit("Error: in file ghosts.in: at least 2 atoms are necessary to make a ghost") if len(ghost_types) == 0: sys.exit("Error: no ghost atom found in ghosts.in") return def read_lps(): lp_alpha = 104.0 # Default values lp_dist = 0.7 # max_lp_type = 2 min_alpha = 90.0 max_alpha = 150.0 min_dist = 0.5 max_dist = 1.5 max_atom_number = len(molecules[0]) try: with open("lps.in") as fh: lpfile = fh.readlines() except EnvironmentError: sys.exit("Error: cannot open file lps.in") for line in lpfile: if len(line.split()) > 1: # Discard lines with less than 2 fields type, *atom_numbers = line.split() if type.isdigit(): # Discard lines that do not start with an integer new_type = int(type) if new_type > max_lp_type: sys.exit("Error: in file lps.in: allowed LP types from 1 to {}".format(max_lp_type)) lp_types.append({}) lp_types[-1]['type'] = new_type lp_types[-1]['numbers'] = [] # Read types 1 and 2 if new_type in (1, 2): if len(atom_numbers) < 3: sys.exit("Error: in file lps.in: at least 3 atoms are necessary to make LPs type 1 and 2") for i in range(3): num = atom_numbers.pop(0) if not num.isdigit(): sys.exit("Error: in file lps.in: expected 3 atom numbers after LPs type 1 and 2") new_num = int(num) if new_num > max_atom_number or new_num < 1: sys.exit("Error: in file lps.in: there is no atom number {}".format(new_num)) else: lp_types[-1]['numbers'].append(new_num) lp_types[-1]['alpha'] = lp_alpha lp_types[-1]['dist'] = lp_dist if len(atom_numbers) != 0: try: alpha = float(atom_numbers.pop(0)) if alpha > min_alpha and alpha < max_alpha: lp_types[-1]['alpha'] = alpha else: atom_numbers = [] except: atom_numbers = [] if len(atom_numbers) != 0: try: dist = float(atom_numbers.pop(0)) if dist > min_dist and dist < max_dist: lp_types[-1]['dist'] = dist except: None # End of types 1 and 2 if len(lp_types) == 0: sys.exit("Error: no lone pair found in lps.in") return # def print_potential(fh): # formatstr = "{:<3d} {:>3d} {:>10.5f} {:>10.5f} {:>10.5f} {:>10.6f} {:>9.5f} {:>7.4f} {:>9.4f}\n" # fh.write("\n" # "==========================================================================================\n") # fh.write(" Potential parameters from file {}:\n".format(dice['ljname'])) # fh.write("------------------------------------------------------------------------------------------\n" # "\n") # fh.write("Combination rule: {}\n".format(dice['combrule'])) # fh.write("Types of molecules: {}\n\n".format(len(molecules))) # i = 0 # for mol in molecules: # i += 1 # fh.write("{} atoms in molecule type {}:\n".format(len(mol), i)) # fh.write("---------------------------------------------------------------------------------\n" # "Lbl AN X Y Z Charge Epsilon Sigma Mass\n") # fh.write("---------------------------------------------------------------------------------\n") # for atom in mol: # fh.write(formatstr.format(atom['lbl'], atom['na'], atom['rx'], atom['ry'], atom['rz'], # atom['chg'], atom['eps'], atom['sig'], atom['mass'])) # fh.write("\n") # if player['ghosts'] == "yes" or player['lps'] == "yes": # fh.write("\n" # "------------------------------------------------------------------------------------------\n" # " Aditional potential parameters:\n" # "------------------------------------------------------------------------------------------\n") # if player['ghosts'] == "yes": # fh.write("\n") # fh.write("{} ghost atoms appended to molecule type 1 at:\n".format(len(ghost_types))) # fh.write("---------------------------------------------------------------------------------\n") # atoms_string = "" # for ghost in ghost_types: # for atom in ghost['numbers']: # atom_sym = atomsymb[ molecules[0][atom - 1]['na'] ].strip() # atoms_string += "{}{} ".format(atom_sym,atom) # if ghost['type'] == "g": # fh.write(textwrap.fill("* Geometric center of atoms {}".format(atoms_string), 80)) # elif ghost['type'] == "m": # fh.write(textwrap.fill("* Center of mass of atoms {}".format(atoms_string), 80)) # elif ghost['type'] == "z": # fh.write(textwrap.fill("* Center of atomic number of atoms {}".format(atoms_string), 80)) # fh.write("\n") # if player['lps'] == 'yes': # fh.write("\n") # fh.write("{} lone pairs appended to molecule type 1:\n".format(len(lp_types))) # fh.write("---------------------------------------------------------------------------------\n") # for lp in lp_types: # # LP type 1 or 2 # if lp['type'] in (1, 2): # atom1_num = lp['numbers'][0] # atom1_sym = atomsymb[ molecules[0][atom1_num - 1]['na'] ].strip() # atom2_num = lp['numbers'][1] # atom2_sym = atomsymb[ molecules[0][atom2_num - 1]['na'] ].strip() # atom3_num = lp['numbers'][2] # atom3_sym = atomsymb[ molecules[0][atom3_num - 1]['na'] ].strip() # fh.write(textwrap.fill( # "* Type {} on atom {}{} with {}{} {}{}. Alpha = {:<5.1f} Deg and D = {:<4.2f} Angs".format( # lp['type'], atom1_sym, atom1_num, atom2_sym, atom2_num, atom3_sym, atom3_num, lp['alpha'], # lp['dist']), 86)) # fh.write("\n") # # Other LP types # fh.write("\n" # "==========================================================================================\n") # return ## Creation of continue_function # def check_executables(fh): # fh.write("\n") # fh.write(90 * "=") # fh.write("\n\n") # dice_path = shutil.which(dice['progname']) # if dice_path != None: # fh.write("Program {} found at {}\n".format(dice['progname'], dice_path)) # else: # sys.exit("Error: cannot find dice executable") # qmprog_path = shutil.which(player['qmprog']) # if qmprog_path != None: # fh.write("Program {} found at {}\n".format(player['qmprog'], qmprog_path)) # else: # sys.exit("Error: cannot find {} executable".format(player['qmprog'])) # if player['qmprog'] in ("g03", "g09", "g16"): # formchk_path = shutil.which("formchk") # if formchk_path != None: # fh.write("Program formchk found at {}\n".format(formchk_path)) # else: # sys.exit("Error: cannot find formchk executable") # return