Source code for pyLOM.mesh

#!/usr/bin/env python
#
# pyLOM, mesh.
#
# Mesh class, to organize data.
#
# Last rev: 26/01/2023
from __future__ import print_function, division

import os, numpy as np

from .       import inp_out as io
from .vmmath import cellCenters, normals
from .utils  import cr_nvtx as cr, mem, raiseError, mpi_reduce


ALYA2ELTYP = {
	# Linear cells
	-1 :  0, # Empty cell
	 3 :  1, # Line element
	10 :  2, # Triangular cell
	12 :  3, # Quadrangular cell
	30 :  4, # Tetrahedral cell
	37 :  5, # Hexahedron
	34 :  6, # Linear prism
	32 :  7, # Pyramid
	# Quadratic, isoparametric cells
	39 : 15, # HEX27
	# Lagrangian cells
	40 : 25, # HEX64
}

ELTYPE2VTK = {
	 0 : 0,  # Empty cell
	 2 : 5,  # Triangular cell
	 3 : 9,  # Quadrangular cell
	 4 : 10, # Tetrahedral cell
	 6 : 13, # Linear prism
	 7 : 14, # Pyramid
	 5 : 12, # Hexahedron
	25 : 72, # Lagrangian Hexahedron
}

ELTYPE2ENSI = {
	2 : 'tria3',  # Triangular cell
	3 : 'quad4',  # Quadrangular cell
	4 : 'tetra4', # Tetrahedral cell
	6 : 'penta6', # Linear prism
	5 : 'hexa8',  # Hexahedron
}

MTYPE2ID = {
	'STRUCT2D' : 1,
	'STRUCT3D' : 2,
	'UNSTRUCT' : 3,
}

ID2MTYPE = {
	1 : 'STRUCT2D',
	2 : 'STRUCT3D',
	3 : 'UNSTRUCT',
}

[docs] class Mesh(object): r''' The Mesh class wraps the mesh details of the case. '''
[docs] def __init__(self,mtype,xyz,connectivity,eltype,cellOrder,pointOrder,ptable): r''' Class constructor ''' self._type = mtype self._xyz = xyz self._xyzc = None self._normal = None self._conec = connectivity self._eltype = eltype self._cellO = cellOrder self._pointO = pointOrder self._ptable = ptable
[docs] def __str__(self): r''' String representation ''' s = 'Mesh (%s) of %d nodes and %d elements:\n' % (self.type,self.npoints,self.ncells) s += ' > xyz - max = ' + str(np.nanmax(self._xyz,axis=0)) + ', min = ' + str(np.nanmin(self._xyz,axis=0)) + '\n' return s
[docs] def find_point(self,xyz:np.array): r''' Return all the points where self._xyz == xyz ''' return np.where(np.all(self._xyz == xyz,axis=1))[0]
[docs] def find_cell(self,eltype:int): r''' Return all the elements where self._elemList == elem ''' return np.where(np.all(self._eltype == eltype))[0]
[docs] def find_point_in_cell(self,inode:int): r''' Return all the elements where the node is ''' return np.where(np.any(np.isin(self._conec,inode),axis=1))[0]
[docs] def size(self,pointData:bool): r''' Return the size accoding to the type of data ''' return self.npoints if pointData else self.ncells
[docs] @cr('Mesh.cellcenters') def cellcenters(self): r''' Computes and returns the cell centers ''' if self.type == 'STRUCT2D': # Recover unique X, Y coordinates x = np.unique(self.x) y = np.unique(self.y) # Compute cell centers xc = x[:-1] + np.diff(x)/2. yc = y[:-1] + np.diff(y)/2. # Build xyzc xx, yy = np.meshgrid(xc,yc,indexing='ij') xyzc = np.zeros((self.ncells,2),dtype=np.double) xyzc[:,0] = xx.reshape((self.ncells,),order='C') xyzc[:,1] = yy.reshape((self.ncells,),order='C') # Connectivity for a 3D mesh if self.type == 'STRUCT3D': # Recover unique X, Y, Z coordinates x = np.unique(self.x) y = np.unique(self.y) z = np.unique(self.z) # Compute cell centers xc = x[:-1] + np.diff(x)/2. yc = y[:-1] + np.diff(y)/2. zc = z[:-1] + np.diff(z)/2. # Build xyzc xx, yy, zz = np.meshgrid(xc,yc,zc,indexing='ij') xyzc = np.zeros((self.ncells,3),dtype=np.double) xyzc[:,0] = xx.reshape((self.ncells,),order='C') xyzc[:,1] = yy.reshape((self.ncells,),order='C') xyzc[:,2] = zz.reshape((self.ncells,),order='C') # Connectivity for a unstructured mesh if self.type == 'UNSTRUCT': xyzc = cellCenters(self._xyz,self._conec) return xyzc
[docs] @cr('Mesh.reshape') def reshape_var(self,var:str,info:dict): r''' Reshape a variable according to the mesh ''' # Obtain number of points from the mesh npoints = self.size(info['point']) # Only reshape the variable if ndim > 1 out = np.ascontiguousarray(var.reshape((npoints,info['ndim']),order='C') if info['ndim'] > 1 else var) # Build 3D vector in case of 2D array if self.type == 'STRUCT2D' and info['ndim'] == 2: out = np.hstack((out,np.zeros((npoints,1)))) return out
[docs] @cr('Mesh.save') def save(self,fname:str,**kwargs): r''' Store the mesh in various formats. ''' # Guess format from extension fmt = os.path.splitext(fname)[1][1:] # skip the . # Pickle format if fmt.lower() == 'pkl': io.pkl_save(fname,self) # H5 format if fmt.lower() == 'h5': # Set default parameters if not 'mode' in kwargs.keys(): kwargs['mode'] = 'w' if not os.path.exists(fname) else 'a' if not 'mpio' in kwargs.keys(): kwargs['mpio'] = True if not 'nopartition' in kwargs.keys(): kwargs['nopartition'] = False # Save io.h5_save_mesh(fname,self.type,self.xyz,self.connectivity,self.eltype,self.cellOrder,self.pointOrder,self.partition_table,**kwargs)
[docs] @classmethod @cr('Mesh.load') def load(cls,fname:str,**kwargs): r''' Load a mesh from various formats ''' # Guess format from extension fmt = os.path.splitext(fname)[1][1:] # skip the . # Pickle format if fmt.lower() == 'pkl': return io.pkl_load(fname) # H5 format if fmt.lower() == 'h5': if not 'mpio' in kwargs.keys(): kwargs['mpio'] = True mtype, xyz, conec, eltype, cellO, pointO, ptable = io.h5_load_mesh(fname,**kwargs) return cls(mtype,xyz,conec,eltype,cellO,pointO,ptable) raiseError('Cannot load file <%s>!'%fname)
[docs] @classmethod @cr('Mesh.new_struct2D') def new_struct2D(cls,nx:int,ny:int,x:float,y:float,dimsx:float,dimsy:float,ptable=None): xyz = _struct2d_compute_xyz(nx,ny,x,y,dimsx,dimsy) conec = _struct2d_compute_conec(nx,ny,xyz) eltype = 3*np.ones(((nx-1)*(ny-1),),np.uint8) cellO = np.arange((nx-1)*(ny-1),dtype=np.int32) pointO = np.arange(nx*ny,dtype=np.int32) return cls('STRUCT2D',xyz,conec,eltype,cellO,pointO,ptable)
[docs] @classmethod @cr('Mesh.new_struct3D') def new_struct3D(cls,nx:int,ny:int,nz:int,x:float,y:float,z:float,dimsx:float,dimsy:float,dimsz:float,ptable=None): xyz = _struct3d_compute_xyz(nx,ny,nz,x,y,z,dimsx,dimsy,dimsz) conec = _struct3d_compute_conec(nx,ny,nz,xyz) eltype = 5*np.ones(((nx-1)*(ny-1)*(nz-1),),np.uint8) cellO = np.arange((nx-1)*(ny-1)*(nz-1),dtype=np.int32) pointO = np.arange(nx*ny*nz,dtype=np.int32) return cls('STRUCT3D',xyz,conec,eltype,cellO,pointO,ptable)
[docs] @classmethod @cr('Mesh.from_pyQvarsi') def from_pyQvarsi(cls,mesh,ptable=None,sod:bool=False): ''' Create the mesh structure from a pyQvarsi mesh structure ''' eltype = np.array([ALYA2ELTYP[t] for t in mesh.eltype_linear],np.uint8) return cls('UNSTRUCT',mesh.xyz,mesh.connectivity_vtk if sod else mesh.connectivity,eltype,mesh.leinv_linear,mesh.lninv,ptable)
@property def type(self): return self._type @property def npoints(self): return self._xyz.shape[0] @property def npointsG(self): return mpi_reduce(self.npoints,op='sum',all=True) @property def npointsG2(self): if self.pointOrder.shape[0] > 0: npoints = self.pointOrder.max() else: npoints = 0 return mpi_reduce(npoints,op='max',all=True) + 1 @property def ndim(self): return self._xyz.shape[1] @property def ncells(self): return self._eltype.shape[0] @property def ncellsG(self): return mpi_reduce(self.ncells,op='sum',all=True) @property def ncellsG2(self): if self.cellOrder.shape[0] > 0: ncells = self.cellOrder.max() else: ncells = 0 return mpi_reduce(ncells,op='max',all=True) + 1 @property def nnodcell(self): return self._conec.shape[1] @property def xyz(self): return self._xyz @property def x(self): return self._xyz[:,0] @property def y(self): return self._xyz[:,1] @property def z(self): return self._xyz[:,2] @property def xyzc(self): if self._xyzc is None: self._xyzc = self.cellcenters() return self._xyzc @property def normal(self): if self._normal is None: self._normal = normals(self._xyz,self._conec) return self._normal @property def connectivity(self): return self._conec @property def cellOrder(self): return self._cellO @property def pointOrder(self): return self._pointO @property def partition_table(self): return self._ptable @partition_table.setter def partition_table(self,value): self._ptable = value @property def eltype(self): return self._eltype @property def eltype2VTK(self): return np.array([ELTYPE2VTK[t] for t in self._eltype],np.uint8) @property def eltype2ENSI(self): return ELTYPE2ENSI[self._eltype[0]]
def _struct2d_compute_xyz(nx:int,ny:int,x:float,y:float,dimsx:float,dimsy:float): r''' Compute points for a 2D structured mesh ''' if x is None: dx = (dimsx[1] - dimsx[0])/(nx - 1.) x = dx*np.arange(nx) + dimsx[0] if y is None: dy = (dimsy[1] - dimsy[0])/(ny - 1.) y = dy*np.arange(ny) + dimsy[0] xx, yy = np.meshgrid(x,y,indexing='ij') xy = np.zeros((nx*ny,3),dtype=np.double) xy[:,0] = xx.reshape((nx*ny,),order='C') xy[:,1] = yy.reshape((nx*ny,),order='C') return xy def _struct2d_compute_conec(nx:int,ny:int,xyz:np.array): r''' Compute connectivity for a 2D structured mesh ''' # Obtain the ids idx = np.lexsort((xyz[:,1],xyz[:,0])) idx2 = idx.reshape((nx,ny)) # Create connectivity array conec = np.zeros(((nx-1)*(ny-1),4),dtype=np.int32) conec[:,0] = idx2[:-1,:-1].ravel() conec[:,1] = idx2[:-1,1:].ravel() conec[:,2] = idx2[1:,1:].ravel() conec[:,3] = idx2[1:,:-1].ravel() return conec def _struct3d_compute_xyz(nx:int,ny:int,nz:int,x:float,y:float,z:float,dimsx:float,dimsy:float,dimsz:float): r''' Compute points for a 2D structured mesh ''' if x is None: dx = (dimsx[1] - dimsx[0])/(nx - 1.) x = dx*np.arange(nx) + dimsx[0] if y is None: dy = (dimsy[1] - dimsy[0])/(ny - 1.) y = dy*np.arange(ny) + dimsy[0] if z is None: dz = (dimsz[1] - dimsz[0])/(nz - 1.) z = dz*np.arange(nz) + dimsz[0] xx, yy, zz = np.meshgrid(x,y,z,indexing='ij') xyz = np.zeros((nx*ny*nz,3),dtype=np.double) xyz[:,0] = xx.reshape((nx*ny*nz,),order='C') xyz[:,1] = yy.reshape((nx*ny*nz,),order='C') xyz[:,2] = zz.reshape((nx*ny*nz,),order='C') return xyz def _struct3d_compute_conec(nx:int,ny:int,nz:int,xyz:np.array): r''' Compute connectivity for a 2D structured mesh ''' # Obtain the ids idx = np.lexsort((xyz[:,2],xyz[:,1],xyz[:,0])) idx2 = idx.reshape((nx,ny,nz)) # Create connectivity array conec = np.zeros(((nx-1)*(ny-1)*(nz-1),8),dtype=np.int32) conec[:,0] = idx2[:-1,:-1,:-1].ravel() conec[:,1] = idx2[:-1,:-1,1:].ravel() conec[:,2] = idx2[:-1,1:,1:].ravel() conec[:,3] = idx2[:-1,1:,:-1].ravel() conec[:,4] = idx2[1:,:-1,:-1].ravel() conec[:,5] = idx2[1:,:-1,1:].ravel() conec[:,6] = idx2[1:,1:,1:].ravel() conec[:,7] = idx2[1:,1:,:-1].ravel() return conec