from scipy import sparse
import numpy as np
import sys
import time
from scipy.sparse import spdiags,linalg,eye

def setup_case():
   global  c_eps_1, c_eps_2,c_omega_1, c_omega_2, cmu, convergence_limit_eps, convergence_limit_k, convergence_limit_om, convergence_limit_pp, \
   convergence_limit_u, convergence_limit_v, convergence_limit_w, cyclic_x, dist, earsm, \
   eps_bc_east, eps_bc_east_type, eps_bc_north, eps_bc_north_type,eps_bc_south, eps_bc_south_type, eps_bc_west, eps_bc_west_type, \
   fx, fy,imon,jmon,kappa,k_bc_east,k_bc_east_type, \
   k_bc_north,k_bc_north_type,k_bc_south, k_bc_south_type,k_bc_west,k_bc_west_type,keps,kom,maxit, \
   ni,nj,nsweep_turb, nsweep_pp, nsweep_vel,  om_bc_east, om_bc_east_type, om_bc_north, om_bc_north_type, \
   om_bc_south, om_bc_south_type, om_bc_west, om_bc_west_type, p_bc_east, p_bc_east_type, \
   p_bc_north, p_bc_north_type, p_bc_south, p_bc_south_type, p_bc_west, p_bc_west_type, pinn, \
   prand_k,prand_eps,prand_omega,resnorm_p,resnorm_vel,restart,save,save_vtk_movie,scheme,scheme_turb,solver_pp,solver_vel, \
   solver_turb,sormax,u_bc_east,u_bc_east_type,u_bc_north,u_bc_north_type,u_bc_south,u_bc_south_type,u_bc_west, \
   u_bc_west_type,urfvis,urf_eps,urf_vel,urf_k,urf_p,urf_omega,v_bc_east,v_bc_east_type,v_bc_north,v_bc_north_type, \
   v_bc_south,v_bc_south_type,v_bc_west,v_bc_west_type,viscos,vol,tk,tk_save,tk_file_name,\
   uu_bc_west,uu_bc_east,uu_bc_south,uu_bc_north,uu_bc_west_type,uu_bc_east_type,uu_bc_south_type,uu_bc_north_type,\
   uv_bc_west,uv_bc_east,uv_bc_south,uv_bc_north,uv_bc_west_type,uv_bc_east_type,uv_bc_south_type,uv_bc_north_type,\
   vv_bc_west,vv_bc_east,vv_bc_south,vv_bc_north,vv_bc_west_type,vv_bc_east_type,vv_bc_south_type,vv_bc_north_type, \
   x2d,xp2d,y2d,yp2d 






   import numpy as np
   import sys


########### section 1 choice of differencing scheme ###########
   scheme='h'  #hybrid
   scheme_turb='h'  #hybrid upwind-central 

########### section 2 turbulence models ###########
   cmu=0.09
   kom  = False 
   keps = True
   c_eps_1=1.5
   c_eps_2 = 1.9
   prand_eps=1.4
   prand_k=1.4
#dummy vals
   c_omega_1 = 5./9.
   c_omega_2 = 3./40.
   prand_omega = 2.0
   urf_omega = 0.5

########### section 3 restart/save ###########
   restart = False
   save = True

########### section 4 fluid properties ###########
   viscos=3.57E-5

########### section 5 relaxation factors ###########
   urfvis=0.5
   urf_vel=0.5
   urf_k=0.5
   urf_p=1.0
   urf_eps=0.5

########### section 6 number of iteration and convergence criterira ###########
   maxit=2000
   min_iter=1
   sormax=5e-5

   solver_vel='lgmres'
   solver_pp='pyamg'
   solver_turb='gmres'

   nsweep_vel=50
   nsweep_pp=50
   nsweep_turb=50
   convergence_limit_u=-1e-6
   convergence_limit_v=-1e-6
   convergence_limit_k=-1e-6 # use absolute conv. criterion
   convergence_limit_om=-1e-6 # use absolute conv. criterion
   convergence_limit_eps=-1e-6 # use absolute conv. criterion
   convergence_limit_pp=5e-2

########### section 7 all variables are printed during the iteration at node ###########
   imon=ni-10
   jmon=int(nj/2)

########### section 8 save data for post-processing ###########
   vtk=False
   save_all_files=False
   vtk_file_name='bound'

########### section 9 residual scaling parameters ###########
   uin=1
   resnorm_p=uin*y2d[1,-1]
   resnorm_vel=uin**2*y2d[1,-1]


########### Section 10 boundary conditions ###########

# boundary conditions for u
   u_bc_west=np.ones(nj)
   u_bc_east=np.zeros(nj)
   u_bc_south=np.zeros(ni)
   u_bc_north=np.zeros(ni)

   u_bc_west_type='d' 
   u_bc_east_type='n' 
   u_bc_south_type='d'
   u_bc_north_type='n'

# boundary conditions for v
   v_bc_west=np.zeros(nj)
   v_bc_east=np.zeros(nj)
   v_bc_south=np.zeros(ni)
   v_bc_north=np.zeros(ni)

   v_bc_west_type='d' 
   v_bc_east_type='n' 
   v_bc_south_type='d'
   v_bc_north_type='d'

# boundary conditions for p
   p_bc_west=np.zeros(nj)
   p_bc_east=np.zeros(nj)
   p_bc_south=np.zeros(ni)
   p_bc_north=np.zeros(ni)

   p_bc_west_type='n'
   p_bc_east_type='n'
   p_bc_south_type='n'
   p_bc_north_type='n'

# boundary conditions for k
   k_bc_west=np.ones(nj)*1e-2
   k_bc_west[10:]=1e-5
   k_bc_east=np.zeros(nj)
   k_bc_south=np.zeros(ni)
   k_bc_north=np.ones(ni)*1e-5

   k_bc_west_type='d'
   k_bc_east_type='n'
   k_bc_south_type='d'
   k_bc_north_type='n'

# boundary conditions
   eps_bc_west=np.ones(nj)*1e-8
   eps_bc_east=np.zeros(nj)
   eps_bc_south=np.zeros(ni)
   eps_bc_north=np.zeros(ni)

   eps_bc_west_type='d'
   eps_bc_east_type='n'
   eps_bc_south_type='d'
   eps_bc_north_type='n'

   return 




def modify_init(u2d,v2d,k2d,om2d,eps2d,vis2d):
   
# set inlet field in entre domain
   u2d=np.repeat(u_bc_west[None,:], repeats=ni, axis=0)
   k2d=np.repeat(k_bc_west[None,:], repeats=ni, axis=0)
   #om2d=np.repeat(om_bc_west[None,:], repeats=ni, axis=0)
   eps2d = cmu * k2d**2 / (100*viscos)
   eps2d = np.maximum(eps2d, 1e-10)   # guard against null div

   vis2d = cmu * k2d**2 / eps2d + viscos

   return u2d,v2d,k2d,om2d,eps2d,vis2d,dist

def modify_inlet():

   global y_rans,y_rans,u_rans,v_rans,k_rans,om_rans,uv_rans,k_bc_west,eps_bc_west,om_bc_west

   return u_bc_west,v_bc_west,k_bc_west,om_bc_west,eps_bc_west,u2d_face_w,convw

def modify_conv(convw,convs):

   return convw,convs

def modify_u(su2d,sp2d):

   global file1

   su2d[0,:]= su2d[0,:]+convw[0,:]*u_bc_west
   sp2d[0,:]= sp2d[0,:]-convw[0,:]
   vist=vis2d[0,:,]-viscos
   su2d[0,:]=su2d[0,:]+vist*aw_bound*u_bc_west
   sp2d[0,:]=sp2d[0,:]-vist*aw_bound


   if iter == 0:
      print('u(5)=%7.3E,u(10)=%7.3E,u(20)=%7.3E,u(30)=%7.3E,u(40)=%7.3E,u(50)=%7.3E,u(60)=%7.3E'\
 %(u2d[ni-5,5],u2d[ni-5,10],u2d[ni-5,20],u2d[ni-5,30],u2d[ni-5,40],u2d[ni-5,50],u2d[ni-5,60]))

   if iter == 0:
      print('file1 opened')
      l1=[iter,u2d[ni-5,5],u2d[ni-5,10],u2d[ni-5,20],u2d[ni-5,30],u2d[ni-5,40],\
           u2d[ni-5,50],u2d[ni-5,60]]
      np.savetxt('u-time-history.dat', l1, newline=" ")
      file1=open('u-time-history.dat','a') #append
   else:
      print('file1 printed')
      file1.write("\n")
      l1=[iter,u2d[ni-5,5],u2d[ni-5,10],u2d[ni-5,20],u2d[ni-5,30],u2d[ni-5,40],\
           u2d[ni-5,50],u2d[ni-5,60]]
      np.savetxt(file1, l1, newline=" ")
#         file1.write("\n")

   return su2d,sp2d

def modify_v(su2d,sp2d):
   su2d[0,:]= su2d[0,:]+convw[0,:]*v_bc_west
   sp2d[0,:]= sp2d[0,:]-convw[0,:]
   vist=vis2d[0,:]-viscos
   su2d[0,:]=su2d[0,:]+vist*aw_bound*v_bc_west
   sp2d[0,:]=sp2d[0,:]-vist*aw_bound

   return su2d,sp2d

def modify_k(su2d,sp2d):

   su2d[0,:]= su2d[0,:]+np.maximum(convw[0,:],0)*k_bc_west
   sp2d[0,:]= sp2d[0,:]-convw[0,:]
   vist=vis2d[0,:]-viscos
   su2d[0,:]=su2d[0,:]+vist*aw_bound*k_bc_west
   sp2d[0,:]=sp2d[0,:]-vist*aw_bound


   return su2d,sp2d

def modify_om(su2d,sp2d):


   su2d[0,:]= su2d[0,:]+np.maximum(convw[0,:],0)*om_bc_west
   sp2d[0,:]= sp2d[0,:]-convw[0,:]
   vist=vis2d[0,:]-viscos
   su2d[0,:]=su2d[0,:]+vist*aw_bound*om_bc_west
   sp2d[0,:]=sp2d[0,:]-vist*aw_bound

   return su2d,sp2d

def modify_eps(su2d, sp2d):
   su2d[0,:] = su2d[0,:] + np.maximum(convw[0,:], 0)*eps_bc_west
   sp2d[0,:] = sp2d[0,:] - convw[0,:]
   vist = vis2d[0,:] - viscos
   su2d[0,:] = su2d[0,:] + vist*aw_bound*eps_bc_west
   sp2d[0,:] = sp2d[0,:] - vist*aw_bound
   return su2d, sp2d

def modify_outlet(convw):

# inlet
   flow_in=np.sum(convw[0,:])
   flow_out=np.sum(convw[-1,:])
#  flow_out=np.sum(convw[-2,:])
   area_out=np.sum(areaw[-1,:])

   uinc=(flow_in-flow_out)/area_out
   ares=areaw[-1,:]
   convw[-1,:]=convw[-1,:]+uinc*ares
#  convw[-1,:]=convw[-2,:]+uinc*ares

   print('area_out',area_out)

   flow_out_new=np.sum(convw[-1,:])

   print(f"{'flow_in: '} {flow_in:.3e},{'  flow_out: '} {flow_out:.3e},{'  flow_out_new: '} {flow_out_new:.3e},{'  uinc: '} {uinc:.3e}")

   return convw

def fix_omega():

   aw2d[:,0]=0
   ae2d[:,0]=0
   as2d[:,0]=0
   an2d[:,0]=0
   ap2d[:,0]=1
   su2d[:,0]=om_bc_south

   return aw2d,ae2d,as2d,an2d,ap2d,su2d,sp2d

def modify_vis(vis2d):

   return vis2d


def fix_k():

   return aw2d,ae2d,as2d,an2d,ap2d,su2d,sp2d


def fix_eps():
   xwall_s = 0.5*(x2d[0:-1,0] + x2d[1:,0])
   ywall_s = 0.5*(y2d[0:-1,0]+ y2d[1:,0])
   dist2_s = (yp2d[:,0]- ywall_s)**2 + (xp2d[:,0] - xwall_s)**2

   eps_wall_s = 2*viscos*k2d[:,0] /  dist2_s

   #bc near wall eps
   aw2d[:,0]=0
   ae2d[:,0]=0
   as2d[:,0]=0
   an2d[:,0]=0
   ap2d[:,0]=1
   su2d[:,0]=eps_wall_s

   return aw2d,ae2d,as2d,an2d,ap2d,su2d,sp2d
   

import torch
import torch.nn as nn
from joblib import load as joblib_load

#load NN model once 
class NuNet(nn.Module):
    def __init__(self, inputs, outputs, layers, neurons):
        super().__init__()
        layerList = []
        layerList.append(nn.Linear(inputs, neurons))       # input layer
        for _ in range(layers):
            layerList.append(nn.Linear(neurons, neurons))  # hidden layers
        layerList.append(nn.Linear(neurons, outputs))      # output layer
        self.layers = nn.ModuleList(layerList)

    def forward(self, x):
        for layer in self.layers[:-1]:
            x = nn.functional.tanh(layer(x))
        return self.layers[-1](x)
# Set Re to match model file trained in NN_SR.py
NN_Re   = 10000
NN_bool = True    # False -> fall back to keps AKN

if NN_bool:
    _NN_model  = torch.load(f'nn/model-f_2-f_mu-Re{NN_Re}.pth',
                            weights_only=False)
    _NN_model.eval()

    # scalers: input0 = yplus, input1 = ystar -> order from NN_SR.py
    _scaler_yp = joblib_load(f'nn/scaler-input0-f_2-f_mu-Re{NN_Re}.bin')
    _scaler_ys = joblib_load(f'nn/scaler-input1-f_2-f_mu-Re{NN_Re}.bin')

    _mm = np.loadtxt(f'nn/min-max-f_2-f_mu-Re{NN_Re}.txt')
    _yplus_min, _yplus_max = _mm[0], _mm[1]
    _ystar_min, _ystar_max = _mm[2], _mm[3]
    _f2_min,    _f2_max    = _mm[4], _mm[5]
    _fmu_min,   _fmu_max   = _mm[6], _mm[7]

    
def calceps(su2d, sp2d, eps2d, gen):
    if iter == 0:
        print(f'calceps called  (NN_bool={NN_bool}, Re={NN_Re})')

    ueps  = (eps2d * viscos) ** 0.25
    ystar = ueps * dist / viscos
    rt    = k2d ** 2 / (eps2d * viscos)

    if NN_bool:

        ustar_col = (viscos * u2d[:, 0] / yp2d[:, 0]) ** 0.5
        yplus_2d  = yp2d * ustar_col[:, None] / viscos

        yplus_2d = np.clip(yplus_2d, _yplus_min, _yplus_max)
        ystar_2d = np.clip(ystar,    _ystar_min, _ystar_max)

        X = np.zeros((ni * nj, 2))
        X[:, 0] = _scaler_yp.transform(yplus_2d.reshape(-1, 1))[:, 0]
        X[:, 1] = _scaler_ys.transform(ystar_2d.reshape(-1, 1))[:, 0]

        with torch.no_grad():
            preds = _NN_model(torch.tensor(X, dtype=torch.float32)).numpy()

    
        f2    = np.clip(preds[:, 0].reshape(ni, nj), _f2_min,  _f2_max)
        fmu2d = np.clip(preds[:, 1].reshape(ni, nj), _fmu_min, _fmu_max)
        fmu2d = np.minimum(fmu2d, 1.0)

    else:
        # standard analytic AKN expressions
        f2    = ((1 - np.exp(-ystar / 3.1)) ** 2) * (1. - 0.3 * np.exp(-(rt / 6.5) ** 2))
        fmu2d = ((1 - np.exp(-ystar / 14)) ** 2) * (1 + 5 / rt ** 0.75 * np.exp(-(rt / 200) ** 2))
        fmu2d = np.minimum(fmu2d, 1.0)


    # production term:  C_eps1 * Cmu * fmu * Pk * (eps/k) * vol
    su2d = su2d + c_eps_1 * cmu * fmu2d * gen * k2d * vol

    # dissipation term: -C_eps2 * f2 * eps^2/k
    sp2d = sp2d - c_eps_2 * f2 * eps2d * vol / k2d

    # case-specific source modifications
    su2d, sp2d = modify_eps(su2d, sp2d)

    ap2d = aw2d + ae2d + as2d + an2d - sp2d

    # under-relaxation
    ap2d = ap2d / urf_eps
    su2d = su2d + (1 - urf_eps) * ap2d * eps2d

    return su2d, sp2d, ap2d, fmu2d
from scipy import sparse
import numpy as np
import sys
import time
import pyamg
from scipy.sparse import spdiags,linalg,eye
import socket

def init():
   print('hostname: ',socket.gethostname())

# distance to nearest wall
   ywall_s=0.5*(y2d[0:-1,0]+y2d[1:,0])
   dist_s=yp2d-ywall_s[:,None]
   ywall_n=0.5*(y2d[0:-1,-1]+y2d[1:,-1])
   dist_n=ywall_n[:,None] -yp2d
   dist=np.minimum(dist_s,dist_n)

#  west face coordinate
   xw=0.5*(x2d[0:-1,0:-1]+x2d[0:-1,1:])
   yw=0.5*(y2d[0:-1,0:-1]+y2d[0:-1,1:])

   del1x=((xw-xp2d)**2+(yw-yp2d)**2)**0.5
   del2x=((xw-np.roll(xp2d,1,axis=0))**2+(yw-np.roll(yp2d,1,axis=0))**2)**0.5
   fx=del2x/(del1x+del2x)
   if cyclic_x:
     fx[0,:]=0.5


#  south face coordinate
   xs=0.5*(x2d[0:-1,0:-1]+x2d[1:,0:-1])
   ys=0.5*(y2d[0:-1,0:-1]+y2d[1:,0:-1])

   del1y=((xs-xp2d)**2+(ys-yp2d)**2)**0.5
   del2y=((xs-np.roll(xp2d,1,axis=1))**2+(ys-np.roll(yp2d,1,axis=1))**2)**0.5
   fy=del2y/(del1y+del2y)

   areawy=np.diff(x2d,axis=1)
   areawx=-np.diff(y2d,axis=1)

   areasy=-np.diff(x2d,axis=0)
   areasx=np.diff(y2d,axis=0)

   areaw=(areawx**2+areawy**2)**0.5
   areas=(areasx**2+areasy**2)**0.5

# volume approaximated as the vector product of two triangles for cells
   ax=np.diff(x2d,axis=1)
   ay=np.diff(y2d,axis=1)
   bx=np.diff(x2d,axis=0)
   by=np.diff(y2d,axis=0)

   areaz_1=0.5*np.absolute(ax[0:-1,:]*by[:,0:-1]-ay[0:-1,:]*bx[:,0:-1])

   ax=np.diff(x2d,axis=1)
   ay=np.diff(y2d,axis=1)
#  areaz_2=0.5*np.absolute(ax[1:,:]*by[:,0:-1]-ay[1:,:]*bx[:,0:-1])
   areaz_2=0.5*np.absolute(ax[1:,:]*by[:,1:]-ay[1:,:]*bx[:,1:])


   vol=areaz_1+areaz_2

# coeff at south wall (without viscosity)
   as_bound=areas[:,0]**2/(0.5*vol[:,0])

# coeff at north wall (without viscosity)
   an_bound=areas[:,-1]**2/(0.5*vol[:,-1])

# coeff at west wall (without viscosity)
   aw_bound=areaw[0,:]**2/(0.5*vol[0,:])

   ae_bound=areaw[-1,:]**2/(0.5*vol[-1,:])

   return areaw,areawx,areawy,areas,areasx,areasy,vol,fx,fy,aw_bound,ae_bound,as_bound,an_bound,dist

def print_indata():

   print('////////////////// Start of input data ////////////////// \n\n\n')

   print('\n\n########### section 1 choice of differencing scheme ###########')
   print(f"{'scheme: ':<29}   {scheme}")
   print(f"{'scheme_turb: ':<29}   {scheme_turb}")

   print('\n\n########### section 2 turbulence models ###########')

   print(f"{'cmu: ':<29} {cmu}")
   print(f"{'kom: ':<29} {kom}")
   print(f"{'keps: ':<29} {keps}")
   print(f"{'EARSM: ':<29} {earsm}")
   print(f"{'PINN: ':<29} {pinn}")
   if kom:
      print(f"{'c_omega_1: ':<29} {c_omega_1:.3f}")
      print(f"{'c_omega_2: ':<29} {c_omega_2}")
      print(f"{'prand_k: ':<29} {prand_k}")
      print(f"{'prand_omega: ':<29} {prand_omega}")
   if keps:
      print(f"{'c_eps_1: ':<29} {c_eps_1}")
      print(f"{'c_eps_2: ':<29} {c_eps_2}")
      print(f"{'prand_k: ':<29} {prand_k}")
      print(f"{'prand_eps: ':<29} {prand_eps}")

   print('\n\n########### section 3 restart/save ###########')
   print(f"{'restart: ':<29} {restart}")
   print(f"{'save: ':<29} {save}")

   print('\n\n########### section 4 fluid properties ###########')
   print(f"{'viscos: ':<29} {viscos:.2e}")

   print('\n\n########### section 5 relaxation factors ###########')
   print(f"{'urfvis: ':<29} {urfvis}")
   print(f"{'urf_vel: ':<29} {urf_vel}")
   print(f"{'urf_p: ':<29} {urf_p}")
   print(f"{'urf_k: ':<29} {urf_k}")

   print('\n\n########### section 6 number of iteration and convergence criterira ###########')
   print(f"{'sormax: ':<29} {sormax}")
   print(f"{'maxit: ':<29} {maxit}")
   print(f"{'solver_pp: ':<29} {solver_pp}")
   print(f"{'solver_vel: ':<29} {solver_vel}")
   print(f"{'solver_turb: ':<29} {solver_turb}")
   print(f"{'nsweep_vel: ':<29} {nsweep_vel}")
   print(f"{'nsweep_pp: ':<29} {nsweep_pp}")
   print(f"{'nsweep_turb: ':<29} {nsweep_turb}")
   print(f"{'convergence_limit_u: ':<29} {convergence_limit_u}")
   print(f"{'convergence_limit_v: ':<29} {convergence_limit_v}")
   print(f"{'convergence_limit_pp: ':<29} {convergence_limit_pp}")
   print(f"{'convergence_limit_k: ':<29} {convergence_limit_k}")
   print(f"{'convergence_limit_om: ':<29} {convergence_limit_om}")

   print('\n\n########### section 7 all variables are printed during the iteration at node ###########')
   print(f"{'imon: ':<29} {imon}")
   print(f"{'jmon: ':<29} {jmon}")


   print('\n\n########### section 8 time-averaging ###########')


   print('\n\n########### section 9 residual scaling parameters ###########')
   print(f"{'resnorm_p: ':<29} {resnorm_p:.1f}")
   print(f"{'resnorm_vel: ':<29} {resnorm_vel:.1f}")


   print('\n\n########### Section 10 grid and boundary conditions ###########')
   print(f"{'ni: ':<29} {ni}")
   print(f"{'nj: ':<29} {nj}")
   print(f"{'cyclic_x: ':<29} {cyclic_x}")
   print('\n')
   print('\n')

   print('------boundary conditions for u')
   print(f"{' ':<5}{'u_bc_west_type: ':<29} {u_bc_west_type}")
   print(f"{' ':<5}{'u_bc_east_type: ':<29} {u_bc_east_type}")
   if u_bc_west_type == 'd':
      print(f"{' ':<5}{'u_bc_west[0]: ':<29} {u_bc_west[0]}")
   if u_bc_east_type == 'd':
      print(f"{' ':<5}{'u_bc_east[0]: ':<29} {u_bc_east[0]}")


   print(f"{' ':<5}{'u_bc_south_type: ':<29} {u_bc_south_type}")
   print(f"{' ':<5}{'u_bc_north_type: ':<29} {u_bc_north_type}")

   if u_bc_south_type == 'd':
      print(f"{' ':<5}{'u_bc_south[0]: ':<29} {u_bc_south[0]}")
   if u_bc_north_type == 'd':
      print(f"{' ':<5}{'u_bc_north[0]: ':<29} {u_bc_north[0]}")

   print('------boundary conditions for v')
   print(f"{' ':<5}{'v_bc_west_type: ':<29} {v_bc_west_type}")
   print(f"{' ':<5}{'v_bc_east_type: ':<29} {v_bc_east_type}")
   if v_bc_west_type == 'd':
      print(f"{' ':<5}{'v_bc_west[0]: ':<29} {v_bc_west[0]}")
   if v_bc_east_type == 'd':
      print(f"{' ':<5}{'v_bc_east[0]: ':<29} {v_bc_east[0]}")


   print(f"{' ':<5}{'v_bc_south_type: ':<29} {v_bc_south_type}")
   print(f"{' ':<5}{'v_bc_north_type: ':<29} {v_bc_north_type}")

   if v_bc_south_type == 'd':
      print(f"{' ':<5}{'v_bc_south[0]: ':<29} {v_bc_south[0]}")
   if v_bc_north_type == 'd':
      print(f"{' ':<5}{'v_bc_north[0]: ':<29} {v_bc_north[0]}")

   print('------boundary conditions for k')
   print(f"{' ':<5}{'k_bc_west_type: ':<29} {k_bc_west_type}")
   print(f"{' ':<5}{'k_bc_east_type: ':<29} {k_bc_east_type}")
   if k_bc_west_type == 'd':
      print(f"{' ':<5}{'k_bc_west[0]: ':<29} {k_bc_west[0]}")
   if k_bc_east_type == 'd':
      print(f"{' ':<5}{'k_bc_east[0]: ':<29} {k_bc_east[0]}")
   
   
   print(f"{' ':<5}{'k_bc_south_type: ':<29} {k_bc_south_type}")
   print(f"{' ':<5}{'k_bc_north_type: ':<29} {k_bc_north_type}")
   
   if k_bc_south_type == 'd':
      print(f"{' ':<5}{'k_bc_south[0]: ':<29} {k_bc_south[0]}")
   if k_bc_north_type == 'd':
      print(f"{' ':<5}{'k_bc_north[0]: ':<29} {k_bc_north[0]}")
   
   if kom:
      print('------boundary conditions for omega')
      print(f"{' ':<5}{'om_bc_west_type: ':<29} {om_bc_west_type}")
      print(f"{' ':<5}{'om_bc_east_type: ':<29} {om_bc_east_type}")
      if om_bc_west_type == 'd':
         print(f"{' ':<5}{'om_bc_west[0]: ':<29} {om_bc_west[0]:.1f}")
      if om_bc_east_type == 'd':
         print(f"{' ':<5}{'om_bc_east[0]: ':<29} {om_bc_east[0]:.1f}")
   
      print(f"{' ':<5}{'om_bc_south_type: ':<29} {om_bc_south_type}")
      print(f"{' ':<5}{'om_bc_north_type: ':<29} {om_bc_north_type}")
   
      if om_bc_south_type == 'd':
         print(f"{' ':<5}{'om_bc_south[0]: ':<29} {om_bc_south[0]:.1f}")
      if om_bc_north_type == 'd':
         print(f"{' ':<5}{'om_bc_north[0]: ':<29} {om_bc_north[0]:.1f}")

   if keps:
      print('------boundary conditions for eps')
      print(f"{' ':<5}{'eps_bc_west_type: ':<29} {eps_bc_west_type}")
      print(f"{' ':<5}{'eps_bc_east_type: ':<29} {eps_bc_east_type}")
      if eps_bc_west_type == 'd':
         print(f"{' ':<5}{'eps_bc_west[0]: ':<29} {eps_bc_west[0]:.1f}")
      if eps_bc_east_type == 'd':
         print(f"{' ':<5}{'eps_bc_east[0]: ':<29} {eps_bc_east[0]:.1f}")
   
      print(f"{' ':<5}{'eps_bc_south_type: ':<29} {eps_bc_south_type}")
      print(f"{' ':<5}{'eps_bc_north_type: ':<29} {eps_bc_north_type}")
   
      if eps_bc_south_type == 'd':
         print(f"{' ':<5}{'eps_bc_south[0]: ':<29} {eps_bc_south[0]:.1f}")
      if eps_bc_north_type == 'd':
         print(f"{' ':<5}{'eps_bc_north[0]: ':<29} {eps_bc_north[0]:.1f}")

   print('\n\n\n ////////////////// End of input data //////////////////\n\n\n')

   return 

def compute_face_phi(phi2d,phi_bc_west,phi_bc_east,phi_bc_south,phi_bc_north,\
    phi_bc_west_type,phi_bc_east_type,phi_bc_south_type,phi_bc_north_type):
   import numpy as np

   phi2d_face_w=np.empty((ni+1,nj))
   phi2d_face_s=np.empty((ni,nj+1))
   phi2d_face_w[0:-1,:]=fx*phi2d+(1-fx)*np.roll(phi2d,1,axis=0)
   phi2d_face_s[:,0:-1]=fy*phi2d+(1-fy)*np.roll(phi2d,1,axis=1)

# west boundary 
   phi2d_face_w[0,:]=phi_bc_west
   if phi_bc_west_type == 'n': 
# neumann
      phi2d_face_w[0,:]=phi2d[0,:]
   if cyclic_x:
      phi2d_face_w[0,:]=0.5*(phi2d[0,:]+phi2d[-1,:])

# east boundary 
   phi2d_face_w[-1,:]=phi_bc_east
   if phi_bc_east_type == 'n': 
# neumann
      phi2d_face_w[-1,:]=phi2d[-1,:]
      phi2d_face_w[-1,:]=phi2d_face_w[-2,:]
   if cyclic_x:
      phi2d_face_w[-1,:]=0.5*(phi2d[0,:]+phi2d[-1,:])


# south boundary 
   phi2d_face_s[:,0]=phi_bc_south
   if phi_bc_south_type == 'n': 
# neumann
      phi2d_face_s[:,0]=phi2d[:,0]

# north boundary 
   phi2d_face_s[:,-1]=phi_bc_north
   if phi_bc_north_type == 'n': 
# neumann
      phi2d_face_s[:,-1]=phi2d[:,-1]
   
   return phi2d_face_w,phi2d_face_s

def dphidx(phi_face_w,phi_face_s):

   phi_w=phi_face_w[0:-1,:]*areawx[0:-1,:]
   phi_e=-phi_face_w[1:,:]*areawx[1:,:]
   phi_s=phi_face_s[:,0:-1]*areasx[:,0:-1]
   phi_n=-phi_face_s[:,1:]*areasx[:,1:]
   return (phi_w+phi_e+phi_s+phi_n)/vol

def dphidy(phi_face_w,phi_face_s):

   phi_w=phi_face_w[0:-1,:]*areawy[0:-1,:]
   phi_e=-phi_face_w[1:,:]*areawy[1:,:]
   phi_s=phi_face_s[:,0:-1]*areasy[:,0:-1]
   phi_n=-phi_face_s[:,1:]*areasy[:,1:]
   return (phi_w+phi_e+phi_s+phi_n)/vol

def coeff(convw,convs,vis2d,prand,scheme_local):

   visw=np.zeros((ni+1,nj))
   viss=np.zeros((ni,nj+1))
   vis_turb=(vis2d-viscos)/prand

   visw[0:-1,:]=fx*vis_turb+(1-fx)*np.roll(vis_turb,1,axis=0)+viscos
   viss[:,0:-1]=fy*vis_turb+(1-fy)*np.roll(vis_turb,1,axis=1)+viscos

   volw=np.ones((ni+1,nj))*1e-10
   vols=np.ones((ni,nj+1))*1e-10
   volw[1:,:]=0.5*np.roll(vol,-1,axis=0)+0.5*vol
   diffw=visw[0:-1,:]*areaw[0:-1,:]**2/volw[0:-1,:]
   vols[:,1:]=0.5*np.roll(vol,-1,axis=1)+0.5*vol
   diffs=viss[:,0:-1]*areas[:,0:-1]**2/vols[:,0:-1]

   if cyclic_x:
      visw[0,:]=0.5*(vis_turb[0,:]+vis_turb[-1,:])+viscos
      diffw[0,:]=visw[0,:]*areaw[0,:]**2/(0.5*(vol[0,:]+vol[-1,:]))

   if scheme_local == 'h':
      if iter == 0:
         print('hybrid scheme, prand=',prand)

      aw2d=np.maximum(convw[0:-1,:],diffw+(1-fx)*convw[0:-1,:])
      aw2d=np.maximum(aw2d,0.)

      ae2d=np.maximum(-convw[1:,:],np.roll(diffw,-1,axis=0)-np.roll(fx,-1,axis=0)*convw[1:,:])
      ae2d=np.maximum(ae2d,0.)

      as2d=np.maximum(convs[:,0:-1],diffs+(1-fy)*convs[:,0:-1])
      as2d=np.maximum(as2d,0.)

      an2d=np.maximum(-convs[:,1:],np.roll(diffs,-1,axis=1)-np.roll(fy,-1,axis=1)*convs[:,1:])
      an2d=np.maximum(an2d,0.)

   if scheme_local == 'c':
      if iter == 0:
         print('CDS scheme, prand=',prand)
      aw2d=diffw+(1-fx)*convw[0:-1,:]
      ae2d=np.roll(diffw,-1,axis=0)-np.roll(fx,-1,axis=0)*convw[1:,:]

      as2d=diffs+(1-fy)*convs[:,0:-1]
      an2d=np.roll(diffs,-1,axis=1)-np.roll(fy,-1,axis=1)*convs[:,1:]

   if not cyclic_x:
      aw2d[0,:]=0
      ae2d[-1,:]=0
   as2d[:,0]=0
   an2d[:,-1]=0

   return aw2d,ae2d,as2d,an2d,su2d,sp2d

