Easy initialisation of empty containers - julia

Consider the following code.
struct MyType
data::Dict{Int, Float64}
end
MyType() = MyType(Dict{Int, Float64}())
Having to repeat the type of data is a bit clumsy, and the problem gets out of hand very quickly if you have more variables and/or your types get more complicated. Can I avoid this?

If the type in question is default-constructible (i.e., it has a no-args constructor), you can use the following trick.
struct Default; end
Base.convert(::Type{T}, ::Default) where T = T()
struct MyType
data::Dict{Int, Float64}
end
MyType() = MyType(Default())

You can do:
Base.#kwdef struct MyType
data::Dict{Int, Float64} = Dict{Int, Float64}()
end
And now you can just write MyType():
julia> MyType()
MyType(Dict{Int64, Float64}())
I personally more like the package Parameters which is additionally affecting how struct is displayed:
using Parameters
#with_kw struct MyType
data::Dict{Int, Float64} = Dict{Int, Float64}()
end
And now in REPL you can see:
julia> MyType()
MyType
data: Dict{Int64, Float64}

The following hack-ish method works (as of Julia 1.8):
MyType() = MyType((t() for t in MyType.types)...)
It doesn't include Int or Float64, so it passes OP conditions. And also, it generalizes to types with many fields, and complex ones because it is essentially recursive.

Related

Julia: Defining promote_rule with multiple parametric types

