import sys, math from copy import deepcopy import numpy as np from numpy import linalg from DPpack.SetGlobals import * epsilon = 1e-8 ####################################### functions ###################################### def best_previous_point(): min_energy = 0 idx = 0 for energy in internal['energy'][:-1]: if energy < min_energy or abs(energy - min_energy) < 1e-10: min_energy = energy min_idx = idx idx += 1 return min_idx def best_point(): min_energy = 0 idx = 0 for energy in internal['energy']: if energy < min_energy or abs(energy - min_energy) < 1e-10: min_energy = energy min_idx = idx idx += 1 return min_idx def line_search(fh): X1 = internal['position'][-1] # numpy array e1 = internal['energy'][-1] G1 = internal['gradient'][-1] # numpy array idx = best_previous_point() X0 = internal['position'][idx] # numpy array e0 = internal['energy'][idx] G0 = internal['gradient'][idx] # numpy array # First try a quartic fit fh.write("Attempting a quartic fit.\n") success, y0 = quartic_fit(X0, X1, e0, e1, G0, G1, fh) if success and y0 > 0: if y0 < 1: new_point = X0 + y0*(X1 - X0) new_gradient = interpolate_gradient(G0, G1, y0) new_gradient = perpendicular_projection(new_gradient, X1 - X0) fh.write("Line search succeded.\n") return True, new_point, new_gradient else: idx = best_point() if idx == len(internal['energy']) - 1: new_point = X0 + y0*(X1 - X0) new_gradient = interpolate_gradient(G0, G1, y0) new_gradient = perpendicular_projection(new_gradient, X1 - X0) fh.write("Line search succeded.\n") return True, new_point, new_gradient else: fh.write("Quartic step is not acceptable. ") elif success: fh.write("Quartic step is not acceptable. ") # If no condition is met, then y0 is unacceptable. Try the cubic fit next fh.write("Attempting a cubic fit.\n") success, y0 = cubic_fit(X0, X1, e0, e1, G0, G1, fh) if success and y0 > 0: if y0 < 1: new_point = X0 + y0*(X1 - X0) new_gradient = interpolate_gradient(G0, G1, y0) new_gradient = perpendicular_projection(new_gradient, X1 - X0) fh.write("Line search succeded.\n") return True, new_point, new_gradient else: previous_step = X1 - internal['position'][-2] previous_step_size = linalg.norm(previous_step) new_point = X0 + y0*(X1 - X0) step = new_point - X1 step_size = linalg.norm(step) if step_size < previous_step_size: new_gradient = interpolate_gradient(G0, G1, y0) new_gradient = perpendicular_projection(new_gradient, X1 - X0) fh.write("Line search succeded.\n") return True, new_point, new_gradient else: fh.write("Cubic step is not acceptable. ") elif success: fh.write("Cubic step is not acceptable. ") # If no condition is met again, then all fits fail. fh.write("All fits fail. ") # Then, if the latest point is not the best, use y0 = 0.5 (step to the midpoint) idx = best_point() if idx < len(internal['energy']) - 1: y0 = 0.5 new_point = X0 + y0*(X1 - X0) new_gradient = interpolate_gradient(G0, G1, y0) new_gradient = perpendicular_projection(new_gradient, X1 - X0) fh.write("Moving to the midpoint.\n") return True, new_point, new_gradient # If the latest point is the best point, no linear search is done fh.write("No linear search will be used in this step.\n") return False, None, None ## For cubic and quartic fits, G0 and G1 are the gradient vectors def cubic_fit(X0, X1, e0, e1, G0, G1, fh): line = X1 - X0 line /= linalg.norm(line) g0 = np.dot(G0, line) g1 = np.dot(G1, line) De = e1 - e0 fh.write("De = {:<18.15e} g0 = {:<12.8f} g1 = {:<12.8f}\n".format(De, g0, g1)) alpha = g1 + g0 - 2*De if abs(alpha) < epsilon: fh.write("Cubic fit failed: alpha too small\n") return False, None beta = 3*De - 2*g0 - g1 discriminant = 4 * (beta**2 - 3*alpha*g0) if discriminant < 0: fh.write("Cubic fit failed: no minimum found (negative Delta)\n") return False, None if abs(discriminant) < epsilon: fh.write("Cubic fit failed: no minimum found (null Delta)\n") return False, None y0 = (-beta + math.sqrt(discriminant/4)) / (3*alpha) fh.write("Minimum found with y0 = {:<8.4f}\n".format(y0)) return True, y0 def quartic_fit(X0, X1, e0, e1, G0, G1, fh): line = X1 - X0 line /= linalg.norm(line) g0 = np.dot(G0, line) g1 = np.dot(G1, line) De = e1 - e0 Dg = g1 - g0 fh.write("De = {:<18.15e} g0 = {:<12.8f} g1 = {:<12.8f}\n".format(De, g0, g1)) if Dg < 0 or De - g0 < 0: fh.write("Quartic fit failed: negative alpha\n") return False, None if abs(Dg) < epsilon or abs(De - g0) < epsilon: fh.write("Quartic fit failed: alpha too small\n") return False, None discriminant = 16 * (Dg**2 - 3*(g1 + g0 - 2*De)**2) if discriminant < 0: fh.write("Quartic fit failed: no minimum found (negative Delta)\n") return False, None alpha1 = (Dg + math.sqrt(discriminant/16)) / 2 alpha2 = (Dg - math.sqrt(discriminant/16)) / 2 fh.write("alpha1 = {:<7.4e} alpha2 = {:<7.4e}\n".format(alpha1, alpha2)) alpha = alpha1 beta = g1 + g0 - 2*De - 2*alpha gamma = De - g0 - alpha - beta y0 = (-1/(2*alpha)) * ((beta**3 - 4*alpha*beta*gamma + 8*g0*alpha**2)/4)**(1/3) fh.write("Minimum found with y0 = {:<8.4f}\n".format(y0)) return True, y0 def rfo_step(gradient, hessian, type): dim = len(gradient) aug_hessian = [] for i in range(dim): aug_hessian.extend(hessian[i,:].tolist()) aug_hessian.append(gradient[i]) aug_hessian.extend(gradient.tolist()) aug_hessian.append(0) aug_hessian = np.array(aug_hessian).reshape(dim + 1, dim + 1) evals, evecs = linalg.eigh(aug_hessian) if type == "min": step = np.array(evecs[:-1,0]) elif type == "ts": step = np.array(evecs[:-1,1]) return step def update_trust_radius(): if internal['trust_radius'] == None: internal['trust_radius'] = player['maxstep'] elif len(internal['energy']) > 1: X1 = internal['position'][-1] X0 = internal['position'][-2] Dx = X1 - X0 displace = linalg.norm(Dx) e1 = internal['energy'][-1] e0 = internal['energy'][-2] De = e1 - e0 g0 = internal['gradient'][-2] h0 = internal['hessian'][-2] rho = De / (np.dot(g0, Dx) + 0.5*np.dot(Dx, np.matmul(h0, Dx.T).T)) if rho > 0.75 and displace > 0.8*internal['trust_radius']: internal['trust_radius'] = 2*internal['trust_radius'] elif rho < 0.25: internal['trust_radius'] = 0.25*displace return def interpolate_gradient(G0, G1, y0): DG = G1 - G0 gradient = G0 + y0*DG return gradient def perpendicular_projection(vector, line): direction = line / linalg.norm(line) projection = np.dot(vector, direction) * direction return vector - projection