-
Paul Dechamps authoredPaul Dechamps authored
DSolversInterface.py 20.22 KiB
import numpy as np
import blast
import fwk
import tbox
from blast.interfaces.DDataStructure import Group
class SolversInterface:
"""
Interface for solvers.
Attributes:
----------
vSolver : object
Viscous solver object.
cfg : dict
Configuration dictionary.
iBnd : list
Inviscid boundary groups.
vBnd : list
Viscous boundary groups.
interpolator : object
Interpolator object.
deltaStarExt : list
Displacement thickness for the quasi-simultaneous interaction law.
xxExt : list
Surface local coordinates for the quasi-simultaneous interaction law.
vtExt : list
Velocity at the edge of the boundary layer for the quasi-simultaneous interaction law.
vinit : bool
Flag used to check if the viscous solver has been initialized.
"""
def __init__(self, _vSolver, _cfg):
"""
Initialize the SolversInterface.
Parameters:
----------
_vSolver : object
Viscous solver object.
_cfg : dict
Configuration dictionary.
"""
self.vSolver = _vSolver
self.cfg = _cfg
if 'nSections' not in self.cfg:
self.cfg['nSections'] = len(self.cfg['sections'])
# Initialize data structures.
self.iBnd = [Group('iWing' if iReg == 0 else 'iWake', self.getnDim()) for iReg in range(2)]
self.vBnd = [[Group('vAirfoil'+str(k) if iReg == 0 else 'vWake'+str(k), self.getnDim()) for iReg in range(2)] for k in range(self.cfg['nSections'])]
# Initialize interpolator and meshes
self.getWing()
if self.cfg['interpolator'] == 'Matching':
from blast.interfaces.interpolators.DMatchingInterpolator import MatchingInterpolator as interp
# Initialize viscous structures for matching computations
for iSec in range(len(self.vBnd)):
for iReg, reg in enumerate(self.vBnd[iSec]):
reg.initStructures(self.iBnd[iReg].nPoints)
self.vBnd[iSec][iReg].nodesCoord = self.iBnd[iReg].nodesCoord.copy()
self.vBnd[iSec][iReg].elemsCoord = self.iBnd[iReg].elemsCoord.copy()
self.vBnd[iSec][iReg].connectListNodes = self.iBnd[iReg].connectListNodes.copy()
self.vBnd[iSec][iReg].connectListElems = self.iBnd[iReg].connectListElems.copy()
elif self.cfg['interpolator'] == 'Rbf':
from blast.interfaces.interpolators.DRbfInterpolator import RbfInterpolator as interp
print('Initializing RBF interpolator.')
self.getVWing()
else:
raise RuntimeError('Incorrect interpolator specified in config.')
self.interpolator = interp(self.cfg, self.getnDim())
# Initialize transfer quantities
self.deltaStarExt = [[np.zeros(0) for iReg in range(3)]\
for iSec in range(self.cfg['nSections'])]
self.xxExt = [[np.zeros(0) for iReg in range(3)]\
for iSec in range(self.cfg['nSections'])]
self.vtExt = [[np.zeros(0) for iReg in range(3)]\
for iSec in range(self.cfg['nSections'])]
self.vinit = False
def setViscousSolver(self):
"""Interpolate solution to viscous mesh and sets the inviscid boundary in the viscous solver.
"""
for iSec in range(self.cfg['nSections']):
for i, reg in enumerate(self.sec[iSec]):
if reg.name == 'wake':
iReg = 1
elif reg.name == 'lower' or reg.name == 'upper':
iReg = 0
else:
raise RuntimeError('Invalid region', reg.name)
reg.setMe(self.vBnd[iSec][iReg].getMach(reg.name))
reg.setRhoe(self.vBnd[iSec][iReg].getRho(reg.name))
reg.setVt(self.vBnd[iSec][iReg].getVt(reg.name))
reg.setDeltaStarExt(self.deltaStarExt[iSec][i])
reg.setxxExt(self.xxExt[iSec][i])
reg.setVtExt(self.vtExt[iSec][i])
def getBlowingBoundary(self, couplIter):
"""Get blowing boundary condition from the viscous solver.
args:
----
couplIter: int
Current iteration of the coupling loop.
"""
if couplIter != 0:
for iSec in range(len(self.vBnd)):
for iReg, reg in enumerate(self.vBnd[iSec]):
if 'vWake' in reg.name:
reg.blowingVel = np.asarray(self.sec[iSec][2].blowingVelocity)
elif 'vAirfoil' in reg.name:
reg.blowingVel = np.concatenate((np.flip(np.asarray(self.sec[iSec][0].blowingVelocity)),
np.asarray(self.sec[iSec][1].blowingVelocity)))
for i in range(len(self.sec[iSec])):
self.xxExt[iSec][i] = np.asarray(self.sec[iSec][i].loc)
self.deltaStarExt[iSec][i] = np.asarray(self.sec[iSec][i].deltaStar)
self.vtExt[iSec][i] = np.asarray(self.sec[iSec][i].vt)
def interpolate(self, dir):
"""Interpolate solution between inviscid and viscous meshes.
args:
----
dir: str
Direction of interpolation.
'i2v': inviscid to viscous.
'v2i': viscous to inviscid.
"""
if dir == 'i2v':
self.interpolator.inviscidToViscous(self.iBnd, self.vBnd)
elif dir == 'v2i':
self.interpolator.viscousToInviscid(self.iBnd, self.vBnd)
else:
raise RuntimeError('Invalid direction', dir)
def updateMesh(self):
"""Collect the mesh on blowing boundaries and update the mesh in the viscous solver.
This function does not update connectivity lists
"""
if self.getnDim() != 2:
raise RuntimeError('SolverInterface::updateMesh - Only 2D meshes are supported.')
if self.cfg['interpolator'] != 'Matching':
raise RuntimeError('SolverInterface::updateMesh - Only Matching interpolator is supported.')
meshList = self.getMesh()
# Update the mesh in data structures
for ireg, reg in enumerate(self.iBnd):
for i, row in enumerate(reg.connectListNodes):
for idim in range(self.getnDim()):
reg.nodesCoord[i, idim] = meshList[ireg][row, idim]
# Recompute elements data
for ireg, reg in enumerate(self.iBnd):
for i in range(len(reg.nodesCoord[:,0])-1):
for idim in range(self.getnDim()):
reg.elemsCoord[i, idim] = (reg.nodesCoord[i, idim] + reg.nodesCoord[i+1, idim])/2
# Update the mesh in the data structures
for iSec in range(len(self.vBnd)):
for iReg, reg in enumerate(self.vBnd[iSec]):
self.vBnd[iSec][iReg].nodesCoord = self.iBnd[iReg].nodesCoord.copy()
self.vBnd[iSec][iReg].elemsCoord = self.iBnd[iReg].elemsCoord.copy()
# Set mesh in the viscous solver
for iSec in range(self.cfg['nSections']):
# Compute section chord
xMin = np.min(self.vBnd[iSec][0].nodesCoord[:,0])
xMax = np.max(self.vBnd[iSec][0].nodesCoord[:,0])
chord = xMax - xMin
for reg in self.sec[iSec]:
if reg.name == 'wake':
iReg = 1
elif reg.name == 'lower' or reg.name == 'upper':
iReg = 0
else:
raise RuntimeError('Invalid region', reg.name)
xIdx = 0
if self.getnDim() == 2:
yIdx = 1
zIdx = 2
elif self.getnDim() == 3:
yIdx = 2
zIdx = 1
else:
raise RuntimeError('Incorrect number of dimensions', self.getnDim())
nodeRows = tbox.std_vector_int()
if self.cfg['interpolator'] == 'Matching':
for val in self.vBnd[iSec][iReg].getNodesRow(reg.name):
nodeRows.push_back(int(val))
else:
for i in range(len(self.vBnd[iSec][iReg].getNodesCoord(reg.name, xIdx))):
nodeRows.push_back(int(0))
connectElems = tbox.std_vector_int()
if self.cfg['interpolator'] == 'Matching':
for val in self.vBnd[iSec][iReg].getConnectList(reg.name):
connectElems.push_back(int(val))
else:
for i in range(len(self.vBnd[iSec][iReg].getNodesCoord(reg.name, xIdx))-1):
connectElems.push_back(int(0))
# x, y, z
xv = fwk.std_vector_double()
for val in self.vBnd[iSec][iReg].getNodesCoord(reg.name, xIdx):
xv.push_back(val)
yv = fwk.std_vector_double()
for val in self.vBnd[iSec][iReg].getNodesCoord(reg.name, yIdx):
yv.push_back(val)
zv = fwk.std_vector_double()
for val in self.vBnd[iSec][iReg].getNodesCoord(reg.name, zIdx):
zv.push_back(val)
# Set mesh
reg.setMesh(xv,
yv,
zv,
chord,
xMin,
nodeRows,
connectElems)
def updateStagnation(self):
"""Update the stagnation points in the viscous solver.
"""
for iSec in range(self.cfg['nSections']):
for reg in self.vBnd[iSec]:
reg.computeStagPoint()
def initializeViscousSolver(self):
"""Initialize the viscous sections in the viscous solver.
"""
if self.vinit == True:
return None
self.sec = [[] for _ in range(self.cfg['nSections'])]
self.vSolver.reset()
for i in range(self.cfg['nSections']):
# TODO: Add functionality to initialize sections without wake.
for j in range(3):
if j == 2:
name = "wake"
xtrF = -1
elif j == 1:
name = "lower"
xtrF = self.cfg['xtrF'][j]
elif j ==0:
name = "upper"
xtrF = self.cfg['xtrF'][j]
self.sec[i].append(blast.BoundaryLayer(xtrF, name))
self.vSolver.addSection(i, self.sec[i][j])
self.getInviscidBC()
self.interpolator.inviscidToViscous(self.iBnd, self.vBnd)
# Compute stagnation points
for iSec in range(self.cfg['nSections']):
for reg in self.vBnd[iSec]:
reg.computeStagPoint()
# Set mesh
for iSec in range(self.cfg['nSections']):
# Compute section chord
xMin = np.min(self.vBnd[iSec][0].nodesCoord[:,0])
xMax = np.max(self.vBnd[iSec][0].nodesCoord[:,0])
chord = xMax - xMin
for reg in self.sec[iSec]:
if reg.name == 'wake':
iReg = 1
elif reg.name == 'lower' or reg.name == 'upper':
iReg = 0
else:
raise RuntimeError('Invalid region', reg.name)
xIdx = 0
if self.getnDim() == 2:
yIdx = 1
zIdx = 2
elif self.getnDim() == 3:
yIdx = 2
zIdx = 1
else:
raise RuntimeError('Incorrect number of dimensions', self.getnDim())
nodeRows = tbox.std_vector_int()
if self.cfg['interpolator'] == 'Matching':
for val in self.vBnd[iSec][iReg].getNodesRow(reg.name):
nodeRows.push_back(int(val))
else:
for i in range(len(self.vBnd[iSec][iReg].getNodesCoord(reg.name, xIdx))):
nodeRows.push_back(int(0))
connectElems = tbox.std_vector_int()
if self.cfg['interpolator'] == 'Matching':
for val in self.vBnd[iSec][iReg].getConnectList(reg.name):
connectElems.push_back(int(val))
else:
for i in range(len(self.vBnd[iSec][iReg].getNodesCoord(reg.name, xIdx))-1):
connectElems.push_back(int(0))
# x, y, z
xv = fwk.std_vector_double()
for val in self.vBnd[iSec][iReg].getNodesCoord(reg.name, xIdx):
xv.push_back(val)
yv = fwk.std_vector_double()
for val in self.vBnd[iSec][iReg].getNodesCoord(reg.name, yIdx):
yv.push_back(val)
zv = fwk.std_vector_double()
for val in self.vBnd[iSec][iReg].getNodesCoord(reg.name, zIdx):
zv.push_back(val)
# Set mesh
reg.setMesh(xv,
yv,
zv,
chord,
xMin,
nodeRows,
connectElems)
# External variables
for i, reg in enumerate(self.sec[iSec]):
if reg.name == 'wake':
iReg = 1
elif reg.name == 'lower' or reg.name == 'upper':
iReg = 0
else:
raise RuntimeError('Invalid region', reg.name)
loc = np.zeros(reg.getnNodes())
for iPoint in range(reg.getnNodes()):
loc[iPoint] = reg.loc[iPoint]
self.xxExt[iSec][i] = loc
self.deltaStarExt[iSec][i] = np.zeros(reg.getnNodes())
self.vtExt[iSec][i] = np.zeros(reg.getnNodes())
self.vinit = True
return 0
def getVWing(self):
"""Obtain the nodes' location and row and cg of all elements.
If the mesh is the viscous one, TE nodes are duplicated (by creating a new row).
They exist for the interpolation and for the solution computation.
If not, the nodes are pseudo-duplicated (without changing the row). They therefore
exist only for the interpolation but not for the solution.
"""
data = [np.zeros((0,4)) for _ in range(2)]
lwRows = []
for e in self.cfg['vMsh'].elems:
if e.ptag.name == 'wing' or e.ptag.name == 'wing_'\
or e.ptag.name == 'leadingEdge':
iRegion = 0
elif e.ptag.name == 'wake' or e.ptag.name == 'wake_':
iRegion = 1
else:
continue
# Get nodes
for nw in e.nodes:
if nw.row not in data[iRegion][:,3]:
data[iRegion] = np.vstack((data[iRegion], [nw.pos[0],\
nw.pos[1],\
nw.pos[2],\
nw.row]))
# If wing_ exists, the lower side is identifiable.
if e.ptag.name == 'wing_':
lwRows.append(nw.row)
if len(lwRows) == 0:
raise RuntimeError('Could not identify lower side.')
self.vlwIdx = []
for idx in range(len(data[0][:,3])):
if data[0][idx, 3] in lwRows:
self.vlwIdx.append(idx)
maxRowNb = max(data[0][:,3])
# Initialize viscous strutures
self.cfg['EffSections'] = np.empty(0)
for iReg, reg in enumerate(data):
for iy, y in enumerate(self.cfg['sections']):
# Find nearest value in the mesh
try:
idx = (np.abs(reg[:,1]-y).argmin())
except:
print('Region', reg[:,1], 'cannot be found through\
config input', y)
raise RuntimeError("Could not identify section.\
Possibly a missing tag")
section = reg[idx,1]
if iReg == 0:
self.cfg['EffSections'] = np.append(self.cfg['EffSections'], section)
nodesSec = reg[abs(reg[:,1]-section)<1e-3, :]
# Separate points on upper and lower side
if iReg == 0:
lower_points = []
upper_points = []
for point in nodesSec:
if point[3] in lwRows:
lower_points.append(point)
else:
upper_points.append(point)
# Sort points in selig format
lower_points = sorted(lower_points, key=lambda p: p[0])
upper_points = sorted(upper_points, key=lambda p: p[0], reverse=True)
nodesSec = np.row_stack((upper_points, lower_points))
nodesSec = np.row_stack((nodesSec, [nodesSec[0,0], nodesSec[0,1], nodesSec[0,2], nodesSec[0,3]+maxRowNb]))
else:
nodesSec = nodesSec[nodesSec[:,0].argsort()]
# Compute cg pos on the section (!= cg of 2D elems on the surface)
cgSec = np.zeros((len(nodesSec[:,0])-1, 4))
for i in range(len(cgSec)):
cgSec[i,:3] = (nodesSec[i,:3] + nodesSec[i+1,:3])/2
cgSec[i, 3] = i
self.vBnd[iy][iReg].initStructures(nodesSec.shape[0])
self.vBnd[iy][iReg].nodesCoord = nodesSec
self.vBnd[iy][iReg].elemsCoord = cgSec
# Inviscid solver methods
def run()->int:
"""Run inviscid solver
"""
raise NotImplementedError('SolverInterface - run not implemented')
def reset()->None:
"""Reset solution
"""
raise NotImplementedError('SolverInterface - reset not implemented')
def save(self)->None:
"""Save solution
"""
raise NotImplementedError('SolverInterface - save not implemented')
def writeCp(self)->None:
"""Write pressure coefficient
"""
raise NotImplementedError('SolverInterface - writeCp not implemented')
# Inviscid solver properties
def getVerbose(self)->int:
"""Get verbose
"""
raise NotImplementedError('SolverInterface - getVerbose not implemented')
# Flow properties and aerodynamic coefficients
def getAoA(self)->float:
"""Get angle of attack
"""
raise NotImplementedError('SolverInterface - getAoA not implemented')
def getCl(self)->float:
"""Get lift coefficient
"""
raise NotImplementedError('SolverInterface - getCl not implemented')
def getCd(self)->float:
"""Get inviscid drag coefficient
"""
raise NotImplementedError('SolverInterface - getCd not implemented')
def getCm(self)->float:
"""Get moment coefficient
"""
raise NotImplementedError('SolverInterface - getCm not implemented')
def getnDim(self)->int:
"""Get number of dimensions
"""
raise NotImplementedError('SolverInterface - getnDim not implemented')
def getWing(self)->None:
"""Get wing mesh
"""
raise NotImplementedError('SolverInterface - getWing not implemented')
def getMesh(self)->list:
"""Get mesh
"""
raise NotImplementedError('SolverInterface - getMesh not implemented')
def getInviscidBC(self)->None:
"""Get inviscid boundary condition
"""
raise NotImplementedError('SolverInterface - getInviscidBC not implemented')
def setBlowingVelocity(self)->None:
"""Set blowing velocity
"""
raise NotImplementedError('SolverInterface - setBlowingVelocity not implemented')
def getnNodesDomain(self)->int:
"""Get number of nodes in domain
"""
raise NotImplementedError('SolverInterface - getnNodesDomain not implemented')