"""
Force field methods.
"""
from .uff import UFF_DATA
from .uff4mof import UFF4MOF_DATA
from .dreiding import DREIDING_DATA
from .uff_nonbonded import UFF_DATA_nonbonded
from .BTW import BTW_angles, BTW_dihedrals, BTW_opbends, BTW_atoms, BTW_bonds, BTW_charges
from .Dubbeldam import Dub_atoms, Dub_bonds, Dub_angles, Dub_dihedrals, Dub_impropers
#from FMOFCu import FMOFCu_angles, FMOFCu_dihedrals, FMOFCu_opbends, FMOFCu_atoms, FMOFCu_bonds
from .MOFFF import MOFFF_angles, MOFFF_dihedrals, MOFFF_opbends, MOFFF_atoms, MOFFF_bonds
from .water_models import SPC_E_atoms, TIP3P_atoms, TIP4P_atoms, TIP5P_atoms
from .gas_models import EPM2_atoms, EPM2_angles
from .lammps_potentials import BondPotential, AnglePotential, DihedralPotential, ImproperPotential, PairPotential
from .atomic import METALS
from .atomic import organic, non_metals, noble_gases, metalloids, lanthanides, actinides, transition_metals
from .atomic import alkali, alkaline_earth, main_group, metals
import math
import numpy as np
from operator import mul
import itertools
import abc
import re
import sys
from .Molecules import *
DEG2RAD = math.pi / 180.
kBtokcal = 0.00198588
[docs]class ForceField(object):
__metaclass__ = abc.ABCMeta
[docs] @abc.abstractmethod
def bond_term(self):
"""Computes the bond parameters"""
[docs] @abc.abstractmethod
def angle_term(self):
"""Computes the angle parameters"""
[docs] @abc.abstractmethod
def dihedral_term(self):
"""Computes the dihedral parameters"""
[docs] @abc.abstractmethod
def improper_term(self):
"""Computes the improper dihedral parameters"""
[docs] def compute_force_field_terms(self):
self.compute_atomic_pair_terms()
self.compute_bond_terms()
self.compute_angle_terms()
self.compute_dihedral_terms()
self.compute_improper_terms()
[docs] def compute_atomic_pair_terms(self):
charges = not np.allclose(0.0, [float(self.graph.node[i]['charge']) for i in list(self.graph.nodes)], atol=0.00001)
for n, data in self.graph.nodes_iter2(data=True):
self.pair_terms(n, data, self.cutoff, charges=charges)
[docs] def compute_bond_terms(self):
del_edges = []
for n1, n2, data in self.graph.edges_iter2(data=True):
if self.bond_term((n1, n2, data)) is None:
del_edges.append((n1, n2))
for (n1, n2) in del_edges:
self.graph.remove_edge(n1, n2)
[docs] def compute_angle_terms(self):
for b, data in self.graph.nodes_iter2(data=True):
# compute and store angle terms
try:
rem_ang = []
ang_data = data['angles']
for (a, c), val in ang_data.items():
if self.angle_term((a, b, c, val)) is None:
rem_ang.append((a,c))
for i in rem_ang:
del(data['angles'][i])
except KeyError:
pass
[docs] def compute_dihedral_terms(self):
for b, c, data in self.graph.edges_iter2(data=True):
try:
rem_dihed = []
dihed_data = data['dihedrals']
for (a, d), val in dihed_data.items():
if self.dihedral_term((a,b,c,d, val)) is None:
rem_dihed.append((a,d))
for i in rem_dihed:
del(data['dihedrals'][i])
except KeyError:
pass
[docs] def compute_improper_terms(self):
for b, data in self.graph.nodes_iter2(data=True):
try:
rem_imp = []
imp_data = data['impropers']
for (a, c, d), val in imp_data.items():
if self.improper_term((a,b,c,d, val)) is None:
rem_imp.append((a,c,d))
for i in rem_imp:
del(data['impropers'][i])
except KeyError:
pass
[docs]class UserFF(ForceField):
[docs] def __init__(self, graph):
self.graph = graph
self.unique_atom_types = {}
self.unique_bond_types = {}
self.unique_angle_types = {}
self.unique_dihedral_types = {}
self.unique_improper_types = {}
self.unique_pair_types = {}
[docs] def bond_term(self, bond):
return 1
[docs] def angle_term(self, angle):
return 1
[docs] def dihedral_term(self, dihedral):
return 1
[docs] def improper_term(self, improper):
return 1
[docs] def unique_atoms(self):
# ff_type keeps track of the unique integer index
print("Here are the unique atoms")
ff_type = {}
count = 0
for atom in self.structure.atoms:
if atom.force_field_type is None:
label = atom.element
else:
label = atom.force_field_type
try:
type = ff_type[label]
except KeyError:
count += 1
type = count
ff_type[label] = type
self.unique_atom_types[type] = atom
atom.ff_type_index = type
print(atom.ff_type_index)
for key, atom in list(self.unique_atom_types.items()):
print(str(key) + " : " + str(atom.index))
[docs] def unique_bonds(self):
print("Here are the unique bonds (Total = " +
str(len(self.structure.bonds)) + ")")
count = 0
bb_type = {}
for bond in self.structure.bonds:
idx1, idx2 = bond.indices
atm1, atm2 = self.structure.atoms[idx1], self.structure.atoms[idx2]
self.bond_term(bond)
try:
type = bb_type[(atm1.ff_type_index,
atm2.ff_type_index,
bond.order)]
except KeyError:
try:
type = bb_type[(atm2.ff_type_index,
atm1.ff_type_index,
bond.order)]
except KeyError:
count += 1
type = count
bb_type[(atm1.ff_type_index,
atm2.ff_type_index,
bond.order)] = type
self.unique_bond_types[type] = bond
bond.ff_type_index = type
print(bond.ff_type_index)
for key, bond in list(self.unique_bond_types.items()):
print(str(key) + " : " + str(bond.atoms[0].index)
+ " - " + str(bond.atoms[1].index))
[docs] def unique_angles(self):
print("Here are the unique angles (Total = " +
str(len(self.structure.angles)) + ")")
ang_type = {}
count = 0
for angle in self.structure.angles:
atom_a, atom_b, atom_c = angle.atoms
type_a, type_b, type_c = (atom_a.ff_type_index,
atom_b.ff_type_index,
atom_c.ff_type_index)
# compute and store angle terms
self.angle_term(angle)
try:
type = ang_type[(type_a, type_b, type_c)]
except KeyError:
try:
type = ang_type[(type_c, type_b, type_a)]
except KeyError:
count += 1
type = count
ang_type[(type_a, type_b, type_c)] = type
self.unique_angle_types[type] = angle
angle.ff_type_index = type
print(angle.ff_type_index)
for key, angle in list(self.unique_angle_types.items()):
print(str(key) + " : " + str(angle.atoms[0].index) + "-" +
str(angle.atoms[1].index) + "-" +
str(angle.atoms[2].index))
print(str(key) + " : " + str(angle.atoms[0].force_field_type)
+ "-" + str(angle.atoms[1].force_field_type) +
"-" + str(angle.atoms[2].force_field_type))
[docs] def unique_dihedrals(self):
print("Here are the unique dihedrals (Total = "
+ str(len(self.structure.dihedrals)) + ")")
count = 0
dihedral_type = {}
for dihedral in self.structure.dihedrals:
atom_a, atom_b, atom_c, atom_d = dihedral.atoms
type_a, type_b, type_c, type_d = (atom_a.ff_type_index,
atom_b.ff_type_index,
atom_c.ff_type_index,
atom_d.ff_type_index)
M = len(atom_c.neighbours)*len(atom_b.neighbours)
try:
type = dihedral_type[(type_a, type_b, type_c, type_d, M)]
except KeyError:
try:
type = dihedral_type[(type_d, type_c, type_b, type_a, M)]
except KeyError:
count += 1
type = count
dihedral_type[(type_a, type_b, type_c, type_d, M)] = type
#self.dihedral_term(dihedral)
self.unique_dihedral_types[type] = dihedral
dihedral.ff_type_index = type
print(dihedral.ff_type_index)
for key, dihedral in list(self.unique_dihedral_types.items()):
print(str(key) + " : " + str(dihedral.atoms[0].index) + "-" +
str(dihedral.atoms[1].index) + "-" + str(dihedral.atoms[2].index)
+ "-" + str(dihedral.atoms[3].index))
print(str(key) + " : " + str(dihedral.atoms[0].force_field_type)
+ "-" + str(dihedral.atoms[1].force_field_type) + "-"
+ str(dihedral.atoms[2].force_field_type) + "-" +
str(dihedral.atoms[3].force_field_type))
[docs] def unique_impropers(self):
"""How many times to list the same set of atoms ???"""
print("Here are the unique impropers (Total = " +
str(len(self.structure.impropers)) + ")")
count = 0
improper_type = {}
#i = 0
#for improper in self.structure.impropers:
# i += 1
# print(str(i) + " : " + str(improper.atoms[0].force_field_type) + "-" + str(improper.atoms[1].force_field_type) + "-" + str(improper.atoms[2].force_field_type) + "-" + str(improper.atoms[3].force_field_type)
for improper in self.structure.impropers:
print("Now keys are + " + str(improper_type.keys()))
atom_a, atom_b, atom_c, atom_d = improper.atoms
type_a, type_b, type_c, type_d = (atom_a.ff_type_index, atom_b.ff_type_index,
atom_c.ff_type_index, atom_d.ff_type_index)
d1 = (type_b, type_a, type_c, type_d)
d2 = (type_b, type_a, type_d, type_c)
d3 = (type_b, type_c, type_d, type_a)
d4 = (type_b, type_c, type_a, type_d)
d5 = (type_b, type_d, type_a, type_c)
d6 = (type_b, type_d, type_c, type_a)
if d1 in improper_type.keys():
print("found d1" + str(d1))
type = improper_type[d1]
elif d2 in improper_type.keys():
print("found d2")
type = improper_type[d2]
elif d3 in improper_type.keys():
print("found d3")
type = improper_type[d3]
elif d4 in improper_type.keys():
print("found d4")
type = improper_type[d4]
elif d5 in improper_type.keys():
print("found d5")
type = improper_type[d5]
elif d6 in improper_type.keys():
print("found d6")
type = improper_type[d6]
else:
print("found else" + str(d1))
count += 1
type = count
improper_type[d1] = type
self.unique_improper_types[type] = improper
improper.ff_type_index = type
print(improper.ff_type_index)
for key, improper in list(self.unique_improper_types.items()):
print(str(key) + " : " + str(improper.atoms[0].force_field_type) +
"-" + str(improper.atoms[1].force_field_type) + "-" +
str(improper.atoms[2].force_field_type) + "-" +
str(improper.atoms[3].force_field_type))
[docs] def van_der_waals_pairs(self):
atom_types = self.unique_atom_types.keys()
for type1, type2 in itertools.combinations_with_replacement(atom_types, 2):
atm1 = self.unique_atom_types[type1]
atm2 = self.unique_atom_types[type2]
print(str(re.findall(r'^[a-zA-Z]*',atm1.force_field_type)[0]))
print(str(re.findall(r'^[a-zA-Z]*',atm2.force_field_type)[0]))
# if we are using non-UFF atom types, need to splice off the end descriptors (first non alphabetic char)
eps1 = UFF_DATA_nonbonded[re.findall(r'^[a-zA-Z]*',atm1.force_field_type)[0]][3]
eps2 = UFF_DATA_nonbonded[re.findall(r'^[a-zA-Z]*',atm2.force_field_type)[0]][3]
# radius --> sigma = radius*2**(-1/6)
sig1 = UFF_DATA_nonbonded[re.findall(r'^[a-zA-Z]*',atm1.force_field_type)[0]][2]*(2**(-1./6.))
sig2 = UFF_DATA_nonbonded[re.findall(r'^[a-zA-Z]*',atm2.force_field_type)[0]][2]*(2**(-1./6.))
# l-b mixing
eps = math.sqrt(eps1*eps2)
sig = (sig1 + sig2) / 2.
self.unique_pair_types[(type1, type2)] = (eps, sig)
[docs] def write_missing_uniques(self, description):
# Warn user about any unique bond, angle, etc. found that have not
# been specified in user_input.txt
pass
[docs] def map_user_to_unique_atom(self, descriptor):
for key, atom in list(self.unique_atom_types.items()):
if descriptor == atom.force_field_type:
return atom.ff_type_index
raise ValueError('Error! An atom identifier ' + str(description) +
' in user_input.txt did not match any atom_site_description in your cif')
[docs] def map_pair_unique_bond(self, pair, descriptor):
for key, bond in list(self.unique_bond_types.items()):
if (pair == [bond.atoms[0].ff_type_index, bond.atoms[1].ff_type_index]
or pair == [bond.atoms[1].ff_type_index, bond.atoms[0].ff_type_index]):
return key
raise ValueError('Error! An bond identifier ' + str(descriptor) +
' in user_input.txt did not match any bonds in your cif')
[docs] def map_triplet_unique_angle(self, triplet, descriptor):
#print(triplet)
#print(descriptor)
for key, angle in list(self.unique_angle_types.items()):
#print(str(key) + " : " + str([angle.atoms[2].ff_type_index, angle.atoms[1].ff_type_index, angle.atoms[0].ff_type_index]))
if (triplet == [angle.atoms[0].ff_type_index,
angle.atoms[1].ff_type_index,
angle.atoms[2].ff_type_index] or
triplet == [angle.atoms[2].ff_type_index,
angle.atoms[1].ff_type_index,
angle.atoms[0].ff_type_index]):
return key
raise ValueError('Error! An angle identifier ' + str(descriptor) +
' in user_input.txt did not match any angles in your cif')
[docs] def map_quadruplet_unique_dihedral(self, quadruplet, descriptor):
for key, dihedral in list(self.unique_dihedral_types.items()):
if (quadruplet == [dihedral.atoms[0].ff_type_index,
dihedral.atoms[1].ff_type_index,
dihedral.atoms[2].ff_type_index,
dihedral.atoms[3].ff_type_index] or
quadruplet == [dihedral.atoms[3].ff_type_index,
dihedral.atoms[2].ff_type_index,
dihedral.atoms[1].ff_type_index,
dihedral.atoms[0].ff_type_index]):
return key
raise ValueError('Error! A dihdral identifier ' + str(descriptor) +
' in user_input.txt did not match any dihedrals in your cif')
[docs] def map_quadruplet_unique_improper(self, quadruplet, descriptor):
for key, improper in list(self.unique_improper_types.items()):
if (quadruplet == [improper.atoms[0].ff_type_index,
improper.atoms[1].ff_type_index,
improper.atoms[2].ff_type_index,
improper.atoms[3].ff_type_index] or
quadruplet == [improper.atoms[3].ff_type_index,
improper.atoms[2].ff_type_index,
improper.atoms[1].ff_type_index,
improper.atoms[0].ff_type_index]):
return key
raise ValueError('Error! An improper identifier ' + str(descriptor) +
' in user_input.txt did not match any improper in your cif')
[docs] def overwrite_force_field_terms(self):
self.parse_user_input("blah")
[docs] def compute_force_field_terms(self):
self.unique_atoms()
self.unique_bonds()
self.unique_angles()
self.unique_dihedrals()
self.unique_impropers()
self.parse_user_input("blah")
self.van_der_waals_pairs()
[docs]class OverwriteFF(ForceField):
"""
Prepare a nanoprous material FF from a given structure for a known
FF type.
Then overwrite any parameters that are supplied by user_input.txt
Methods are duplicated from UserFF, can reduce redundancy of code
later if desired
"""
[docs] def __init__(self, struct, base_FF):
# Assign the base ForceField
if(baseFF == "UFF"):
self = UFF(struct)
elif(baseFF == "DREIDING"):
self = Dreiding(struct)
elif(baseFF == "CVFF"):
print("CVFF not implemented yet...")
sys.exit()
pass
elif(baseFF == "CHARMM"):
print("CHARMM not implemented yet...")
sys.exit()
pass
else:
# etc. TODO worth adding in these additional FF types
print("Invalid base FF requested\nExiting...")
sys.exit()
# Overwrite any parameters specified by user_input.txt
parse_user_input("user_input.txt")
[docs] def write_missing_uniques(self, description):
# Warn user about any unique bond, angle, etc. found that have not
# been specified in user_input.txt
pass
[docs] def map_user_to_unique_atom(self, descriptor):
for key, atom in list(self.unique_atom_types.items()):
if descriptor == atom.force_field_type:
return atom.ff_type_index
raise ValueError('Error! An atom identifier ' + str(description) +
' in user_input.txt did not match any atom_site_description in your cif')
[docs] def map_pair_unique_bond(self, pair, descriptor):
for key, bond in list(self.unique_bond_types.items()):
if (pair == [bond.atoms[0].ff_type_index, bond.atoms[1].ff_type_index]
or pair == [bond.atoms[1].ff_type_index, bond.atoms[0].ff_type_index]):
return key
raise ValueError('Error! A bond identifier ' + str(descriptor) +
' in user_input.txt did not match any bonds in your cif')
[docs] def map_triplet_unique_angle(self, triplet, descriptor):
#print(triplet)
#print(descriptor)
for key, angle in list(self.unique_angle_types.items()):
#print(str(key) + " : " + str([angle.atoms[2].ff_type_index, angle.atoms[1].ff_type_index, angle.atoms[0].ff_type_index]))
if (triplet == [angle.atoms[0].ff_type_index,
angle.atoms[1].ff_type_index,
angle.atoms[2].ff_type_index] or
triplet == [angle.atoms[2].ff_type_index,
angle.atoms[1].ff_type_index,
angle.atoms[0].ff_type_index]):
return key
raise ValueError('Error! An angle identifier ' + str(descriptor) +
' in user_input.txt did not match any angles in your cif')
[docs] def map_quadruplet_unique_dihedral(self, quadruplet, descriptor):
for key, dihedral in list(self.unique_dihedral_types.items()):
if (quadruplet == [dihedral.atoms[0].ff_type_index,
dihedral.atoms[1].ff_type_index,
dihedral.atoms[2].ff_type_index,
dihedral.atoms[3].ff_type_index] or
quadruplet == [dihedral.atoms[3].ff_type_index,
dihedral.atoms[2].ff_type_index,
dihedral.atoms[1].ff_type_index,
dihedral.atoms[0].ff_type_index]):
return key
raise ValueError('Error! A dihdral identifier ' + str(descriptor) +
' in user_input.txt did not match any dihedrals in your cif')
[docs] def map_quadruplet_unique_improper(self, quadruplet, descriptor):
for key, improper in list(self.unique_improper_types.items()):
if (quadruplet == [improper.atoms[0].ff_type_index,
improper.atoms[1].ff_type_index,
improper.atoms[2].ff_type_index,
improper.atoms[3].ff_type_index] or
quadruplet == [improper.atoms[3].ff_type_index,
improper.atoms[2].ff_type_index,
improper.atoms[1].ff_type_index,
improper.atoms[0].ff_type_index]):
return key
raise ValueError('Error! An improper identifier ' + str(descriptor) +
' in user_input.txt did not match any improper in your cif')
[docs]class BTW_FF(ForceField):
[docs] def __init__(self, **kwargs):
self.pair_in_data = False
self.keep_metal_geometry = False
self.graph = None
# override existing arguments with kwargs
for key, value in kwargs.items():
setattr(self, key, value)
if (self.graph is not None):
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def detect_ff_terms(self):
"""
Assigning force field type of atoms
"""
# for each atom determine the ff type if it is None
BTW_organics = ["O", "C", "H"]
mof_sbus = set(self.graph.inorganic_sbus.keys())
BTW_sbus = set(["Cu Paddlewheel", "Zn4O", "Zr_UiO"])
if not (mof_sbus <= BTW_sbus):
print("The system cannot be simulated with BTW-FF!")
sys.exit()
elif ( len(mof_sbus)> 1):
print("No exact charge for the IRMOF is available from BTW-FF. Average charges in BTW-FF is used.")
chrg_flag="TFF_" # Transferable FF charges (average values)
elif("Zn4O" in mof_sbus):
#resp= input("What is the IRMOF number?[1/10]")
# temp fix so I don't have to respond to screen prompt
resp = "0"
if self.graph.number_of_nodes() == 424:
resp = "1"
elif self.graph.number_of_nodes() == 664:
resp = "10"
if resp=="1":
chrg_flag="Zn4O_"
elif resp=="10":
chrg_flag="IRMOF10_"
else:
#print("No exact charge for the IRMOF is available from BTW-FF. Average charges in BTW-FF is used.")
#chrg_flag="TFF_"
print("Cannot parameterize this MOF with BTW-FF.")
sys.exit()
else:
sbu_type = next(iter(mof_sbus))
chrg_flag=sbu_type+"_"
#Assigning force field type of atoms
for node, atom in self.graph.nodes_iter2(data=True):
# check if element not in one of the SBUS
if atom['element'] == "Cu":
try:
if atom['special_flag'] == 'Cu_pdw':
atom['force_field_type']="185"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
else:
print("ERROR: Cu %i is not assigned to a Cu Paddlewheel! exiting"%(node))
sys.exit()
except KeyError:
print("ERROR: Cu %i is not assigned to a Cu Paddlewheel! exiting"%(node))
sys.exit()
elif atom['element'] == "Zn":
try:
if atom['special_flag'] == 'Zn4O':
atom['force_field_type']="172"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
else:
print("ERROR: Zn %i is not assigned to a Zn4O! exiting"%(node))
sys.exit()
except KeyError:
print("ERROR: Zn %i is not assigned to a Zn4O! exiting"%(node))
sys.exit()
elif atom['element'] == "Zr":
try:
if atom['special_flag'] == 'Zr_UiO':
atom['force_field_type']="192"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
else:
print("ERROR: Zr %i is not assigned to a Zr_UiO! exiting"%(node))
sys.exit()
except KeyError:
print("ERROR: Zr %i is not assigned to a Zr_UiO! exiting"%(node))
sys.exit()
if atom['force_field_type'] is None:
type_assigned = False
neighbours = [self.graph.node[i] for i in self.graph.neighbors(node)]
neighbour_elements = [a['element'] for a in neighbours]
special = False
if 'special_flag' in atom:
special = True
if (atom['element'] == "O") and special:
# Zn4O cases
if atom['special_flag'] == "O_z_Zn4O":
atom['force_field_type'] = "171"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
elif atom['special_flag'] == "O_c_Zn4O":
atom['force_field_type'] = "170"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
# Zr_UiO cases
elif atom['special_flag'] == "O_z_Zr_UiO":
atom['force_field_type'] = "171"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
elif atom['special_flag'] == "O_h_Zr_UiO":
atom['force_field_type'] = "75"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
elif atom['special_flag'] == "O_c_Zr_UiO":
atom['force_field_type'] = "170"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
# Cu Paddlewheel case
elif (atom['special_flag'] == "O1_Cu_pdw") or (atom['special_flag'] == "O2_Cu_pdw"):
atom['force_field_type'] = "170"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
else:
print("Oxygen number %i type cannot be detected!"%node)
sys.exit()
elif (atom['element'] == "C") and special:
# Zn4O case
if atom['special_flag'] == "C_Zn4O":
atom['force_field_type'] = "913" # C-acid
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
# Zr_UiO case
elif atom['special_flag'] == "C_Zr_UiO":
atom['force_field_type'] = "913" # C-acid
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
# Cu Paddlewheel case
elif atom['special_flag'] == "C_Cu_pdw":
atom['force_field_type'] = "913" # C-acid
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
else:
print("Carbon number %i type cannot be detected!"%node)
sys.exit()
elif (atom['element'] == "H") and special:
# only UiO case
if atom['special_flag'] == "H_o_Zr_UiO":
atom['force_field_type'] = "21"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
else:
print("Hydrogen number %i type cannot be detected!"%node)
sys.exit()
# currently no oxygens assigned types outside of metal SBUs
elif (atom['element'] == "O") and not special:
print("Oxygen number %i type cannot be detected!"%node)
sys.exit()
elif (atom['element'] == "C") and not special:
# all organic SBUs have the same types..
if set(neighbour_elements) == set(["C","H"]):
atom['force_field_type'] = "912" # C- benzene we should be careful that in this case C in ligand has also bond with H, but not in the FF
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
elif set(neighbour_elements) == set(["C"]):
# check if carbon adjacent to metal SBU (signified by the key 'special_flag')
if any(['special_flag' in at for at in neighbours]):
atom['force_field_type'] = "902"
else:
atom['force_field_type'] = "903"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
else:
print("Carbon number %i type cannot be detected!"%node)
sys.exit()
elif (atom['element'] == "H") and not special:
if set(neighbour_elements)<=set(["C"]):
atom['force_field_type'] = "915"
chrg_key = chrg_flag+atom['force_field_type']
atom['charge']=BTW_charges[chrg_key]
else:
print("Hydrogen number %i type cannot be detected!"%node)
sys.exit()
# Assigning force field type of bonds
for a, b, bond in self.graph.edges_iter2(data=True):
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
atom_a_fflabel, atom_b_fflabel = a_atom['force_field_type'], b_atom['force_field_type']
bond_fflabel1 = atom_a_fflabel+"_"+atom_b_fflabel
bond_fflabel2 = atom_b_fflabel+"_"+atom_a_fflabel
if bond_fflabel1 in BTW_bonds:
bond['force_field_type']=bond_fflabel1
elif bond_fflabel2 in BTW_bonds:
bond['force_field_type']=bond_fflabel2
else:
print ("BTW-FF cannot be used for the system!\nNo parameter found for bond %s"%(bond_fflabel1))
sys.exit()
#Assigning force field type of angles
missing_labels=[]
for b , data in self.graph.nodes_iter2(data=True):
try:
missing_angles=[]
ang_data = data['angles']
for (a, c), val in ang_data.items():
a_atom = self.graph.node[a]
b_atom = data
c_atom = self.graph.node[c]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
angle_fflabel1=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
angle_fflabel2=atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
if (angle_fflabel1=="170_185_170"):
val['force_field_type']=angle_fflabel1
elif angle_fflabel1 in BTW_angles:
val['force_field_type']=angle_fflabel1
elif angle_fflabel2 in BTW_angles:
val['force_field_type']=angle_fflabel2
else:
missing_angles.append((a,c))
missing_labels.append(angle_fflabel1)
for key in missing_angles:
del ang_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s angle is deleted since the angle was not parametrized in BTW-FF!"%(ff_label))
#Assigning force field type of dihedrals
missing_labels=[]
for b, c, data in self.graph.edges_iter2(data=True):
try:
missing_dihedral=[]
dihed_data = data['dihedrals']
for (a, d), val in dihed_data.items():
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
c_atom = self.graph.node[c]
d_atom = self.graph.node[d]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
atom_d_fflabel = d_atom['force_field_type']
dihedral_fflabel1=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel+"_"+atom_d_fflabel
dihedral_fflabel2=atom_d_fflabel+"_"+atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
if dihedral_fflabel1 in BTW_dihedrals:
val['force_field_type']=dihedral_fflabel1
elif dihedral_fflabel2 in BTW_dihedrals:
val['force_field_type']=dihedral_fflabel2
else:
missing_dihedral.append((a,d))
missing_labels.append(dihedral_fflabel1)
for key in missing_dihedral:
del dihed_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s dihedral is deleted since the dihedral was not parametrized in BTW-FF!"%(ff_label))
#Assigning force field type of impropers
missing_labels=[]
for b, data in self.graph.nodes_iter2(data=True):
try:
missing_improper=[]
imp_data = data['impropers']
for (a, c, d), val in imp_data.items():
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
c_atom = self.graph.node[c]
d_atom = self.graph.node[d]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
atom_d_fflabel = d_atom['force_field_type']
improper_fflabel=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel+"_"+atom_d_fflabel
if improper_fflabel in BTW_opbends:
val['force_field_type']=improper_fflabel
else:
missing_improper.append((a,c,d))
missing_labels.append(improper_fflabel)
for key in missing_improper:
del imp_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s improper is deleted since the improper was not parametrized in BTW-FF!"%(ff_label))
[docs] def bond_term(self, edge):
"""class2 bond: 4-order polynomial """
n1, n2, data = edge
Ks = BTW_bonds[data['force_field_type']][0]
l0 = BTW_bonds[data['force_field_type']][1]
### All the factors are conversion to kcal/mol from the units in the paper ###
K2= 71.94*Ks
K3= -2.55*K2
K4= 3.793125*K2
data['potential'] = BondPotential.Class2()
data['potential'].K2 = K2
data['potential'].K3 = K3
data['potential'].K4 = K4
data['potential'].R0 = l0
return 1
[docs] def angle_term(self, angle):
"""class2 angle
NOTE: We ignored the 5and6 order terms of polynomial since the functional is not implemented in LAMMPS!!
"""
a, b, c, data = angle
if (data['force_field_type']=="170_185_170"): ### in the case of square planar coordination of Cu-paddle-wheel, fourier angle must be used
data['potential'] = AnglePotential.CosinePeriodic()
data['potential'].C = 126.64 # conversion from the K value in MOF-FF to the C value for LAMMPS in cosine/periodic
data['potential'].B = 1
data['potential'].n = 4
return 1
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
ab_bond = self.graph[a][b]
bc_bond = self.graph[b][c]
atom_a_fflabel = a_data['force_field_type']
atom_b_fflabel = b_data['force_field_type']
atom_c_fflabel = c_data['force_field_type']
ang_ff_tmp = atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
Ktheta = BTW_angles[data['force_field_type']][0]
theta0 = BTW_angles[data['force_field_type']][1]
### BondAngle ###
baN1 = BTW_angles[data['force_field_type']][4]
baN2 = BTW_angles[data['force_field_type']][5]
### BondBond ###
bbM = BTW_angles[data['force_field_type']][6]
if not (ang_ff_tmp == data['force_field_type']): # switching the force constants in the case of assigning swaped angle_ff_label
buf1 = atom_a_fflabel
atom_a_fflabel = atom_c_fflabel
atom_c_fflabel = buf1
buf2 = baN1
baN1=baN2
baN2=buf2
### assingning the equilibrium distance of each bond from FF
bond1_label = atom_a_fflabel+"_"+atom_b_fflabel
bond2_label = atom_b_fflabel+"_"+atom_c_fflabel
if (bond1_label) in BTW_bonds:
r1 = BTW_bonds[bond1_label][1]
else:
bond1_label = atom_b_fflabel+"_"+atom_a_fflabel
r1 = BTW_bonds[bond1_label][1]
if (bond2_label) in BTW_bonds:
r2 = BTW_bonds[bond2_label][1]
else:
bond2_label = atom_c_fflabel+"_"+atom_b_fflabel
r2 = BTW_bonds[bond2_label][1]
### Unit conversion ###
bbM = bbM *71.94
baN1 = 2.51118 * baN1 / (DEG2RAD)
baN2 = 2.51118 * baN2/ (DEG2RAD)
# 0.021914 is 143.82/2 * (pi/180)**2
K2 = 0.021914*Ktheta/(DEG2RAD**2)
K3 = -0.014*K2/(DEG2RAD**1)
K4 = 5.6e-5*K2/(DEG2RAD**2)
data['potential'] = AnglePotential.Class2()
data['potential'].theta0 = theta0
data['potential'].K2 = K2
data['potential'].K3 = K3
data['potential'].K4 = K4
data['potential'].ba.N1 = baN1
data['potential'].ba.N2 = baN2
data['potential'].ba.r1 = r1
data['potential'].ba.r2 = r2
return 1
[docs] def dihedral_term(self, dihedral):
""" fourier diherdral """
a,b,c,d, data = dihedral
kt1 = 0.5 * BTW_dihedrals[data['force_field_type']][0]
kt2 = 0.5 * BTW_dihedrals[data['force_field_type']][3]
kt3 = 0.5 * BTW_dihedrals[data['force_field_type']][6]
kt4 = 0.5 * BTW_dihedrals[data['force_field_type']][9]
n1 = BTW_dihedrals[data['force_field_type']][2]
n2 = BTW_dihedrals[data['force_field_type']][5]
n3 = BTW_dihedrals[data['force_field_type']][8]
n4 = BTW_dihedrals[data['force_field_type']][11]
d1 = BTW_dihedrals[data['force_field_type']][1]
d2 = BTW_dihedrals[data['force_field_type']][4]
d3 = BTW_dihedrals[data['force_field_type']][7]
d4 = BTW_dihedrals[data['force_field_type']][10]
ki = [kt1,kt2,kt3,kt4]
ni = [n1,n2,n3,n4]
di = [d1,d2,d3,d4]
data['potential'] = DihedralPotential.Fourier()
data['potential'].Ki = ki
data['potential'].ni = ni
data['potential'].di = di
return 1
[docs] def improper_term(self, improper):
"""class2 improper"""
a,b,c,d, data = improper
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
d_data = self.graph.node[d]
atom_a_fflabel=a_data['force_field_type']
atom_b_fflabel=b_data['force_field_type']
atom_c_fflabel=c_data['force_field_type']
atom_d_fflabel=d_data['force_field_type']
# 0.021914 is 143.82/2 * (pi/180)**-2
Kopb = BTW_opbends[data['force_field_type']][0]/(DEG2RAD**2)*0.02191418
c0 = BTW_opbends[data['force_field_type']][1]
#Angle-Angle term
# units should be energy/distance
M1 = BTW_opbends[data['force_field_type']][2]/(DEG2RAD**2)*0.02191418*(-1)/3. # Dividing by three to get rid of overcounting of angle-angle terms
M2 = BTW_opbends[data['force_field_type']][3]/(DEG2RAD**2)*0.02191418*(-1)/3. # Dividing by three to get rid of overcounting of angle-angle terms
M3 = BTW_opbends[data['force_field_type']][4]/(DEG2RAD**2)*0.02191418*(-1)/3. # Dividing by three to get rid of overcounting of angle-angle terms
ang1_ff_label = atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
ang2_ff_label = atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_d_fflabel
ang3_ff_label = atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_d_fflabel
if (ang1_ff_label) in BTW_angles:
Theta1 = BTW_angles[ang1_ff_label][1]
else:
ang1_ff_label = atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
Theta1 = BTW_angles[ang1_ff_label][1]
if (ang2_ff_label) in BTW_angles:
Theta2 = BTW_angles[ang2_ff_label][1]
else:
ang2_ff_label = atom_d_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
Theta2 = BTW_angles[ang2_ff_label][1]
if (ang3_ff_label) in BTW_angles:
Theta3 = BTW_angles[ang3_ff_label][1]
else:
ang3_ff_label = atom_d_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
Theta3 = BTW_angles[ang3_ff_label][1]
data['potential'] = ImproperPotential.Class2() #does not work now!
data['potential'].K = Kopb
data['potential'].chi0 = c0
data['potential'].aa.M1 = M1
data['potential'].aa.M2 = M2
data['potential'].aa.M3 = M3
data['potential'].aa.theta1 = Theta1
data['potential'].aa.theta2 = Theta2
data['potential'].aa.theta3 = Theta3
return 1
[docs] def pair_terms( self, node , data, cutoff, **kwargs):
"""
Buckingham equation in MM3 type is used!
"""
eps = BTW_atoms[data['force_field_type']][4]
sig = BTW_atoms[data['force_field_type']][3]
data['pair_potential']=PairPotential.BuckCoulLong()
data['pair_potential'].cutoff= cutoff
data['pair_potential'].eps = eps
data['pair_potential'].sig = sig
[docs] def special_commands(self):
st = ["%-15s %s"%("pair_modify", "tail yes"),
"%-15s %s"%("special_bonds", "lj/coul 0.0 0.0 1"),
"%-15s %.2f"%('dielectric', 1.5)]
return st
[docs]class MOF_FF(ForceField):
[docs] def __init__(self, **kwargs):
self.pair_in_data = False
self.keep_metal_geometry = False
self.graph = None
# override existing arguments with kwargs
for key, value in kwargs.items():
setattr(self, key, value)
if (self.graph is not None):
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def detect_ff_terms(self):
""" MOF-FF contains force field descriptions for three different
inorganic SBUs:
Cu-paddle-wheel
Zn cluster --> IRMOF series
Zr cluster --> UiO series
"""
# for each atom determine the ff type if it is None
MOF_FF_organics = ["O", "C", "H"]
# change to cluster detection.
mof_sbus = set(self.graph.inorganic_sbus.keys())
MOF_FF_sbus = set(["Cu Paddlewheel", "Zn4O", "Zr_UiO"])
if not (mof_sbus <= MOF_FF_sbus):
print("The system cannot be simulated with MOF-FF!")
sys.exit()
#Assigning force field type of atoms
for node, atom in self.graph.nodes_iter2(data=True):
# check if element not in one of the SBUS
if atom['element'] == "Cu":
try:
if atom['special_flag'] == 'Cu_pdw':
atom['force_field_type'] = "165"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
else:
print("ERROR: Cu %i is not assigned to a Cu Paddlewheel! exiting"%(node))
sys.exit()
except KeyError:
print("ERROR: Cu %i is not assigned to a Cu Paddlewheel! exiting"%(node))
sys.exit()
elif atom['element'] == "Zn":
try:
if atom['special_flag'] == 'Zn4O':
atom['force_field_type'] = "166"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
else:
print("ERROR: Zn %i is not assigned to a Zn4O! exiting"%(node))
sys.exit()
except KeyError:
print("ERROR: Zn %i is not assigned to a Zn4O! exiting"%(node))
sys.exit()
elif atom['element'] == "Zr":
try:
if atom['special_flag'] == 'Zr_UiO':
atom['force_field_type'] = "101"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
else:
print("ERROR: Zr %i is not assigned to a Zr_UiO! exiting"%(node))
sys.exit()
except KeyError:
print("ERROR: Zr %i is not assigned to a Zr_UiO! exiting"%(node))
sys.exit()
if atom['force_field_type'] is None:
type_assigned = False
neighbours = [self.graph.node[i] for i in self.graph.neighbors(node)]
neighbour_elements = [a['element'] for a in neighbours]
special = False
if 'special_flag' in atom:
special = True
if (atom['element'] == "O") and special:
# Zn4O cases
if atom['special_flag'] == "O_z_Zn4O":
atom['force_field_type'] = "165"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
elif atom['special_flag'] == "O_c_Zn4O":
atom['force_field_type'] = "167"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
# Zr_UiO cases
elif atom['special_flag'] == "O_z_Zr_UiO":
atom['force_field_type'] = "102"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
elif atom['special_flag'] == "O_h_Zr_UiO":
atom['force_field_type'] = "103"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
elif atom['special_flag'] == "O_c_Zr_UiO":
atom['force_field_type'] = "106"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
# Cu Paddlewheel case
elif (atom['special_flag'] == "O1_Cu_pdw") or (atom['special_flag'] == "O2_Cu_pdw"):
atom['force_field_type'] = "167"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
else:
print("Oxygen number %i type cannot be detected!"%node)
sys.exit()
elif (atom['element'] == "C") and special:
# Zn4O case
if atom['special_flag'] == "C_Zn4O":
atom['force_field_type'] = "168"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
# Zr_UiO case
elif atom['special_flag'] == "C_Zr_UiO":
atom['force_field_type'] = "104"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
# Cu Paddlewheel case
elif atom['special_flag'] == "C_Cu_pdw":
atom['force_field_type'] = "168"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
else:
print("Carbon number %i type cannot be detected!"%node)
sys.exit()
elif (atom['element'] == "H") and special:
# only UiO case
if atom['special_flag'] == "H_o_Zr_UiO":
atom['force_field_type'] = "105"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
else:
print("Hydrogen number %i type cannot be detected!"%node)
sys.exit()
# currently no oxygens assigned types outside of metal SBUs
elif (atom['element'] == "O") and not special:
print("Oxygen number %i type cannot be detected!"%node)
sys.exit()
elif (atom['element'] == "C") and not special:
# all organic SBUs have the same types..
if set(neighbour_elements) == set(["C","H"]):
atom['force_field_type'] = "2"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
elif set(neighbour_elements) == set(["C"]):
atom['force_field_type'] = "2"
# check if Zn4O, UiO, or Cu pdw
if mof_sbus == set(['Zn4O']):
atom['charge']= 0.18 #special charge for C_ph - C_carb
elif mof_sbus == set(['Zr_UiO']):
atom['charge']= 0.042 #special charge for C_ph - C_carb
elif mof_sbus == set(['Cu Paddlewheel']):
atom['charge']= 0.15 #special charge for C_ph - C_carb
else:
print("Carbon number %i type cannot be detected!"%node)
sys.exit()
elif (atom['element'] == "H") and not special:
if set(neighbour_elements)<=set(["C"]):
atom['force_field_type'] = "5"
atom['charge']=MOFFF_atoms[atom['force_field_type']][6]
else:
print("Hydrogen number %i type cannot be detected!"%node)
sys.exit()
# atom['charge']=0
# THE REST OF THIS SHOULD BE IN SEPARATE FUNCTIONS AS PER OTHER FF's DESCRIBED HERE
# TODO(Mohammad): make this easier to read.
#Assigning force field type of bonds
for a, b, bond in self.graph.edges_iter2(data=True):
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
atom_a_fflabel, atom_b_fflabel = a_atom['force_field_type'], b_atom['force_field_type']
bond1_fflabel=atom_a_fflabel+"_"+atom_b_fflabel
bond2_fflabel=atom_b_fflabel+"_"+atom_a_fflabel
if bond1_fflabel in MOFFF_bonds:
bond['force_field_type']=bond1_fflabel
elif bond2_fflabel in MOFFF_bonds:
bond['force_field_type']=bond2_fflabel
else:
print ("%s bond does not exist in FF!"%(bond1_fflabel))
exit()
#Assigning force field type of angles
missing_labels=[]
for b , data in self.graph.nodes_iter2(data=True):
try:
missing_angles=[]
ang_data = data['angles']
for (a, c), val in ang_data.items():
a_atom = self.graph.node[a]
b_atom = data
c_atom = self.graph.node[c]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
angle1_fflabel=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
angle2_fflabel=atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
if (angle1_fflabel=="167_165_167"):
val['force_field_type']=angle1_fflabel
elif (angle1_fflabel=="103_101_106") or (angle1_fflabel=="106_101_103"):
val['force_field_type']="103_101_106"
elif (angle1_fflabel=="106_101_106"):
val['force_field_type']=angle1_fflabel
elif angle1_fflabel in MOFFF_angles:
val['force_field_type']=angle1_fflabel
elif angle2_fflabel in MOFFF_angles:
val['force_field_type']=angle2_fflabel
else:
missing_angles.append((a,c))
missing_labels.append(angle1_fflabel)
for key in missing_angles:
del ang_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s angle does not exist in FF!"%(ff_label))
#Assigning force field type of dihedrals
missing_labels=[]
for b, c, data in self.graph.edges_iter2(data=True):
try:
missing_dihedral=[]
dihed_data = data['dihedrals']
for (a, d), val in dihed_data.items():
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
c_atom = self.graph.node[c]
d_atom = self.graph.node[d]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
atom_d_fflabel = d_atom['force_field_type']
dihedral1_fflabel=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel+"_"+atom_d_fflabel
dihedral2_fflabel=atom_d_fflabel+"_"+atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
if dihedral1_fflabel in MOFFF_dihedrals:
val['force_field_type']=dihedral1_fflabel
elif dihedral2_fflabel in MOFFF_dihedrals:
val['force_field_type']=dihedral2_fflabel
else:
missing_dihedral.append((a,d))
missing_labels.append(dihedral1_fflabel)
for key in missing_dihedral:
del dihed_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s dihedral does not exist in FF!"%(ff_label))
#Assigning force field type of impropers
missing_labels=[]
for b, data in self.graph.nodes_iter2(data=True):
try:
missing_improper=[]
imp_data = data['impropers']
for (a, c, d), val in imp_data.items():
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
c_atom = self.graph.node[c]
d_atom = self.graph.node[d]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
atom_d_fflabel = d_atom['force_field_type']
improper_fflabel=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel+"_"+atom_d_fflabel
if improper_fflabel in MOFFF_opbends:
val['force_field_type']=improper_fflabel
else:
missing_improper.append((a,c,d))
missing_labels.append(improper_fflabel)
for key in missing_improper:
del imp_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s improper does not exist in FF!"%(ff_label))
[docs] def bond_term(self, edge):
"""class2 bond
Es=71.94*Ks*(l-l0)^2[1-2.55(l-l0)+(7/12)*2.55*(l-l0)^2]
(Allinger et. al. J.Am.Chem.Soc., Vol. 111, No. 23, 1989)
"""
n1, n2, data = edge
Ks = MOFFF_bonds[data['force_field_type']][0]
l0 = MOFFF_bonds[data['force_field_type']][1]
D = MOFFF_bonds[data['force_field_type']][2] # the value should be in kcal/mol
if (D!=0): # in case of coordination bond, MOF-FF used morse potential
alpha = np.sqrt(Ks*2*71.94/(2.0*D))
data['potential'] = BondPotential.Morse()
data['potential'].D = D
data['potential'].alpha = alpha
data['potential'].R0 = l0
return 1
K2= 71.94*Ks # mdyne to kcal *(1/2)
K3= -2.55*K2
K4= 3.793125*K2
data['potential'] = BondPotential.Class2()
data['potential'].K2 = K2
data['potential'].K3 = K3
data['potential'].K4 = K4
data['potential'].R0 = l0
return 1
[docs] def angle_term(self, angle):
"""class2 angle
Be careful that the 5and6 order terms are vanished here since they are not implemented in LAMMPS!!
Etheta = 0.021914*Ktheta*(theta-theta0)^2[1-0.014(theta-theta0)+5.6(10^-5)*(theta-theta0)^2-7.0*(10^-7)*(theta-theta0)^3+9.0*(10^-10)*(theta-theta0)^4]
(Allinger et. al. J.Am.Chem.Soc., Vol. 111, No. 23, 1989)
"""
a, b, c, data = angle
if (data['force_field_type']=="167_165_167"): ### in the case of square planar coordination of Cu-paddle-wheel, fourier angle must be used
data['potential'] = AnglePotential.CosinePeriodic()
data['potential'].C = 100 # Need to be parameterized!
data['potential'].B = 1
data['potential'].n = 4
return 1
elif (data['force_field_type']=="106_101_106"): ### in the case of square planar coordination of Cu-paddle-wheel, fourier angle must be used
data['potential'] = AnglePotential.CosinePeriodic()
data['potential'].C = 0 # Need to be parameterized!
data['potential'].B = 1
data['potential'].n = 4
return 1
elif (data['force_field_type']=="103_101_106"): ### in the case of square planar coordination of Cu-paddle-wheel, fourier angle must be used
data['potential'] = AnglePotential.CosinePeriodic()
data['potential'].C = 0 # Need to be parameterized!
data['potential'].B = 1
data['potential'].n = 4
return 1
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
ab_bond = self.graph[a][b]
bc_bond = self.graph[b][c]
atom_a_fflabel = a_data['force_field_type']
atom_b_fflabel = b_data['force_field_type']
atom_c_fflabel = c_data['force_field_type']
ang_ff_tmp = atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
Ktheta = MOFFF_angles[data['force_field_type']][0]
theta0 = MOFFF_angles[data['force_field_type']][1]
### BondAngle ###
baN1 = MOFFF_angles[data['force_field_type']][4]
baN2 = MOFFF_angles[data['force_field_type']][5]
### BondBond ###
bbM = MOFFF_angles[data['force_field_type']][6]
if not (ang_ff_tmp == data['force_field_type']): # switching the force constants in the case of assigning swaped angle_ff_label
buf1 = atom_a_fflabel
atom_a_fflabel = atom_c_fflabel
atom_c_fflabel = buf1
buf2 = baN1
baN1=baN2
baN2=buf2
### assingning the equilibrium distance of each bond from FF
bond1_label = atom_a_fflabel+"_"+atom_b_fflabel
bond2_label = atom_b_fflabel+"_"+atom_c_fflabel
if (bond1_label) in MOFFF_bonds:
r1 = MOFFF_bonds[bond1_label][1]
else:
bond1_label = atom_b_fflabel+"_"+atom_a_fflabel
r1 = MOFFF_bonds[bond1_label][1]
if (bond2_label) in MOFFF_bonds:
r2 = MOFFF_bonds[bond2_label][1]
else:
bond2_label = atom_c_fflabel+"_"+atom_b_fflabel
r2 = MOFFF_bonds[bond2_label][1]
### Unit conversion ###
bbM = bbM *71.94 # (TODO) maybe is wrong!
baN1 = 2.51118 * baN1 / (DEG2RAD)
baN2 = 2.51118 * baN2/ (DEG2RAD)
K2 = 0.021914*Ktheta/(DEG2RAD**2)
K3 = -0.014*K2/(DEG2RAD**1)
K4 = 5.6e-5*K2/(DEG2RAD**2)
data['potential'] = AnglePotential.Class2()
data['potential'].theta0 = theta0
data['potential'].K2 = K2
data['potential'].K3 = K3
data['potential'].K4 = K4
data['potential'].bb.M = 0.0 #bbM
data['potential'].bb.r1 = r1
data['potential'].bb.r2 = r2
data['potential'].ba.N1 = 0.0 #baN1
data['potential'].ba.N2 = 0.0 # baN2
data['potential'].ba.r1 = r1
data['potential'].ba.r2 = r2
return 1
[docs] def dihedral_term(self, dihedral):
"""fourier diherdral
Ew = (V1/2)(1 + cos w) + (V2/2)(1 - cos 2*w)+(V3/2)(1 + cos 3*w)+(V4/2)(1 + cos 4*w)
(Allinger et. al. J.Am.Chem.Soc., Vol. 111, No. 23, 1989)
"""
a,b,c,d, data = dihedral
kt1 = 0.5 * MOFFF_dihedrals[data['force_field_type']][0]
kt2 = 0.5 * MOFFF_dihedrals[data['force_field_type']][3]
kt3 = 0.5 * MOFFF_dihedrals[data['force_field_type']][6]
kt4 = 0.5 * MOFFF_dihedrals[data['force_field_type']][9]
n1 = MOFFF_dihedrals[data['force_field_type']][2]
n2 = MOFFF_dihedrals[data['force_field_type']][5]
n3 = MOFFF_dihedrals[data['force_field_type']][8]
n4 = MOFFF_dihedrals[data['force_field_type']][11]
d1 = -1.0 * MOFFF_dihedrals[data['force_field_type']][1]
d2 = -1.0 * MOFFF_dihedrals[data['force_field_type']][4]
d3 = -1.0 * MOFFF_dihedrals[data['force_field_type']][7]
d4 = -1.0 * MOFFF_dihedrals[data['force_field_type']][10]
ki = [kt1,kt2,kt3,kt4]
ni = [n1,n2,n3,n4]
di = [d1,d2,d3,d4]
data['potential'] = DihedralPotential.Fourier()
data['potential'].Ki = ki
data['potential'].ni = ni
data['potential'].di = di
return 1
[docs] def improper_term(self, improper):
"""Harmonic improper"""
a,b,c,d, data = improper
Kopb = MOFFF_opbends[data['force_field_type']][0]/(DEG2RAD**2)*0.02191418
c0 = MOFFF_opbends[data['force_field_type']][1]
data['potential'] = ImproperPotential.Harmonic()
data['potential'].K = Kopb
data['potential'].chi0 = c0
return 1
[docs] def pair_terms(self, node, data, cutoff, **kwargs):
"""
Buckingham equation in MM3 type is used!
Also, Table for short range coulombic interactions
"""
eps = MOFFF_atoms[data['force_field_type']][4]
sig = MOFFF_atoms[data['force_field_type']][3]
data['pair_potential'] = PairPotential.Buck()
data['pair_potential'].cutoff = cutoff
data['pair_potential'].eps = eps
data['pair_potential'].sig = sig
data['tabulated_potential'] = True
data['table_function'] = self.pair_coul_term
data['table_potential'] = PairPotential.Table()
[docs] def pair_coul_term(self, node1, node2, data):
"""MOF-FF uses a damped gaussian for close-range interactions.
This is added in a tabulated form.
k = 332.063711 kcal*angstroms/e^2
E_ij = k*qi*qj*erf(r_ij/sig_ij)/r_ij
--- From Wolfram Alpha ---
F_ij = - (K*qi*qj) * (2/(pi^(.5) * sig_ij)) * [ e^(-r_ij^2/sig_ij^2) / r_ij - erf(r_ij/sig_ij)/r_ij^2 ]
---
N is set to 1000 by default
"""
str = ""
# kcal/mol energy units assumed...
K = 332.063711
n = 5000
rlow=0.01
R = np.linspace(rlow, self.cutoff+2., n)
ff1 = node1['force_field_type']
ff2 = node2['force_field_type']
qi = node1['charge']
qj = node2['charge']
sigi = MOFFF_atoms[ff1][7]
sigj = MOFFF_atoms[ff2][7]
sigij = math.sqrt(sigi**2+sigj**2)
E_coeff = K*qi*qj
str += "# damped coulomb potential for %s - %s\n"%(ff1, ff2)
str += "GAUSS_%s_%s\n"%(ff1, ff2)
str += "N %i R %.2f %.f\n\n"%(n, rlow, self.cutoff+2.)
data['table_potential'].style = 'linear'
data['table_potential'].N = n
data['table_potential'].keyword = 'ewald'
data['table_potential'].cutoff = self.cutoff
data['table_potential'].entry = "GAUSS_%s_%s"%(ff1, ff2)
data['table_potential'].N = n
for i, r in enumerate(R):
rsq = r**2
e = E_coeff*math.erf(r/sigij)/r
f = -E_coeff * (math.exp(-(rsq)/(sigij**2))/ r* 2/(math.sqrt(math.pi) * sigij) - math.erf(r/sigij)/(rsq))
str += "%i %.3f %f %f\n"%(i+1, r, e, f)
return(str)
[docs] def special_commands(self):
st = ["%-15s %s"%("pair_modify", "tail yes"),
"%-15s %s"%("special_bonds", "lj 0 0 1 coul 1 1 1 #!!! Note: Gaussian charges have to be used!"),
"%-15s %.1f"%("dielectric", 1.0)]
return st
[docs]class FMOFCu(ForceField):
[docs] def __init__(self, **kwargs):
self.pair_in_data = False
self.keep_metal_geometry = False
self.graph = None
# override existing arguments with kwargs
for key, value in kwargs.items():
setattr(self, key, value)
if (self.graph is not None):
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def insert_graph(self, graph):
self.graph = graph
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def detect_ff_terms(self):
# for each atom determine the ff type if it is None
FMOFCu_organics = [ "O", "C","H","F" ]
FMOFCu_metals = ["Cu"]
for node, atom in self.graph.nodes_iter2(data=True):
flag_coordination=False
if atom['force_field_type'] is None:
type_assigned=False
neighbours = [self.graph.node[i] for i in self.graph.neighbors(node)]
neighbour_elements = [a['element'] for a in neighbours]
if atom['element'] in FMOFCu_organics:
if (atom['element'] == "O"):
if("H" in neighbour_elements): #O-H
atom['force_field_type']="75"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
elif ("C" in neighbour_elements): # Carboxylate
for i in self.graph.neighbors(node):
if (self.graph.node[i]['element']=="C"):
neighboursofneighbour = self.graph.neighbors(i)
neighboursofneighbour_elements=[self.graph.node[at]['element'] for at in neighboursofneighbour]
for atom1 in neighboursofneighbour:
if (self.graph.node[atom1]['element']=="O"):
bondeds=[self.graph.node[j] for j in self.graph.neighbors(atom1)]
bondeds_elements=[at['element'] for at in bondeds]
if("H" in bondeds_elements):
flag_coordination=True
if (flag_coordination):
atom['force_field_type']="180"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
else:
atom['force_field_type']="170"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
else:
print("Oxygen number : %i could not be recognized!"%atom.index)
sys.exit()
elif (atom['element'] == "H"):
if ("O" in neighbour_elements):
atom['force_field_type']="24"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
elif("C" in neighbour_elements):
atom['force_field_type']="915"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
else:
print("Hydrogen number : %i could not be recognized!"%atom.index)
elif (atom['element'] == "F"):
if (set(neighbour_elements) <= set(["C"])):
atom['force_field_type']="911"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
else:
print("Flourine number : %i could not be recognized!"%atom.index)
elif( atom['element']=="C"):
if ("O" in neighbour_elements):
atom['force_field_type']="913" # C-acid
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
elif ("H" in neighbour_elements):
atom['force_field_type']="912" # C- benzene we should be careful that in this case C in ligand has also bond with H, but not in the FF
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
elif ("F" in neighbour_elements):
atom['force_field_type']="101" # C- benzene we should be careful that in this case C in ligand has also bond with H, but not in the FF
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
elif (set(neighbour_elements)<=set(["C"])):
for i in self.graph.neighbors(node):
neighboursofneighbour = self.graph.neighbors(i)
neighboursofneighbour_elements=[self.graph.node[at]['element'] for at in neighboursofneighbour]
if ("O" in neighboursofneighbour_elements):
atom['force_field_type']="902"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
type_assigned=True
if (type_assigned==False) and (atom['hybridization']=="aromatic"):
atom['force_field_type']="903"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
elif (type_assigned==False) and (atom['hybridization']=="sp3"):
atom['force_field_type']="901"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
elif (type_assigned==False):
print("Carbon number : %i could not be recognized! erorr1 %s "%(atom.index, atom.hybridization))
else:
print("Carbon number : %i could not be recognized! error2"%atom.index)
elif atom['element'] in FMOFCu_metals:
if (atom['element'] == "Cu"):
atom['force_field_type']="185"
atom['charge']=FMOFCu_atoms[atom['force_field_type']][6]
else:
print('Error!! Cannot detect atom types. Atom type does not exist in FMOFCu-FF!')
# atom.charge=0
else:
print('FFtype is already assigned!')
""" """ """ """ """
Assigning force field type of bonds
""" """ """ """ """
for a, b, bond in self.graph.edges_iter2(data=True):
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
atom_a_fflabel, atom_b_fflabel = a_atom['force_field_type'], b_atom['force_field_type']
bond1_fflabel=atom_a_fflabel+"_"+atom_b_fflabel
bond2_fflabel=atom_b_fflabel+"_"+atom_a_fflabel
if bond1_fflabel in FMOFCu_bonds:
bond['force_field_type']=bond1_fflabel
elif bond2_fflabel in FMOFCu_bonds:
bond['force_field_type']=bond2_fflabel
else:
print ("%s bond does not exist in FF!"%(bond1_fflabel))
exit()
""" """ """ """ """
Assigning force field type of angles
""" """ """ """ """
missing_labels=[]
for b , data in self.graph.nodes_iter2(data=True):
# compute and store angle terms
try:
missing_angles=[]
ang_data = data['angles']
for (a, c), val in ang_data.items():
a_atom = self.graph.node[a]
b_atom = data
c_atom = self.graph.node[c]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
angle1_fflabel=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
angle2_fflabel=atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
if (angle1_fflabel=="170_185_170"):
val['force_field_type']=angle1_fflabel
# elif (angle1_fflabel=="103_101_106") or (angle1_fflabel=="106_101_103"):
# val['force_field_type']="103_101_106"
# elif (angle1_fflabel=="106_101_106"):
# val['force_field_type']=angle1_fflabel
elif angle1_fflabel in FMOFCu_angles:
val['force_field_type']=angle1_fflabel
elif angle2_fflabel in FMOFCu_angles:
val['force_field_type']=angle2_fflabel
else:
print("1")
missing_angles.append((a,c))
missing_labels.append(angle1_fflabel)
for key in missing_angles:
del ang_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s angle does not exist in FF!"%(ff_label))
""" """ """ """ """
Assigning force field type of dihedrals
""" """ """ """ """
missing_labels=[]
for b, c, data in self.graph.edges_iter2(data=True):
try:
missing_dihedral=[]
dihed_data = data['dihedrals']
for (a, d), val in dihed_data.items():
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
c_atom = self.graph.node[c]
d_atom = self.graph.node[d]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
atom_d_fflabel = d_atom['force_field_type']
dihedral1_fflabel=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel+"_"+atom_d_fflabel
dihedral2_fflabel=atom_d_fflabel+"_"+atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
if dihedral1_fflabel in FMOFCu_dihedrals:
val['force_field_type']=dihedral1_fflabel
elif dihedral2_fflabel in FMOFCu_dihedrals:
val['force_field_type']=dihedral2_fflabel
else:
missing_dihedral.append((a,d))
missing_labels.append(dihedral1_fflabel)
for key in missing_dihedral:
del dihed_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s dihedral does not exist in FF!"%(ff_label))
""" """ """ """ """
Assigning force field type of impropers
""" """ """ """ """
missing_labels=[]
for b, data in self.graph.nodes_iter2(data=True):
try:
missing_improper=[]
imp_data = data['impropers']
for (a, c, d), val in imp_data.items():
a_atom = self.graph.node[a]
b_atom = self.graph.node[b]
c_atom = self.graph.node[c]
d_atom = self.graph.node[d]
atom_a_fflabel = a_atom['force_field_type']
atom_b_fflabel = b_atom['force_field_type']
atom_c_fflabel = c_atom['force_field_type']
atom_d_fflabel = d_atom['force_field_type']
improper_fflabel=atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel+"_"+atom_d_fflabel
if improper_fflabel in FMOFCu_opbends:
val['force_field_type']=improper_fflabel
else:
missing_improper.append((a,c,d))
missing_labels.append(improper_fflabel)
for key in missing_improper:
del imp_data[key]
except KeyError:
pass
for ff_label in set(missing_labels):
print ("%s improper does not exist in FF!"%(ff_label))
[docs] def bond_term(self, edge):
"""class2 bond"""
"""
Es=71.94*Ks*(l-l0)^2[1-2.55(l-l0)+(7/12)*2.55*(l-l0)^2]
(Allinger et. al. J.Am.Chem.Soc., Vol. 111, No. 23, 1989)
"""
n1, n2, data = edge
Ks = FMOFCu_bonds[data['force_field_type']][0]
l0 = FMOFCu_bonds[data['force_field_type']][1]
K2= 71.94*Ks # mdyne to kcal *(1/2)
K3= -2.55*K2
K4= 3.793125*K2
data['potential'] = BondPotential.Class2()
data['potential'].K2 = K2
data['potential'].K3 = K3
data['potential'].K4 = K4
data['potential'].R0 = l0
return 1
[docs] def angle_term(self, angle):
"""class2 angle"""
"""
Be careful that the 5and6 order terms are vanished here since they are not implemented in LAMMPS!!
Etheta = 0.021914*Ktheta*(theta-theta0)^2[1-0.014(theta-theta0)+5.6(10^-5)*(theta-theta0)^2-7.0*(10^-7)*(theta-theta0)^3+9.0*(10^-10)*(theta-theta0)^4]
(Allinger et. al. J.Am.Chem.Soc., Vol. 111, No. 23, 1989)
"""
a, b, c, data = angle
if (data['force_field_type']=="170_185_170"): ### in the case of square planar coordination of Cu-paddle-wheel, fourier angle must be used
data['potential'] = AnglePotential.CosinePeriodic()
data['potential'].C = 100 # Need to be parameterized!
data['potential'].B = 1
data['potential'].n = 4
return 1
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
ab_bond = self.graph[a][b]
bc_bond = self.graph[b][c]
atom_a_fflabel = a_data['force_field_type']
atom_b_fflabel = b_data['force_field_type']
atom_c_fflabel = c_data['force_field_type']
ang_ff_tmp = atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
Ktheta = FMOFCu_angles[data['force_field_type']][0]
theta0 = FMOFCu_angles[data['force_field_type']][1]
### BondAngle ###
baN1 = FMOFCu_angles[data['force_field_type']][4]
baN2 = FMOFCu_angles[data['force_field_type']][5]
### BondBond ###
bbM = FMOFCu_angles[data['force_field_type']][6]
if not (ang_ff_tmp == data['force_field_type']): # switching the force constants in the case of assigning swaped angle_ff_label
buf1 = atom_a_fflabel
atom_a_fflabel = atom_c_fflabel
atom_c_fflabel = buf1
buf2 = baN1
baN1=baN2
baN2=buf2
### assingning the equilibrium distance of each bond from FF
bond1_label = atom_a_fflabel+"_"+atom_b_fflabel
bond2_label = atom_b_fflabel+"_"+atom_c_fflabel
if (bond1_label) in FMOFCu_bonds:
r1 = FMOFCu_bonds[bond1_label][1]
else:
bond1_label = atom_b_fflabel+"_"+atom_a_fflabel
r1 = FMOFCu_bonds[bond1_label][1]
if (bond2_label) in FMOFCu_bonds:
r2 = FMOFCu_bonds[bond2_label][1]
else:
bond2_label = atom_c_fflabel+"_"+atom_b_fflabel
r2 = FMOFCu_bonds[bond2_label][1]
### Unit conversion ###
bbM = bbM *71.94 # (TODO) maybe is wrong!
baN1 = 2.51118 * baN1 / (DEG2RAD)
baN2 = 2.51118 * baN2/ (DEG2RAD)
K2 = 0.021914*Ktheta/(DEG2RAD**2)
K3 = -0.014*K2/(DEG2RAD**1)
K4 = 5.6e-5*K2/(DEG2RAD**2)
data['potential'] = AnglePotential.Class2()
data['potential'].theta0 = theta0
data['potential'].K2 = K2
data['potential'].K3 = K3
data['potential'].K4 = K4
data['potential'].ba.N1 = baN1
data['potential'].ba.N2 = baN2
data['potential'].ba.r1 = r1
data['potential'].ba.r2 = r2
return 1
[docs] def dihedral_term(self, dihedral):
"""fourier diherdral"""
"""
Ew = (V1/2)(1 + cos w) + (V2/2)(1 - cos 2*w)+(V3/2)(1 + cos 3*w)+(V4/2)(1 + cos 4*w)
(Allinger et. al. J.Am.Chem.Soc., Vol. 111, No. 23, 1989)
"""
a,b,c,d, data = dihedral
kt1 = 0.5 * FMOFCu_dihedrals[data['force_field_type']][0]
kt2 = 0.5 * FMOFCu_dihedrals[data['force_field_type']][3]
kt3 = 0.5 * FMOFCu_dihedrals[data['force_field_type']][6]
kt4 = 0.5 * FMOFCu_dihedrals[data['force_field_type']][9]
n1 = FMOFCu_dihedrals[data['force_field_type']][2]
n2 = FMOFCu_dihedrals[data['force_field_type']][5]
n3 = FMOFCu_dihedrals[data['force_field_type']][8]
n4 = FMOFCu_dihedrals[data['force_field_type']][11]
d1 = -1.0 * FMOFCu_dihedrals[data['force_field_type']][1]
d2 = -1.0 * FMOFCu_dihedrals[data['force_field_type']][4]
d3 = -1.0 * FMOFCu_dihedrals[data['force_field_type']][7]
d4 = -1.0 * FMOFCu_dihedrals[data['force_field_type']][10]
ki = [kt1,kt2,kt3,kt4]
ni = [n1,n2,n3,n4]
di = [d1,d2,d3,d4]
data['potential'] = DihedralPotential.Fourier()
data['potential'].Ki = ki
data['potential'].ni = ni
data['potential'].di = di
return 1
[docs] def improper_term(self, improper):
"""class2 diherdral"""
a,b,c,d, data = improper
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
d_data = self.graph.node[d]
atom_a_fflabel=a_data['force_field_type']
atom_b_fflabel=b_data['force_field_type']
atom_c_fflabel=c_data['force_field_type']
atom_d_fflabel=d_data['force_field_type']
Kopb = FMOFCu_opbends[data['force_field_type']][0]/(DEG2RAD**2)*0.02191418
c0 = FMOFCu_opbends[data['force_field_type']][1]
"""
Angle-Angle term
"""
M1 = FMOFCu_opbends[data['force_field_type']][2]/(DEG2RAD**2)*0.02191418*(-1)/3. # Three times counting one angle-angle interaction
M2 = FMOFCu_opbends[data['force_field_type']][3]/(DEG2RAD**2)*0.02191418*(-1)/3. # Three times counting one angle-angle interaction
M3 = FMOFCu_opbends[data['force_field_type']][4]/(DEG2RAD**2)*0.02191418*(-1)/3. # Three times counting one angle-angle interaction
ang1_ff_label = atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
ang2_ff_label = atom_a_fflabel+"_"+atom_b_fflabel+"_"+atom_d_fflabel
ang3_ff_label = atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_d_fflabel
if (ang1_ff_label) in FMOFCu_angles:
Theta1 = FMOFCu_angles[ang1_ff_label][1]
else:
ang1_ff_label = atom_c_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
Theta1 = FMOFCu_angles[ang1_ff_label][1]
if (ang2_ff_label) in FMOFCu_angles:
Theta2 = FMOFCu_angles[ang2_ff_label][1]
else:
ang2_ff_label = atom_d_fflabel+"_"+atom_b_fflabel+"_"+atom_a_fflabel
Theta2 = FMOFCu_angles[ang2_ff_label][1]
if (ang3_ff_label) in FMOFCu_angles:
Theta3 = FMOFCu_angles[ang3_ff_label][1]
else:
ang3_ff_label = atom_d_fflabel+"_"+atom_b_fflabel+"_"+atom_c_fflabel
Theta3 = FMOFCu_angles[ang3_ff_label][1]
data['potential'] = ImproperPotential.Class2() #does not work now!
data['potential'].K = Kopb
data['potential'].chi0 = c0
data['potential'].aa.M1 = M1
data['potential'].aa.M2 = M2
data['potential'].aa.M3 = M3
data['potential'].aa.theta1 = Theta1
data['potential'].aa.theta2 = Theta2
data['potential'].aa.theta3 = Theta3
return 1
[docs] def pair_terms( self, node , data, cutoff, **kwargs):
"""
Buckingham equation in MM3 type is used!
"""
eps = FMOFCu_atoms[data['force_field_type']][4]
sig = FMOFCu_atoms[data['force_field_type']][3]
data['pair_potential']=PairPotential.BuckCoulLong()
data['pair_potential'].cutoff= cutoff
data['pair_potential'].eps = eps
data['pair_potential'].sig = sig
[docs] def special_commands(self):
st = ["%-15s %s"%("pair_modify", "tail yes"),
"%-15s %s"%("special_bonds", "lj/coul 0.0 0.0 1"),
"%-15s %.2f"%("dielectric", 1.50)]
return st
[docs]class UFF(ForceField):
"""Parameterize the periodic material with the UFF parameters.
NB: I have come across important information regarding the
implementation of UFF from the author of MCCCS TOWHEE.
It can be found here: (as of 05/11/2015)
http://towhee.sourceforge.net/forcefields/uff.html
The ammendments mentioned that document are included here
"""
[docs] def __init__(self, **kwargs):
self.pair_in_data = True
self.keep_metal_geometry = False
self.graph = None
# override existing arguments with kwargs
for key, value in kwargs.items():
setattr(self, key, value)
if (self.graph is not None):
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def pair_terms(self, node, data, cutoff, charges=True):
"""Add L-J term to atom"""
if(charges):
data['pair_potential'] = PairPotential.LjCutCoulLong()
else:
data['pair_potential'] = PairPotential.LjCut()
data['pair_potential'].eps = UFF_DATA[data['force_field_type']][3]
data['pair_potential'].sig = UFF_DATA[data['force_field_type']][2]*(2**(-1./6.))
data['pair_potential'].cutoff = cutoff
[docs] def bond_term(self, edge):
"""Harmonic assumed"""
n1, n2, data = edge
n1_data, n2_data = self.graph.node[n1], self.graph.node[n2]
fflabel1, fflabel2 = n1_data['force_field_type'], n2_data['force_field_type']
r_1 = UFF_DATA[fflabel1][0]
r_2 = UFF_DATA[fflabel2][0]
chi_1 = UFF_DATA[fflabel1][8]
chi_2 = UFF_DATA[fflabel2][8]
rbo = -0.1332*(r_1 + r_2)*math.log(data['order'])
ren = r_1*r_2*(((math.sqrt(chi_1) - math.sqrt(chi_2))**2))/(chi_1*r_1 + chi_2*r_2)
r0 = (r_1 + r_2 + rbo - ren)
# The values for K in the UFF paper were set such that in the final
# harmonic function, they would be divided by '2' to satisfy the
# form K/2(R-Req)**2
# in Lammps, the value for K is already assumed to be divided by '2'
K = 664.12*(UFF_DATA[fflabel1][5]*UFF_DATA[fflabel2][5])/(r0**3) / 2.
if (self.keep_metal_geometry) and (n1_data['atomic_number'] in METALS
or n2_data['atomic_number'] in METALS):
r0 = data['length']
data['potential'] = BondPotential.Harmonic()
data['potential'].K = K
data['potential'].R0 = r0
return 1
[docs] def angle_term(self, angle):
"""several cases exist where the type of atom in a particular environment is considered
in both the parameters and the functional form of the term.
A small cosine fourier expansion in (theta)
E_0 = K_{IJK} * {sum^{m}_{n=0}}C_{n} * cos(n*theta)
Linear, trigonal-planar, square-planar, and octahedral:
two-term expansion of the above equation, n=0 as well as
n=1, n=3, n=4, and n=4 for the above geometries.
E_0 = K_{IJK}/n^2 * [1 - cos(n*theta)]
in Lammps, the angle syle called 'fourier/simple' can be used
to describe this functional form.
general non-linear case:
three-term Fourier expansion
E_0 = K_{IJK} * [C_0 + C_1*cos(theta) + C_2*cos(2*theta)]
in Lammps, the angle style called 'fourier' can be used to
describe this functional form.
Both 'fourier/simple' and 'fourier' are available from the
USER_MISC package in Lammps, so be sure to compile Lammps with
this package.
"""
# fourier/simple
sf = ['linear', 'trigonal-planar', 'square-planar', 'octahedral']
a, b, c, data = angle
angle_type = self.uff_angle_type(b)
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
ab_bond = self.graph[a][b]
bc_bond = self.graph[b][c]
auff, buff, cuff = a_data['force_field_type'], b_data['force_field_type'], c_data['force_field_type']
theta0 = UFF_DATA[buff][1]
# just check if the central node is a metal, then apply a rigid angle term.
# NB: Functional form may change dynamics, but at this point we will not
# concern ourselves if the force constants are big.
if (self.keep_metal_geometry) and (b_data['atomic_number'] in METALS):
theta0 = self.graph.compute_angle_between(a, b, c)
# should put this angle in the general - non-linear case
# unless the angle is 0 or 180 deg - then linear case.
# here the K value will be scaled by the number of neighbors
angle_type = "None"
# note, coefficient might be too strong here, if the metal
# is octahedral, for example, kappa = ka/16
if np.allclose(theta0, 180.0, atol=0.1):
angle_type = 'linear'
cosT0 = np.cos(theta0*DEG2RAD)
sinT0 = np.sin(theta0*DEG2RAD)
c2 = 1.0 / (4.0*sinT0*sinT0)
c1 = -4.0 * c2 * cosT0
c0 = c2 * (2.0*cosT0*cosT0 + 1.0)
za = UFF_DATA[auff][5]
zc = UFF_DATA[cuff][5]
r_ab = ab_bond['potential'].R0
r_bc = bc_bond['potential'].R0
r_ac = math.sqrt(r_ab*r_ab + r_bc*r_bc - 2.*r_ab*r_bc*cosT0)
beta = 664.12/r_ab/r_bc
ka = beta*(za*zc /(r_ac**5.))*r_ab*r_bc
ka *= (3.*r_ab*r_bc*(1. - cosT0*cosT0) - r_ac*r_ac*cosT0)
if angle_type in sf or (angle_type == 'tetrahedral' and int(theta0) == 90):
if angle_type == 'linear':
kappa = ka
c0 = -1.
B = 1
c1 = 1.
# the description of the actual parameters for 'n' are not obvious
# for the tetrahedral special case from the UFF paper or the write up in TOWHEE.
# The values were found in the TOWHEE source code (eg. Bi3+3).
if angle_type == 'tetrahedral':
kappa = ka/4.
c0 = 1.
B = -1
c1 = 2.
if angle_type == 'trigonal-planar':
kappa = ka/9.
c0 = -1.
B = -1
c1 = 3.
if angle_type == 'square-planar' or angle_type == 'octahedral':
kappa = ka/16.
c0 = -1.
B = 1
c1 = 4.
#data['potential'] = AnglePotential.FourierSimple()
data['potential'] = AnglePotential.CosinePeriodic()
#data['potential'].K = kappa
data['potential'].C = kappa*(c1**2)
#data['potential'].c = c0
data['potential'].B = B
data['potential'].n = c1
# general-nonlinear
else:
#TODO: a bunch of special cases which require molecular recognition here..
# water, for example has it's own theta0 angle.
kappa = ka
data['potential'] = AnglePotential.Fourier()
data['potential'].K = kappa
data['potential'].C0 = c0
data['potential'].C1 = c1
data['potential'].C2 = c2
return 1
[docs] def uff_angle_type(self, b):
name = self.graph.node[b]['force_field_type']
try:
coord_type = name[2]
except IndexError:
# eg, H_, F_
return 'linear'
if coord_type == "1":
return 'linear'
elif coord_type in ["R", "2"]:
return 'trigonal-planar'
elif coord_type == "3":
return 'tetrahedral'
elif coord_type == "4":
return 'square-planar'
elif coord_type == "5":
return 'trigonal-bipyrimidal'
elif coord_type == "6":
return 'octahedral'
elif coord_type == "8":
return 'cubic-antiprism'
else:
print("ERROR: Cannot find coordination type for %s"%name)
sys.exit()
[docs] def dihedral_term(self, dihedral):
"""Use a small cosine Fourier expansion
E_phi = 1/2*V_phi * [1 - cos(n*phi0)*cos(n*phi)]
this is available in Lammps in the form of a harmonic potential
E = K * [1 + d*cos(n*phi)]
NB: the d term must be negated to recover the UFF potential.
"""
a,b,c,d, data = dihedral
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
d_data = self.graph.node[d]
torsiontype = self.graph[b][c]['order']
coord_bc = (self.graph.degree(b), self.graph.degree(c))
bc = (b_data['force_field_type'], c_data['force_field_type'])
M = mul(*coord_bc)
V = 0
n = 0
mixed_case = ((b_data['hybridization'] == 'sp2' or b_data['hybridization'] == 'aromatic') and
c_data['hybridization'] == 'sp3') or \
(b_data['hybridization'] == 'sp3' and
(c_data['hybridization'] == 'sp2' or c_data['hybridization'] == 'aromatic'))
all_sp2 = ((b_data['hybridization'] == 'sp2' or b_data['hybridization'] == 'aromatic') and
c_data['hybridization'] == 'sp2' or c_data['hybridization'] == 'aromatic')
all_sp3 = (b_data['hybridization'] == 'sp3' and
c_data['hybridization'] == 'sp3')
phi0 = 0
if (b_data['atomic_number'] in METALS or c_data['atomic_number'] in METALS):
return None
if all_sp3:
phi0 = 60.0
n = 3
vi = UFF_DATA[b_data['force_field_type']][6]
vj = UFF_DATA[c_data['force_field_type']][6]
if b_data['atomic_number'] == 8:
vi = 2.
n = 2
phi0 = 90.
elif b_data['atomic_number'] in (16, 34, 52, 84):
vi = 6.8
n = 2
phi0 = 90.0
if c_data['atomic_number'] == 8:
vj = 2.
n = 2
phi0 = 90.0
elif c_data['atomic_number'] in (16, 34, 52, 84):
vj = 6.8
n = 2
phi0 = 90.0
V = (vi*vj)**0.5
elif all_sp2:
ui = UFF_DATA[b_data['force_field_type']][7]
uj = UFF_DATA[c_data['force_field_type']][7]
phi0 = 180.0
n = 2
V = 5.0 * (ui*uj)**0.5 * (1. + 4.18*math.log(torsiontype))
elif mixed_case:
phi0 = 180.0
n = 3
V = 2.
if c_data['hybridization'] == 'sp3':
if c_data['atomic_number'] in (8, 16, 34, 52):
n = 2
phi0 = 90.
elif b_data['hybridization'] == 'sp3':
if b_data['atomic_number'] in (8, 16, 34, 52):
n = 2
phi0 = 90.0
# special case group 6 elements
if n==2:
ui = UFF_DATA[b_data['force_field_type']][7]
uj = UFF_DATA[c_data['force_field_type']][7]
V = 5.0 * (ui*uj)**0.5 * (1. + 4.18*math.log(torsiontype))
V /= float(M)
nphi0 = n*phi0
if abs(math.sin(nphi0*DEG2RAD)) > 1.0e-3:
print("WARNING!!! nphi0 = %r" % nphi0)
if (self.keep_metal_geometry) and (b_data['atomic_number'] in METALS or
c_data['atomic_number'] in METALS):
# must use different potential with minimum at the computed dihedral
# angle.
#nphi0 = n*self.graph.compute_dihedral_between(a, b, c, d)
#data['potential'] = DihedralPotential.Charmm()
#data['potential'].K = 0.5
#data['potential'].d = 180 + nphi0
#data['potential'].n = n
# NO torsional terms in UFF for non-main group elements
return None
if V==0.:
return None
data['potential'] = DihedralPotential.Harmonic()
data['potential'].K = 0.5*V
data['potential'].d = -math.cos(nphi0*DEG2RAD)
data['potential'].n = n
return 1
[docs] def improper_term(self, improper):
"""
The improper function can be described with a fourier function
E = K*[C_0 + C_1*cos(w) + C_2*cos(2*w)]
NB: not sure if keep metal geometry is important here.
"""
a, b, c, d, data = improper
b_data = self.graph.node[b]
a_ff = self.graph.node[a]['force_field_type']
c_ff = self.graph.node[c]['force_field_type']
d_ff = self.graph.node[d]['force_field_type']
if not b_data['atomic_number'] in (6, 7, 8, 15, 33, 51, 83):
return None
if b_data['force_field_type'] in ('N_3', 'N_2', 'N_R', 'O_2', 'O_R'):
c0 = 1.0
c1 = -1.0
c2 = 0.0
koop = 6.0
elif b_data['force_field_type'] in ('P_3+3', 'As3+3', 'Sb3+3', 'Bi3+3'):
if b_data['force_field_type'] == 'P_3+3':
phi = 84.4339 * DEG2RAD
elif b_data['force_field_type'] == 'As3+3':
phi = 86.9735 * DEG2RAD
elif b_data['force_field_type'] == 'Sb3+3':
phi = 87.7047 * DEG2RAD
else:
phi = 90.0 * DEG2RAD
c1 = -4.0 * math.cos(phi)
c2 = 1.0
c0 = -1.0*c1*math.cos(phi) + c2*math.cos(2.0*phi)
koop = 22.0
elif b_data['force_field_type'] in ('C_2', 'C_R'):
c0 = 1.0
c1 = -1.0
c2 = 0.0
koop = 6.0
if 'O_2' in (a_ff, c_ff, d_ff):
# check to make sure an aldehyde (i.e. not carboxylate bonded to metal)
if a_ff == "O_2" and self.graph.degree(a) == 1:
koop = 50.0
elif c_ff == "O_2" and self.graph.degree(c) == 1:
koop = 50.0
elif d_ff == "O_2" and self.graph.degree(d) == 1:
koop = 50.0
else:
return None
koop /= 3.
data['potential'] = ImproperPotential.Fourier()
data['potential'].K = koop
data['potential'].C0 = c0
data['potential'].C1 = c1
data['potential'].C2 = c2
return 1
[docs] def special_commands(self):
st = ["%-15s %s %s"%("pair_modify", "tail yes", "mix arithmetic"),
"%-15s %s"%("special_bonds", "lj/coul 0.0 0.0 1.0"),
"%-15s %.1f"%('dielectric', 1.0)
]
return st
[docs] def detect_ff_terms(self):
# for each atom determine the ff type if it is None
organics = ["C", "N", "O", "S"]
halides = ["F", "Cl", "Br", "I"]
sqpl = ["He", "Ne", "Ar", "Ni", "Kr", "Pd", "Xe", "Pt", "Au", "Rn"]
for node, data in self.graph.nodes_iter2(data=True):
if data['force_field_type'] is None:
if data['element'] in organics:
if data['hybridization'] == "sp3":
data['force_field_type'] = "%s_3"%data['element']
if data['element'] == "O" and self.graph.degree(node) >= 2:
neigh_elem = set([self.graph.node[i]['element'] for i in self.graph.neighbors(node)])
if neigh_elem <= metals and self.graph.degree(node) == 2:
data['force_field_type'] = "O_2"
elif neigh_elem <= metals and self.graph.degree(node) == 3:
data['force_field_type'] = "O_3"
# zeolites
if neigh_elem <= set(["Si", "Al"]):
data['force_field_type'] = "O_3_z"
elif data['element'] == "S":
# default sp3 hybridized sulphur set to S_3+6
data['force_field_type'] = "S_3+6"
elif data['hybridization'] == "aromatic":
data['force_field_type'] = "%s_R"%data['element']
# fix to make the angle 120
#if data['element'] == "O":
# data['force_field_type'] = "O_2"
elif data['hybridization'] == "sp2":
data['force_field_type'] = "%s_2"%data['element']
elif data['hybridization'] == "sp":
data['force_field_type'] = "%s_1"%data['element']
elif data['element'] == "H":
data['force_field_type'] = "H_"
elif data['element'] in halides:
data['force_field_type'] = data['element']
if data['element'] == "F":
data['force_field_type'] += "_"
elif data['element'] == "I":
data['force_field_type'] += "_"
elif data['element'] == "Li":
data['force_field_type'] = data['element']
else:
ffs = list(UFF_DATA.keys())
valency = self.graph.degree(node)
# temp fix for some real geometrical analysis
if (valency == 4) and (data['element'] not in sqpl):
valency = 3
for j in ffs:
if data['element'] == j[:2].strip("_"):
try:
if valency == int(j[2]):
data['force_field_type'] = j
except IndexError:
# no valency for this atom
data['force_field_type'] = j
if data['force_field_type'] is None:
assigned = False
# find the first entry that corresponds to this element and print a warning
for j in list(UFF_DATA.keys()):
if data['element'] == j[:2].strip("_"):
data['force_field_type'] = j
neigh = self.graph.degree(node)
print("WARNING: Atom %i element "%data['index'] +
"%s has %i neighbors, "%(data['element'], neigh)+
"but was assigned %s as a force field type!"%(j))
assigned = True
if not assigned:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
[docs]class Dreiding(ForceField):
[docs] def __init__(self, graph=None, h_bonding=False, **kwargs):
self.pair_in_data = True
self.h_bonding = h_bonding
self.bondtype = 'harmonic'
self.keep_metal_geometry = False
# override existing arguments with kwargs
for key, value in kwargs.items():
setattr(self, key, value)
if (graph is not None):
self.graph = graph
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def bond_term(self, edge):
"""The DREIDING Force Field contains two possible bond terms, harmonic and Morse.
The authors recommend using harmonic as a default, and Morse potentials for more
'refined' calculations.
Here we will assume a harmonic term by default, then the user can chose to switch
to Morse if they so choose. (change type argument to 'morse')
E = 0.5 * K * (R - Req)^2
E = D * [exp{-(alpha*R - Req)} - 1]^2
"""
n1, n2, data = edge
n1_data, n2_data = self.graph.node[n1], self.graph.node[n2]
fflabel1, fflabel2 = n1_data['force_field_type'], n2_data['force_field_type']
R1 = DREIDING_DATA[fflabel1][0]
R2 = DREIDING_DATA[fflabel2][0]
order = data['order']
K = order*700.
D = order*70.
Re = R1 + R2 - 0.01
if (self.keep_metal_geometry) and (n1_data['atomic_number'] in METALS
or n2_data['atomic_number'] in METALS):
if self.bondtype.lower() == "harmonic":
data['potential'] = BondPotential.Harmonic()
data['potential'].K = K/2.
data['potential'].R0 = data['length']
elif self.bondtype.lower() == "morse":
alpha = order * np.sqrt(K/2./D)
data['potential'] = BondPotential.Morse()
data['potential'].D = D
data['potential'].alpha = alpha
data['potential'].R = data['length']
return 1
if self.bondtype.lower() == 'harmonic':
data['potential'] = BondPotential.Harmonic()
data['potential'].K = K/2.
data['potential'].R0 = Re
elif self.bondtype.lower() == 'morse':
alpha = order * np.sqrt(K/2./D)
data['potential'] = BondPotential.Morse()
data['potential'].D = D
data['potential'].alpha = alpha
data['potential'].R0 = Re
else:
print("ERROR: Cannot recognize bond potential for Dreiding: %s"%self.bondtype)
print("Please chose between 'morse' or 'harmonic'")
sys.exit()
return 1
[docs] def angle_term(self, angle):
"""
Harmonic cosine angle
E = 0.5*C*[cos(theta) - cos(theta0)]^2
This is available in LAMMPS as the cosine/squared angle style
(NB. the prefactor includes the usual 1/2 term.)
if theta0 == 180, use
E = K*(1 + cos(theta))
This is available in LAMMPS as the cosine angle style
"""
a, b, c, data = angle
K = 100.0
a_data, b_data, c_data = self.graph.node[a], self.graph.node[b], self.graph.node[c]
btype = b_data['force_field_type']
theta0 = DREIDING_DATA[btype][1]
if (self.keep_metal_geometry) and (b_data['atomic_number'] in METALS):
theta0 = self.graph.compute_angle_between(a, b, c)
data['potential'] = AnglePotential.CosineSquared()
K = 0.5*K/(np.sin(theta0*DEG2RAD))**2
data['potential'].K = K
data['potential'].theta0 = theta0
return 1
if (theta0 == 180.):
data['potential'] = AnglePotential.Cosine()
data['potential'].K = K / 2.
# Dreiding suggests setting K = 100.0, but the resulting maxima in the cosine function
# wind up as 200.0 due to the functional form. I have divided by two in case this is an
# error in the intended Dreiding force field.
#data['potential'].K = K
elif (theta0 == 90.0):
# this is for the Cu paddlehweel. This function has minima at 90 and 180 deg
# and is an addition made by Peter Boyd.
# NB: the slope around the minima is similar compared with the CosineSquared function,
# however, the energetic barrier is much lower (50 kcal vs ~ 12). There
# may be some unphysical effects of this parameterization
data['potential'] = AnglePotential.CosinePeriodic()
n = 4 # makes four minima in the angle range 0 - 360
data['potential'].C = K #/n**2
data['potential'].B = 1.
data['potential'].n = n
else:
#data['potential'] = AnglePotential.Harmonic()
data['potential'] = AnglePotential.CosineSquared()
K = 0.5*K/(np.sin(theta0*DEG2RAD))**2
data['potential'].K = K
data['potential'].theta0 = theta0
return 1
[docs] def dihedral_term(self, dihedral):
"""
The DREIDING dihedral is of the form
E = 0.5*V*[1 - cos(n*(phi - phi0))]
LAMMPS has a similar potential 'charmm' which is described as
E = K*[1 + cos(n*phi - d)]
In this case the 'd' term must be multiplied by 'n' before
inputting to lammps. In addition a +180 degrees out-of-phase
shift must be added to 'd' to ensure that the potential behaves
the same as the DREIDING article intended.
"""
monovalent = ["Na", "F_", "Cl", "Br", "I_", "K"]
a,b,c,d, data = dihedral
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
d_data = self.graph.node[d]
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
order = self.graph[b][c]['order']
a_hyb = a_data['hybridization']
b_hyb = b_data['hybridization']
c_hyb = c_data['hybridization']
d_hyb = d_data['hybridization']
# special cases associated with oxygen column, listed here
oxygen_sp3 = ["O_3", "S_3", "Se3", "Te3"]
non_oxygen_sp2 = ["C_R", "N_R", "B_2", "C_2", "N_2"]
sp2 = ["aromatic", "sp2"]
# default is to include the full 1-4 non-bonded interactions.
# but this breaks Lammps unless extra work-arounds are in place.
# the weighting is added via a special_bonds keyword
#w = 1.0
w = 0.0
#for ring in d_data['rings']:
# if a in ring and len(ring) == 6:
# w = 0.5
# if a in ring and len(ring) < 6:
# w = 0.0 # otherwise 1-2 and 1-3 non-bonded interactions would be counted.
# g)
if (b_hyb == 'sp' or c_hyb == 'sp') or (btype in monovalent or ctype in monovalent) or \
(b_data['atomic_number'] in METALS or c_data['atomic_number'] in METALS):
V = 0.0
n = 2
phi0 = 180.0
return None
# a)
elif((b_hyb == "sp3")and(c_hyb == "sp3")):
V = 2.0
n = 3
phi0 = 180.0
# h) special case..
if((btype in oxygen_sp3) and (ctype in oxygen_sp3)):
V = 2.0
n = 2
phi0 = 90.0
# b)
elif(((b_hyb in sp2) and (c_hyb == "sp3"))or(
(b_hyb == "sp3") and (c_hyb in sp2))):
V = 1.0
n = 6
phi0 = 0.0
# i) special case..
if(((btype in oxygen_sp3)and(ctype in non_oxygen_sp2)) or
(ctype in oxygen_sp3)and(btype in non_oxygen_sp2)):
V = 2.0
n = 2
phi0 = 180.0
# j) special case..
if(((b_hyb in sp2) and (a_hyb not in sp2))or(
(c_hyb in sp2) and (d_hyb not in sp2))):
V = 2.0
n = 3
phi0 = 180.0
# c)
elif((b_hyb in sp2) and (c_hyb in sp2) and (order == 2.)):
V = 45.0
n = 2
phi0 = 180.0
# d)
elif((b_hyb in sp2) and (c_hyb in sp2) and (order >= 1.5)):
V = 25.0
n = 2
phi0 = 180.0
# e)
elif((b_hyb in sp2) and (c_hyb in sp2) and (order == 1.0)):
V = 5.0
#V = 25.0 # temp fix aromatic...
n = 2
phi0 = 180.0
# f) just check if neighbours are aromatic, then apply the exception
# NB: this may fail for phenyl esters if the oxygen atoms are not
# labelled as "R" (i.e. will fail if they are O_2 or O_3)
if(b_hyb == "aromatic" and c_hyb == "aromatic"):
b_arom = True
for cycle in b_data['rings']:
# Need to make sure this isn't part of the same ring.
if c in cycle:
b_arom = False
print("WARNING: two resonant atoms "+
"%s and %s"%(b_data['ciflabel'], c_data['ciflabel'])+
"in the same ring have a bond order of 1.0! "
"This will likely yield unphysical characteristics"+
" of your system.")
c_arom = True
for cycle in c_data['rings']:
# Need to make sure this isn't part of the same ring.
if b in cycle:
c_arom = False
print("WARNING: two resonant atoms "+
"%s and %s"%(b_data['ciflabel'], c_data['ciflabel'])+
"in the same ring have a bond order of 1.0! "
"This will likely yield unphysical characteristics"+
" of your system.")
if (b_arom and c_arom):
V *= 2.0
# divide V by the number of dihedral angles
# to compute across this a-b bond
b_neigh = self.graph.degree(b) - 1
c_neigh = self.graph.degree(c) - 1
norm = float(b_neigh * c_neigh)
V /= norm
d = n*phi0 + 180
data['potential'] = DihedralPotential.Charmm()
data['potential'].K = V/2.
data['potential'].n = n
data['potential'].d = d
data['potential'].w = w
return 1
[docs] def improper_term(self, improper):
"""Dreiding improper term.
::
b J
/ /
/ /
c----a , DREIDING = K----I
\ \
\ \
d L
For all non-planar configurations, DREIDING uses::
E = 0.5*C*(cos(phi) - cos(phi0))^2
For systems with planar equilibrium geometries, phi0 = 0 ::
E = K*[1 - cos(phi)].
This is available in LAMMPS as the 'umbrella' improper potential.
"""
a, b, c, d, data = improper
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
d_data = self.graph.node[d]
btype = b_data['force_field_type']
# special case: ignore N column
sp3_N = ["N_3", "P_3", "As3", "Sb3"]
K = 40.0
if b_data['hybridization'] == 'sp2' or b_data['hybridization'] == 'aromatic':
K /= 3.
if btype in sp3_N:
return None
omega0 = DREIDING_DATA[btype][4]
data['potential'] = ImproperPotential.Umbrella()
data['potential'].K = K
data['potential'].omega0 = omega0
return 1
[docs] def pair_terms(self, node, data, cutoff, nbpot='LJ', hbpot='morse', charges=True):
""" DREIDING can adopt the exponential-6 or
Ex6 = A*exp{-C*R} - B*R^{-6}
the Lennard-Jones type interactions.
Elj = A*R^{-12} - B*R^{-6}
This will eventually be user-defined
"""
eps = DREIDING_DATA[data['force_field_type']][3]
R = DREIDING_DATA[data['force_field_type']][2]
sig = R*(2**(-1./6.))
if nbpot == "LJ":
if(charges):
data['pair_potential'] = PairPotential.LjCutCoulLong()
else:
data['pair_potential'] = PairPotential.LjCut()
#data['pair_potential'] = PairPotential.LjCharmmCoulLong()
data['pair_potential'].eps = eps
data['pair_potential'].sig = sig
#data['pair_potential'].eps14 = eps
#data['pair_potential'].sig14 = sig
else:
S = DREIDING_DATA[data['force_field_type']][5]
A = eps*(6./(S - 6.))*np.exp(S)
rho = R
C = eps*(S/(S - 6.)*R**6)
data['pair_potential'] = PairPotential.BuckLongCoulLong()
data['pair_potential'].A = A
data['pair_potential'].C = C
data['pair_potential'].rho = rho
data['pair_potential'].cutoff = cutoff
if data['h_bond_donor']:
for n in self.graph.neighbors(node):
if self.graph.node[n]['force_field_type'] == "H__HB":
data['h_bond_function'] = self.hbond_pot(node, hbpot, n)
break
[docs] def hbond_pot(self, node, nbpot, hnode):
"""
DREIDING can describe hbonded donor and acceptors
using a lj function or a morse potential
the morse potential is apparently better, so it
will be default here
DREIDING III h-bonding terms
specified in 10.1021/ja8100227.
Table S3 of the SI of 10.1021/ja8100227 is poorly documented,
I have parameterized, to the best of my ability, what was intended
in that paper. This message posted on the lammps-users
message board http://lammps.sandia.gov/threads/msg36158.html
was helpful
N_3H == tertiary amine
N_3P == primary amine
N_3HP == protonated primary amine
nb. need connectivity information - this is accessed by self.graph
"""
if (nbpot == 'morse'):
pass
elif (nbpot == 'lj'):
# not yet implemented
pass
def hbond_pair(node2, graph, flipped=False):
potential = PairPotential.HbondDreidingMorse()
data = graph.node[node]
data2 = graph.node[node2]
if(flipped):
potential.donor = 'j'
data1 = data2.copy()
data2 = data.copy()
node1 = node2
node2 = node
else:
data1 = data
node1 = node
ff1 = data1['force_field_type']
ff2 = data2['force_field_type']
# generic HB
D0 = 9.5
R0 = 2.75
ineigh = [graph.node[q]['element'] for q in graph.neighbors(node1)]
jneigh = [graph.node[q]['element'] for q in graph.neighbors(node2)]
if(ff1 == "N_3") or (ff1 == "N_R" and ineigh.count("H") == 2):
# tertiary amine
if ((ineigh.count("H") < 3) and (len(ineigh) == 4)or
ineigh.count("H")<2 and (len(ineigh) == 3)):
if(ff2 == "Cl_"):
D0 = 3.23
R0 = 3.575
elif(ff2 == "O_3"):
D0 = 1.31
R0 = 3.41
elif(ff2 == "O_2"):
D0 = 1.25
R0 = 3.405
elif(ff2 == "N_3"):
if((jneigh.count("H") > 0)):
D0 = 0.93
R0 = 3.47
else:
D0 = 0.1870
R0 = 3.90
# primary amine
elif((ineigh.count("H") == 2) and (len(ineigh) == 3)):
if(ff2 == "Cl_"):
D0 = 10.00
R0 = 2.9795
elif(ff2 == "O_3"):
D0 = 2.21
R0 = 3.12
elif(ff2 == "O_2" or ff2 == "O_R"): # Added the O_R if statement
#D0 = 8.38
#R0 = 2.77
R0 = 3.00
elif(ff2 == "N_3"):
if((jneigh.count("H") > 0)):
D0 = 8.45
R0 = 2.84
else:
D0 = 5.0
R0 = 2.765
# protonated primary amine
elif((ineigh.count("H") == 3) and (len(ineigh) >= 3)):
if(ff2 == "Cl_"):
D0 = 7.6
R0 = 3.275
elif(ff2 == "O_3"):
D0 = 1.22
R0 = 3.2
elif(ff2 == "O_2"):
D0 = 8.56
R0 = 2.635
elif(ff2 == "N_3"):
if((jneigh.count("H") > 0)):
D0 = 10.14
R0 = 2.6
else:
D0 = 0.8
R0 = 3.22
elif(ff1 == "N_R"):
if(ff2 == "Cl_"):
D0 = 5.6
R0 = 3.265
elif(ff2 == "O_3"):
D0 = 1.38
R0 = 3.17
elif(ff2 == "N_R"):
R0 = 2.72 # fix for adenine w-c
elif(ff2 == "O_2"):
D0 = 3.88
R0 = 2.9
elif(ff2 == "N_3"):
if((jneigh.count("H") > 0)):
D0 = 2.44
R0 = 3.15
else:
D0 = 0.43
R0 = 3.4
elif(ff1 == "O_3"):
if(ff2 == "O_2"):
D0 = 1.33
R0 = 3.15
elif(ff2 == "N_3"):
if((jneigh.count("H") > 0)):
D0 = 1.97
R0 = 3.12
else:
D0 = 1.25
R0 = 3.15
potential.htype = graph.node[hnode]['ff_type_index']
potential.D0 = D0
potential.alpha = 10.0/ 2. / R0
potential.R0 = R0
potential.n = 2
# one can edit these values for bookkeeping.
potential.Rin = 9.0
potential.Rout = 11.0
potential.a_cut = 30.0
return potential
return hbond_pair
[docs] def special_commands(self):
if self.pair_in_data:
st = ["%-15s %s %s"%("pair_modify", "tail yes", "mix arithmetic")]
else:
st = ["%-15s %s"%("pair_modify", "tail yes")]
st += ["%-15s %.1f"%('dielectric', 1.0),
"%-15s %s"%("special_bonds", "dreiding") # equivalent to 'special_bonds lj 0.0 0.0 1.0'
]
return st
[docs] def detect_ff_terms(self):
# for each atom determine the ff type if it is None
organics = ["C", "N", "O", "S"]
halides = ["F", "Cl", "Br", "I"]
electro_neg_atoms = ["N", "O", "F"]
for node, data in self.graph.nodes_iter2(data=True):
if data['force_field_type'] is None or self.h_bonding:
if data['element'] in organics:
if data['hybridization'] == "sp3":
data['force_field_type'] = "%s_3"%data['element']
elif data['hybridization'] == "aromatic":
data['force_field_type'] = "%s_R"%data['element']
elif data['hybridization'] == "sp2":
data['force_field_type'] = "%s_2"%data['element']
elif data['hybridization'] == "sp":
data['force_field_type'] = "%s_1"%data['element']
else:
data['force_field_type'] = "%s_3"%data['element']
elif data['element'] == "H":
data['force_field_type'] = "H_"
if self.h_bonding:
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] in electro_neg_atoms:
self.graph.node[n]['h_bond_donor'] = True
data['force_field_type'] = "H__HB"
elif data['element'] in halides:
data['force_field_type'] = data['element']
if data['element'] == "F":
data['force_field_type'] += "_"
elif data['element'] == "I":
data['force_field_type'] += "_"
else:
ffs = list(DREIDING_DATA.keys())
for j in ffs:
if data['element'] == j[:2].strip("_"):
data['force_field_type'] = j
elif data['force_field_type'] not in DREIDING_DATA.keys():
print('Error: %s is not a force field type in DREIDING.'%(data['force_field_type']))
sys.exit()
if data['force_field_type'] is None:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
[docs]class UFF4MOF(ForceField):
"""Parameterize the periodic material with the UFF4MOF parameters.
"""
[docs] def __init__(self, **kwargs):
self.pair_in_data = True
self.keep_metal_geometry = False
self.graph = None
# override existing arguments with kwargs
for key, value in kwargs.items():
setattr(self, key, value)
if (self.graph is not None):
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def pair_terms(self, node, data, cutoff, charges=True):
"""Add L-J term to atom"""
if(charges):
data['pair_potential'] = PairPotential.LjCutCoulLong()
else:
data['pair_potential'] = PairPotential.LjCut()
data['pair_potential'].eps = UFF4MOF_DATA[data['force_field_type']][3]
data['pair_potential'].sig = UFF4MOF_DATA[data['force_field_type']][2]*(2**(-1./6.))
data['pair_potential'].cutoff = cutoff
return 1
[docs] def bond_term(self, edge):
"""Harmonic assumed"""
n1, n2, data = edge
n1_data, n2_data = self.graph.node[n1], self.graph.node[n2]
fflabel1, fflabel2 = n1_data['force_field_type'], n2_data['force_field_type']
r_1 = UFF4MOF_DATA[fflabel1][0]
r_2 = UFF4MOF_DATA[fflabel2][0]
chi_1 = UFF4MOF_DATA[fflabel1][8]
chi_2 = UFF4MOF_DATA[fflabel2][8]
rbo = -0.1332*(r_1 + r_2)*math.log(data['order'])
ren = r_1*r_2*(((math.sqrt(chi_1) - math.sqrt(chi_2))**2))/(chi_1*r_1 + chi_2*r_2)
r0 = (r_1 + r_2 + rbo - ren)
# The values for K in the UFF paper were set such that in the final
# harmonic function, they would be divided by '2' to satisfy the
# form K/2(R-Req)**2
# in Lammps, the value for K is already assumed to be divided by '2'
K = 664.12*(UFF4MOF_DATA[fflabel1][5]*UFF4MOF_DATA[fflabel2][5])/(r0**3) / 2.
if (self.keep_metal_geometry) and (n1_data['atomic_number'] in METALS
or n2_data['atomic_number'] in METALS):
r0 = data['length']
data['potential'] = BondPotential.Harmonic()
data['potential'].K = K
data['potential'].R0 = r0
return 1
[docs] def angle_term(self, angle):
"""several cases exist where the type of atom in a particular environment is considered
in both the parameters and the functional form of the term.
A small cosine fourier expansion in (theta)
E_0 = K_{IJK} * {sum^{m}_{n=0}}C_{n} * cos(n*theta)
Linear, trigonal-planar, square-planar, and octahedral:
two-term expansion of the above equation, n=0 as well as
n=1, n=3, n=4, and n=4 for the above geometries.
E_0 = K_{IJK}/n^2 * [1 - cos(n*theta)]
in Lammps, the angle syle called 'fourier/simple' can be used
to describe this functional form.
general non-linear case:
three-term Fourier expansion
E_0 = K_{IJK} * [C_0 + C_1*cos(theta) + C_2*cos(2*theta)]
in Lammps, the angle style called 'fourier' can be used to
describe this functional form.
Both 'fourier/simple' and 'fourier' are available from the
USER_MISC package in Lammps, so be sure to compile Lammps with
this package.
"""
# fourier/simple
sf = ['linear', 'trigonal-planar', 'square-planar', 'octahedral']
a, b, c, data = angle
angle_type = self.uff_angle_type(b)
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
ab_bond = self.graph[a][b]
bc_bond = self.graph[b][c]
auff, buff, cuff = a_data['force_field_type'], b_data['force_field_type'], c_data['force_field_type']
theta0 = UFF4MOF_DATA[buff][1]
# just check if the central node is a metal, then apply a rigid angle term.
# NB: Functional form may change dynamics, but at this point we will not
# concern ourselves if the force constants are big.
if (self.keep_metal_geometry) and (b_data['atomic_number'] in METALS):
theta0 = self.graph.compute_angle_between(a, b, c)
angle_type = "None"
cosT0 = math.cos(theta0*DEG2RAD)
sinT0 = math.sin(theta0*DEG2RAD)
c2 = 1.0 / (4.0*sinT0*sinT0)
c1 = -4.0 * c2 * cosT0
c0 = c2 * (2.0*cosT0*cosT0 + 1.0)
za = UFF4MOF_DATA[auff][5]
zc = UFF4MOF_DATA[cuff][5]
r_ab = ab_bond['potential'].R0
r_bc = bc_bond['potential'].R0
r_ac = math.sqrt(r_ab*r_ab + r_bc*r_bc - 2.*r_ab*r_bc*cosT0)
beta = 664.12/r_ab/r_bc
ka = beta*(za*zc /(r_ac**5.))*r_ab*r_bc
ka *= (3.*r_ab*r_bc*(1. - cosT0*cosT0) - r_ac*r_ac*cosT0)
#if ("special_flag" in b_data.keys()) and b_data["special_flag"] == "Cu_pdw":
# angle_type = "None"
# print(self.graph.compute_angle_between(a, b, c))
# # try the fourier expansion instead of the small term.
if angle_type in sf or (angle_type == 'tetrahedral' and int(theta0) == 90):
if angle_type == 'linear':
kappa = ka
c0 = -1.
B = 1
c1 = 1.
# the description of the actual parameters for 'n' are not obvious
# for the tetrahedral special case from the UFF paper or the write up in TOWHEE.
# The values were found in the TOWHEE source code (eg. Bi3+3).
if angle_type == 'tetrahedral':
kappa = ka/4.
c0 = -1.
B = -1
c1 = 2.
if angle_type == 'trigonal-planar':
kappa = ka/9.
c0 = -1.
B = -1
c1 = 3.
if angle_type == 'square-planar' or angle_type == 'octahedral':
kappa = ka/16.
c0 = -1.
B = 1
c1 = 4.
#data['potential'] = AnglePotential.FourierSimple()
data['potential'] = AnglePotential.CosinePeriodic()
#data['potential'].K = kappa
# this is done for you in the source code of LAMMPS k[i] = c_one/(n_one*n_one);
data['potential'].C = kappa*(c1**2)
#data['potential'].c = c0
data['potential'].B = B
data['potential'].n = c1
return 1
# general-nonlinear
else:
#TODO: a bunch of special cases which require molecular recognition here..
# water, for example has it's own theta0 angle.
kappa = ka
data['potential'] = AnglePotential.Fourier()
data['potential'].K = kappa
data['potential'].C0 = c0
data['potential'].C1 = c1
data['potential'].C2 = c2
return 1
[docs] def uff_angle_type(self, b):
name = self.graph.node[b]['force_field_type']
try:
coord_type = name[2]
except IndexError:
# eg, H_, F_
return 'linear'
if coord_type == "1":
return 'linear'
elif coord_type in ["R", "2"]:
return 'trigonal-planar'
elif coord_type == "3":
return 'tetrahedral'
elif coord_type == "4":
return 'square-planar'
elif coord_type == "5":
return 'trigonal-bipyrimidal'
elif coord_type == "6":
return 'octahedral'
elif coord_type == "8":
return 'cubic-antiprism'
else:
print("ERROR: Cannot find coordination type for %s"%name)
sys.exit()
[docs] def dihedral_term(self, dihedral):
"""Use a small cosine Fourier expansion
E_phi = 1/2*V_phi * [1 - cos(n*phi0)*cos(n*phi)]
this is available in Lammps in the form of a harmonic potential
E = K * [1 + d*cos(n*phi)]
NB: the d term must be negated to recover the UFF potential.
"""
a,b,c,d, data = dihedral
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
d_data = self.graph.node[d]
torsiontype = self.graph[b][c]['order']
coord_bc = (self.graph.degree(b), self.graph.degree(c))
bc = (b_data['force_field_type'], c_data['force_field_type'])
M = mul(*coord_bc)
V = 0
n = 0
mixed_case = ((b_data['hybridization'] == 'sp2' or b_data['hybridization'] == 'aromatic') and
c_data['hybridization'] == 'sp3') or \
(b_data['hybridization'] == 'sp3' and
(c_data['hybridization'] == 'sp2' or c_data['hybridization'] == 'aromatic'))
all_sp2 = ((b_data['hybridization'] == 'sp2' or b_data['hybridization'] == 'aromatic') and
c_data['hybridization'] == 'sp2' or c_data['hybridization'] == 'aromatic')
all_sp3 = (b_data['hybridization'] == 'sp3' and
c_data['hybridization'] == 'sp3')
phi0 = 0
if (b_data['atomic_number'] in METALS or c_data['atomic_number'] in METALS):
return None
if all_sp3:
phi0 = 60.0
n = 3
vi = UFF4MOF_DATA[b_data['force_field_type']][6]
vj = UFF4MOF_DATA[c_data['force_field_type']][6]
if b_data['atomic_number'] == 8:
vi = 2.
n = 2
phi0 = 90.
elif b_data['atomic_number'] in (16, 34, 52, 84):
vi = 6.8
n = 2
phi0 = 90.0
if c_data['atomic_number'] == 8:
vj = 2.
n = 2
phi0 = 90.0
elif c_data['atomic_number'] in (16, 34, 52, 84):
vj = 6.8
n = 2
phi0 = 90.0
V = (vi*vj)**0.5
elif all_sp2:
ui = UFF4MOF_DATA[b_data['force_field_type']][7]
uj = UFF4MOF_DATA[c_data['force_field_type']][7]
phi0 = 180.0
n = 2
V = 5.0 * (ui*uj)**0.5 * (1. + 4.18*math.log(torsiontype))
elif mixed_case:
phi0 = 180.0
n = 3
V = 2.
if c_data['hybridization'] == 'sp3':
if c_data['atomic_number'] in (8, 16, 34, 52):
n = 2
phi0 = 90.
elif b_data['hybridization'] == 'sp3':
if b_data['atomic_number'] in (8, 16, 34, 52):
n = 2
phi0 = 90.0
# special case group 6 elements
if n==2:
ui = UFF4MOF_DATA[b_data['force_field_type']][7]
uj = UFF4MOF_DATA[c_data['force_field_type']][7]
V = 5.0 * (ui*uj)**0.5 * (1. + 4.18*math.log(torsiontype))
V /= float(M)
nphi0 = n*phi0
if abs(math.sin(nphi0*DEG2RAD)) > 1.0e-3:
print("WARNING!!! nphi0 = %r" % nphi0)
if (self.keep_metal_geometry) and (b_data['atomic_number'] in METALS or
c_data['atomic_number'] in METALS):
# must use different potential with minimum at the computed dihedral
# angle.
nphi0 = n*self.graph.compute_dihedral_between(a, b, c, d)
data['potential'] = DihedralPotential.Charmm()
data['potential'].K = 0.5
data['potential'].d = 180 + nphi0
data['potential'].n = n
return 1
if V==0.:
return None
data['potential'] = DihedralPotential.Harmonic()
data['potential'].K = 0.5*V
data['potential'].d = -math.cos(nphi0*DEG2RAD)
data['potential'].n = n
return 1
[docs] def improper_term(self, improper):
"""Improper term described by a Fourier function
E = K*[C_0 + C_1*cos(w) + C_2*cos(2*w)]
"""
a, b, c, d, data = improper
b_data = self.graph.node[b]
a_ff = self.graph.node[a]['force_field_type']
c_ff = self.graph.node[c]['force_field_type']
d_ff = self.graph.node[d]['force_field_type']
if not b_data['atomic_number'] in (6, 7, 8, 15, 33, 51, 83):
return None
if b_data['force_field_type'] in ('N_3', 'N_2', 'N_R', 'O_2', 'O_R'):
c0 = 1.0
c1 = -1.0
c2 = 0.0
koop = 6.0
elif b_data['force_field_type'] in ('P_3+3', 'As3+3', 'Sb3+3', 'Bi3+3'):
if b_data['force_field_type'] == 'P_3+3':
phi = 84.4339 * DEG2RAD
elif b_data['force_field_type'] == 'As3+3':
phi = 86.9735 * DEG2RAD
elif b_data['force_field_type'] == 'Sb3+3':
phi = 87.7047 * DEG2RAD
else:
phi = 90.0 * DEG2RAD
c1 = -4.0 * math.cos(phi)
c2 = 1.0
c0 = -1.0*c1*math.cos(phi) + c2*math.cos(2.0*phi)
koop = 22.0
elif b_data['force_field_type'] in ('C_2', 'C_R'):
c0 = 1.0
c1 = -1.0
c2 = 0.0
koop = 6.0
if 'O_2' in (a_ff, c_ff, d_ff):
# check to make sure an aldehyde (i.e. not carboxylate bonded to metal)
if a_ff == "O_2" and self.graph.degree(a) == 1:
koop = 50.0
elif c_ff == "O_2" and self.graph.degree(c) == 1:
koop = 50.0
elif d_ff == "O_2" and self.graph.degree(d) == 1:
koop = 50.0
else:
return None
koop /= 3.
data['potential'] = ImproperPotential.Fourier()
data['potential'].K = koop
data['potential'].C0 = c0
data['potential'].C1 = c1
data['potential'].C2 = c2
return 1
[docs] def special_commands(self):
st = ["%-15s %s %s"%("pair_modify", "tail yes", "mix arithmetic"),
"%-15s %s"%("special_bonds", "lj/coul 0.0 0.0 1.0"),
"%-15s %.1f"%('dielectric', 1.0)
]
return st
[docs] def detect_ff_terms(self):
"""All new terms are associated with inorganic clusters, and the number of cases are extensive.
Implementation of all of the metal types would require a bit of effort, but should be done
in the near future.
"""
# for each atom determine the ff type if it is None
organics = ["C", "N", "O", "S"]
halides = ["F", "Cl", "Br", "I"]
for node, data in self.graph.nodes_iter2(data=True):
special = 'special_flag' in data
if data['force_field_type'] is None:
if special:
# Zn4O case TODO(pboyd): generalize these cases...
if data['special_flag'] == "O_z_Zn4O":
data['force_field_type'] = "O_3_f"
elif data['special_flag'] == "Zn4O":
data['force_field_type'] = "Zn3f2"
# change the bond orders to 0.5 as per the paper
for n in self.graph.neighbors(node):
self.graph[node][n]['order'] = 0.5
# woops! this is correct only for the M3O type SBUs
#if self.graph.node[n]['special_flag'] == "O_z_Zn4O":
# self.graph[node][n]['order'] = 1.0
#else:
# self.graph[node][n]['order'] = 0.5
elif data['special_flag'] == "C_Zn4O":
data['force_field_type'] = "C_R"
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] == "O":
self.graph[node][n]['order'] = 1.5
elif self.graph.node[n]['element'] == "C":
self.graph[node][n]['order'] = 1
elif data['special_flag'] == "O_c_Zn4O":
data['force_field_type'] = 'O_2'
# Copper Paddlewheel TODO(pboyd): generalize these cases...
elif data['special_flag'] == "O1_Cu_pdw" or data['special_flag'] == "O2_Cu_pdw":
data['force_field_type'] = 'O_2'
elif data['special_flag'] == "Cu_pdw":
data['force_field_type'] = 'Cu4+2'
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] == "Cu":
self.graph[node][n]['order'] = 0.25
else:
self.graph[node][n]['order'] = 0.5
elif data['special_flag'] == "C_Cu_pdw":
data['force_field_type'] = 'C_R'
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] == "O":
self.graph[node][n]['order'] = 1.5
elif self.graph.node[n]['element'] == "C":
self.graph[node][n]['order'] = 1
# Zn Paddlewheel TODO(pboyd): generalize these cases...
elif data['special_flag'] == "O1_Zn_pdw" or data['special_flag'] == "O2_Zn_pdw":
data['force_field_type'] = 'O_2'
elif data['special_flag'] == "Zn_pdw":
data['force_field_type'] = 'Zn4+2'
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] == "Zn":
self.graph[node][n]['order'] = 0.25
else:
self.graph[node][n]['order'] = 0.5
elif data['special_flag'] == "C_Zn_pdw":
data['force_field_type'] = 'C_R'
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] == "O":
self.graph[node][n]['order'] = 1.5
elif self.graph.node[n]['element'] == "C":
self.graph[node][n]['order'] = 1
# Al Pillar TODO(pboyd): generalize these cases...
elif data['special_flag'] == "O_c_Al_pillar":
data['force_field_type'] = 'O_2'
elif data['special_flag'] == "O_z_Al_pillar":
data['force_field_type'] = 'O_2'
elif data['special_flag'] == "H_Al_pillar":
data['force_field_type'] = 'H_'
elif data['special_flag'] == "Al_pillar":
data['force_field_type'] = 'Al6+3'
elif data['special_flag'] == "C_Al_pillar":
data['force_field_type'] = 'C_R'
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] == "O":
self.graph[node][n]['order'] = 1.5
elif self.graph.node[n]['element'] == "C":
self.graph[node][n]['order'] = 1
# V Pillar TODO(pboyd): generalize these cases...
elif data['special_flag'] == "O_c_V_pillar":
data['force_field_type'] = 'O_2'
elif data['special_flag'] == "O_z_V_pillar":
data['force_field_type'] = 'O_2'
elif data['special_flag'] == "V_pillar":
data['force_field_type'] = 'V6+3'
elif data['special_flag'] == "C_V_pillar":
data['force_field_type'] = 'C_R'
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] == "O":
self.graph[node][n]['order'] = 1.5
elif self.graph.node[n]['element'] == "C":
self.graph[node][n]['order'] = 1
elif data['element'] in organics:
neigh_elem = set([self.graph.node[i]['element'] for i in self.graph.neighbors(node)])
if data['hybridization'] == "sp3":
data['force_field_type'] = "%s_3"%data['element']
elif data['hybridization'] == "aromatic":
data['force_field_type'] = "%s_R"%data['element']
elif data['hybridization'] == "sp2":
data['force_field_type'] = "%s_2"%data['element']
elif data['hybridization'] == "sp":
data['force_field_type'] = "%s_1"%data['element']
if data['element'] == "O" and self.graph.degree(node) == 2:
if neigh_elem <= metals:
data['force_field_type'] = "O_2"
if neigh_elem <= set(["Si", "Al"]):
data['force_field_type'] = "O_3_z"
if neigh_elem <= metals | set(["C"]):
data['force_field_type'] = "O_2"
elif data['element'] == "O" and self.graph.degree(node) == 3:
if (neigh_elem <= metals):
data['force_field_type'] = "O_2_z"
# temp fix for UiO-series MOFs
if neigh_elem == set(["Zr"]):
data['force_field_type'] = "O_3_f"
else:
data['force_field_type'] = "O_2"
elif data['element'] == "O" and self.graph.degree(node) == 4:
if (neigh_elem <= metals | set(["H"])):
data['force_field_type'] = "O_3_f"
elif data['element'] == "H":
data['force_field_type'] = "H_"
elif data['element'] in halides:
data['force_field_type'] = data['element']
if data['element'] == "F":
data['force_field_type'] += "_"
elif data['element'] == "I":
data['force_field_type'] += "_"
elif data['element'] in metals:
if len(data['element']) == 1:
fftype = data['element'] + "_"
else:
fftype = data['element']
# get coordination number and angles between atoms
if(self.graph.degree(node) == 2):
fftype += "1f1"
elif(self.graph.degree(node) == 3):
fftype += "2f2"
elif(self.graph.degree(node) == 4):
# tetrahedral or square planar
if self.graph.coplanar(node):
fftype += "4f2"
# Could implement a tetrahedrality index here, but that would be overkill.
else:
fftype += "3f2"
elif(self.graph.degree(node) == 5):
# assume paddlewheels........
fftype += "4+2"
elif(self.graph.degree(node) == 6):
fftype += "6f3"
elif(self.graph.degree(node) == 8):
fftype += "8f4"
try:
UFF4MOF_DATA[fftype]
data['force_field_type'] = fftype
# couldn't find the force field type!
except KeyError:
try:
fftype = fftype.replace("f", "+")
UFF4MOF_DATA[fftype]
except KeyError:
pass
for n in self.graph.neighbors(node):
if self.graph.node[n]['element'] in metals:
self.graph[node][n]['order'] = 0.25
elif self.graph.node[n]['element'] == "O":
self.graph[node][n]['order'] = 0.5
# else: bond order stays = 1
# WARNING, the following else statement will do unknown things to the system.
if data['force_field_type'] is None:
ffs = list(UFF4MOF_DATA.keys())
for j in ffs:
if data['element'] == j[:2].strip("_"):
print("WARNING: Could not find an appropriate UFF4MOF type for %s. Assigning %s"%(
data['element'], j))
data['force_field_type'] = j
if data['force_field_type'] is None:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
[docs]class Dubbeldam(ForceField):
[docs] def __init__(self, graph=None, **kwargs):
self.pair_in_data = True
# override existing arguments with kwargs
for key, value in kwargs.items():
setattr(self, key, value)
if (graph is not None):
self.graph = graph
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def bond_term(self, edge):
"""
Harmonic term
E = 0.5 * K * (R - Req)^2
"""
n1, n2, data = edge
type1 = self.graph.node[n1]['force_field_type']
type2 = self.graph.node[n2]['force_field_type']
string = "_".join([type1, type2])
if type1 == "Zn" or type2 == "Zn":
#data['potential'] = BondPotential.Harmonic()
#data['potential'].K = 0.0
#data['potential'].R0 = 2.4
return None
if string not in Dub_bonds.keys():
string = "_".join([type2, type1])
if string not in Dub_bonds.keys():
print("ERROR: Could not find the bond parameters for the bond between %s"%string)
sys.exit()
data['potential'] = BondPotential.Harmonic()
data['potential'].K = Dub_bonds[string][0]*kBtokcal/2.
data['potential'].R0 = Dub_bonds[string][1]
return 1
[docs] def angle_term(self, angle):
"""
"""
a, b, c, data = angle
K = 100.0
a_data, b_data, c_data = self.graph.node[a], self.graph.node[b], self.graph.node[c]
atype = a_data['force_field_type']
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
string = "_".join([atype, btype, ctype])
if atype == "Zn" or btype == "Zn" or ctype == "Zn":
return None
if string not in Dub_angles.keys():
string = "_".join([ctype, btype, atype])
if string not in Dub_angles.keys():
print("ERROR: Could not find the angle parameters for the atom types %s"%string)
sys.exit()
data['potential'] = AnglePotential.Harmonic()
# check to make sure to divide by DEG2RAD**2
data['potential'].K = Dub_angles[string][0]*kBtokcal/2. / DEG2RAD**2
data['potential'].theta0 = Dub_angles[string][1]
return 1
[docs] def dihedral_term(self, dihedral):
"""
The Dihedral potential has the form
U(torsion) = k * (1 + cos(m*theta - theta0))
in LAMMPS this potential can be accessed by the dihedral_style charmm
E = K * [ 1 + d*cos(n*theta - d) ]
"""
a,b,c,d, data = dihedral
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
d_data = self.graph.node[d]
atype = a_data['force_field_type']
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
dtype = d_data['force_field_type']
if atype == "Zn" or btype == "Zn" or ctype == "Zn" or dtype == "Zn":
return None
string = "_".join([atype, btype, ctype, dtype])
if string not in Dub_dihedrals.keys():
string = "_".join([dtype, ctype, btype, atype])
if string not in Dub_dihedrals.keys():
print("ERROR: Could not find the torsion parameters for the atom types %s"%string)
sys.exit()
w = 0.0
data['potential'] = DihedralPotential.Charmm()
data['potential'].K = Dub_dihedrals[string][0]*kBtokcal
data['potential'].n = Dub_dihedrals[string][2]
data['potential'].d = Dub_dihedrals[string][1]
data['potential'].w = w
return 1
[docs] def improper_term(self, improper):
"""
The Improper potential has the form
U(torsion) = k * (1 + cos(m*theta - theta0))
in LAMMPS this potential can be accessed by the improper_style cvff
E = K * [ 1 + d*cos(n*theta) ]
# NO out of phase shift! to get a minima at 180 set d to -1.
# all of Dubbeldam's terms are for 180 degrees out-of-plane movement so
# this is fine.
"""
a, b, c, d, data = improper
a_data = self.graph.node[a]
b_data = self.graph.node[b]
c_data = self.graph.node[c]
d_data = self.graph.node[d]
atype = a_data['force_field_type']
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
dtype = d_data['force_field_type']
if atype == "Zn" or btype == "Zn" or ctype == "Zn" or dtype == "Zn":
return None
string = "_".join([atype, btype, ctype, dtype])
if string not in Dub_impropers.keys():
string = "_".join([atype, btype, dtype, ctype])
if string not in Dub_impropers.keys():
string = "_".join([ctype, btype, atype, dtype])
if string not in Dub_impropers.keys():
string = "_".join([ctype, btype, dtype, atype])
if string not in Dub_impropers.keys():
string = "_".join([dtype, btype, atype, ctype])
if string not in Dub_impropers.keys():
string = "_".join([dtype, btype, ctype, atype])
if string not in Dub_impropers.keys():
print("ERROR: Could not find the improper torsion parameters for the atom types %s"%string)
sys.exit()
data['potential'] = ImproperPotential.Cvff()
# I have 3 impropers, he only has 1. Divide by 3 to get average?
data['potential'].K = Dub_impropers[string][0]*kBtokcal / self.graph.degree(b)
data['potential'].d = -1
data['potential'].n = Dub_impropers[string][2]
return 1
[docs] def pair_terms(self, node, data, cutoff, **kwargs):
"""
"""
data['pair_potential'] = PairPotential.LjCutCoulLong()
data['pair_potential'].eps = Dub_atoms[data['force_field_type']][0]*kBtokcal
data['pair_potential'].sig = Dub_atoms[data['force_field_type']][1]
data['pair_potential'].cutoff = cutoff
data['charge'] = Dub_atoms[data['force_field_type']][2]
[docs] def special_commands(self):
st = ["%-15s %s"%("pair_modify", "tail yes"),
"%-15s %s"%("special_bonds", "lj/coul 0.0 0.0 1.0"),
"%-15s %.1f"%('dielectric', 1.0)]
return st
[docs] def detect_ff_terms(self):
""" Instead of the painful experience of coding a huge set of 'if' statements over
extended bonding neighbours to identify IRMOF-1, 10 and 16, all of the force field types
will be detected from maximum cliques.
This means that failing to find the SBUs will result in a bad parameterization.
"""
for node, data in self.graph.nodes_iter2(data=True):
special = 'special_flag' in data
if not special:
print("ERROR: Some atoms were not detected as part of an SBU." +
" This is a requirement for successful parameterization of the "+
"Dubbeldam forcefield for the IRMOFs.")
sys.exit()
if data['special_flag'] == "O_z_Zn4O":
data['force_field_type'] = "Oa"
elif data['special_flag'] == "Zn4O":
data['force_field_type'] = "Zn"
elif data['special_flag'] == "C_Zn4O":
data['force_field_type'] = "Ca"
elif data['special_flag'] == "O_c_Zn4O":
data['force_field_type'] = "Ob"
else:
# NB this works only because the special flags for the organic
# SBUs are set to be the force field types assigned by Dubeldam!
# if the 'special_flags' are changed for the aromatic molecules in mof_sbus.py then this
# will break!!
data['force_field_type'] = data['special_flag']
if data['force_field_type'] is None:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
[docs]class SPC_E(ForceField):
[docs] def __init__(self, graph=None, **kwargs):
self.pair_in_data = True
# override existing arguments with kwargs
for key, value in kwargs.items():
setattr(self, key, value)
if (graph is not None):
self.graph = graph
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def bond_term(self, edge):
"""Harmonic term
E = 0.5 * K * (R - Req)^2
just a placeholder in LAMMPS.
The bond is rigid and fixed by SHAKE in LAMMPS
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
n1, n2, data = edge
data['potential'] = BondPotential.Harmonic()
data['potential'].K = 350.0
data['potential'].R0 = 1.0
data['potential'].special_flag = "shake"
return 1
[docs] def angle_term(self, angle):
"""Harmonic angle term.
E = 0.5 * K * (theta - theta0)^2
just a placeholder in LAMMPS.
The angle is rigid and fixed by SHAKE in LAMMPS
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
a, b, c, data = angle
K = 100.0
a_data, b_data, c_data = self.graph.node[a], self.graph.node[b], self.graph.node[c]
atype = a_data['force_field_type']
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
assert (b_data['element'] == "O")
data['potential'] = AnglePotential.Harmonic()
data['potential'].K = K/2.
data['potential'].theta0 = 109.47
# extra flag?
data['potential'].special_flag = "shake"
return 1
[docs] def dihedral_term(self, dihedral):
"""
No dihedral potential in SPC/E water model.
"""
return None
[docs] def improper_term(self, improper):
"""
No improper potential in SPC/E water model.
"""
return None
[docs] def pair_terms(self, node, data, cutoff, **kwargs):
"""
Lennard - Jones potential for OW and HW.
cutoff should be set to 9 angstroms, but this may
be unrealistic.
Also, no long range treatment of coulombic
term! Otherwise
this isn't technically the SPC/E model but
Ewald should be used for periodic materials.
"""
data['pair_potential'] = PairPotential.LjCutCoulLong()
data['pair_potential'].eps = SPC_E_atoms[data['force_field_type']][2]
data['pair_potential'].sig = SPC_E_atoms[data['force_field_type']][1]
data['pair_potential'].cutoff = cutoff
[docs] def special_commands(self):
st = [
"%-15s %s"%("pair_modify", "tail yes")
]
return st
[docs] def detect_ff_terms(self):
"""Water consists of O and H, not too difficult.
"""
for node, data in self.graph.nodes_iter2(data=True):
if data['element'] == "O":
fftype = "OW"
elif data['element'] == "H":
fftype = "HW"
else:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
data['force_field_type'] = fftype
data['mass'] = SPC_E_atoms[fftype][0]
data['charge'] = SPC_E_atoms[fftype][3]
[docs]class TIP3P(ForceField):
[docs] def __init__(self, graph=None, **kwargs):
self.pair_in_data = True
# override existing arguments with kwargs
# TIP3P can have flexible OH bonds and angles,
# set this to 'True' to enable a flexible
# model.
self.flexible = False
for key, value in kwargs.items():
setattr(self, key, value)
if (graph is not None):
self.graph = graph
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def bond_term(self, edge):
"""Harmonic term
E = 0.5 * K * (R - Req)^2
just a placeholder in LAMMPS.
The bond is rigid and fixed by SHAKE in LAMMPS
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
n1, n2, data = edge
data['potential'] = BondPotential.Harmonic()
data['potential'].K = 450.0
data['potential'].R0 = 0.9572
if not self.flexible:
data['potential'].special_flag = "shake"
return 1
[docs] def angle_term(self, angle):
"""Harmonic angle term.
E = 0.5 * K * (theta - theta0)^2
just a placeholder in LAMMPS.
The angle is rigid and fixed by SHAKE in LAMMPS
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
a, b, c, data = angle
a_data, b_data, c_data = self.graph.node[a], self.graph.node[b], self.graph.node[c]
atype = a_data['force_field_type']
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
assert (b_data['element'] == "O")
data['potential'] = AnglePotential.Harmonic()
data['potential'].K = 55.0
data['potential'].theta0 = 104.52
if not self.flexible:
data['potential'].special_flag = "shake"
return 1
[docs] def dihedral_term(self, dihedral):
"""
No dihedral potential in TIP3P water model.
"""
return None
[docs] def improper_term(self, improper):
"""
No improper potential in TIP3P water model.
"""
return None
[docs] def pair_terms(self, node, data, cutoff, **kwargs):
"""
Lennard - Jones potential for OW and HW.
"""
data['pair_potential'] = PairPotential.LjCutCoulLong()
data['pair_potential'].eps = TIP3P_atoms[data['force_field_type']][2]
data['pair_potential'].sig = TIP3P_atoms[data['force_field_type']][1]
data['pair_potential'].cutoff = cutoff
[docs] def special_commands(self):
st = [
"%-15s %s"%("pair_modify", "tail yes")
]
return st
[docs] def detect_ff_terms(self):
"""Water consists of O and H, not too difficult.
"""
for node, data in self.graph.nodes_iter2(data=True):
if data['element'] == "O":
fftype = "OW"
elif data['element'] == "H":
fftype = "HW"
else:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
data['force_field_type'] = fftype
data['mass'] = TIP3P_atoms[fftype][0]
data['charge'] = TIP3P_atoms[fftype][3]
[docs]class TIP4P(ForceField, TIP4P_Water):
[docs] def __init__(self, graph=None, **kwargs):
self.pair_in_data = True
self.lammps_implicit = False
for key, value in kwargs.items():
setattr(self, key, value)
if (graph is not None):
self.graph = graph
if not self.lammps_implicit:
self.graph.rigid = True
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def bond_term(self, edge):
"""Harmonic term
E = 0.5 * K * (R - Req)^2
just a placeholder in LAMMPS.
The bond is rigid and fixed by SHAKE in LAMMPS
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
n1, n2, data = edge
n1data, n2data = self.graph.node[n1], self.graph.node[n2]
n1fftype, n2fftype = n1data['force_field_type'], n2data['force_field_type']
data['potential'] = BondPotential.Harmonic()
if not self.lammps_implicit:
data['potential'].K = 4500000.0
data['potential'].special_flag = "rigid"
else:
data['potential'].K = 450.0
data['potential'].special_flag = "shake"
if set([n1fftype, n2fftype]) == set(["HW", "OW"]):
data['potential'].R0 = self.ROH
elif set([n1fftype, n2fftype]) == set(["X", "OW"]):
data['potential'].R0 = self.Rdum
return 1
[docs] def angle_term(self, angle):
"""Harmonic angle term.
E = 0.5 * K * (theta - theta0)^2
just a placeholder in LAMMPS.
The angle is rigid and fixed by SHAKE in LAMMPS
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
a, b, c, data = angle
a_data, b_data, c_data = self.graph.node[a], self.graph.node[b], self.graph.node[c]
atype = a_data['force_field_type']
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
assert (b_data['element'] == "O")
data['potential'] = AnglePotential.Harmonic()
if not self.lammps_implicit:
data['potential'].K = 550000.0
data['potential'].special_flag = "rigid"
else:
data['potential'].K = 55.0
data['potential'].special_flag = "shake"
if atype == "HW" and ctype == "HW":
data['potential'].theta0 = self.HOH
elif (set([atype,ctype]) == set(["X", "HW"])) and (not self.lammps_implicit):
data['potential'].theta0 = self.graph.compute_angle_between(a,b,c)
return 1
[docs] def dihedral_term(self, dihedral):
"""
No dihedral potential in TIP4P water model.
"""
return None
[docs] def improper_term(self, improper):
"""
No improper potential in TIP4P water model.
"""
return None
[docs] def pair_terms(self, node, data, cutoff, **kwargs):
"""
Lennard - Jones potential for OW and HW.
"""
# check to see if the user wants to use the built-in TIP4P function,
# or the explicit dummy model.
if self.lammps_implicit:
data['pair_potential'] = PairPotential.LjCutTip4pLong()
data['pair_potential'].qdist = 0.1250
else:
data['pair_potential'] = PairPotential.LjCutCoulLong()
data['pair_potential'].eps = TIP4P_atoms[data['force_field_type']][2]
data['pair_potential'].sig = TIP4P_atoms[data['force_field_type']][1]
data['pair_potential'].cutoff = cutoff
[docs] def special_commands(self):
st = [
"%-15s %s"%("pair_modify", "tail yes")
]
return st
[docs] def detect_ff_terms(self):
"""Water consists of O and H, not too difficult.
"""
for node, data in self.graph.nodes_iter2(data=True):
if data['element'] == "O":
fftype = "OW"
elif data['element'] == "H":
fftype = "HW"
elif data['element'] == "X":
fftype = "X"
else:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
data['force_field_type'] = fftype
data['mass'] = TIP4P_atoms[fftype][0]
data['charge'] = TIP4P_atoms[fftype][3]
[docs]class TIP5P(ForceField):
[docs] def __init__(self, graph=None, **kwargs):
self.pair_in_data = True
for key, value in kwargs.items():
setattr(self, key, value)
if (graph is not None):
self.graph = graph
self.graph.rigid = True
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def bond_term(self, edge):
"""Harmonic term
E = 0.5 * K * (R - Req)^2
just a placeholder in LAMMPS.
The bond is rigid and fixed by SHAKE in LAMMPS
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
n1, n2, data = edge
n1data = self.graph.node[n1]
n2data = self.graph.node[n2]
data['potential'] = BondPotential.Harmonic()
if (n1data['force_field_type'] == "X") or (n2data['force_field_type'] == "X"):
data['potential'].R0 = 0.7
else:
data['potential'].R0 = 0.9572
data['potential'].K = 450000.0 # strong bond potential to ensure that the structure
# will not deviate far from its intended form
# during a minimization
data['potential'].special_flag = "rigid"
return 1
[docs] def angle_term(self, angle):
"""Harmonic angle term.
E = 0.5 * K * (theta - theta0)^2
just a placeholder in LAMMPS.
The angle is rigid and fixed by SHAKE in LAMMPS
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
a, b, c, data = angle
a_data, b_data, c_data = self.graph.node[a], self.graph.node[b], self.graph.node[c]
atype = a_data['force_field_type']
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
assert (b_data['element'] == "O")
data['potential'] = AnglePotential.Harmonic()
if atype == "X" and ctype == "X":
data['potential'].theta0 = 109.47
elif set([atype,ctype]) == set(["X", "HW"]):
data['potential'].theta0 = self.graph.compute_angle_between(a,b,c)
else:
data['potential'].theta0 = 104.52 # HW - OW - HW
data['potential'].K = 5550.0 # very strong angle term to ensure that if a minimization
# is requested, the molecule does not deviate far from
# its designed geometry
data['potential'].special_flag = "rigid"
return 1
[docs] def dihedral_term(self, dihedral):
"""
No dihedral potential in TIP5P water model.
"""
return None
[docs] def improper_term(self, improper):
"""
No improper potential in TIP5P water model.
"""
return None
[docs] def pair_terms(self, node, data, cutoff, **kwargs):
"""
Lennard - Jones potential for OW and HW.
"""
data['pair_potential'] = PairPotential.LjCutCoulLong()
data['pair_potential'].eps = TIP5P_atoms[data['force_field_type']][2]
data['pair_potential'].sig = TIP5P_atoms[data['force_field_type']][1]
data['pair_potential'].cutoff = cutoff
[docs] def special_commands(self):
st = [
"%-15s %s"%("pair_modify", "tail yes")
]
return st
[docs] def detect_ff_terms(self):
"""Water consists of O and H, not too difficult.
"""
for node, data in self.graph.nodes_iter2(data=True):
if data['element'] == "O":
fftype = "OW"
elif data['element'] == "H":
fftype = "HW"
elif data['element'] == "X":
fftype = "X"
else:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
data['force_field_type'] = fftype
data['mass'] = TIP5P_atoms[fftype][0]
data['charge'] = TIP5P_atoms[fftype][3]
[docs]class EPM2_CO2(ForceField):
[docs] def __init__(self, graph=None, **kwargs):
self.pair_in_data = True
for key, value in kwargs.items():
setattr(self, key, value)
if (graph is not None):
self.graph = graph
self.graph.rigid = True
self.detect_ff_terms()
self.compute_force_field_terms()
[docs] def bond_term(self, edge):
"""Harmonic term
E = 0.5 * K * (R - Req)^2
just a placeholder in LAMMPS.
The bond is rigid
therefore an unambiguous extra flag must be
added here to ensure the potential is not
grouped with identical (however unlikely) potentials
which are not rigid.
"""
n1, n2, data = edge
n1data = self.graph.node[n1]
n2data = self.graph.node[n2]
data['potential'] = BondPotential.Harmonic()
data['potential'].R0 = 1.149
data['potential'].K = 450000.0 # strong bond potential to ensure that the structure
# will not deviate far from its intended form
# during a minimization
data['potential'].special_flag = "rigid"
return 1
[docs] def angle_term(self, angle):
"""Harmonic angle term.
E = 0.5 * K * (theta - theta0)^2
Can be rigid or not with EPM2
"""
a, b, c, data = angle
a_data, b_data, c_data = self.graph.node[a], self.graph.node[b], self.graph.node[c]
atype = a_data['force_field_type']
btype = b_data['force_field_type']
ctype = c_data['force_field_type']
assert (b_data['element'] == "C")
assert (a_data['element'] == "O")
assert (c_data['element'] == "O")
data['potential'] = AnglePotential.Harmonic()
data['potential'].theta0 = EPM2_angles["_".join([atype,btype,ctype])][1]
data['potential'].K = EPM2_angles["_".join([atype,btype,ctype])][0]/2.
data['potential'].special_flag = "rigid"
return 1
[docs] def dihedral_term(self, dihedral):
"""
No dihedral potential in EPM2 CO2 model.
"""
return None
[docs] def improper_term(self, improper):
"""
No improper potential in EPM2 CO2 model.
"""
return None
[docs] def pair_terms(self, node, data, cutoff, **kwargs):
"""
Lennard - Jones potential for Cx and Ox.
"""
data['pair_potential'] = PairPotential.LjCutCoulLong()
data['pair_potential'].eps = EPM2_atoms[data['force_field_type']][2]
data['pair_potential'].sig = EPM2_atoms[data['force_field_type']][1]
data['pair_potential'].cutoff = cutoff
[docs] def special_commands(self):
st = [
"%-15s %s"%("pair_modify", "tail yes")
]
return st
[docs] def detect_ff_terms(self):
"""CO2 consists of C and O, not too difficult.
"""
for node, data in self.graph.nodes_iter2(data=True):
if data['element'] == "O":
fftype = "Ox"
elif data['element'] == "C":
fftype = "Cx"
else:
print("ERROR: could not find the proper force field type for atom %i"%(data['index'])+
" with element: '%s'"%(data['element']))
sys.exit()
data['force_field_type'] = fftype
data['mass'] = EPM2_atoms[fftype][0]
data['charge'] = EPM2_atoms[fftype][3]