def dot(self, x, y): """ Return the preconditioned dot product <P x, y> Uses 128-bit floating point math for vector dot products """ return longsum(self.P.dot(x) * y)
def estimate_mu(self, atoms, H=None): r"""Estimate optimal preconditioner coefficient \mu \mu is estimated from a numerical solution of [dE(p+v) - dE(p)] \cdot v = \mu < P1 v, v > with perturbation v(x,y,z) = H P_lowest_nonzero_eigvec(x, y, z) or v(x,y,z) = H (sin(x / Lx), sin(y / Ly), sin(z / Lz)) After the optimal \mu is found, self.mu will be set to its value. If `atoms` is an instance of Filter an additional \mu_c will be computed for the cell degrees of freedom . Args: atoms: Atoms object for initial system H: 3x3 array or None Magnitude of deformation to apply. Default is 1e-2*rNN*np.eye(3) Returns: mu : float mu_c : float or None """ if self.dim != 3: raise ValueError('Automatic calculation of mu only possible for ' 'three-dimensional preconditioners. Try setting ' 'mu manually instead.') if self.r_NN is None: self.r_NN = estimate_nearest_neighbour_distance(atoms) # deformation matrix, default is diagonal if H is None: H = 1e-2 * self.r_NN * np.eye(3) # compute perturbation p = atoms.get_positions() if self.estimate_mu_eigmode: self.mu = 1.0 self.mu_c = 1.0 c_stab = self.c_stab self.c_stab = 0.0 if isinstance(atoms, Filter): n = len(atoms.atoms) else: n = len(atoms) P0 = self._make_sparse_precon(atoms, initial_assembly=True)[:3 * n, :3 * n] eigvals, eigvecs = sparse.linalg.eigsh(P0, k=4, which='SM') #print('estimate_mu(): lowest 4 eigvals = %f %f %f %f' # % (eigvals[0], eigvals[1], eigvals[2], eigvals[3])) # check eigenvalues if any(eigvals[0:3] > 1e-6): raise ValueError('First 3 eigenvalues of preconditioner matrix' 'do not correspond to translational modes.') elif eigvals[3] < 1e-6: raise ValueError('Fourth smallest eigenvalue of ' 'preconditioner matrix ' 'is too small, increase r_cut.') x = np.zeros(n) for i in range(n): x[i] = eigvecs[:, 3][3 * i] x = x / np.linalg.norm(x) if x[0] < 0: x = -x v = np.zeros(3 * len(atoms)) for i in range(n): v[3 * i] = x[i] v[3 * i + 1] = x[i] v[3 * i + 2] = x[i] v = v / np.linalg.norm(v) v = v.reshape((-1, 3)) self.c_stab = c_stab else: Lx, Ly, Lz = [p[:, i].max() - p[:, i].min() for i in range(3)] #print('estimate_mu(): Lx=%.1f Ly=%.1f Lz=%.1f' % (Lx, Ly, Lz)) x, y, z = p.T # sine_vr = [np.sin(x/Lx), np.sin(y/Ly), np.sin(z/Lz)], but we need # to take into account the possibility that one of Lx/Ly/Lz is # zero. sine_vr = [x, y, z] for i, L in enumerate([Lx, Ly, Lz]): if L == 0: warnings.warn( 'Cell length L[%d] == 0. Setting H[%d,%d] = 0.' % (i, i, i)) H[i, i] = 0.0 else: sine_vr[i] = np.sin(sine_vr[i] / L) v = np.dot(H, sine_vr).T natoms = len(atoms) if isinstance(atoms, Filter): natoms = len(atoms.atoms) eps = H / self.r_NN v[natoms:, :] = eps v1 = v.reshape(-1) # compute LHS dE_p = -atoms.get_forces().reshape(-1) atoms_v = atoms.copy() atoms_v.calc = atoms.calc if isinstance(atoms, Filter): atoms_v = atoms.__class__(atoms_v) if hasattr(atoms, 'constant_volume'): atoms_v.constant_volume = atoms.constant_volume atoms_v.set_positions(p + v) dE_p_plus_v = -atoms_v.get_forces().reshape(-1) # compute left hand side LHS = (dE_p_plus_v - dE_p) * v1 # assemble P with \mu = 1 self.mu = 1.0 self.mu_c = 1.0 P1 = self._make_sparse_precon(atoms, initial_assembly=True) # compute right hand side RHS = P1.dot(v1) * v1 # use partial sums to compute separate mu for positions and cell DoFs self.mu = longsum(LHS[:3 * natoms]) / longsum(RHS[:3 * natoms]) if self.mu < 1.0: warnings.warn('mu (%.3f) < 1.0, capping at mu=1.0' % self.mu) self.mu = 1.0 if isinstance(atoms, Filter): self.mu_c = longsum(LHS[3 * natoms:]) / longsum(RHS[3 * natoms:]) if self.mu_c < 1.0: print( 'mu_c (%.3f) < 1.0, capping at mu_c=1.0' % self.mu_c) self.mu_c = 1.0 print('estimate_mu(): mu=%r, mu_c=%r' % (self.mu, self.mu_c)) self.P = None # force a rebuild with new mu (there may be fixed atoms) return (self.mu, self.mu_c)
def estimate_mu(self, atoms, H=None): """ Estimate optimal preconditioner coefficient \mu \mu is estimated from a numerical solution of [dE(p+v) - dE(p)] \cdot v = \mu < P1 v, v > with perturbation v(x,y,z) = H P_lowest_nonzero_eigvec(x, y, z) or v(x,y,z) = H (sin(x / Lx), sin(y / Ly), sin(z / Lz)) After the optimal \mu is found, self.mu will be set to its value. If `atoms` is an instance of Filter an additional \mu_c will be computed for the cell degrees of freedom . Args: atoms: Atoms object for initial system H: 3x3 array or None Magnitude of deformation to apply. Default is 1e-2*rNN*np.eye(3) Returns: mu : float mu_c : float or None """ if self.dim != 3: raise ValueError('Automatic calculation of mu only possible for ' 'three-dimensional preconditioners. Try setting ' 'mu manually instead.') if self.r_NN is None: self.r_NN = estimate_nearest_neighbour_distance(atoms) # deformation matrix, default is diagonal if H is None: H = 1e-2 * self.r_NN * np.eye(3) # compute perturbation p = atoms.get_positions() if self.estimate_mu_eigmode: self.mu = 1.0 self.mu_c = 1.0 c_stab = self.c_stab self.c_stab = 0.0 if isinstance(atoms, Filter): n = len(atoms.atoms) else: n = len(atoms) P0 = self._make_sparse_precon(atoms, initial_assembly=True)[:3 * n, :3 * n] eigvals, eigvecs = sparse.linalg.eigsh(P0, k=4, which='SM') logger.debug('estimate_mu(): lowest 4 eigvals = %f %f %f %f' % (eigvals[0], eigvals[1], eigvals[2], eigvals[3])) # check eigenvalues if any(eigvals[0:3] > 1e-6): raise ValueError('First 3 eigenvalues of preconditioner matrix' 'do not correspond to translational modes.') elif eigvals[3] < 1e-6: raise ValueError('Fourth smallest eigenvalue of ' 'preconditioner matrix ' 'is too small, increase r_cut.') x = np.zeros(n) for i in range(n): x[i] = eigvecs[:, 3][3 * i] x = x / np.linalg.norm(x) if x[0] < 0: x = -x v = np.zeros(3 * len(atoms)) for i in range(n): v[3 * i] = x[i] v[3 * i + 1] = x[i] v[3 * i + 2] = x[i] v = v / np.linalg.norm(v) v = v.reshape((-1, 3)) self.c_stab = c_stab else: Lx, Ly, Lz = [p[:, i].max() - p[:, i].min() for i in range(3)] logger.debug('estimate_mu(): Lx=%.1f Ly=%.1f Lz=%.1f', Lx, Ly, Lz) x, y, z = p.T # sine_vr = [np.sin(x/Lx), np.sin(y/Ly), np.sin(z/Lz)], but we need # to take into account the possibility that one of Lx/Ly/Lz is # zero. sine_vr = [x, y, z] for i, L in enumerate([Lx, Ly, Lz]): if L == 0: logger.warning( 'Cell length L[%d] == 0. Setting H[%d,%d] = 0.' % (i, i, i)) H[i, i] = 0.0 else: sine_vr[i] = np.sin(sine_vr[i] / L) v = np.dot(H, sine_vr).T natoms = len(atoms) if isinstance(atoms, Filter): natoms = len(atoms.atoms) eps = H / self.r_NN v[natoms:, :] = eps v1 = v.reshape(-1) # compute LHS dE_p = -atoms.get_forces().reshape(-1) atoms_v = atoms.copy() atoms_v.set_calculator(atoms.get_calculator()) if isinstance(atoms, Filter): atoms_v = atoms.__class__(atoms_v) if hasattr(atoms, 'constant_volume'): atoms_v.constant_volume = atoms.constant_volume atoms_v.set_positions(p + v) dE_p_plus_v = -atoms_v.get_forces().reshape(-1) # compute left hand side LHS = (dE_p_plus_v - dE_p) * v1 # assemble P with \mu = 1 self.mu = 1.0 self.mu_c = 1.0 P1 = self._make_sparse_precon(atoms, initial_assembly=True) # compute right hand side RHS = P1.dot(v1) * v1 # use partial sums to compute separate mu for positions and cell DoFs self.mu = longsum(LHS[:3 * natoms]) / longsum(RHS[:3 * natoms]) if self.mu < 1.0: logger.info('mu (%.3f) < 1.0, capping at mu=1.0', self.mu) self.mu = 1.0 if isinstance(atoms, Filter): self.mu_c = longsum(LHS[3 * natoms:]) / longsum(RHS[3 * natoms:]) if self.mu_c < 1.0: logger.info( 'mu_c (%.3f) < 1.0, capping at mu_c=1.0', self.mu_c) self.mu_c = 1.0 logger.info('estimate_mu(): mu=%r, mu_c=%r', self.mu, self.mu_c) self.P = None # force a rebuild with new mu (there may be fixed atoms) return (self.mu, self.mu_c)
def handle_args(self, x_start, dirn, a_max, a1, func_start, func_old, func_prime_start): """Verify passed parameters and set appropriate attributes accordingly. A suitable value for the initial step-length guess will be either verified or calculated, stored in the attribute self.a_start, and returned. Args: The args should be identical to those of self.run(). Returns: The suitable initial step-length guess a_start Raises: ValueError for problems with arguments """ self.a_max = a_max self.x_start = x_start self.dirn = dirn self.func_old = func_old self.func_start = func_start self.func_prime_start = func_prime_start if a_max is None: a_max = 2.0 if a_max < self.tol: logger.warning( "a_max too small relative to tol. Reverting to " "default value a_max = 2.0 (twice the <ideal> step).") a_max = 2.0 # THIS ASSUMES NEWTON/BFGS TYPE BEHAVIOUR! if func_start is None: logger.debug("Setting func_start") self.func_start = self.func(x_start) self.phi_prime_start = longsum(self.func_prime_start * self.dirn) if self.phi_prime_start >= 0: logger.error("Passed direction which is not downhill. Aborting...") raise ValueError("Direction is not downhill.") elif math.isinf(self.phi_prime_start): logger.error("Passed func_prime_start and dirn which are too big. " "Aborting...") raise ValueError("func_prime_start and dirn are too big.") if a1 is None: if func_old is not None: # Interpolating a quadratic to func and func_old - see NW # equation 3.60 a1 = 2 * (self.func_start - self.func_old) / self.phi_prime_start logger.debug("Interpolated quadratic, obtained a1 = %e", a1) if a1 is None or a1 > a_max: logger.debug("a1 greater than a_max. Reverting to default value " "a1 = 1.0") a1 = 1.0 if a1 is None or a1 < self.tol: logger.debug( "a1 is None or a1 < self.tol. Reverting to default value " "a1 = 1.0") a1 = 1.0 self.a_start = a1 logger.debug("phi_start = %e, phi_prime_start = %e", self.func_start, self.phi_prime_start) logger.debug("func_start = %s, self.func_old = %s", self.func_start, self.func_old) logger.debug("a1 = %e, a_max = %e", a1, a_max) return a1
def handle_args(self, x_start, dirn, a_max, a1, func_start, func_old, func_prime_start): """Verify passed parameters and set appropriate attributes accordingly. A suitable value for the initial step-length guess will be either verified or calculated, stored in the attribute self.a_start, and returned. Args: The args should be identical to those of self.run(). Returns: The suitable initial step-length guess a_start Raises: ValueError for problems with arguments """ self.a_max = a_max self.x_start = x_start self.dirn = dirn self.func_old = func_old self.func_start = func_start self.func_prime_start = func_prime_start if a_max is None: a_max = 2.0 if a_max < self.tol: logger.warning("a_max too small relative to tol. Reverting to " "default value a_max = 2.0 (twice the <ideal> step).") a_max = 2.0 # THIS ASSUMES NEWTON/BFGS TYPE BEHAVIOUR! if func_start is None: logger.debug("Setting func_start") self.func_start = self.func(x_start) self.phi_prime_start = longsum(self.func_prime_start * self.dirn) if self.phi_prime_start >= 0: logger.error("Passed direction which is not downhill. Aborting...") raise ValueError("Direction is not downhill.") elif math.isinf(self.phi_prime_start): logger.error("Passed func_prime_start and dirn which are too big. " "Aborting...") raise ValueError("func_prime_start and dirn are too big.") if a1 is None: if func_old is not None: # Interpolating a quadratic to func and func_old - see NW # equation 3.60 a1 = 2*(self.func_start - self.func_old)/self.phi_prime_start logger.debug("Interpolated quadratic, obtained a1 = %e", a1) if a1 is None or a1 > a_max: logger.debug("a1 greater than a_max. Reverting to default value " "a1 = 1.0") a1 = 1.0 if a1 is None or a1 < self.tol: logger.debug("a1 is None or a1 < self.tol. Reverting to default value " "a1 = 1.0") a1 = 1.0 self.a_start = a1 logger.debug("phi_start = %e, phi_prime_start = %e", self.func_start, self.phi_prime_start) logger.debug("func_start = %s, self.func_old = %s", self.func_start, self.func_old) logger.debug("a1 = %e, a_max = %e", a1, a_max) return a1