Skip to content

Commit 8f4b504

Browse files
authored
Merge pull request #138 from JuliaGeodynamics/bk-asagi
Support for ASAGI files
2 parents ecddbdf + 2972971 commit 8f4b504

File tree

10 files changed

+352
-66
lines changed

10 files changed

+352
-66
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
2020
LightXML = "9c8b4983-aa76-5018-a973-4c85ecc9e179"
2121
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
2222
MeshIO = "7269a6da-0436-5bbc-96c2-40638cbb6118"
23+
NCDatasets = "85f8d34a-cbdd-5861-8df4-14fed0d494ab"
2324
NearestNeighbors = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
2425
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
2526
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
@@ -62,8 +63,8 @@ NearestNeighbors = "0.2 - 0.4"
6263
Parameters = "0.9 - 0.12"
6364
SpecialFunctions = "1.0, 2"
6465
StaticArrays = "1"
65-
WriteVTK = "1"
6666
WhereTheWaterFlows = "0.10"
67+
WriteVTK = "1"
6768
julia = "1.9"
6869

6970
[extras]

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ makedocs(;
113113
"Data Structures" => "man/datastructures.md",
114114
"Data Import" => "man/dataimport.md",
115115
"Projection" => "man/projection.md",
116+
"ASAGI" => "man/asagi_io.md",
116117
"Paraview output" => "man/paraview_output.md",
117118
"Paraview collection" => "man/paraview_collection.md",
118119
"Surfaces" => "man/surfaces.md",

docs/src/man/asagi_io.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# ASAGI I/O
2+
3+
[ASAGI](https://github.com/TUM-I5/ASAGI) is a fileformat that is used by codes such as SeisSol or ExaHype. It employs the NetCDF4 data format.
4+
5+
We can read ASAGI files into GMG, and write GMG datasets to ASAGI format, which makes it straightforward to use GMG to create a model setup or to couple the results of geodynamic simulations (e.g., produced by LaMEM) with codes that support ASAGI.
6+
7+
```@docs
8+
read_ASAGI
9+
write_ASAGI
10+
```

src/GeophysicalModelGenerator.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ include("Setup_geometry.jl")
4444
include("stl.jl")
4545
include("ProfileProcessing.jl")
4646
include("IO.jl")
47+
include("IO_ASAGI.jl")
4748
include("event_counts.jl")
4849
include("surface_functions.jl")
4950
include("movies_from_pics.jl")

src/IO_ASAGI.jl

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# This allows creating ASAGI files from GMG structures, which can be used for example
2+
# as input for codes such as SeisSol or ExaHype
3+
#
4+
# see https://github.com/TUM-I5/ASAGI
5+
#
6+
#
7+
8+
using NCDatasets
9+
using NCDatasets: nc_create, NC_NETCDF4, NC_CLOBBER, NC_NOWRITE, nc_def_dim, nc_def_compound, nc_insert_compound, nc_def_var, nc_put_var, nc_close, NC_INT, nc_unsafe_put_var, libnetcdf, check, ncType, nc_open, nc_inq_vartype, nc_inq_compound_nfields, nc_inq_compound_size, nc_inq_compound_name, nc_inq_compound_fieldoffset,nc_inq_compound_fieldndims,nc_inq_compound_fielddim_sizes, nc_inq_compound_fieldname, nc_inq_compound_fieldindex, nc_inq_compound_fieldtype, nc_inq_compound, nc_inq_varid, nc_get_var!, nc_insert_array_compound, nc_def_vlen
10+
11+
export write_ASAGI, read_ASAGI
12+
13+
"""
14+
write_ASAGI(fname::String, Data::CartData;
15+
fields::Union{Nothing, Tuple}=nothing,
16+
km_to_m::Bool=false)
17+
18+
Writes a CartData structure `Data` to an ASAGI file, which can be read by SeisSol or ExaHype.
19+
You can optionally pass a tuple with fields to be written. Note that we can only write individual (scalar) fields to disk,
20+
so vector or tensor fields needs to be split first
21+
"""
22+
function write_ASAGI(fname::String, Data::CartData;
23+
fields::Union{Nothing, Tuple}=nothing,
24+
km_to_m::Bool=false)
25+
26+
nx,ny,nz = size(Data.x)
27+
x = Data.x.val[:,1,1]
28+
y = Data.y.val[1,:,1]
29+
z = Data.z.val[1,1,:]
30+
if km_to_m==true
31+
println("convert to meters")
32+
x = x.*1000
33+
y = y.*1000
34+
z = z.*1000
35+
end
36+
37+
# Transfer data to a single array with NamedTuple entries
38+
material = fields_to_namedtuple(Data.fields, fields)
39+
40+
fname_asagi = fname*"_ASAGI.nc"
41+
42+
#ncid = nc_create(fname_asagi, NC_NETCDF4|NC_CLOBBER)
43+
ds = NCDataset(fname_asagi,"c", format=:netcdf4)
44+
45+
# Write dimensions
46+
x_dimid = nc_def_dim(ds.ncid, "x", nx)
47+
y_dimid = nc_def_dim(ds.ncid, "y", ny)
48+
z_dimid = nc_def_dim(ds.ncid, "z", nz)
49+
50+
v_x = defVar(ds,"x",eltype(x),("x",))
51+
v_y = defVar(ds,"y",eltype(x),("y",))
52+
v_z = defVar(ds,"z",eltype(x),("z",))
53+
v_x[:] = x
54+
v_y[:] = y
55+
v_z[:] = z
56+
57+
# add Tuple with data to file
58+
dimids = [x_dimid, y_dimid, z_dimid]
59+
T = eltype(material)
60+
typeid = nc_def_compound(ds.ncid, sizeof(T), "material")
61+
62+
for i = 1:fieldcount(T)
63+
#local dim_sizes
64+
offset = fieldoffset(T,i)
65+
nctype = ncType[fieldtype(T,i)]
66+
nc_insert_compound(
67+
ds.ncid, typeid, fieldname(T,i),
68+
offset, nctype)
69+
end
70+
71+
varid = nc_def_var(ds.ncid, "data", typeid, reverse(dimids))
72+
nc_put_var(ds.ncid, varid, material)
73+
74+
# close file
75+
close(ds)
76+
77+
return fname_asagi
78+
end
79+
80+
81+
82+
# Transfer fields to a single array with NamedTuple entries
83+
function fields_to_namedtuple(fields::NamedTuple, selected_fields)
84+
names = keys(fields)
85+
if !isnothing(selected_fields)
86+
names = selected_fields
87+
end
88+
nfield = length(names)
89+
ndim = length(size(fields[1]))
90+
91+
s2 = NamedTuple{names}(zeros(nfield))
92+
93+
# check that they are all DiskArrays
94+
for ifield in 1:nfield
95+
if !isa(getproperty(fields,names[ifield]), Array)
96+
@show typeof(getproperty(fields,names[ifield]))
97+
error("Field $(names[ifield]) is not an Array but instead a $(typeof(getproperty(fields,names[ifield]))); only Arrays are supported")
98+
end
99+
end
100+
101+
material = Array{typeof(s2),ndim}(undef, size(fields[1]))
102+
for I in eachindex(material)
103+
data_local = []
104+
for ifield in 1:nfield
105+
push!(data_local,getproperty(fields,names[ifield])[I])
106+
107+
end
108+
109+
local_tup = NamedTuple{names}(data_local)
110+
111+
material[I] = local_tup
112+
end
113+
114+
return material
115+
end
116+
117+
"""
118+
data::CartData = read_ASAGI(fname_asagi::String)
119+
120+
This reads a 3D ASAGI NetCDF file, which is used as input for a number of codes such as SeisSol.
121+
It returns a CartData dataset
122+
"""
123+
function read_ASAGI(fname_asagi::String)
124+
125+
@assert fname_asagi[end-2:end]==".nc"
126+
127+
ds = NCDataset(fname_asagi,"r")
128+
129+
x = ds["x"][:];
130+
y = ds["y"][:];
131+
z = ds["z"][:];
132+
133+
nx,ny,nz = length(x),length(y),length(z)
134+
135+
data_set_names = keys(ds)
136+
id = findall(data_set_names.!="x" .&& data_set_names.!="y" .&& data_set_names.!="z")
137+
data_name = data_set_names[id]
138+
139+
varid = nc_inq_varid(ds.ncid,data_name[1])
140+
xtype = nc_inq_vartype(ds.ncid,varid)
141+
142+
# retrieve names of the fields
143+
numfields = nc_inq_compound_nfields(ds.ncid,xtype)
144+
cnames = Symbol.(nc_inq_compound_fieldname.(ds.ncid,xtype,0:(numfields-1)))
145+
146+
types = []
147+
for fieldid = 0:(numfields-1)
148+
local dim_sizes
149+
fT = NCDatasets.jlType[nc_inq_compound_fieldtype(ds.ncid,xtype,fieldid)]
150+
151+
fieldndims = nc_inq_compound_fieldndims(ds.ncid,xtype,fieldid)
152+
153+
if fieldndims == 0
154+
push!(types,fT)
155+
else
156+
dim_sizes = nc_inq_compound_fielddim_sizes(ds.ncid,xtype,fieldid)
157+
fT2 = NTuple{Int(dim_sizes[1]),fT}
158+
push!(types,fT2)
159+
end
160+
end
161+
162+
# Create a single NamedTuple with correct type and names
163+
data_element = ();
164+
for ifield=1:numfields
165+
data_element = (data_element..., types[ifield](0.0))
166+
end
167+
168+
T2 = typeof(NamedTuple{(cnames...,)}(data_element))
169+
data = Array{T2,3}(undef,nx,ny,nz)
170+
nc_get_var!(ds.ncid, varid, data)
171+
172+
# At this stage, data is an array of NamedTuple with correct names & types
173+
#
174+
# Now split them into separate fields.
175+
read_fields_data = ()
176+
for ifield=1:numfields
177+
data_1 = zeros(types[ifield],nx,ny,nz)
178+
179+
for I in CartesianIndices(data)
180+
loc = data[I]
181+
data_1[I] = getproperty(loc, cnames[ifield])
182+
end
183+
184+
read_fields_data = (read_fields_data..., data_1)
185+
end
186+
read_fields = NamedTuple{(cnames...,)}(read_fields_data)
187+
188+
close(ds)
189+
190+
return CartData(xyz_grid(x,y,z)..., read_fields)
191+
end

src/utils.jl

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,14 @@ function dropnames(namedtuple::NamedTuple, names::Tuple{Vararg{Symbol}})
100100
end
101101

102102
"""
103-
V = removefield(V::AbstractGeneralGrid,field_name::String)
103+
V = removefield(V::AbstractGeneralGrid,field_name::Symbol)
104104
105105
Removes the field with name `field_name` from the GeoData or CartData dataset
106106
107107
"""
108-
function removefield(V::AbstractGeneralGrid,field_name::String)
108+
function removefield(V::AbstractGeneralGrid,field_name::Symbol)
109109
fields_new = V.fields;
110-
fields_new = dropnames(fields_new, (Symbol(field_name),))
110+
fields_new = dropnames(fields_new, (field_name,))
111111

112112
if isa(V,GeoData)
113113
V = GeoData(V.lon.val,V.lat.val,V.depth.val,fields_new)
@@ -120,6 +120,30 @@ function removefield(V::AbstractGeneralGrid,field_name::String)
120120
return V
121121
end
122122

123+
"""
124+
V = removefield(V::AbstractGeneralGrid,field_name::String)
125+
126+
Removes the field with name `field_name` from the GeoData or CartData dataset
127+
128+
"""
129+
function removefield(V::AbstractGeneralGrid,field_name::String)
130+
return removefield(V,Symbol(field_name))
131+
end
132+
133+
"""
134+
V = removefield(V::AbstractGeneralGrid,field_name::NTuple{N,Symbol})
135+
136+
Removes the fields in the tuple `field_name` from the GeoData or CartData dataset
137+
138+
"""
139+
function removefield(V::AbstractGeneralGrid,field_name::NTuple{N,Symbol}) where N
140+
141+
for ifield=1:N
142+
V = removefield(V,field_name[ifield])
143+
end
144+
145+
return V
146+
end
123147

124148

125149
"""
@@ -1821,3 +1845,6 @@ function inpoly_fast(PolyX::Vector{T}, PolyY::Vector{T}, x::T, y::T) where T <:
18211845
end
18221846
return inside
18231847
end
1848+
1849+
1850+

0 commit comments

Comments
 (0)