CGNS 是一种通用(跨平台、易扩展、受众广)的 CFD 文件(数据库)系统。
术语 | 含义 |
---|---|
ADF | Advanced Data Format |
API | Application Programming Interface |
CFD | Computational Fluid Dynamics |
CGNS | CFD General Notation System |
CPEX | CGNS Proposal for EXtension |
FMM | File Mapping Manual |
HDF | Hierarchical Data Format |
MLL | Mid-Level Library |
SIDS | Standard Interface Data Structures |
符合 SIDS 规范的 CGNS 文件(数据库)是按 ADF 或 HDF5 编码的,因此无法用普通的文本编辑器读写,但可以用 CGNSview、HDFView 等工具安全地读写,其操作类似于在操作系统中访问文件树。
如果要构建上述工具,则需安装以下依赖库,并在 CMake 中将 CGNS_BUILD_CGNSTOOLS
设为 ON
:
sudo apt install libx11-dev libxmu-dev
sudo apt install libopengl-dev libglu1-mesa-dev libgl1-mesa-dev
sudo apt install libtcl8.6 tk8.6-dev
⚠️ Gmsh、VTK 只能打开比它们所依赖的 CGNS 库更旧的 CGNS 文件。
每个 CGNS 文件(数据库)在逻辑上是一棵由若干结点 (node) 相互链接而成的树 (tree)。每个结点都含有以下数据:
Label
表示其类型,通常是以 _t
为后缀的预定义类型。Name
表示其身份,通常是由用户自定义的字符串,但有时需符合命名规范。Data
是实际数据,可以为空(用 MT
表示)。⚠️ 为避免混淆,本文称树上的结点为对象 (object),而将网格中的点称为顶点 (vertex) 或网格点 (mesh/grid point)。
并行版的 CGNS/MLL 依赖于并行版的 HDF5,后者依赖于 MPI。安装这两个依赖项最简单的方式为:
apt install hdf5-mpi # Ubuntu
brew install hdf5-mpi # macOS
或者先手动构建 MPI,再手动构建 HDF5。 以 Linux 下构建 HDF5-1.14.0 为例:
HDF5_SOURCE_DIR
使其指向源文件根目录。build
目录,设置环境变量 HDF5_BUILD_DIR
使其指向该目录。cmake -S ${HDF5_SOURCE_DIR} -B ${HDF5_BUILD_DIR} -D CMAKE_BUILD_TYPE=Release -D HDF5_ENABLE_PARALLEL=ON
命令,开始配置。配置过程中,CMake 会自动查找已安装的 MPI;若自动查找失败,则需手动设置相关路径。${HDF5_BUILD_DIR}
目录,运行 cmake --build .
命令,开始构建。install
目录,设置环境变量 HDF5_INSTALL_DIR
使其指向该目录。${HDF5_BUILD_DIR}
目录,运行 cmake --install . --prefix ${HDF5_INSTALL_DIR}
命令,开始安装。MPI 及 HDF5 安装完成后,即可在构建 CGNS/MLL 时,开启并行相关选项:
cmake -D CGNS_ENABLE_PARALLEL=ON \
-D CGNS_ENABLE_HDF5=ON \
-D HDF5_NEED_MPI=ON \
-D HDF5_DIR:PATH=${HDF5_INSTALL_DIR}/cmake \
-B ${CGNS_BUILD_DIR} -S ${CGNS_SOURCE_DIR}
入门指南主要介绍 Fortran-API,这里(平行地)介绍 C-API ,以便 C/C++ 用户参考。
1
开始(与 SIDS 一致),而 C 则从 0
开始(故调用 C-API 时可能需要转换)。下载或克隆 CGNS 代码库后,可在 ${CGNS_SOURCE_DIR}/src/Test_UserGuideCode/
中找到所有示例的源文件。源文件头部的注释给出了独立构建各示例的方法;若要批量构建所有示例,可在 CMake 中开启 CGNS_ENABLE_TESTS
等选项,这样生成的可执行文件位于 ${CGNS_BUILD_DIR}/src/Test_UserGuideCode/
中。
cg_look
).git clone https://github.com/pyCGNS/pyCGNS.git temp
cd temp
python setup.py build
python setup.py install
root
root
├── CGNSLibraryVersion_t
│ ├── Name: CGNSLibraryVersion
│ └── Data: 版本号
└── CGNSBase_t // 可有多个,各表示一个算例
/* open/close a CGNS file */
ier = cg_open(char *file_name,
int mode/* CG_MODE_WRITE | CG_MODE_READ | CG_MODE_MODIFY */,
/* output: */int *i_file);
ier = cg_close(int i_file);
/* parallel version (need MPI) */
ier = cgp_open(char *file_name, int mode,
/* output: */int *i_file);
ier = cgp_close(int i_file);
用于新建对象的函数 *_open()
或 *_write()
总是以 (int) id
作为返回值。此 id
可以被后续代码用来访问该对象。
CGNSBase_t
CGNSBase_t
├── Name: 文件名
├── Data: cell_dim // int
│ phys_dim // int
└── Zone_t // 可有多个,各表示一块网格
/* write/read CGNSBase_t */
ier = cg_base_write(int i_file, char *base_name, int cell_dim, int phys_dim,
/* output: */int *i_base);
ier = cg_base_read(int i_file, int i_base,
/* output: */char *base_name, int *cell_dim, int *phys_dim);
Zone_t
Zone_t
├── ZoneType_t
│ ├── Name: ZoneType
│ └── Data: Structured | Unstructured | UserDefined | Null
├── GridCoordinates_t // 顶点坐标
├── Elements_t // 单元顶点列表(非结构网格特有)
├── FlowSolution_t // 物理量在顶点或单元上的值
├── ZoneBC_t // 边界条件
└── ZoneGridConnectivity_t // 多区网格的衔接方式
/* write/read Zone_t */
ier = cg_zone_write(int i_file, int i_base, char *zone_name, cgsize_t *zone_size,
ZoneType_t zone_type/* CGNS_ENUMV(Structured) | CGNS_ENUMV(Unstructured) */,
/* output: */int *i_zone);
ier = cg_zone_read(int i_file, int i_base, int i_zone,
/* output: */char *zone_name, cgsize_t *zone_size);
其中
cell_dim
、phys_dim
分别表示单元(流形)维数、物理(空间)维数。zone_size
是一个二维数组(的首地址), 3
,各行分别表示顶点数、单元数、边界单元数 。1
。0
。1
。0
。/* Access a node via label-index or name-0 pairs.
* 0 = CG_OK, 1 = CG_ERROR, 2 = CG_NODE_NOT_FOUND, 3 = CG_INCORRECT_PATH
*/
ier = cg_goto(int i_file, int i_base, ..., "end");
ier = cg_gopath(int i_file, const char *path);
/* Delete a child of current node. */
ier = cg_delete_node(char *node_name);
/* e.g. */
cg_goto(i_file, i_base, "Zone_t", 1, "UserDefinedData_t", 1, NULL);
cg_goto(i_file, i_base "Zone1", 0, "UserDefinedData_t", 1, NULL);
cg_goto(i_file, i_base, "Zone_t", 1, "User", 0, NULL);
cg_goto(i_file, i_base, "Zone1", 0, "User", 0, NULL);
cg_gopath(i_file, "/Base/Zone1/User");
结构网格 | 非结构网格 | |
---|---|---|
写出 | write_grid_str.c | write_grid_unst.c |
读入 | read_grid_str.c | read_grid_unst.c |
GridCoordinates_t
GridCoordinates_t
├── Name: GridCoordinates
└── DataArray_t // 个数 == 所属 CGNSBase_t 对象的 phys_dim
├── Name: CoordinateX | CoordinateY | CoordinateZ |
│ CoordinateR | CoordinateTheta | CoordinatePhi
└── Data: 一维数组,长度 = 顶点数 + 表皮层数 // 沿当前方向
/* write/read (GridCoordinates_t) "GridCoordinates" */
ier = cg_grid_write(int i_file, int i_base, int i_zone,
char *grid_name/* GridCoordinates */,
/* output: */int *i_grid);
ier = cg_grid_read(int i_file, int i_base, int i_zone, int i_grid,
/* output: */char *grid_name);
/* write/read (DataArray_t) "Coordinate[XYZ]" */
ier = cg_coord_write(int i_file, int i_base, int i_zone,
DataType_t data_type/* CGNS_ENUMV(RealDouble) */,
char *coord_name, void *coord_array,
/* output: */int *i_coord);
ier = cg_coord_read(int i_file, int i_base, int i_zone,
char *coord_name, DataType_t data_type,
cgsize_t *range_min, cgsize_t *range_max, // 1-based inclusive
/* output: */void *coord_array);
/* parallel version (need MPI) */
ier = cgp_coord_write(int i_file, int i_base, int i_zone,
DataType_t data_type/* CGNS_ENUMV(RealDouble) */, char *coord_name,
/* output: */int *i_coord);
ier = cgp_coord_read(); /* undefined */
ier = cgp_coord_write_data(int i_file, int i_base, int i_zone, int i_coord,
cgsize_t *range_min, cgsize_t *range_max, // 1-based inclusive
void *coord_array);
ier = cgp_coord_read_data(int i_file, int i_base, int i_zone, int i_coord,
cgsize_t *range_min, cgsize_t *range_max, // 1-based inclusive
/* output: */void *coord_array);
其中
cg_coord_write()
写出的是以 coord_array
为首地址的前 N
个元素: N
可由 zone_size[0]
算出。coord_array
通常声明为多维数组,此时除第一维长度至少等于该方向的顶点数外,其余维度的长度必须等于相应方向的顶点数。coord_array
通常声明为长度不小于 N
的一维数组。coord_name
必须取自《SIDS-standard names》,即 CoordinateX | CoordinateY | CoordinateZ
CoordinateR | CoordinateTheta | CoordinatePhi
data_type
应当与 coord_array
的类型匹配: CGNS_ENUMV(RealSingle)
对应 float
。CGNS_ENUMV(RealDouble)
对应 double
。cgp_write_coord()
创建空 DataArray_t
对象;cgp_write_coord_data()
写入当前进程所负责的部分。Elements_t
结构网格的顶点信息已经隐含了单元信息,因此不需要显式创建单元。与之相反,非结构网格的单元信息需要显式给出:
Elements_t
├── IndexRange_t
│ ├── Name: ElementRange
│ └── Data: int[2] = {first, last} // 单个 Elements_t 内的单元需连续编号
├── ElementType_t
│ └── Data: NODE = 2 | BAR_2 = 3 | TRI_3 = 5 | QUAD_4 = 7 |
│ TETRA_4 = 10 | PYRA_5 = 12 | PENTA_6 = 14 | HEXA_8 = 17 |
│ NGON_n = 22 | NFACE_n = 23 | MIXED = 20
└── DataArray_t
│ ├── Name: ElementConnectivity
│ └── Data: int[ElementDataSize] // ElementDataSize :=
│ // 同种单元 ElementSize * NPE(ElementType)
│ // 多种单元 Sum(NPE(ElementType[i]) + 1/* for ElementType[i] */)
│ // NPE := number of nodes for the given ElementType
└── DataArray_t // 含多种单元时使用
├── Name: ElementStartOffset
└── Data: int[ElementSize + 1]
ier = cg_nsections(int i_file, int i_base, int i_zone,
/* output: */int *n_sections);
// for fixed-size elements:
ier = cg_section_write(int i_file, int i_base, int i_zone,
char *section_name, ElementType_t element_type,
cgsize_t first, cgsize_t last, int n_boundary,
cgsize_t *connectivity, /* output: */int *i_sect);
ier = cg_section_read(int i_file, int i_base, int i_zone, int i_sect,
/* output: */char *section_name, ElementType_t *element_type,
cgsize_t *first, cgsize_t *last, int *n_boundary, int *parent_flag);
ier = cg_elements_write(); /* undefined */
ier = cg_elements_read(int i_file, int i_base, int i_zone, int i_sect,
/* output: */cgsize_t *connectivity, cgsize_t *parent_data);
ier = cg_elements_partial_write(int i_file, int i_base, int i_zone, int i_sect,
cgsize_t first, cgsize_t last, cgsize_t *connectivity);
ier = cg_elements_partial_read(int i_file, int i_base, int i_zone, int i_sect,
cgsize_t first, cgsize_t last,
/* output: */cgsize_t *connectivity, cgsize_t *parent_data);
// for MIXED | NGON_n | NFACE_n:
ier = cg_poly_section_write(int i_file, int i_base, int i_zone,
char *section_name, ElementType_t element_type,
cgsize_t first, cgsize_t last, int n_boundary,
cgsize_t *connectivity, cgsize_t *offset,
/* output: */int *i_sect);
ier = cg_poly_section_read(); /* undefined */
ier = cg_poly_elements_write(); /* undefined */
ier = cg_poly_elements_read(int i_file, int i_base, int i_zone, int i_sect,
/* output: */cgsize_t *connectivity, cgsize_t *offset, cgsize_t *parent_data);
ier = cg_poly_elements_partial_write(int i_file, int i_base, int i_zone, int i_sect,
cgsize_t first, cgsize_t last, cgsize_t *connectivity, cgsize_t *offset);
ier = cg_poly_elements_partial_read(int i_file, int i_base, int i_zone, int i_sect,
cgsize_t first, cgsize_t last,
/* output: */cgsize_t *connectivity, cgsize_t *offset, cgsize_t *parent_data);
/* parallel version (need MPI) */
ier = cgp_section_write(int i_file, int i_base, int i_zone,
char *section_name, ElementType_t element_type,
cgsize_t first, cgsize_t last, int n_boundary,
/* output: */int *i_sect);
ier = cgp_elements_write_data(int i_file, int i_base, int i_zone, int i_sect,
cgsize_t first, cgsize_t last, cgsize_t *connectivity);
ier = cgp_section_read(); /* undefined */
ier = cgp_elements_read_data(int i_file, int i_base, int i_zone, int i_sect,
cgsize_t first, cgsize_t last,
/* output: */cgsize_t *connectivity);
其中
cg_section_write()
或 cg_poly_section_write()
在给定的 Zone_t
对象下新建一个单元片段 (element section),即 Elements_t
对象。二者区别在于:前者用于只含一种单元的 sections,只需提供 connectivity
;后者用于含多种单元的 sections,还需提供 start_offset
。Zone_t
对象下可以有多个 Elements_t
对象: Elements_t
对象下的所有单元必须具有同一种 element_type
,并且必须是枚举类型 ElementType_t
的有效值之一。Zone_t
下的所有单元(含所有维数)都必须有连续且互异的编号。first
、last
cgp_section_write()
中为当前 Elements_t
对象内的首、末单元的编号。cgp_elements_write_data()
及 cgp_elements_write_data()
中为当前进程所读写的首、末单元的编号。n_boundary
为当前 Elements_t
对象的边界单元数:若 n_boundary > 0
,则单元已被排序,且前 n_boundary
个单元为边界单元。parent_flag
用于判断 parent data 是否存在。结构网格:
> ${CGNS_BUILD_DIR}/src/Test_UserGuideCode/C_code/write_grid_str
Program write_grid_str
created simple 3-D grid points
Successfully wrote grid to file grid_c.cgns
> ${CGNS_BUILD_DIR}/src/Test_UserGuideCode/C_code/read_grid_str
Successfully read grid from file grid_c.cgns
For example, zone 1 x,y,z[8][16][20]= 20.000000, 16.000000, 8.000000
Program successful... ending now
非结构网格:
> ${CGNS_BUILD_DIR}/src/Test_UserGuideCode/C_code/write_grid_unst
Program write_grid_unst
created simple 3-D grid points
Successfully wrote unstructured grid to file grid_c.cgns
> ${CGNS_BUILD_DIR}/src/Test_UserGuideCode/C_code/read_grid_unst
number of sections=4
Reading section data...
section name=Elem
section type=HEXA_8
istart,iend=1, 2560
reading element data for this element
Reading section data...
section name=InflowElem
section type=QUAD_4
istart,iend=2561, 2688
not reading element data for this element
Reading section data...
section name=OutflowElem
section type=QUAD_4
istart,iend=2689, 2816
not reading element data for this element
Reading section data...
section name=SidewallElem
section type=QUAD_4
istart,iend=2817, 3776
not reading element data for this element
Successfully read unstructured grid from file grid_c.cgns
for example, element 1 is made up of nodes: 1, 2, 23, 22, 358, 359, 380, 379
x,y,z of node 357 are: 0.000000, 0.000000, 1.000000
x,y,z of node 1357 are: 13.000000, 13.000000, 3.000000
⚠️ 本节生成的 grid_c.cgns
将在后续示例中反复使用,因此必确正确运行 write_grid_str
或 write_grid_unst
,以获得以上输出。
FlowSolution_t
FlowSolution_t
├── GridLocation_t
│ └── Data: Vertex | CellCenter | EdgeCenter | FaceCenter
├── IndexRange_t // 与 IndexList_t 二选一,对 Vertex | CellCenter 非必需
│ ├── Name: PointRange
│ └── Data: int[2] = {first, last}
├── IndexList_t // 与 IndexRange_t 二选一,对 Vertex | CellCenter 非必需
│ ├── Name: PointList
│ └── Data: int[] = {...}
└── DataArray_t
├── Name: Pressure | Density | VelocityX | MomentumX | ...
└── Data: DataType[DataSize] /* 编号与相应的 Elements_t 一致
if (有 PointRange | PointList):
DataSize = Size(PointRange | PointList)
else:
DataSize = VertexSize | CellSize
if (有 Rind):
DataSize += RindSize */
结构网格 | 非结构网格 | |
---|---|---|
写出 | write_flowvert_str | write_flowvert_unst |
读入 | read_flowvert_str | read_flowvert_unst |
新增的 API 如下:
/* write/read FlowSolution_t */
ier = cg_sol_write(int i_file, int i_base, int i_zone, char *sol_name,
GridLocation_t location/* CGNS_ENUMV(Vertex) */,
/* output: */int *i_soln);
ier = cg_sol_read(); /* undefined */
ier = cg_sol_info(int i_file, int i_base, int i_zone, int i_soln,
/* output: */char *sol_name, GridLocation_t *location);
/* Read field info */
ier = cg_nfields(int i_file, int i_base, int i_zone, int i_soln,
/* output: */int *n_fields);
ier = cg_field_info(int i_file, int i_base, int i_zone, int i_soln, int i_field,
/* output: */DataType_t *data_type, char *field_name);
/* write DataArray_t */
ier = cg_field_write(int i_file, int i_base, int i_zone, int i_soln,
DataType_t datatype, char *field_name, void *sol_array,
/* output: */int *i_field);
ier = cg_field_partial_write(int i_file, int i_base, int i_zone, int i_soln,
DataType_t datatype, char *field_name,
cgsize_t *range_min, cgsize_t *range_max, // 1-based inclusive
void *sol_array,
/* output: */int *i_field);
/* read DataArray_t */
ier = cg_field_read(int i_file, int i_base, int i_zone, int i_soln,
char *field_name, DataType_t data_type,
cgsize_t *range_min, cgsize_t *range_max,
/* output: */void *sol_array);
ier = cg_field_partial_read(); /* undefined */
/* parallel version (need MPI) */
ier = cgp_field_write(int i_file, int i_base, int i_zone, int i_soln,
DataType_t datatype, char *field_name,
/* output: */int *i_field);
ier = cgp_field_write_data(int i_file, int i_base, int i_zone, int i_soln, int i_field,
cgsize_t *range_min, cgsize_t *range_max, // 1-based inclusive
void *sol_array);
ier = cgp_field_read(); /* undefined, use cg_field_read() */
ier = cgp_field_read_data(int i_file, int i_base, int i_zone, int i_soln, int i_field,
cgsize_t *range_min, cgsize_t *range_max, // 1-based inclusive
/* output: */void *sol_array);
ier = cgp_field_general_read_data(int i_file, int i_base, int i_zone, int i_soln, int i_field,
cgsize_t *range_min, cgsize_t *range_max, // range in file
DataType_t mem_datatype/* data type of the array in memory */,
int mem_rank/* number of dimensions of array in memory */,
cgsize_t *mem_dimensions/* dimensions of array in memory */,
cgsize_t *mem_range_min, cgsize_t *mem_range_max, // range in memory
/* output: */void *sol_array);
其中
cg_sol_write()
用于在 Zone_t
对象下创建一个表示一组物理量的 FlowSolution_t
对象。 Zone_t
对象下可以有多个 FlowSolution_t
对象。FlowSolution_t
对象都平行于 GridCoordinates_t
对象。cg_field_write()
用于在 FlowSolution_t
对象下创建一个表示单个物理量的对象,例如 DataArray_t
对象、Rind_t
对象。 sol_array
尺寸应当与顶点数量匹配:对于结构网格,通常声明为多维数组;对于非结构网格,通常声明为一位数组。field_name
应当取自《SIDS-standard names》,例如 Density | Pressure
。write_flowcent_str.c
与 read_flowcent_str.c
演示了这种流场表示方法,所用 API 与前一小节几乎完全相同,只需注意:
cg_sol_write()
时,将 location
的值由 CGNS_ENUMV(Vertex)
改为 CGNS_ENUMV(CellCenter)
。表皮数据 (rind data) 是指存储在网格最外侧的一层或多层影子单元 (ghost cells) 上的数据 :
┌───╔═══╦═══╦═══╗───┬───┐ ═══ 网格单元
│ o ║ o ║ o ║ o ║ o │ o │
└───╚═══╩═══╩═══╝───┴───┘ ─── 影子单元
write_flowcentrind_str.c
与 read_flowcentrind_str.c
演示了这种表示方法,新增的 API 如下:
/* API in `write_flowcentrind_str.c` and `read_flowcentrind_str.c` */
// Access a node via [label|name]-index pairs:
ier = cg_goto(int i_file, int i_base, ..., "end");
// e.g.
ier = cg_goto(i_file, i_base,
"Zone_t", i_zone,
"FlowSolution_t", i_soln,
"end");
// Number of rind layers for each direction (structured grid):
int rind_data[6] = {
1/* i_low */, 1/* i_high */,
1/* j_low */, 1/* j_high */,
0/* k_low */, 0/* k_high */};
// Write number of rind layers:
ier = cg_rind_write(int *rind_data);
// Read number of rind layers:
ier = cg_rind_read(int *rind_data);
其中
cg_goto()
用于定位将要创建 Rind_t
对象的那个 FlowSolution_t
对象。DataClass_t
/* Write data class: */
ier = cg_goto(i_file, i_base, "end");
ier = cg_dataclass_write(CGNS_ENUMV(Dimensional));
ier = cg_dataclass_write(CGNS_ENUMV(NondimensionalParameter));
DimensionalUnits_t
/* Write first five dimensional units: */
ier = cg_units_write(
CGNS_ENUMV(Kilogram), CGNS_ENUMV(Meter),
CGNS_ENUMV(Second), CGNS_ENUMV(Kelvin),
CGNS_ENUMV(Degree));
DimensionalExponents_t
/* Write first five dimensional exponents of coordinates: */
float dimensional_exponents[5] = {0., 1., 0., 0., 0.};
ier = cg_goto(i_file, i_base, "Zone_t", i_zone, "GridCoordinates_t", 1,
"DataArray_t", i_coord, "end");
ier = cg_exponents_write(CGNS_ENUMV(RealSingle), dimensional_exponents);
/* Write first five dimensional exponents of pressure: */
dimensional_exponents[0] = +1.0;
dimensional_exponents[1] = -1.0;
dimensional_exponents[2] = -2.0;
ier = cg_goto(i_file, i_base, "Zone_t", i_zone, "FlowSolution_t", i_soln,
"DataArray_t", i_field, "end");
ier = cg_exponents_write(CGNS_ENUMV(RealSingle), dimensional_exponents);
两种 BC 表示方法:
PointRange
通过指定顶点编号范围来确定边界,因此只适用于结构网格的长方形边界。write_bc_str.c
与 read_bc_str.c
演示了这种方法。PointList
通过指定顶点编号列表来确定边界,因此适用于所有边界。write_bcpnts_str.c
与 read_bcpnts_str.c
演示了这种方法。尽管本节标题为结构网格,但上述方法也可以用于非结构网格,只是后者有更简单的方法。
/* API in `write_bc_str.c` and `read_bc_str.c`
and `write_bcpnt_str.c` and `read_bcpnt_str.c` */
// Write boundary condition type and data:
ier = cg_boco_write(int i_file, int i_base, int i_zone,
char *boco_name, BCType_t boco_type/* CGNS_ENUMV(BCType_t) */,
PointSetType_t point_set_type/* CGNS_ENUMV(PointRange) | CGNS_ENUMV(PointList) */,
cgsize_t n_point, cgsize_t *point_set,
/* output: */int *i_boco);
// Get number of boundary condition in zone:
ier = cg_nbocos(int i_file, int i_base, int i_zone,
/* output: */int *n_boco);
// Get boundary condition info:
ier = cg_boco_info(int i_file, int i_base, int i_zone, int i_boco,
/* output: */char *boco_name, BCType_t *boco_type,
PointSetType_t *point_set_type, cgsize_t *n_point,
int *i_normal,
cgsize_t *normal_list_size,
DataType_t *normal_data_type,
int *n_data_set);
// Read boundary condition data and normals:
ier = cg_boco_read(int i_file, int i_base, int i_zone, int i_boco,
/* output: */cgsize_t *point_set, void *normal_list);
其中
cg_boco_write()
用于创建一个表示具体边界条件的 BC_t
对象。 BC_t
对象都含有一个 IndexRange_t
或 IndexArray_t
子对象。BC_t
对象都是同一个 ZoneBC_t
对象的子对象。ZoneBC_t
对象是某个 Zone_t
对象的子对象,因此是 GridCoordinates_t
对象及 FlowSolution_t
对象的同辈对象。boco_type
的取值必须是枚举类型 BCType_t
的有效值,例如 BCWallInviscid | BCInflowSupersonic | BCOutflowSubsonic
,完整列表参见《Boundary Condition Type Structure Definition》。point_set
用于指定顶点编号,其行数(至少)为 n_point
。 point_set
的列数为空间维数,而 n_point
2
,若 point_set_type
为 CGNS_ENUMV(PointRange)
。此时 point_set
的第一、二行分别表示编号的下界、上界。point_set_type
为 CGNS_ENUMV(PointList)
。point_set
的列数为 1
,而 n_point
point_set_type
只能为 CGNS_ENUMV(PointList)
。尽管非结构网格可以像结构网格那样,通过指定边界上的顶点来施加边界条件,但利用读写单元时创建的 Elements_t
对象来指定边界上的单元通常会更加方便。write_bcpnts_unst.c
与 read_bcpnts_unst.c
演示了这种方法,主要的 API 如下:
/* API in `write_bcpnt_unst.c` and `read_bcpnt_unst.c` */
// Write boundary condition type and data:
ier = cg_boco_write(int i_file, int i_base, int i_zone, char *boco_name,
BCType_t boco_type/* CGNS_ENUMV(BCType_t) */,
PointSetType_t point_set_type/* CGNS_ENUMV(PointRange) | CGNS_ENUMV(PointList) */,
cgsize_t n_cell, cgsize_t *cell_set,
/* output: */int *i_boco);
// Write grid location:
ier = cg_gridlocation_write(GridLocation_t grid_location/*
CGNS_ENUMV(CellCenter) | CGNS_ENUMV(FaceCenter) |
CGNS_ENUMV(EdgeCenter) | CGNS_ENUMV(Vertex) */);
// Read grid location:
ier = cg_gridlocation_read(GridLocation_t *grid_location);
其中
cg_boco_write()
在形式上与结构网格版本一样,只是将 n_point
、point_set
替换成了 n_cell
、cell_set
。grid_location == CGNS_ENUMV(FaceCenter)
表示当前边界条件定义在面单元上,即 cell_set
是存储面单元编号的数组。cg_gridlocation_write()
之前必须先用 cg_goto()
定位到所需的 BC_t
对象。Family_t
Family_t
可以是某个 CGNSBase_t
的直系 child,或其他 Family_t
的直系 child。
为表示网格对象(Zone_t
或 ZoneSubRegion_t
或 BC_t
)与 Family_t
的关联,可以为该网格对象定义一个名为 FamilyName
的 FamilyName_t
型 child。 该 child 的值为字符串,表示其 parent 所关联的 Family_t
对象。 若该字符串不含 /
,则表示当前 CGNSBase_t
的直系 Family_t
型 child;否则(含若干 /
)需给出形如 /CGNSBaseName/<FamilyName1>/.../<FamilyNameN>
的完整名称。
在 Gmsh 输出的 CGNS 文件中,每个
CGNSBase_t/Family_t
及一个 ZoneBC_t/BC_t
,后者含有一个指向前者的 FamilyName_t
型 child。CGNSBase_t/Family_t
,而是存储为上述 Family_t
的 FamilyName_t
型 child 的值。SIDS 定义了两种迭代数据结构,以管理多个时间(或迭代)步的数据:
BaseIterativeData_t
对象位于 CGNSBase_t
对象之下,一般用于存储时间步总数及各步的时间值,有时(如网格拓扑发生改变)也用来存储指向各步的指针:
BaseIterativeData_t := {
int NumberOfSteps (r)
DataArray_t<real, 1, NumberOfSteps> TimeValues ; (o/r)
DataArray_t<int, 1, NumberOfSteps> IterationValues ; (r/o)
DataArray_t<int, 1, NumberOfSteps> NumberOfZones ; (o)
DataArray_t<int, 1, NumberOfSteps> NumberOfFamilies ; (o)
DataArray_t<char, 3, [65, MaxNumberOfZones, NumberOfSteps]>
ZonePointers ; (o)
DataArray_t<char, 3, [65, MaxNumberOfFamilies, NumberOfSteps]>
FamilyPointers ; (o)
List( DataArray_t<> DataArray1 ... DataArrayN ) ; (o)
List( Descriptor_t Descriptor1 ... DescriptorN ) ; (o)
DataClass_t DataClass ; (o)
DimensionalUnits_t DimensionalUnits ; (o)
List( UserDefinedData_t UserDefinedData1 ... UserDefinedDataN ) ; (o)
}
ZoneIterativeData_t
对象位于 Zone_t
对象之下,一般用于存储指向各步的指针:
ZoneIterativeData_t< int NumberOfSteps > := {
DataArray_t<char, 2, [32, NumberOfSteps]>
RigidGridMotionPointers ; (o)
DataArray_t<char, 2, [32, NumberOfSteps]>
ArbitraryGridMotionPointers ; (o)
DataArray_t<char, 2, [32, NumberOfSteps]>
GridCoordinatesPointers ; (o)
DataArray_t<char, 2, [32, NumberOfSteps]>
FlowSolutionPointers ; (o)
DataArray_t<char, 2, [32, NumberOfSteps]>
ZoneGridConnectivityPointers ; (o)
DataArray_t<char, 2, [32, NumberOfSteps]>
ZoneSubRegionPointers ; (o)
List( DataArray_t<> DataArray1 ... DataArrayN ) ; (o)
List( Descriptor_t Descriptor1 ... DescriptorN ) ; (o)
DataClass_t DataClass ; (o)
DimensionalUnits_t DimensionalUnits ; (o)
List( UserDefinedData_t UserDefinedData1 ... UserDefinedDataN ) ; (o)
}
⚠️ 上述指针目前由字符串实现。
网格固定不变意味着 GridCoordinates_t
及 Elements_t
(s) 可复用,故只需将各时间步上的 FlowSolution_t
(s) 记录在 ZoneIterativeData_t
内的 (DataArray_t) FlowSolutionPointers
中。
官方教程中的 write_timevert_str.c
与 read_timevert_str.c
演示了这种方法。 主要 API 用法如下:
/* API in `write_timevert_str.c` and `read_timevert_str.c` */
// Base-level
ier = cg_simulation_type_write(i_file, i_base, CGNS_ENUMV(TimeAccurate));
ier = cg_biter_write(i_file, i_base, "TimeIterValues", n_steps);
ier = cg_goto(ii_file, i_base, "BaseIterativeData_t", 1, "end");
int n_steps = 3;
double times[3] = {1.0, 2.0, 3.0};
ier = cg_array_write("TimeValues", CGNS_ENUMV(RealDouble), 1, &n_steps, ×);
// Zone-level
ier = cg_ziter_write(i_file, i_base, i_zone, "ZoneIterativeData");
ier = cg_goto(i_file, i_base, "Zone_t", i_zone, "ZoneIterativeData_t", 1, "end");
char names[97]; /* need an extra byte for the terminating '\0' */
strcpy(names , "FlowSolution1");
strcpy(names+32, "FlowSolution2");
strcpy(names+64, "FlowSolution3");
cgsize_t n_dims[2] = {32, 3};
ier = cg_array_write("FlowSolutionPointers", CGNS_ENUMV(Character),
2, n_dims, names/* the head of a 2d array, whose type is char[3][32] */);
⚠️ 一个 FlowSolution_t
有且仅有一个 GridLocation_t
,这意味着
FlowSolution_t
内,不能既顶点数据、又有单元数据。FlowSolutionPointers
内,所有 FlowSolution_t
(s) 必须有相同的 GridLocation_t
值。FlowSolutionPointers
再创建一个 FlowSolutionXXXPointers
,这样两类数据都能被 ParaView 识别。示例文件 write_fixed_grid.cpp
演示了这种方法。网格作刚体运动意味着 Elements_t
(s) 可复用,而顶点坐标可以由初始位置(记录在当前 Zone_t
下唯一的 GridCoordinates_t
中)与刚体运动信息(随体坐标系的原点位置、原点速度、坐标架转角、坐标架转速等)快速地算出,后者记录在 RigidGridMotion_t
中(一个时间步对应一个这样的 RigidGridMotion_t
,对应关系由 ZoneIterativeData_t
中的 (DataArray_t) RigidGridMotionPointers
管理)。
RigidGridMotion_t := {
List( Descriptor_t Descriptor1 ... DescriptorN ) ; (o)
RigidGridMotionType_t RigidGridMotionType ; (r)
DataArray_t<real, 2, [PhysicalDimension, 2]> OriginLocation ; (r)
DataArray_t<real, 1, PhysicalDimension> RigidRotationAngle ; (o/d)
DataArray_t<real, 1, PhysicalDimension> RigidVelocity ; (o)
DataArray_t<real, 1, PhysicalDimension> RigidRotationRate ; (o)
List( DataArray_t DataArray1 ... DataArrayN ) ; (o)
DataClass_t DataClass ; (o)
DimensionalUnits_t DimensionalUnits ; (o)
List( UserDefinedData_t UserDefinedData1 ... UserDefinedDataN ) ; (o)
}
顶点作任意运动意味着 Elements_t
(s) 仍可复用,但顶点坐标不再能快速算出,故需为每个时间步分别创建
GridCoordinates_t
,用于记录该时间步的顶点坐标,并将其名称记录在 ZoneIterativeData_t
下的 (DataArray_t) GridCoordinatesPointers
中。ArbitraryGridMotion_t
,用于记录其所属 Zone_t
的顶点速度,并将其名称记录在 ZoneIterativeData_t
下的 (DataArray_t) ArbitraryGridMotionPointers
中。ArbitraryGridMotion_t< int IndexDimension,
int VertexSize[IndexDimension],
int CellSize[IndexDimension] > := {
ArbitraryGridMotionType_t ArbitraryGridMotionType ; (r)
List( DataArray_t<real, IndexDimension, DataSize[]>
GridVelocityX GridVelocityY ... ) ; (o)
List( Descriptor_t Descriptor1 ... DescriptorN ) ; (o)
GridLocation_t GridLocation ; (o/d)
Rind_t<IndexDimension> Rind ; (o/d)
DataClass_t DataClass ; (o)
DimensionalUnits_t DimensionalUnits ; (o)
List( UserDefinedData_t UserDefinedData1 ... UserDefinedDataN ) ; (o)
}
网格拓扑发生改变意味着 Elements_t
(s) 不再能复用,故需创建新的 Zone_t
以对应网格拓扑发生变化的各时间步,对应关系由其所属 CGNSBase_t
中的 (DataArray_t) ZonePointers
管理。示例文件 write_adaptive_grid.cpp
演示了这种方法。
CGNSBase_t "RemeshingCase" {
BaseIterativeData_t "Steps" {
int "NumberOfSteps" = 4
DataArray_t<double> "TimeValues" = {0, 5, 10, 15, 20}
DataArray_t<int> "NumberOfZones" = {1, 1, 1, 1, 1}
DataArray_t<char*> "ZonePointers" = {
"Zone1", "Zone1", "Zone1", "Zone2", "Zone2"
}
}
Zone_t "Zone1" {
GridCoordinates_t "GridCoordinates"
Elements_t "Elements"
ZoneBC_t "ZoneBC"
FlowSolution_t "Solution0"
FlowSolution_t "Solution5"
FlowSolution_t "Solution10"
ZoneIterativeData_t {
DataArray_t<char*> "FlowSolutionPointers" = {
"Solution0", "Solution5", "Solution10", "Null", "Null"
}
}
}
Zone_t "Zone2" = {
GridCoordinates_t "GridCoordinates"
Elements_t "Elements"
ZoneBC_t "ZoneBC"
FlowSolution_t "Solution15"
FlowSolution_t "Solution20"
ZoneIterativeData_t {
DataArray_t<char*> "FlowSolutionPointers" = {
"Null", "Null", "Null", "Solution15", "Solution20"
}
}
}
}
另一种更通用的方法是创建 CGNS 文件序列,即各时间步分别对应一个 CGNS 文件。示例文件 write_file_series.cpp
演示了这种方法。在 ParaView 中加载 CGNS 文件序列时,需勾选 Ignore FlowSolutionPointers
及 Ignore Reader Time
,否则所有时间步会叠在一起显示。