1. Creating the mesh
The first step consists in creating a geometry and meshing it, as well as adding a so-called wake to solve the flow using a potential formulation.
1.1 Meshing the geometry
Currently, the mesh can be given directly or the geometry file can be given with options. In both cases, the only supported format is the native gmsh format (.msh or .geo).
msh = gmsh.MeshLoader('path/to/mshfile','caller/path').execute()
# or
pars = {key : value, ...}
msh = gmsh.MeshLoader('path/to/geofile','caller/path').execute(**pars)
where the 'caller/path'
and 'path/to/gmshfile'
will be concatenated to retrieve the gmsh file. In the second scenario, pars
is a python dictionary containing a the parameters to be passed to gmsh. Note that the key is ALWAYS a string and that, if the first character is a - (dash), the key will be considered as a gmsh option instead.
This returns the msh
(tbox::MshData C++ class) object containing the mesh data structure that can be further used.
1.2 Adding the wake
For potential flows to be lifting, the potential value must be multivalued along a cut inside the computational domain. For aerodynamics applications, this cut is referred to as wake and extends from the trailing edge of any lifting configurations to the downstream boundary of the domain. In order to assign a discontinuous value to the potential on the wake, all nodes belonging to this surface must be duplicated. This is done by using the tbox::MshCrack C++ class,
mshCrck = tbox.MshCrack(msh, dim)
mshCrck.setCrack('wake')
for bnd in ['boundary_0', 'boundary_1', ...]:
mshCrck.addBoundary(bnd)
where dim
is the dimension of the problem (2 or 3), 'wake'
is the name of the physical group of the mesh containing the wake surface, and 'boundary_0'
, ... are the name of the physical groups of the mesh having elements touching the wake surface. These groups typically include the fluid domain, the airfoil, wing or and fuselage, and the downstream and symmetry boundaries. mshCrck
will automatically create a new physical group named 'wake_'
. Two methods are implemented to detect if the elements touching the wake are above or below it. Either the physical groups 'boundary'
and 'boundary_'
are present in the mesh, and the algorithm will consider that the elements of 'boundary'
are above and that those of 'boundary_'
are below, or only the group 'boundary'
is present, and the algorithm will try to detect automatically were the elements are located with respect to the wake surface. For surface boundaries (of dimension dim-1
), this is done by comparing the orientation of the normal of the closest wake element to the vector linking the center of gravity of the closest wake element to the considered element. The same process is applied to treat volume boundaries (of dimension dim
), except that the algorithm will first check whether the nodes of the volume also belong to a surface located below the wake. The wake normals must be pointing in the positive (y in 2D and z in 3D) direction. Note that, in the first case, the group 'boundary_'
will be merged within the group 'boundary'
.
For a 3D flow, the potential at the wake tip (i.e., the edge of the wake downstream of the wingtip) will be continuous. This is enforced by not duplicating the nodes in this line,
mshCrck.setExcluded('wakeTip')
where 'wakeTip'
is the name of the physical group of the mesh containing the wake free edge. The utility can now be executed, and directly deleted to save memory,
mshCrck.run()
del mshCrck
Since the execution will duplicate the nodes on the wake surface and modify the elements touching the wake, the existing mesh file must be overwritten. This is accomplished by using the tbox::GmshExport class,
tbox.GmshExport(msh).save(msh.name)
1.3 Creating a mesh morpher
A mesh morpher can be used to deform the volume mesh in order to track the motion of a given boundary surface of that mesh. This is typically required for applications where a body immersed in the fluid can move or deform, such as aerostructural or optimization calculations. The morpher is instantiated using the tbox::MshDeform C++ class as,
morpher = tbox.MshDeform(msh, linsol, dim, nthrds=1, vrb=1)
where nthrds
controls the number of threads, vrb
controls the verbosity level and linsol
is a linear solver. In order to solve the linear elasticity equations to deform the mesh, an ILU preconditioned GMRES usually works well as linear solver: linsol = tbox.Gmres(1, 1e-6, 30, 1e-8)
. More information about the available linear solvers can be found here.
The grid associated to the volume (fluid), and the fixed, moving and internal boundaries are added using
morpher.setField('fld') # volume grid
for fxd in ['fxd_0', 'fxd_1', ...]:
morpher.addFixed(fxd) # fixed boundary
morpher.addMoving('mov') # moving boundary
morpher.addInternal('wk', 'wk_') # pair of internal boundaries
where 'fld'
is the name of the physical group of the mesh contaning the volume grid (fluid), 'fxd_i'
is the name of the ith physical group of the mesh contaning a fixed boundary, 'mov'
is the name of the physical group of the mesh contaning the moving boundary (typically, a body), and 'wk','wk_'
is the pair of names of the physical groups of the mesh contaning the internal boundaries (typically, a wake). Note that several pair of internal boundaries can be added by repeating the last instruction.
For 3D computations, if the problem contains a symmetry plane, it can be added as,
morpher.setSymmetry('sym', 1)
where 'sym'
is the name of the physical group of the mesh contaning the symmetry boundary, and 1
indiciates to block the second degree of freedom (y-direction).
Finally, the morpher is initialized using,
morpher.initialize()
and can be latter executed using,
morpher.savePos() # save the initial position of the nodes of the moving boudnary
# set the new positions of the nodes of the moving boundary, and then call
morpher.deform() # perform the volume deformation