def test_anapot_lanczos(): geom = AnaPot.get_geom((-0.5767, 1.6810, 0.)) guess = (0.4, 0.3, 0.0) w_min, v_min = geom_lanczos(geom, guess=guess, dx=1e-5, dl=1e-5, logger=logger) H = geom.hessian w, v = np.linalg.eigh(H) w_ref = w[0] v_ref = v[:,0] assert w_min == pytest.approx(w_ref, abs=1e-4) assert any([np.allclose(v, v_ref, atol=5e-2) for v in (v_min, -v_min)])
def test_hcn_iso_lanczos(coord_type, guess): geom = geom_loader("lib:hcn_iso_hf_sto3g_ts_opt.xyz", coord_type=coord_type) calc = PySCF(pal=2, basis="sto3g") geom.set_calculator(calc) w_min, v_min = geom_lanczos(geom, guess=guess, logger=logger) # Reference values H = geom.hessian w, v = np.linalg.eigh(H) w_ref = w[0] v_ref = v[:,0] assert w_min == pytest.approx(w_ref, abs=1e-2) assert any([np.allclose(v, v_ref, atol=5e-2) for v in (v_min, -v_min)])
def get_tangent(self, i, kind="upwinding", lanczos_guess=None): """ [1] Equations (8) - (11)""" tangent_kinds = ("upwinding", "simple", "bisect", "lanczos") assert kind in tangent_kinds, \ "Invalid kind! Valid kinds are: {tangent_kinds}" prev_index = max(i - 1, 0) next_index = min(i + 1, len(self.images) - 1) prev_image = self.images[prev_index] ith_image = self.images[i] next_image = self.images[next_index] # If (i == 0) or (i == len(self.images)-1) then one # of this tangents is zero. tangent_plus = next_image - ith_image tangent_minus = ith_image - prev_image # Handle first and last image if i == 0: return tangent_plus / np.linalg.norm(tangent_plus) elif i == (len(self.images) - 1): return tangent_minus / np.linalg.norm(tangent_minus) # [1], Eq. (1) if kind == "simple": tangent = next_image - prev_image # [1], Eq. (2) elif kind == "bisect": first_term = tangent_minus / np.linalg.norm(tangent_minus) sec_term = tangent_plus / np.linalg.norm(tangent_plus) tangent = first_term + sec_term # Upwinding tangent from [1] Eq. (8) and so on elif kind == "upwinding": prev_energy = prev_image.energy ith_energy = ith_image.energy next_energy = next_image.energy next_energy_diff = abs(next_energy - ith_energy) prev_energy_diff = abs(prev_energy - ith_energy) delta_energy_max = max(next_energy_diff, prev_energy_diff) delta_energy_min = min(next_energy_diff, prev_energy_diff) # Uphill if next_energy > ith_energy > prev_energy: tangent = tangent_plus # Downhill elif next_energy < ith_energy < prev_energy: tangent = tangent_minus # Minimum or Maximum else: if next_energy >= prev_energy: tangent = (tangent_plus * delta_energy_max + tangent_minus * delta_energy_min) # next_energy < prev_energy else: tangent = (tangent_plus * delta_energy_min + tangent_minus * delta_energy_max) elif kind == "lanczos": # Calculating a lanczos tangent is costly, so we store the # tangent in a dictionary. The current coordinates are # stringified with precision=4 and then hashed. The tangent # is stored/looked up with this hash. cur_hash = hash_arr(ith_image.coords, precision=4) try: tangent = self.lanczos_tangents[cur_hash] self.log( "Returning previously calculated Lanczos tangent with " f"hash={cur_hash}") except KeyError: # Try to use previous Lanczos tangent guess = lanczos_guess if (guess is None) and (self.prev_lanczos_hash is not None): guess = self.lanczos_tangents[self.prev_lanczos_hash] self.log( f"Using tangent with hash={self.prev_lanczos_hash} " "as initial guess for Lanczos algorithm.") w_min, tangent = geom_lanczos(ith_image, guess=guess, logger=self.logger) self.lanczos_tangents[cur_hash] = tangent # Update hash self.prev_lanczos_hash = cur_hash tangent /= np.linalg.norm(tangent) return tangent