From 13ed460929c0240ec1e897c3dd8116cc53830006 Mon Sep 17 00:00:00 2001
From: Paul Dechamps <paul.dechamps@uliege.be>
Date: Sun, 26 Jan 2025 19:27:03 +0100
Subject: [PATCH] (feat) Added abstract class for interpolators

---
 blast/interfaces/blSolversInterface.py        |  2 +-
 .../interpolators/blInterpolator.py           | 68 +++++++++++++++
 .../interpolators/blMatchingInterpolator.py   | 58 +++++++++----
 .../interpolators/blRbfInterpolator.py        | 85 +++++++++++++------
 4 files changed, 170 insertions(+), 43 deletions(-)
 create mode 100644 blast/interfaces/interpolators/blInterpolator.py

diff --git a/blast/interfaces/blSolversInterface.py b/blast/interfaces/blSolversInterface.py
index 05e23dd..724bdff 100644
--- a/blast/interfaces/blSolversInterface.py
+++ b/blast/interfaces/blSolversInterface.py
@@ -70,7 +70,7 @@ class SolversInterface:
         else:
             raise RuntimeError('Incorrect interpolator specified in config.')
 
-        self.interpolator = interp(self.cfg, self.getnDim())
+        self.interpolator = interp(self.getnDim(), **self.cfg)
 
         # Initialize transfer quantities
         self.deltaStarExt = [[np.zeros(0) for iReg in range(3)]\
diff --git a/blast/interfaces/interpolators/blInterpolator.py b/blast/interfaces/interpolators/blInterpolator.py
new file mode 100644
index 0000000..4a03f4d
--- /dev/null
+++ b/blast/interfaces/interpolators/blInterpolator.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2024 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Abstract interpolator class
+# Paul Dechamps
+
+class Interpolator:
+    """
+    Abstract interpolator class
+
+    Attributes:
+    ----------
+    ndim : int
+        Number of dimensions (must be 2 or 3).
+    """
+    def __init__(self, _ndim):
+        """
+        Initialize the Interpolator.
+
+        Parameters:
+        ----------
+        ndim : int
+            Number of dimensions (must be 2 or 3).
+        """
+        if _ndim != 2 and _ndim != 3:
+            raise ValueError('Number of dimensions must be 2 or 3 but {} was given'.format(ndim))
+        self.ndim = _ndim
+    
+    def inviscidToViscous(self, iDict, vDict):
+        """
+        Interpolate data from the inviscid mesh to the viscous mesh.
+
+        Parameters:
+        ----------
+        iDict : list
+            List blDataStructure objects for the inviscid part.
+        vDict : list
+            List blDataStructure objects for the viscous part.
+        """
+        raise NotImplementedError('inviscidToViscous method not implemented')
+    
+    def viscousToInviscid(self, iDict, vDict):
+        """
+        Interpolate data from the viscous mesh to the inviscid mesh.
+
+        Parameters:
+        ----------
+        iDict : list
+            List blDataStructure objects for the inviscid part.
+        vDict : list
+            List blDataStructure objects for the viscous part.
+        """
+        raise NotImplementedError('viscousToInviscid method not implemented')
diff --git a/blast/interfaces/interpolators/blMatchingInterpolator.py b/blast/interfaces/interpolators/blMatchingInterpolator.py
index 1dd052e..f795585 100644
--- a/blast/interfaces/interpolators/blMatchingInterpolator.py
+++ b/blast/interfaces/interpolators/blMatchingInterpolator.py
@@ -1,28 +1,52 @@
-import numpy as np
-class MatchingInterpolator:
+# Copyright 2024 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Matching interpolator class
+# Paul Dechamps
+
+from blast.interfaces.interpolators.blInterpolator import Interpolator
+
+class MatchingInterpolator(Interpolator):
     """
     Matching Interpolator for inviscid and viscous data.
 
     Attributes:
     ----------
-    cfg : dict
-        Configuration dictionary.
-    nDim : int
-        Number of dimensions.
+    _sections : list
+        List of boundary layers sections.
     """
-    def __init__(self, cfg, ndim):
+    def __init__(self, ndim, **kwargs):
         """
         Initialize the MatchingInterpolator.
 
         Parameters:
         ----------
-        _cfg : dict
-            Configuration dictionary.
-        _nDim : int
+        ndim : int
             Number of dimensions.
+        kwargs : dict
+            Optional arguments.
+
+        Optional arguments:
+        -------------------
+        sections : list
+            List of sections for 3D cases.
         """
-        self._cfg = cfg
-        self._ndim = ndim
+        super().__init__(ndim)
+        self._sections = kwargs.get('sections')
+        if self.ndim == 2 and len(self._sections) > 1:
+            raise RuntimeError('Multiple sections are not supported in 2D')
 
     def inviscidToViscous(self, iDict, vDict):
         """
@@ -35,11 +59,11 @@ class MatchingInterpolator:
         vDict : dict
             Viscous data dictionary.
         """
-        if self._ndim == 2:
+        if self.ndim == 2:
             for iReg in range(len(iDict)):
                 vDict[0][iReg].updateVars(iDict[iReg].V, iDict[iReg].M, iDict[iReg].Rho)
-        elif self._ndim == 3:
-            for iSec, ysec in enumerate(self._cfg['sections']):
+        elif self.ndim == 3:
+            for iSec, ysec in enumerate(self._sections):
                 for iReg in range(2):
                     print(iDict[iReg].nodesCoord[iDict[iReg].nodesCoord[:,1] == ysec])
                     print(iDict[iReg].V[iDict[iReg].nodesCoord[:,1] == ysec])
@@ -56,8 +80,8 @@ class MatchingInterpolator:
         vDict : dict
             Viscous data dictionary.
         """
-        if self._ndim == 2:
+        if self.ndim == 2:
             for iReg in range(2):
                 iDict[iReg].blowingVel = vDict[0][iReg].blowingVel
         else:
-            raise RuntimeError('Incorrect number of dimensions', self._ndim)
\ No newline at end of file
+            raise RuntimeError('Incorrect number of dimensions', self.ndim)
\ No newline at end of file
diff --git a/blast/interfaces/interpolators/blRbfInterpolator.py b/blast/interfaces/interpolators/blRbfInterpolator.py
index 1a203fe..6949536 100644
--- a/blast/interfaces/interpolators/blRbfInterpolator.py
+++ b/blast/interfaces/interpolators/blRbfInterpolator.py
@@ -1,33 +1,69 @@
+# Copyright 2024 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 
+# RBF interpolator class
+# Paul Dechamps
+
+from blast.interfaces.interpolators.blInterpolator import Interpolator
 import numpy as np
 from scipy.interpolate import RBFInterpolator
-class RbfInterpolator:
+class RbfInterpolator(Interpolator):
     """
     Radial Basis Function (RBF) Interpolator for inviscid and viscous data.
 
     Attributes:
     ----------
-    _cfg : dict
-        Configuration dictionary.
-    _ndim : int
-        Number of dimensions (2 or 3).
+    _neighbors : int
+        Number of neighbors to use for interpolation.
+    _rbftype : str
+        Type of RBF kernel to use.
+    _smoothing : float
+        Smoothing factor.
+    _degree : int
+        Degree of the polynomial kernel.
+    _sym : list
+        List of symmetry planes.
     """
-    def __init__(self, cfg, ndim):
+    def __init__(self, ndim, **kwargs):
         """
         Initialize the RbfInterpolator.
 
         Parameters:
         ----------
-        cfg : dict
-            Configuration dictionary.
         ndim : int
             Number of dimensions (must be 2 or 3).
+        kwargs : dict
+            Optional arguments.
+            
+        Optional arguments:
+        -------------------
+        nneighbors : int
+            Number of neighbors to use for interpolation. Default is 10.
+        rbftype : str
+            Type of RBF kernel to use. Default is 'linear'.
+        smoothing : float
+            Smoothing factor. Default is 0.0.
+        degree : int
+            Degree of the polynomial kernel. Default is 0.
         """
-        self._cfg = cfg
-        if ndim != 2 and ndim != 3:
-            raise ValueError('Number of dimensions must be 2 or 3 but {} was given'.format(ndim))
-        self._ndim = ndim
+        super().__init__(ndim)
+        self._neighbors = kwargs.get('neighbors', 10)
+        self._rbftype = kwargs.get('rbftype', 'linear')
+        self._smoothing = kwargs.get('smoothing', 0.0)
+        self._degree = kwargs.get('degree', 0)
+        self._sym = kwargs.get('Sym', [])
 
     def inviscidToViscous(self, iDict, vDict):
         """
@@ -48,9 +84,9 @@ class RbfInterpolator:
                 M = np.zeros(reg.nodesCoord.shape[0])
                 rho = np.zeros(reg.nodesCoord.shape[0])
                 for iDim in range(3):
-                    v[:,iDim] = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self._ndim if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].V[:,iDim], reg.nodesCoord[:,:(self._ndim if 'vAirfoil' in reg.name else 1)])
-                M = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self._ndim if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].M, reg.nodesCoord[:,:(self._ndim if 'vAirfoil' in reg.name else 1)])
-                rho = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self._ndim if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].Rho, reg.nodesCoord[:,:(self._ndim if 'vAirfoil' in reg.name else 1)])
+                    v[:,iDim] = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self.ndim if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].V[:,iDim], reg.nodesCoord[:,:(self.ndim if 'vAirfoil' in reg.name else 1)])
+                M = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self.ndim if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].M, reg.nodesCoord[:,:(self.ndim if 'vAirfoil' in reg.name else 1)])
+                rho = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self.ndim if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].Rho, reg.nodesCoord[:,:(self.ndim if 'vAirfoil' in reg.name else 1)])
                 vDict[iSec][iReg].updateVars(v, M, rho)
 
     def viscousToInviscid(self, iDict, vDict):
@@ -64,22 +100,21 @@ class RbfInterpolator:
         vDict : dict
             Viscous data dictionary.
         """
-        if self._ndim == 2:
+        if self.ndim == 2:
             for iReg, reg in enumerate(iDict):
-                iDict[iReg].blowingVel = self.__rbfToSection(vDict[0][iReg].elemsCoordTr[:,:(self._ndim-1 if 'vAirfoil' in reg.name else 1)], vDict[0][iReg].blowingVel, reg.elemsCoordTr[:,:(self._ndim-1 if reg.name == 'iWing' else 1)])
-        elif self._ndim == 3:
+                iDict[iReg].blowingVel = self.__rbfToSection(vDict[0][iReg].elemsCoordTr[:,:(self.ndim-1 if 'vAirfoil' in reg.name else 1)], vDict[0][iReg].blowingVel, reg.elemsCoordTr[:,:(self.ndim-1 if reg.name == 'iWing' else 1)])
+        elif self.ndim == 3:
             for iReg, reg in enumerate(iDict):
                 viscElemsCoord = np.zeros((0,3))
                 viscBlowing = np.zeros(0)
                 for iSec, sec in enumerate(vDict):
                     viscElemsCoord = np.row_stack((viscElemsCoord, sec[iReg].elemsCoord[:,:3]))
                     viscBlowing = np.concatenate((viscBlowing, sec[iReg].blowingVel))
-                if 'Sym' in self._cfg:
-                    for s in self._cfg['Sym']:
-                        dummy = viscElemsCoord.copy()
-                        dummy[:,1] = (dummy[:,1] - s)*(-1) + s
-                        viscElemsCoord = np.row_stack((viscElemsCoord, dummy))
-                        viscBlowing = np.concatenate((viscBlowing, viscBlowing))
+                for s in self._sym:
+                    dummy = viscElemsCoord.copy()
+                    dummy[:,1] = (dummy[:,1] - s)*(-1) + s
+                    viscElemsCoord = np.row_stack((viscElemsCoord, dummy))
+                    viscBlowing = np.concatenate((viscBlowing, viscBlowing))
                 reg.blowingVel = self.__rbfToSection(viscElemsCoord, viscBlowing, reg.elemsCoord[:,:3])
 
     def __rbfToSection(self, x, var, xs):
@@ -103,5 +138,5 @@ class RbfInterpolator:
         if np.all(var == var[0]):
             varOut = RBFInterpolator(x, var, neighbors=1, kernel='linear', smoothing=0.0, degree=0)(xs)
         else:
-            varOut = RBFInterpolator(x, var, neighbors=self._cfg['neighbors'], kernel=self._cfg['rbftype'], smoothing=self._cfg['smoothing'], degree=self._cfg['degree'])(xs)
+            varOut = RBFInterpolator(x, var, neighbors=self._neighbors, kernel=self._rbftype, smoothing=self._smoothing, degree=self._degree)(xs)
         return varOut
\ No newline at end of file
-- 
GitLab