Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ae2b764
read/write enum arrays
Alexander-Barth Jul 18, 2026
38be7ac
update tests
Alexander-Barth Jul 18, 2026
ed6ed2d
add src/enum_types.jl
Alexander-Barth Jul 18, 2026
e7932ce
enum attributes
Alexander-Barth Jul 18, 2026
ab3e95d
test enum attributes
Alexander-Barth Jul 19, 2026
122397e
vlen-array of enums
Alexander-Barth Jul 19, 2026
e694cfb
enums in compound types
Alexander-Barth Jul 20, 2026
2b42a56
fix for julia 1.10
Alexander-Barth Jul 20, 2026
d8d8c54
restructure nctypeid and create_type (1)
Alexander-Barth Jul 20, 2026
a20cb32
restructure nctypeid and create_type (2)
Alexander-Barth Jul 20, 2026
9e288df
restructure nctypeid and create_type (3)
Alexander-Barth Jul 20, 2026
2c6ba36
defCompoundType/defEnumType -> defType
Alexander-Barth Jul 20, 2026
4292570
clean-up
Alexander-Barth Jul 20, 2026
5741726
remove benchmark from test
Alexander-Barth Jul 20, 2026
448c7cc
ncgen for enums; xtype -> typeid
Alexander-Barth Jul 20, 2026
663deca
update docs
Alexander-Barth Jul 20, 2026
1241c2b
benchmark parametric struct for NetCDF enums
Alexander-Barth Jul 23, 2026
d7553f2
avoid eval for reconstructed types
Alexander-Barth Jul 23, 2026
342dcbe
avoid suffix not available in julia 1.10
Alexander-Barth Jul 23, 2026
cddad51
drop mod parameter
Alexander-Barth Jul 23, 2026
148cd2c
reduce warnings in tests
Alexander-Barth Jul 23, 2026
ee3f625
run the output of ncgen in a separate process
Alexander-Barth Jul 23, 2026
ff82ad8
rename usertype -> typemap (similar to JLD2)
Alexander-Barth Jul 23, 2026
759ab1a
enum scalars
Alexander-Barth Jul 24, 2026
4706de4
typemap parameter
Alexander-Barth Jul 24, 2026
a64ad60
type casting
Alexander-Barth Jul 24, 2026
a9e1703
ncgen for structs
Alexander-Barth Jul 24, 2026
b91cf9c
custom printing for invalid enums
Alexander-Barth Jul 24, 2026
f3b27be
NCDatasets.enums always returns a named tuple; more docs
Alexander-Barth Jul 24, 2026
bc4fce1
add more docs
Alexander-Barth Jul 24, 2026
96ca068
test vlen-array of structs
Alexander-Barth Jul 24, 2026
d490647
update docs
Alexander-Barth Jul 24, 2026
4c68931
improve docs
Alexander-Barth Jul 24, 2026
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: 1 addition & 1 deletion docs/src/dataset.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Otherwise, we attempt to use standard structures from the Julia standard library
A NetCDF group is a dataset (with variables, attributes, dimensions and sub-groups) and
can be arbitrarily nested.
A group is created with `defGroup` and accessed via the `group` property of
a `NCDataset`.
a `NCDataset`. Groups can be nested in arbitrary depth.

