From d5baec8108a9aa3603122939a94dee206d195401 Mon Sep 17 00:00:00 2001
From: Michael Kuron <mkuron@icp.uni-stuttgart.de>
Date: Mon, 13 Feb 2017 16:17:59 +0100
Subject: [PATCH] Python 2.7 compatibility

This commit makes the Python code backwards compatible down to Python 2.7. Previously it would only run on Python 3.5 and up.

Problems fixed included:
- `time.perf_counter()` doesn't exist
- all classes need to be new-style
- `functools.lru_cache` doesn't exist
- only the last argument to a function call can be `*`-expanded
- the `nonlocal` keyword doesn't exist
- metaclasses are used with a different syntax
- `yield from` doesn't exist
- `tempdir.TemporaryDirectory` doesn't exist
- iterators need a `next()` method
---
 boundaries/boundaryhandling.py          |  2 +-
 diskcache.py => cache.py                | 11 ++++---
 continuous_distribution_measures.py     |  7 ++---
 creationfunctions.py                    | 13 ++++----
 cumulants.py                            | 11 +++----
 fieldaccess.py                          |  2 +-
 forcemodels.py                          |  6 ++--
 gridvisualization.py                    |  2 +-
 maxwellian_equilibrium.py               |  2 +-
 methods/abstractlbmethod.py             |  3 +-
 methods/conservedquantitycomputation.py |  2 +-
 methods/creationfunctions.py            | 11 +++----
 methods/entropic.py                     |  2 +-
 methods/momentbasedsimplifications.py   |  2 +-
 moments.py                              | 14 ++++++---
 serialscenario.py                       | 42 ++++++++++---------------
 16 files changed, 62 insertions(+), 70 deletions(-)
 rename diskcache.py => cache.py (65%)

diff --git a/boundaries/boundaryhandling.py b/boundaries/boundaryhandling.py
index 213fc1f6..4cfa82d5 100644
--- a/boundaries/boundaryhandling.py
+++ b/boundaries/boundaryhandling.py
@@ -11,7 +11,7 @@ INV_DIR_SYMBOL = TypedSymbol("invDir", "int")
 WEIGHTS_SYMBOL = TypedSymbol("weights", "double")
 
 
-class BoundaryHandling:
+class BoundaryHandling(object):
     def __init__(self, symbolicPdfField, domainShape, lbMethod, ghostLayers=1, target='cpu'):
         self._symbolicPdfField = symbolicPdfField
         self._shapeWithGhostLayers = [d + 2 * ghostLayers for d in domainShape]
diff --git a/diskcache.py b/cache.py
similarity index 65%
rename from diskcache.py
rename to cache.py
index 75e7f8f3..96e46531 100644
--- a/diskcache.py
+++ b/cache.py
@@ -1,10 +1,14 @@
+try:
+    from functools import lru_cache as memorycache
+except ImportError:
+    from backports.functools_lru_cache import lru_cache as memorycache
+
 try:
     from joblib import Memory
     diskcache = Memory(cachedir="/tmp/lbmpy", verbose=False).cache
 except ImportError:
     # fallback to in-memory caching if joblib is not available
-    import functools
-    diskcache = functools.lru_cache(maxsize=64)
+    diskcache = memorycache(maxsize=64)
 
 
 # joblibs Memory decorator does not play nicely with sphinx autodoc (decorated functions do not occur in autodoc)
@@ -12,5 +16,4 @@ except ImportError:
 import sys
 calledBySphinx = 'sphinx' in sys.modules
 if calledBySphinx:
-    import functools
-    diskcache = functools.lru_cache(maxsize=64)
\ No newline at end of file
+    diskcache = memorycache(maxsize=64)
\ No newline at end of file
diff --git a/continuous_distribution_measures.py b/continuous_distribution_measures.py
index df384282..acf6cf05 100644
--- a/continuous_distribution_measures.py
+++ b/continuous_distribution_measures.py
@@ -3,12 +3,11 @@
 """
 
 import sympy as sp
-import functools
 from pystencils.sympyextensions import makeExponentialFuncArgumentSquares
-from lbmpy.diskcache import diskcache
+from lbmpy.cache import diskcache, memorycache
 
 
-@functools.lru_cache()
+@memorycache()
 def momentGeneratingFunction(function, symbols, symbolsInResult):
     """
     Computes the moment generating function of a probability distribution. It is defined as:
@@ -87,7 +86,7 @@ def multiDifferentiation(generatingFunction, index, symbols):
     return r
 
 
-@functools.lru_cache(maxsize=512)
+@memorycache(maxsize=512)
 def __continuousMomentOrCumulant(function, moment, symbols, generatingFunction):
 
     if type(moment) is tuple:
diff --git a/creationfunctions.py b/creationfunctions.py
index 6c0626b6..e4a2a7a5 100644
--- a/creationfunctions.py
+++ b/creationfunctions.py
@@ -54,7 +54,8 @@ def createLatticeBoltzmannFunction(ast=None, optimizationParams={}, **kwargs):
     params, optParams = _getParams(kwargs, optimizationParams)
 
     if ast is None:
-        ast = createLatticeBoltzmannAst(**params, optimizationParams=optParams)
+        params['optimizationParams'] = optParams
+        ast = createLatticeBoltzmannAst(**params)
 
     if params['target'] == 'cpu':
         from pystencils.cpu import makePythonFunction as makePythonCpuFunction, addOpenMP
@@ -79,7 +80,8 @@ def createLatticeBoltzmannAst(updateRule=None, optimizationParams={}, **kwargs):
     params, optParams = _getParams(kwargs, optimizationParams)
 
     if updateRule is None:
-        updateRule = createLatticeBoltzmannUpdateRule(**params, optimizationParams=optimizationParams)
+        params['optimizationParams'] = optimizationParams
+        updateRule = createLatticeBoltzmannUpdateRule(**params)
 
     if params['target'] == 'cpu':
         from pystencils.cpu import createKernel
@@ -168,12 +170,11 @@ def createLatticeBoltzmannMethod(**params):
         assert len(relaxationRates) >= 2, "Not enough relaxation rates"
         method = createTRT(stencil, relaxationRates[0], relaxationRates[1], **commonParams)
     elif methodName.lower() == 'mrt':
-        nextRelaxationRate = 0
+        nextRelaxationRate = [0]
 
         def relaxationRateGetter(momentGroup):
-            nonlocal nextRelaxationRate
-            res = relaxationRates[nextRelaxationRate]
-            nextRelaxationRate += 1
+            res = relaxationRates[nextRelaxationRate[0]]
+            nextRelaxationRate[0] += 1
             return res
         method = createOrthogonalMRT(stencil, relaxationRateGetter, **commonParams)
     else:
diff --git a/cumulants.py b/cumulants.py
index 1cec741e..fcafd342 100644
--- a/cumulants.py
+++ b/cumulants.py
@@ -3,12 +3,10 @@ This module provides functions to compute cumulants of discrete functions.
 Additionally functions are provided to compute cumulants from moments and vice versa
 """
 
-import functools
 import sympy as sp
-
 from lbmpy.moments import momentsUpToComponentOrder
 from lbmpy.continuous_distribution_measures import multiDifferentiation
-
+from lbmpy.cache import memorycache
 
 # ------------------------------------------- Internal Functions -------------------------------------------------------
 from pystencils.sympyextensions import fastSubs
@@ -60,7 +58,6 @@ def __cumulantRawMomentTransform(index, dependentVarDict, outerFunction, default
     subsDict = {}
 
     def createMomentSymbol(idx):
-        nonlocal subsDict
         idx = tuple(idx)
         resultSymbol = sp.Symbol(defaultPrefix + "_" + "_".join(["%d"] * len(idx)) % idx)
         if dependentVarDict is not None and idx in dependentVarDict:
@@ -103,7 +100,7 @@ def __cumulantRawMomentTransform(index, dependentVarDict, outerFunction, default
     return fastSubs(result, subsDict)
 
 
-@functools.lru_cache(maxsize=16)
+@memorycache(maxsize=16)
 def __getDiscreteCumulantGeneratingFunction(function, stencil, waveNumbers):
     assert len(stencil) == len(function)
 
@@ -117,7 +114,7 @@ def __getDiscreteCumulantGeneratingFunction(function, stencil, waveNumbers):
 # ------------------------------------------- Public Functions ---------------------------------------------------------
 
 
-@functools.lru_cache(maxsize=64)
+@memorycache(maxsize=64)
 def discreteCumulant(function, cumulant, stencil):
     """
     Computes cumulant of discrete function
@@ -146,7 +143,7 @@ def discreteCumulant(function, cumulant, stencil):
         return result
 
 
-@functools.lru_cache(maxsize=8)
+@memorycache(maxsize=8)
 def cumulantsFromPdfs(stencil, cumulantIndices=None, pdfSymbols=None):
     """
     Transformation of pdfs (or other discrete function on a stencil) to cumulant space
diff --git a/fieldaccess.py b/fieldaccess.py
index a444d577..6b04c389 100644
--- a/fieldaccess.py
+++ b/fieldaccess.py
@@ -6,7 +6,7 @@ import abc
 # ------------------------------------------------ Interface -----------------------------------------------------------
 
 
-class PdfFieldAccessor:
+class PdfFieldAccessor(object):
     """
     Defines how data is read and written in an LBM time step.
 
diff --git a/forcemodels.py b/forcemodels.py
index ca0c4203..8d0cf3d7 100644
--- a/forcemodels.py
+++ b/forcemodels.py
@@ -6,7 +6,7 @@
 import sympy as sp
 
 
-class Simple:
+class Simple(object):
     r"""
     A simple force model which introduces the following additional force term in the
     collision process :math:`3  w_i  e_i  f_i` (often: force = rho * acceleration)
@@ -26,7 +26,7 @@ class Simple:
                 for direction, w_i in zip(lbMethod.stencil, lbMethod.weights)]
 
 
-class Luo:
+class Luo(object):
     r"""
     Force model by Luo with the following forcing term
 
@@ -53,7 +53,7 @@ class Luo:
         return defaultVelocityShift(density, self._force)
 
 
-class Guo:
+class Guo(object):
     r"""
      Force model by Guo with the following term:
 
diff --git a/gridvisualization.py b/gridvisualization.py
index ffca51cd..5cf887db 100644
--- a/gridvisualization.py
+++ b/gridvisualization.py
@@ -1,7 +1,7 @@
 import matplotlib.patches as patches
 
 
-class Grid:
+class Grid(object):
     """Visualizes a 2D LBM grid with matplotlib by drawing cells and pdf arrows"""
 
     def __init__(self, xCells, yCells):
diff --git a/maxwellian_equilibrium.py b/maxwellian_equilibrium.py
index 3fc185cf..4b03b820 100644
--- a/maxwellian_equilibrium.py
+++ b/maxwellian_equilibrium.py
@@ -6,7 +6,7 @@ Additionally functions are provided to compute moments and cumulants of these di
 
 import sympy as sp
 from sympy import Rational as R
-from lbmpy.diskcache import diskcache
+from lbmpy.cache import diskcache
 import warnings
 
 
diff --git a/methods/abstractlbmethod.py b/methods/abstractlbmethod.py
index de64dac5..ed32c2c3 100644
--- a/methods/abstractlbmethod.py
+++ b/methods/abstractlbmethod.py
@@ -12,8 +12,7 @@ class LbmCollisionRule(EquationCollection):
         super(LbmCollisionRule, self).__init__(*args, **kwargs)
         self.method = lbmMethod
 
-
-class AbstractLbMethod(metaclass=abc.ABCMeta):
+class AbstractLbMethod(abc.ABCMeta('ABC', (object,), {})):
     """
     Abstract base class for all LBM methods
     """
diff --git a/methods/conservedquantitycomputation.py b/methods/conservedquantitycomputation.py
index 41f31460..de59c5dc 100644
--- a/methods/conservedquantitycomputation.py
+++ b/methods/conservedquantitycomputation.py
@@ -5,7 +5,7 @@ import sympy as sp
 from pystencils.equationcollection import EquationCollection
 
 
-class AbstractConservedQuantityComputation(metaclass=abc.ABCMeta):
+class AbstractConservedQuantityComputation(abc.ABCMeta('ABC', (object,), {})):
     """
 
     This class defines how conserved quantities are computed as functions of the pdfs.
