Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
anchal-physics opened this issue Apr 6, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@anchal-physics
Copy link
Contributor

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.

@anchal-physics anchal-physics added the enhancement New feature or request label Apr 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant