From 1b82593bcd883e4070928b4f0564a26eabda51a4 Mon Sep 17 00:00:00 2001
From: Martin Reinecke <martin@mpa-garching.mpg.de>
Date: Tue, 13 Mar 2018 13:37:30 +0100
Subject: [PATCH] add reference L_BFGS implementation

---
 nifty4/minimization/__init__.py           |  8 ++-
 nifty4/minimization/l_bfgs.py             | 76 +++++++++++++++++++++++
 test/test_minimization/test_minimizers.py |  3 +-
 3 files changed, 83 insertions(+), 4 deletions(-)
 create mode 100644 nifty4/minimization/l_bfgs.py

diff --git a/nifty4/minimization/__init__.py b/nifty4/minimization/__init__.py
index 0dd11d63c..b24aa2768 100644
--- a/nifty4/minimization/__init__.py
+++ b/nifty4/minimization/__init__.py
@@ -8,14 +8,16 @@ from .nonlinear_cg import NonlinearCG
 from .descent_minimizer import DescentMinimizer
 from .steepest_descent import SteepestDescent
 from .vl_bfgs import VL_BFGS
+from .l_bfgs import L_BFGS
 from .relaxed_newton import RelaxedNewton
 from .scipy_minimizer import ScipyMinimizer, NewtonCG, L_BFGS_B
 from .energy import Energy
 from .quadratic_energy import QuadraticEnergy
 from .line_energy import LineEnergy
 
-__all__ = ["LineSearch", "LineSearchStrongWolfe", "IterationController",
-           "GradientNormController",
+__all__ = ["LineSearch", "LineSearchStrongWolfe",
+           "IterationController", "GradientNormController",
            "Minimizer", "ConjugateGradient", "NonlinearCG", "DescentMinimizer",
            "SteepestDescent", "VL_BFGS", "RelaxedNewton", "ScipyMinimizer",
-           "NewtonCG", "L_BFGS_B", "Energy", "QuadraticEnergy", "LineEnergy"]
+           "NewtonCG", "L_BFGS_B", "Energy", "QuadraticEnergy", "LineEnergy",
+           "L_BFGS"]
diff --git a/nifty4/minimization/l_bfgs.py b/nifty4/minimization/l_bfgs.py
new file mode 100644
index 000000000..c7e910e34
--- /dev/null
+++ b/nifty4/minimization/l_bfgs.py
@@ -0,0 +1,76 @@
+# 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-2018 Max-Planck-Society
+#
+# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
+# and financially supported by the Studienstiftung des deutschen Volkes.
+
+from __future__ import division
+from builtins import range
+import numpy as np
+from .descent_minimizer import DescentMinimizer
+from .line_search_strong_wolfe import LineSearchStrongWolfe
+from .. import dobj
+
+
+class L_BFGS(DescentMinimizer):
+
+    def __init__(self, controller, line_searcher=LineSearchStrongWolfe(),
+                 max_history_length=5):
+        super(L_BFGS, self).__init__(controller=controller,
+                                     line_searcher=line_searcher)
+        self.max_history_length = max_history_length
+
+    def __call__(self, energy):
+        self.reset()
+        return super(L_BFGS, self).__call__(energy)
+
+    def reset(self):
+        self._k = 0
+        self._s = [None]*self.max_history_length
+        self._y = [None]*self.max_history_length
+
+    def get_descent_direction(self, energy):
+        x = energy.position
+        s = self._s
+        y = self._y
+        k = self._k
+        maxhist = self.max_history_length
+        gradient = energy.gradient
+
+        nhist = min(k, maxhist)
+        alpha = [None]*maxhist
+        p = -gradient
+        if k > 0:
+            idx = (k-1) % maxhist
+            s[idx] = x-self._lastx
+            y[idx] = gradient-self._lastgrad
+        if nhist > 0:
+            for i in range(k-1, k-nhist-1, -1):
+                idx = i % maxhist
+                alpha[idx] = s[idx].vdot(p)/s[idx].vdot(y[idx])
+                p -= alpha[idx]*y[idx]
+            idx = (k-1) % maxhist
+            fact = s[idx].vdot(y[idx]) / y[idx].vdot(y[idx])
+            if fact <= 0.:
+                dobj.mprint("L-BFGS curvature not positive definite!")
+            p *= fact
+            for i in range(k-nhist, k):
+                idx = i % maxhist
+                beta = y[idx].vdot(p) / s[idx].vdot(y[idx])
+                p += (alpha[idx]-beta)*s[idx]
+        self._lastx = x
+        self._lastgrad = gradient
+        self._k += 1
+        return p
diff --git a/test/test_minimization/test_minimizers.py b/test/test_minimization/test_minimizers.py
index 76807ab92..222b91c7a 100644
--- a/test/test_minimization/test_minimizers.py
+++ b/test/test_minimization/test_minimizers.py
@@ -34,7 +34,8 @@ minimizers = ['ift.VL_BFGS(IC)',
               'ift.NonlinearCG(IC, "Fletcher-Reeves")',
               'ift.NonlinearCG(IC, "5.49")',
               'ift.NewtonCG(IC)',
-              'ift.L_BFGS_B(IC)']
+              'ift.L_BFGS_B(IC)',
+              'ift.L_BFGS(IC)']
 
 newton_minimizers = ['ift.RelaxedNewton(IC)']
 quadratic_only_minimizers = ['ift.ConjugateGradient(IC)']
-- 
GitLab