Skip to content

Commit a8b52f5

Browse files
author
Roberto Di Remigio
committed
Switch to robust LDLt for CPCM.
This avoids computing and storing the explicit inverse of SI. Symmetrization to polarization weights is done on the computed charges.
1 parent 8857799 commit a8b52f5

File tree

8 files changed

+88
-82
lines changed

8 files changed

+88
-82
lines changed

CHANGELOG.md

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22

33
## [Unreleased]
44

5+
### Changed
6+
7+
- The `PEDRA.OUT` cavity generator log now reports the initial _and_ final
8+
lists of spheres. The final list only contains those spheres that were
9+
actually tesselated and, possibly, the added spheres.
10+
- The `CPCMSolver` object now stores the scaled, unsymmetrized S matrix. The
11+
explicit calculation and storage of its inverse is thus avoided. A robust
12+
Cholesky decomposition is used to solve the linear equation system. The
13+
symmetrization needed to obtain the polarization weights happens directly on
14+
the computed charges.
15+
- The `hermitivitize` function can now correctly symmetrize vectors.
16+
17+
### Fixed
18+
19+
- A fix for the initialization of the explicit list of spheres when running the
20+
standalone executable. The bug prevented the generation of the correct
21+
`Molecule` object, with subsequent failure in the cavity generator.
22+
- A memory leak occuring in the cavity generator PEDRA was fixed. This was uncovered by @shoefener
23+
and manifested only with considerably large cavities (> 200 input spheres)
24+
25+
### Removed
26+
27+
- The function `CPCMMatrix` in the `SolverImpl.hpp` header file is no longer available.
28+
529
## [v1.1.2] (2016-05-31)
630