def bc(su2d,sp2d,phi_bc_west,phi_bc_east,phi_bc_south,phi_bc_north\
     ,phi_bc_west_type,phi_bc_east_type,phi_bc_south_type,phi_bc_north_type):

   su2d=np.zeros((ni,nj))
   sp2d=np.zeros((ni,nj))

#south
   if phi_bc_south_type == 'd':
      sp2d[:,0]=sp2d[:,0]-viscos*as_bound
      su2d[:,0]=su2d[:,0]+viscos*as_bound*phi_bc_south

#north
   if phi_bc_north_type == 'd':
      sp2d[:,-1]=sp2d[:,-1]-viscos*an_bound
      su2d[:,-1]=su2d[:,-1]+viscos*an_bound*phi_bc_north

#west
   if phi_bc_west_type == 'd' and not cyclic_x:
      sp2d[0,:]=sp2d[0,:]-viscos*aw_bound
      su2d[0,:]=su2d[0,:]+viscos*aw_bound*phi_bc_west
#east
   if phi_bc_east_type == 'd' and not cyclic_x:
      sp2d[-1,:]=sp2d[-1,:]-viscos*ae_bound
      su2d[-1,:]=su2d[-1,:]+viscos*ae_bound*phi_bc_east

   return su2d,sp2d

def muscl_source(phi2d,cep,cem,cwp,cwm,cnp,cnm,csp,csm):

    phip=np.roll(phi2d,-1,axis=0)
    phim=np.roll(phi2d,1,axis=0)
    phipp=np.roll(phi2d,-2,axis=0)
    phimm=np.roll(phi2d,2,axis=0)

#       su(i,j,k)=su(i,j,k)-0.5*
#    &            (conve(i,j,k)*cep*rminmo(phie-phip,phip-phiw)
#    &            -conve(i,j,k)*cem*rminmo(phie-phip,phiee-phie)
#    &            -conve(i-1,j,k)*cwp*rminmo(phip-phiw,phiw-phiww)
#    &            +conve(i-1,j,k)*cwm*rminmo(phip-phiw,phie-phip)



    ss=-0.5*(convw[1:,:]*(cep*minmo(phip-phi2d,phi2d-phim)-cem*minmo(phip-phi2d,phipp-phip)) \
          -convw[0:-1,:]*(cwp*minmo(phi2d-phim,phim-phimm)-cwm*minmo(phi2d-phim,phip-phi2d)))

    phip=np.roll(phi2d,-1,axis=1)
    phim=np.roll(phi2d,1,axis=1)
    phipp=np.roll(phi2d,-2,axis=1)
    phimm=np.roll(phi2d,2,axis=1)

    ss=ss\
     -0.5*(convs[:,1:]*(cnp*minmo(phip-phi2d,phi2d-phim)-cnm*minmo(phip-phi2d,phipp-phip)) \
          -convs[:,0:-1]*(csp*minmo(phi2d-phim,phim-phimm)-csm*minmo(phi2d-phim,phip-phi2d)))

    return ss

def minmo(a,b):

#    asign=sign(1.,a)
#     rminmo=asign*max(0.,min(abs(a),b*asign))
 
   asign=np.sign(a)

   return asign*np.maximum(0,np.minimum(abs(a),b*asign))

def coeff_m(convw,convs,vis2d,phi2d,prand):

   if iter == 0:
      print('muscle for phi scheme called')

   visw=np.zeros((ni+1,nj))
   viss=np.zeros((ni,nj+1))
   vis_turb=(vis2d-viscos)/prand

   visw[0:-1,:]=fx*vis_turb+(1-fx)*np.roll(vis_turb,1,axis=0)+viscos
   viss[:,0:-1]=fy*vis_turb+(1-fy)*np.roll(vis_turb,1,axis=1)+viscos

   volw=np.ones((ni+1,nj))*1e-10
   vols=np.ones((ni,nj+1))*1e-10
   volw[1:]=0.5*np.roll(vol,-1,axis=0)+0.5*vol
   diffw=visw[0:-1]*areaw[0:-1,:]**2/volw[0:-1,:]
   vols[:,1:]=0.5*np.roll(vol,-1,axis=1)+0.5*vol
   diffs=viss[:,0:-1]*areas[:,0:-1]**2/vols[:,0:-1]

   if cyclic_x:
      visw[0,:]=0.5*(vis_turb[0,:]+vis_turb[-1,:])+viscos
      diffw[0,:]=visw[0,:]*areaw[0,:]**2/(0.5*(vol[0,:]+vol[-1,:]))

#        cep=0.5+sign(0.5,conve(i,j,k))
#        cwp=0.5+sign(0.5,conve(i-1,j,k))
#        cem=sign(0.5,conve(i,j,k))-0.5

   cwp=0.5+0.5*np.sign(convw[0:-1,:])
   cep=0.5+0.5*np.sign(convw[1:,:])
   cwm=0.5*np.sign(convw[0:-1,:])-0.5
   cem=0.5*np.sign(convw[1:,:])-0.5

   csp=0.5+0.5*np.sign(convs[:,0:-1])
   cnp=0.5+0.5*np.sign(convs[:,1:])
   csm=0.5*np.sign(convs[:,0:-1])-0.5
   cnm=0.5*np.sign(convs[:,1:])-0.5

#  fix boundaries: no contribution 
   if not cyclic_x:
      cem[-1,:]=0
      cwp[0,:]=0
   cnm[:,-1]=0
   csp[:,0]=0

# first-order upwind in left-hand side
   aw2d=np.maximum(convw[0:-1,:],0)+diffw
   ae2d=np.maximum(-convw[1:,:],-0)+np.roll(diffw,-1,axis=0)
   as2d=np.maximum(convs[:,0:-1],0)+diffs
   an2d=np.maximum(-convs[:,1:],0)+np.roll(diffs,-1,axis=1)

   su2d  =muscl_source(phi2d,cep,cem,cwp,cwm,cnp,cnm,csp,csm)

   if not cyclic_x:
      aw2d[0,:]=0
      ae2d[-1,:]=0
   as2d[:,0]=0
   an2d[:,-1]=0

   return aw2d,ae2d,as2d,an2d,su2d

def conv(u2d,v2d,p2d_face_w,p2d_face_s):
#compute convection
   u2d_face_w,u2d_face_s=compute_face_phi(u2d,u_bc_west,u_bc_east,u_bc_south,u_bc_north,\
    u_bc_west_type,u_bc_east_type,u_bc_south_type,u_bc_north_type)
   v2d_face_w,v2d_face_s=compute_face_phi(v2d,v_bc_west,v_bc_east,v_bc_south,v_bc_north,\
    v_bc_west_type,v_bc_east_type,v_bc_south_type,v_bc_north_type)

   apw=np.zeros((ni+1,nj))
   aps=np.zeros((ni,nj+1))

   convw=-u2d_face_w*areawx-v2d_face_w*areawy
   convs=-u2d_face_s*areasx-v2d_face_s*areasy

#\\\\\\\\\\\\\\\\\ west face

# create ghost cells at east & west boundaries with Neumann b.c.
   p2d_e=p2d
   p2d_w=p2d
# duplicate last row and put it at the end
   p2d_e=np.insert(p2d_e,-1,p2d_e[-1,:],axis=0)
# duplicate row 0 and put it before row 0 (west boundary)
   p2d_w=np.insert(p2d_w,0,p2d_w[0,:],axis=0)

   dp=np.roll(p2d_e,-1,axis=0)-3*p2d_e+3*p2d_w-np.roll(p2d_w,1,axis=0)

#  apw[1:,:]=fx*np.roll(ap2d_vel,-1,axis=0)+(1-fx)*ap2d_vel
   apw[0:-1,:]=fx*ap2d_vel+(1-fx)*np.roll(ap2d_vel,1,axis=0)
   apw[-1,:]=1e-20
 
   dvelw=dp*areaw/4/apw

# boundaries (no corrections)
   dvelw[0,:]=0
   dvelw[-1,:]=0

   convw=convw+areaw*dvelw

#\\\\\\\\\\\\\\\\\ south face
# create ghost cells at north & south boundaries with Neumann b.c.
   p2d_n=p2d
   p2d_s=p2d
# duplicate last column and put it at the end
   p2d_n=np.insert(p2d_n,-1,p2d_n[:,-1],axis=1)
# duplicate first column and put it before column 0 (south boundary)
   p2d_s=np.insert(p2d_s,0,p2d_s[:,0],axis=1)

   dp=np.roll(p2d_n,-1,axis=1)-3*p2d_n+3*p2d_s-np.roll(p2d_s,1,axis=1)

#  aps[:,1:]=fy*np.roll(ap2d_vel,-1,axis=1)+(1-fy)*ap2d_vel
   aps[:,0:-1]=fy*ap2d_vel+(1-fy)*np.roll(ap2d_vel,1,axis=1)
   aps[:,-1]=1e-20
 
   dvels=dp*areas/4/aps

# boundaries (no corrections)
   dvels[:,0]=0
   dvels[:,-1]=0

   convs=convs+areas*dvels

# boundaries 
# west
   if u_bc_west_type == 'd' and not cyclic_x:
      convw[0,:]=-u_bc_west*areawx[0,:]-v_bc_west*areawy[0,:]
# east
   if u_bc_east_type == 'd' and not cyclic_x:
      convw[-1,:]=-u_bc_east*areawx[-1,:]-v_bc_east*areawy[-1,:]
# south
   if v_bc_south_type == 'd':
      convs[:,0]=-u_bc_south*areasx[:,0]-v_bc_south*areasy[:,0]
# north
   if v_bc_north_type == 'd':
      convs[:,-1]=-u_bc_north*areasx[:,-1]-v_bc_north*areasy[:,-1]

   convw, convs = modify_conv(convw, convs)

   return convw,convs

def solve_2d(phi2d,aw2d,ae2d,as2d,an2d,su2d,ap2d,tol_conv,nmax,solver_local):
   if iter == 0:
      print('solve_2d called')
      print('solver_local',solver_local)
      print('nmax',nmax)

   aw=np.matrix.flatten(aw2d)
   ae=np.matrix.flatten(ae2d)
   as1=np.matrix.flatten(as2d)
   an=np.matrix.flatten(an2d)
   ap=np.matrix.flatten(ap2d)
  
   m=ni*nj

   if cyclic_x:
