import sys
import numpy as np
from pysisyphus.Geometry import Geometry
from pysisyphus.interpolate.Interpolator import Interpolator
from pysisyphus.intcoords.exceptions import (
DifferentPrimitivesException,
RebuiltInternalsException,
)
from pysisyphus.intcoords.helpers import get_tangent, form_coordinate_union
from pysisyphus.xyzloader import write_geoms_to_trj
[docs]
class Redund(Interpolator):
def __init__(self, *args, align=True, rebuild_geoms=True, **kwargs):
super().__init__(*args, align=align, **kwargs)
self.rebuild_geoms = rebuild_geoms
if self.rebuild_geoms:
self.geoms = [
Geometry(geom.atoms, geom.cart_coords, coord_type="redund")
for geom in self.geoms
]
[docs]
def dump_progress(self, geoms, out_fn="redund_interpol_fail.trj"):
write_geoms_to_trj(geoms, out_fn)
print(f"Dumped interpolation progress to '{out_fn}'.")
[docs]
def interpolate(
self,
initial_geom,
final_geom,
interpolate_only=0,
extrapolate=False,
typed_prims=None,
):
print(f"No. of primitives at initial structure: {initial_geom.coords.size}")
print(f"No. of primitives at final structure: {final_geom.coords.size}")
if typed_prims is None:
typed_prims = form_coordinate_union(initial_geom, final_geom)
print(f"Union of primitives: {len(typed_prims)}")
else:
print(f"Using supplied primitive internals ({len(typed_prims)}).")
# Recreate geometries with consistent set of internal coordinates
geom1 = Geometry(
initial_geom.atoms,
initial_geom.cart_coords,
coord_type="redund",
coord_kwargs={
"typed_prims": typed_prims,
"recalc_B": True,
},
)
geom2 = Geometry(
final_geom.atoms,
final_geom.cart_coords,
coord_type="redund",
coord_kwargs={
"typed_prims": typed_prims,
"recalc_B": True,
},
)
dihedral_indices = geom1.internal.dihedral_indices
initial_tangent = get_tangent(geom1.coords, geom2.coords, dihedral_indices)
initial_diff = np.linalg.norm(initial_tangent)
approx_step_size = initial_diff / (self.between + 1)
final_prims = geom2.internal.prim_coords
geoms = [
geom1,
]
def restart(new_geom):
interpolate_kwargs = {
"initial_geom": initial_geom,
"final_geom": final_geom,
"extrapolate": extrapolate,
"interpolate_only": interpolate_only,
}
return self.restart_interpolate(geoms, new_geom, interpolate_kwargs)
# initial_geom, final_geom, geoms, new_geom)
interpolations = interpolate_only if interpolate_only else self.between
ip_ex = "Extrapolating" if extrapolate else "Interpolating"
for i in range(interpolations):
print(f"{ip_ex} {i+1:03d}/{interpolations:03d}")
new_geom = geoms[-1].copy()
# Try to use the target primtive internals (final_prims) to calculate
# a tangent at the current, new geometry. As some primitives may be
# invalid at 'new_geom', DifferentPrimitivesException may be raised.
try:
prim_tangent = get_tangent(
new_geom.coords, final_prims, dihedral_indices
)
# If this is raised we update our target primitives and try to continue
# with them.
except DifferentPrimitivesException:
# return self.restart_interpolate(initial_geom, final_geom, new_geom)
return restart(new_geom)
if extrapolate:
prim_tangent *= -1
approx_step_size *= self.extrapolate_damp
step = self.step_along_tangent(new_geom, prim_tangent, approx_step_size)
try:
new_coords = new_geom.coords + step
# Will be raised when the sizes of both vectors differ
except ValueError:
# return self.restart_interpolate(initial_geom, final_geom, new_geom)
return restart(new_geom)
# The current primitives may also break down when a step along the
# tangent is taken.
try:
new_geom.coords = new_coords
except RebuiltInternalsException:
# return self.restart_interpolate(initial_geom, final_geom, new_geom)
return restart(new_geom)
geoms.append(new_geom)
sys.stdout.flush()
print()
return geoms[1:]
# def restart_interpolate(self, initial_geom, final_geom, geoms, new_geom):
[docs]
def restart_interpolate(self, geoms, new_geom, interpolate_kwargs):
new_typed_prims = new_geom.internal.typed_prims
print(
f"Encountered breakdown of current primitive internals.\n"
"Restarting interpolation with reduced number of internals."
)
self.dump_progress(geoms, out_fn=f"redund_interpol_fail.trj")
print()
# Recursive call with reduced set of primitive internals
return self.interpolate(**interpolate_kwargs, typed_prims=new_typed_prims)
[docs]
def step_along_tangent(self, geom, prim_tangent, step_size):
# Form active set
B = geom.internal.B_prim
G = B.dot(B.T)
eigvals, eigvectors = np.linalg.eigh(G)
U = eigvectors[:, np.abs(eigvals) > 1e-6]
reduced_tangent = (np.einsum("i,ij->j", prim_tangent, U) * U).sum(axis=1)
reduced_tangent /= np.linalg.norm(reduced_tangent)
step = step_size * reduced_tangent
return step