How to use a numerical type parameters in declaration

I’m trying to do something about physics, and I’ve written a struct like

struct System
    charge_density
end

now there is something called “spin” that may, but not must be considered in physics, and the data structure will be different. It looks something like

struct SystemNoSpin
    charge_density::Array{Float64, 3}
end

struct SystemWithSpin
    charge_density::Array{Float64, 4}
end

conventionally, we will set a variable called “SPIN” = 1 when not considering it, and 2 when considering. So I try to use parametric types. I found this is a practical way:

struct System{SPIN}
    charge_density::Array{Float64, SPIN+2}
end

but this is not:

charge_type = Dict{Int, Type}([(1, Array{Float64, 3}), (2, Array{Float64, 4})])
struct System{SPIN}
    charge_density::charge_type[SPIN]
end

it will raise “KeyError:: key SPIN not found”

However, I feel that the second way, if available, seems to be more readable. Any way to do it?


Maybe I should make it more clear what I want. In physics, there are many properties, and they may be categorized into many kinds. For example, all space-related properties are Array{T, 3} without spin and Array{T, 4} with spin, while all band-related properties are Array{T, 2} without spin and Array{T, 3} with spin. T maybe either Float64 or ComplexF64. That’s why I think the second way mentioned above is more readable. In that way I can define some type alias, some dictionaries, then write

struct System{SPIN}
    property1::space_related[SPIN]{Float64}
    property2::space_related[SPIN]{ComplexF64}
    property3::band_related[SPIN]{Float64}
end

On the contrary, in the first way I mentioned above, it can only be written as

struct System{SPIN}
    property1::Array{Float64, 2 + SPIN}
    property2::Array{ComplexF64, 2 + SPIN}
    property3::Array{Float64, 1 + SPIN}
end

And now I’ve turned to a third solution:

struct SpaceRelatedProperties{SPIN}
    property1::Array{Float64, 2 + SPIN}
    property2::Array{ComplexF64, 2 + SPIN}
end
struct BandRelatedProperties{SPIN}
    property3::Array{Float64, 1 + SPIN}
end
struct System{SPIN}
    s_p::SpaceRelatedProperties{SPIN}
    b_p::BandRelatedProperties{SPIN}
end

Any better ways?

I think your type declaration could be:

julia> struct System{SPIN,N}
           charge_density::Array{Float64}
       end

and then one or other option lies in the constructors:

julia> function System(spin,n) # not sure what is the size  of the array you want
         if spin == 1
           return System{spin,n}(zeros(Float64,n,n,n))
         elseif spin == 2
           return System{spin,n}(zeros(Float64,n,n,n,n))
         else
           error(" spin must be 1 or 2 ")
         end
       end
System

julia> System(1,4) 
System{1,4}([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 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 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 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; 0.0 0.0 0.0 0.0])

julia> System(2,3)
System{2,3}([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; 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; 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]

[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; 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; 0.0 0.0 0.0; 0.0 0.0 0.0])


1 Like

I think your point is, not specifying the concrete dimensions in type declaration, but during construction. It is a nice suggestion, but I think it differs not much from the first way I’ve discussed.

I don’t think you can do this, don’t you get this error?

julia> struct System{SPIN}
           charge_density::Array{Float64, SPIN+2}
       end
ERROR: MethodError: no method matching +(::TypeVar, ::Int64)

julia> methods(System)
# 0 methods for type constructor:

The normal way, as @lmiq says, is to make the constructor enforce it. If you do this as an inner constructor, then there will be no method to construct a mismatched object:

julia> struct System2{SPIN, N}
           charge_density::Array{Float64, N}
           System2(x::Array) = new{ndims(x)-2, ndims(x)}(x)
       end

julia> System2(ones(2,2)) isa System2{0}
true

You can also use GitHub - vtjnash/ComputedFieldTypes.jl: Build types in Julia where some fields have computed types to write and handle the extra type parameters for you:

julia> using ComputedFieldTypes

julia> @macroexpand @computed struct SystemC{SPIN}
                      charge_density::Array{Float64, SPIN+2}
                    end
quote
    struct SystemC{SPIN, var"##272"}
        #= REPL[8]:2 =#
        charge_density::Array{Float64, var"##272"}
        function (::Base.Type{SystemC{SPIN}})(charge_density) where SPIN
            return new{SPIN, SPIN + 2}(charge_density)
        end
    end
    function ComputedFieldTypes.fulltype(::Base.Type{SystemC{SPIN}}) where SPIN
        return SystemC{SPIN, SPIN + 2}
    end
end
1 Like

when I test the code on my machine I only use a simplified version without +2 and :cold_sweat:
so the problem is that it is a typevar, not the Int passed in. I will try your ways.

Another question. I can add an additional dimension to non-spin case, in which the size is always 1, so that the dimension in both cases is the same. Will this additional dimension introduce much overhead? It seems much easier to complement.