import logging
logger = logging.getLogger("displace")
import re
from math import cos, sin
import numpy as np
from eon import atoms
from eon import fileio as io
from eon.config import ConfigClass # Typing
[docs]
class DisplacementManager:
def __init__(self, reactant, moved_atoms, config: ConfigClass = None):
if config is None:
raise TypeError("DisplacementManager requires a ConfigClass instance")
self.config = config
self.reactant = reactant
if self.config.displace_random_weight > 0:
self.random = Random(
self.reactant,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
config=self.config,
)
if self.config.displace_under_coordinated_weight > 0:
self.under = Undercoordinated(
self.reactant,
self.config.disp_max_coord,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=self.config.comp_neighbor_cutoff,
use_covalent=self.config.comp_use_covalent,
covalent_scale=self.config.comp_covalent_scale,
config=self.config,
)
if self.config.displace_least_coordinated_weight > 0:
self.least = Leastcoordinated(
self.reactant,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=self.config.comp_neighbor_cutoff,
use_covalent=self.config.comp_use_covalent,
covalent_scale=self.config.comp_covalent_scale,
config=self.config,
)
if self.config.displace_listed_atom_weight > 0:
self.listed_atoms = ListedAtoms(
self.reactant,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=self.config.comp_neighbor_cutoff,
use_covalent=self.config.comp_use_covalent,
covalent_scale=self.config.comp_covalent_scale,
displace_all=self.config.displace_all_listed,
config=self.config,
)
if self.config.displace_listed_type_weight > 0:
self.listed_types = ListedTypes(
self.reactant,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=self.config.comp_neighbor_cutoff,
use_covalent=self.config.comp_use_covalent,
covalent_scale=self.config.comp_covalent_scale,
config=self.config,
)
if self.config.displace_not_FCC_HCP_weight > 0:
self.not_FCC_HCP = NotFCCorHCP(
self.reactant,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=self.config.comp_neighbor_cutoff,
use_covalent=self.config.comp_use_covalent,
covalent_scale=self.config.comp_covalent_scale,
config=self.config,
)
if self.config.displace_not_TCP_BCC_weight > 0:
self.not_TCP_BCC = NotTCPorBCC(
self.reactant,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=self.config.comp_neighbor_cutoff,
use_covalent=self.config.comp_use_covalent,
covalent_scale=self.config.comp_covalent_scale,
config=self.config,
)
if self.config.displace_not_TCP_weight > 0:
self.not_TCP = NotTCP(
self.reactant,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
cutoff=self.config.comp_neighbor_cutoff,
use_covalent=self.config.comp_use_covalent,
covalent_scale=self.config.comp_covalent_scale,
config=self.config,
)
if self.config.displace_water_weight > 0:
self.water = Water(
self.reactant,
self.config.stdev_translation,
self.config.stdev_rotation,
self.config.molecule_list,
self.config.disp_at_random,
)
total = 0.0
total += self.config.displace_random_weight
total += self.config.displace_listed_atom_weight
total += self.config.displace_listed_type_weight
total += self.config.displace_under_coordinated_weight
total += self.config.displace_least_coordinated_weight
total += self.config.displace_not_FCC_HCP_weight
total += self.config.displace_not_TCP_BCC_weight
total += self.config.displace_not_TCP_weight
total += self.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,
self.config.disp_magnitude,
self.config.disp_radius,
hole_epicenters=moved_atoms,
config=self.config,
)
else:
self.plist = [self.config.displace_random_weight / total]
self.plist.append(
self.plist[-1] + self.config.displace_listed_atom_weight / total
)
self.plist.append(
self.plist[-1] + self.config.displace_listed_type_weight / total
)
self.plist.append(
self.plist[-1] + self.config.displace_under_coordinated_weight / total
)
self.plist.append(
self.plist[-1] + self.config.displace_least_coordinated_weight / total
)
self.plist.append(
self.plist[-1] + self.config.displace_not_FCC_HCP_weight / total
)
self.plist.append(
self.plist[-1] + self.config.displace_not_TCP_BCC_weight / total
)
self.plist.append(self.plist[-1] + self.config.displace_not_TCP_weight / total)
self.plist.append(self.plist[-1] + self.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 = np.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 DisplaceError(Exception):
pass
[docs]
class Displace:
def __init__(
self, reactant, std_dev, radius, hole_epicenters, config: ConfigClass = None
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
"""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.config = config
self.reactant = reactant
self.std_dev = std_dev
self.radius = radius
self.hole_epicenters = hole_epicenters
# ## mike w.
self.void_bias_fraction = 0.2 # self.config.void_bias_fraction
# temporary numpy array of same size as self.reactant.r
self.temp_array = np.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, self.config.comp_brute_neighbors
)
displacement_norm = 0.0
displacement = np.zeros(self.reactant.r.shape)
if hasattr(atom_index, "__getitem__"):
logger.debug("Displacement epicenters: %s", 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 = np.array(list(set(neighbors)), dtype=int)
displaced_atoms = np.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 <= self.config.disp_min_norm:
displacement = np.zeros(self.reactant.r.shape)
for i in range(len(displaced_atoms)):
# don't displace frozen atoms
if not self.reactant.atom_is_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]] = np.random.normal(
scale=self.std_dev, size=3
)
if self.config.displace_1d:
displacement[displaced_atoms[i]] = displacement[
displaced_atoms[i]
] * [1, 0, 0]
displacement_norm = np.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, self.config.comp_brute_neighbors
)
## treats the nearest neighbors as repulsive, since I keep finding
## interstitials
pseudoelectrostatic_force = np.zeros(self.reactant.r.shape)
for atom_index in displaced_atoms:
for vec in self.neighbor_list_vectors[atom_index]:
mag = (
np.linalg.norm(vec) + 1e-6
) # I just want to prevent NaNs in perfectly symmetric situations
pseudoelectrostatic_force[atom_index] += -vec / (mag**3)
# A symmetric shell cancels. Dividing by that norm writes NaN
# into the mode, so the bias is skipped when there is no direction.
force_norm = np.linalg.norm(pseudoelectrostatic_force)
if force_norm > 0.0:
void_vec = pseudoelectrostatic_force / force_norm
displacement = (
self.void_bias_fraction * displacement_norm * void_vec
+ (1.0 - self.void_bias_fraction) * displacement
)
displacement_atoms = self.reactant.copy()
displacement_atoms.r += displacement
if self.config.random_mode:
displacement *= 0.0
for i in range(len(displaced_atoms)):
if not self.reactant.atom_is_free()[displaced_atoms[i]]:
continue
displacement[displaced_atoms[i]] = np.random.normal(
scale=self.std_dev, size=3
)
if self.config.displace_1d:
displacement[displaced_atoms[i]] = displacement[
displaced_atoms[i]
] * [1, 0, 0]
displacement /= np.linalg.norm(displacement)
return displacement_atoms, displacement / np.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,
config: ConfigClass = None,
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters, config)
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.atom_is_free()[i]
]
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."""
if not self.initialized:
self.init()
epicenter = self.undercoordinated_atoms[
np.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,
config: ConfigClass = None,
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters, config)
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.atom_is_free()[i]
]
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[
np.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,
config: ConfigClass = None,
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters, config)
self.displace_all = displace_all
# Static INI disp_listed_atoms are CON file-order rows (0, 1, 2).
# Structure rows are atom_id order. Always remap those first: a
# first-pass that treats the numbers as Structure rows keeps a
# coincidentally-free post-sort hit and never remaps the rest of
# a mixed list.
#
# displace_atom_kmc_state_script sees savecon(Structure), which
# writes atom_id / Structure order. Its printed indices are
# already Structure rows; remapping them as original file-order
# sends the wrong atoms.
free = self.reactant.atom_is_free()
listed = self.config.disp_listed_atoms
if listed == -1:
listed = [-1]
# -1 is the documented "all free atoms" sentinel (akmc-al).
if listed == [-1]:
self.listed_atoms = [i for i in range(len(free)) if free[i]]
elif getattr(self.config, "disp_listed_from_script", False):
self.listed_atoms = [
int(i) for i in listed if 0 <= int(i) < len(free) and free[int(i)]
]
else:
f2s = getattr(self.reactant, "file_to_struct", None)
if f2s is not None:
remapped = [int(f2s[i]) for i in listed if 0 <= int(i) < len(f2s)]
else:
from eon.structure import file_rows_to_structure_rows
remapped = file_rows_to_structure_rows(self.reactant.atom_ids, listed)
self.listed_atoms = [i for i in remapped if 0 <= i < len(free) and free[i]]
if remapped != [int(i) for i in listed] and self.listed_atoms:
logger.debug(
"Listed atoms remapped through atom_id sort: %s -> %s",
listed,
self.listed_atoms,
)
self.listed_atoms = self.filter_epicenters(self.listed_atoms)
logger.debug("Listed atoms: %s", self.listed_atoms)
if len(self.listed_atoms) == 0:
raise DisplaceError("Listed atoms are all frozen")
[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[np.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,
config: ConfigClass = None,
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters, config)
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.atom_is_free()[i]
and (self.reactant.names[i] in self.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"
)
[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[np.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,
config: ConfigClass = None,
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters, config)
# 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.atom_is_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[np.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,
config: ConfigClass = None,
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters, config)
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.atom_is_free()[i]
]
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[
np.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,
config: ConfigClass = None,
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters, config)
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.atom_is_free()[i]
]
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[
np.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,
config: ConfigClass = None,
):
if config is None:
raise TypeError("displace helper requires a ConfigClass instance")
Displace.__init__(self, reactant, std_dev, radius, hole_epicenters, config)
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.atom_is_free()[i]
]
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[np.random.randint(len(self.not_TCP_atoms))]
return self.get_displacement(epicenter)
[docs]
class Water:
"""Displace water molecules as rigid units. Not a Displace epicenter helper."""
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."""
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(np.random.uniform(0, n))
molecule_list.append(self.molecule_list[i])
else:
molecule_list = self.molecule_list
for i in molecule_list:
h1 = i * 2
h2 = i * 2 + 1
o = i + self.n_water * 2
# Displace one of the free atoms by a gaussian distributed
# random number with a standard deviation of self.std_dev.
disp = np.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 = np.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" np.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 = np.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 = np.tensordot(rot, (hydrogen1 - G), 1) + G
rh2 = np.tensordot(rot, (hydrogen2 - G), 1) + G
ro = np.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])
if len(sys.argv) > 3:
from eon.config import ConfigClass
cfg = ConfigClass()
cfg.init(sys.argv[3])
d = Random(
reactant,
float(cfg.disp_magnitude),
float(cfg.disp_radius),
config=cfg,
)
else:
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()
dt = time.time() - t0
outf = io.savecon(sys.argv[2], reactant)
print("%.2f displacements per second" % (float(ntimes) / dt))