def solve_poisson_becke(density_decomposition): '''Compute the electrostatic potential of a density expanded in real spherical harmonics **Arguments:** density_decomposition A list of cubic splines returned by the method AtomicGrid.get_spherical_decomposition. The returned list of splines is a spherical decomposition of the hartree potential (felt by a particle with the same charge unit as the density). ''' biblio.cite('becke1988_poisson', 'the numerical integration of the Poisson equation') lmax = np.sqrt(len(density_decomposition)) - 1 assert lmax == int(lmax) lmax = int(lmax) result = [] counter = 0 for l in xrange(0, lmax + 1): for m in xrange(-l, l + 1): rho = density_decomposition[counter] rtf = rho.rtransform rgrid = RadialGrid(rtf) radii = rtf.get_radii() # The approach followed here is obtained after substitution of # u = r*V in Eq. (21) in Becke's paper. After this transformation, # the boundary conditions can be implemented such that the output # is more accurate. fy = -4 * np.pi * rho.y fd = -4 * np.pi * rho.dx f = CubicSpline(fy, fd, rtf) b = CubicSpline(2 / radii, -2 / radii**2, rtf) a = CubicSpline(-l * (l + 1) * radii**-2, 2 * l * (l + 1) * radii**-3, rtf) # Derivation of boundary condition at rmax: # Multiply differential equation with r**l and integrate. Using # partial integration and the fact that V(r)=A/r**(l+1) for large # r, we find -(2l+1)A=-4pi*int_0^infty r**2 r**l rho(r) and so # V(rmax) = A/rmax**(l+1) = integrate(r**l rho(r))/(2l+1)/rmax**(l+1) V_rmax = rgrid.integrate( rho.y * radii**l) / radii[-1]**(l + 1) / (2 * l + 1) # Derivation of boundary condition at rmin: # Same as for rmax, but multiply differential equation with r**(-l-1) # and assume that V(r)=B*r**l for small r. V_rmin = rgrid.integrate( rho.y * radii**(-l - 1)) * radii[0]**(l) / (2 * l + 1) bcs = (V_rmin, None, V_rmax, None) v = solve_ode2(b, a, f, bcs, PotentialExtrapolation(l)) result.append(v) counter += 1 return result
def get_spline(self, number, parameters=0, combine='linear'): '''Construct a proatom spline. **Arguments:** See ``get_rho`` method. ''' rho, deriv = self.get_rho(number, parameters, combine, do_deriv=True) return CubicSpline(rho, deriv, self.get_rgrid(number).rtransform)
def get_cosine_spline(): # Construct a simple spline for the function cos(x)+1 in the range 0,pi rtf = LinearRTransform(0.0, np.pi, 100) x = rtf.get_radii() y = np.cos(x) + 1 d = -np.sin(x) return CubicSpline(y, d, rtf)
def solve_ode2(b, a, f, bcs, extrapolation=None): '''Solve a second order ODE. **Arguments:** b, a, f Cubic splines for the given functions in the second order ODE. (See build_neumann for details.) These cubic splines must have identical RTransform objects. bcs The boundary conditions (See build_neumann for details.) **Optional arguments:** extrapolation The extrapolation object for the returned cubic spline. **Returns:** a cubic spline object with the solution that uses the same RTransform object as the input functions a, b and f. ''' # Parse args. rtf = b.rtransform if rtf.to_string() != a.rtransform.to_string(): raise ValueError('The RTransform objects of b and a do not match.') if rtf.to_string() != f.rtransform.to_string(): raise ValueError('The RTransform objects of b and f do not match.') # Transform the given functions to the linear coordinate. j1 = rtf.get_deriv() j2 = rtf.get_deriv2() j3 = rtf.get_deriv3() j1sq = j1 * j1 by_new = j1 * b.y - j2 / j1 bd_new = j2 * b.y + j1sq * b.dx + (j2 * j2 - j1 * j3) / j1sq ay_new = a.y * j1sq ad_new = (a.dx * j1sq + 2 * a.y * j2) * j1 fy_new = f.y * j1sq fd_new = (f.dx * j1sq + 2 * f.y * j2) * j1 # Transform the boundary conditions new_bcs = ( bcs[0], None if bcs[1] is None else bcs[1] * j1[0], bcs[2], None if bcs[3] is None else bcs[3] * j1[-1], ) # Call the equation builder. coeffs, rhs = build_ode2(by_new, bd_new, ay_new, ad_new, fy_new, fd_new, new_bcs) solution = spsolve(csc_matrix(coeffs), rhs) uy_new = solution[::2] ud_new = solution[1::2] # Transform solution back to the original coordinate. uy_orig = uy_new.copy() # A copy of is needed to obtain contiguous arrays. ud_orig = ud_new / j1 return CubicSpline(uy_orig, ud_orig, rtf, extrapolation)
def get_wcor_fit_funcs(self, index): # skip wcor if the element is not listed among those who need a correction: if self.numbers[index] not in self.wcor_numbers: return [] center = self.coordinates[index] atom_nbasis = self.hebasis.get_atom_nbasis(index) rtf = self.get_rgrid(index).rtransform splines = [] for j0 in xrange(atom_nbasis): rho0 = self.hebasis.get_basis_rho(index, j0) splines.append(CubicSpline(rho0, rtransform=rtf)) for j1 in xrange(j0 + 1): rho1 = self.hebasis.get_basis_rho(index, j1) splines.append(CubicSpline(rho0 * rho1, rtransform=rtf)) return [(center, splines)]
def get_proatom_spline(self, index, *args, **kwargs): # Get the radial density rho, deriv = self.get_proatom_rho(index, *args, **kwargs) # Double check and fix if needed rho, deriv = self.fix_proatom_rho(index, rho, deriv) # Make a spline rtf = self.get_rgrid(index).rtransform return CubicSpline(rho, deriv, rtf)
def get_spherical_decomposition(self, *args, **kwargs): r'''Returns the decomposition of the product of the given functions in spherical harmonics **Arguments:** data1, data2, ... All arguments must be arrays with the same size as the number of grid points. The arrays contain the functions, evaluated at the grid points. **Optional arguments:** lmax=0 The maximum angular momentum to consider when computing multipole moments. **Remark** A series of radial functions is returned as CubicSpline objects. These radial functions are defined as: .. math:: f_{\ell m}(r) = \int f(\mathbf{r}) Y_{\ell m}(\mathbf{r}) d \Omega where the integral is carried out over angular degrees of freedom and :math:`Y_{\ell m}` are real spherical harmonics. The radial functions can be used to reconstruct the original integrand as follows: .. math:: f(\mathbf{r}) = \sum_{\ell m} f_{\ell m}(|\mathbf{r}|) Y_{\ell m}(\mathbf{r}) One can also derive the pure multipole moments by integrating these radial functions as follows: .. math:: Q_{\ell m} = \sqrt{\frac{4\pi}{2\ell+1}} \int_0^\infty f_{\ell m}(r) r^l r^2 dr (These multipole moments are equivalent to the ones obtained with option ``mtype==2`` of the ``integrate`` method). ''' lmax = kwargs.pop('lmax', 0) if lmax < 0: raise ValueError('lmax can not be negative.') if len(kwargs) > 0: raise TypeError('Unexpected keyword argument: %s' % kwargs.popitem()[0]) angular_ints = self.integrate(*args, center=self.center, mtype=4, lmax=lmax, segments=self.nlls) angular_ints /= self.integrate(segments=self.nlls).reshape(-1, 1) results = [] counter = 0 lmaxs = self.lmaxs for l in xrange(0, lmax + 1): mask = lmaxs < 2 * l for m in xrange(-l, l + 1): f = angular_ints[:, counter].copy() f *= np.sqrt( 4 * np.pi * (2 * l + 1)) # proper norm for spherical harmonics f[mask] = 0 # mask out unreliable results results.append(CubicSpline(f, rtransform=self.rgrid.rtransform)) counter += 1 return results
def get_exp_spline(): rtf = LinearRTransform(0.0, 20.0, 100) x = rtf.get_radii() y = np.exp(-0.2 * x) d = -0.2 * np.exp(-0.2 * x) return CubicSpline(y, d, rtf)