Skip to content

Commit e11a9a4

Browse files
authored
feat: implement DataVariable type with broadcasting support and metadata handling (#11)
1 parent de83481 commit e11a9a4

File tree

6 files changed

+83
-21
lines changed

6 files changed

+83
-21
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# SpaceDataModel
22

3-
[![Build Status](https://github.com/JuliaSpacePhysics/SpaceDataModel.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JuliaSpacePhysics/SpaceDataModel.jl/actions/workflows/CI.yml?query=branch%3Amain)
4-
[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaSpacePhysics.github.io/SpaceDataModel.jl/dev/)
3+
[![Documentation](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaSpacePhysics.github.io/SpaceDataModel.jl/dev/)
54
[![DOI](https://zenodo.org/badge/958430775.svg)](https://doi.org/10.5281/zenodo.15207556)
5+
6+
[![Build Status](https://github.com/JuliaSpacePhysics/SpaceDataModel.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JuliaSpacePhysics/SpaceDataModel.jl/actions/workflows/CI.yml?query=branch%3Amain)
67
[![](https://img.shields.io/badge/%F0%9F%9B%A9%EF%B8%8F_tested_with-JET.jl-233f9a)](https://github.com/aviatesk/JET.jl)
78
[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)
89
[![Coverage](https://codecov.io/gh/JuliaSpacePhysics/SpaceDataModel.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaSpacePhysics/SpaceDataModel.jl)
@@ -23,13 +24,15 @@ Pkg.add("SpaceDataModel")
2324
## Usage
2425

2526
```julia
26-
using SpaceDataModel
27+
using SpaceDataModel: Project, Instrument, DataSet, DataVariable
2728

2829
# Create a project
2930
project = Project(; name="Project Name")
3031
instrument = Instrument(; name="Instrument Name")
3132
dataset = DataSet(name="Dataset Name")
33+
var = DataVariable([1.0, 2.0, 3.0], Dict())
3234

3335
push!(project, instrument, dataset)
3436
push!(instrument, dataset)
37+
push!(dataset, var)
3538
```

src/dataset.jl

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
44
A concrete dataset with a name, data (parameters), and metadata.
55
"""
6-
@kwdef struct DataSet{T} <: AbstractDataSet
6+
@kwdef struct DataSet{T, MD} <: AbstractDataSet
77
name::String = ""
88
data::T = Dict()
9-
metadata::Dict = Dict()
9+
metadata::MD = NoMetadata()
1010
end
1111

1212
"""Construct a `DataSet` from a name and data, with optional metadata."""
@@ -49,11 +49,11 @@ ds = DataSet(lds; probe=1, data_rate="fast", data_type="des")
4949
The format string and variable patterns use placeholders like `{probe}`, `{data_rate}`,
5050
which are replaced with actual values when creating a concrete `DataSet`.
5151
"""
52-
@kwdef struct LDataSet <: AbstractDataSet
52+
@kwdef struct LDataSet{MD} <: AbstractDataSet
5353
name::String = ""
5454
format::String = ""
5555
data::Dict{String,String} = Dict()
56-
metadata::Dict = Dict()
56+
metadata::MD = NoMetadata()
5757
end
5858

5959
"Construct a `LDataSet` from a dictionary."
@@ -83,4 +83,11 @@ for f in (:getindex, :iterate, :size, :length, :keys)
8383
@eval Base.$f(var::AbstractDataSet, args...) = $f(parent(var), args...)
8484
end
8585

86-
Base.getindex(ds::DataSet{<:Dict}, i::Integer) = ds[collect(keys(ds))[i]]
86+
# https://github.com/JuliaLang/julia/issues/54454
87+
_nth(itr, n) = begin
88+
y = iterate(Base.Iterators.drop(itr, n-1))
89+
isnothing(y) ? throw(BoundsError(itr, n)) : first(y)
90+
end
91+
92+
Base.getindex(ds::DataSet, i::Integer) = _nth(values(ds.data), i)
93+
Base.push!(ds::DataSet, v) = push!(ds.data, v)

src/variable.jl

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Optional:
1010
* `meta(v)`: the metadata of the variable
1111
* `name(v)`: the name of the variable
1212
"""
13-
abstract type AbstractDataVariable{T,N} <: AbstractArray{T,N} end
13+
abstract type AbstractDataVariable{T, N} <: AbstractArray{T, N} end
1414

1515
# https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array
1616
Base.parent(var::AbstractDataVariable) = var.data
@@ -21,21 +21,21 @@ end
2121

2222
for f in (:getindex,)
2323
@eval @propagate_inbounds Base.$f(var::AbstractDataVariable, I::Vararg{Int}) = $f(parent(var), I...)
24-
@eval Base.$f(var::AbstractDataVariable, s::Union{String,Symbol}) = $f(meta(var), s)
24+
@eval Base.$f(var::AbstractDataVariable, s::Union{String, Symbol}) = $f(meta(var), s)
2525
end
2626

2727
@propagate_inbounds Base.setindex!(var::AbstractDataVariable, v, I::Vararg{Int}) = setindex!(parent(var), v, I...)
28-
Base.setindex!(var::AbstractDataVariable, v, s::Union{String,Symbol}) = setindex!(meta(var), v, s)
28+
Base.setindex!(var::AbstractDataVariable, v, s::Union{String, Symbol}) = setindex!(meta(var), v, s)
2929

30-
Base.get(var::AbstractDataVariable, s::Union{String,Symbol}, d=nothing) = get(meta(var), s, d)
31-
Base.get(f::Function, var::AbstractDataVariable, s::Union{String,Symbol}) = get(f, meta(var), s)
30+
Base.get(var::AbstractDataVariable, s::Union{String, Symbol}, d = nothing) = get(meta(var), s, d)
31+
Base.get(f::Function, var::AbstractDataVariable, s::Union{String, Symbol}) = get(f, meta(var), s)
3232

3333
tmin(v) = minimum(times(v))
3434
tmax(v) = maximum(times(v))
3535

3636
_timerange_str(times) = "Time Range: $(minimum(times)) to $(maximum(times))"
3737

38-
function Base.show(io::IO, var::T) where {T<:AbstractDataVariable}
38+
function Base.show(io::IO, var::T) where {T <: AbstractDataVariable}
3939
ismissing(var) && return
4040
print_name(io, var)
4141
print(io, " [")
@@ -45,10 +45,14 @@ function Base.show(io::IO, var::T) where {T<:AbstractDataVariable}
4545
isnothing(u) || print(io, " Units: ", u, ",")
4646
print(io, " Size: ", size(var))
4747
print(io, "]")
48+
return
4849
end
4950

51+
_is_valid(::Nothing) = false
52+
_is_valid(x) = applicable(isempty, x) ? !isempty(x) : true
53+
5054
# Add Base.show methods for pretty printing
51-
function Base.show(io::IO, m::MIME"text/plain", var::T) where {T<:AbstractDataVariable}
55+
function Base.show(io::IO, m::MIME"text/plain", var::T) where {T <: AbstractDataVariable}
5256
ismissing(var) && return
5357
print(io, "$T: ")
5458
print_name(io, var)
@@ -59,8 +63,34 @@ function Base.show(io::IO, m::MIME"text/plain", var::T) where {T<:AbstractDataVa
5963
isnothing(u) || println(io, " Units: ", u)
6064
println(io, " Size: ", size(var))
6165
println(io, " Memory Usage: ", Base.format_bytes(Base.summarysize(var)))
62-
if (m = meta(var)) !== nothing
66+
if (m = meta(var)) |> _is_valid
6367
print(io, " Metadata:")
6468
_println_value(io, m)
6569
end
70+
return
71+
end
72+
73+
# DataVariable (a example of AbstractDataVariable)
74+
struct DataVariable{T, N, A <: AbstractArray{T, N}, D} <: AbstractDataVariable{T, N}
75+
data::A
76+
metadata::D
77+
end
78+
79+
Base.similar(A::DataVariable, ::Type{T}, dims::Dims) where {T} = DataVariable(similar(A.data, T, dims), A.metadata)
80+
81+
# Broadcast
82+
# Reference: https://docs.julialang.org/en/v1/manual/interfaces/#man-interfaces-broadcasting
83+
# Reference: https://github.com/rafaqz/DimensionalData.jl/blob/main/src/array/broadcast.jl
84+
using Base.Broadcast: ArrayStyle, Broadcasted
85+
Base.BroadcastStyle(::Type{<:AbstractDataVariable}) = ArrayStyle{AbstractDataVariable}()
86+
87+
Base.similar(bc::Broadcasted{ArrayStyle{AbstractDataVariable}}, ::Type{T}) where {T} = similar(find_datavariable(bc), T)
88+
89+
find_datavariable(x::Broadcasted) = find_datavariable(x.args)
90+
function find_datavariable(x::Tuple)
91+
found = find_datavariable(x[1])
92+
return isnothing(found) ? find_datavariable(Base.tail(x)) : found
6693
end
94+
find_datavariable(::Tuple{}) = nothing
95+
find_datavariable(x::AbstractDataVariable) = x
96+
find_datavariable(::Any) = nothing

src/workload.jl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ function workload()
22
io = IOContext(IOBuffer(), :color => true)
33
var1 = [1, 2, 3]
44
var2 = (4, 5, 6)
5-
dataset = DataSet("Dataset Name", (var1, var2))
6-
project = Project(name="Project Name", abbreviation="Proj", links="links")
5+
var = DataVariable([1, 2, 3], Dict())
6+
dataset = DataSet("Dataset Name", [var1, var2])
7+
project = Project(name = "Project Name", abbreviation = "Proj", links = "links")
8+
instrument = Instrument(name = "Instrument Name")
9+
show(io, MIME"text/plain"(), var)
710
show(io, MIME"text/plain"(), dataset)
811
show(io, MIME"text/plain"(), project)
9-
end
12+
push!(project, instrument, dataset)
13+
push!(instrument, dataset)
14+
push!(dataset, var)
15+
return
16+
end

test/runtests.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ using Test
33

44
@run_package_tests
55

6-
@testset "SpaceDataModel.jl" begin
7-
# Write your tests here.
6+
@testitem "SpaceDataModel.jl" begin
7+
@test_nowarn SpaceDataModel.workload()
88
end
99

1010
@testitem "Aqua" begin
@@ -41,6 +41,7 @@ end
4141
@test dataset2["key1"] === var1
4242
@test dataset2["key2"] === var2
4343
@test dataset2[2] (var1, var2)
44+
@test_throws BoundsError dataset2[3]
4445
end
4546

4647
@testitem "parse_datetime" begin
@@ -78,6 +79,7 @@ end
7879
end
7980
end
8081

82+
8183
@testitem "JET - Workload" begin
8284
using JET
8385
println(@report_opt ignored_modules = (Base,) SpaceDataModel.workload())

test/variable.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@testitem "AbstractDataVariable Broadcasting" begin
2+
using SpaceDataModel: AbstractDataVariable, DataVariable
3+
using Base.Broadcast: ArrayStyle, Broadcasted
4+
5+
# Test BroadcastStyle
6+
var = DataVariable([1.0, 2.0, 3.0], Dict("istest" => true))
7+
@test Base.BroadcastStyle(typeof(var)) == ArrayStyle{AbstractDataVariable}()
8+
9+
# Test broadcasting operations
10+
result1 = var .+ 1
11+
@test result1 isa DataVariable
12+
@test parent(result1) == [2.0, 3.0, 4.0]
13+
end

0 commit comments

Comments
 (0)