Skip to content
This repository was archived by the owner on Aug 18, 2021. It is now read-only.

Add 3d functionality to Trixi2Img #18

Merged
merged 23 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.2.1-pre"

[deps]
ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63"
EllipsisNotation = "da5c29d0-fa7d-589e-88eb-ea29b0a81949"
GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
Expand All @@ -13,6 +14,7 @@ TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"

[compat]
ArgParse = "1.1"
EllipsisNotation = "1"
GR = "0.51, 0.52"
Glob = "1.3"
HDF5 = "0.13"
Expand Down
19 changes: 16 additions & 3 deletions src/convert.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ Convert two-dimensional Trixi-generated output files to image files (PNG or PDF)
- `nvisnodes`: Number of visualization nodes per element (default: twice the number of DG nodes).
A value of `0` (zero) uses the number of nodes in the DG elements.
- `max_supported_level`: Maximum cell refinement level supported for plotting.
- `slice_axis`: Axis orthogonal to the slice plane. Will be ignored in 2D. May be :x, :y or :z.
- `slice_axis_intercept`: Point on the slice axis where it intersects with the slice plane.

# Examples
```julia
julia> trixi2img("out/solution_000*.h5")
[...]

julia> trixi2img("out/solution_000*.h5", slice_axis=:z, slice_axis_intercept=0.5) # Slice plane will be z=0.5
[...]
```
"""
function trixi2img(filename::AbstractString...;
format=:png, variables=[], verbose=false, grid_lines=false,
output_directory=".", nvisnodes=nothing, max_supported_level=11)
output_directory=".", nvisnodes=nothing, max_supported_level=11,
slice_axis=:z, slice_axis_intercept=0)
# Reset timer
reset_timer!()

Expand Down Expand Up @@ -65,7 +71,7 @@ function trixi2img(filename::AbstractString...;
# Read mesh
verbose && println("| Reading mesh file...")
@timeit "read mesh" (center_level_0, length_level_0,
leaf_cells, coordinates, levels) = read_meshfile(meshfile)
leaf_cells, coordinates, levels, ndims) = read_meshfile(meshfile)

# Read data
verbose && println("| Reading data file...")
Expand All @@ -92,6 +98,14 @@ function trixi2img(filename::AbstractString...;
# level-0-cell) that contains the number of visualization nodes for any
# refinement level to visualize on an equidistant grid

if ndims == 3
verbose && println("| Extracting 2D slice...")
# convert 3d unstructured data to 2d slice
@timeit "extract 2D slice" (unstructured_data, coordinates, levels,
center_level_0) = unstructured_2d_to_3d(unstructured_data, coordinates,
levels, length_level_0, center_level_0, slice_axis, slice_axis_intercept)
end

# Normalize element coordinates: move center to (0, 0) and domain size to [-1, 1]²
n_elements = length(levels)
normalized_coordinates = similar(coordinates)
Expand Down Expand Up @@ -180,4 +194,3 @@ function trixi2img(filename::AbstractString...;
print_timer()
println()
end

118 changes: 117 additions & 1 deletion src/interpolate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,123 @@ function cell2node(cell_centered_data::AbstractArray{Float64})
end


# Convert 3d unstructured data to 2d slice.
# Additional to the new unstructured data updated coordinates, levels and
# center coordinates are returned.
function unstructured_2d_to_3d(unstructured_data::AbstractArray{Float64},
coordinates::AbstractArray{Float64},
levels::AbstractArray{Int}, length_level_0::Float64,
center_level_0::AbstractArray{Float64},
slice_axis, slice_axis_intercept)

if slice_axis === :x
slice_axis_dimension = 1
other_dimensions = [2, 3]
elseif slice_axis === :y
slice_axis_dimension = 2
other_dimensions = [1, 3]
elseif slice_axis === :z
slice_axis_dimension = 3
other_dimensions = [1, 2]
else
error("illegal dimension '$slice_axis', supported dimensions are :x, :y, and :z")
end

# Limits of domain in slice_axis dimension
lower_limit = center_level_0[slice_axis_dimension] - length_level_0 / 2
upper_limit = center_level_0[slice_axis_dimension] + length_level_0 / 2

if slice_axis_intercept < lower_limit || slice_axis_intercept > upper_limit
error(string("Slice plane $slice_axis = $slice_axis_intercept outside of domain. ",
"$slice_axis must be between $lower_limit and $upper_limit"))
end

# Extract data shape information
n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data)

# Get node coordinates for DG locations on reference element
nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)

# New unstructured data has one dimension less.
# The redundant element ids are removed later.
@views new_unstructured_data = similar(unstructured_data[1, ..])

# Declare new empty arrays to fill in new coordinates and levels
new_coordinates = Array{Float64}(undef, 2, n_elements)
new_levels = Array{eltype(levels)}(undef, n_elements)

# Counter for new element ids
new_id = 0

# Save vandermonde matrices in a Dict to prevent redundant generation
vandermonde_to_2d = Dict()

for element_id in 1:n_elements
# Distance from center to border of this element (half the length)
element_length = length_level_0 / 2^levels[element_id]
min_coordinate = coordinates[:, element_id] .- element_length / 2
max_coordinate = coordinates[:, element_id] .+ element_length / 2

# Check if slice plane and current element intersect.
# The first check uses a "greater but not equal" to only match one cell if the
# slice plane lies between two cells.
# The second check is needed if the slice plane is at the upper border of
# the domain due to this.
if !((min_coordinate[slice_axis_dimension] <= slice_axis_intercept &&
max_coordinate[slice_axis_dimension] > slice_axis_intercept) ||
(slice_axis_intercept == upper_limit &&
max_coordinate[slice_axis_dimension] == upper_limit))
# Continue for loop if they don't intersect
continue
end

# This element is of interest
new_id += 1

# Add element to new coordinates and levels
new_coordinates[:, new_id] = coordinates[other_dimensions, element_id]
new_levels[new_id] = levels[element_id]

# Construct vandermonde matrix (or load from Dict if possible)
normalized_intercept =
(slice_axis_intercept - min_coordinate[slice_axis_dimension]) /
element_length * 2 - 1

if haskey(vandermonde_to_2d, normalized_intercept)
vandermonde = vandermonde_to_2d[normalized_intercept]
else
# Generate vandermonde matrix to interpolate values at nodes_in to one value
vandermonde = polynomial_interpolation_matrix(nodes_in, [normalized_intercept])
vandermonde_to_2d[normalized_intercept] = vandermonde
end

# 1D interpolation to specified slice plane
for i in 1:n_nodes_in
for ii in 1:n_nodes_in
if slice_axis == :x
data = unstructured_data[:, i, ii, element_id, :]
elseif slice_axis == :y
data = unstructured_data[i, :, ii, element_id, :]
elseif slice_axis == :z
data = unstructured_data[i, ii, :, element_id, :]
end
value = interpolate_nodes(permutedims(data), vandermonde, n_variables)
new_unstructured_data[i, ii, new_id, :] = value[:, 1]
end
end
end

# Remove redundant element ids
unstructured_data = new_unstructured_data[:, :, 1:new_id, :]
new_coordinates = new_coordinates[:, 1:new_id]
new_levels = new_levels[1:new_id]

center_level_0 = center_level_0[other_dimensions]

return unstructured_data, new_coordinates, new_levels, center_level_0
end


# Interpolate unstructured DG data to structured data (cell-centered)
function unstructured2structured(unstructured_data::AbstractArray{Float64},
normalized_coordinates::AbstractArray{Float64},
Expand Down Expand Up @@ -241,4 +358,3 @@ function calc_vertices(coordinates::AbstractArray{Float64, 2},

return x, y
end

28 changes: 19 additions & 9 deletions src/io.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using EllipsisNotation

# Use data file to extract mesh filename from attributes
function extract_mesh_filename(filename::String)
# Open file for reading
Expand All @@ -12,22 +14,22 @@ end

# Read in mesh file and return relevant data
function read_meshfile(filename::String)
n_children_per_cell = 2^ndim
# Open file for reading
h5open(filename, "r") do file
# Extract basic information
if exists(attrs(file), "ndims")
ndims_ = read(attrs(file)["ndims"])
ndims = read(attrs(file)["ndims"])
else
ndims_ = read(attrs(file)["ndim"]) # FIXME once Trixi's 3D branch is merged & released
ndims = read(attrs(file)["ndim"]) # FIXME once Trixi's 3D branch is merged & released
end
n_children_per_cell = 2^ndims
n_cells = read(attrs(file)["n_cells"])
n_leaf_cells = read(attrs(file)["n_leaf_cells"])
center_level_0 = read(attrs(file)["center_level_0"])
length_level_0 = read(attrs(file)["length_level_0"])

# Extract coordinates, levels, child cells
coordinates = Array{Float64}(undef, ndim, n_cells)
coordinates = Array{Float64}(undef, ndims, n_cells)
coordinates .= read(file["coordinates"])
levels = Array{Int}(undef, n_cells)
levels .= read(file["levels"])
Expand All @@ -50,14 +52,15 @@ function read_meshfile(filename::String)
coordinates = coordinates[:, leaf_cells]
levels = levels[leaf_cells]

return center_level_0, length_level_0, leaf_cells, coordinates, levels
return center_level_0, length_level_0, leaf_cells, coordinates, levels, ndims
end
end


function read_datafile(filename::String)
# Open file for reading
h5open(filename, "r") do file
ndims = read(attrs(file)["ndims"])
# Extract basic information
if exists(attrs(file), "polydeg")
polydeg = read(attrs(file)["polydeg"])
Expand All @@ -76,14 +79,21 @@ function read_datafile(filename::String)

# Extract data arrays
n_nodes = polydeg + 1
data = Array{Float64}(undef, n_nodes, n_nodes, n_elements, n_variables)

if ndims == 3
# Read 3d data
data = Array{Float64}(undef, n_nodes, n_nodes, n_nodes, n_elements, n_variables)
elseif ndims == 2
# Read 2d data
data = Array{Float64}(undef, n_nodes, n_nodes, n_elements, n_variables)
else
error("unsupported number of dimensions: $ndims")
end
for v = 1:n_variables
vardata = read(file["variables_$v"])
#=@show minimum(vardata), maximum(vardata)=#
@views data[:, :, :, v][:] .= vardata
@views data[.., v][:] .= vardata
end

return labels, data, n_nodes, time
end
end

1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
include("test_2d.jl")
include("test_3d.jl")
80 changes: 80 additions & 0 deletions test/test_3d.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
module Test3D

using Test
using Trixi2Img

include("test_trixi2img.jl")

# Start with a clean environment: remove Trixi output directory if it exists
outdir = "out"
isdir(outdir) && rm(outdir, recursive=true)


@testset "3D" begin
run_trixi(joinpath("3d", "parameters.toml"), n_steps_max=1)

@testset "uniform mesh as PNG" begin
@testset "default slice" begin
test_trixi2img("solution_000000.h5", outdir)
# To be able to compare hashes, we would need to make sure that exactly the same versions
# of all required libraries are installed everywhere, which does not seem to be a good option
# right now.
end

@testset "x-axis slice" begin
test_trixi2img("solution_000000.h5", outdir, slice_axis=:x, slice_axis_intercept=0.6)
end

@testset "y-axis slice" begin
test_trixi2img("solution_000000.h5", outdir, slice_axis=:y, slice_axis_intercept=-0.35)
end

@testset "z-axis slice" begin
test_trixi2img("solution_000000.h5", outdir, slice_axis=:z, slice_axis_intercept=-0.89)
end

@testset "negative border slice" begin
test_trixi2img("solution_000000.h5", outdir, slice_axis=:y, slice_axis_intercept=-1)
end

@testset "positive border slice" begin
test_trixi2img("solution_000000.h5", outdir, slice_axis=:z, slice_axis_intercept=1)
end

@testset "outside negative border slice" begin
slice_axis_intercept=-1.01
slice_axis=:x
@test_throws ErrorException(string(
"Slice plane $slice_axis = $slice_axis_intercept outside of domain. ",
"$slice_axis must be between -1.0 and 1.0")) trixi2img(
joinpath(outdir, "solution_000000.h5"); output_directory=outdir,
slice_axis=slice_axis, slice_axis_intercept=slice_axis_intercept)
end

@testset "outside positive border slice" begin
slice_axis_intercept=1.005
slice_axis=:y
@test_throws ErrorException(string(
"Slice plane $slice_axis = $slice_axis_intercept outside of domain. ",
"$slice_axis must be between -1.0 and 1.0")) trixi2img(
joinpath(outdir, "solution_000000.h5"); output_directory=outdir,
slice_axis=slice_axis, slice_axis_intercept=slice_axis_intercept)
end
end

# PDF tests do not work on Windows
if !Sys.iswindows()
@testset "uniform mesh as PDF with grid lines" begin
test_trixi2img("solution_000000.h5", outdir,
format=:pdf, grid_lines=true)
# To be able to compare hashes, we would need to make sure that exactly the same versions
# of all required libraries are installed everywhere, which does not seem to be a good option
# right now.
end
end
end

# Clean up afterwards: delete Trixi output directory
@test_nowarn rm(outdir, recursive=true)

end