import logging
logger = logging.getLogger('displace')
import os, re
from math import cos, sin
import numpy
from eon import atoms
from eon import fileio as io
from eon.config import config
[docs]
class DisplacementManager:
def __init__(self, reactant, moved_atoms):
self.reactant = reactant
# TODO: Remove all the pointless config.* crap
if config.displace_random_weight > 0:
self.random = Random(self.reactant,
config.disp_magnitude, config.disp_radius,
hole_epicenters=moved_atoms)
if config.displace_under_coordinated_weight > 0:
self.under = Undercoordinated(self.reactant,
config.disp_max_coord,
config.disp_magnitude, config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=config.comp_neighbor_cutoff,
use_covalent=config.comp_use_covalent,
covalent_scale=config.comp_covalent_scale)
if config.displace_least_coordinated_weight > 0:
self.least = Leastcoordinated(self.reactant,
config.disp_magnitude, config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=config.comp_neighbor_cutoff,
use_covalent=config.comp_use_covalent,
covalent_scale=config.comp_covalent_scale)
if config.displace_listed_atom_weight > 0:
self.listed_atoms = ListedAtoms(self.reactant,
config.disp_magnitude, config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=config.comp_neighbor_cutoff,
use_covalent=config.comp_use_covalent,
covalent_scale=config.comp_covalent_scale,
displace_all=config.displace_all_listed)
if config.displace_listed_type_weight > 0:
self.listed_types = ListedTypes(self.reactant,
config.disp_magnitude,
config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=config.comp_neighbor_cutoff,
use_covalent=config.comp_use_covalent,
covalent_scale=config.comp_covalent_scale)
if config.displace_not_FCC_HCP_weight > 0:
self.not_FCC_HCP = NotFCCorHCP(self.reactant,
config.disp_magnitude,
config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=config.comp_neighbor_cutoff,
use_covalent=config.comp_use_covalent,
covalent_scale=config.comp_covalent_scale)
if config.displace_not_TCP_BCC_weight > 0:
self.not_TCP_BCC = NotTCPorBCC(self.reactant,
config.disp_magnitude,
config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=config.comp_neighbor_cutoff,
use_covalent=config.comp_use_covalent,
covalent_scale=config.comp_covalent_scale)
if config.displace_not_TCP_weight > 0:
self.not_TCP = NotTCP(self.reactant,
config.disp_magnitude,
config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=config.comp_neighbor_cutoff,
use_covalent=config.comp_use_covalent,
covalent_scale=config.comp_covalent_scale)
if config.displace_water_weight > 0:
self.water = Water(self.reactant,
config.stdev_translation, config.stdev_rotation,
config.molecule_list, config.disp_at_random)
total = 0.0
total += config.displace_random_weight
total += config.displace_listed_atom_weight
total += config.displace_listed_type_weight
total += config.displace_under_coordinated_weight
total += config.displace_least_coordinated_weight
total += config.displace_not_FCC_HCP_weight
total += config.displace_not_TCP_BCC_weight
total += config.displace_not_TCP_weight
total += config.displace_water_weight
# If no fractions are defined, do 100% random displacements.
if total == 0.0:
total = 1.0
self.plist = [1.0/total]
self.random = Random(self.reactant,
config.disp_magnitude, config.disp_radius,
hole_epicenters=moved_atoms)
else:
self.plist = [config.displace_random_weight/total]
self.plist.append(self.plist[-1] + config.displace_listed_atom_weight/total)
self.plist.append(self.plist[-1] + config.displace_listed_type_weight/total)
self.plist.append(self.plist[-1] + config.displace_under_coordinated_weight/total)
self.plist.append(self.plist[-1] + config.displace_least_coordinated_weight/total)
self.plist.append(self.plist[-1] + config.displace_not_FCC_HCP_weight/total)
self.plist.append(self.plist[-1] + config.displace_not_TCP_BCC_weight/total)
self.plist.append(self.plist[-1] + config.displace_not_TCP_weight/total)
self.plist.append(self.plist[-1] + config.displace_water_weight/total)
[docs]
def make_displacement(self):
disp_types = ["random", "listed_atoms", "listed_types", "under", "least", "not_FCC_HCP", "not_TCP_BCC", "not_TCP", "water"]
r = numpy.random.random_sample()
i = 0
while self.plist[i] < r:
i += 1
disp_type = disp_types[i]
if disp_type == "random":
logger.debug("Made random displacement")
return self.random.make_displacement()
elif disp_type == "listed_atoms":
logger.debug("Made listed atoms displacement")
return self.listed_atoms.make_displacement()
elif disp_type == "listed_types":
logger.debug("Made listed atom types displacement")
return self.listed_types.make_displacement()
elif disp_type == "under":
logger.debug("Made under-coordinated displacement")
return self.under.make_displacement()
elif disp_type == "least":
logger.debug("Made least-coordinated displacement")
return self.least.make_displacement()
elif disp_type == "not_FCC_HCP":
logger.debug("Made not-FCC-or-HCP displacement")
return self.not_FCC_HCP.make_displacement()
elif disp_type == "not_TCP_BCC":
logger.debug("Made not-TCP-or-BCC displacement")
return self.not_TCP_BCC.make_displacement()
elif disp_type == "not_TCP":
logger.debug("Made not-TCP displacement")
return self.not_TCP.make_displacement()
elif disp_type == "water":
logger.debug("Made water displacement")
return self.water.make_displacement()
raise DisplaceError()
[docs]
class NotImplementedError(Exception):
pass
[docs]
class DisplaceError(Exception):
pass
[docs]
class Displace:
def __init__(self, reactant, std_dev, radius, hole_epicenters):
'''Reactant is an Atoms object. std_dev is the standard deviation
of the normal distribution used to create the random displacements.
radius is the distance to neighbors that will also be displaced.
'''
self.reactant = reactant
self.std_dev = std_dev
self.radius = radius
self.hole_epicenters = hole_epicenters
## mike w.
self.void_bias_fraction = config.void_bias_fraction
# temporary numpy array of same size as self.reactant.r
self.temp_array = numpy.zeros(self.reactant.r.shape)
self.neighbors_list = None
[docs]
def make_displacement(self):
'''Writes the displacement_passed.con and mode_passed.dat to path.'''
raise NotImplementedError
[docs]
def get_displacement(self, atom_index):
'''Returns a displacement to be added to self.reactant.r'''
if self.neighbors_list is None:
self.neighbors_list = atoms.neighbor_list(self.reactant, self.radius, config.comp_brute_neighbors)
displacement_norm = 0.0
displacement = numpy.zeros(self.reactant.r.shape)
if hasattr(atom_index, '__getitem__'):
logger.debug("Displacement epicenters: ", atom_index)
neighbors = [ self.neighbors_list[i] for i in range(len(self.neighbors_list)) if i in atom_index ]
#flatten
neighbors = sum(neighbors,[])
neighbors = numpy.array(list(set(neighbors)), dtype=int)
displaced_atoms = numpy.append(atom_index, neighbors)
else:
logger.debug("Displacement epicenter: %d" % atom_index)
displaced_atoms = [atom_index] + self.neighbors_list[atom_index]
# ensures that the total displacement vector exceeds a given length, but the current default is zero
while (displacement_norm <= config.disp_min_norm):
displacement = numpy.zeros(self.reactant.r.shape)
for i in range(len(displaced_atoms)):
# don't displace frozen atoms
if not self.reactant.free[displaced_atoms[i]]:
continue
# Displace one of the free atoms by a gaussian distributed
# random number with a standard deviation of self.std_dev.
displacement[displaced_atoms[i]] = numpy.random.normal(scale = self.std_dev, size=3)
if config.displace_1d:
displacement[displaced_atoms[i]] = displacement[displaced_atoms[i]] * [1,0,0]
displacement_norm = numpy.linalg.norm(displacement)
### Mike W.
## this is a fraction of the total displacement magnitude so a small that
## a hard coded value will not break things later. It's essentially
## negligible at this size.
if self.void_bias_fraction > 1e-6:
self.neighbor_list_vectors = atoms.neighbor_list_vectors(self.reactant,
self.radius, config.comp_brute_neighbors)
# print config.random_mode
# print sorted(displaced_atoms)
# print numpy.linalg.norm(displacement)
# for atom_index in displaced_atoms:
# print (atom_index)
# print (self.neighbors_list[atom_index])
# dist_list = []
# for vec in self.neighbor_list_vectors[atom_index]:
# dist_list.append(numpy.linalg.norm(vec))
# print (dist_list)
## treats the nearest neighbors as repulsive, since I keep finding
## interstitials
pseudoelectrostatic_force = numpy.zeros(self.reactant.r.shape)
for atom_index in displaced_atoms:
for vec in self.neighbor_list_vectors[atom_index]:
mag = numpy.linalg.norm(vec) + 1e-6 # I just want to prevent NaNs in perfectly symmetric situations
pseudoelectrostatic_force[atom_index] += -vec/(mag**3)
# now we norm it for mixing
void_vec = pseudoelectrostatic_force/\
numpy.linalg.norm(pseudoelectrostatic_force)
displacement = \
self.void_bias_fraction*displacement_norm*void_vec + \
(1 - self.void_bias_fraction)*displacement
displacement_atoms = self.reactant.copy()
displacement_atoms.r += displacement
if config.random_mode:
displacement *= 0.0
for i in range(len(displaced_atoms)):
if not self.reactant.free[displaced_atoms[i]]:
continue
displacement[displaced_atoms[i]] = numpy.random.normal(scale = self.std_dev, size=3)
if config.displace_1d:
displacement[displaced_atoms[i]] = displacement[displaced_atoms[i]] * [1,0,0]
displacement /= numpy.linalg.norm(displacement)
return displacement_atoms, displacement/numpy.linalg.norm(displacement)
[docs]
def filter_epicenters(self, epicenters):
'''Returns the epicenters that lie in the hole defined by Displace.hole_epicenters.
If Displace.hole_epicenters is None, all of the epicenters are accepted.'''
if self.hole_epicenters is None:
return epicenters
new_epicenters = []
for e in epicenters:
if e in self.hole_epicenters:
new_epicenters.append(e)
#GH: added to prevent case in which there are no displaceable atoms in the active region
if len(new_epicenters) == 0:
logger.warning("No displaceable atoms found in the active region around atoms that moved in the previous transition;")
logger.warning(" reverting to the full list of any displaceable atoms.")
return epicenters
return new_epicenters
[docs]
class Undercoordinated(Displace):
def __init__(self, reactant, max_coordination, std_dev=0.05, radius=5.0, hole_epicenters=None, cutoff=3.3, use_covalent=False, covalent_scale=1.3):
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters)
self.max_coordination = max_coordination
self.undercoordinated_atoms = []
self.coordination_distance = cutoff
self.initialized = False
[docs]
def init(self):
self.initialized = True
# cns is an array of the coordination numbers for each of the atoms.
cns = atoms.coordination_numbers(self.reactant, self.coordination_distance)
# Only allow displacements for atoms <= the maximum coordination and that are free.
self.undercoordinated_atoms = [ i for i in range(len(cns))
if cns[i] <= self.max_coordination and
self.reactant.free[i] == 1]
self.undercoordinated_atoms = self.filter_epicenters(self.undercoordinated_atoms)
if len(self.undercoordinated_atoms) == 0:
errmsg = "No free atoms have a coordination of %i or less"
errmsg = errmsg % self.max_coordination
raise DisplaceError(errmsg)
[docs]
def make_displacement(self):
"""Select an undercoordinated atom and displace all atoms in a radius about it."""
# TODO: We should make sure that the amount of I/O to disk is what we think it should be:
# about 100 kB or so per make_displacement().
if not self.initialized:
self.init()
epicenter = self.undercoordinated_atoms[numpy.random.randint(len(self.undercoordinated_atoms))]
return self.get_displacement(epicenter)
[docs]
class Leastcoordinated(Displace):
def __init__(self, reactant, std_dev=0.05, radius=5.0, hole_epicenters=None, cutoff=3.3, use_covalent=False, covalent_scale=1.3):
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters)
self.leastcoordinated_atoms = []
self.coordination_distance = cutoff
self.leastcoordinated_atoms = atoms.least_coordinated(self.reactant,
self.coordination_distance)
self.leastcoordinated_atoms = [ i for i in self.leastcoordinated_atoms
if self.reactant.free[i] == 1]
self.leastcoordinated_atoms = self.filter_epicenters(self.leastcoordinated_atoms)
if len(self.leastcoordinated_atoms) == 0:
errmsg = "The least coordinated atoms are all frozen"
raise DisplaceError(errmsg)
[docs]
def make_displacement(self):
"""Select an undercoordinated atom and displace all atoms in a radius about it."""
epicenter = self.leastcoordinated_atoms[numpy.random.randint(len(self.leastcoordinated_atoms))]
return self.get_displacement(epicenter)
[docs]
class ListedAtoms(Displace):
def __init__(self, reactant, std_dev=0.05, radius=5.0, hole_epicenters=None, cutoff=3.3, use_covalent=False, covalent_scale=1.3, displace_all=False):
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters)
self.displace_all = displace_all
# each item in this list is the index of a free atom
self.listed_atoms = [ i for i in config.disp_listed_atoms
if self.reactant.free[i] ]
#print "self.listed_atoms:"
#print self.listed_atoms
self.listed_atoms = self.filter_epicenters(self.listed_atoms)
#print self.listed_atoms
if len(self.listed_atoms) == 0:
#raise DisplaceError("Listed atoms are all frozen")
raise DisplaceError("Listed atoms are all frozen")
#print self.listed_atoms
[docs]
def make_displacement(self):
"""Select a listed atom and displace all atoms in a radius about it."""
# chose a random atom from the supplied list
if self.displace_all:
epicenter = self.listed_atoms
else:
epicenter = self.listed_atoms[numpy.random.randint(len(self.listed_atoms))]
return self.get_displacement(epicenter)
[docs]
class ListedTypes(Displace):
def __init__(self, reactant, std_dev=0.05, radius=5.0, hole_epicenters=None, cutoff=3.3, use_covalent=False, covalent_scale=1.3, displace_all=False):
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters)
# print config.disp_listed_types
self.displace_all = displace_all
# each item in this list is the index of a free atom
self.listed_atoms = [ i for i in range(len(self.reactant.free))
if (self.reactant.free[i] == 1) and (self.reactant.names[i] in config.disp_listed_types) ]
self.listed_atoms = self.filter_epicenters(self.listed_atoms)
if len(self.listed_atoms) == 0:
raise DisplaceError("Listed atom types are all frozen or not found in reactant")
# print self.listed_atoms
[docs]
def make_displacement(self):
"""Select a listed atom and displace all atoms in a radius about it."""
# chose a random atom from the supplied list
if self.displace_all:
epicenter = self.listed_atoms
else:
epicenter = self.listed_atoms[numpy.random.randint(len(self.listed_atoms))]
return self.get_displacement(epicenter)
[docs]
class Random(Displace):
def __init__(self, reactant, std_dev=0.05, radius=5.0, hole_epicenters=None):
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters)
# each item in this list is the index of a free atom
self.free_atoms = [ i for i in range(len(self.reactant.free))
if self.reactant.free[i] ]
self.free_atoms = self.filter_epicenters(self.free_atoms)
if len(self.free_atoms) == 0:
raise DisplaceError("There are no free atoms in the reactant")
[docs]
def make_displacement(self):
"""Select a random atom and displace all atoms in a radius about it."""
# chose a random atom
epicenter = self.free_atoms[numpy.random.randint(len(self.free_atoms))]
return self.get_displacement(epicenter)
[docs]
class NotFCCorHCP(Displace):
def __init__(self, reactant, std_dev=0.05, radius=5.0, hole_epicenters=None, cutoff=3.3, use_covalent=False, covalent_scale=1.3):
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters)
self.not_HCP_or_FCC_atoms = []
self.coordination_distance = cutoff
self.not_HCP_or_FCC_atoms = atoms.not_HCP_or_FCC(self.reactant,
self.coordination_distance)
self.not_HCP_or_FCC_atoms = [ i for i in self.not_HCP_or_FCC_atoms
if self.reactant.free[i] == 1]
self.not_HCP_or_FCC_atoms = self.filter_epicenters(self.not_HCP_or_FCC_atoms)
if len(self.not_HCP_or_FCC_atoms) == 0:
errmsg = "The atoms without FCC or HCP coordination are all frozen"
raise DisplaceError(errmsg)
[docs]
def make_displacement(self):
"""Select an atom without HCP or FCC coordination and displace all atoms in a radius about it."""
epicenter = self.not_HCP_or_FCC_atoms[numpy.random.randint(len(self.not_HCP_or_FCC_atoms))]
return self.get_displacement(epicenter)
[docs]
class NotTCPorBCC(Displace):
def __init__(self, reactant, std_dev=0.05, radius=5.0, hole_epicenters=None, cutoff=3.3, use_covalent=False, covalent_scale=1.3):
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters)
self.not_TCP_or_BCC_atoms = []
self.coordination_distance = cutoff
self.not_TCP_or_BCC_atoms = atoms.not_TCP_or_BCC(self.reactant,
self.coordination_distance)
self.not_TCP_or_BCC_atoms = [ i for i in self.not_TCP_or_BCC_atoms
if self.reactant.free[i] == 1]
self.not_TCP_or_BCC_atoms = self.filter_epicenters(self.not_TCP_or_BCC_atoms)
if len(self.not_TCP_or_BCC_atoms) == 0:
errmsg = "The atoms without BCC or TCP coordination are all frozen."
raise DisplaceError(errmsg)
[docs]
def make_displacement(self):
"""Select an atom without TCP or BCC coordination and displace all atoms in a radius about it."""
epicenter = self.not_TCP_or_BCC_atoms[numpy.random.randint(len(self.not_TCP_or_BCC_atoms))]
return self.get_displacement(epicenter)
[docs]
class NotTCP(Displace):
def __init__(self, reactant, std_dev=0.05, radius=5.0, hole_epicenters=None, cutoff=3.3, use_covalent=False, covalent_scale=1.3):
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters)
self.not_TCP_atoms = []
self.coordination_distance = cutoff
self.not_TCP_atoms = atoms.not_TCP(self.reactant,
self.coordination_distance)
self.not_TCP_atoms = [ i for i in self.not_TCP_atoms
if self.reactant.free[i] == 1]
self.not_TCP_atoms = self.filter_epicenters(self.not_TCP_atoms)
if len(self.not_TCP_atoms) == 0:
errmsg = "The atoms without TCP coordination are all frozen"
raise DisplaceError(errmsg)
[docs]
def make_displacement(self):
"""Select an atom without HCP or FCC coordination and displace all atoms in a radius about it."""
epicenter = self.not_TCP_atoms[numpy.random.randint(len(self.not_TCP_atoms))]
return self.get_displacement(epicenter)
[docs]
class Water(Displace):
'''Displace molecules of water without streatching them.'''
def __init__(self, reactant, stdev_translation, stdev_rotation, molecule_list=[], random=0):
"""reactant: structure to be displaced\n"""\
"""stdev_translation: translational standard deviation (Angstrom)\n"""\
"""stdev_rotation: rotational standard deviation (radian)"""\
"""molecules: list of indices of the molecules to displace or None to displace all the molecules"""\
"""random: if 0 displace all molecules in molecule_list, if 'random > 0' picked up at random in 'molecule_list' a number of molecules equal to the number soted in 'random' and displace only these"""
assert(isinstance(molecule_list, list))
self.reactant = reactant
self.stdev_translation = stdev_translation
self.stdev_rotation = stdev_rotation
for i, name in enumerate(reactant.names):
if not re.search('^H', name): break
# For water assume that all the hydrogen are listed first, then all the oxygen
self.n_water = i/2
if len(molecule_list) == 0: molecule_list=list(range(self.n_water))
self.molecule_list = molecule_list
self.random = random
[docs]
def make_displacement(self):
'''Returns Atom object containing displaced structure and an array containing the displacement.'''
return self.get_displacement()
[docs]
def get_displacement(self):
'''Returns Atom object containing displaced structure and an array containing the displacement.'''
free = self.reactant.free
displaced_atoms = self.reactant.copy()
if self.random > 0:
n=len(self.molecule_list)
molecule_list = list()
for i in range(self.random):
i = int(numpy.random.uniform(0, n))
molecule_list.append(self.molecule_list[i])
else: molecule_list = self.molecule_list
for i in molecule_list:
#print 'displacing', i
h1 = i*2
h2 = i*2+1
o = i+self.n_water*2
#don't displace if any of the three atoms is fixed
#if not (free[h1] and free[h2] and free[o]):
# continue
#Displace one of the free atoms by a gaussian distributed
#random number with a standard deviation of self.std_dev.
disp = numpy.random.normal(scale = self.stdev_translation, size=3)
displaced_atoms.r[h1] += disp
displaced_atoms.r[h2] += disp
displaced_atoms.r[o] += disp
rh1 = displaced_atoms.r[h1]
rh2 = displaced_atoms.r[h2]
ro = displaced_atoms.r[o]
disp = numpy.random.normal(scale = self.stdev_rotation, size = 3)
rh1, rh2, ro = self.rotate_water(rh1, rh2, ro, disp[0], disp[1], disp[2])
displaced_atoms.r[h1] = rh1
displaced_atoms.r[h2] = rh2
displaced_atoms.r[o] = ro
displacement = displaced_atoms.r - self.reactant.r
return displaced_atoms, displacement
## Rotate a molecule of water.
# Rotate around the centre of gravity of the molecule.
# @param[in] "hydrogen1, hydrogen2, oxygen" numpy.array: coordinates of atoms.
# @param[in] "psi, theta, phi" float: Angle in @em radians of rotation around <em> x, y, z </em> axes.
# @param[in] "hydrogenMass, oxygenMass" float: masses of the hydrogen and oxygen atoms.
# @return "hydrogen1, hydrogen2, oxygen" coordinates of atoms after rotation
# The rotations uses the @em x, y, z convention (pitch-roll-yaw). The fisrt rotation to take place is around z, then y, then x.
# Equations and notations are from: http://mathworld.wolfram.com/EulerAngles.html .
[docs]
@staticmethod
def rotate_water(hydrogen1, hydrogen2, oxygen, psi, theta, phi, hydrogen_mass = 1.0, oxygen_mass = 16.0):
G = (hydrogen_mass*(hydrogen1 + hydrogen2) + oxygen_mass*oxygen)/(hydrogen_mass*2.0 + oxygen_mass)
rot = numpy.array([
[cos(theta)*cos(phi), cos(theta)*sin(phi), -sin(theta)],
[sin(psi)*sin(theta)*cos(phi)-cos(psi)*sin(phi), sin(psi)*sin(theta)*sin(phi)+cos(psi)*cos(phi), cos(theta)*sin(psi)],
[cos(psi)*sin(theta)*cos(phi)+sin(psi)*sin(phi), cos(psi)*sin(theta)*sin(phi)-sin(psi)*cos(phi), cos(theta)*cos(psi)] ])
rh1 = numpy.tensordot(rot, (hydrogen1-G), 1) + G
rh2 = numpy.tensordot(rot, (hydrogen2-G), 1) + G
ro = numpy.tensordot(rot, (oxygen-G), 1) + G
return rh1, rh2, ro
if __name__ == '__main__':
import sys
import time
if len(sys.argv) < 3:
print("%s: reactant.con outpath" % sys.argv[0])
sys.exit(1)
reactant = io.loadcon(sys.argv[1])
d = Random(reactant, 0.05, 5.0)
#d = Undercoordinated(reactant, 11, 0.05, 5.0)
t0 = time.time()
ntimes = 1000
for i in range(ntimes):
d.make_displacement(sys.argv[2])
dt = time.time()-t0
print("%.2f displacements per second" % (float(ntimes)/dt))