inline double imagePotential(double eps, double epsSolv, double radius, const Eigen::Vector3d & origin, const Eigen::Vector3d & sp, const Eigen::Vector3d & pp) { Eigen::Vector3d sp_origin = sp - origin; double sp_origin_norm = sp_origin.norm(); Eigen::Vector3d pp_origin = pp - origin; double pp_origin_norm = pp_origin.norm(); double cos_gamma = sp_origin.dot(pp_origin) / (sp_origin.norm() * pp_origin.norm()); // Clean-up cos_gamma, Legendre polynomials are only defined for -1 <= x <= 1 if (numericalZero(cos_gamma - 1)) cos_gamma = 1.0; if (numericalZero(cos_gamma + 1)) cos_gamma = -1.0; // Image charge position Eigen::Vector3d r_img = origin + std::pow(radius / pp_origin_norm, 2) * pp_origin; double sp_image_norm = (sp - r_img).norm(); // Image charge double q_img = radius / pp_origin_norm; // Permittivity factor double factor = (eps - epsSolv) / (eps + epsSolv); // Image Green's function double G_img = factor * (q_img / sp_image_norm - q_img / sp_origin_norm); // Image Green's function // Accumulate Legendre polynomial expansion of image potential double f_0 = radius / (sp_origin_norm * pp_origin_norm); double f_l = f_0; for (int l = 1; l <= 200; ++l) { f_l = f_l * radius * f_0; double C_0_l = (eps - epsSolv) * l / ((eps + epsSolv) * l + epsSolv); double pl_x = boost::math::legendre_p(l, cos_gamma); G_img += f_l * (C_0_l - factor) * pl_x; } return G_img / epsSolv; }
inline double derivativeImagePotential(double eps, double epsSolv, double radius, const Eigen::Vector3d & origin, const Eigen::Vector3d & sp, const Eigen::Vector3d & ppNormal, const Eigen::Vector3d & pp) { Eigen::Vector3d sp_origin = sp - origin; double sp_origin_norm = sp_origin.norm(); Eigen::Vector3d pp_origin = pp - origin; double pp_origin_norm = pp_origin.norm(); double cos_gamma = sp_origin.dot(pp_origin) / (sp_origin_norm * pp_origin_norm); // Clean-up cos_gamma, Legendre polynomials are only defined for -1 <= x <= 1 if (numericalZero(cos_gamma - 1)) cos_gamma = 1.0; if (numericalZero(cos_gamma + 1)) cos_gamma = -1.0; double pp_origin_norm_3 = std::pow(pp_origin_norm, 3); Eigen::Vector3d r_img = origin + std::pow(radius / pp_origin_norm, 2) * pp_origin; Eigen::Vector3d pp_image = pp - r_img; double pp_image_norm_3 = std::pow(pp_image.norm(), 3); double factor = (eps - epsSolv) / (eps + epsSolv); double der_G_img = factor * (radius / sp_origin_norm) * (pp_origin.dot(ppNormal) / pp_origin_norm_3 - pp_image.dot(ppNormal) / pp_image_norm_3); /* // Accumulate Legendre polynomial expansion of image potential double f_0 = radius / (sp_origin_norm * pp_origin_norm); double f_l = f_0; double pp_origin_norm_l_3 = pp_origin_norm_3; // To accumulate (pp-origin).norm()^(l+3) double pp_origin_norm_l_1 = pp_origin_norm; // To accumulate (pp-origin).norm()^(l+1) for (int l = 1; l <= 200; ++l) { f_l = f_l * radius * f_0; pp_origin_norm_l_3 *= pp_origin_norm; pp_origin_norm_l_1 *= pp_origin_norm; double pl_x = boost::math::legendre_p(l, cos_gamma); // P_l(cos_gamma) double pl_1_x = boost::math::legendre_p(l+1, cos_gamma); // P_(l+1)(cos_gamma) double cos_denom = std::pow(cos_gamma, 2) - 1; double tmp_a = ((l+1) * pl_x * pp_origin.dot(ppNormal)) / pp_origin_norm_l_3; double tmp_b = ((l+1) * (pl_1_x - cos_gamma * pl_x) ) / (pp_origin_norm_l_1 * cos_denom); double tmp_c = sp_origin.dot(ppNormal) / (sp_origin_norm * pp_origin_norm); double tmp_d = (pp_origin.dot(sp_origin) * pp_origin.dot(ppNormal)) / (sp_origin_norm * pp_origin_norm_3); double tmp_e = tmp_b * (tmp_c - tmp_d); double C_0_l = (eps - epsSolv) * l / ((eps + epsSolv) * l + epsSolv); double C_l = C_0_l - factor; der_G_img += f_l * C_l * (-tmp_a + tmp_e); } */ return der_G_img / epsSolv; }
/*! Provides a functor for the evaluation of the system * of first-order ODEs needed by Boost.Odeint * The second-order ODE and the system of first-order ODEs * are reported in the manuscript. * \param[in] rho state vector holding the function and its first derivative * \param[out] drhodr state vector holding the first and second derivative * \param[in] r position on the integration grid */ void operator()(const StateType & rho, StateType & drhodr, const double r) { // Evaluate the dielectric profile double eps = 0.0, epsPrime = 0.0; pcm::tie(eps, epsPrime) = eval_(r); if (numericalZero(eps)) throw std::domain_error("Division by zero!"); double gamma_epsilon = epsPrime / eps; // System of equations is defined here drhodr[0] = rho[1]; drhodr[1] = -rho[1] * (rho[1] + 2.0/r + gamma_epsilon) + l_ * (l_ + 1) / std::pow(r, 2); }
/*! \fn inline void symmetryBlocking(Eigen::MatrixXd & matrix, int cavitySize, int ntsirr, int nr_irrep) * \param[out] matrix the matrix to be block-diagonalized * \param[in] cavitySize the size of the cavity (size of the matrix) * \param[in] ntsirr the size of the irreducible portion of the cavity (size of the blocks) * \param[in] nr_irrep the number of irreducible representations (number of blocks) */ inline void symmetryBlocking(Eigen::MatrixXd & matrix, size_t cavitySize, int ntsirr, int nr_irrep) { // This function implements the simmetry-blocking of the PCM // matrix due to point group symmetry as reported in: // L. Frediani, R. Cammi, C. S. Pomelli, J. Tomasi and K. Ruud, J. Comput.Chem. 25, 375 (2003) // u is the character table for the group (t in the paper) Eigen::MatrixXd u = Eigen::MatrixXd::Zero(nr_irrep, nr_irrep); for (int i = 0; i < nr_irrep; ++i) { for (int j = 0; j < nr_irrep; ++j) { u(i, j) = parity(i&j); } } // Naming of indices: // a, b, c, d run over the total size of the cavity (N) // i, j, k, l run over the number of irreps (n) // p, q, r, s run over the irreducible size of the cavity (N/n) // Instead of forming U (T in the paper) and then perform the dense // multiplication, we multiply block-by-block using just the u matrix. // matrix = U * matrix * Ut; U * Ut = Ut * U = id // First half-transformation, i.e. first_half = matrix * Ut Eigen::MatrixXd first_half = Eigen::MatrixXd::Zero(cavitySize, cavitySize); for (int i = 0; i < nr_irrep; ++i) { int ioff = i * ntsirr; for (int k = 0; k < nr_irrep; ++k) { int koff = k * ntsirr; for (int j = 0; j < nr_irrep; ++j) { int joff = j * ntsirr; double ujk = u(j, k) / nr_irrep; for (int p = 0; p < ntsirr; ++p) { int a = ioff + p; for (int q = 0; q < ntsirr; ++q) { int b = joff + q; int c = koff + q; first_half(a, c) += matrix(a, b) * ujk; } } } } } // Second half-transformation, i.e. matrix = U * first_half matrix.setZero(cavitySize, cavitySize); for (int i = 0; i < nr_irrep; ++i) { int ioff = i * ntsirr; for (int k = 0; k < nr_irrep; ++k) { int koff = k * ntsirr; for (int j = 0; j < nr_irrep; ++j) { int joff = j * ntsirr; double uij = u(i, j); for (int p = 0; p < ntsirr; ++p) { int a = ioff + p; int b = joff + p; for (int q = 0; q < ntsirr; ++q) { int c = koff + q; matrix(a, c) += uij * first_half(b, c); } } } } } // Traverse the matrix and discard numerical zeros for (size_t a = 0; a < cavitySize; ++a) { for (size_t b = 0; b < cavitySize; ++b) { if (numericalZero(matrix(a, b))) { matrix(a, b) = 0.0; } } } }