From 1bd66a03e8244d424a7396222f08275b77e1d038 Mon Sep 17 00:00:00 2001 From: Stuart Daines Date: Tue, 31 Dec 2024 17:53:19 +0000 Subject: [PATCH] Simplify and tidy up FieldRecord and netcdf output Add methods to disentangle FieldRecord data format conversions from netcdf input/output: Add get_array_full(fr::FieldRecord) -> Array, (dimnames...) to convert a FieldRecord into an array suitable for writing to netcdf, with corresponding FieldRecord constructor for the inverse operation. Add variable_is_constant(fr::FieldRecord) to test whether a FieldRecord is actually time-independent, and hence record dimension can be omitted when saving to netcdf TODO PALEO still stores a record at each timestep. --- src/FieldRecord.jl | 215 ++++++++++++++++++++++++++++++++- src/OutputWriters.jl | 276 +++++++++---------------------------------- 2 files changed, 268 insertions(+), 223 deletions(-) diff --git a/src/FieldRecord.jl b/src/FieldRecord.jl index f938e3e..4fd94cb 100644 --- a/src/FieldRecord.jl +++ b/src/FieldRecord.jl @@ -96,6 +96,8 @@ end space(fr::FieldRecord{FieldData, Space, V, N, Mesh, R}) where {FieldData, Space, V, N, Mesh, R} = Space +field_data(fr::FieldRecord{FieldData, Space, V, N, Mesh, R}) where {FieldData, Space, V, N, Mesh, R} = FieldData + function PB.get_dimensions(fr::FieldRecord; expand_cartesian=false) dims_spatial = PB.get_dimensions(fr.mesh, space(fr); expand_cartesian) dims = [dims_spatial..., fr.data_dims..., PB.NamedDimension(fr.dataset.record_dim.name, length(fr))] @@ -226,6 +228,35 @@ function Base.copy(fr::FieldRecord{FieldData, Space, V, N, Mesh, R}) where {Fiel ) end +""" + PB.get_data(fr::FieldRecord; records=nothing) + +Get data records in raw format. + +`records` may be `nothing` to get all records, +an `Int` to select a single record, or a range to select multiple records. +""" +function PB.get_data(fr::FieldRecord; records=nothing) + + if isnothing(records) + data_output = fr.records + else + # bodge - fix scalar data + # if isa(records, Integer) && !isa(data_output, AbstractVector) + # data_output =[data_output] + # + if isa(records, Integer) && field_single_element(fr) + # represent a scalar as a 0D Array + data_output = Array{eltype(fr.records), 0}(undef) + data_output[] = fr.records[records] + else + data_output = fr.records[records] + end + end + + return data_output +end + """ get_array(fr::FieldRecord [, allselectargs::NamedTuple] [; coords::AbstractVector]) -> fa::FieldArray [deprecated] get_array(fr::FieldRecord [; coords::AbstractVector] [; allselectargs...]) -> fa::FieldArray @@ -601,4 +632,186 @@ function _filter_dims_coords( end return nothing -end \ No newline at end of file +end + + +# TODO time-independent variables are indicated by setting :is_constant attribute, +# but this is only recently added and FieldRecord still stores a record for every timestep +# This uses :is_constant to guess if a variable is constant, and then checks the values really are constant +function variable_is_constant(fr::FieldRecord) + + is_constant = false + # PALEO indicates time-independent variables by setting :is_constant attribute, + # but currently FieldRecord still stores all the records + if get(fr.attributes, :is_constant, false) == true + data_identical = false + for rcd in fr.records + if rcd == first(fr.records) || (all(isnan, rcd) && all(isnan, first(fr.records))) + data_identical = true + end + end + if data_identical + is_constant = true + else + @warn "variable $(fr.name) has :is_constant set but data is not constant !" + end + end + + return is_constant +end + + +""" + get_array_full(fr::FieldRecord; [expand_cartesian=false], [omit_recorddim_if_constant=true]) + -> avalues::Array, (avalues_dimnames...) + +Return all records as an n-dimensional Array, with a Tuple of dimension names + +If `omit_recorddim_if_constant`, squeeze out record dimension if `variable_is_constant(fr) == true`. +""" +function get_array_full(@nospecialize(fr::FieldRecord); expand_cartesian=false, omit_recorddim_if_constant=true) + + if omit_recorddim_if_constant + is_constant = variable_is_constant(fr) + else + is_constant = false + end + + dims_all = PB.get_dimensions(fr; expand_cartesian) + last_dim_idx = is_constant ? length(dims_all) - 1 : length(dims_all) + avalues_dimnames = Tuple(nd.name for nd in dims_all[1:last_dim_idx]) + + # create values array + if field_single_element(fr) + if is_constant + # represent a scalar as a 0D Array + avalues = Array{eltype(fr.records), 0}(undef) + avalues[] = fr.records[1] + else + avalues = fr.records + end + else + if expand_cartesian && PB.has_internal_cartesian(fr.mesh, space(fr)) + expand_fn = x -> PB.Grids.internal_to_cartesian(fr.mesh, x) + aeltype = Union{Missing, eltype(first(fr.records))} + else + expand_fn = identity + aeltype = eltype(first(fr.records)) + end + + acolons_no_recorddim = ntuple(x->Colon(), length(dims_all)-1) + + if is_constant + avalues = Array{aeltype, length(dims_all)-1}(undef, [nd.size for nd in dims_all[1:end-1]]...) + avalues[acolons_no_recorddim...] .= expand_fn(fr.records[1]) + else + avalues = Array{aeltype, length(dims_all)}(undef, [nd.size for nd in dims_all]...) + _copy_array_from_records!(avalues, acolons_no_recorddim, fr, expand_fn) + end + end + + return avalues, avalues_dimnames +end + +# function barrier optimisation +function _copy_array_from_records!(avalues, acolons_no_recorddim, fr, expand_fn) + for ri in 1:length(fr) + avalues[acolons_no_recorddim..., ri] .= expand_fn(fr.records[ri]) + end + return avalues +end + +""" + FieldRecord(dataset, avalues::Array, avalues_dimnames, attributes::Dict{Symbol, Any}; [expand_cartesian=false]) + +Create from an Array of data values `avalues` (inverse of [`get_array_full`](@ref)) + +FieldRecord type and dimensions are set from a combination of `attributes` and `dataset` dimensions and grid, where +`Space = attributes[:space]`, `FieldData = attributes[:field_data]`, `data_dims` are set from names in `attributes[:data_dims]`. + +Dimension names and size are cross-checked against supplied names in `avalues_dimnames` +""" +function FieldRecord( + dataset, + avalues::AbstractArray, + avalues_dimnames::Union{Vector{String}, Tuple{Vararg{String}}}, + attributes::Dict{Symbol, Any}; + expand_cartesian::Bool=false, +) + FieldData = attributes[:field_data] + + Space = attributes[:space] + dims_spatial = PB.get_dimensions(dataset.grid, Space; expand_cartesian) + + data_dim_names = attributes[:data_dims] + dataset_dims_all = PB.get_dimensions(dataset) + + data_dims_nd_vec = PB.NamedDimension[] + for dd_name in data_dim_names + dd_idx = findfirst(nd -> nd.name == dd_name, dataset_dims_all) + push!(data_dims_nd_vec, dataset_dims_all[dd_idx]) + end + data_dims = Tuple(data_dims_nd_vec) + + record_dim = dataset_dims_all[end] + is_constant = !(record_dim.name in avalues_dimnames) + + # check dimension names and sizes + dims_expected = [dims_spatial..., data_dims...] + if !is_constant + push!(dims_expected, record_dim) + end + @assert length(avalues_dimnames) == length(dims_expected) + for i in 1:length(avalues_dimnames) + @assert avalues_dimnames[i] == dims_expected[i].name + @assert size(avalues, i) == dims_expected[i].size + end + + if field_single_element(FieldData, length(data_dims), Space, typeof(dataset.grid)) + if is_constant + records = fill(avalues[], record_dim.size) # avalues is 0D Array if is_constant == true + else + records = avalues + end + else + if expand_cartesian && PB.has_internal_cartesian(dataset.grid, Space) + pack_fn = x -> PB.Grids.cartesian_to_internal(dataset.grid, x) + else + pack_fn = identity + end + + if is_constant + first_record = pack_fn(avalues) + records = [first_record for i in 1:record_dim.size] + else + records = _create_records_from_array!(avalues, pack_fn) + end + end + + vfr = PALEOmodel.FieldRecord( + dataset, + records, + FieldData, + data_dims, + Space, + dataset.grid, + attributes, + ) + + return vfr +end + +# function barrier optimisation +function _create_records_from_array!(avalues, pack_fn) + acolons_no_recorddim = ntuple(x->Colon(), ndims(avalues)-1) + + first_record = pack_fn(avalues[acolons_no_recorddim..., 1]) + records = Vector{typeof(first_record)}(undef, last(size(avalues))) + records[1] = first_record + + for ri in 2:last(size(avalues)) + records[ri] = pack_fn(avalues[acolons_no_recorddim..., ri]) + end + + return records +end diff --git a/src/OutputWriters.jl b/src/OutputWriters.jl index 544d73e..2f10137 100644 --- a/src/OutputWriters.jl +++ b/src/OutputWriters.jl @@ -470,18 +470,8 @@ end function PB.get_data(output::OutputMemoryDomain, varname::AbstractString; records=nothing) fr = PB.get_field(output, varname) - - if isnothing(records) - data_output = fr.records - else - data_output = fr.records[records] - # bodge - fix scalar data - if isa(records, Integer) && !isa(data_output, AbstractVector) - data_output =[data_output] - end - end - return data_output + return PB.get_data(fr; records) end function PB.show_variables( @@ -932,54 +922,36 @@ function save_netcdf( nc_dataset.attrib[k] = v end - for (domname, dom) in output.domains + for odom in values(output.domains) - dsg = NCDatasets.defGroup(nc_dataset, dom.name; attrib=[]) - record_dim_name = dom.record_dim.name # record dimension (eg tmodel) + dsg = NCDatasets.defGroup(nc_dataset, odom.name; attrib=[]) + record_dim_name = odom.record_dim.name # record dimension (eg tmodel) dsg.attrib["record_dim_name"] = record_dim_name - NCDatasets.defDim(dsg, record_dim_name, dom.record_dim.size) - dsg.attrib["record_dim_coordinates"] = dom.record_dim_coordinates + NCDatasets.defDim(dsg, record_dim_name, odom.record_dim.size) + dsg.attrib["record_dim_coordinates"] = odom.record_dim_coordinates - spatial_dimnames, spatial_unpackfn = grid_to_netcdf!(dsg, dom.grid) + grid_to_netcdf!(dsg, odom.grid) # data_dims # TODO these are NamedDimension with attached FixedCoord, where # the FixedCoord may not be present as a variable in the data, # and may also not have the correct name or even a unique name ! # As a workaround, we generate a unique name from dim_name * coord_name, and add the Variable - data_dim_names = String[d.name for d in dom.data_dims] + data_dim_names = String[d.name for d in odom.data_dims] dsg.attrib["data_dims"] = data_dim_names - coordnames_to_netcdf(dsg, "data_dims_coords", dom.data_dims_coordinates) + coordnames_to_netcdf(dsg, "data_dims_coords", odom.data_dims_coordinates) - varnames = sort(String.(keys(dom.data))) - added_isotopelinear_dims = false + varnames = sort(String.(keys(odom.data))) nc_all_vdata = [] for vname in varnames - - varfr = PB.get_field(dom, vname) - # see get_field method above - field_data = varfr.attributes[:field_data] - space = varfr.attributes[:space] - - if field_data == PB.IsotopeLinear && !added_isotopelinear_dims - NCDatasets.defDim(dsg, "isotopelinear", 2) - v = NCDatasets.defVar(dsg, "isotopelinear", ["total", "delta"], ("isotopelinear",)) - v.attrib["comment"] = "components of an isotope variable" - added_isotopelinear_dims = true - end + varfr = PB.get_field(odom, vname) - @debug "writing Variable $(dom.name).$vname records eltype $(eltype(varfr.records)) space $space" + @debug "writing Variable $(odom.name).$vname records eltype $(eltype(varfr.records)) space $(space(varfr))" nc_v, nc_vdata = variable_to_netcdf!( dsg, - record_dim_name, vname, - varfr, - field_data, - spatial_dimnames, - spatial_unpackfn, - space, - dom.grid; + varfr; define_only=define_all_first, # just define the variable, don't write the data ) @@ -1062,7 +1034,7 @@ function load_netcdf!(output::OutputMemory, filename; check_ext=true) # reading out variables is slow, so do this once dsgvars = Dict(varname=>var for (varname, var) in dsg) - grid, spatial_packfn = netcdf_to_grid(dsg, dsgvars) + grid = netcdf_to_grid(dsg, dsgvars) data_dim_names = ncattrib_as_vector(dsg, "data_dims") data_dim_sizes = [dsg.dim[ddn] for ddn in data_dim_names] @@ -1082,14 +1054,13 @@ function load_netcdf!(output::OutputMemory, filename; check_ext=true) ) for (vnamenetcdf, var) in dsgvars - if haskey(var.attrib, "var_name") # a PALEO variable, not eg a grid variable - attributes = netcdf_to_attributes(var) + attributes = netcdf_to_attributes(var) + if haskey(attributes, :var_name) # a PALEO variable, not eg a grid variable vname = netcdf_to_name(vnamenetcdf, attributes) vfr = netcdf_to_variable( odom, var, attributes, - spatial_packfn, ) odom.data[Symbol(vname)] = vfr @@ -1108,54 +1079,28 @@ end # write a variable to netcdf. cf get_field, FieldRecord function variable_to_netcdf!( ds, - record_dim_name::AbstractString, vname::AbstractString, - @nospecialize(vfr::PALEOmodel.FieldRecord), - field_data::Type{<:PB.AbstractData}, - grid_spatial_dims::NTuple{NS, String}, - spatial_unpackfn, - space::Type{S}, - grid::Union{Nothing, PB.AbstractMesh}; + @nospecialize(varfr::PALEOmodel.FieldRecord); define_only=true, -) where {NS, S <: PB.AbstractSpace} - - if space === PB.ScalarSpace - spatial_dims = () - unpackfn = identity - else - spatial_dims = grid_spatial_dims - unpackfn = spatial_unpackfn - end - - vname = name_to_netcdf(vname, vfr.attributes) +) + vname = name_to_netcdf(vname, varfr.attributes) - data_dims = vfr.attributes[:data_dims] - ND = length(data_dims) - - if variable_is_constant(vname, vfr.records, vfr.attributes) - v_records_dim = () - vdata = unpackfn(first(vfr.records)) - else - v_records_dim = (record_dim_name,) - if !PALEOmodel.field_single_element(field_data, ND, space, typeof(grid)) - # concatenate to array - vdata = vectors_to_array(vfr.records, unpackfn) - else - vdata = vfr.records - end - end + vdata, vdata_dims = PALEOmodel.get_array_full(varfr; expand_cartesian=true, omit_recorddim_if_constant=true) + field_data = PALEOmodel.field_data(varfr) + add_field_data_netcdf_dimensions(ds, field_data) field_data_dims = field_data_netcdf_dimensions(field_data) vdata = field_data_to_netcdf(field_data, vdata) + vdata_dims = (field_data_dims..., vdata_dims...) # if define_only = true, only define the variable, don't actually write the data # (allows optimisation as netcdf is slow to swap between 'define' and 'data' mode) # TODO fails if vdata contains missing (so eltype is eg Union{Missing, Float64}) at least with NCDatsets v0.12.17 # https://github.com/Alexander-Barth/NCDatasets.jl/issues/223 vdt = define_only ? eltype(vdata) : vdata - v = NCDatasets.defVar(ds, vname, vdt, (field_data_dims..., spatial_dims..., data_dims..., v_records_dim...)) + v = NCDatasets.defVar(ds, vname, vdt, vdata_dims) - attributes_to_netcdf!(v, vfr.attributes) + attributes_to_netcdf!(v, varfr.attributes) return (v, vdata) end @@ -1165,51 +1110,24 @@ function netcdf_to_variable( odom, var, attributes, - spatial_packfn, ) - field_data = attributes[:field_data] - space = attributes[:space] - data_dim_names = attributes[:data_dims] - data_dims_nd_vec = PB.NamedDimension[] - odom_dims = PB.get_dimensions(odom) - for dd_name in data_dim_names - dd_idx = findfirst(nd -> nd.name == dd_name, odom_dims) - push!(data_dims_nd_vec, odom_dims[dd_idx]) - end - data_dims = Tuple(data_dims_nd_vec) vdata = Array(var) # convert to Julia Array vdata = netcdf_to_field_data(vdata, field_data) - - packfn = space == PB.ScalarSpace ? identity : spatial_packfn - if odom.record_dim.name in NCDatasets.dimnames(var) - if !PALEOmodel.field_single_element(field_data, length(data_dims), space, typeof(odom.grid)) - vdata = array_to_vectors(vdata, packfn) - end - else - # OutputMemory has no concept of a constant variable, so replicate - vdata = packfn(vdata) - if vdata isa AbstractArray && ndims(vdata) == 0 - # 0D Array -> scalar - vdata = vdata[] - end - vdata = fill(vdata, odom.record_dim.size) # scalar -> Vector, Array/Vector -> Vector of Arrays/Vectors - end - - + fielddata_dim_names = field_data_netcdf_dimensions(field_data) + vdata_dimnames = filter(dn -> !(dn in fielddata_dim_names), NCDatasets.dimnames(var)) + vfr = PALEOmodel.FieldRecord( odom, - vdata, - field_data, - data_dims, - space, - odom.grid, - attributes, - ) - + vdata, + vdata_dimnames, + attributes; + expand_cartesian=true, + ) + return vfr end @@ -1236,11 +1154,21 @@ end # ScalarData no additional dimensions field_data_netcdf_dimensions(field_data::Type{PB.ScalarData}) = () +add_field_data_netcdf_dimensions(ds, field_data::Type{PB.ScalarData}) = nothing field_data_netcdf_dimensions(field_data::Type{PB.ArrayScalarData}) = () +add_field_data_netcdf_dimensions(ds, field_data::Type{PB.ArrayScalarData}) = nothing field_data_to_netcdf(field_data::Type, x) = x # fallback for ScalarData, ArrayScalarData # serialize IsotopeLinear as (total, delta), NB: internal representation is (total, total*delta) field_data_netcdf_dimensions(field_data::Type{PB.IsotopeLinear}) = ("isotopelinear",) +function add_field_data_netcdf_dimensions(ds, field_data::Type{PB.IsotopeLinear}) + if !haskey(ds.dim, "isotopelinear") + NCDatasets.defDim(ds, "isotopelinear", 2) + v = NCDatasets.defVar(ds, "isotopelinear", ["total", "delta"], ("isotopelinear",)) + v.attrib["comment"] = "components of an isotope variable" + end + return nothing +end field_data_to_netcdf(field_data::Type{PB.IsotopeLinear}, x) = (x.v, x.v_delta) field_data_to_netcdf(field_data::Type{PB.IsotopeLinear}, ::Missing) = (missing, missing) function field_data_to_netcdf(field_data::Type{PB.IsotopeLinear}, x::Array{T}) where {T} @@ -1304,98 +1232,6 @@ function netcdf_to_field_data(x, field_data::Type{PB.IsotopeLinear}) return xout end -# TODO PALEO has no real concept of time-independent variables -# time-independent variables are indicated by setting datatype attribute, -# but this is also used for other purposes -# This uses data_type to guess if a variable is constant, and then checks the values -function variable_is_constant(vname::AbstractString, vdata::Vector, attributes) - - is_constant = false - # PALEO indicates time-independent variables by setting data_type attribute - if get(attributes, :datatype, nothing) == Float64 - data_identical = false - for v in vdata - if v == first(vdata) || (all(isnan, v) && all(isnan, first(vdata))) - data_identical = true - end - end - if data_identical - is_constant = true - else - @warn "variable $vname has :data_type Float64 but data is not constant !" - end - end - - return is_constant -end - - -""" - vectors_to_array(vecvec::Vector{<:AbstractArray}, unpackfn) -> array::Array - -Convert vector-of-vectors to Array, -with extra last dimension = length of vecvec - -# Examples - - julia> PALEOmodel.OutputWriters.vectors_to_array([[1, 3], [2, 4]], identity) - 2×2 Matrix{Int64}: - 1 2 - 3 4 -""" -function vectors_to_array(vecvec::Vector{<:AbstractArray}, unpackfn) - firstvec = unpackfn(first(vecvec)) - vs = size(firstvec) - T = eltype(firstvec) - - a = Array{T}(undef, (vs..., length(vecvec))) - vcolons = ntuple(x->Colon(), length(vs)) - - # function barrier optimisation - function _fill(vecvec, vcolons::NTuple) - for (i, vec) in enumerate(vecvec) - a[vcolons..., i] .= unpackfn(vec) - end - end - - _fill(vecvec, vcolons) - - return a -end - -""" - array_to_vectors(array::AbstractArray, packfn) -> vecvec::Vector{<:Array} - -Create vector-of-arrays length = size of last dimension of array - -# Examples - julia> PALEOmodel.OutputWriters.array_to_vectors([1 2; 3 4], identity) # 2x2 Matrix - 2-element Vector{Vector{Int64}}: - [1, 3] - [2, 4] - - julia> PALEOmodel.OutputWriters.array_to_vectors([1 2], identity) # 1x2 Matrix - 2-element Vector{Vector{Int64}}: - [1] - [2] - - julia> PALEOmodel.OutputWriters.array_to_vectors([1, 2], identity) # 2-element Vector - 2-element Vector{Int64}: - 1 - 2 -""" -function array_to_vectors(array::AbstractArray, packfn) - - vs = size(array)[1:end-1] - vcolons = ntuple(x->Colon(), length(vs)) - - vecvec = [packfn(array[vcolons..., i]) for i in 1:last(size(array))] - - return vecvec -end - - - function subdomain_to_netcdf!(ds, name::AbstractString, subdom::PB.Grids.BoundarySubdomain) NCDatasets.defDim(ds, "subdomain_"*name, length(subdom.indices)) @@ -1438,7 +1274,7 @@ end function grid_to_netcdf!(ds, grid::Nothing) ds.attrib["PALEO_GridType"] = "Nothing" - return ((), identity) + return nothing end @@ -1461,7 +1297,7 @@ function grid_to_netcdf!(ds, grid::PB.Grids.UnstructuredVectorGrid) subdomain_to_netcdf!(ds, name, subdom) end - return (("cells", ), identity) + return nothing end function grid_to_netcdf!(ds, grid::PB.Grids.UnstructuredColumnGrid) @@ -1491,7 +1327,7 @@ function grid_to_netcdf!(ds, grid::PB.Grids.UnstructuredColumnGrid) subdomain_to_netcdf!(ds, name, subdom) end - return (("cells", ), identity) + return nothing end function grid_to_netcdf!(ds, grid::PB.Grids.CartesianArrayGrid{N}) where {N} @@ -1507,7 +1343,7 @@ function grid_to_netcdf!(ds, grid::PB.Grids.CartesianArrayGrid{N}) where {N} coordnames_to_netcdf(ds, "PALEO_grid_coords", grid.coordinates) - return (Tuple(nd.name for nd in grid.dimensions), identity) + return nothing end function grid_to_netcdf!(ds, grid::PB.Grids.CartesianLinearGrid{N}) where {N} @@ -1549,9 +1385,7 @@ function grid_to_netcdf!(ds, grid::PB.Grids.CartesianLinearGrid{N}) where {N} coordnames_to_netcdf(ds, "PALEO_grid_coords", grid.coordinates) - spatial_unpackfn = d -> PB.Grids.internal_to_cartesian(grid, d) # unpack linear representation into cartesian grid - - return (Tuple(nd.name for nd in grid.dimensions), spatial_unpackfn) + return nothing end function _cartesiandimscoords_to_netcdf( @@ -1601,7 +1435,7 @@ function netcdf_to_grid(ds::NCDatasets.Dataset, dsvars::Dict) end end -netcdf_to_grid(::Type{Nothing}, ds::NCDatasets.Dataset, dsvars::Dict) = (nothing, identity) +netcdf_to_grid(::Type{Nothing}, ds::NCDatasets.Dataset, dsvars::Dict) = nothing function netcdf_to_grid(::Type{PB.Grids.UnstructuredVectorGrid}, ds::NCDatasets.Dataset, dsvars::Dict) ncells = ds.dim["cells"] @@ -1613,7 +1447,7 @@ function netcdf_to_grid(::Type{PB.Grids.UnstructuredVectorGrid}, ds::NCDatasets. coordinates = netcdf_to_coordnames(ds, "PALEO_grid_coords") - return (PB.Grids.UnstructuredVectorGrid(ncells, cellnames, subdomains, coordinates), identity) + return PB.Grids.UnstructuredVectorGrid(ncells, cellnames, subdomains, coordinates) end function netcdf_to_grid(::Type{PB.Grids.UnstructuredColumnGrid}, ds::NCDatasets.Dataset, dsvars::Dict) @@ -1644,7 +1478,7 @@ function netcdf_to_grid(::Type{PB.Grids.UnstructuredColumnGrid}, ds::NCDatasets. columnnames = Symbol[] end - return (PB.Grids.UnstructuredColumnGrid(ncells, Icolumns, columnnames, subdomains, coordinates), identity) + return PB.Grids.UnstructuredColumnGrid(ncells, Icolumns, columnnames, subdomains, coordinates) end function netcdf_to_grid(::Type{PB.Grids.CartesianArrayGrid}, ds::NCDatasets.Dataset, dsvars::Dict) @@ -1665,7 +1499,7 @@ function netcdf_to_grid(::Type{PB.Grids.CartesianArrayGrid}, ds::NCDatasets.Data coordinates, ) - return (grid, identity) + return grid end function netcdf_to_grid(::Type{PB.Grids.CartesianLinearGrid}, ds::NCDatasets.Dataset, dsvars::Dict) @@ -1705,10 +1539,8 @@ function netcdf_to_grid(::Type{PB.Grids.CartesianLinearGrid}, ds::NCDatasets.Dat linear_index, cartesian_index, ) - - spatial_packfn = d -> PB.Grids.cartesian_to_internal(grid, d) - return (grid, spatial_packfn) + return grid end function _netcdf_to_cartesiandimscoords(