Wrapper Modules
FFTW Abstraction
In the core simulation algorithm, the velocity-pressure states are required to be transformed between physical and fourier domain. This is where the Fast Fourier Transform in the West (FFTW) comes into play. Since the transformation is done in-place for each array, the main way the FFTW library interfaces with these arrays are with a FFTW plan.
fft_plan * array
However, a distinction is required to be made for the discrete Fourier coefficients with the Fourier series coefficient which has a scaling of nx * nz
between them. So in addition to a transformation operation as shown in the code snippet above, a normalisation of nx * nz
is required as well.
fft_plan * array
array[:] ./= nx * nz
For the treatment of non-linear terms, an additional dealiasing-mode is required, leading to the zero-ing of frequencies larger than a threshold.
# De-aliasing
if (dealias_mode)
for ix in 1:nx
if (abs(tf.freq_kx[ix]) > tf.freq_kx_max)
arr[:, ix, :] .= 0.0 + 0.0im
end
end
for iz in 1:nz
if (abs(tf.freq_kz[iz]) > tf.freq_kz_max)
arr[:, :, iz] .= 0.0 + 0.0im
end
end
end
The combination of all these optional and package specific features leads to the public APIs written for the Transformations
module, allowing the developer to repeatably use the following transformation function as ease.
physical2fourier!(
tf::Transformer{T}, arr::Array{Complex{T},3}, frac_mode::Bool, dealias_mode::Bool
)
HDF5 Abstraction
For input/output of velocity-pressure state arrays, the HDF5 package is used with its defined groups and dataset formalism. The public APIs written for the InputOutputManagers
module allows the group definition and datasets to be written out in code, having a clear separation of responsibilities, segregated by modules. Hence, for a user reading code in run_simulation!(ns::NavierStokesPropagator)
, write_flowfield(io_m::InputOutputManager)
would clearly been seen as an abstraction of output. The alternative way of not writing the InputOutputManagers
wrapper module would require simulation code and HDF5 API calls to be interleaved in run_simulation!(ns::NavierStokesPropagator)
, leading to convoluted and harder-to-read code.
A key point to make here is the switching of the x
and y
dimensions in the simulation code for 3D arrays. In the simulation, since we predominantly use array operators in y
and loop through x
and z
dimensions, it makes sense for the arrays to be arranged in (y,x,z)
, allowing for contiguous memory layout to be achieved. However, normal convention would expect for a (x,y,z)
layout, hence, this extra layer of complication can be readily addressed by the InputOutputManagers
module which is responsible for the HDF5 abstraction. As seen in read_flowfield!(io_m::InputOutputManger, state::State)
, we have calls to PermutedDimsArray
handling the permutation of dimensions for the user.
# Read State and Attribute
h5open(input_path, "r") do fid
g::HDF5.Group = open_group(fid, "/")
# U Velocity
state.u[:, :, :] = PermutedDimsArray(read(g, "u"), (2, 1, 3))
# V Velocity
state.v[:, :, :] = PermutedDimsArray(read(g, "v"), (2, 1, 3))
# W Velocity
state.w[:, :, :] = PermutedDimsArray(read(g, "w"), (2, 1, 3))
end