You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
434 lines
28 KiB
434 lines
28 KiB
2 years ago
|
# Zellij
|
||
|
|
||
|
Zellij is a "mesh concatenation" application for generating a mesh
|
||
|
consisting of a "lattice" containing one or more "unit cell" template
|
||
|
meshes. The lattice is a two-dimensional arrangement of the unit cell
|
||
|
template meshes.
|
||
|
|
||
|
The unit cell template meshes are placed by zellij into the specified
|
||
|
locations in the lattice and the nodes on the boundaries of the unit
|
||
|
cell meshes are united or coincident. Each unit cell mesh must have
|
||
|
the same exterior boundary meshes and coordinate extents on the X and
|
||
|
Y coordinate faces, but the Z faces are only required to have the same
|
||
|
coordinate extent; the Z face meshes are not required to be the same
|
||
|
among the different unit cells.
|
||
|
|
||
|
The lattice can be represented as a IxJ regular grid with each "cell"
|
||
|
in the grid or lattice containing one of the unit cell template
|
||
|
meshes.
|
||
|
|
||
|
[TOC]
|
||
|
|
||
|
## Execution
|
||
|
|
||
|
Executing zellij with the `-help` option will result in output similar to the following:
|
||
|
|
||
|
```
|
||
|
Zellij
|
||
|
(A code for tiling 1 or more template databases into a single output database.)
|
||
|
(Version: 1.4.1) Modified: 2021/03/16
|
||
|
Parallel Capability Not Enabled.
|
||
|
|
||
|
usage: zellij [options] -lattice <lattice_definition_file>
|
||
|
-lattice <$val> (Name of file to read lattice definition from. [required])
|
||
|
-output <$val> (Name of output file to create. Default is `zellij-out.e`)
|
||
|
|
||
|
-rcb (Use recursive coordinate bisection method to decompose the input mesh in a parallel run.)
|
||
|
-rib (Use recursive inertial bisection method to decompose the input mesh in a parallel run.)
|
||
|
-hsfc (Use hilbert space-filling curve method to decompose the input mesh in a parallel run. [default])
|
||
|
-linear (Use the linear method to decompose the input mesh in a parallel run.
|
||
|
Elements in order first n/p to proc 0, next to proc 1.)
|
||
|
-cyclic (Use the cyclic method to decompose the input mesh in a parallel run.
|
||
|
Elements handed out to id % proc_count)
|
||
|
-random (Use the random method to decompose the input mesh in a parallel run.
|
||
|
Elements assigned randomly to processors in a way that preserves balance
|
||
|
(do _not_ use for a real run))
|
||
|
|
||
|
-ranks <$val> (Number of ranks to decompose mesh across)
|
||
|
-start_rank <$val> (In partial output mode, start outputting decomposed files at this rank)
|
||
|
-rank_count <$val> (In partial output or subcycle modes, output this number of ranks)
|
||
|
-subcycle (Process cells in groups of '-rank_count'. Helps minimize open files,
|
||
|
but is faster than only having a single file open.)
|
||
|
-scale <$val> (Scale the output mesh coordinates by the specified value)
|
||
|
-minimize_open_files [$val] (Close files after accessing them to avoid issues with too many open files.
|
||
|
If argument is 'output' then close output, if 'unit' then close unit cells;
|
||
|
if 'all' or no argument close all.
|
||
|
Should not need to use this option unless you get an error message indicating this issue.)
|
||
|
|
||
|
-ignore_sidesets (Do not copy any sidesets in the unit cells to the output file.)
|
||
|
-generate_sidesets <$val> (Which surfaces on the output mesh should have sidesets generated,
|
||
|
Valid options are:
|
||
|
'x' or 'i' for surface on minimum X coordinate, default name = `min_i`
|
||
|
'y' or 'j' for surface on minimum Y coordinate, default name = `min_j`
|
||
|
'z' or 'k' for surface on minimum Z coordinate, default name = `min_k`
|
||
|
'X' or 'I' for surface on maximum X coordinate, default name = `max_i`
|
||
|
'Y' or 'J' for surface on maximum Y coordinate, default name = `max_j`
|
||
|
'Z' or 'K' for surface on maximum Z coordinate, default name = `max_k`
|
||
|
For example `xyXY` would generate sidesets on min/max X and Y surfaces.)
|
||
|
-sideset_names <$val> (Specify names for one or more of the generated sidesets.
|
||
|
Form is `axis:name,axis:name,...`
|
||
|
where 'axis' is one of 'ijkIJKxyzXYZ', and 'name' is the name of the sideset.
|
||
|
The default names are 'min_i', 'max_i', 'min_j', 'max_j', 'min_k', 'max_k'.
|
||
|
For example `x:left,X:right` would name the sideset on the min x face 'left' and the max X face 'right'.)
|
||
|
|
||
|
-netcdf3 (Output database will be a netcdf3 native classical netcdf file format (32-bit only))
|
||
|
-netcdf4 (Output database will be a netcdf4 hdf5-based file instead of the classical netcdf file format (default))
|
||
|
-netcdf5 (Output database will be a netcdf5 (CDF5) file instead of the classical netcdf file format)
|
||
|
|
||
|
-32-bit (True if forcing the use of 32-bit integers for the output file)
|
||
|
-64-bit (True if forcing the use of 64-bit integers for the output file (default))
|
||
|
|
||
|
-zlib (Use the Zlib / libz compression method if compression is enabled (default) [exodus only].)
|
||
|
-szip (Use SZip compression. [exodus only, enables netcdf-4])
|
||
|
-compress <$val> (Specify the hdf5 zlib compression level [0..9] or szip [even, 4..32] to be used on the output file.)
|
||
|
|
||
|
-separate_cells (Do not equivalence the nodes between adjacent unit cells.)
|
||
|
-repeat <$val> (Each lattice entry will be used the specified number of times as will
|
||
|
each row in the lattice (for debugging). `-repeat 2` would double the lattice.)
|
||
|
-skip <$val> (Skip the specified number of lattice entries and rows. For example, -skip 1
|
||
|
would read every other entry on the row and every other row. (for debugging))
|
||
|
-help (Print this summary and exit)
|
||
|
-version (Print version and exit)
|
||
|
-debug <$val> (debug level (values are or'd)
|
||
|
1 = Time stamp information.
|
||
|
2 = Memory information.
|
||
|
4 = Verbose Unit Cell information.
|
||
|
8 = Verbose output of Grid finalization calculations.
|
||
|
16 = Put exodus library into verbose mode.
|
||
|
32 = Verbose decomposition information.
|
||
|
64 = Verbose output database summary information.
|
||
|
128 = Verbose sideset generation information.)
|
||
|
-copyright (Show copyright and license data.)
|
||
|
|
||
|
Can also set options via ZELLIJ_OPTIONS environment variable.
|
||
|
|
||
|
->->-> Send email to gdsjaar@sandia.gov for zellij support.<-<-<-
|
||
|
```
|
||
|
The only required option is `-lattice` followed by the name of the file containing the lattice description. The other options are used to specify compression of the output file; the format of the output file; or to request additional debug output.
|
||
|
|
||
|
If the `-output <filename>` option is not specified, then the output mesh will be named `zellij-out.e`.
|
||
|
|
||
|
## Lattice Description File Format
|
||
|
The format of the lattice description file is fairly simple, but is also very rigid. There are two sections of the file -- the _unit cell_ dictionary and the _lattice_ definition.
|
||
|
|
||
|
### Unit Cell Dictionary
|
||
|
The unit cell dictionary defines the unit cell template meshes that will be placed in the lattice. The dictionary begins with a line containing `BEGIN_DICTIONARY` followed by one or more lines defining the unit cells and is then ended with a line containing `END_DICTIONARY`
|
||
|
|
||
|
The syntax of the lines defining the unit cells consists of two fields -- an arbitrary _key_ and the filename containing the Exodus file defining the mesh for this unit cell. The only restriction on the _key_ is that it must be unique in the dictionary. The filenames must specify the path (either absolute or relative to the current execution directory) to the Exodus file; it can optionally be delimited by double quotes (`"`). The filenames do not need to be unique, but it is more efficient in both memory and time if each unit cell template mesh is unique.
|
||
|
|
||
|
As an example, here is a valid dictionary definition:
|
||
|
|
||
|
```
|
||
|
BEGIN_DICTIONARY
|
||
|
0001 "../zellij-example/xatom-1b.e"
|
||
|
0002 "../zellij-example/xatom-Y.e"
|
||
|
0003 "../zellij-example/xatom-X.e"
|
||
|
0004 "../zellij-example/xatom-2b.e"
|
||
|
END_DICTIONARY
|
||
|
```
|
||
|
The unit cell dictionary must appear before the lattice definition in the lattice description file.
|
||
|
|
||
|
If an error is detected during the parsing of the unit cell dictionary, the code will output an error message and terminate. Errors can be incorrect syntax, missing unit cell template meshes, duplicate keys, or problems reading the mesh description from a unit cell template mesh. The unit cell template mesh file is accessed and partially read at the time that zellij parses the corresponding unit cell dictionary line.
|
||
|
|
||
|
### Lattice Definition
|
||
|
|
||
|
The lattice definition specifies the size of the lattice and the distribution of the unit cell(s) within that lattice. The lattice definition must follow the unit cell dictionary in the lattice description file.
|
||
|
|
||
|
The first line of the lattice definition begins with the line `BEGIN_LATTICE {i} {j} 1` where `{i}` and `{j}` specify the size of the `IxJ` arrangement of unit cells. For example, the line `BEGIN_LATTICE 5 5 1` would define a lattice containing 25 unit cell instances arranged in a 5 by 5 regular grid.
|
||
|
|
||
|
The last line of the lattice definition is the line `END_LATTICE`. When that line is encountered, zellij will begin outputting the mesh.
|
||
|
|
||
|
Between the `BEGIN_LATTICE` and `END_LATTICE` are `{j}` lines with `{i}` entries per line. The entries are any of the _key_s that were specified in the unit cell dictionary.
|
||
|
|
||
|
As an example, here is a valid lattice definition using the keys of the example dictionary from the previous section:
|
||
|
|
||
|
```
|
||
|
BEGIN_LATTICE 5 5 1
|
||
|
0001 0002 0003 0002 0001
|
||
|
0002 0003 0003 0003 0002
|
||
|
0003 0003 0004 0003 0003
|
||
|
0002 0003 0003 0003 0002
|
||
|
0001 0002 0003 0002 0001
|
||
|
END_LATTICE
|
||
|
```
|
||
|
|
||
|
Although the lattice is typically symmetric and square, this is not a requirement and is not checked.
|
||
|
|
||
|
If an error is detected during the parsing of the _lattice_, the code will output an error message and terminate. Errors can include invalid keys, incorrect number of lattice definition lines, or incorrect number of keys on a definition line.
|
||
|
|
||
|
Note that zellij does not require that the unit cell keys be numeric; the following example shows a different method for specifying the same lattice definition file as the previous example:
|
||
|
|
||
|
```
|
||
|
BEGIN_DICTIONARY
|
||
|
- "../zellij-example/xatom-1b.e"
|
||
|
| "../zellij-example/xatom-Y.e"
|
||
|
+ "../zellij-example/xatom-X.e"
|
||
|
* "../zellij-example/xatom-2b.e"
|
||
|
END_DICTIONARY
|
||
|
|
||
|
BEGIN_LATTICE 5 5 1
|
||
|
- | + | -
|
||
|
| + + + |
|
||
|
+ + * + +
|
||
|
| + + + |
|
||
|
- | + | -
|
||
|
END_LATTICE
|
||
|
```
|
||
|
|
||
|
## Unit Cell Template Mesh Requirements
|
||
|
Zellij requires that the boundary mesh (`X` and `Y` faces) of each of the unit cell templates be a _regular_ "structured" mesh. Basically this means that the faces of the mesh elements on the boundary are in a regular rectangular grid such that each mesh face is rectangular (90 degree corners) and that the boundary mesh on the minimum `X` face is the same as that on the maximum `X` face and similarly for the minimum `Y` face and the maximum `Y` face.
|
||
|
|
||
|
Additionally, the X faces on _all_ unit cells must match and the Y faces on _all_
|
||
|
unit cells must match both in structure and in coordinate extent. This requirement is verified during execution. The `Z` faces are less constrained with the only requirement being that the coordinate extents of all `Z` faces must be the same (which follows from the `X` and `Y` face requirement); the structure of the mesh on the `Z` faces is arbitrary.
|
||
|
|
||
|
The unit cell meshes can contain any number of element blocks; however, each element block _must_ contain hexahedral elements with 8-nodes per element. The element blocks do not need to be the same in each unit cell mesh, but if they do share the same element block `id`, then those elements will be combined into the same element block in the output mesh with the same `id`.
|
||
|
|
||
|
The output mesh will contain the union of all element blocks existing on the input mesh unit cells. For example, if:
|
||
|
|
||
|
* unit cell `0001` has element blocks `1 10 100`
|
||
|
* unit cell `0002` has element blocks `2 20 200`
|
||
|
* unit cell `0003` has element blocks `1 2 10 20`
|
||
|
* unit cell `0004` has element blocks `10 20 100 200`
|
||
|
|
||
|
The output mesh will have element blocks `1 2 10 20 100 200`
|
||
|
|
||
|
## Sideset Handling
|
||
|
By default, zellij will replicate any sidesets that are defined on the
|
||
|
input unit cell meshes to the output mesh file. The sidesets will
|
||
|
have the same names and ids as the sidesets on the input unit cell
|
||
|
meshes. If you do not want the sidesets replicated, you can add the
|
||
|
command line option `-ignore_sidesets` and any sidesets on the input
|
||
|
unit cell meshes will be ignored.
|
||
|
|
||
|
Zellij can also generate new sidesets on the boundaries of the output
|
||
|
mesh via the command line option `-generate_sidesets <axes>` where
|
||
|
`axes` is one or more letters specifying the face of the output mesh
|
||
|
on which to generate a sideset. Valid letters are `xyzXYZ` or
|
||
|
`ijkIJK` which correspond to:
|
||
|
|
||
|
* `x` or `i` for surface on minimum X coordinate (default name = `min_i`)
|
||
|
* `y` or `j` for surface on minimum Y coordinate (default name = `min_j`)
|
||
|
* `z` or `k` for surface on minimum Z coordinate (default name = `min_k`)
|
||
|
* `X` or `I` for surface on maximum X coordinate (default name = `max_i`)
|
||
|
* `Y` or `J` for surface on maximum Y coordinate (default name = `max_j`)
|
||
|
* `Z` or `K` for surface on maximum Z coordinate (default name = `max_k`)
|
||
|
|
||
|
For example `-generate_sidesets xyXY` would generate sideset on the
|
||
|
surfaces corresponding to the minimum and maximum X and Y coordinates
|
||
|
on the output mesh.
|
||
|
|
||
|
By default, the generated sidesets will be named as shown in the list
|
||
|
above. The names can be changed with the `-sideset_names <arg>`
|
||
|
command line option. The syntax of `<arg>` is
|
||
|
`axis:name,axis:name,...` where `axis` is one of `ijkIJK` or `xyzXYZ`
|
||
|
and `name` is the name of the specified sideset. For example,
|
||
|
`-sideset_names x:left,X:right` would name the sidesets on the
|
||
|
minimum x and maximum X faces `left` and `right` respectively. There
|
||
|
will be an error if two or more sidesets have the same name.
|
||
|
|
||
|
## Parallel Execution
|
||
|
Zellij can produce a mesh decomposed into a file-per-rank for use in a
|
||
|
parallel analysis application. Note that Zellij itself is run
|
||
|
serially. The main option that tells Zellij to produce the decomposed
|
||
|
files is `-ranks <number_of_ranks>`. If this is specified, then
|
||
|
Zellij will create `number_of_ranks` individual files each containing
|
||
|
a portion of the complete model. The files will have the information
|
||
|
needed by a parallel application to read the data and set up the
|
||
|
correct communication paths and identify the nodes that are shared
|
||
|
across processor boundaries.
|
||
|
|
||
|
The decomposition method can also be specified. This determines the
|
||
|
algorithm that is used to break the lattice into `number_of_ranks`
|
||
|
pieces each with approximately the same computational complexity. The
|
||
|
decomposition methods are:
|
||
|
|
||
|
* `-rcb` Use recursive coordinate bisection method to decompose the input mesh in a parallel run.
|
||
|
* `-rib` Use recursive inertial bisection method to decompose the input mesh in a parallel run.
|
||
|
* `-hsfc` Use hilbert space-filling curve method to decompose the input mesh in a parallel run.
|
||
|
* `-linear` Use the linear method to decompose the input mesh in a parallel run. Elements in order first `n/p` to proc 0, next to proc 1.
|
||
|
* `-cyclic` Use the cyclic method to decompose the input mesh in a parallel run. Elements handed out to `id % proc_count`.
|
||
|
* `-random` Use the random method to decompose the input mesh in a parallel run. Elements are assigned randomly to processors in a way that preserves balance (do _not_ use for a real run))
|
||
|
|
||
|
The `-hsfc` method is the default if no other decomposition method is
|
||
|
specified. Note that the decomposition occurs at the _grid_ level so
|
||
|
the elements of each grid cell will not be split across multiple ranks. The grid
|
||
|
cells are weighted by the number of elements in the cell which should
|
||
|
produce a balanced decomposition if there are unit cells of varying
|
||
|
element counts.
|
||
|
|
||
|
The `-linear`, `-cyclic`, and `-random` methods are typically used for
|
||
|
debugging and testing Zellij and should not be used in a production
|
||
|
run, especially the `-random` method.
|
||
|
|
||
|
### Partial Parallel Output Mode
|
||
|
There is a _partial parallel output_ mode in which you can tell Zellij to only output a portion of the parallel decomposed files. This is selected with the `-start_rank <rank>` and `-rank_count <count>` options. In this case, Zellij will only output the ranks from `rank` up to `rank+count-1`. For example, if you run `zellij -ranks 10 -start_rank 5 -rank_count 3`, then zellij would output files for ranks 5, 6, and 7. This is somewhat inefficient since zellij will do many of the calculations for all ranks and only output the specified ranks; however, it does allow you to run multiple copies of zellij simultaneously. For example, you could run:
|
||
|
|
||
|
```
|
||
|
zellij -ranks 16 --start_rank 0 --rank_count 4
|
||
|
zellij -ranks 16 --start_rank 4 --rank_count 4
|
||
|
zellij -ranks 16 --start_rank 8 --rank_count 4
|
||
|
zellij -ranks 16 --start_rank 12 --rank_count 4
|
||
|
```
|
||
|
|
||
|
simultaneously and all 16 files should be output faster than running a single execution that wrote all of the files.
|
||
|
|
||
|
### Parallel Capable Parallel Execution
|
||
|
|
||
|
If Zellij is compiled with parallel capability enabled (This is shown at the beginning of the `-help` output or the version
|
||
|
information output when zellij begins executing as `Parallel Capability Enabled`), then you can run Zellij in parallel using the
|
||
|
normal `mpiexec -np <#> zellij <normal zellij options>` command. In this case, there will be `#` copies of zellij running
|
||
|
simultaneously and each copy will divide up the output files and work among each process/copy.
|
||
|
|
||
|
For example, if you run `mpiexec -np 8 zellij -ranks 1024 -latice lattice.txt`, then there will be 8 copies of zellij running
|
||
|
and each will output `1024/8 = 128` output files.
|
||
|
|
||
|
### Maximum Open File Complications
|
||
|
|
||
|
Most compute systems have a limit on the number of files that a program can have open simultaneously. For many systems, this
|
||
|
limit is 1024. The files that zellij deals with are (1) the unit cell meshes and (2) the per-rank output files, and (3) the
|
||
|
standard input, output, and error files. Because of this, it is somewhat easy for a zellij execution to exceed the open file
|
||
|
limit. Zellij attempts to handle this automatically using logic similar to:
|
||
|
|
||
|
* If the unit cell count exceeds the open file limit, then close each unit cell after each access before opening the next unit
|
||
|
cell mesh.
|
||
|
|
||
|
* If the number of `-ranks` that zellij is creating exceeds the open file count, then determine how many output files can be
|
||
|
open at one time (max_open = open file limit - 3 - number of unit cells open simultaneously) and run zellij in a `subcycle` mode
|
||
|
where it is only writing to `max_open` files at one time.
|
||
|
|
||
|
* If the `max_open` calculated in the above bullet is too small, then set the mode to only open a single unit cell mesh at a
|
||
|
time and redo the calculation.
|
||
|
|
||
|
* If all else fails, run with only a single unit cell file open and only a single output mesh rank file open.
|
||
|
|
||
|
If the above logic fails and Zellij is unable to run without exceeding the open file count, you can specify the behavior
|
||
|
manually using a combination of the `-minimize_open_files=<UNIT|OUTPUT|ALL>` option and the `-subcycle` and `-rank_count <#>`
|
||
|
options.
|
||
|
|
||
|
The options to `-minimize_open_files` are:
|
||
|
|
||
|
* `UNIT` - only have a single unit cell mesh open at one time; close before accessing another unit cell mesh.
|
||
|
* `OUTPUT` - only have a single output rank mesh file open at one time.
|
||
|
* `ALL` - both of the above options.
|
||
|
|
||
|
The `-subcycle` and `-rank_count <#>` options cause zellij to output `#` output files at a time and then cycle to the next `#`
|
||
|
output files until all files have been output. For example, `zellij -ranks 1024 -subcycle -rank_count 256` would do the
|
||
|
following:
|
||
|
|
||
|
* First cycle would output ranks 0 to 255,
|
||
|
* Second cycle would output ranks 256 to 511,
|
||
|
* Third cycle would output ranks 512 to 767,
|
||
|
* Fourth cycle would output ranks 768 to 1023.
|
||
|
|
||
|
In this mode, there will the `#` output files open simultaneously (unless
|
||
|
`-minimize_open_files=OUTPUT|ALL` was specified also). So the total number of open files will be `unit cell count + 3 + #` or
|
||
|
`1 + 3 + #` if `-minimize_open_files=UNIT` was specified.
|
||
|
|
||
|
## Execution Complexity
|
||
|
|
||
|
Zellij is intended to produce extremely large meshes and is therefore very concerned with both memory efficiency and execution
|
||
|
time efficiency.
|
||
|
|
||
|
### Memory Complexity
|
||
|
|
||
|
Zellij stores the following data:
|
||
|
|
||
|
* For each unit cell template mesh:
|
||
|
* metadata
|
||
|
* 64-bit Ids of nodes on each min_I, max_I, min_J, max_J face
|
||
|
* For each entry in the lattice definition:
|
||
|
* metadata (approximately 1KiByte)
|
||
|
* temporarily it will hold 64-bit Ids of nodes on the max_I and max_J faces. This will be deleted once the upper `I` and upper `J` "neighbor" entry has been processed (see below)
|
||
|
* For the lattice:
|
||
|
* vector containing the lattice definition.
|
||
|
|
||
|
The main memory use once the output file is being processed is the temporary storage containing the nodes on the `max_I` and `max_J` faces. The lattice is processed cell by cell. For an `II by JJ` sized grid, the cells are processed in the order `(1,1), (2,1), ... , (II, 1), (1,2), (2,2), ..., (II, JJ)`. The temporary storage on the `max_I` face is only needed until the next cell is processed. That is, for cell `(i,j)`, its `max_I` nodes will be used during the processing of cell `(i+1, j)` and then deleted.
|
||
|
|
||
|
The temporary storage on the `max_J` face is retained for a longer time. For cell `(i,j)`, the `max_J` storage is needed for cell `(i, j+1)` and then deleted.
|
||
|
|
||
|
For a grid of size `(II, JJ)`, there will at most be:
|
||
|
|
||
|
* 1 temporary vector of size `max_I` nodes
|
||
|
* `II` temporary vectors of size `max_J` nodes.
|
||
|
|
||
|
If you have a lattice that is rectangular (`II != JJ`), then it is more efficient for memory usage to make the `I` direction the smallest value if possible.
|
||
|
|
||
|
In addition to the above memory usage, zellij must also transfer the mesh coordinate data and element block connectivity data for each lattice entry to the output file. Zellij outputs the model using the following pseudo-code:
|
||
|
|
||
|
```
|
||
|
for each j : J
|
||
|
for each i : I
|
||
|
read cell(i,j) x, y, and z local coordinates
|
||
|
map coordinates to offset in output mesh
|
||
|
eliminate nodes that join to an already output neighbor cell
|
||
|
write cell(i,j) x, y, and z global coordinates
|
||
|
|
||
|
for each j : J
|
||
|
for each i : I
|
||
|
for each element block in cell(i,j) mesh
|
||
|
read block connectivity
|
||
|
map local node ids to global node ids
|
||
|
write block connectivity
|
||
|
```
|
||
|
|
||
|
The maximum memory use will be the size of storage needed for the `x` `y` and `z` coordinates of a unit cell mesh or the storage needed to hold the connectivity for a single unit cell element block.
|
||
|
|
||
|
Note that the memory requirements are proportional to the size of an individual unit cell mesh and not a function of the size of the output mesh. It is possible to create meshes which are much larger than the amount of memory present on the compute system running zellij.
|
||
|
|
||
|
The memory being used by zellij during execution will be output if the `--debug 2` argument is specified at execution time.
|
||
|
|
||
|
### Execution Time Complexity
|
||
|
|
||
|
For a large model, the majority of the execution time is related to:
|
||
|
|
||
|
* Read/process/write element block connectivity
|
||
|
* Read/process/write nodal coordinates
|
||
|
* Categorize boundary nodes on each unit cell mesh
|
||
|
|
||
|
### Efficiency at the NetCDF level
|
||
|
The Exodus format which is used for the unit cell template meshes and the output mesh uses the NetCDF library for on-disk storage. There are several variants of the NetCDF on-disk storage including the format: `netcdf3`, `netcdf4`, and `netcdf5` and the integer size (32-bit integers or 64-bit integers). Although these details are usually transparent to the user, they can affect the execution time especially when very large meshes are being processed.
|
||
|
|
||
|
#### Format
|
||
|
The `netcdf3` format is the original native NetCDF format. At the time the library was being developed, the `byte endianness` of data stored on disk was not standard among the computes in use at that time and the NetCDF developers had to pick an `endianness` for the data. They picked the XDR standard which stood for _eXternal Data Representation_ which was used for communicating between different computer systems. Regretfully, the representation used by XDR turned out to be opposite of the representation used by (almost?) all systems in use today, so each read and write of data in the `netcdf3` format results in a translation of the endianness. This translation is very fast, but is overhead that would not be needed if the on-disk format was the opposite representation. This representation is also used by the `netcdf5` format.
|
||
|
|
||
|
However, the NetCDF `netcdf4` format is based on using the HDF5 library to manage the underlying data format on disk and it can read and write data using the native endianness of the system on which the data is being read and written and therefore does not incur the cost of transforming the data's endianness.
|
||
|
|
||
|
#### Integer Size
|
||
|
By default, most current mesh generators will output a mesh using 32-bit integer data. This is sufficient to represent a mesh with up to approximately 2.1 billion nodes and elements.
|
||
|
|
||
|
If the input mesh and the output mesh have the same integer size, then there is no data conversion needed. The data will be read as `N`-bit integers, processed as `N`-bit integers, and written as `N`-bit integers. However, if the input mesh is `N`-bit integers and the output mesh is `M`-bit integers, then the NetCDF library will convert all integer data (element block connectivity typically) from `N` bits to `M` bits which for large meshes can incur an execution time overhead.
|
||
|
|
||
|
#### Compression
|
||
|
The NetCDF library supports compression of the output file. Typically, the `zlib` compression algorithm is used, but recently NetCDF begain supporting the `szip` compression and a few more algorithms are starting to be supported.
|
||
|
|
||
|
The benefit of the compression is that it can result in much smaller output (and input) mesh files; the disadvantage is that the default `zlib` compression algorithm is not very fast and can increase the execution time of zellij. The `szip` compression algorithm is faster with typically (but not always) slightly less compression, but it still will incur an overhead in execution time.
|
||
|
|
||
|
#### Recommendations
|
||
|
For minimal overhead, it is recommended that:
|
||
|
|
||
|
* Use the `netcdf4` format for all input and output meshes
|
||
|
* Use the same integer size for all input and output meshes
|
||
|
* The integer size of the output mesh can be specified using the `-32` or `-64` options.
|
||
|
* The `-64` option is the default.
|
||
|
|
||
|
It is most efficient if the format and integer size of the input mesh matches the output mesh. The format of the input meshes can be converted using the `io_shell` application with the `-netcdf4` and `-64` or `-32` options. The format and integer size of a mesh can be queried using the `exo_format` application.
|
||
|
|
||
|
For illustration, here is the execution time for several runs with different format and integer size. In all cases, the input and output mesh sizes are the same:
|
||
|
|
||
|
| input | output | integer input | integer output | execution time |
|
||
|
|:-------:|:-------:|:-------------:|:--------------:|:--------------:|
|
||
|
| netcdf3 | netcdf3 | 32 | 32 | 7.0 |
|
||
|
| netcdf3 | netcdf4 | 32 | 32 | 2.6 |
|
||
|
| netcdf3 | netcdf4 | 32 | 64 | 3.8 |
|
||
|
| netcdf4 | netcdf3 | 32 | 32 | 6.5 |
|
||
|
| netcdf4 | netcdf3 | 64 | 32 | 7.4 |
|
||
|
| netcdf4 | netcdf5 | 64 | 64 | 9.4 |
|
||
|
| netcdf4 | netcdf4 | 32 | 32 | 2.4 |
|
||
|
| netcdf4 | netcdf4 | 32 | 64 | 3.6 |
|
||
|
| netcdf4 | netcdf4 | 64 | 32 | 3.2 |
|
||
|
| netcdf4 | netcdf4 | 64 | 64 | 3.3 |
|
||
|
|
||
|
The fastest option is both input and output using 32-bit integers and the `netcdf4` format. Almost as fast is the case where the input format is `netcdf3` and the output `netcdf4`. The `64-bit` integer options with both input and output using `netcdf4` are slightly slower, but this is probably due to the doubling of the size of the integer data being read and written.
|
||
|
|
||
|
The output mesh in this case consisted of 37.3 million elements and 38.5 million nodes in a grid of 46 x 46 unit cells. There were 56 unit cell template meshes.
|