# A = sparse.diags([ap, -ah[:-1], -al[1:], -an[0:-nk], -as1[nk:], -ae, -aw[nj*nk:],-aw,-ae[nj*nk*(ni-1):]], \
#           [0, 1, -1, nk,-nk, nk*nj, -nk*nj, nj*nk*(ni-1), -nj*nk*(ni-1)], format='csr')
      A = sparse.diags([ap, -an[:-1], -as1[1:], -ae, -aw[nj:],-aw,-ae[nj*(ni-1):]],\
            [0, 1, -1, nj, -nj,nj*(ni-1), -nj*(ni-1)], format='csr')
   else:
      A = sparse.diags([ap, -an[0:-1], -as1[1:], -ae, -aw[nj:]], [0, 1, -1, nj, -nj], format='csr')

   su=np.matrix.flatten(su2d)
   phi=np.matrix.flatten(phi2d)

   res_su=np.linalg.norm(su)
   resid_init=np.linalg.norm(A*phi - su)

   phi_org=phi

# bicg (BIConjugate Gradient)
# bicgstab (BIConjugate Gradient STABilized)
# cg (Conjugate Gradient) - symmetric positive definite matrices only
# cgs (Conjugate Gradient Squared)
# gmres (Generalized Minimal RESidual)
# minres (MINimum RESidual)
# qmr (Quasi
   abs_tol=1e-10
   if tol_conv < 0:
# use absolute convergence criterium
      abs_tol =abs(tol_conv)*resid_init
      tol_conv=0

   if solver_local == 'direct':
      if iter == 0:
         print('solver in solve_2d: direct solver')
      info=0
      resid=np.linalg.norm(A*phi - su)
      phi = linalg.spsolve(A,su)
   if solver_local == 'pyamg':
      if iter == 0:
         print('solver in solve_2d: pyamg solver')
      App = pyamg.ruge_stuben_solver(A)                    # construct the multigrid hierarchy
      res_amg = []
      phi = App.solve(su, tol=tol_conv, x0=phi, residuals=res_amg)
      info=0
      print('Residual history in pyAMG', ["%0.4e" % i for i in res_amg])
   if solver_local == 'cgs':
      if iter == 0:
         print('solver in solve_2d: cgs')
      phi,info=linalg.cgs(A,su,x0=phi,  rtol=tol_conv, atol=abs_tol,  maxiter=nmax)  # good
   if solver_local == 'cg':
      if iter == 0:
         print('solver in solve_2d: cg')
      phi,info=linalg.cg(A,su,x0=phi,  rtol=tol_conv, atol=abs_tol,  maxiter=nmax)  # good
   if solver_local == 'gmres':
      if iter == 0:
         print('solver in solve_2d: gmres')
      phi,info=linalg.gmres(A,su,x0=phi, rtol=tol_conv, atol=abs_tol,  maxiter=nmax)  # good
   if solver_local == 'qmr':
      if iter == 0:
         print('solver in solve_2d: qmr')
      phi,info=linalg.qmr(A,su,x0=phi, rtol=tol_conv, atol=abs_tol,  maxiter=nmax)  # good
   if solver_local == 'lgmres':
      if iter == 0:
         print('solver in solve_2d: lgmres')
      phi,info=linalg.lgmres(A,su,x0=phi, rtol=tol_conv, atol=abs_tol,  maxiter=nmax)  # good
   if info > 0:
      print('warning in module solve_2d: convergence in sparse matrix solver not reached')

# compute residual without normalizing with |b|=|su2d|
   if solver_local != 'direct':
      resid=np.linalg.norm(A*phi - su)

   delta_phi=np.max(np.abs(phi-phi_org))

   phi2d=np.reshape(phi,(ni,nj))
   phi2d_org=np.reshape(phi_org,(ni,nj))

   if solver_local != 'pyamg':
      print(f"{'residual history in solve_2d: initial residual: '} {resid_init:.2e}{'final residual: ':>30}{resid:.2e}\
      {'delta_phi: ':>25}{delta_phi:.2e}")

# we return the initial residual; otherwise the solution is always satisfied (but the non-linearity is not accounted for)
   return phi2d,resid_init

def calcu(su2d,sp2d,p2d_face_w,p2d_face_s,uu2d_face_w,uu2d_face_s,uv2d_face_w,uv2d_face_s):
   if iter == 0:
      print('calcu called')
# b.c., sources, coefficients

# presssure gradient
   dpdx=dphidx(p2d_face_w,p2d_face_s)
   su2d=su2d-dpdx*vol

# Reynolds stresses (exkl. the part related to vis2d_earsm)
   if earsm:
      duudx=dphidx(uu2d_face_w,uu2d_face_s)
      duvdy=dphidy(uv2d_face_w,uv2d_face_s)
#     phiw=fx(i-1,j,k)*phi(i,j,k,n)+(1.-fx(i-1,j,k))*phi(i-1,j,k,n)
#     phie=fx(i,j,k)*phi(i+1,j,k,n)+(1.-fx(i,j,k))*phi(i,j,k,n)
#     di=(areanx(i,j,k)**2+areany(i,j,k)**2)**0.5
#     dphidi_RANS=(phie-phiw)/di
      duu_di = (uu2d_face_w[1:,:]-uu2d_face_w[0:-1,:])/areas[:,1:]**0.5
#     phis=fy(i,j-1,k)*phi(i,j,k,n)+(1.-fy(i,j-1,k))*phi(i,j-1,k,n)
#     phin=fy(i,j,k)*phi(i,j+1,k,n)+(1.-fy(i,j,k))*phi(i,j,k,n)
#     dj=(areaex(i,j,k)**2+areaey(i,j,k)**2)**0.5
#     dphidj_RANS=(phin-phis)/dj
      duv_dj = (uv2d_face_s[:,1:]-uv2d_face_s[:,0:-1])/areaw[1:,:]**0.5

      su2d = su2d-(duudx+duvdy)*vol
#     su2d = su2d-(duu_di+duv_dj)*vol

# modify su & sp
   su2d,sp2d=modify_u(su2d,sp2d)
 
   ap2d=aw2d+ae2d+as2d+an2d-sp2d

# under-relaxation
   ap2d=ap2d/urf_vel
   su2d=su2d+(1-urf_vel)*ap2d*u2d

   return su2d,sp2d,ap2d

def calcv(su2d,sp2d,p2d_face_w,p2d_face_s,uv2d_face_w,uv2d_face_s,vv2d_face_w,vv2d_face_s):
   if iter == 0:
      print('calcv called')
# b.c., sources, coefficients 

# presssure gradient
   dpdy=dphidy(p2d_face_w,p2d_face_s)
   su2d=su2d-dpdy*vol

# Reynolds stresses (exkl. the part related to vis2d_earsm)
   if earsm:
      duvdx=dphidx(uv2d_face_w,uv2d_face_s)
      dvvdy=dphidy(vv2d_face_w,vv2d_face_s)
      duv_di = (uv2d_face_w[1:,:]-uv2d_face_w[0:-1,:])/areas[:,1:]**0.5
#     phis=fy(i,j-1,k)*phi(i,j,k,n)+(1.-fy(i,j-1,k))*phi(i,j-1,k,n)
#     phin=fy(i,j,k)*phi(i,j+1,k,n)+(1.-fy(i,j,k))*phi(i,j,k,n)
#     dj=(areaex(i,j,k)**2+areaey(i,j,k)**2)**0.5
#     dphidj_RANS=(phin-phis)/dj
      dvv_dj = (vv2d_face_s[:,1:]-vv2d_face_s[:,0:-1])/areaw[1:,:]**0.5

      su2d = su2d-(duvdx+dvvdy)*vol
#     su2d = su2d-(duv_di+dvv_dj)*vol

# modify su & sp
   su2d,sp2d=modify_v(su2d,sp2d)

   ap2d=aw2d+ae2d+as2d+an2d-sp2d

# under-relaxation
   ap2d=ap2d/urf_vel
   su2d=su2d+(1-urf_vel)*ap2d*v2d

# ap2d will be used in calcp; store it as ap2d_vel
   ap2d_vel=ap2d

   return su2d,sp2d,ap2d,ap2d_vel

def calck_keps(su2d,sp2d,k2d,eps2d,vis2d,u2d_face_w,u2d_face_s,v2d_face_w,v2d_face_s):
# b.c., sources, coefficients 
   if iter == 0:
      print('calck_keps called')

# production term
   dudx=dphidx(u2d_face_w,u2d_face_s)
   dvdx=dphidx(v2d_face_w,v2d_face_s)

   dudy=dphidy(u2d_face_w,u2d_face_s)
   dvdy=dphidy(v2d_face_w,v2d_face_s)

   gen= (2.*(dudx**2+dvdy**2)+(dudy+dvdx)**2)
   vist=np.maximum(vis2d-viscos,1e-10)
   su2d=su2d+vist*gen*vol

   sp2d=sp2d-c_k*eps2d*vol/k2d

# modify su & sp
   su2d,sp2d=modify_k(su2d,sp2d)

   ap2d=aw2d+ae2d+as2d+an2d-sp2d

# under-relaxation
   ap2d=ap2d/urf_k
   su2d=su2d+(1-urf_k)*ap2d*k2d

   return su2d,sp2d,gen,ap2d

def calck(su2d,sp2d,k2d,om2d,vis2d,u2d_face_w,u2d_face_s,v2d_face_w,v2d_face_s):
# b.c., sources, coefficients 
   if iter == 0:
      print('calck_kom called')

# production term
   dudx=dphidx(u2d_face_w,u2d_face_s)
   dvdx=dphidx(v2d_face_w,v2d_face_s)

   dudy=dphidy(u2d_face_w,u2d_face_s)
   dvdy=dphidy(v2d_face_w,v2d_face_s)

   if earsm:
      vist = vis2d_earsm - viscos
      uu_tot = uu2d - vist*dudx
      vv_tot = vv2d- vist*dvdy
      uv_tot = uv2d - vist*(dudy+dvdx)
      gen = -uu_tot*dudx-uv_tot*(dudy+dvdx)-vv_tot*dvdy
      su2d=su2d+gen*vol
   else:
      gen= (2.*(dudx**2+dvdy**2)+(dudy+dvdx)**2)
      vist=np.maximum(vis2d-viscos,1e-10)
      su2d=su2d+vist*gen*vol

# c_k = 1 except when PINN is used, see folder 
# channel-2000-half-channel-PINN
   sp2d=sp2d-cmu*c_k*om2d*vol


# modify su & sp
   su2d,sp2d=modify_k(su2d,sp2d)

   ap2d=aw2d+ae2d+as2d+an2d-sp2d

# under-relaxation
   ap2d=ap2d/urf_k
   su2d=su2d+(1-urf_k)*ap2d*k2d

   return su2d,sp2d,gen,ap2d

def calcom(su2d,sp2d,om2d,gen):
   if iter == 0:
      print('calcom called')

#--------production term
   if earsm:
      su2d=su2d+c_omega_1*gen*vol*om2d/k2d
   else:
      su2d=su2d+c_omega_1*gen*vol

#--------dissipation term
   sp2d=sp2d-c_omega_2*om2d*vol

# modify su & sp
   su2d,sp2d=modify_om(su2d,sp2d)

   ap2d=aw2d+ae2d+as2d+an2d-sp2d

# under-relaxation
   ap2d=ap2d/urf_omega
   su2d=su2d+(1-urf_omega)*ap2d*om2d

   return su2d,sp2d,ap2d

def calceps_standard(su2d,sp2d,eps2d,gen):
   if iter == 0:
      print('calceps called')

   ueps=(eps2d*viscos)**0.25
   ystar=ueps*dist/viscos
   rt=k2d**2/eps2d/viscos
   fdampf2=((1-np.exp(-ystar/3.1))**2)*(1.-0.3*np.exp(-(rt/6.5)**2))
   fmu2d=((1-np.exp(-ystar/14))**2)*(1+5/rt**0.75*np.exp(-(rt/200)**2))
   fmu2d=np.minimum(fmu2d,1)

#--------production term
   vist = vis2d-viscos
   su2d=su2d+c_eps_1*cmu*fmu2d*gen*k2d*vol


#--------dissipation term
   sp2d=sp2d-c_eps_2*fdampf2*eps2d*vol/k2d

# modify su & sp
   su2d,sp2d=modify_eps(su2d,sp2d)

   ap2d=aw2d+ae2d+as2d+an2d-sp2d

# under-relaxation
   ap2d=ap2d/urf_eps
   su2d=su2d+(1-urf_eps)*ap2d*eps2d

   return su2d,sp2d,ap2d,fmu2d

def calcp(pp2d,ap2d_vel):

   if iter == 0:
      print('calcp called')
# b.c., sources, coefficients and under-relaxation for pp2d

   apw=np.zeros((ni+1,nj))
   aps=np.zeros((ni,nj+1))

   pp2d=0
#----------simplec: multiply ap by (1-urf)
   ap2d_vel=np.maximum(ap2d_vel*(1.-urf_vel),1.e-20)

#\\\\\\\\\\\\\\\\ west face 
#  visw[0:-1,:,:]=fx*vis_turb+(1-fx)*np.roll(vis_turb,1,axis=0)+viscos
#  viss[:,0:-1,:]=fy*vis_turb+(1-fy)*np.roll(vis_turb,1,axis=1)+viscos

#  apw[1:,:]=fx*np.roll(ap2d_vel,-1,axis=0)+(1-fx)*ap2d_vel
   apw[0:-1,:]=fx*ap2d_vel+(1-fx)*np.roll(ap2d_vel,1,axis=0)
   if cyclic_x:
      apw[0,:]=0.5*(ap2d_vel[0,:]+ap2d_vel[-1,:])
      apw[-1,:]=apw[0,:]
   else:
      apw[0,:]=1e-20
   dw=areawx**2+areawy**2
   aw2d=dw[0:-1,:]/apw[0:-1,:]
   ae2d=np.roll(aw2d,-1,axis=0)

#\\\\\\\\\\\\\\\\ south face 
#  aps[:,1:]=fy*np.roll(ap2d_vel,-1,axis=1)+(1-fy)*ap2d_vel
   aps[:,0:-1]=fy*ap2d_vel+(1-fy)*np.roll(ap2d_vel,1,axis=1)
   aps[:,0]=1e-20
   ds=areasx**2+areasy**2
   as2d=ds[:,0:-1]/aps[:,0:-1]
   an2d=np.roll(as2d,-1,axis=1)

   as2d[:,0]=0
   an2d[:,-1]=0
   if not cyclic_x:
     aw2d[0,:]=0
     ae2d[-1,:]=0


   ap2d=aw2d+ae2d+as2d+an2d

# continuity error
#  su2d=convw[0:-1,:]-np.roll(convw[0:-1,:],-1,axis=0)+convs[:,0:-1]-np.roll(convs[:,0:-1],-1,axis=1)
   su2d=convw[0:-1,:]-convw[1:,:]+convs[:,0:-1]-convs[:,1:]

# set pp2d=0 in [0,0] tp make it non-singular
   as2d[0,0]=0
   an2d[0,0]=0
   aw2d[0,0]=0
   ae2d[0,0]=0
   ap2d[0,0]=1
#  su2d[0,0]=0

   return aw2d,ae2d,as2d,an2d,su2d,ap2d


def correct_u_v_p(u2d,v2d,p2d):
   if iter == 0:
      print('correct_u_v_p called')

# correct convections
#\\\\\\\\\\\\\ west face
   if cyclic_x:
#     convw[1:-1,:,:]=convw[1:-1,:,:]+aw2d_p[1:,:,:]*(p3d_w[1:-1,:,:]-p3d[1:,:,:])*dtt
#     convw[0,:,:]=convw[0,:,:]+aw3d_p[0,:,:]*(p3d[-1,:,:]-p3d[0,:,:])*dtt
#     convw[-1,:,:]=convw[0,:,:]
      convw[1:-1,:]=convw[1:-1,:]+aw2d[0:-1,:]*(pp2d[1:,:]-pp2d[0:-1,:])
      convw[0,:]=convw[0,:]+aw2d[0,:]*(pp2d[0,:]-pp2d[-1,:])
      convw[-1,:]=convw[0,:]
   else:
      convw[1:-1,:]=convw[1:-1,:]+aw2d[0:-1,:]*(pp2d[1:,:]-pp2d[0:-1,:])

#\\\\\\\\\\\\\ south face
   convs[:,1:-1]=convs[:,1:-1]+as2d[:,0:-1]*(pp2d[:,1:]-pp2d[:,0:-1])

# correct p
   p2d=p2d+urf_p*(pp2d-pp2d[0,0])

# compute pressure correecion at faces (N.B. p_bc_west,, ... are not used since we impose Neumann b.c., everywhere)
   pp2d_face_w,pp2d_face_s=compute_face_phi(pp2d,p_bc_west,p_bc_east,p_bc_south,p_bc_north,\
        'n','n','n','n')

   dppdx=dphidx(pp2d_face_w,pp2d_face_s)
   u2d=u2d-dppdx*vol/ap2d_vel

   dppdy=dphidy(pp2d_face_w,pp2d_face_s)
   v2d=v2d-dppdy*vol/ap2d_vel


   return convw,convs,p2d,u2d,v2d,su2d


def vist_kom(vis2d,k2d,om2d):
   if iter == 0:
      print('vist_kom called')

   visold= vis2d
   vis2d= k2d/om2d+viscos

# modify viscosity
   vis2d=modify_vis(vis2d)

#            under-relax viscosity
   vis2d= urfvis*vis2d+(1.-urfvis)*visold

   return vis2d

def vist_keps(vis2d,k2d,eps2d,fmu2d):
   if iter == 0:
      print('vist_keps called')

   visold= vis2d
   vis2d= fmu2d*cmu*k2d**2/eps2d+viscos

# modify viscosity
   vis2d=modify_vis(vis2d)

#            under-relax viscosity
   vis2d= urfvis*vis2d+(1.-urfvis)*visold

   return vis2d

def save_vtk():
   scalar_names = ['pressure']
   scalar_variables = [p2d]
   scalar_names.append('turb_kin')
   scalar_names.append('omega')
   scalar_variables.append(k2d)
   scalar_variables.append(om2d)

   if save_vtk_movie:
      file_name = '%s.%d.vtk' % (vtk_file_name, itstep)
   else:
      file_name = '%s.vtk' % (vtk_file_name)

   nk=1
   dz=1
   f = open(file_name,'w')
   f.write('# vtk DataFile Version 3.0\npyCALC-LES Data\nASCII\nDATASET STRUCTURED_GRID\n')
   f.write('DIMENSIONS %d %d %d\nPOINTS %d double\n' % (nk+1,nj+1,ni+1,(ni+1)*(nj+1)*(nk+1)))
   for i in range(ni+1):
      for j in range(nj+1):
         for k in range(nk+1):
            f.write('%.5f %.5f %.5f\n' % (x2d[i,j],y2d[i,j],dz*k))
   f.write('\nCELL_DATA %d\n' % (ni*nj*nk))

   f.write('\nVECTORS velocity double\n')
   for i in range(ni):
      for j in range(nj):
         for k in range(nk):
            f.write('%.12e %.12e %.12e\n' % (u2d[i,j,k],v2d[i,j,k],w2d[i,j,k]))

   for v in range(len(scalar_names)):
      var_name = scalar_names[v]
      var = scalar_variables[v]
      f.write('\nSCALARS %s double 1\nLOOKUP_TABLE default\n' % (var_name))
      for i in range(ni):
         for j in range(nj):
            for k in range(nk):
               f.write('%.10e\n' % (var[i,j,k]))
   f.close()

   print('Flow state save into VTK format to file %s\n' % (file_name))

def read_restart_data():

   print('read_restart_data called')

   u2d=np.load('u2d_saved.npy')
   v2d=np.load('v2d_saved.npy')
   p2d=np.load('p2d_saved.npy')
   k2d=np.load('k2d_saved.npy')
   eps2d=np.ones((ni,nj))*1e-10
   om2d=np.ones((ni,nj))*1e-10
   if kom:
      om2d=np.load('om2d_saved.npy')
   if keps:
      eps2d=np.load('eps2d_saved.npy')
   vis2d=np.load('vis2d_saved.npy')
   ap2d_vel=np.load('ap2d_vel_saved.npy')

   return u2d,v2d,p2d,k2d,om2d,eps2d,vis2d,ap2d_vel

def save_data(u2d,v2d,p2d,k2d,om2d,eps2d,vis2d,ap2d_vel):

   print('save_data called')
   np.save('u2d_saved', u2d)
   np.save('v2d_saved', v2d)
   np.save('p2d_saved', p2d)
   np.save('k2d_saved', k2d)
   if kom:
      np.save('om2d_saved', om2d)
   if keps:
      np.save('eps2d_saved', eps2d)
   np.save('vis2d_saved', vis2d)
   np.save('ap2d_vel_saved', ap2d_vel)
   if earsm:
      np.save('uu2d_saved', uu2d)
      np.save('vv2d_saved', vv2d)
      np.save('ww2d_saved', ww2d)
      np.save('uv2d_saved', uv2d)
      np.save('vis2d_earsm_saved', vis2d_earsm)

   return 

######################### the execution of the code starts here #############################

########### grid specification ###########
datax= np.loadtxt("x2d.dat")
x=datax[0:-1]
ni=int(datax[-1])
datay= np.loadtxt("y2d.dat")
y=datay[0:-1]
nj=int(datay[-1])

x2d=np.zeros((ni+1,nj+1))
y2d=np.zeros((ni+1,nj+1))

x2d=np.reshape(x,(ni+1,nj+1))
y2d=np.reshape(y,(ni+1,nj+1))

# compute cell centers
xp2d=0.25*(x2d[0:-1,0:-1]+x2d[0:-1,1:]+x2d[1:,0:-1]+x2d[1:,1:])
yp2d=0.25*(y2d[0:-1,0:-1]+y2d[0:-1,1:]+y2d[1:,0:-1]+y2d[1:,1:])

