Source code for thermo.tools.lj

import pickle
import os

__author__ = "Alexander Gabourie"
__email__ = "gabourie@stanford.edu"

###################################
# UFF
###################################

[docs]def load_UFF(): """ Loads dictionary that stores relevant LJ from UFF. Returns: dict: Dictionary with atom symbols as the key and a tuple of epsilon and sigma in units of eV and Angstroms, respectively. """ path = os.path.abspath(os.path.join(__file__, '../../data/UFF.params')) return pickle.load(open(path, 'rb'))
################################# # Lorentz-Berthelot Mixing #################################
[docs]def lb_mixing(a1, a2): """ Applies Lorentz-Berthelot mixing rules on two atoms. Args: a1 (tuple): Tuple of (epsilon, sigma) a2 (tuple): Tuple of (epsilon, sigma) """ eps = (a1[0]*a2[0])**(1./2) sigma = (a1[1] + a2[1])/2. return eps, sigma
################################# # LJ Object #################################
[docs]class LJ(object): """ Stores all atoms for a simulation with their LJ parameters. A special dictionary with atom symbols for keys and the epsilon and sigma LJ parameters for the values. This object interfaces with the UFF LJ potential parameters but can also accept arbitrary parameters. Args: symbols (str or list(str)): Optional input. A single symbol or a list of symbols to add to the initial LJ list. ignore_pairs (list(sets)): List of sets where each set has two elements. Each element is a string for the symbol of the atom to ignore in that pair. Order in set is not important. cut_scale (float): Specifies the multiplicative factor to use on the sigma parameter to define the cutoffs. Default is 2.5. """ def __init__(self, symbols=None, ignore_pairs=None, cut_scale=2.5): self.ljdict = dict() self.ignore = list() self.cutoff = dict() self.global_cutoff = None self.cut_scale = cut_scale if symbols: self.add_UFF_params(symbols) if ignore_pairs: self.ignore_pairs(ignore_pairs)
[docs] def add_UFF_params(self, symbols, replace=False): """ Adds UFF parameters to the LJ object. Will replace existing parameters if 'replace' is set to True. UFF parameters are loaded from the package. Args: symbols (str or list(str)): A single symbol or a list of symbols to add to the initial LJ list. replace (bool): Whether or not to replace existing symbols """ if type(symbols) == str: symbols = [symbols] # convert to list if one string UFF = load_UFF() for symbol in symbols: if not replace and symbol in self.ljdict: print("Warning: {} is already in LJ list and".format(symbol) +\ " will not be included.\nTo include, use " + \ "replace_UFF_params or toggle 'replace' boolean.\n") else: self.ljdict[symbol] = UFF[symbol]
[docs] def replace_UFF_params(self, symbols, add=False): """ Replaces current LJ parameters with UFF values. Will add new entries if 'add' is set to True. UFF parameters are loaded from the package. Args: symbols (str or list(str)): A single symbol or a list of symbols to add to the initial LJ list. add (bool): Whether or not to replace existing symbols """ if type(symbols) == str: symbols = [symbols] # convert to list if one string UFF = load_UFF() for symbol in symbols: if symbol in self.ljdict or add: self.ljdict[symbol] = UFF[symbol] else: print("Warning: {} is not in LJ list and".format(symbol) +\ " cannot be replaced.\nTo include, use " + \ "add_UFF_params or toggle 'add' boolean.\n")
[docs] def add_param(self, symbol, data, replace=True): """ Adds a custom parameter to the LJ object. Args: symbol (str): Symbol of atom type to add. data (tuple(float)): A two-element tuple of numbers to represent the epsilon and sigma LJ values. replace (bool): Whether or not to replace the item. """ # check params good = (tuple == type(data) and len(data) == 2 and \ all([isinstance(item, (int, float)) for item in data]) and \ type(symbol) == str) if good: if symbol in self.ljdict: if replace: self.ljdict[symbol] = data else: print("Warning: {} exists and cannot be added.\n".format(symbol)) else: self.ljdict[symbol] = data else: raise ValueError("Invalid data parameter.")
[docs] def remove_param(self, symbol): """ Removes an element from the LJ object. If item does not exist, nothing happens. Args: symbol (str): Symbol of atom type to remove. """ # remove symbol from object self.ljdict.pop(symbol, None) # remove any ignore statements with symbol remove_list = list() for i, pair in enumerate(self.ignore): if symbol in pair: remove_list.append(i) for i in sorted(remove_list, reverse=True): del self.ignore[i]
[docs] def ignore_pair(self, pair): """ Adds a pair to the list of pairs that will be ignored when output to file. Args: pair (set): A two-element set where each entry is a string of the symbol in the pair to ignore. """ self.__check_pair(pair) # check if pair exists already exists = False for curr_pair in self.ignore: if curr_pair == pair: return self.ignore.append(pair)
[docs] def ignore_pairs(self, pairs): """ Adds a list of pairs that will be ignored when output to file. Args: pairs (list(set)): A list of two-element sets where each entry of each set is a string of the symbol in the pair to ignore. """ for pair in pairs: self.ignore_pair(pair)
[docs] def acknowledge_pair(self, pair): """ Removes the pair from the ignore list and acknowledges it during the output. Args: pair (set): A two-element set where each entry is a string of the symbol in the pair to un-ignore. """ self.__check_pair(pair) # check if pair exists already exists = False for i, curr_pair in enumerate(self.ignore): if curr_pair == pair: del self.ignore[i] return raise ValueError('Pair not found.')
[docs] def acknowledge_pairs(self, pairs): """ Removes pairs from the ignore list. Args: pairs (list(set)): A list of two-elements sets where each entry in each set is a string of the symbol in the pair to un-ignore. """ for pair in pairs: self.acknowledge_pair(pair)
[docs] def custom_cutoff(self, pair, cutoff): """ Sets a custom cutoff for a specific pair of atoms. Args: pair (set): A two-element set where each entry is a string of the symbol in the pair. cutoff (float): Custom cutoff to use. In Angstroms. """ self.__check_pair(pair) self.__check_cutoff(cutoff) key = self.__get_cutkey(pair) self.cutoff[key] = cutoff
[docs] def remove_custom_cutoff(self, pair): """ Removes a custom cutoff for a pair of atoms. Args: pair (set): A two-element set where each entry is a string of the symbol in the pair. """ self.__check_pair(pair) key = self.__get_cutkey(pair) self.cutoff.pop(key, None)
[docs] def set_global_cutoff(self, cutoff): """ Sets a global cutoff for all pairs. Warning: setting this will remove all other cutoff parameters. Args: cutoff (float): Custom cutoff to use. In Angstroms. """ self.__check_cutoff(cutoff) self.global_cutoff = cutoff self.cutoff = dict() self.cut_scale = None
[docs] def set_cut_scale(self, cut_scale): """ Sets the amount to scale the sigma values of each pair by to set the cutoff. Warning: setting this will remove any global cutoff, but leave custom cutoffs. Args: cut_scale (float): Scaling factor to be used on sigma """ self.__check_cutoff(cut_scale) self.global_cutoff = None self.cut_scale = cut_scale
[docs] def create_file(self, filename='ljparams.txt', atom_order=None): """ Outputs a GPUMD style LJ parameters file using the atoms defined in atom_order in the order defined in atom_order. Args: filename (str): The filename or full path with filename of the output. atom_order (list(str)): List of atom symbols to include LJ params output file. The order will determine the order in the output file. *Required* Ex. ['a', 'b', 'c'] = pairs => 'aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb' 'cc' in this order. """ if not atom_order: raise ValueError('atom_order is required.') # check if atoms in atom_order exist in LJ for symbol in atom_order: if symbol not in self.ljdict: raise ValueError('{} atom does not exist in LJ'.format(symbol)) out_txt = 'lj {}\n'.format(len(atom_order)) for i, sym1 in enumerate(atom_order): for j, sym2 in enumerate(atom_order): pair = {sym1, sym2} if pair in self.ignore: if i+1==len(atom_order) and j+1==len(atom_order): out_txt += '0 0 0' else: out_txt += '0 0 0\n' continue a1 = self.ljdict[sym1] a2 = self.ljdict[sym2] eps, sig = lb_mixing(a1, a2) cutkey = self.__get_cutkey(pair) if self.global_cutoff: cutoff = self.global_cutoff elif cutkey in self.cutoff: cutoff = self.cutoff[cutkey] else: cutoff = self.cut_scale*sig if i+1==len(atom_order) and j+1==len(atom_order): out_txt += '{} {} {}'.format(eps, sig, cutoff) else: out_txt += '{} {} {}\n'.format(eps, sig, cutoff) with open(filename, 'w') as f: f.writelines(out_txt)
def __get_cutkey(self, pair): keylist = sorted(list(pair)) if len(keylist) == 1: keylist.append(keylist[0]) key = ' '.join(keylist) return key def __check_pair(self, pair): # check params if not (type(pair) == set and (len(pair) == 2 or len(pair) == 1)): raise ValueError('Invalid pair.') # check pair good = True for item in list(pair): good = good and item in self.ljdict if not good: raise ValueError('Elements in pair not found in LJ object.') def __check_cutoff(self, cutoff): if not (isinstance(cutoff, (int, float)) and cutoff > 0): raise ValueError('Invalid cutoff.') def __str__(self): out_str = 'Symbol: Epsilon (eV), Sigma (Angs.)\n' for key in self.ljdict: cur = self.ljdict[key] out_str += "{}: {}, {}\n".format(key, cur[0], cur[1]) if self.cut_scale: out_str += "\nCutoff scaling factor = {}\n".format(self.cut_scale) else: # Global cutoff out_str += "\nGlobal cutoff = {} Angstroms\n".format(self.global_cutoff) if len(self.cutoff) > 0: out_str += "\nCustom Cutoffs\n" for pair in self.cutoff: lpair = pair.split() out_str += "[{}, {}] : {}\n".format(lpair[0], lpair[1], self.cutoff[pair]) if len(self.ignore) > 0: out_str += "\nIgnored Pairs (Order not important)\n" for pair in self.ignore: pair = list(pair) if len(pair) == 1: pair.append(pair[0]) out_str += "[{}, {}]\n".format(pair[0], pair[1]) return out_str