While preconditioned CG with incomplete Cholesky (ICC) is reasonably straightforward to formulate mathematically, writing an efficient implementation is a non-trivial matter.
Here's some of the things that would make for an efficient implementation:
- the ICC implementation exploits the sparsity of your matrix $A$ (you'll want to store $A$ and $M$ in some sparse matrix format)
- $M$ is computationally "easy" to invert (you won't compute the actual inverse $M^{-1}$, just its action on a vector)
- you set appropriate hyper-parameters for the ICC algorithm (e.g., block size, levels of fill-in, zero threshold) and (pre-)allocate memory space for $M$ accordingly
- miscellaneous optimizations (e.g., the Eisenstat trick, see the paper by Chan)
- code optimizations that target your specific hardware platform (I won't go into specifics here).
At the end of the day, what you're after is a preconditioned iteration that converges faster (in some objective measure) than its un-preconditioned counterpart.
References to get you started:
Yousef Saad, Iterative methods for sparse linear
systems -
in particular chapters 9.2, 9.2.1 and 9.2.2 deal with the preconditioned
CG iteration (there is some pseudocode that you can base your initial implementation on).
Chan and van der Vorst, Approximate and Incomplete
Factorizations
(they discuss incomplete LU and its variants, but the ideas there
apply to ICC as well).
Chih-Jen Lin and Jorge J. Moré, Incomplete Cholesky Factorizations
with Limited
Memory, SIAM SISC 21(1), 1999
Some high-quality C/C++ ICC implementations (these libraries have a fairly steep learning curve, but the source code is available for inspection):
PETSc's ICC routines.
IFPACK (part of the Trilinos
suite). The user manual can be found here.
Eigen's C++ ICC preconditioners for CG.