```julia
# create the variable "temperature" inside the group "forecast"
Expand Down
83 changes: 74 additions & 9 deletions docs/src/experimental_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ NCDatasets.filter
## Experimental MPI support

Experimental MPI support is available as a package extension. It is important to load `MPI` in addition to `NCDatasets` to enable this package extension.
All metadata operators (creating dimensions, variables, attributes, groups or types) must be done *collectively*.
Reading and writing data of netCDF variables can be done *independently* (default) or *collectively*. If a variable (or whole dataset) is marked for *collectively* data access, the underlying HDF5 library can enable additional optimization.
All metadata operations (creating dimensions, variables, attributes, groups or types) must be done *collectively*.
Reading and writing data of netCDF variables can be done *independently* (default) or *collectively*. If a variable (or whole dataset) is marked for *collective* data access, the underlying HDF5 library can enable additional optimization.
More information is available in the [NetCDF documentation](https://web.archive.org/web/20240414204638/https://docs.unidata.ucar.edu/netcdf-c/current/parallel_io.html).
For the MPI IO standard, collective IO means that all MPI processes execute all the same OI functions (calling for example [MPI\_File\_write\_at\_all](https://www.mpich.org/static/docs/v4.1/www3/MPI_File_write_at_all.html)). If this is not the case, then the access is independently (calling for example [MPI\_File\_write\_at](https://www.mpich.org/static/docs/v4.1/www3/MPI_File_write_at.html)).
For the MPI IO standard, collective IO means that all MPI processes execute all the same IO functions (calling for example [MPI\_File\_write\_at\_all](https://www.mpich.org/static/docs/v4.1/www3/MPI_File_write_at_all.html)). If this is not the case, then the access is independent (calling for example [MPI\_File\_write\_at](https://www.mpich.org/static/docs/v4.1/www3/MPI_File_write_at.html)).

Only the NetCDF 4 format can be currently used for parallel access. On Windows, the MPI interface is [currently unsupported](https://github.com/JuliaPackaging/Yggdrasil/issues/8523). Help from developers with access to Windows would be appreciated.

Expand Down Expand Up @@ -60,39 +60,62 @@ NCDataset(comm::MPI.Comm,filename::AbstractString,mode::AbstractString)
NCDatasets.paraccess
```

# NetCDF compound types
# NetCDF 4 user-defined types

NCDatasets.jl supports variable-length arrays, compound types (`struct` in julia) and enums.
Variable-length arrays can be composed of primitive types, compound types or enums.
Opaque variables are currently not supported.

## NetCDF compound types

NetCDF 4 allows the users to define their own type, in particular, compound types which correspond to Julia structures.
Compound types can be composed of primitive types, other compound types or enums or vectors of fixed sizes of these types.
An array of such structures can be written to and loaded from a NetCDF file. For example:

```julia
fname = download("https://raw.githubusercontent.com/Unidata/netcdf-c/refs/tags/v4.8.1/dap4_test/nctestfiles/test_struct_array.nc")
ds = NCDataset(fname)

array = ds["s"][:,:]
typeof(array)

propertynames(array[1,1])
# output
# Matrix{c_t} (alias for Array{NCDatasets.ReconstructedTypes_123.c_t, 2})
#
# (:x, :y)


# access individual elements
array[1,1].x
# output
#
# 1


# access all fields x
getproperty.(array,:x)


struct MyCompoundType
x::Int32
y::Int32
end

NCDatasets.usertype!(ds,"c_t",MyCompoundType)
ds = NCDataset(fname, typemap = Dict("c_t" => MyCompoundType))
array = ds["s"][:,:]
typeof(array)
# output
#
# Matrix{MyCompoundType} (alias for Array{MyCompoundType, 2})

```

