Skip to content

Allow getproperty, setproperty, getindex, and setindex on vectors of IDS #8

@anchal-physics

Description

@anchal-physics

Use case

ids.edge_profiles.ggd is the array of ggd element with each representing a time step. So if I want a property value on 5th time step, I need to do: ids.edge_profiles.ggd[5].electrons.density[1].values. However, if the want that property values for all teh time steps, I will need to run a for loop because I can not do ids.edge_profiles.ggd[:].electrons.density[1].values.

This motivated me to think of some feature request for overloading getproperty, setproperty, getindex and setindex to operate over arrays of IDS objects so that we can access these quantities in a similar way they are descibed on OMAS Schema page

Feature request

I am not sure how to describe the request best other than to show some example. If you run the following in julia REPL or notebook, it will show you what I mean:

using Pkg

Pkg.activate(".")
Pkg.add(; url="git@github.com:ProjectTorreyPines/IMASDD.jl.git", rev="master")
Pkg.instantiate()

using IMASDD: IMASDD

function Base.getproperty(vec::Vector{T}, field::Symbol) where {T<:IMASDD.IDS}
    if fieldtype(eltype(vec), field) <: AbstractArray{U} where {U<:Real}
        println("  (1.1 Using getproperty(vec::Vector{T}, field::Symbol) for vector fields)")
        return mapreduce(permutedims, vcat, getfield.(vec, field))
    else
        println("  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)")
        return getfield.(vec, field)
    end
end

function Base.getproperty(vec::Matrix{T}, field::Symbol) where {T<:IMASDD.IDS}
    if fieldtype(eltype(vec), field) <: AbstractArray{U} where {U<:Real}
        println("  (2.1 Using getproperty(vec::Matrix{T}, field::Symbol) for vector fields)")
        return permutedims(stack(getfield.(vec, field)), [2, 3, 1])

    else
        println("  (2.2 Using getproperty(vec::Matrix{T}, field::Symbol)")
        return getfield.(vec, field)
    end
end

function Base.getindex(vec::Vector{T}, iter::Colon, I_2::Int) where {T<:IMASDD.IDSvector{U}} where {U<:IMASDD.IDSvectorElement}
    println("  (3.0 Using getindex(vec::Vector{T}, iter::Colon, I_2::Int))")
    return getindex.(vec, I_2)
end

function Base.getindex(vec::Vector{T}, iter::Colon, iter2::Colon) where {T<:IMASDD.IDSvector{U}} where {U<:IMASDD.IDSvectorElement}
    println("  (4.0 Using getindex(vec::Vector{T}, iter::Colon, I_2::Int))")
    return mapreduce(permutedims, vcat, [ids_vec for ids_vec in vec])
end

Let's define a small IDS with some edge_profiles data. Ideally, even this step would become free of for loops if setproperty and setindex are overloaded properly:

ids = IMASDD.dd();
resize!(ids.edge_profiles.ggd, 5)
for epggd in ids.edge_profiles.ggd
    resize!(epggd.electrons.density, 2)
    epggd.electrons.density[1].values = zeros(3)
    epggd.electrons.density[2].values = ones(3)
    epggd.electrons.density[1].grid_subset_index = 1
    epggd.electrons.density[2].grid_subset_index = 2
end

julia> ids.edge_profiles.ggd
ggd
├─ 1
│  └─ electrons
│     └─ density
│        ├─ 1
│        │  ├─ grid_subset_index ➡ 1
│        │  └─ values ➡ [0,0,0] [m^-3]
│        └─ 2
│           ├─ grid_subset_index ➡ 2
│           └─ values ➡ [1,1,1] [m^-3]
├─ 2
│  └─ electrons
│     └─ density
│        ├─ 1
│        │  ├─ grid_subset_index ➡ 1
│        │  └─ values ➡ [0,0,0] [m^-3]
│        └─ 2
│           ├─ grid_subset_index ➡ 2
│           └─ values ➡ [1,1,1] [m^-3]
├─ 3
│  └─ electrons
│     └─ density
│        ├─ 1
│        │  ├─ grid_subset_index ➡ 1
│        │  └─ values ➡ [0,0,0] [m^-3]
│        └─ 2
│           ├─ grid_subset_index ➡ 2
│           └─ values ➡ [1,1,1] [m^-3]
├─ 4
│  └─ electrons
│     └─ density
│        ├─ 1
│        │  ├─ grid_subset_index ➡ 1
│        │  └─ values ➡ [0,0,0] [m^-3]
│        └─ 2
│           ├─ grid_subset_index ➡ 2
│           └─ values ➡ [1,1,1] [m^-3]
└─ 5
   └─ electrons
      └─ density
         ├─ 1
         │  ├─ grid_subset_index ➡ 1
         │  └─ values ➡ [0,0,0] [m^-3]
         └─ 2
            ├─ grid_subset_index ➡ 2
            └─ values ➡ [1,1,1] [m^-3]

