Source code for n2v.methods.wuyang

"""
wuyang.py

Functions associated with wuyang inversion
"""

import numpy as np
from opt_einsum import contract
from scipy.optimize import minimize

[docs]class WuYang(): """ Performs Optimization as in: 10.1063/1.1535422 - Qin Wu + Weitao Yang Attributes: ----------- lambda_rgl: {None, float}. If float, lambda-regularization is added with lambda=lambda_rgl. """ regul_norm = None # Regularization norm: ||v||^2 lambda_reg = None # Regularization constant
[docs] def wuyang(self, opt_max_iter, reg=None, tol=1e-7, gtol=1e-3, opt_method='trust-krylov', opt=None): """ Calls scipy minimizer to minimize lagrangian. """ self.lambda_reg = reg if opt is None: opt = {"disp" : False} opt['maxiter'] = opt_max_iter opt['gtol'] = gtol # Initialization for D and C self._diagonalize_with_potential_pbs(self.v_pbs) if opt_method.lower() == 'bfgs' or opt_method.lower() == 'l-bfgs-b': opt_results = minimize( fun = self.lagrangian_wy, x0 = self.v_pbs, jac = self.gradient_wy, method = opt_method, tol = tol, options = opt ) else: opt_results = minimize( fun = self.lagrangian_wy, x0 = self.v_pbs, jac = self.gradient_wy, hess = self.hessian_wy, method = opt_method, tol = tol, options = opt ) if opt_results.success == False: self.v_pbs = opt_results.x self.opt_info = opt_results raise ValueError("Optimization was unsucessful (|grad|=%.2e) within %i iterations, " "try a different initial guess. %s"% (np.linalg.norm(opt_results.jac), opt_results.nit, opt_results.message) ) else: print("Optimization Successful within %i iterations! " "|grad|=%.2e" % (opt_results.nit, np.linalg.norm(opt_results.jac))) self.v_pbs = opt_results.x self.opt_info = opt_results
def _diagonalize_with_potential_pbs(self, v): """ Diagonalize Fock matrix with additional external potential """ self.v_pbs = np.copy(v) vks_a = contract("ijk,k->ij", self.S3, v[:self.npbs]) + self.va fock_a = self.V + self.T + vks_a self.Ca, self.Coca, self.Da, self.eigvecs_a = self.diagonalize( fock_a, self.nalpha ) if self.ref == 1: self.Cb, self.Cocb, self.Db, self.eigvecs_b = self.Ca.copy(), self.Coca.copy(), self.Da.copy(), self.eigvecs_a.copy() self.Fock = fock_a else: vks_b = contract("ijk,k->ij", self.S3, v[self.npbs:]) + self.vb fock_b = self.V + self.T + vks_b self.Cb, self.Cocb, self.Db, self.eigvecs_b = self.diagonalize( fock_b, self.nbeta ) self.Fock = (fock_a, fock_b)
[docs] def lagrangian_wy(self, v): """ Lagrangian to be minimized wrt external potential Equation (5) of main reference """ # If v is not updated, will not re-calculate. if not np.allclose(v, self.v_pbs): self._diagonalize_with_potential_pbs(v) self.grad_a = contract('ij,ijt->t', (self.Da - self.Dt[0]), self.S3) self.grad_b = contract('ij,ijt->t', (self.Db - self.Dt[1]), self.S3) kinetic = np.sum(self.T * (self.Da)) potential = np.sum((self.V + self.va) * (self.Da - self.Dt[0])) optimizing = np.sum(v[:self.npbs] * self.grad_a) if self.ref == 1: L = 2 * (kinetic + potential + optimizing) else: kinetic += np.sum(self.T * (self.Db)) potential += np.sum((self.V + self.vb) * (self.Db - self.Dt[1])) optimizing += np.sum(v[self.npbs:] * self.grad_b) L = kinetic + potential + optimizing # Add lambda-regularization if self.lambda_reg is not None: T = self.T_pbs if self.ref == 1: norm = 2 * (v[:self.npbs] @ T @ v[:self.npbs]) else: norm = (v[self.npbs:] @ T @ v[self.npbs:]) + (v[:self.npbs] @ T @ v[:self.npbs]) L -= norm * self.lambda_reg self.regul_norm = norm # if print_flag: # print(f"Kinetic: {kinetic:6.4f} | Potential: {np.abs(potential):6.4e} | From Optimization: {np.abs(optimizing):6.4e}") return - L
[docs] def gradient_wy(self, v): """ Calculates gradient wrt target density Equation (11) of main reference """ if not np.allclose(v, self.v_pbs): self._diagonalize_with_potential_pbs(v) self.grad_a = contract('ij,ijt->t', (self.Da - self.Dt[0]), self.S3) self.grad_b = contract('ij,ijt->t', (self.Db - self.Dt[1]), self.S3) if self.ref == 1: self.grad = self.grad_a else: self.grad = np.concatenate(( self.grad_a, self.grad_b )) if self.lambda_reg is not None: T = self.T_pbs if self.ref == 1: rgl_vector = 4 * self.lambda_reg*np.dot(T, v[:self.npbs]) self.grad -= rgl_vector else: self.grad[:self.npbs] -= 2 * self.lambda_reg*np.dot(T, v[:self.npbs]) self.grad[self.npbs:] -= 2 * self.lambda_reg*np.dot(T, v[self.npbs:]) return -self.grad
[docs] def hessian_wy(self, v): """ Calculates gradient wrt target density Equation (13) of main reference """ if not np.allclose(v, self.v_pbs): self._diagonalize_with_potential_pbs(v) na, nb = self.nalpha, self.nbeta eigs_diff_a = self.eigvecs_a[:na, None] - self.eigvecs_a[None, na:] C3a = contract('mi,va,mvt->iat', self.Ca[:,:na], self.Ca[:,na:], self.S3) Ha = 2 * contract('iau,iat,ia->ut', C3a, C3a, eigs_diff_a**-1) if self. ref == 1: if self.lambda_reg is not None: Ha -= 4 * self.T_pbs * self.lambda_reg Hs = Ha else: eigs_diff_b = self.eigvecs_b[:nb, None] - self.eigvecs_b[None, nb:] C3b = contract('mi,va,mvt->iat', self.Cb[:,:nb], self.Cb[:,nb:], self.S3) Hb = 2 * contract('iau,iat,ia->ut', C3b, C3b, eigs_diff_b**-1) if self.lambda_reg is not None: Ha -= 2 * self.T_pbs * self.lambda_reg Hb -= 2 * self.T_pbs * self.lambda_reg Hs = np.block( [[Ha, np.zeros((self.npbs, self.npbs))], [np.zeros((self.npbs, self.npbs)), Hb ]] ) return - Hs
[docs] def find_regularization_constant_wy(self, opt_max_iter, opt_method="trust-krylov", gtol=1e-3, tol=None, opt=None, lambda_list=None): """ Finding regularization constant lambda. Note: it is recommend to set a specific convergence criteria by opt or tol, in order to control the same convergence for different lambda value. After the calculation is done, one can plot the returns to select a good lambda. Parameters: ----------- opt_max_iter: int maximum iteration opt_method: string default: "trust-krylov" opt_methods available in scipy.optimize.minimize tol: float Tolerance for termination. See scipy.optimize.minimize for details. gtol: float gtol for scipy.optimize.minimize: the gradient norm for convergence opt: dictionary, optional if given: scipy.optimize.minimize(method=opt_method, options=opt). Notice that opt has lower priorities than opt_max_iter and gtol. lambda_list: np.ndarray, optional A array of lambda to search; otherwise, it will be 10 ** np.linspace(-1, -7, 7). Returns: -------- lambda_list: np.ndarray A array of lambda searched. P_list: np.ndarray The value defined by [Bulat, Heaton-Burgess, Cohen, Yang 2007] eqn (21). Corresponding to lambda in lambda_list. Ts_list: np.ndarray The Ts value for each lambda. """ Ts_list = [] L_list = [] v_norm_list = [] if lambda_list is None: lambda_list = 10 ** np.linspace(-3, -9, 7) if opt is None: opt = {"disp" : False} opt['maxiter'] = opt_max_iter opt['gtol'] = gtol self.lambda_reg = None # Initial calculation with no regularization # Initialization for D and C self._diagonalize_with_potential_pbs(self.v_pbs) if opt_method.lower() == 'bfgs' or opt_method.lower() == 'l-bfgs-b': initial_result = minimize(fun=self.lagrangian_wy, x0=self.v_pbs, jac=self.gradient_wy, method=opt_method, tol=tol, options=opt ) else: initial_result = minimize(fun=self.lagrangian_wy, x0=self.v_pbs, jac=self.gradient_wy, hess=self.hessian_wy, method=opt_method, tol=tol, options=opt ) if initial_result.success == False: raise ValueError("Optimization was unsucessful (|grad|=%.2e) within %i iterations, " "try a different intitial guess"% (np.linalg.norm(initial_result.jac), initial_result.nit) + initial_result.message) else: L0 = -initial_result.fun initial_v0 = initial_result.x # This is used as the initial guess for with regularization calculation. for reg in lambda_list: self.lambda_reg = reg if opt_method.lower() == 'bfgs' or opt_method.lower() == 'l-bfgs-b': opt_results = minimize(fun=self.lagrangian_wy, x0=initial_v0, jac=self.gradient_wy, method=opt_method, tol=tol, options=opt ) else: opt_results = minimize(fun=self.lagrangian_wy, x0=initial_v0, jac=self.gradient_wy, hess=self.hessian_wy, method=opt_method, tol=tol, options=opt ) Ts_list.append(np.sum(self.T * (self.Da + self.Db))) v_norm_list.append(self.regul_norm) L_list.append(-opt_results.fun + self.lambda_reg * self.regul_norm) P_list = lambda_list * np.array(v_norm_list) / (L0 - np.array(L_list)) return lambda_list, P_list, np.array(Ts_list)