It is preferable in fact that the user defines the compound type as a julia struct and register it using the `NCDatasets.usertype!`.
It is preferable in fact that the user defines the compound type as a julia struct and register it using the `typemap` argument.
Users should not rely on the type name generated internally by `NCDatasets`. Note also that Julia treats two types as different even if they have
the same memory layout.
When defining these structures, avoid using the type `Int` as its size is platform-dependent. Vectors of fixed length can also be used in struct fields.
They should be declared as `NTuple`s (see
[Calling C and Fortran Code](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#Struct-Type-Correspondences) for the manual).
The corresponding julia type definitions of NetCDF compound types and enums can be automatically generated using the `ncgen` function.

Here is an example to write such a dataset:

Expand All @@ -115,7 +138,8 @@ NCDataset(fname,"c") do ds
end
```

An important restriction is that the `struct` must be immutable and contain only immutable fields. The memory layout of a `mutable struct` is not compatible with the layout expected by the C library. To update a single field in a struct, the user has to recreate the structure. For example to update the field `x` of the the first element to 10:
Nested struct/compound types (possibly containing enums) are supported.
An important restriction is that the `struct` must be immutable and contain only immutable fields. The memory layout of a `mutable struct` is not compatible with the layout expected by the C library. To update a single field in a struct, the user has to recreate the structure. For example to update the field `x` of the first element to 10:

```julia
array2[1] = MyCompoundType(10,array2[1].y)
Expand All @@ -127,3 +151,44 @@ For large structures, it might be beneficial to use [Accessors](https://github.c
using Accessors
@set array2[1].x = 10
```

## NetCDF enum type

NetCDF enum types are implemented as Julia enum types. This example shows how to create an enum type and write as an vector of enums to a NetCDF file:

``` julia
@enum TestEnum::Int8 good=1 bad=2 ugly=3

data = [good, bad, good, ugly]
fname = tempname()
NCDataset(fname,"c") do ds
# the Julia type name is used by default in the netcdf file
# dimension "dim" is created automatically
ncv = defVar(ds,"data",data,("dim",))
end

```

The julia type `TestEnum` must be internally reconstructed unless
it is provided via the `typemap` parameter (which is preferred).
Loading the data:

``` julia
data2 = NCDataset(fname,"r", typemap = Dict("TestEnum" => TestEnum)) do ds
ds["data"][:]
end
```

The array of enums `data` can be converted to, for example, a `CategoricalArray` of strings using:

```julia
using CategoricalArrays
enum_dict = Dict(inst => string(inst) for inst in instances(eltype(data)))
ca = CategoricalArray([enum_dict[x] for x in data]; levels=collect(values(enum_dict)))
```


```@docs
NCDatasets.enums
NCDatasets.typemap!
```
1 change: 1 addition & 0 deletions src/NCDatasets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ include("errorhandling.jl")
include("lock.jl")
include("netcdf_c.jl")
include("compound_types.jl")
include("enum_types.jl")
include("dataset.jl")
include("attributes.jl")
include("dimensions.jl")
Expand Down
19 changes: 16 additions & 3 deletions src/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ ds = NCDataset("file.nc")
title = ds.attrib["title"]
```
"""
attrib(ds::Union{Dataset,Variable},name::SymbolOrString) = nc_get_att(_ncid(ds),_varid(ds),name)

function attrib(ds::Union{Dataset,Variable},name::SymbolOrString)
xtype,len = nc_inq_att(_ncid(ds),_varid(ds),name)
attribT = _jltype(_ncid(ds),xtype,_dataset(ds).typemap)
nc_get_att(_ncid(ds),_varid(ds),name; attribT)
end


"""
Expand Down Expand Up @@ -81,12 +84,22 @@ ds.attrib["title"] = ["my title"]
close(ds)
"""
function defAttrib(ds::Union{Dataset,Variable},name::SymbolOrString,data)
if !(data isa Union{String,Enum,NCEnum}) && (ndims(data) > 1)
error("Attributes must be a scalar or a vector (while writting attribute '$name' to file $(path(_dataset(ds))).")
end

# make sure that the file is in define mode
defmode(_dataset(ds)) do
return nc_put_att(_ncid(ds),_varid(ds),name,data)
typeid = nctypeid(_dataset(ds),(data isa AbstractVector ? eltype(data) : typeof(data)))
return nc_put_att(_ncid(ds),_varid(ds),name,data; typeid)
end
end

function defAttrib(ds::Union{Dataset,Variable},name::SymbolOrString,data::Vector{Any})
T = promote_type(typeof.(data)...)
@debug "promoted type for attribute $T"
defAttrib(ds,name,T.(data))
end

"""
Base.haskey(a::Attributes,name::SymbolOrString)
Expand Down
16 changes: 1 addition & 15 deletions src/cfvariable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,21 +109,7 @@ function defVar(ds::NCDataset,name::SymbolOrString,vtype::DataType,dimnames;
defmode(ds) do
dimids = Cint[nc_inq_dimid(ds.ncid,dimname) for dimname in dimnames[end:-1:1]]

typeid =
if vtype <: Vector
# variable-length type
typeid = nc_def_vlen(ds.ncid, typename, ncType[eltype(vtype)])
elseif haskey(ncType, vtype)
ncType[vtype]
elseif length(fieldnames(vtype)) > 0
@debug "assume type $vtype is a struct "
typename = (isnothing(typename) ? string(vtype) : typename)
defCompoundType(ds,vtype,typename)
else
@warn "unsupported type: class=$(class)"
Nothing
end

typeid = nctypeid(ds,vtype; typename)
varid = nc_def_var(ds.ncid,name,typeid,dimids)

if chunksizes !== nothing
Expand Down
Loading
Loading