# initialize geometric arrays

vol=np.zeros((ni,nj))
areas=np.zeros((ni,nj+1))
areasx=np.zeros((ni,nj+1))
areasy=np.zeros((ni,nj+1))
areaw=np.zeros((ni+1,nj))
areawx=np.zeros((ni+1,nj))
areawy=np.zeros((ni+1,nj))
areaz=np.zeros((ni,nj))
as_bound=np.zeros((ni))
an_bound=np.zeros((ni))
aw_bound=np.zeros((nj))
ae_bound=np.zeros((nj))
az_bound=np.zeros((ni,nj))
fx=np.zeros((ni,nj))
fy=np.zeros((ni,nj))

# default values
# boundary conditions for u
u_bc_west=np.ones(nj)
u_bc_east=np.zeros(nj)
u_bc_south=np.zeros(ni)
u_bc_north=np.zeros(ni)

u_bc_west_type='d'
u_bc_east_type='n'
u_bc_south_type='d'
u_bc_north_type='d'

# boundary conditions for v
v_bc_west=np.zeros(nj)
v_bc_east=np.zeros(nj)
v_bc_south=np.zeros(ni)
v_bc_north=np.zeros(ni)

v_bc_west_type='d'
v_bc_east_type='n'
v_bc_south_type='d'
v_bc_north_type='d'

# boundary conditions for p
p_bc_west=np.zeros(nj)
p_bc_east=np.zeros(nj)
p_bc_south=np.zeros(ni)
p_bc_north=np.zeros(ni)

p_bc_west_type='n'
p_bc_east_type='n'
p_bc_south_type='n'
p_bc_north_type='n'

# boundary conditions for k
k_bc_west=np.zeros(nj)
k_bc_east=np.zeros(nj)
k_bc_south=np.zeros(ni)
k_bc_north=np.zeros(ni)

k_bc_west_type='n'
k_bc_east_type='n'
k_bc_south_type='d'
k_bc_north_type='d'


# boundary conditions for omega
om_bc_west=np.zeros(nj)
om_bc_east=np.zeros(nj)
om_bc_south=np.zeros(ni)
om_bc_north=np.zeros(ni)

om_bc_west_type='d'
om_bc_east_type='n'
om_bc_south_type='d'
om_bc_north_type='d'

# boundary conditions for eps
eps_bc_west=np.zeros(nj)
eps_bc_east=np.zeros(nj)
eps_bc_south=np.zeros(ni)
eps_bc_north=np.zeros(ni)

eps_bc_west_type='d'
eps_bc_east_type='n'
eps_bc_south_type='d'
eps_bc_north_type='d'
 
# boundary conditions for uu
uu_bc_west=np.zeros(nj)
uu_bc_east=np.zeros(nj)
uu_bc_south=np.zeros(ni)
uu_bc_north=np.zeros(ni)

uu_bc_west_type='n'
uu_bc_east_type='n'
uu_bc_south_type='d'
uu_bc_north_type='d'

# boundary conditions for uv
uv_bc_west=np.zeros(nj)
uv_bc_east=np.zeros(nj)
uv_bc_south=np.zeros(ni)
uv_bc_north=np.zeros(ni)

uv_bc_west_type='n'
uv_bc_east_type='n'
uv_bc_south_type='d'
uv_bc_north_type='d'

# boundary conditions for vv
vv_bc_west=np.zeros(nj)
vv_bc_east=np.zeros(nj)
vv_bc_south=np.zeros(ni)
vv_bc_north=np.zeros(ni)

vv_bc_west_type='n'
vv_bc_east_type='n'
vv_bc_south_type='d'
vv_bc_north_type='d'


cyclic_x=False
vtk=False
earsm = False
kom = False
keps = False
pinn = False

setup_case()

print_indata()

areaw,areawx,areawy,areas,areasx,areasy,vol,fx,fy,aw_bound,ae_bound,as_bound,an_bound,dist=init()


# initialization
u2d=np.ones((ni,nj))*1e-20
v2d=np.ones((ni,nj))*1e-20
p2d=np.ones((ni,nj))*1e-20
pp2d=np.ones((ni,nj))*1e-20
k2d=np.ones((ni,nj))*1
om2d=np.ones((ni,nj))*1
eps2d=np.ones((ni,nj))*1
vis2d=np.ones((ni,nj))*viscos
vis2d_earsm=np.ones((ni,nj))*viscos
uu2d=np.ones((ni,nj))*1e-20
uv2d=np.ones((ni,nj))*1e-20
vv2d=np.ones((ni,nj))*1e-20
ww2d=np.ones((ni,nj))*1e-20

fmu2d=np.ones((ni,nj))
gen=np.ones((ni,nj))

convw=np.ones((ni+1,nj))*1e-20
convs=np.ones((ni,nj+1))*1e-20

aw2d=np.ones((ni,nj))*1e-20
ae2d=np.ones((ni,nj))*1e-20
as2d=np.ones((ni,nj))*1e-20
an2d=np.ones((ni,nj))*1e-20
al2d=np.ones((ni,nj))*1e-20
ah2d=np.ones((ni,nj))*1e-20
ap2d=np.ones((ni,nj))*1e-20
ap2d_vel=np.ones((ni,nj))*1e-20
su2d=np.ones((ni,nj))*1e-20
sp2d=np.ones((ni,nj))*1e-20
ap2d=np.ones((ni,nj))*1e-20
dudx=np.ones((ni,nj))*1e-20
dudy=np.ones((ni,nj))*1e-20
usynt_inlet=np.ones((nj))*1e-20
vsynt_inlet=np.ones((nj))*1e-20
wsynt_inlet=np.ones((nj))*1e-20

uu2d_face_w = np.ones((ni,nj))*1e-20
uu2d_face_s = np.ones((ni,nj))*1e-20
vv2d_face_w = np.ones((ni,nj))*1e-20
vv2d_face_s = np.ones((ni,nj))*1e-20
uv2d_face_w = np.ones((ni,nj))*1e-20
uv2d_face_s = np.ones((ni,nj))*1e-20

# comute Delta_max for LES/DES/PANS models
delta_max=np.maximum(vol/areas[:,1:],vol/areaw[1:,:])

# the three arrays below are constant except when PINN is used, see folder 
# channel-2000-half-channel-PINN

c_k =np.ones((ni,nj))
prand_k =np.ones((ni,nj))*prand_k
c_omega_2 =np.ones((ni,nj))*c_omega_2

iter=0

# initialize
u2d,v2d,k2d,om2d,eps2d,vis2d,dist=modify_init(u2d,v2d,k2d,om2d,eps2d,vis2d)

# read data from restart
if restart: 
   u2d,v2d,p2d,k2d,om2d,eps2d,vis2d,ap2d_vel= read_restart_data()

k2d=np.maximum(k2d,1e-6)

u2d_face_w,u2d_face_s=compute_face_phi(u2d,u_bc_west,u_bc_east,u_bc_south,u_bc_north,\
    u_bc_west_type,u_bc_east_type,u_bc_south_type,u_bc_north_type)
v2d_face_w,v2d_face_s=compute_face_phi(v2d,v_bc_west,v_bc_east,v_bc_south,v_bc_north,\
    v_bc_west_type,v_bc_east_type,v_bc_south_type,v_bc_north_type)
p2d_face_w,p2d_face_s=compute_face_phi(p2d,p_bc_west,p_bc_east,p_bc_south,p_bc_north,\
    p_bc_west_type,p_bc_east_type,p_bc_south_type,p_bc_north_type)


if not cyclic_x:
   u_bc_west,v_bc_west,k_bc_west,om_bc_west,eps_bc_west,u2d_face_w,convw = modify_inlet()

# initialize
if pinn:
   prand_k, c_k, c_omega_2 = modify_PINN(prand_k, c_k, c_omega_2)


convw,convs=conv(u2d,v2d,p2d_face_w,p2d_face_s)

iter=0

if kom: 
   urf_temp=urfvis # no under-relaxation
   urfvis=1
   vis2d=vist_kom(vis2d,k2d,om2d)
   urfvis=urf_temp

if keps: 
   urf_temp=urfvis # no under-relaxation
   urfvis=1
   ueps=(eps2d*viscos)**0.25
   ystar=ueps*dist/viscos
   rt=k2d**2/eps2d/viscos
   fdampf2=((1-np.exp(-ystar/3.1))**2)*(1.-0.3*np.exp(-(rt/6.5)**2))
   fmu2d=((1-np.exp(-ystar/14))**2)*(1+5/rt**0.75*np.exp(-(rt/200)**2))
   fmu2d=np.minimum(fmu2d,1)


   vis2d=vist_keps(vis2d,k2d,eps2d,fmu2d)
   urfvis=urf_temp

# find max index
#sumax=np.max(su2d.flatten())
#print('[i,j,k]', np.where(su2d == np.amax(su2d)) 

residual_u=0
residual_v=0
residual_p=0
residual_k=0
residual_om=0
residual_eps=0

######################### start of global iteration process #############################

for iter in range(0,abs(maxit)):

      start_time_iter = time.time()
# coefficients for velocities
      start_time = time.time()
# conpute inlet fluc
      if iter == 0:
         if not cyclic_x:
            u_bc_west,v_bc_west,k_bc_west,om_bc_west,eps_bc_west,u2d_face_w,convw = modify_inlet()
      if earsm:
         uu2d,vv2d,ww2d,uv2d,vis2d_earsm=calc_earsm(k2d,om2d,u2d_face_w,u2d_face_s,v2d_face_w,v2d_face_s,\
            uu2d,uv2d,vv2d,ww2d,vis2d_earsm)
         aw2d,ae2d,as2d,an2d,su2d,sp2d=coeff(convw,convs,vis2d_earsm,1,scheme)
      else:
         if scheme == 'm':
             aw2d,ae2d,as2d,an2d,su2d_local =coeff_m(convw,convs,vis2d,u2d,1)
             su2d=su2d+su2d_local
         else:
             aw2d,ae2d,as2d,an2d,su2d,sp2d=coeff(convw,convs,vis2d,1,scheme)
      if earsm:
         uu2d_face_w,uu2d_face_s=compute_face_phi(uu2d,uu_bc_west,uu_bc_east,uu_bc_south,uu_bc_north,\
            uu_bc_west_type,uu_bc_east_type,uu_bc_south_type,uu_bc_north_type)
         uv2d_face_w,uv2d_face_s=compute_face_phi(uv2d,uv_bc_west,uv_bc_east,uv_bc_south,uv_bc_north,\
            uv_bc_west_type,uv_bc_east_type,uv_bc_south_type,uv_bc_north_type)
         vv2d_face_w,vv2d_face_s=compute_face_phi(vv2d,vv_bc_west,vv_bc_east,vv_bc_south,vv_bc_north,\
            vv_bc_west_type,vv_bc_east_type,vv_bc_south_type,vv_bc_north_type)

