Source code for nifty8.minimization.conjugate_gradient
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright(C) 2013-2019 Max-Planck-Society
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik.
import numpy as np
from ..logger import logger
from .minimizer import Minimizer
[docs]
class ConjugateGradient(Minimizer):
"""Implementation of the Conjugate Gradient scheme.
It is an iterative method for solving a linear system of equations:
Ax = b
Parameters
----------
controller : :py:class:`nifty8.IterationController`
Object that decides when to terminate the minimization.
nreset : int
every `nreset` CG steps the residual will be recomputed accurately
by applying the operator instead of updating the old residual
References
----------
Jorge Nocedal & Stephen Wright, "Numerical Optimization", Second Edition,
2006, Springer-Verlag New York
"""
[docs]
def __init__(self, controller, nreset=20):
self._controller = controller
self._nreset = nreset
[docs]
def __call__(self, energy, preconditioner=None):
"""Runs the conjugate gradient minimization.
Parameters
----------
energy : Energy object at the starting point of the iteration.
Its metric operator must be independent of position, otherwise
linear conjugate gradient minimization will fail.
preconditioner : Operator *optional*
This operator can be provided which transforms the variables of the
system to improve the conditioning. Default: None.
Returns
-------
QuadraticEnergy
state at last point of the iteration
int
Can be controller.CONVERGED or controller.ERROR
"""
controller = self._controller
status = controller.start(energy)
if status != controller.CONTINUE:
return energy, status
r = energy.gradient
d = r if preconditioner is None else preconditioner(r)
previous_gamma = r.s_vdot(d).real
if np.isnan(previous_gamma):
logger.error("Error: ConjugateGradient: previous_gamma==NaN")
return energy, controller.ERROR
if previous_gamma == 0:
return energy, controller.CONVERGED
ii = 0
while True:
q = energy.apply_metric(d)
curv = d.s_vdot(q).real
if np.isnan(curv):
logger.error("Error: ConjugateGradient: curv==NaN")
return energy, controller.ERROR
if curv == 0.:
logger.error("Error: ConjugateGradient: curv==0.")
return energy, controller.ERROR
alpha = previous_gamma/curv
if alpha < 0:
logger.error("Error: ConjugateGradient: alpha<0.")
return energy, controller.ERROR
ii += 1
if ii < self._nreset:
r = r - q*alpha
energy = energy.at_with_grad(energy.position - alpha*d, r)
else:
energy = energy.at(energy.position - alpha*d)
r = energy.gradient
ii = 0
s = r if preconditioner is None else preconditioner(r)
gamma = r.s_vdot(s).real
if np.isnan(gamma):
logger.error("Error: ConjugateGradient: gamma==NaN")
return energy, controller.ERROR
if gamma < 0:
logger.error(
"Positive definiteness of preconditioner violated!")
return energy, controller.ERROR
if gamma == 0:
return energy, controller.CONVERGED
status = controller.check(energy)
if status != controller.CONTINUE:
return energy, status
d = d * max(0, gamma/previous_gamma) + s
previous_gamma = gamma