From 1727b0775f40b3c796420aa5785d29bca903ff64 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Keck <Jean-Baptiste.Keck@imag.fr>
Date: Wed, 10 Apr 2019 22:17:53 +0200
Subject: [PATCH] more interpolation mode support, working opencl and python
 interpolation

---
 examples/multiresolution/scalar_advection.py  |   6 +-
 .../opencl/operator/spatial_filtering.py      |  12 +-
 .../host/python/operator/spatial_filtering.py |  20 +-
 hysop/numerics/interpolation/polynomial.py    | 437 +++++++++++-------
 hysop/operator/base/spatial_filtering.py      |  23 +-
 hysop/tools/warning.py                        |   6 +
 6 files changed, 314 insertions(+), 190 deletions(-)

diff --git a/examples/multiresolution/scalar_advection.py b/examples/multiresolution/scalar_advection.py
index fa9d3a586..9acfdebc2 100644
--- a/examples/multiresolution/scalar_advection.py
+++ b/examples/multiresolution/scalar_advection.py
@@ -26,9 +26,9 @@ def compute(args):
 
     # Define domain
     dim   = args.ndim
-    npts  = args.npts
-    snpts = args.snpts
-    fnpts = tuple(2*_ for _ in snpts)
+    npts  = args.npts                  # coarse resolution
+    snpts = args.snpts                 # fine resolution
+    fnpts = tuple(3*_ for _ in snpts)  # finest resolution
     lboundaries = (BoxBoundaryCondition.PERIODIC,)*dim
     rboundaries = (BoxBoundaryCondition.PERIODIC,)*dim
     box  = Box(origin=args.box_origin, length=args.box_length, dim=dim,
diff --git a/hysop/backend/device/opencl/operator/spatial_filtering.py b/hysop/backend/device/opencl/operator/spatial_filtering.py
index c01c11a2b..d6f168e6e 100644
--- a/hysop/backend/device/opencl/operator/spatial_filtering.py
+++ b/hysop/backend/device/opencl/operator/spatial_filtering.py
@@ -32,17 +32,17 @@ class OpenClPolynomialInterpolationFilter(PolynomialInterpolationFilterBase, Ope
         dim = dFin.dim
         assert dFin.is_scalar
         assert dFout.is_scalar
+        assert self.subgrid_interpolator.gr == gr
         
         ekg = self.elementwise_kernel_generator
-        W   = self.subgrid_interpolator.W
-        s   = self.subgrid_interpolator.s
-        n   = (self.subgrid_interpolator.n,)*dim
-        ghosts = np.asarray((self.subgrid_interpolator.ghosts,)*dim)
+        Wr  = self.subgrid_interpolator.Wr
+        n   = self.subgrid_interpolator.n
+        ghosts = np.asarray(self.subgrid_interpolator.ghosts)
         
         I = np.asarray(local_indices_symbols[:dim][::-1])
         fin, fout = ekg.dfields_to_ndbuffers(dFin, dFout)
         Fin  = ekg.symbolic_tmp_scalars('F', shape=n, dtype=dFin.dtype)
-        Fout_values = W.dot(Fin.ravel()).reshape(s)
+        Fout_values = Wr.dot(Fin.ravel()).reshape(gr)
 
         exprs = ()
         for idx in np.ndindex(*n):
@@ -53,7 +53,7 @@ class OpenClPolynomialInterpolationFilter(PolynomialInterpolationFilterBase, Ope
             exprs += (e,)
         
         interpolate_grid_kernel, _ = ekg.elementwise_kernel('interpolate_grid', 
-                *exprs, compute_resolution=dFin.compute_resolution, debug=False)
+                *exprs, compute_resolution=self.iter_shape, debug=False)
 
         exchange_ghosts = self.dFout.exchange_ghosts(build_launcher=True)
         
diff --git a/hysop/backend/host/python/operator/spatial_filtering.py b/hysop/backend/host/python/operator/spatial_filtering.py
index 24a2fd065..a82688827 100644
--- a/hysop/backend/host/python/operator/spatial_filtering.py
+++ b/hysop/backend/host/python/operator/spatial_filtering.py
@@ -16,8 +16,7 @@ class PythonPolynomialInterpolationFilter(PolynomialInterpolationFilterBase, Hos
         if self.discretized:
             return
         super(PythonPolynomialInterpolationFilter, self).discretize(**kwds)
-        self.W = self.subgrid_interpolator.W.astype(self.dtype)
-        self.iter_shape = self.dFin.compute_resolution
+        self.Wr = self.subgrid_interpolator.Wr.astype(self.dtype)
 
     @op_apply
     def apply(self, **kwds):
@@ -25,16 +24,17 @@ class PythonPolynomialInterpolationFilter(PolynomialInterpolationFilterBase, Hos
         super(PythonPolynomialInterpolationFilter, self).apply(**kwds)
         fin    = self.fin
         fout   = self.fout
-        shape  = self.iter_shape
+        ishape  = self.dFin.compute_resolution
+        oshape = self.dFout.compute_resolution
+        periodicity = self.dFin.periodicity
         gr     = self.grid_ratio
-        s, n   = self.subgrid_interpolator.s, self.subgrid_interpolator.n
-        oshape = fout.shape
+        gr, n  = self.subgrid_interpolator.gr, self.subgrid_interpolator.n
+        Wr     = self.Wr
         
-        for idx in np.ndindex(*shape):
-            oslc = tuple(slice(j*gr[i], (j+1)*gr[i]+1, 1) for i,j in enumerate(idx)) 
-            islc = tuple(slice(1+j, 1+j+n, 1) for i,j in enumerate(idx))
-            rslc = tuple(slice(0, min(s[i], oshape[i]-j*gr[i]), 1) for i,j in enumerate(idx))
-            fout[oslc] = self.W.dot(fin[islc].ravel()).reshape(s)[rslc]
+        for idx in np.ndindex(*self.iter_shape):
+            oslc = tuple(slice(j*gr[i], (j+1)*gr[i], 1) for i,j in enumerate(idx)) 
+            islc = tuple(slice(periodicity[i]+j, periodicity[i]+j+n[i], 1) for i,j in enumerate(idx))
+            fout[oslc] = Wr.dot(fin[islc].ravel()).reshape(gr)
         self.dFout.exchange_ghosts()
 
 
diff --git a/hysop/numerics/interpolation/polynomial.py b/hysop/numerics/interpolation/polynomial.py
index 8aa98499a..2e49a40aa 100644
--- a/hysop/numerics/interpolation/polynomial.py
+++ b/hysop/numerics/interpolation/polynomial.py
@@ -2,6 +2,7 @@ import itertools as it
 import numpy as np
 import scipy as sp
 import sympy as sm
+import warnings
 
 try:
     import flint
@@ -16,11 +17,12 @@ except ImportError:
     has_flint = False
 
 from hysop.tools.enum import EnumFactory
-from hysop.tools.types import check_instance, InstanceOf
+from hysop.tools.types import check_instance, InstanceOf, to_tuple
 from hysop.tools.cache import update_cache, load_data_from_cache
 from hysop.tools.io_utils import IO
 from hysop.tools.sympy_utils import tensor_xreplace, tensor_symbol
 from hysop.tools.decorators import debug
+from hysop.tools.warning import HysopCacheWarning
 from hysop.numerics.stencil.stencil_generator import CenteredStencilGenerator, MPQ
 from hysop.numerics.interpolation.interpolation import MultiScaleInterpolation
 
@@ -96,7 +98,7 @@ class PolynomialInterpolationMethod(SpaceDiscretizationMethod):
     Operator helper to handle polynomial interpolation method.
     """
     __default_method = {
-        MultiScaleInterpolation: PolynomialInterpolation.NONIC_FDC2,
+        MultiScaleInterpolation: PolynomialInterpolation.CUBIC_FDC2,
     }
 
     __available_methods = {
@@ -168,11 +170,11 @@ class PolynomialInterpolator(object):
         ----------
         dim: int
             Number of dimensions to interpolate.
-        deg: int
+        deg: int or tuple of ints
             Polynomial degree (1=linear, 3=cubic, 5=quintic, 7=septic, ...)
-            Degree should be odd.
-        fd: int
-            Order of centered finite differences used to compute derivatives.
+            Degree should be odd on each axis.
+        fd: int or tuple of ints
+            Order of centered finite differences used to compute derivatives in each direction.
             Will affect the number of ghosts of the method.
             Should be even because this interpolator only use centered dinite differences.
         approximative: bool
@@ -184,20 +186,20 @@ class PolynomialInterpolator(object):
         ----------
         dim: int
             Number of dimensions to interpolate.
-        deg: int
+        deg: tuple of ints
             Polynomial degree (1=linear, 3=cubic, 5=quintic, 7=septic, ...)
             Degree should be odd: deg=2k+1 
-        fd: int
-            Order of centered finite differences stencils used to compute derivatives.
-        p: int
+        fd: tuple of ints
+            Order of centered finite differences stencils used to compute derivatives for each direction.
+        p: tuple of ints
             Corresponds to deg+1.
-            The total number of polynomial coefficients corresponds to P=p**dim.
+            The total number of polynomial coefficients corresponds to P=p0*p1*...*p(dim-1).
         P: int
-            The total number of polynomial coefficients P=p**dim.
-        k: int
-            Max derivative order required to compute the polynomial interpolator coefficients.
+            The total number of polynomial coefficients P=p0*p1*...*p(dim-1)
+        k: tuple of ints
+            Max derivative order required to compute the polynomial interpolator coefficients in each direction.
             Also the regularity of the resulting interpolant. Corresponds to (deg-1)/2.
-        ghosts: int
+        ghosts: tuple of ints
             Return the number of ghosts required by the interpolator on each axis.
             Corresponds to (k>0)*[fd//2 - 1 + (k+1)//2]
                       deg    k   (k+1)/2  | FDC2  FDC4  FDC6
@@ -206,29 +208,29 @@ class PolynomialInterpolator(object):
             quintic:   5     2      1     |  1     2     3
             septic:    7     3      2     |  2     3     4
             nonic:     9     4      2     |  2     3     4
-        n: int
+        n: tuple of ints
             Corresponds to 2*(ghosts+1), the number of required nodes to generate the
             polynomial coefficients (in each direction). 
-            In total we have N=n**dim input nodes.
+            In total we have N=n0*n1*...*n(dim-1) input nodes.
 
-            G     G
+            G1    G1
            <->   <->
             X X X X
             X P P X
             X P P X
             X X X X
             <----->
-               n
+               n1
         N: int
-            Total number of input nodes N=n**dim.
+            Total number of input nodes N=n0*n1*...*n(dim-1).
         M: np.ndarray
             Grid values to polynomial coefficient matrix:
                 M.dot(F.ravel()) will give C.ravel(), coefficients of P(x0,x1,...)
-                 N = n**dim 
+                     N 
                 <--------->
                 X X X X X X ^                         |f0| ^                   |c0| ^
                 X X X X X X |                         |f1| |                   |c1| |
-            M = X X X X X X | P = p**dim          F = |f2| | N       C = M*F = |c2| | P
+            M = X X X X X X | P                   F = |f2| | N       C = M*F = |c2| | P
                 X X X X X X |                         |f3| |                   |c3| |
                 X X X X X X v                         |f4| |                   |c4| v
                                                       |f5| v
@@ -242,23 +244,44 @@ class PolynomialInterpolator(object):
         """
         
         assert dim>0, 'dim<=0'
-        assert deg%2==1, 'deg % 2 != 1'
-        assert fd%2==0, 'fd % 2 != 0'
 
-        dim = dim
-        k = (deg-1)/2
-
-        if (k>0):
-            required_ghosts = (k+1)/2
-            ghosts = (fd/2) - 1 + required_ghosts
-        else:
-            ghosts = 0
-
-        p = deg+1
-        n = 2*(ghosts+1)
+        deg = to_tuple(deg)
+        if len(deg)==1:
+            deg*=dim
+        check_instance(deg, tuple, values=int, size=dim)
         
-        P = p**dim
-        N = n**dim
+        fd = to_tuple(fd)
+        if len(fd)==1:
+            fd*=dim
+        check_instance(fd, tuple, values=int, size=dim)
+
+        p = tuple(degi+1 for degi in deg)
+        k = tuple((degi-1)/2 for degi in deg)
+
+        ghosts = ()
+        n = ()
+        for (fdi,ki) in zip(fd,k):
+            if (ki>0):
+                gi = (fdi/2) - 1 + (ki+1)/2
+            else:
+                gi = 0
+            ni = 2*(gi+1)
+            ghosts += (gi,)
+            n += (ni,)
+        
+        check_instance(deg,   tuple, values=int, size=dim)
+        check_instance(fd,    tuple, values=int, size=dim)
+        check_instance(p,     tuple, values=int, size=dim)
+        check_instance(k,     tuple, values=int, size=dim)
+        check_instance(n,     tuple, values=int, size=dim)
+        check_instance(ghosts,tuple, values=int, size=dim)
+        assert all(fdi%2==0 for fdi in fd), 'fd % 2 != 0'
+        assert all(degi%2==1 for degi in deg), 'deg % 2 != 1'
+        assert all(pi%2==0 for pi in p), 'p % 2 != 0'
+        assert all(ni%2==0 for ni in n), 'n % 2 != 0'
+        
+        P = np.prod(p, dtype=np.int32)
+        N = np.prod(n, dtype=np.int32)
 
         self.dim             = dim
         self.deg             = deg
@@ -278,7 +301,7 @@ class PolynomialInterpolator(object):
         dim     = self.dim
         ghosts  = self.ghosts
         verbose = self.verbose
-        order   = self.fd
+        fd      = self.fd
         approximative = self.approximative
 
         k = self.k
@@ -289,76 +312,89 @@ class PolynomialInterpolator(object):
             print '\nCollecting 1D stencils:'
         
         SG = CenteredStencilGenerator()
-        SG.configure(dim=1, dtype=np.float64, order=order)
-        S = []
-        for i in xrange(k+1):
-            msg='Failed to compute stencil derivative={}, order={}, origin={}'
-            msg=msg.format(i, order, ghosts)
-            try:
-                if approximative:
-                    Si = SG.generate_approximative_stencil(derivative=i)
-                else:
-                    Si = SG.generate_exact_stencil(derivative=i)
-                Si.replace_symbols({Si.dx:1})
-            except:
-                print msg
-                raise
-            msg+=' got {}.'.format(Si.coeffs)
-            assert (not Si.is_symbolic()), msg
-            Si = Si.reshape((self.n-1,))
-            assert Si.origin == ghosts
-            Si = Si.coeffs
-            S.append(Si)
+        SG.configure(dim=1, dtype=np.float64)
+        S = {}
+        for direction in xrange(dim):
             if verbose:
-                print '  {}-th derivative: {}'.format(i,Si)
-        self.S = S
+                print ' Direction {}'.format(direction)
+            Sd  = S.setdefault(direction, [])
+            nd  = n[direction]
+            kd  = k[direction]
+            fdd = fd[direction]
+            gd  = ghosts[direction]
+            for i in xrange(kd+1):
+                msg='Failed to compute stencil derivative={}, order={}, origin={}'
+                msg=msg.format(i, fdd, gd)
+                try:
+                    if approximative:
+                        Si = SG.generate_approximative_stencil(order=fdd, derivative=i)
+                    else:
+                        Si = SG.generate_exact_stencil(order=fdd, derivative=i)
+                    Si.replace_symbols({Si.dx:1})
+                except:
+                    print msg
+                    raise
+                msg+=' got {}.'.format(Si.coeffs)
+                assert (not Si.is_symbolic()), msg
+                Si = Si.reshape((nd-1,))
+                assert Si.origin == gd
+                Si = Si.coeffs
+                Sd.append(Si)
+                if verbose:
+                    print '  {}-th derivative: {}'.format(i,Si)
+        return S
 
     def _build_stencil(self, dvec):
         dvec = np.asarray(dvec)    
+        k = self.k
+        S = self.S
         assert dvec.size == self.dim, 'dvec.size != dim'
-        assert (dvec>=0).all(), 'dvec < 0    => {}'.format(dvec)
-        assert (dvec<=self.k).all(), 'dvec > dmax => {}'.format(dvec)
+        assert all(dvec>=0), 'dvec < 0  => {}'.format(dvec)
+        assert all(di<=ki for (di,ki) in zip(dvec,k)), 'dvec > dmax => {}'.format(dvec)
 
-        Sd = self.S[dvec[0]].copy()
-        for di in dvec[1:]:
-            Sdi = self.S[di]
-            Sd = np.tensordot(Sd, Sdi, axes=0)
+        Sd = S[0][dvec[0]].copy()
+        for (d,i) in enumerate(dvec[1:],1):
+            Sd = np.tensordot(Sd, S[d][i], axes=0)
         return Sd
 
     def _build_interpolator(self):
-        dim    = self.dim
-        deg    = self.deg
-        maxd   = self.k
+        dim = self.dim
+        deg = self.deg
+        k   = self.k
+        n   = self.n
+        p   = self.p
         ghosts = self.ghosts
         approximative = self.approximative
         verbose = self.verbose
 
-        forigin = (ghosts,)*dim
-        fshape  = (2*(ghosts+1),)*dim
-        pshape  = (deg+1,)*dim
-        
-        xvals,  xvars  = tensor_symbol('x',shape=(dim,))
-        fvals,  fvars  = tensor_symbol('F',fshape,forigin)
-        pvals,  pvars  = tensor_symbol('C',pshape)
+        xvals,  xvars  = tensor_symbol('x', shape=(dim,))
+        fvals,  fvars  = tensor_symbol('F', n, ghosts)
+        pvals,  pvars  = tensor_symbol('C', p)
 
         self.xvals,  self.xvars  = xvals, xvars
         self.fvals,  self.fvars  = fvals, fvars
         self.pvals,  self.pvars  = pvals, pvars
         
+        try:
+            data = load_data_from_cache(self.cache_file(), self.key)
+            if (data is not None):
+                (P0, S, M) = data
+                if _check_matrix(M):
+                    self.P0 = P0
+                    self.S  = S
+                    self.M  = M
+                    return
+        except Exception as e:
+            msg='Failed to load data from cache because:\n{}'.format(e)
+            warnings.warn(msg, HysopCacheWarning)
+        
         P0 = 0
-        for idx in it.product(range(0,deg+1), repeat=dim):
-            val = pvals[idx]
-            for i in xrange(dim):
-                val*= pow(xvals[i],idx[i])
-            P0 += val
+        for idx in it.product(*tuple(range(0,pi) for pi in p)):
+            P0 += pvals[idx] * np.prod(np.power(xvals, idx))
         self.P0 = P0
         
-        self._collect_stencils()
-        
-        M = load_data_from_cache(self.cache_file(), self.key)
-        if (M is not None) and _check_matrix(M):
-            self.M = M
-            return
+        S = self._collect_stencils()
+        self.S = S
         
         if verbose:
             print '\nGenerating variables:'
@@ -373,7 +409,7 @@ class PolynomialInterpolator(object):
             print '\nBuilding system...'
         
         eqs = []
-        for dvec in it.product( range(0,maxd+1), repeat=dim ):
+        for dvec in it.product(*tuple(range(0,ki+1) for ki in k)):
             if verbose:
                 print '  => derivative {}'.format(dvec)
             
@@ -386,17 +422,17 @@ class PolynomialInterpolator(object):
                 print '     stencil:'
                 print stencil
 
-            for idx in it.product( range(ghosts,ghosts+2), repeat=dim):
+            for idx in it.product(*tuple(range(gi,gi+2) for gi in ghosts)):
                 if verbose:
                     print '    -> point {}'.format(idx)
                 
                 pos = np.asarray(idx)-ghosts
                 pos = dict(zip(xvals, pos))
                 eq = dP0.xreplace(pos)
-
-                for offset in it.product( range(-ghosts, ghosts+1), repeat=dim):
-                    fidx = tuple(np.asarray(idx)+np.asarray(offset))
-                    sidx = tuple(np.asarray(offset)+ghosts)
+                
+                for offset in it.product(*tuple(range(-gi, gi+1) for gi in ghosts)):
+                    fidx = tuple(np.add(idx, offset))
+                    sidx = tuple(np.add(offset, ghosts))
                     eq -= fvals[fidx]*stencil[sidx]
                 
                 eqs.append(eq)
@@ -434,7 +470,7 @@ class PolynomialInterpolator(object):
             print '\nBuilding matrix...'
         M = Ainv.dot(B)
         self.M = M
-        update_cache(self.cache_file(), self.key, M)
+        update_cache(self.cache_file(), self.key, (P0,S,M))
         
 
     def interpolate(self, fvals):
@@ -458,7 +494,7 @@ class PolynomialSubgridInterpolator(object):
         ----------
         interpolator: PolynomialInterpolator
             Interpolant used to compute weights.
-        grid_ratio: tuple of integers
+        grid_ratio: tuple of int
             Tuple of integers representing the ratio between the coarse and the fine grid.
         dtype: np.dtype
             Force to cast dtype for all matrices (interpolator.M may contain rationals).
@@ -467,14 +503,15 @@ class PolynomialSubgridInterpolator(object):
         ----------
         dim: int
             Number of dimensions to interpolate (same as interpolator.dim).
-        ghosts: int
+        ghosts: tuple of int
             Number of required ghosts.
-        n: int
+        n: tuple of int
             Corresponds to 2*(ghosts+1), the number of required nodes to generate the
             polynomial coefficients (same as interpolator.n). 
         N: int
             Total number of input nodes N including ghosts (same as interpolator.N).
-        s: int
+            N = n0*n1*...*n[dim-1]
+        s: tuple of int
             Corresponds to grid_ratio + 1, number of points of the subgrid in each directions.
             Example for a grid ratio=(3,3), we have s=(4,4): O=coarse grid nodes, X=fine grid nodes
             
@@ -489,10 +526,48 @@ class PolynomialSubgridInterpolator(object):
         S: int
             Represents the number of fine grid points contained in a coarse grid cell.
             S = s0*s1*...*s[dim-1]
+        gr: tuple of int
+            Corresponds to grid_ratio, number of points of the subgrid in each directions, minus one.
+            Example for a grid ratio=(3,3), we have gr=(3,3) and s=(4,4): 
+               O=coarse grid nodes, X=fine grid nodes, -=excluded find grid nodes
+            
+            Coarse grid:               Fine grid:
+
+           ^  O-----O                  ^  O X X O  ^
+           |  |     |              gr0 |  X X X -  | s0
+         1 |  |     |                  v  X X X -  |
+           v  O-----O                     O - - O  v
+              <----->                     <--->
+                 1                         gr1
+        GR: int
+            Represents the number of fine grid points contained in a coarse grid cell exluding right most points.
+            GR = gr0*gr1*...*gr[dim-1]
         W: np.ndarray
-            Pre computed weights to interpolate directly from coarse to fine grid.
+                Pre computed weights to interpolate directly from coarse to fine grid.
+                Let F be the vector of N known coarse grid node values (including required ghosts).
+                Let G be the vector of S unknown fine grid node values.
+                
+                         N                 
+                    <--------->
+                    X X X X X X ^                                         |g0| ^
+                    X X X X X X |                |f0| ^                   |g1| |
+                    X X X X X X |                |f1| |                   |g2| |
+                    X X X X X X |                |f2| |                   |g3| |
+                W = X X X X X X | S           F= |f3| | N       G = W*F = |g4| | S
+                    X X X X X X |                |f4| |                   |g5| |
+                    X X X X X X |                |f5| v                   |g6| |
+                    X X X X X X |                                         |g7| |
+                    X X X X X X v                                         |g8| v
+
+                Will contain the same data type as intepolator.M if dtype is not passed,
+                else W will be computed from user given dtype.
+
+        Wr: np.ndarray
+            Reduced W that exludes rightmost output points of ndimensional output vector.
+                
+            Pre computed weights to interpolate directly from coarse to inner fine grid.
             Let F be the vector of N known coarse grid node values (including required ghosts).
-            Let G be the vector of S unknown fine grid node values.
+            Let G be the vector of GR unknown fine inner grid node values (see gr attribute).
             
                      N                 
                 <--------->
@@ -500,33 +575,33 @@ class PolynomialSubgridInterpolator(object):
                 X X X X X X |                |f0| ^                   |g1| |
                 X X X X X X |                |f1| |                   |g2| |
                 X X X X X X |                |f2| |                   |g3| |
-            W = X X X X X X | S           F= |f3| | N       G = W*F = |g3| | S
-                X X X X X X |                |f4| |                   |g3| |
-                X X X X X X |                |f5| v                   |g3| |
-                X X X X X X |                                         |g3| |
-                X X X X X X v                                         |g4| v
-
-            Will contain the same data type as intepolator.M if dtype is not passed,
-            else W will be computed from user given dtype.
-
-        U: tuple
-            Contains unique values contained in W.
-        I: np.ndarray of np.int8 or np.int16
-            Represents W as integers indexed into U.
+           Wr = X X X X X X | GR          F= |f3| | N       G = W*F = |g4| | GR
+                X X X X X X |                |f4| |                   |g5| |
+                X X X X X X |                |f5| v                   |g6| |
+                X X X X X X |                                         |g7| |
+                X X X X X X v                                         |g8| v
+
+            Same data type as W.
         """
         check_instance(interpolator, PolynomialInterpolator)
         check_instance(grid_ratio, tuple, size=interpolator.dim, minval=1)
 
         p = interpolator.p
-        P = interpolator.P
         n = interpolator.n
+        P = interpolator.P
         N = interpolator.N
+        M = interpolator.M
         ghosts = interpolator.ghosts
+
+        gr = grid_ratio
+        GR = np.prod(gr, dtype=np.int32)
+        del grid_ratio
         
-        s = tuple(gr+1 for gr in grid_ratio)
-        S = np.prod(s, dtype=np.int64)
+        s = tuple(gri+1 for gri in gr)
+        S = np.prod(s, dtype=np.int32)
+
         dim = interpolator.dim
-        key = ('PolynomialSubgridInterpolator', interpolator.key, grid_ratio, str(dtype))
+        key = ('PolynomialSubgridInterpolator', interpolator.key, gr, str(dtype))
         
         self.p = p
         self.P = p
@@ -534,60 +609,48 @@ class PolynomialSubgridInterpolator(object):
         self.S = S
         self.n = n
         self.N = N
+        self.gr = gr
+        self.GR = GR
         self.ghosts = interpolator.ghosts
         self.dim = dim
         self.interpolator = interpolator
         self.key = key
         
         cache_file = interpolator.cache_file()
-        data = load_data_from_cache(cache_file, key)
-        if (data is not None) and _check_matrices(*data):
-            (W,U,I) = data
-        else:
-            X = tuple(np.asarray(tuple(sm.Rational(j,gr) for j in xrange(0,si))) for i,(gr,si) in enumerate(zip(grid_ratio, s)))
-            M = interpolator.M
-            V = np.vander(X[0], N=p, increasing=True)
-            for i in xrange(1, dim):
-                Vi = np.vander(X[i], N=p, increasing=True)
-                V = np.multiply.outer(V,Vi)
-            
-            even_axes = range(0,V.ndim,2)
-            odd_axes  = range(1,V.ndim,2)
-            axes = even_axes + odd_axes
-
-            V = np.transpose(V, axes=axes).copy()
-            assert V.shape[:dim] == s
-            assert V.shape[dim:] == (p,)*dim
-            V = V.reshape((S,P))
-            W = V.dot(M)
-            assert W.shape == (S,N)
-
-            if interpolator.approximative:
-                U = None
-                I = None
-            else:
-                U = list(sorted(set(W.ravel().tolist())))
-                U.remove(0)
-                U.remove(1)
-                U = (0,1,)+tuple(U)
-
-                if len(U)<np.iinfo(np.uint8).max:
-                    itype = np.uint8
-                elif len(U)<np.iinfo(np.uint16).max:
-                    itype = np.uint16
-                else:
-                    raise NotImplementedError(len(U))
-
-                I = np.empty(shape=W.shape, dtype=itype)
-                for idx in np.ndindex(*W.shape):
-                    I[idx] = U.index(W[idx])
-
-            if (dtype is not None):
-                W = W.astype(dtype)
-            update_cache(cache_file, key, (W,U,I))
+        try:
+            data = load_data_from_cache(cache_file, key)
+            if (data is not None):
+                W = data
+                if _check_matrix(W):
+                    self.W = data
+                    return
+        except Exception as e:
+            msg='Failed to load data from cache because:\n{}'.format(e)
+            warnings.warn(msg, HysopCacheWarning)
+
+        X = tuple(np.asarray(tuple(sm.Rational(j,gr) for j in xrange(0,si))) 
+                for i,(gr,si) in enumerate(zip(gr, s)))
+        V = np.vander(X[0], N=p[0], increasing=True)
+        for i in xrange(1, dim):
+            Vi = np.vander(X[i], N=p[i], increasing=True)
+            V = np.multiply.outer(V,Vi)
+        
+        even_axes = range(0,V.ndim,2)
+        odd_axes  = range(1,V.ndim,2)
+        axes = even_axes + odd_axes
+
+        V = np.transpose(V, axes=axes).copy()
+        assert V.shape[:dim] == s
+        assert V.shape[dim:] == p
+        V = V.reshape((S,P))
+        W = V.dot(M)
+        assert W.shape == (S,N)
+
+        if (dtype is not None):
+            W = W.astype(dtype)
+
+        update_cache(cache_file, key, W)
         self.W = W
-        self.U = U
-        self.I = I
 
     def __call__(self, F):
         return self.W.dot(F.ravel()).reshape(self.s)
@@ -595,6 +658,13 @@ class PolynomialSubgridInterpolator(object):
     def generate_subgrid_restrictor(self):
         return PolynomialSubgridRestrictor(subgrid_interpolator=self)
 
+    @property
+    def Wr(self):
+        assert self.W.shape == (self.S, self.N)
+        view = (slice(0,-1,None),)*self.dim + (slice(None, None, None),)*self.dim
+        Wr = self.W.reshape(self.s+self.n)[view].reshape(self.GR, self.N).copy()
+        return Wr
+
 
 class PolynomialSubgridRestrictor(object):
     def __init__(self, subgrid_interpolator):
@@ -677,6 +747,31 @@ if __name__ == '__main__':
     print 'Biquintic (FDC2)'
     print GI1(F)
     print
+    
+    F = [[0,1,1,0], 
+         [0,1,1,0]]
+    F = np.asarray(F)
+    
+    print 'Solving linear/cubic...'
+    PI0 = PolynomialInterpolator(dim=2, deg=(1,3), fd=2, verbose=False)
+    GI0 = PI0.generate_subgrid_interpolator(grid_ratio=grid_ratio, dtype=np.float64)
+    print 'Linear/Cubic (FDC2)'
+    print GI0(F)
+    print
+    
+    F = [[0,0],
+         [1,1], 
+         [1,1],
+         [0,0]]
+    F = np.asarray(F)
+    
+    print 'Solving cubic/linear...'
+    PI0 = PolynomialInterpolator(dim=2, deg=(3,1), fd=2, verbose=False)
+    GI0 = PI0.generate_subgrid_interpolator(grid_ratio=grid_ratio, dtype=np.float64)
+    print 'Cubic/Linear (FDC2)'
+    print GI0(F)
+    print
+    
 
     F = [[0,0,0,0,0,0],
          [0,0,0,0,0,0],
@@ -714,6 +809,20 @@ if __name__ == '__main__':
     print GI3(F)
     print
     
+    print 'Solving septic2/nonic2 ...'
+    PI4 = PolynomialInterpolator(dim=2, deg=(7,9), fd=2, verbose=False, approximative=(not has_flint))
+    GI4 = PI4.generate_subgrid_interpolator(grid_ratio=grid_ratio, dtype=np.float64)
+    print 'septic/nonic (FDC2)'
+    print GI4(F)
+    print
+    
+    print 'Solving septic2/quintic4 ...'
+    PI5 = PolynomialInterpolator(dim=2, deg=(7,5), fd=(2,4), verbose=False, approximative=(not has_flint))
+    GI5 = PI5.generate_subgrid_interpolator(grid_ratio=grid_ratio, dtype=np.float64)
+    print 'septic/nonic (FDC2/FDC4)'
+    print GI5(F)
+    print
+    
     # 3D test
     grid_ratio = (2,2,2)
     print 'Solving trilinear...'
diff --git a/hysop/operator/base/spatial_filtering.py b/hysop/operator/base/spatial_filtering.py
index 401a02e1c..a73b8e975 100644
--- a/hysop/operator/base/spatial_filtering.py
+++ b/hysop/operator/base/spatial_filtering.py
@@ -2,6 +2,7 @@
 @file advection.py
 RestrictionFilter operator generator.
 """
+import numpy as np
 from hysop.constants import Implementation
 from hysop.methods import Remesh
 from hysop.numerics.remesh.remesh import RemeshKernel
@@ -39,11 +40,17 @@ class SpatialFilterBase(object):
                 input_fields={input_field: input_topo},
                 output_fields={output_field: output_topo},
                 **kwds)
-        assert (input_field.dim == output_field.dim)
-        self.Fin   = input_field
-        self.Fout  = output_field
-        self.dim   = input_field.dim
-        self.dtype = find_common_dtype(input_field.dtype, output_field.dtype)
+
+        Fin = input_field
+        Fout = output_field
+        assert (Fin.dim == Fout.dim)
+        assert (Fin.lboundaries == Fout.lboundaries).all()
+        assert (Fin.rboundaries == Fout.rboundaries).all()
+        assert (Fin.periodicity == Fout.periodicity).all()
+        self.Fin   = Fin
+        self.Fout  = Fout
+        self.dim   = Fin.dim
+        self.dtype = find_common_dtype(Fin.dtype, Fout.dtype)
         self.iratio     = None # will be set in get_field_requirements
         self.grid_ratio = None # will be set in discretize
     
@@ -58,6 +65,7 @@ class SpatialFilterBase(object):
         self.dFin  = dFin
         self.dFout = dFout
         self.grid_ratio = grid_ratio
+        self.iter_shape = self.dFin.compute_resolution + 1 - self.dFin.periodicity
     
     @classmethod
     def supports_multiple_field_topologies(cls):
@@ -381,7 +389,7 @@ class PolynomialInterpolationFilterBase(PolynomialInterpolationMethod, Interpola
     @debug
     def get_field_requirements(self):
         reqs = super(PolynomialInterpolationFilterBase, self).get_field_requirements()
-        required_input_ghosts = (self.polynomial_interpolator.ghosts+1,)*self.dim
+        required_input_ghosts = np.add(self.polynomial_interpolator.ghosts, self.Fin.periodicity)
         Fin_topo, Fin_requirements = reqs.get_input_requirement(self.Fin)
         Fin_requirements.min_ghosts = required_input_ghosts
         self.required_input_ghosts = required_input_ghosts
@@ -392,8 +400,9 @@ class PolynomialInterpolationFilterBase(PolynomialInterpolationMethod, Interpola
             return
         super(PolynomialInterpolationFilterBase, self).discretize()
         dFin, dFout = self.dFin, self.dFout
+        ghosts = self.dFin.topology_state.transposed(self.required_input_ghosts)
         psi = self.polynomial_interpolator.generate_subgrid_interpolator(grid_ratio=self.grid_ratio)
         self.subgrid_interpolator = psi
-        self.fin  = dFin.sdata[dFin.local_slices(ghosts=self.required_input_ghosts)].handle
+        self.fin  = dFin.sdata[dFin.local_slices(ghosts=ghosts)].handle
         self.fout = dFout.sdata[dFout.compute_slices].handle
 
diff --git a/hysop/tools/warning.py b/hysop/tools/warning.py
index 18da7991b..cb040bf5b 100644
--- a/hysop/tools/warning.py
+++ b/hysop/tools/warning.py
@@ -17,6 +17,12 @@ class HysopPerformanceWarning(HysopWarning):
     """
     pass
 
+class HysopCacheWarning(HysopWarning):
+    """
+    Custom warning class for hysop caching.
+    """
+    pass
+
 def configure_hysop_warnings(action):
     """ 
     Configure hysop warnings.
-- 
GitLab