Say I want to define a promote_rule() for a type that has multiple parametric types, for example for type MyType:
abstract type State end
struct Open<:State end
struct Closed<:State end
struct MyType{T,S<:State}
x::T
state::S
end
Is there a way to define a promote_rule() which only promotes the first type and not the second, for example:
myFloat = MyType(1.0, Open()) # MyType{Float64, Open}
myInt = MyType(2, Closed()) # MyType{Int64, Closed}
promote(myFloat, myInt)
# (MyType{Float64, Open}, MyType{Float64, Closed})
By definition, the result of a promotion is one common type. So while you can just recursively promote the Ts, you have to resort to a common supertype for the Ss if you want to keep them as is. Simply using State would be a valid choice, but a Union leads to a bit more fine-grained results:
julia> Base.promote_rule(::Type{MyType{T1, S1}}, ::Type{MyType{T2, S2}}) where {T1, T2, S1, S2} = MyType{promote_type(T1, T2), <:Union{S1, S2}}
julia> promote_type(MyType{Int, Closed}, MyType{Float64, Closed})
MyType{Float64,#s12} where #s12<:Closed
julia> promote_type(MyType{Int, Closed}, MyType{Float64, Open})
MyType{Float64,#s12} where #s12<:Union{Closed, Open}
You still have to define the respective convert methods for promote to work, of course; specifically, one ignoring the state type:
julia> Base.convert(::Type{<:MyType{T}}, m::MyType) where {T} = MyType(convert(T, m.x), m.state)
julia> promote(myFloat, myInt)
(MyType{Float64,Open}(1.0, Open()), MyType{Float64,Closed}(2.0, Closed()))
But be sure to test all kinds of combinations really well. Promotion and conversion is really fiddly and hard to get right the first time, in my experience.

How to hard code struct variables in Julia?

I have a Julia struct:
struct WindChillCalc
location::Tuple;
w_underground_url::String;
WindChillCalc(location, wug) = new(location, w_underground_url);
end
How do I hard code w_underground_url to contain "someString" upon the constructor of WindChillCalc being called?
Try something like below
struct testStruct
x::Real
y::String
testStruct(x,y) = new(x,"printThis")
end
test = testStruct(1,"")
test2 = testStruct(2,"")
println(test.y)
println(test2.y)
It prints "printThis" for any object.
Just write for example:
struct WindChillCalc{T}
location::T;
w_underground_url::String;
WindChillCalc(location::T) where {T <: NTuple{2, Real}} =
new{T}(location, "some string");
end
and now Julia automatically creates a concrete type for you:
julia> WindChillCalc((1, 2.5))
WindChillCalc{Tuple{Int64,Float64}}((1, 2.5), "some string")
Note that I have restricted the type of the parameter to be a two element tuple where each element is a Real. You could of course use another restriction (or use no restriction).
With this approach your code will be fast as during compile time Julia will know exact types of all fields in your struct.

Abstract Parametric Types in Julia

Is it possible in Julia to have two structs with the same name but be assigned different types and thus be distinguishable?
I have been reading https://docs.julialang.org/en/v1/manual/types/#Parametric-Types-1 and it seems to be leading towards what I want but I can't get it to work...
In force-fields for molecular simulation there are dihedral parameters to describe torsion angles in molecules. There are different kinds for example purposes lets limit them to 2 kinds: proper and improper. I would like to have two structures, both called dihedral, but given the types "proper" and "improper". I would then have methods specific to each type to calculate the forces due to dihedrals. I think abstract parametric types get me the closest to what I want but I can't get them sorted...
abstract type proper end
abstract type improper end
struct Dihedral <: proper
ai::Int64
kparam::Vector{Float64}
end
struct Dihedral <: improper
ai:Int64
kparam::Float64
end
The above code does not work... I have tried using
abstract type dihedral end
abstract type proper <: dihedral end
abstract type improper <: dihedral end
struct Dihedral <: dihedral{proper}
...
end
struct Dihedral <: dihedral{improper}
...
end
But I always get in trouble for redefining Dihedral
ERROR: LoadError: invalid redefinition of constant Dihedral
Stacktrace:
[1] top-level scope at none:0
My thought is that I can add in more types of dihedrals and all i need to do is also add in their methods and the simulation will automatically use the new dihedral.methods. If I try making structs of different names, then I start having to use if statements to direct the program to the correct structure and later to the correct methods... This is what I want to avoid i.e.,
if dihedraltype == "proper"
struct_proper(...)
elseif dihedraltype =="improper"
struct_improper()
elseif dihedraltype == "newStyle"
struct_newStyle()
end
using this method I would have to find all places in my code where I call dihedral and add in the new type... dihedral is just an example, there are many "phenomenas" that have different methods for calculating the phenomena.
I would use the following approach if you want to use a parametric type:
abstract type DihedralType end
struct Proper <: DihedralType
ai::Int64
kparam::Vector{Float64}
end
struct Improper <: DihedralType
ai::Int64
kparam::Float64
end
struct Dihedral{T<:DihedralType}
value::T
end
Dihedral(ai::Int64, kparam::Vector{Float64}) = Dihedral(Proper(ai, kparam))
Dihedral(ai::Int64, kparam::Float64) = Dihedral(Improper(ai, kparam))
and now you can write e.g.:
Dihedral(1, [1.0, 2.0])
Dihedral(1, 1.0)
The parameter of type Dihedral passes you information what kind of the object you are working with. Then some methods may be generic and call Dihedral e.g.:
julia> ai(d::Dihedral) = d.value.ai
ai (generic function with 1 method)
julia> ai(Dihedral(1, 1.0))
1
julia> ai(Dihedral(1, [1.0, 2.0]))
1
julia> kparam(d::Dihedral) = d.value.kparam
kparam (generic function with 1 method)
julia> kparam(Dihedral(1, 1.0))
1.0
julia> kparam(Dihedral(1, [1.0, 2.0]))
2-element Array{Float64,1}:
1.0
2.0
and some may be type parameter specific:
julia> len(d::Dihedral{Proper}) = length(kparam(d))
len (generic function with 1 method)
julia> len(Dihedral(1, [1.0, 2.0]))
2
julia> len(Dihedral(1, 1.0))
ERROR: MethodError: no method matching len(::Dihedral{Improper})
Closest candidates are:
len(::Dihedral{Proper}) at REPL[15]:1
Stacktrace:
[1] top-level scope at none:0
Does this approach give you what you have expected?
EDIT
Actually maybe an even simpler approach may be enough for you (depending on the use case). Just define:
abstract type AbstractDihedral end
struct Proper <: AbstractDihedral
ai::Int64
kparam::Vector{Float64}
end
struct Improper <: AbstractDihedral
ai::Int64
kparam::Float64
end
and then implement methods in terms of DihedralType if they are generic for all dihedrals and if you want to add some specific method to a given concrete type just add this method with this concrete type in the signature. For example:
ai(d::AbstractDihedral) = d.ai
kparam(d::AbstractDihedral) = d.kparam
len(d::Proper) = length(d.kparam) # will not work for Improper
In this approach you do not need to use a parametric type. The difference is that in the parametric type approach you can extract out the parameters that are the same for all dihedrals to the "parent" struct and define only dihedral specific parameters in the "wrapped" struct. In the second approach you have define all fields every time for each struct.

How to define a constant in Julia struct

I want to define a struct:
struct unit_SI_gen
x::Float32
const c = 2.99792458e8
speed(x)=c*x
end
However, it raise an error :
syntax: "c = 2.99792e+08" inside type definition is reserved
I know I cant use struct as class in python, but I can not find how to solve this problem.
How to define a constant in struct ?
Given I agree with what was said above about normal usage of struct in Julia, it is actually possible to define what was requested in the question using an inner constructor:
struct unit_SI_gen{F} # need a parametric type to make it fast
x::Float32
c::Float64 # it is a constant across all unit_SI_gen instances
speed::F # it is a function
function unit_SI_gen(x)
c = 2.99792458e8
si(x) = c*x
new{typeof(si)}(x, c, si)
end
end
I second #Tasos's comment, you should probably make yourself familiar with Julia's structs first. The relevant part of the documentation is probably here.
Since you declared your struct as struct (in contrast to mutable struct) it is immutable and hence all (immutable) fields of the struct are constants in the sense that they can't be changed.
julia> struct A
x::Int
end
julia> a = A(3)
A(3)
julia> a.x = 4
ERROR: type A is immutable
Stacktrace:
[1] setproperty!(::A, ::Symbol, ::Int64) at .\sysimg.jl:19
[2] top-level scope at none:0
Note, that they get their unchangeable value in the construction process and not in the struct definition.
Also, methods should generally live outside of the struct definition.

automatic inference of parametric types

I am using the code below and it seems Julia should be able to infer the type parameters by itself, however this is not the case. Any ideas, maybe I'm doing something wrong?
abstract type ABS{A,B} end
struct MyStruct{A,B,K<:ABS{A,B}}
a::A
b::B
MyStruct{A,B,K}(a::A,b::B) where {A,B,K<:ABS{A,B}} = new(a,b)
end
MyStruct{Int64,Float64,ABS{Int64,Float64}}(1,2.1) # <<-- works
MyStruct(1,2.1) # <<-- doesn't work
I forgot the outer constructor, as #gnimuc pointed out. This code works:
abstract type ABS{A,B} end
struct Myk <: ABS{Int64,Float64} end
struct MyStruct{A,B,K<:ABS{A,B}}
a::A
b::B
MyStruct{A,B,K}(a::A,b::B) where {A,B,K<:ABS{A,B}}= new(a,b)
end
# this is the outer constructor:
MyStruct(a::A, b::B, ::K) where {A,B,K<:ABS{A,B}} = MyStruct{A,B,K}(a,b)
# now this works:
MyStruct(1,2.1,Myk())

Resources