Now let's try to access values inside ggd array without specifying an index but giving a colon to emphasize that it is a vector.

julia> ids.edge_profiles.ggd[:].electrons.density
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
5-element Vector{IMASDD.IDSvector{IMASDD.edge_profiles__ggd___electrons__density{Float64}}}:
 edge_profiles.ggd[1].electrons.density[1...2]

 edge_profiles.ggd[2].electrons.density[1...2]

 edge_profiles.ggd[3].electrons.density[1...2]

 edge_profiles.ggd[4].electrons.density[1...2]

 edge_profiles.ggd[5].electrons.density[1...2]

One can do further indexing as if it is a 2-D array

julia> ids.edge_profiles.ggd[:].electrons.density[:, 1]
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (3.0 Using getindex(vec::Vector{T}, iter::Colon, I_2::Int))
5-element Vector{IMASDD.edge_profiles__ggd___electrons__density{Float64}}:
 edge_profiles.ggd[1].electrons.density[1]{grid_subset_index, values}
 edge_profiles.ggd[2].electrons.density[1]{grid_subset_index, values}
 edge_profiles.ggd[3].electrons.density[1]{grid_subset_index, values}
 edge_profiles.ggd[4].electrons.density[1]{grid_subset_index, values}
 edge_profiles.ggd[5].electrons.density[1]{grid_subset_index, values}

One can do further getpropert and get a stacked matrix:

julia> ids.edge_profiles.ggd[:].electrons.density[:, 1].values
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (3.0 Using getindex(vec::Vector{T}, iter::Colon, I_2::Int))
  (1.1 Using getproperty(vec::Vector{T}, field::Symbol) for vector fields)
5×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

One can also get matrix of all sub-IDS objects by using the conventional [:, :]

julia> ids.edge_profiles.ggd[:].electrons.density[:, :]
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (4.0 Using getindex(vec::Vector{T}, iter::Colon, I_2::Int))
5×2 Matrix{IMASDD.edge_profiles__ggd___electrons__density{Float64}}:
 edge_profiles.ggd[1].electrons.density[1]{grid_subset_index, values}    edge_profiles.ggd[1].electrons.density[2]{grid_subset_index, values}
 edge_profiles.ggd[2].electrons.density[1]{grid_subset_index, values}     edge_profiles.ggd[2].electrons.density[2]{grid_subset_index, values}
 edge_profiles.ggd[3].electrons.density[1]{grid_subset_index, values}     edge_profiles.ggd[3].electrons.density[2]{grid_subset_index, values}
 edge_profiles.ggd[4].electrons.density[1]{grid_subset_index, values}     edge_profiles.ggd[4].electrons.density[2]{grid_subset_index, values}
 edge_profiles.ggd[5].electrons.density[1]{grid_subset_index, values}     edge_profiles.ggd[5].electrons.density[2]{grid_subset_index, values}

One can access a field of a matrix of IDS if the field values have the same dimensions, returning a higher dimensional array:

julia> ids.edge_profiles.ggd[:].electrons.density[:, :].values
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (4.0 Using getindex(vec::Vector{T}, iter::Colon, I_2::Int))
  (2.1 Using getproperty(vec::Matrix{T}, field::Symbol) for vector fields)
5×2×3 Array{Float64, 3}:
[:, :, 1] =
 0.0  1.0
 0.0  1.0
 0.0  1.0
 0.0  1.0
 0.0  1.0

[:, :, 2] =
 0.0  1.0
 0.0  1.0
 0.0  1.0
 0.0  1.0
 0.0  1.0

[:, :, 3] =
 0.0  1.0
 0.0  1.0
 0.0  1.0
 0.0  1.0
 0.0  1.0

Or same dimensions if the value is just a scalar

julia> ids.edge_profiles.ggd[:].electrons.density[:, :].grid_subset_index
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (1.2 Using getproperty(vec::Vector{T}, field::Symbol)
  (4.0 Using getindex(vec::Vector{T}, iter::Colon, I_2::Int))
  (2.2 Using getproperty(vec::Matrix{T}, field::Symbol)
5×2 Matrix{Int64}:
 1  2
 1  2
 1  2
 1  2
 1  2

So I hope this gives the idea of the kind of feature I am requesting. Infact, for getproperty and getindex, I am already content with the 4 functions I defined above. But I might be missing nuances about speed, allocations, and interference with other use cases while writing these functions. I believe similar functionality can be given to setproperty and setindex too, but I think that is more complex. If you all think this is worth exploring and someone has time, we should get these features. I'd be happy to help in any way I can to this effort.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions