diff --git a/flow/default.py b/flow/default.py
index 9af0ed3a25150675978222c7cc0367f0278804af..ebc7aeecf54bf2949fbc38fa207d613cee110d8d 100644
--- a/flow/default.py
+++ b/flow/default.py
@@ -65,11 +65,14 @@ def problem(msh, dim, alpha, beta, minf, mcrit, sref, lref, xref, yref, zref, su
     bnd = flo.Boundary(msh, [sur, fld])
     pbl.add(bnd)
     if vsc:
-        blw = flo.Blowing(msh, 'airfoil')
+        blw = flo.Blowing(msh, sur)
+        blww = flo.Blowing(msh, wk)
         pbl.add(blw)
+        pbl.add(blww)
     else:
         blw = None
-    return pbl, dirichlet, wake, bnd, blw
+        blww = None
+    return pbl, dirichlet, wake, bnd, [blw, blww]
 
 ## Initialize Picard solver
 def picard(pbl):
diff --git a/flow/tests/bli.py b/flow/tests/bli.py
index 4a6d2262f90fded20f9d07b88d19bbc84a2904fb..3a89ec2ed433952e7ad907ffd6dc443c63311c38 100644
--- a/flow/tests/bli.py
+++ b/flow/tests/bli.py
@@ -16,9 +16,18 @@
 # limitations under the License.
 
 
-# @brief Compute lifting (linear or nonlinear) viscous flow around a naca0012
-# @authors Adrien Crovato, Amaury Bilocq
+## Compute lifting (linear or nonlinear) viscous flow around a NACA 0012
+#
+# Amaury Bilocq
 # Test the viscous-inviscid interaction scheme
+# Reference to the master's thesis: http://hdl.handle.net/2268/252195
+# Reference test cases with Naca0012: 
+# 1) Incompressible: Re = 1e7, M_inf = 0, alpha = 5°, p = 2, m = 7, tol = 10^-4, msTE = 0.01, msLE = 0.001
+#    -> nIt = 41, Cl = 0.58, Cd = 0.0062, xtrTop = 0.0555, xtrBot = 0.7397
+# 2) Compressible: Re = 1e7, M_inf = 5, alpha = 5°, p = 2, m = 7, tol = 10^-4, msTE = 0.01, msLE = 0.001
+#    -> nIt = , Cl = 0.69, Cd = 0.0067, xtrTop = 0.0384, xtrBot = 0.7364
+# 3) Separated: Re = 1e7, M_inf = 0, alpha = 12°, p = 2, m = 11, tol = 5.2*10^-3, msTE = 0.01, msLE = 0.00075
+#    -> nIt = , Cl = 1.39 , Cd = 0.011, xtrTop = 0.008, xtrBot = 1
 #
 # CAUTION
 # This test is provided to ensure that the solver works properly.
@@ -29,10 +38,11 @@ import flow.utils as floU
 import flow.default as floD
 import flow.viscous.solver as floVS
 import flow.viscous.coupler as floVC
+import tbox
 import tbox.utils as tboxU
 import fwk
 from fwk.testing import *
-from fwk.coloring import ccolors 
+from fwk.coloring import ccolors
 
 def main():
     # timer
@@ -40,16 +50,23 @@ def main():
     tms['total'].start()
 
     # define flow variables
-    alpha = 0*math.pi/180
-    M_inf = 0.0
+    Re = 1e7
+    alpha = 5*math.pi/180
+    U_inf = [math.cos(alpha), math.sin(alpha)] # norm should be 1
+    M_inf = 0.5
     M_crit = 5 # Squared critical Mach number (above which density is modified)
     c_ref = 1
     dim = 2
 
+    # define filter parameters and tolerance of the VII 
+    p = 2
+    m = 7
+    tol = 1e-4
+
     # mesh the geometry
     print(ccolors.ANSI_BLUE + 'PyMeshing...' + ccolors.ANSI_RESET)
     tms['msh'].start()
-    pars = {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1., 'msTe' : 0.0075, 'msLe' : 0.0075}
+    pars = {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1, 'msTe' : 0.01, 'msLe' : 0.001}
     msh, gmshWriter = floD.mesh(dim, 'models/n0012.geo', pars, ['field', 'airfoil', 'downstream'])
     tms['msh'].stop()
 
@@ -62,8 +79,8 @@ def main():
     print(ccolors.ANSI_BLUE + 'PySolving...' + ccolors.ANSI_RESET)
     tms['solver'].start()
     isolver = floD.newton(pbl)
-    vsolver = floVS.Solver(blw)
-    coupler = floVC.Coupler(isolver, vsolver, gmshWriter)
+    vsolver = floVS.Solver(Re, p, m)
+    coupler = floVC.Coupler(isolver, vsolver, blw[0], blw[1], tol, gmshWriter)
     coupler.run()
     tms['solver'].stop()
 
@@ -72,8 +89,8 @@ def main():
     tboxU.write(Cp, 'Cp_airfoil.dat', '%1.5e', ', ', 'x, y, z, Cp', '')
     # display results
     print(ccolors.ANSI_BLUE + 'PyRes...' + ccolors.ANSI_RESET)
-    print('       M    alpha       Cl       Cd       Cm')
-    print('{0:8.2f} {1:8.1f} {2:8.4f} {3:8.4f} {4:8.4f}'.format(M_inf, alpha*180/math.pi, isolver.Cl, isolver.Cd, isolver.Cm))
+    print('      Re        M    alpha       Cl       Cd      Cdp      Cdf       Cm')
+    print('{0:6.1f}e6 {1:8.2f} {2:8.1f} {3:8.4f} {4:8.4f} {5:8.4f} {6:8.4f} {7:8.4f}'.format(Re/1e6, M_inf, alpha*180/math.pi, isolver.Cl, vsolver.Cd, vsolver.Cdp, vsolver.Cdf, isolver.Cm))
 
     # display timers
     tms['total'].stop()
@@ -83,16 +100,32 @@ def main():
 
     # visualize solution and plot results
     floD.initViewer(pbl)
-    tboxU.plot(Cp[:,0], Cp[:,3], 'x', 'Cp', 'Cl = {0:.{3}f}, Cd = {1:.{3}f}, Cm = {2:.{3}f}'.format(isolver.Cl, isolver.Cd, isolver.Cm, 4), True)
+    tboxU.plot(Cp[:,0], Cp[:,3], 'x', 'Cp', 'Cl = {0:.{3}f}, Cd = {1:.{3}f}, Cm = {2:.{3}f}'.format(isolver.Cl, vsolver.Cd, isolver.Cm, 4), True)
 
     # check results
     print(ccolors.ANSI_BLUE + 'PyTesting...' + ccolors.ANSI_RESET)
     tests = CTests()
-    if M_inf == 0 and alpha == 0*math.pi/180:
-        tests.add(CTest('min(Cp)', min(Cp[:,3]), -0.41, 1e-1)) # TODO check value and tolerance
-        tests.add(CTest('Cl', isolver.Cl, 0., 5e-2))
+    if Re == 1e7 and M_inf == 0 and alpha == 5*math.pi/180:
+        tests.add(CTest('Cl', isolver.Cl, 0.58, 5e-2))
+        tests.add(CTest('Cd', vsolver.Cd, 0.0062, 0.01))
+        tests.add(CTest('Cdp', vsolver.Cdp, 0.0018, 0.01))
+        tests.add(CTest('xtr_top', vsolver.xtr[0], 0.056, 0.05))
+        tests.add(CTest('xtr_bot', vsolver.xtr[1], 0.740, 0.05))
+    elif Re == 1e7 and M_inf == 0.5 and alpha == 5*math.pi/180:
+        tests.add(CTest('Cl', isolver.Cl, 0.69, 5e-2))
+        tests.add(CTest('Cd', vsolver.Cd, 0.0067, 0.01))
+        tests.add(CTest('Cdp', vsolver.Cdp, 0.0025, 0.01))
+        tests.add(CTest('xtr_top', vsolver.xtr[0], 0.038, 0.05))
+        tests.add(CTest('xtr_bot', vsolver.xtr[1], 0.736, 0.05))
+    elif Re == 1e7 and M_inf == 0 and alpha == 12*math.pi/180:
+        tests.add(CTest('Cl', isolver.Cl, 1.39, 5e-2))
+        tests.add(CTest('Cd', vsolver.Cd, 0.0011, 0.01))
+        tests.add(CTest('Cdp', vsolver.Cdp, 0.0025, 0.01))
+        tests.add(CTest('xtr_top', vsolver.xtr[0], 0.008, 0.05))
+        tests.add(CTest('xtr_bot', vsolver.xtr[1], 1.000, 0.05))
     else:
         raise Exception('Test not defined for this flow')
+    
     tests.run()
 
     # eof
diff --git a/flow/viscous/airfoil.py b/flow/viscous/airfoil.py
new file mode 100644
index 0000000000000000000000000000000000000000..0dd0ccd147eda95c7f3952eae6078e1afe5df29b
--- /dev/null
+++ b/flow/viscous/airfoil.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 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.
+
+
+## Airfoil around which the boundary layer is computed
+#
+# Amaury Bilocq
+
+from flow.viscous.boundary import Boundary
+import numpy as np 
+
+class Airfoil(Boundary): 
+    def __init__(self, _boundary):
+        Boundary.__init__(self, _boundary)
+        self.name = 'airfoil' # type of boundary
+        self.T0 = 0 # initial condition for the momentum thickness
+        self.H0 = 0 
+        self.n0 = 0 
+        self.Ct0 = 0
+        self.TEnd = [0,0]
+        self.HEnd = [0,0]
+        self.CtEnd = [0,0]
+        self.nEnd = [0,0]
+
+    def initialConditions(self, Re, dv):
+        if dv > 0:
+            self.T0 = np.sqrt(0.075/(Re*dv))
+        else:
+            self.T0 = 1e-6
+        self.H0 = 2.23 # One parameter family Falkner Skan
+        self.n0 = 0
+        self.Ct0 = 0
+        return self.T0, self.H0, self.n0, self.Ct0
+
+    def connectList(self):
+        ''' Sort the value read by the viscous solver/ Create list of connectivity  
+        '''
+        N1 = np.zeros(self.nN, dtype=int) # Node number
+        connectListNodes = np.zeros(self.nN, dtype=int) # Index in boundary.nodes
+        connectListElems = np.zeros(self.nE, dtype=int) # Index in boundary.elems
+        data = np.zeros((self.boundary.nodes.size(), 10))
+        i = 0
+        for n in self.boundary.nodes:
+            data[i,0] = n.no
+            data[i,1] = n.pos[0]
+            data[i,2] = n.pos[1]
+            data[i,3] = n.pos[2]
+            data[i,4] = self.v[i,0]
+            data[i,5] = self.v[i,1]
+            data[i,6] = self.Me[i]
+            data[i,7] = self.rhoe[i]
+            data[i,8] = self.deltaStar[i]
+            data[i,9] = self.xx[i]
+            i += 1
+        # Table containing the element and its nodes
+        eData = np.zeros((self.nE,3), dtype=int) 
+        for i in range(0, self.nE):
+            eData[i,0] = self.boundary.tag.elems[i].no
+            eData[i,1] = self.boundary.tag.elems[i].nodes[0].no
+            eData[i,2] = self.boundary.tag.elems[i].nodes[1].no
+        # Defining TE/LE nodes number 
+        idxStag = np.argmin(np.sqrt(data[:,4]**2+data[:,5]**2))
+        globStag = int(data[idxStag,0]) # position of the stagnation point in boundary.nodes
+        idxTE = np.where(data[:,1] == 1.0)[0]
+        if idxTE[0] < idxTE[1]:
+            upperIdxTE = idxTE[0]
+            lowerIdxTE = idxTE[1]
+        else:
+            upperIdxTE = idxTE[1]
+            lowerIdxTE = idxTE[0]
+        upperGlobTE = data[upperIdxTE,0] # Number of the upper TE node
+        lowerGlobTE = data[lowerIdxTE,0] # Number of the lower TE node   
+        connectListElems[0] = np.where(eData[:,1] == globStag)[0]
+        N1[0] = eData[connectListElems[0],1] # number of the stag node elems.nodes
+        connectListNodes[0] = np.where(data[:,0] == N1[0])[0]
+        i = 1
+        upperTE = 0 
+        lowerTE = 0
+        # Sort the suction part 
+        while upperTE == 0:
+            N1[i] = eData[connectListElems[i-1],2] # Second node of the element
+            connectListElems[i] = np.where(eData[:,1] == N1[i])[0] # # Index of the first node of the next element in elems.nodes 
+            connectListNodes[i] = np.where(data[:,0] == N1[i])[0] # Index of the node in boundary.nodes
+            if eData[connectListElems[i],2] == int(upperGlobTE): 
+                upperTE = 1
+            i += 1
+        # Sort the pressure side
+        connectListElems[i] = np.where(eData[:,2] == globStag)[0]
+        connectListNodes[i] = np.where(data[:,0] == upperGlobTE)[0]
+        N1[i] = eData[connectListElems[i],2]
+        while lowerTE == 0:
+            N1[i+1]  = eData[connectListElems[i],1] # First node of the element
+            connectListElems[i+1] = np.where(eData[:,2] == N1[i+1])[0] # # Index of the second node of the next element in elems.nodes 
+            connectListNodes[i+1] = np.where(data[:,0] == N1[i+1])[0] # Index of the node in boundary.nodes
+            if eData[connectListElems[i+1],1] == int(lowerGlobTE): 
+                lowerTE = 1
+            i += 1
+        connectListNodes[i+1] = np.where(data[:,0] == lowerGlobTE)[0]
+        data[:,0] = data[connectListNodes,0]
+        data[:,1] = data[connectListNodes,1]
+        data[:,2] = data[connectListNodes,2]
+        data[:,3] = data[connectListNodes,3]
+        data[:,4] = data[connectListNodes,4]
+        data[:,5] = data[connectListNodes,5]
+        data[:,6] = data[connectListNodes,6]
+        data[:,7] = data[connectListNodes,7]
+        data[:,8] = data[connectListNodes,8]
+        data[:,9] = data[connectListNodes,9]
+        # Separated upper and lower part 
+        data = np.delete(data,0,1)       
+        uData = data[0:np.argmax(data[:,0])+1]
+        lData = data[np.argmax(data[:,0])+1:None]
+        lData = np.insert(lData, 0, uData[0,:], axis = 0) #double the stagnation point
+        return connectListNodes, connectListElems,[uData, lData]
diff --git a/flow/viscous/boundary.py b/flow/viscous/boundary.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a8e862bb038441ce3b6d8398dc22a13f4247a59
--- /dev/null
+++ b/flow/viscous/boundary.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 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.
+
+
+## Base class representing a physical boundary
+#
+# Amaury Bilocq
+
+import numpy as np
+
+class Boundary: 
+    def __init__(self, _boundary):
+        self.boundary = _boundary # boundary of interest
+        self.nN = len(self.boundary.nodes) # number of nodes
+        self.nE = len(self.boundary.tag.elems) # number of elements
+        self.v = np.zeros((self.nN, 3)) # velocity at edge of BL
+        self.u = np.zeros(self.nE) # blowing velocity
+        self.Me = np.zeros(self.nN) # Mach number at the edge of the boundary layer 
+        self.rhoe = np.zeros(self.nN) # density at the edge of the boundary layer
+        self.connectListnodes = np.zeros(self.nN) # connectivity for nodes
+        self.connectListElems = np.zeros(self.nE) # connectivity for elements
+        self.deltaStar = np.zeros(self.nN) # displacement thickness
+        self.xx = np.zeros(self.nN) # coordinates in the reference of the physical surface. Used to store the values for the interaction law
diff --git a/flow/viscous/coupler.py b/flow/viscous/coupler.py
index a75855a54c97586bee7c76e7ea5e3e94825944a2..727a9a73ebd9693fa132fda9d0cb81c4741da85d 100644
--- a/flow/viscous/coupler.py
+++ b/flow/viscous/coupler.py
@@ -16,18 +16,21 @@
 # limitations under the License.
 
 
-# @brief Viscous-inviscid coupler
-# @authors Adrien Crovato, Amaury Bilocq
+## Viscous-inviscid coupler (quasi-simultaneous coupling)
+#
+# Amaury Bilocq
 
 import numpy as np
 from fwk.coloring import ccolors
+from flow.viscous.airfoil import Airfoil
+from flow.viscous.wake import Wake
 
 class Coupler:
-    def __init__(self, _isolver, _vsolver, _writer):
-        '''...
-        '''
+    def __init__(self, _isolver, _vsolver, _boundaryAirfoil, _boundaryWake, _tol, _writer):
         self.isolver =_isolver # inviscid solver
         self.vsolver = _vsolver # viscous solver
+        self.group = [Airfoil(_boundaryAirfoil), Wake(_boundaryWake)] # airfoil and wake python objects
+        self.tol = _tol # tolerance of the VII
         self.writer = _writer
 
     def run(self):
@@ -35,28 +38,48 @@ class Coupler:
         '''
         # initialize loop
         it = 0
-        converged = False # temp
-        while True:
+        converged = 0 # temp
+        CdOld = self.vsolver.Cd
+        while converged == 0:
             print(ccolors.ANSI_BLUE + 'Iteration: ', it, ccolors.ANSI_RESET)
             # run inviscid solver
             self.isolver.run()
-            # get velocity at edge of BL from inviscid solver and update viscous solver
-            for i in range(0, len(self.vsolver.boundary.nodes)):
-                self.vsolver.v[i,0] = self.isolver.U[self.vsolver.boundary.nodes[i].row][0]
-                self.vsolver.v[i,1] = self.isolver.U[self.vsolver.boundary.nodes[i].row][1]
-                self.vsolver.v[i,2] = self.isolver.U[self.vsolver.boundary.nodes[i].row][2]
-            # run viscous solver
-            self.vsolver.run()
-            # get blowing velocity from viscous solver and update inviscid solver
-            for i in range(0, self.vsolver.nE):
-                self.vsolver.boundary.setU(i, self.vsolver.u[i])
-            # convergence test
-            if converged:
-                break
+            print('--- Viscous solver parameters ---')
+            print('Filter windows length:', self.vsolver.m)
+            print('Filter polynomial order:', self.vsolver.p)
+            print('Tolerance:', self.tol)
+            print('--- Viscous problem definition ---')
+            print('Reynolds number:', self.vsolver.Re)
+            print('')
+            for n in range(0, len(self.group)):
+                print('Computing for', self.group[n].name, '...', end = ' ')
+                for i in range(0, len(self.group[n].boundary.nodes)):
+                    self.group[n].v[i,0] = self.isolver.U[self.group[n].boundary.nodes[i].row][0]
+                    self.group[n].v[i,1] = self.isolver.U[self.group[n].boundary.nodes[i].row][1]
+                    self.group[n].v[i,2] = self.isolver.U[self.group[n].boundary.nodes[i].row][2]
+                    self.group[n].Me[i] = self.isolver.M[self.group[n].boundary.nodes[i].row]
+                    self.group[n].rhoe[i] = self.isolver.rho[self.group[n].boundary.nodes[i].row]
+                    # run viscous solver
+                self.vsolver.run(self.group[n])
+                if self.group[n].name == 'airfoil':
+                    self.group[n+1].T0 = self.group[n].TEnd[0]+self.group[n].TEnd[1]
+                    self.group[n+1].H0 = (self.group[n].HEnd[0]*self.group[n].TEnd[0]+self.group[n].HEnd[1]*self.group[n].TEnd[1])/self.group[n+1].T0
+                    self.group[n+1].Ct0 = (self.group[n].CtEnd[0]*self.group[n].TEnd[0]+self.group[n].CtEnd[1]*self.group[n].TEnd[1])/self.group[n+1].T0
+                # get blowing velocity from viscous solver and update inviscid solver                    
+                for i in range(0, self.group[n].nE):                   
+                    self.group[n].boundary.setU(i, self.group[n].u[i])
+                print('done!')
+            print('    Iter           Cd      xtr_top      xtr_bot        Error')
+            print('{0:8d} {1:12.5f} {2:12.5f} {3:12.5f} {4:12.5f}'.format(it, self.vsolver.Cd, self.vsolver.xtr[0], self.vsolver.xtr[1], abs(self.vsolver.Cd - CdOld)/self.vsolver.Cd))
+            print('')
+            # Converged or not
+            if abs(self.vsolver.Cd - CdOld)/self.vsolver.Cd < self.tol:
+                converged = 1   
             else:
-                converged = True # perform 2 iterations
-                it += 1
-            
+                CdOld = self.vsolver.Cd 
+            it += 1
+            self.vsolver.it += 1     
         # save results
         self.isolver.save(0, self.writer)
+        self.vsolver.writeFile()
         print('\n')
diff --git a/flow/viscous/newton.py b/flow/viscous/newton.py
new file mode 100644
index 0000000000000000000000000000000000000000..bed39ec64ca0a43df345cce476da8c4c5e8da6c2
--- /dev/null
+++ b/flow/viscous/newton.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 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.
+
+
+## Newton raphson method coupled with linear solver
+#
+# Amaury Bilocq
+
+import numpy as np
+from fwk.coloring import ccolors
+
+def newton(f, x, maxIt, tol=1.0e-9):
+    '''Newton procedure to solve the non linear set of  boundary layer equations.
+    Boundary layer equations are parabolic in x -> downstream marching is used
+    An implicit marching model is applied (Crank Nicolson) -> matrix inversion is performed
+    '''
+    # Compute Jacobian 
+    def jacobian(f, x):
+        dx = 1.0e-8
+        n = len(x)
+        jac = np.zeros((n,n))
+        f0 = f(x) 
+        for j in range(n):
+            Dxj = (abs(x[j])*dx if x[j] != 0 else dx)
+            x_plus = [(xi if k != j else xi+Dxj) for k, xi in enumerate(x)]
+            jac[:,j] = (f(x_plus)-f0)/Dxj # numerical solution of the jacobian 
+        return jac, f0
+    # Solve
+    for i in range(maxIt):
+        jac, f0 = jacobian(f,x)
+        if np.sqrt(np.dot(f0,f0)/len(x)) < tol and x[0]>0 and  x[1]>0: return x 
+        dx = np.linalg.solve(jac, -f0)
+        x = x + dx
+        x[1] = max(x[1],1.0005) # to avoid too low shape parameter. Solver must be redone to be more robust
+        if np.sqrt(np.dot(dx, dx)) < tol*max(abs(any(x)),1) and x[0]>0 and x[1]>0: return x
+    # Error
+    raise RuntimeError(ccolors.ANSI_RED + 'Viscous Newton solver - too many iterations, aborting!\n' + ccolors.ANSI_RESET)
diff --git a/flow/viscous/solver.py b/flow/viscous/solver.py
index 3b52be11afd0d7e6a9f90bea6c8f29338e5bdec6..6af0f757cd87ee6b61dcdc004de6fea9c2c6942f 100644
--- a/flow/viscous/solver.py
+++ b/flow/viscous/solver.py
@@ -16,35 +16,474 @@
 # limitations under the License.
 
 
-# @brief Boundary Layer Equations viscous solver
-# @authors Adrien Crovato, Amaury Bilocq
+## Integral boundary layer equations viscous solver
+#
+# Amaury Bilocq
+#
+# TODO list
+# - Improve the filter in boundaryLayer (l 263)
+# - Inverse procedure at the singularity location (such as in Xfoil)
+# - Create a new mesh for the viscous solver
+# - Improve the transition solver by refining the grid to split the transitional element into a laminar element and a turbulent element.
+#   Here it is an average between laminar and turbulent solution (l 397-416)
+# - Implement quasi-2D method for 3D capability
+# - User can define the transition location (l 303)
+# - Write the empirical relation between the amplification factor and the turbulence intensity (l 303)
+# - Check why the computation crash if the first point of the wake is computed with the turbulent closure (l 309)
 
+from flow.viscous.boundary import Boundary
+from flow.viscous.wake import Wake
+import flow.viscous.newton as Newton
 import numpy as np
+from scipy.signal import savgol_filter
 
-class Solver:
-    def __init__(self, _boundary):
-        '''...
+class Solver:       
+    def __init__(self, _Re, _p, _m):
+        '''Initialize the viscous solver
         '''
-        # TODO generalize to handle several boundaries (use python array)
-        self.boundary = _boundary # boundary on which BLE are sovled
-        self.nN = len(self.boundary.nodes) # number of nodes
-        self.nE = len(self.boundary.tag.elems) # number of elements
-        self.v = np.zeros((self.nN, 3)) # velocity at edge of BL
-        self.u = np.zeros(self.nE) # blowing velocity
-
-    def run(self):
-        ''' Solve BLE
+        self.Re = _Re # Reynolds number
+        self.p = _p # Filtering order
+        self.m = _m # Filtering bandwith
+        self.gamma = 1.4 # heat capacity of air
+        self.Cd = 0 # drag coefficient
+        self.it = 0 # viscous inviscid iteration for interaction law
+        self.xtr = [1, 1] # set transition at TE reference coordinates
+
+    def dictionary(self):
+        '''Create dictionary with the coordinates and the boundary layer parameters
+        '''
+        wData = {}
+        wData['x'] = self.data[:,0]
+        wData['y'] = self.data[:,1]
+        wData['z'] = self.data[:,2]
+        wData['x/c'] = self.data[:,0]/max(self.data[:,0])
+        wData['Cd'] = self.blScalars[0]
+        wData['Cdf'] = self.blScalars[1]
+        wData['Cdp'] = self.blScalars[2]
+        wData['delta*'] = self.blVectors[0]
+        wData['theta'] = self.blVectors[1]
+        wData['H'] = self.blVectors[2]
+        wData['Hk'] = self.blVectors[3]
+        wData['H*'] = self.blVectors[4]
+        wData['H**'] = self.blVectors[5]
+        wData['Cf'] = self.blVectors[6]
+        wData['Cdissip'] = self.blVectors[7]
+        wData['Ctau'] = self.blVectors[8]
+        wData['xtrTop'] = self.xtr[0]
+        wData['xtrBot'] = self.xtr[1]
+        return wData
+
+    def writeFile(self):
+        '''Write the results in airfoil_viscous.dat
+        '''
+        wData = self.dictionary()
+        toW = ['delta*', 'H', 'Cf'] # list of variable to be written (should be a user input)
+        # Write
+        print('writing file: airfoil_viscous.dat...', end = '')
+        f = open('airfoil_viscous.dat', 'w+')
+        f.write('$Aerodynamic coefficients\n')
+        f.write('             Re              Cd             Cdp             Cdf         xtr_top         xtr_bot\n')
+        f.write('{0:15.6f} {1:15.6f} {2:15.6f} {3:15.6f} {4:15.6f} {5:15.6f}\n'.format(self.Re, wData['Cd'], wData['Cdp'], wData['Cdf'], wData['xtrTop'], wData['xtrBot']))
+        f.write('$Boundary layer variables\n')
+        f.write('              x               y               z             x/c')
+        for s in toW:
+            f.write(' {0:>15s}'.format(s))
+        f.write('\n')
+        for i in range(len(self.data[:,0])):
+            f.write('{0:15.6f} {1:15.6f} {2:15.6f} {3:15.6f}'.format(wData['x'][i], wData['y'][i], wData['z'][i], wData['x/c'][i]))
+            for s in toW:
+                f.write(' {0:15.6f}'.format(wData[s][i]))
+            f.write('\n')
+        f.close()
+        print('done!')           
+
+    def __getBLcoordinates(self, data):  
+        '''Transform the reference coordinates into airfoil coordinates
+        '''    
+        nN = len(data[:,0])
+        nE = nN-1 #nbr of element along the surface   
+        vt = np.zeros(nN)
+        xx = np.zeros(nN) # position along the surface of the airfoil
+        dx = np.zeros(nE) # distance along two nodes
+        dv = np.zeros(nE) # speed difference according to element
+        alpha = np.zeros(nE) # angle of the element for the rotation matrix                                  
+        for i in range(0,nE):
+            alpha[i] = np.arctan2((data[i+1,1]-data[i,1]),(data[i+1,0]-data[i,0]))
+            dx[i] = np.sqrt((data[i+1,0]-data[i,0])**2+(data[i+1,1]-data[i,1])**2)
+            xx[i+1] = dx[i]+xx[i] 
+            vt[i] = (data[i,3]*np.cos(alpha[i]) + data[i,4]*np.sin(alpha[i])) # tangential speed at node 1 element i 
+            vt[i+1] = (data[i+1,3]*np.cos(alpha[i]) + data[i+1,4]*np.sin(alpha[i])) # tangential speed at node 2 element i
+            dv[i] = (vt[i+1] - vt[i])/dx[i] #velocity gradient according to element i. central scheme with half point 
+        return xx, dv, vt, nN, alpha
+
+    def __amplRoutine(self, Hk, Ret, theta):
+        '''Compute the amplitude of the TS waves envelop for the transition
         '''
-        print('--- I am a fake BL solver and I am setting dummy blowing velocities! ---')
-        # 6th order fitting of delta_star for naca 0012 at alpha = 0, from XFoil
-        a = 0.000054925976379640116
-        b = 0.007407077078697043
-        c = -0.043918316829113194
-        d = 0.1487463222137225
-        e = -0.20162146328539832
-        f = 0.0880698637668166
-        g = 0.004436812366681563
-        for i in range(0, self.nE):
-            x = 0.5*(self.boundary.tag.elems[i].nodes[0].pos[0]+self.boundary.tag.elems[i].nodes[1].pos[0])
-            # CAUTION: a positive velocity will result in a suction!
-            self.u[i] = -(b + 2*c*x + 3*d*x*x* + 4*e*x*x*x + 5*f*x*x*x*x + 6*g*x*x*x*x*x) # ue = d(delta_star) / dx
+        Dgr = 0.08
+        Hk1 = 3.5
+        Hk2 = 4
+        Hmi = (1/(Hk-1))
+        logRet_crit = 2.492*(1/(Hk-1))**0.43 + 0.7*(np.tanh(14*Hmi-9.24)+1.0) # value at which the amplification starts to grow
+        if Ret <=0:
+            Ret = 1
+        if np.log10(Ret) < logRet_crit - Dgr  :
+            Ax = 0
+        else:
+            Rnorm = (np.log10(Ret) - (logRet_crit-Dgr))/(2*Dgr)
+            if Rnorm <=0:
+                Rfac = 0
+            if Rnorm >= 1:
+                Rfac = 1
+            else:
+                Rfac = 3*Rnorm**2 - 2*Rnorm**3    
+            # normal envelope amplification rate          
+            m = -0.05+2.7*Hmi-5.5*Hmi**2+3*Hmi**3+0.1*np.exp(-20*Hmi)
+            arg = 3.87*Hmi-2.52
+            dndRet = 0.028*(Hk-1)-0.0345*np.exp(-(arg**2))
+            Ax = (m*dndRet/theta)*Rfac
+            # set correction for separated profiles
+            if Hk > Hk1:
+                Hnorm = (Hk - Hk1)/(Hk2-Hk1)
+                if Hnorm >=1:
+                    Hfac = 1
+                else: 
+                    Hfac = 3*Hnorm**2 - 2*Hnorm**3
+                Ax1 = Ax
+                Gro = 0.3+0.35*np.exp(-0.15*(Hk-5))
+                Tnr = np.tanh(1.2*(np.log10(Ret)-Gro))
+                Ax2 = (0.086*Tnr - 0.25/(Hk-1)**1.5)/theta
+                if Ax2 < 0:
+                    Ax2 = 0
+                Ax = Hfac*Ax2 + (1-Hfac)*Ax1
+                if Ax < 0:
+                    Ax = 0
+        return Ax
+
+    def __laminarClosure(self, theta, H, Ret,Me, name): 
+        ''' Laminar closure derived from the Falkner-Skan one-parameter profile family
+            Nishida and Drela (1996)
+        '''
+        Hk = (H - 0.29*Me**2)/(1+0.113*Me**2) # Kinematic shape factor
+        if name == "airfoil":
+            Hk = max(Hk, 1.02)
+            Hk = min(Hk,6)
+        else:
+            Hk = max(Hk, 1.00005)
+            Hk = min(Hk,6)
+        Hstar2 = (0.064/(Hk-0.8)+0.251)*Me**2 # Density shape parameter
+        delta = min((3.15+ H + (1.72/(Hk-1)))*theta, 12*theta)
+        Hks = Hk - 4.35
+        if Hk <= 4.35:
+            dens = Hk + 1
+            Hstar = 0.0111*Hks**2/dens - 0.0278*Hks**3/dens + 1.528 -0.0002*(Hks*Hk)**2
+        elif Hk > 4.35:
+            Hstar = 1.528 + 0.015*Hks**2/Hk
+        Hstar = (Hstar + 0.028*Me**2)/(1+0.014*Me**2) # Whitfield's minor additional compressibility correction
+        if Hk < 5.5: 
+            tmp = (5.5-Hk)**3 / (Hk+1)
+            Cf = (-0.07 + 0.0727*tmp)/Ret
+        elif Hk >= 5.5:
+            tmp = 1 - 1/(Hk-4.5)
+            Cf = (-0.07 + 0.015*tmp**2) /Ret
+        if Hk < 4:
+            Cd = ( 0.00205*(4.0-Hk)**5.5 + 0.207 ) * (Hstar/(2*Ret))
+        elif Hk >= 4:
+            HkCd = (Hk-4)**2
+            denCd = 1+0.02*HkCd
+            Cd = ( -0.0016*HkCd/denCd + 0.207) * (Hstar/(2*Ret))
+        if name == 'wake':
+            Cd = 2*(1.10*(1.0-1.0/Hk)**2/Hk)* (Hstar/(2*Ret))  
+            Cf = 0          
+        return Cd, Cf, Hstar, Hstar2, Hk, delta, H
+
+    def __turbulentClosure(self, theta, H, Ct, Ret, Me, name):
+        ''' Turbulent closure derived from Nishida and Drela (1996)
+        '''
+        # eliminate absurd transients 
+        Ct = min(Ct, 0.30)
+        Ct = max(Ct, 0.0000001)
+        Hk = (H - 0.29*Me**2)/(1+0.113*Me**2)
+        if name == 'wake':
+            Hk = max(Hk, 1.00005) 
+            Hk = min(Hk,10)
+        else:
+            Hk = max(Hk, 1.00005)
+            Hk = min(Hk,10)
+        Hstar2 = ((0.064/(Hk-0.8))+0.251)*Me**2
+        gam = self.gamma - 1
+        Fc = np.sqrt(1+0.5*gam*Me**2)
+        if Ret <= 400:
+            H0 = 4
+        elif Ret > 400:
+            H0 = 3 + (400/Ret)
+        if Ret > 200:
+            Ret = Ret
+        elif Ret <= 200: 
+            Ret = 200
+        if Hk <= H0:
+            Hstar = ((0.5-4/Ret)*((H0-Hk)/(H0-1))**2)*(1.5/(Hk+0.5))+1.5+4/Ret
+        elif Hk > H0:
+            Hstar = (Hk-H0)**2*(0.007*np.log(Ret)/(Hk-H0+4/np.log(Ret))**2 + 0.015/Hk) + 1.5 + 4/Ret
+        Hstar = (Hstar + 0.028*Me**2)/(1+0.014*Me**2) # Whitfield's minor additional compressibility correction
+        logRt = np.log(Ret/Fc)
+        logRt = np.max((logRt,3))
+        arg = np.max((-1.33*Hk, -20))
+        Us = (Hstar/2)*(1-4*(Hk-1)/(3*H)) # Equivalent normalized wall slip velocity
+        delta = min((3.15+ H + (1.72/(Hk-1)))*theta, 12*theta)
+        Ctcon = 0.5/(6.7**2*0.75)          
+        if name == 'wake':
+            if Us > 0.99995:
+                Us = 0.99995
+            n = 2 # two wake halves
+            Cf = 0 # no friction inside the wake
+            Hkc = Hk - 1  
+            Cdw = 0  # wall contribution 
+            Cdd = (0.995-Us)*Ct**2 # turbulent outer layer contribution
+            Cdl = 0.15*(0.995-Us)**2/Ret # laminar stress contribution to outer layer
+        else:
+            if Us > 0.95:
+                Us = 0.98
+            n = 1
+            Cf = (1/Fc)*(0.3*np.exp(arg)*(logRt/2.3026)**(-1.74-0.31*Hk)+0.00011*(np.tanh(4-(Hk/0.875))-1))
+            Hkc = max(Hk-1-18/Ret, 0.01)
+            # dissipation coefficient 
+            Hmin = 1 + 2.1/np.log(Ret)
+            Fl = (Hk-1)/(Hmin-1)
+            Dfac = 0.5+0.5*np.tanh(Fl)
+            Cdw = 0.5*(Cf*Us) * Dfac
+            Cdd = (0.995-Us)*Ct**2
+            Cdl = 0.15*(0.995-Us)**2/Ret
+        Cteq = np.sqrt(n*Hstar*Ctcon*((Hk-1)*Hkc**2)/((1-Us)*(Hk**2)*H)) #Here it is Cteq^0.5
+        Cd = n*(Cdw+ Cdd + Cdl)         
+        Ueq = 1.25/Hk*(Cf/2-((Hk-1)/(6.432*Hk))**2)   
+        return Cd, Cf, Hstar, Hstar2, Hk, Cteq, delta, Ueq, Ct
+
+
+    def __boundaryLayer(self, data, group): 
+        '''Discretize and solve the boundary layer equations for laminar/turbulent flows
+        '''  
+        xx, dv, vtOld, nN, alpha = self.__getBLcoordinates(data)
+        Me = data[:,5]
+        rhoe = data[:,6]
+        deltaStarOld = data[:,7] 
+        #Filter the initial conditions (Me and rho can be filtered too)
+        if group.name == 'airfoil':
+            vtOld = savgol_filter(vtOld, self.m, self.p, mode = 'interp')
+        vtInv = vtOld
+        rhoInv = rhoe                           
+        if self.it == 0:
+            xxOld = xx 
+        else: 
+            xxOld = data[:,8]
+        #Initialize variables
+        blwVel = np.zeros(nN-1) # Blowing velocity
+        vt = np.zeros(nN) # Boundary layer velocity 
+        theta = np.zeros(nN) # Momentum thickness
+        H = np.zeros(nN) # Shape factor
+        deltaStar = np.zeros(nN) # Displacement thickness
+        Hstar = np.zeros(nN) # Kinetic energy shape parameter
+        Hstar2 = np.zeros(nN) # Density shape parameter  
+        Hk = np.zeros(nN) # Kinematic shape factor      
+        Ret = np.zeros(nN) #Re based on momentum thickness
+        Cd = np.zeros(nN) # Dissipation coefficient 
+        Cf = np.zeros(nN) # Skin friction 
+        n = np.zeros(nN) # Logarithm of the maximum amplification ratio 
+        Ax = np.zeros(nN) # Amplification factor 
+        Ct = np.zeros(nN) # Shear stress coefficient 
+        Cteq = np.zeros(nN) # Equilibrium shear stress coefficient 
+        Ueq = np.zeros(nN) # Equilibrium velocity gradient
+        delta = np.zeros(nN) # BL thickness
+        # Boundary conditions           
+        if group.name == 'airfoil':
+            theta[0], H[0], n[0], Ct[0] = group.initialConditions(self.Re, dv[0])
+        elif group.name == 'wake':
+            theta[0] = group.T0
+            H[0] = group.H0
+            n[0] = group.n0
+            Ct[0] = group.Ct0
+        vt[0] = vtOld[0]
+        Ret[0] = vt[0]*theta[0]*self.Re
+        if Ret[0] < 1:
+            Ret[0] = 1
+            vt[0] = Ret[0]/(theta[0]*self.Re)
+        deltaStar[0] = theta[0]*H[0]
+        # Define transition location ( N = 9) and compute stagnation point
+        ntr = 9  
+        if n[0] < 9:
+            turb =0 # we begin with laminar flow 
+            Cd[0], Cf[0],  Hstar[0], Hstar2[0], Hk[0], delta[0], H[0] = self.__laminarClosure( theta[0], H[0], Ret[0],Me[0], group.name)
+        else : 
+            turb = 1 # typically for the wake 
+            Cd[0], Cf[0],  Hstar[0], Hstar2[0], Hk[0], delta[0], H[0] = self.__laminarClosure( theta[0], H[0], Ret[0],Me[0], group.name)
+        xtr = 0 # if only turbulent flow
+        itTurb = 0
+        # Solve the boundary layer equations
+        for i in range(1,nN):
+            # x[0] = theta; x[1] = H; x[2] = n for laminar/Ct for turbulent; x[3] = vt from interaction law
+            def f_lam(x):
+                f = np.zeros(len(x))                
+                Ret[i] = np.maximum(x[3]*x[0]*self.Re, 1)
+                Cd[i], Cf[i], Hstar[i], Hstar2[i], Hk[i], delta[i], x[1] = self.__laminarClosure(x[0], x[1], Ret[i],Me[i], group.name)
+                Ta = upw*x[0]+(1-upw)*theta[i-1]
+                Ha = upw*x[1]+(1-upw)*H[i-1]
+                uea = upw*x[3]+(1-upw)*vt[i-1]
+                Reta = upw*Ret[i] + (1-upw)*Ret[i-1]
+                Mea = upw*Me[i]+(1-upw)*Me[i-1]
+                Cda, Cfa, Hstara, Hstar2a, Hka, deltaa, Ha = self.__laminarClosure(Ta , Ha, Reta, Mea, group.name)
+                dx = xx[i] - xx[i-1]
+                Axa = self.__amplRoutine(Hka, Reta, Ta)
+                dTheta = x[0]-theta[i-1]
+                due = x[3]-vt[i-1]
+                dH = x[1]-H[i-1]
+                dn = x[2] - n[i-1]
+                dHstar = Hstar[i]-Hstar[i-1]
+                Hstara = upw*Hstar[i]+(1-upw)*Hstar[i-1]
+                f[0] = dTheta/dx + (2 + Ha - Mea**2)*(Ta/uea)*due/dx - Cfa/2
+                f[1] = Ta*(dHstar/dx) + (2*Hstar2a + Hstara*(1-Ha))*Ta/uea * due/dx - 2*Cda + Hstara*Cfa/2
+                f[2] = dn - dx*Axa
+                f[3] = uea - 4/(np.pi*dx)*Ta*Ha - (upw*vtOld[i]+(1-upw)*vtOld[i-1]) + 4/(np.pi*(xxOld[i]-xxOld[i-1]))*(upw*deltaStarOld[i]+(1-upw)*deltaStarOld[i-1])
+                return f
+            def f_turb(x):
+                f = np.zeros(len(x))
+                if group.name == 'wake':
+                    dissipRatio = 0.9 # wall/wake dissipation length ratio 
+                else:
+                    dissipRatio = 1
+                Ret[i] = x[3]*x[0]*self.Re
+                Ta = upw*x[0]+(1-upw)*theta[i-1]
+                xxa = upw*xx[i]+(1-upw)*xx[i-1]
+                Ha = upw*x[1]+(1-upw)*H[i-1]
+                Cta = upw*x[2]+(1-upw)*Ct[i-1]
+                uea = upw*x[3]+(1-upw)*vt[i-1]
+                Reta = np.maximum(upw*(x[3]*x[0]*self.Re)+(1-upw)*(vt[i-1]*theta[i-1]*self.Re), 1)
+                Mea = upw*Me[i]+(1-upw)*Me[i-1]
+                Cd[i], Cf[i], Hstar[i], Hstar2[i],Hk[i],Cteq[i], delta[i], Ueq[i], x[2] = self.__turbulentClosure(x[0], x[1],x[2], Ret[i],Me[i], group.name)
+                Cda, Cfa, Hstara, Hstar2a, Hka,Cteqa, deltaa, Ueqa, Cta = self.__turbulentClosure(Ta, Ha,Cta, Reta,Mea, group.name)
+                dx = xx[i]-xx[i-1]
+                dTheta = x[0]-theta[i-1]
+                due = x[3]-vt[i-1]
+                dH = x[1]-H[i-1]
+                dHstar = Hstar[i]-Hstar[i-1]
+                dCt = x[2]-Ct[i-1] # here it is Ct^1/2 which is represented                   
+                Ta = upw*x[0]+(1-upw)*theta[i-1]
+                Cta = upw*x[2]+(1-upw)*Ct[i-1]           
+                f[0] = dTheta/dx + (2 + Ha - Mea**2)*(Ta/uea)*due/dx - Cfa/2
+                f[1] = Ta*(dHstar/dx) + (2*Hstar2a + Hstara*(1-Ha))*Ta/uea * due/dx - 2*Cda + Hstara*Cfa/2
+                f[2] = (2*deltaa/Cta)*(dCt/dx) - 5.6*(Cteqa-Cta*dissipRatio)-2*deltaa*(4/(3*Ha*Ta)*(Cfa/2-((Hka-1)/(6.7*Hka*dissipRatio))**2)-1/uea * due/dx)
+                f[3] = uea - 4/(np.pi*dx)*Ta*Ha - (upw*vtOld[i]+(1-upw)*vtOld[i-1]) + 4/(np.pi*(xxOld[i]-xxOld[i-1]))*(upw*deltaStarOld[i]+(1-upw)*deltaStarOld[i-1])
+                return f
+            # Laminar solver 
+            if turb == 0:
+                if n[i-1] > 8 or i < 5:
+                    upw = 1 # backward Euler
+                else: 
+                    upw = 0.5 # Trapezoidal scheme 
+                x = np.array([theta[i-1],H[i-1], n[i-1], vt[i-1]])     
+                sol = Newton.newton(f_lam, x, 200)
+                # Determine if the amplification waves start to grow or not   
+                logRet_crit = 2.492*(1/(Hk[i]-1))**0.43 + 0.7*(np.tanh(14*(1/(Hk[i]-1))-9.24)+1.0) 
+                if np.log10(Ret[i]) <= logRet_crit - 0.08  :
+                    n[i] = 0
+                else: 
+                    n[i] = sol[2]
+                xtr = 1
+            # Turbulent solver
+            elif turb == 1:
+                if i <2 and group.name =='wake':
+                    upw = 1 # begin of the wake
+                elif itTurb <2 and group.name == 'airfoil':
+                    upw = 1
+                    itTurb += 1
+                else:
+                    upw = 0.5 # trapezoidal scheme 
+                x = np.array([theta[i-1], H[i-1], Ct[i-1], vt[i-1]]) 
+                sol = Newton.newton(f_turb, x, 200)  
+                Ct[i] = sol[2]  
+            theta[i] = sol[0]
+            H[i] = sol[1]
+            vt[i] = sol[3]
+           # Transition solver
+            if n[i] >= ntr and turb ==0:
+                turb = 1
+                upw = 1
+                xtr = (ntr-n[i-1])*((data[i,0]-data[i-1,0])/(n[i]-n[i-1]))+data[i-1,0] 
+                avTurb = (data[i,0]-xtr)/(data[i,0]-data[i-1,0]) # percentage of the subsection corresponding to a turbulent flow
+                avLam = 1- avTurb # percentage of the subsection corresponding to a laminar flow  
+                # Averaging both solutions
+                Cteq[i-1]= self.__turbulentClosure(theta[i-1], H[i-1], 0.03, Ret[i-1], Me[i-1], group.name)[5]
+                Ct[i-1] = avLam*Cteq[i-1] * 0.7
+                x_turb = np.array([theta[i-1], 1.515, Ct[i-1] , vt[i-1]]) 
+                sol_turb = Newton.newton(f_turb, x_turb, 200)
+                theta[i] = avLam*sol[0]+avTurb*sol_turb[0]
+                H[i] = avLam*sol[1]+avTurb*sol_turb[1]
+                if group.name == 'airfoil':
+                    Ct[i] =  max(avTurb*sol_turb[2],0.03)
+                else:
+                    Ct[i] =  max(avTurb*sol_turb[2],0.05)
+                vt[i] = avLam*sol[3]+avTurb*sol_turb[3]
+                Cd[i-1], Cf[i-1], Hstar[i-1], Hstar2[i-1], Hk[i-1], delta[i-1], _ = self.__laminarClosure(theta[i-1], H[i-1], Ret[i-1],Me[i-1], group.name)
+                Cd[i], Cf[i], Hstar[i], Hstar2[i],Hk[i],Cteq[i], delta[i], Ueq[i], _ = self.__turbulentClosure(theta[i], H[i],Ct[i], Ret[i],Me[i], group.name)
+            deltaStar[i] = H[i]*theta[i]              
+            blwVel[i-1] = -1*(rhoe[i]*vt[i]*deltaStar[i]-rhoe[i-1]*vt[i-1]*deltaStar[i-1])/(rhoe[i]*(xx[i]-xx[i-1]))   # TO DO: need to divide by rhoe or not?
+        # Normalize the friction and dissipation coefficients by the freestream velocity
+        Cf = vt**2*Cf 
+        Cd = vt**2*Cd
+        # Compute the various drag coefficients (total, friction, profile)
+        Cdtot = 2*theta[-1]*(vt[-1]**((H[-1]+5)/2))  
+        Cdf = np.trapz(Cf, data[:,0]) 
+        Cdp = Cdtot-Cdf 
+        # Outputs
+        blScalars = [Cdtot, Cdf, Cdp]
+        blVectors = [deltaStar, theta, H, Hk, Hstar, Hstar2, Cf, Cd, Ct]
+        return blwVel, xtr, xx, blScalars, blVectors
+
+
+    def run(self, group):
+        ''' Run the viscous solver to solve the BL equations and give the outputs to the coupler
+        '''
+        group.connectListNodes, group.connectListElems, data  = group.connectList()
+        # Compute BLE for the airfoil (upper section(data[0]) and lower section (data[1])) 
+        if len(data) == 2:
+            ublwVel, xtrTop, uxx, ublScalars, ublVectors = self.__boundaryLayer(data[0], group)
+            lblwVel, xtrBot, lxx, lblScalars, lblVectors = self.__boundaryLayer(data[1], group)
+            # Put the upper and lower values together
+            self.data = np.concatenate((data[0], data[1]))
+            self.blScalars = np.add(ublScalars, lblScalars)
+            self.blVectors = np.hstack((ublVectors, lblVectors))
+            blwVel = np.concatenate((ublwVel, lblwVel))
+            blwVel = savgol_filter(blwVel, self.m, self.p, mode = 'interp')
+            self.xtr = [xtrTop, xtrBot]
+            # Boundary conditions for the wake
+            group.TEnd = [ublVectors[1][-1], lblVectors[1][-1]]
+            group.HEnd = [ublVectors[2][-1], lblVectors[2][-1]]
+            group.CtEnd = [ublVectors[8][-1], lblVectors[8][-1]]
+            # Delete the stagnation point doublon for variables in the VII loop
+            lblVectors[0] = np.delete(lblVectors[0],0,0) #deltastar
+            lxx = np.delete(lxx,0,0) # airfoil coordinates
+            group.deltaStar = np.concatenate((ublVectors[0], lblVectors[0]))
+            group.xx = np.concatenate((uxx, lxx))
+        # Compute BLE for the wake
+        else:           
+            blwVel, _, group.xx, blScalars, blVectors = self.__boundaryLayer(data, group)
+            group.deltaStar = blVectors[0]
+            # The drag is measured at the end of the wake
+            self.Cd = blScalars[0]
+            self.Cdp = self.Cd-self.blScalars[1] # Cdf comes from the airfoil as there is no friction in the wake
+            self.blScalars[0] = self.Cd
+            self.blScalars[2] = self.Cdp
+            self.Cdf = self.blScalars[1]
+        # Sort the following results in reference frame
+        group.deltaStar = group.deltaStar[group.connectListNodes.argsort()] 
+        group.xx = group.xx[group.connectListNodes.argsort()]
+        group.u = blwVel[group.connectListElems]  
+
+
+
+        
+
+
+
+
+
+            
+        
diff --git a/flow/viscous/wake.py b/flow/viscous/wake.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e8da24ab423f1f4d34bb89a34780bb27095aced
--- /dev/null
+++ b/flow/viscous/wake.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 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.
+
+
+## Wake behind airfoil (around which the boundary layer is computed)
+#
+# Amaury Bilocq
+
+from flow.viscous.boundary import Boundary
+from flow.viscous.airfoil import Airfoil
+import numpy as np 
+
+class Wake(Boundary): 
+    def __init__(self, _boundary):
+        Boundary.__init__(self, _boundary)
+        self.name = 'wake' # type of boundary
+        self.T0 = 0 # initial condition for the momentum thickness
+        self.H0 = 0 
+        self.n0 = 9 # wake is always turbulent 
+        self.Ct0 = 0
+
+
+    def connectList(self):
+        ''' Sort the value read by the viscous solver/ Create list of connectivity  
+        '''
+        N1 = np.zeros(self.nN, dtype=int) # Node number
+        connectListNodes = np.zeros(self.nN, dtype=int) # Index in boundary.nodes
+        connectListElems = np.zeros(self.nE, dtype=int) # Index in boundary.elems
+        data = np.zeros((self.boundary.nodes.size(), 10))
+        i = 0
+        for n in self.boundary.nodes:
+            data[i,0] = n.no
+            data[i,1] = n.pos[0]
+            data[i,2] = n.pos[1]
+            data[i,3] = n.pos[2]
+            data[i,4] = self.v[i,0]
+            data[i,5] = self.v[i,1]
+            data[i,6] = self.Me[i]
+            data[i,7] = self.rhoe[i]
+            data[i,8] = self.deltaStar[i]
+            data[i,9] = self.xx[i]
+            i += 1
+        # Table containing the element and its nodes
+        eData = np.zeros((self.nE,3), dtype=int) 
+        for i in range(0, self.nE):
+            eData[i,0] = self.boundary.tag.elems[i].no
+            eData[i,1] = self.boundary.tag.elems[i].nodes[0].no
+            eData[i,2] = self.boundary.tag.elems[i].nodes[1].no
+        connectListNodes = data[:,1].argsort()
+        connectListElems[0] = np.where(eData[:,1] == 1.0)[0]
+        for i in range(1, len(eData[:,0])):
+            connectListElems[i] = np.where(eData[:,1] == eData[connectListElems[i-1],2])[0]
+        data[:,0] = data[connectListNodes,0]
+        data[:,1] = data[connectListNodes,1]
+        data[:,2] = data[connectListNodes,2]
+        data[:,3] = data[connectListNodes,3]
+        data[:,4] = data[connectListNodes,4]
+        data[:,5] = data[connectListNodes,5]
+        data[:,6] = data[connectListNodes,6]
+        data[:,7] = data[connectListNodes,7]
+        data[:,8] = data[connectListNodes,8]
+        data[:,9] = data[connectListNodes,9]
+        # Separated upper and lower part 
+        data = np.delete(data,0,1)
+        return connectListNodes, connectListElems, data