# u2d
# boundary conditions for u2d
      su2d,sp2d=bc(su2d,sp2d,u_bc_west,u_bc_east,u_bc_south,u_bc_north, \
                   u_bc_west_type,u_bc_east_type,u_bc_south_type,u_bc_north_type)
      su2d,sp2d,ap2d=calcu(su2d,sp2d,p2d_face_w,p2d_face_s,uu2d_face_w,uu2d_face_s,uv2d_face_w,uv2d_face_s)

      if maxit > 0:
         u2d,residual_u=solve_2d(u2d,aw2d,ae2d,as2d,an2d,su2d,ap2d,convergence_limit_u,nsweep_vel,solver_vel)
      print(f"{'time u: '}{time.time()-start_time:.2e}")

      start_time = time.time()
# v2d
# boundary conditions for v2d
      su2d,sp2d=bc(su2d,sp2d,v_bc_west,v_bc_east,v_bc_south,v_bc_north, \
                   v_bc_west_type,v_bc_east_type,v_bc_south_type,v_bc_north_type)
      su2d,sp2d,ap2d,ap2d_vel=calcv(su2d,sp2d,p2d_face_w,p2d_face_s,uv2d_face_w,uv2d_face_s,vv2d_face_w,vv2d_face_s)
      if scheme == 'm':
         aw2d,ae2d,as2d,an2d,su2d_local =coeff_m(convw,convs,vis2d,v2d,1)
         su2d=su2d+su2d_local
      if maxit > 0:
         v2d,residual_v=solve_2d(v2d,aw2d,ae2d,as2d,an2d,su2d,ap2d,convergence_limit_v,nsweep_vel,solver_vel)
      print(f"{'time v: '}{time.time()-start_time:.2e}")

      start_time = time.time()
# pp2d
      convw,convs=conv(u2d,v2d,p2d_face_w,p2d_face_s)
      if not cyclic_x:
         convw=modify_outlet(convw)
      aw2d,ae2d,as2d,an2d,su2d,ap2d=calcp(pp2d,ap2d_vel)
      pp2d=np.zeros((ni,nj))
      if maxit > 0:
         pp2d,residual_p=solve_2d(pp2d,aw2d,ae2d,as2d,an2d,su2d,ap2d,convergence_limit_pp,nsweep_pp,solver_pp)

# correct u, v, w, p
      convw,convs,p2d,u2d,v2d,su2d= correct_u_v_p(u2d,v2d,p2d)
      convw=modify_outlet(convw)

# continuity error
      su2d=convw[0:-1,:]-np.roll(convw[0:-1,:],-1,axis=0)+convs[:,0:-1]-np.roll(convs[:,0:-1],-1,axis=1)
      residual_pp=abs(np.sum(su2d))

      print(f"{'time pp: '}{time.time()-start_time:.2e}")

      u2d_face_w,u2d_face_s=compute_face_phi(u2d,u_bc_west,u_bc_east,u_bc_south,u_bc_north,\
        u_bc_west_type,u_bc_east_type,u_bc_south_type,u_bc_north_type)
      v2d_face_w,v2d_face_s=compute_face_phi(v2d,v_bc_west,v_bc_east,v_bc_south,v_bc_north,\
        v_bc_west_type,v_bc_east_type,v_bc_south_type,v_bc_north_type)
      p2d_face_w,p2d_face_s=compute_face_phi(p2d,p_bc_west,p_bc_east,p_bc_south,p_bc_north,\
        p_bc_west_type,p_bc_east_type,p_bc_south_type,p_bc_north_type)

      start_time = time.time()

      if kom: 

         if pinn: 
            prand_k, c_k, c_omega_2 = modify_PINN(prand_k, c_k, c_omega_2)
            prand_omega = np.minimum(2*prand_k,2)
         if not earsm:
            vis2d=vist_kom(vis2d,k2d,om2d)
# coefficients
         start_time = time.time()
         if earsm:
            aw2d,ae2d,as2d,an2d,su2d,sp2d=coeff(convw,convs,vis2d_earsm,prand_k,scheme_turb)
         else:
            if scheme == 'm':
                aw2d,ae2d,as2d,an2d,su2d_local =coeff_m(convw,convs,vis2d,k2d,prand_k)
                su2d=su2d+su2d_local
            else:
                aw2d,ae2d,as2d,an2d,su2d,sp2d=coeff(convw,convs,vis2d,prand_k,scheme_turb)

# boundary conditions for k2d
         su2d,sp2d=bc(su2d,sp2d,k_bc_west,k_bc_east,k_bc_south,k_bc_north, \
                   k_bc_west_type,k_bc_east_type,k_bc_south_type,k_bc_north_type)
         su2d,sp2d,gen,ap2d=calck(su2d,sp2d,k2d,om2d,vis2d,u2d_face_w,u2d_face_s,v2d_face_w,v2d_face_s)

         aw2d,ae2d,as2d,an2d,ap2d,su2d,sp2d=fix_k()

         if maxit > 0:
            k2d,residual_k=solve_2d(k2d,aw2d,ae2d,as2d,an2d,su2d,ap2d,convergence_limit_k,nsweep_turb,solver_turb)
         k2d=np.maximum(k2d,1e-10)
         print(f"{'time k: '}{time.time()-start_time:.2e}")

         start_time = time.time()
# omega
# boundary conditions for om2d
         if earsm:
            aw2d,ae2d,as2d,an2d,su2d,sp2d=coeff(convw,convs,vis2d_earsm,prand_omega,scheme_turb)
         else:
            if scheme == 'm':
                aw2d,ae2d,as2d,an2d,su2d_local =coeff_m(convw,convs,vis2d,om2d,prand_omega)
                su2d=su2d+su2d_local
            else:
                aw2d,ae2d,as2d,an2d,su2d,sp2d=coeff(convw,convs,vis2d,prand_omega,scheme_turb)
         su2d,sp2d=bc(su2d,sp2d,om_bc_west,om_bc_east,om_bc_south,om_bc_north,\
                   om_bc_west_type,om_bc_east_type,om_bc_south_type,om_bc_north_type)
         su2d,sp2d,ap2d= calcom(su2d,sp2d,om2d,gen)

         aw2d,ae2d,as2d,an2d,ap2d,su2d,sp2d=fix_omega()

         if maxit > 0:
            om2d,residual_om=solve_2d(om2d,aw2d,ae2d,as2d,an2d,su2d,ap2d,convergence_limit_om,nsweep_turb,solver_turb)
         om2d=np.maximum(om2d,1e-10)

         print(f"{'time omega: '}{time.time()-start_time:.2e}")
      if keps: 

         vis2d=vist_keps(vis2d,k2d,eps2d,fmu2d)
# coefficients
         start_time = time.time()
         if scheme == 'm':
            aw2d,ae2d,as2d,an2d,su2d_local =coeff_m(convw,convs,vis2d,k2d,prand_k)
            su2d=su2d+su2d_local
         else:
            aw2d,ae2d,as2d,an2d,su2d,sp2d=coeff(convw,convs,vis2d,prand_k,scheme_turb)

# boundary conditions for k2d
         su2d,sp2d=bc(su2d,sp2d,k_bc_west,k_bc_east,k_bc_south,k_bc_north, \
                   k_bc_west_type,k_bc_east_type,k_bc_south_type,k_bc_north_type)
         su2d,sp2d,gen,ap2d=calck_keps(su2d,sp2d,k2d,eps2d,vis2d,u2d_face_w,u2d_face_s,v2d_face_w,v2d_face_s)

         aw2d,ae2d,as2d,an2d,ap2d,su2d,sp2d=fix_k()

         if maxit > 0:
            k2d,residual_k=solve_2d(k2d,aw2d,ae2d,as2d,an2d,su2d,ap2d,convergence_limit_k,nsweep_turb,solver_turb)
         k2d=np.maximum(k2d,1e-10)
         print(f"{'time k: '}{time.time()-start_time:.2e}")

         start_time = time.time()
# boundary conditions for eps2d
         if scheme == 'm':
            aw2d,ae2d,as2d,an2d,su2d_local =coeff_m(convw,convs,vis2d,eps2d,prand_eps)
            su2d=su2d+su2d_local
         else:
            aw2d,ae2d,as2d,an2d,su2d,sp2d=coeff(convw,convs,vis2d,prand_eps,scheme_turb)
         su2d,sp2d=bc(su2d,sp2d,eps_bc_west,eps_bc_east,eps_bc_south,eps_bc_north,\
                   eps_bc_west_type,eps_bc_east_type,eps_bc_south_type,eps_bc_north_type)
         su2d,sp2d,ap2d, fmu2d = calceps(su2d,sp2d,eps2d,gen)

         aw2d,ae2d,as2d,an2d,ap2d,su2d,sp2d=fix_eps()

         if maxit > 0:
            eps2d,residual_eps=solve_2d(eps2d,aw2d,ae2d,as2d,an2d,su2d,ap2d,convergence_limit_eps,nsweep_turb,solver_turb)
         eps2d=np.maximum(eps2d,1e-10)

         print(f"{'time eps: '}{time.time()-start_time:.2e}")

# scale residuals
      residual_u=residual_u/resnorm_vel
      residual_v=residual_v/resnorm_vel
      residual_pp=residual_p/resnorm_p
      residual_k=residual_k/resnorm_vel**2
      residual_om=residual_om/resnorm_vel
      residual_eps=residual_eps/resnorm_vel

      resmax=np.max([residual_u ,residual_v,residual_pp,residual_k,residual_om])

      print(f"\n{'--iter:'}{iter:d}, {'max residual:'}{resmax:.2e}, {'u:'}{residual_u:.2e}\
, {'v:'}{residual_v:.2e}, {'cont:'}{residual_pp:.2e}, {'k:'}{residual_k:.2e}\
, {'om:'}{residual_om:.2e}, {'eps:'}{residual_eps:.2e}\n")

      print(f"\n{'monitor iteration:'}{iter:4d}, {'u:'}{u2d[imon,jmon]: .2e}\
, {'v:'}{v2d[imon,jmon]: .2e}, {'p:'}{p2d[imon,jmon]: .2e}\
, {'k:'}{k2d[imon,jmon]: .2e}, {'om:'}{om2d[imon,jmon]: .2e}, {'eps:'}{eps2d[imon,jmon]: .2e}\n")



      vismax=np.max(vis2d.flatten())/viscos
      umax=np.max(u2d.flatten())
      ommin=np.min(om2d.flatten())
      epsmin=np.min(eps2d.flatten())
      kmin=np.min(k2d.flatten())
      kmax=np.max(k2d.flatten())

      print('kmax',kmax)

      print(f"\n{'---iter: '}{iter:2d}, {'umax: '}{umax:.2e},{' vismax: '}{vismax:.2e}, {'kmin: '}{kmin:.2e}, {'ommin: '}{ommin:.2e}, {'epsmmin: ' }{epsmin:.2e}\n")

      print(f"{'time one iteration: '}{time.time()-start_time_iter:.2e}")

      if resmax < sormax and iter > 0: 

         break

######################### end of global iteration process #############################
      
# save data for restart
if save:
   save_data(u2d,v2d,p2d,k2d,om2d,eps2d,vis2d,ap2d_vel)

if vtk:
   itstep=ntstep
   save_vtk()

print('program reached normal stop')