731
### Fixed
@@ -26,7 +50,7 @@
2650
program execution if this is the case.
2751
- An API function to retrieve the areas/weights of the cavity finite elements.
2852
The values in the returned array are in Bohr^2. Addresses a feature request
29-
from @shofener (Issue #13)
53+
from @shoefener (Issue #13)
3054
- The standalone executable `run_pcm` is now tested within the unit tests
3155
suite. The tests cover the cases where the cavity is given implicitly,
3256
explicitly or by substitution of radii on chosen atoms.
@@ -36,12 +60,12 @@
3660
- Boundary integral operators classes learnt to accept a scaling factor for the
3761
diagonal elements of the approximate collocation matrices. The change is
3862
reflected in the Green's funtion classes and in the input parsing. Addresses
39-
a feature request from @shofener (Issue #16)
40-
- GePolCavity learnt to print also the list of spheres used to generate the
63+
a feature request from @shoefener (Issue #16)
64+
- `GePolCavity` learnt to print also the list of spheres used to generate the
4165
cavity.
4266
- Different internal handling of conversion factors from Bohr to Angstrom.
4367
- CMake minimum required version is 2.8.10
44-
- Atom, Solvent and Sphere are now PODs. The radii and solvent lists are free
68+
- `Atom`, `Solvent` and `Sphere` are now PODs. The radii and solvent lists are free
4569
functions.
4670
- `PCMSOLVER_ERROR` kills program execution when an error arises but does not
4771
use C++ exceptions.
@@ -53,15 +77,15 @@
5377

5478
### Known Issues
5579

56-
- The new printer in GePolCavity might not work properly when an explicit list
80+
- The new printer in `GePolCavity` might not work properly when an explicit list
5781
of spheres is provided in the input.
5882
- On Ubuntu 12.10, 32 bit the Intel compiler version 2013.1 produces a faulty
5983
library. It is possibly a bug in the implementation of `iso_c_binding`, see
6084
Issue #25
6185

6286
### Removed
6387

64-
- SurfaceFunction as a class is no longer available. We keep track of surface
88+
- `SurfaceFunction` as a class is no longer available. We keep track of surface
6589
functions at the interface level _via_ a label-vector map.
6690

6791
## [v1.1.0] (2016-02-07)

doc/users/input.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ Medium section keywords
174174

175175
.. glossary::
176176

177-
Solver
177+
SolverType
178178
Type of solver to be used. All solvers are based on the Integral Equation Formulation of
179179
the Polarizable Continuum Model :cite:`Cances1998`
180180

src/pedra/pedra_cavity.F90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ subroutine polyhedra(intsph,vert,centr,newsph,icav1,icav2,xval,yval, &
478478
call output(pts, 1_regint_k, 3_regint_k, 1_regint_k, nv, 3_regint_k, nv, 1_regint_k, lvpri)
479479
end if
480480
if(abs(area) <= 1.0d-14) then
481-
write(lvpri, '(a, i4)') "Zero area in tessera", nn + 1
481+
if (iprpcm >= 10) write(lvpri, '(a, i8)') "Zero area in tessera ", nn + 1
482482
go to 310
483483
end if
484484
if (nn >= mxts) then
@@ -1313,7 +1313,7 @@ subroutine tessera(ns, nv, pts, ccc, pp, pp1, area, intsph, numts)
13131313
end do
13141314
ICUT = ICUT / 2
13151315
IF(ICUT > 1) THEN
1316-
WRITE(LVPRI,*) 'Tessera cut in pieces and removed.'
1316+
IF(IPRPCM >= 10) WRITE(LVPRI,*) 'Tessera cut in pieces and removed.'
13171317
RETURN
13181318
end if
13191319

src/pedra/pedra_cavity_interface.F90

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ subroutine generatecavity_cpp(maxts_, maxsph_, maxvert_, &
166166
deallocate(centr)
167167

168168
write(pedra_unit, *) "Error code is ", error_code
169+
write(pedra_unit, *) '<<< Done with PEDRA Fortran code >>>'
169170

170171
close(pedra_unit)
171172

src/solver/CPCMSolver.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
#include "Config.hpp"
3333

3434
#include <Eigen/Core>
35-
#include <Eigen/LU>
35+
#include <Eigen/Cholesky>
3636

3737
#include "cavity/Cavity.hpp"
3838
#include "cavity/Element.hpp"
@@ -43,17 +43,26 @@
4343
void CPCMSolver::buildSystemMatrix_impl(const Cavity & cavity, const IGreensFunction & gf_i, const IGreensFunction & gf_o)
4444
{
4545
if (!isotropic_) PCMSOLVER_ERROR("C-PCM is defined only for isotropic environments!", BOOST_CURRENT_FUNCTION);
46-
fullPCMMatrix_ = CPCMMatrix(cavity, gf_i, profiles::epsilon(gf_o.permittivity()), correction_);
47-
// Symmetrize K := (K + K+)/2
48-
if (hermitivitize_) {
49-
hermitivitize(fullPCMMatrix_);
50-
}
46+
TIMER_ON("Computing SI");
47+
double epsilon = profiles::epsilon(gf_o.permittivity());
48+
SI_ = gf_i.singleLayer(cavity.elements());
49+
SI_ /= (epsilon - 1.0)/(epsilon + correction_);
50+
TIMER_OFF("Computing SI");
51+
5152
// Symmetry-pack
5253
// The number of irreps in the group
5354
int nrBlocks = cavity.pointGroup().nrIrrep();
5455
// The size of the irreducible portion of the cavity
5556
int dimBlock = cavity.irreducible_size();
56-
symmetryPacking(blockPCMMatrix_, fullPCMMatrix_, dimBlock, nrBlocks);
57+
// Perform symmetry blocking
58+
// If the group is C1 avoid symmetry blocking, we will just pack the fullPCMMatrix
59+
// into "block diagonal" when all other manipulations are done.
60+
if (cavity.pointGroup().nrGenerators() != 0) {
61+
TIMER_ON("Symmetry blocking");
62+
symmetryBlocking(SI_, cavity.size(), dimBlock, nrBlocks);
63+
TIMER_OFF("Symmetry blocking");
64+
}
65+
symmetryPacking(blockSI_, SI_, dimBlock, nrBlocks);
5766

5867
built_ = true;
5968
}
@@ -63,12 +72,15 @@ Eigen::VectorXd CPCMSolver::computeCharge_impl(const Eigen::VectorXd & potential
6372
// The potential and charge vector are of dimension equal to the
6473
// full dimension of the cavity. We have to select just the part
6574
// relative to the irrep needed.
66-
int fullDim = fullPCMMatrix_.rows();
75+
int fullDim = SI_.rows();
6776
Eigen::VectorXd charge = Eigen::VectorXd::Zero(fullDim);
68-
int nrBlocks = blockPCMMatrix_.size();
77+
int nrBlocks = blockSI_.size();
6978
int irrDim = fullDim/nrBlocks;
7079
charge.segment(irrep*irrDim, irrDim) =
71-
- blockPCMMatrix_[irrep] * potential.segment(irrep*irrDim, irrDim);
80+
- blockSI_[irrep].ldlt().solve(potential.segment(irrep*irrDim, irrDim));
81+
// Symmetrize ASC charge := (charge + charge*)/2
82+
if (hermitivitize_) hermitivitize(charge);
83+
7284
return charge;
7385
}
7486

src/solver/CPCMSolver.hpp

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@
22
/*
33
* PCMSolver, an API for the Polarizable Continuum Model
44
* Copyright (C) 2013-2015 Roberto Di Remigio, Luca Frediani and contributors
5-
*
5+
*
66
* This file is part of PCMSolver.
7-
*
7+
*
88
* PCMSolver is free software: you can redistribute it and/or modify
99
* it under the terms of the GNU Lesser General Public License as published by
1010
* the Free Software Foundation, either version 3 of the License, or
1111
* (at your option) any later version.
12-
*
12+
*
1313
* PCMSolver is distributed in the hope that it will be useful,
1414
* but WITHOUT ANY WARRANTY; without even the implied warranty of
1515
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1616
* GNU Lesser General Public License for more details.
17-
*
17+
*
1818
* You should have received a copy of the GNU Lesser General Public License
1919
* along with PCMSolver. If not, see <http://www.gnu.org/licenses/>.
20-
*
20+
*
2121
* For information on the complete list of contributors to the
2222
* PCMSolver API, see: <http://pcmsolver.readthedocs.org/>
2323
*/
@@ -43,7 +43,13 @@ class IGreensFunction;
4343
* \class CPCMSolver
4444
* \brief Solver for conductor-like approximation: C-PCM (COSMO)
4545
* \author Roberto Di Remigio
46-
* \date 2013
46+
* \date 2013, 2016
47+
*
48+
* \note We store the unsymmetrized SI matrix and use a robust
49+
* Cholesky decomposition to solve for the ASC. The ASC is then
50+
* symmetrized. This avoids computing and storing the inverse explicitly.
51+
* The SI matrix is already scaled by the dielectric factor entering the
52+
* definition of the conductor model!
4753
*/
4854

4955
class CPCMSolver : public PCMSolver
@@ -65,10 +71,10 @@ class CPCMSolver : public PCMSolver
6571
bool hermitivitize_;
6672
/*! Correction for the conductor results */
6773
double correction_;
68-
/*! PCM matrix, not symmetry blocked */
69-
Eigen::MatrixXd fullPCMMatrix_;
70-
/*! PCM matrix, symmetry blocked form */
71-
std::vector<Eigen::MatrixXd> blockPCMMatrix_;
74+
/*! SI matrix, not symmetry blocked */
75+
Eigen::MatrixXd SI_;
76+
/*! SI matrix, symmetry blocked form */
77+
std::vector<Eigen::MatrixXd> blockSI_;
7278

7379
/*! \brief Calculation of the PCM matrix
7480
* \param[in] cavity the cavity to be used

src/solver/SolverImpl.hpp

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -192,49 +192,6 @@ inline Eigen::MatrixXd isotropicIEFMatrix(const Cavity & cav, const IGreensFunct
192192
return fullPCMMatrix;
193193
}
194194

195-
/*! \brief Builds the CPCM matrix
196-
* \param[in] cav the discretized cavity
197-
* \param[in] gf_i Green's function inside the cavity
198-
* \param[in] epsilon permittivity outside the cavity
199-
* \param[in] correction CPCM correction factor
200-
* \return the \f$ \mathbf{K} = f(\varepsilon)\mathbf{S}^{-1} \f$ matrix
201-
*
202-
* This function calculates the PCM matrix.
203-
* The matrix is not symmetrized and is not symmetry packed.
204-
*/
205-
inline Eigen::MatrixXd CPCMMatrix(const Cavity & cav, const IGreensFunction & gf_i, double epsilon, double correction)
206-
{
207-
// The total size of the cavity
208-
PCMSolverIndex cavitySize = cav.size();
209-
// The number of irreps in the group
210-
int nrBlocks = cav.pointGroup().nrIrrep();
211-
// The size of the irreducible portion of the cavity
212-
int dimBlock = cav.irreducible_size();
213-
214-
// Compute SI and DI on the whole cavity, regardless of symmetry
215-
TIMER_ON("Computing SI");
216-
Eigen::MatrixXd SI = gf_i.singleLayer(cav.elements());
217-
TIMER_OFF("Computing SI");
218-
219-
// Perform symmetry blocking
220-
// If the group is C1 avoid symmetry blocking, we will just pack the fullPCMMatrix
221-
// into "block diagonal" when all other manipulations are done.
222-
if (cav.pointGroup().nrGenerators() != 0) {
223-
TIMER_ON("Symmetry blocking");
224-
symmetryBlocking(SI, cavitySize, dimBlock, nrBlocks);
225-
TIMER_OFF("Symmetry blocking");
226-
}
227-
228-
double fact = (epsilon - 1.0)/(epsilon + correction);
229-
// Invert SI using LU decomposition with full pivoting
230-
// This is a rank-revealing LU decomposition, this allows us
231-
// to test if SI is invertible before attempting to invert it.
232-
Eigen::FullPivLU<Eigen::MatrixXd> SI_LU(SI);
233-
if (!(SI_LU.isInvertible()))
234-
PCMSOLVER_ERROR("SI matrix is not invertible!", BOOST_CURRENT_FUNCTION);
235-
return fact * SI_LU.inverse();
236-
}
237-
238195
/*! \brief Builds the **anisotropic** \f$ \mathbf{T}_\varepsilon \f$ matrix
239196
* \param[in] cav the discretized cavity
240197
* \param[in] gf_i Green's function inside the cavity

src/utils/MathUtils.hpp

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -203,19 +203,25 @@ inline void symmetryPacking(std::vector<Eigen::MatrixXd> & blockedMatrix,
203203
}
204204
}
205205

206-
/*! \fn inline void hermitivitize(Eigen::MatrixBase<Derived> & matrix_)
207-
* \param[out] matrix_ the matrix to be hermitivitized
208-
* \tparam Derived the numeric type of matrix_ elements
206+
/*! \fn inline void hermitivitize(Eigen::MatrixBase<Derived> & obj_)
207+
* \param[out] obj_ the Eigen object to be hermitivitized
208+
* \tparam Derived the numeric type of obj_ elements
209209
*
210-
* Given matrix_ returns 0.5 * (matrix_ + matrix_^dagger)
210+
* Given obj_ returns 0.5 * (obj_ + obj_^dagger)
211+
* \note We check if a matrix or vector was given, since in the latter
212+
* case we only want the complex conjugation operation to happen.
211213
*/
212-
template <typename Derived>
213-
inline void hermitivitize(Eigen::MatrixBase<Derived> & matrix_)
214+
template <typename Derived>
215+
inline void hermitivitize(Eigen::MatrixBase<Derived> & obj_)
214216
{
215-
// We need to use adjoint().eval() to avoid aliasing issues, see:
216-
// http://eigen.tuxfamily.org/dox/group__TopicAliasing.html
217-
// The adjoint is evaluated explicitly into an intermediate.
218-
matrix_ = 0.5 * (matrix_ + matrix_.adjoint().eval());
217+
// We need to use adjoint().eval() to avoid aliasing issues, see:
218+
// http://eigen.tuxfamily.org/dox/group__TopicAliasing.html
219+
// The adjoint is evaluated explicitly into an intermediate.
220+
if ((obj_.rows() != 1) && (obj_.cols() != 1)) {
221+
obj_ = 0.5 * (obj_ + obj_.adjoint().eval());
222+
} else {
223+
obj_ = 0.5 * (obj_ + obj_.conjugate().eval());
224+
}
219225
}
220226

221227
/*! \fn inline void eulerRotation(Eigen::Matrix3d & R_, const Eigen::Vector3d & eulerAngles_)

0 commit comments

Comments
 (0)