diff --git a/methods/creationfunctions.py b/methods/creationfunctions.py
index 420d6f32..55eebd62 100644
--- a/methods/creationfunctions.py
+++ b/methods/creationfunctions.py
@@ -33,7 +33,7 @@ def createWithDiscreteMaxwellianEqMoments(stencil, momentToRelaxationRateDict, c
     :param forceModel: force model instance, or None if no external forces
     :param equilibriumAccuracyOrder: approximation order of macroscopic velocity :math:`\mathbf{u}` in the equilibrium
     :param cumulant: if True relax cumulants instead of moments
-    :return: :class:`lbmpy.methods.MomentBasedLbmMethod` instance
+    :return: :class:`lbmpy.methods.MomentBasedLbMethod` instance
     """
     momToRrDict = OrderedDict(momentToRelaxationRateDict)
     assert len(momToRrDict) == len(stencil), \
@@ -107,7 +107,7 @@ def createSRT(stencil, relaxationRate, useContinuousMaxwellianEquilibrium=False,
                            usually called :math:`\omega` in LBM literature
     :param useContinuousMaxwellianEquilibrium: determines if the discrete or continuous maxwellian equilibrium is
                            used to compute the equilibrium moments
-    :return: :class:`lbmpy.methods.MomentBasedLbmMethod` instance
+    :return: :class:`lbmpy.methods.MomentBasedLbMethod` instance
     """
     moments = getDefaultMomentSetForStencil(stencil)
     rrDict = OrderedDict([(m, relaxationRate) for m in moments])
@@ -371,10 +371,9 @@ def sortMomentsIntoGroupsOfSameOrder(moments):
 
 
 def defaultRelaxationRateNames():
-    nextIndex = 0
+    nextIndex = [0]
 
     def result(momentList):
-        nonlocal nextIndex
         shearMomentInside = False
         allConservedMoments = True
         for m in momentList:
@@ -388,8 +387,8 @@ def defaultRelaxationRateNames():
         elif allConservedMoments:
             return 0
         else:
-            nextIndex += 1
-            return sp.Symbol("omega_%d" % (nextIndex,))
+            nextIndex[0] += 1
+            return sp.Symbol("omega_%d" % (nextIndex[0],))
 
     return result
 
diff --git a/methods/entropic.py b/methods/entropic.py
index 490d0194..4adc69cd 100644
--- a/methods/entropic.py
+++ b/methods/entropic.py
@@ -86,7 +86,7 @@ def decompositionByRelaxationRate(updateRule, relaxationRate):
     return affineTerms, linearTerms, quadraticTerms
 
 
-class RelaxationRatePolynomialDecomposition:
+class RelaxationRatePolynomialDecomposition(object):
 
     def __init__(self, collisionRule, freeRelaxationRates, fixedRelaxationRates):
         self._collisionRule = collisionRule
diff --git a/methods/momentbasedsimplifications.py b/methods/momentbasedsimplifications.py
index 6d653d31..4f46fafe 100644
--- a/methods/momentbasedsimplifications.py
+++ b/methods/momentbasedsimplifications.py
@@ -1,7 +1,7 @@
 """
 This module holds special transformations for simplifying the collision equations of moment-based methods.
 All of these transformations operate on :class:`pystencils.equationcollection.EquationCollection` and need special
-simplification hints, which are set by the MomentBasedLbmMethod.
+simplification hints, which are set by the MomentBasedLbMethod.
 """
 import sympy as sp
 from pystencils.sympyextensions import replaceAdditive, replaceSecondOrderProducts, extractMostCommonFactor
diff --git a/moments.py b/moments.py
index 3a7c5579..6c7ce3c9 100644
--- a/moments.py
+++ b/moments.py
@@ -37,12 +37,12 @@ Functions
 import sympy as sp
 import math
 import itertools
-import functools
 from copy import copy
 from collections import Counter
 
 from lbmpy.continuous_distribution_measures import continuousMoment
-from lbmpy_old.transformations import removeHigherOrderTerms
+from lbmpy.cache import memorycache
+from pystencils.sympyextensions import removeHigherOrderTerms
 
 MOMENT_SYMBOLS = sp.symbols("x y z")
 
@@ -84,7 +84,8 @@ def __generateFixedSumTuples(tupleLength, tupleSum, allowedValues=None, ordered=
                 newSum = totalSum - i
                 if newSum < 0:
                     continue
-                yield from recursive_helper(currentList, newPosition, newSum)
+                for item in recursive_helper(currentList, newPosition, newSum):
+                    yield item
         else:
             if totalSum in allowedValues:
                 currentList[-1] = totalSum
@@ -132,7 +133,10 @@ def momentPermutations(exponentTuple):
 
 def momentsOfOrder(order, dim=3, includePermutations=True):
     """All tuples of length 'dim' which sum equals 'order'"""
-    yield from __generateFixedSumTuples(dim, order, ordered=not includePermutations)
+    for item in __generateFixedSumTuples(dim, order, ordered=not includePermutations):
+        assert(len(item) == dim)
+        assert(sum(item) == order)
+        yield item
 
 
 def momentsUpToOrder(order, dim=3, includePermutations=True):
@@ -264,7 +268,7 @@ def isShearMoment(moment):
 isShearMoment.shearMoments = set([c[0] * c[1] for c in itertools.combinations(MOMENT_SYMBOLS, 2)])
 
 
-@functools.lru_cache(maxsize=512)
+@memorycache(maxsize=512)
 def discreteMoment(function, moment, stencil):
     """
     Computes discrete moment of given distribution function
diff --git a/serialscenario.py b/serialscenario.py
index 4224507c..a897c366 100644
--- a/serialscenario.py
+++ b/serialscenario.py
@@ -18,19 +18,20 @@ def runScenario(domainSize, boundarySetupFunction, methodParameters, optimizatio
         methodParameters['stencil'] = 'D2Q9' if D == 2 else 'D3Q27'
 
     Q = len(getStencil(methodParameters['stencil']))
-    pdfSrc = np.zeros(domainSizeWithGhostLayer + (Q,))
-    pdfDst = np.zeros_like(pdfSrc)
+    pdfArrays = [np.zeros(domainSizeWithGhostLayer + (Q,)),
+                 np.zeros(domainSizeWithGhostLayer + (Q,))]
 
     # Create kernel
     if lbmKernel is None:
-        lbmKernel = createLatticeBoltzmannFunction(**methodParameters, optimizationParams=optimizationParameters)
+        methodParameters['optimizationParams'] = optimizationParameters
+        lbmKernel = createLatticeBoltzmannFunction(**methodParameters)
     method = lbmKernel.method
 
     assert D == method.dim, "Domain size and stencil do not match"
 
     # Boundary setup
     if boundarySetupFunction is not None:
-        symPdfField = Field.createFromNumpyArray('pdfs', pdfSrc, indexDimensions=1)
+        symPdfField = Field.createFromNumpyArray('pdfs', pdfArrays[0], indexDimensions=1)
         boundaryHandling = BoundaryHandling(symPdfField, domainSize, lbmKernel.method)
         boundarySetupFunction(boundaryHandling=boundaryHandling, method=method)
         boundaryHandling.prepare()
@@ -38,25 +39,24 @@ def runScenario(domainSize, boundarySetupFunction, methodParameters, optimizatio
         boundaryHandling = None
 
     # Macroscopic value input/output
-    densityArr = np.zeros(domainSizeWithGhostLayer)
-    velocityArr = np.zeros(domainSizeWithGhostLayer + (D,))
-    getMacroscopic = compileMacroscopicValuesGetter(method, ['density', 'velocity'], pdfArr=pdfSrc)
-    setMacroscopic = compileMacroscopicValuesSetter(method, {'density': 1.0, 'velocity': [0] * D}, pdfArr=pdfSrc)
-    setMacroscopic(pdfs=pdfSrc)
+    densityArr = [np.zeros(domainSizeWithGhostLayer)]
+    velocityArr = [np.zeros(domainSizeWithGhostLayer + (D,))]
+    getMacroscopic = compileMacroscopicValuesGetter(method, ['density', 'velocity'], pdfArr=pdfArrays[0])
+    setMacroscopic = compileMacroscopicValuesSetter(method, {'density': 1.0, 'velocity': [0] * D}, pdfArr=pdfArrays[0])
+    setMacroscopic(pdfs=pdfArrays[0])
 
     # Run simulation
     def timeLoop(timeSteps):
-        nonlocal pdfSrc, pdfDst, densityArr, velocityArr
         for t in range(timeSteps):
             for f in preUpdateFunctions:
-                f(pdfSrc)
+                f(pdfArrays[0])
             if boundaryHandling is not None:
-                boundaryHandling(pdfs=pdfSrc)
-            lbmKernel(src=pdfSrc, dst=pdfDst)
+                boundaryHandling(pdfs=pdfArrays[0])
+            lbmKernel(src=pdfArrays[0], dst=pdfArrays[1])
 
-            pdfSrc, pdfDst = pdfDst, pdfSrc
-        getMacroscopic(pdfs=pdfSrc, density=densityArr, velocity=velocityArr)
-        return pdfSrc, densityArr, velocityArr
+            pdfArrays[0], pdfArrays[1] = pdfArrays[1], pdfArrays[0]
+        getMacroscopic(pdfs=pdfArrays[0], density=densityArr[0], velocity=velocityArr[0])
+        return pdfArrays[0], densityArr[0], velocityArr[0]
 
     timeLoop.kernel = lbmKernel
 
@@ -163,13 +163,3 @@ def runForceDrivenChannel(dim, force, domainSize=None, radius=None, length=None,
     return runScenario(domainSize, boundarySetupFunction, kwargs, optimizationParameters, lbmKernel=lbmKernel,
                        preUpdateFunctions=[periodicity])
 
-if __name__ == '__main__':
-    import sympy as sp
-    from pystencils.display_utils import highlightCpp
-    from pystencils.cpu.cpujit import generateC
-    from lbmpy.serialscenario import runPressureGradientDrivenChannel
-    import lbmpy.plot2d as plt
-    timeloop = runPressureGradientDrivenChannel(radius=10, length=30, pressureDifference=0.001,
-                                                relaxationRates=[1.9],
-                                                dim=2)
-    pdfs, rho, vel = timeloop(20)
-- 
GitLab