Can you show me or point me to documentation on defining iteration on my type?
Note, you don’t need to do this if you go with the StaticArrays.jl solution. StaticArrays.jl will do this for you.
A quick google search of “iteration define julia” gives the following link to the docs.
I don’t have a good understanding of what API means yet, so I’m not following the distinction you are making.
It depends on the context, but normally defining
function f(x) end
function f(x::AbstractArray) end
does this job.
The API is basically the way the user interacts with your objects. It’s all the things they can do with your object.
You can have a super complicated object
struct MyComplicatedObject{T}
a::T
b::Float64
c:::T
...
z::String
end
but then the only function you define for it is onesinglefunction
onesinglefunction(x::MyComplicatedObject) = x.a
Then the function onesinglefunction
is the API and the user doesn’t ever need to know there are fields a
through z
in the struct.
I read Parametric Composite Types, but I’m still a bit confused on the best way forward with types.
struct CylindricalStress1
r::Number
θ::Number
z::Number
end
struct CylindricalStress2{T} #where T <: Number
r::T
θ::T
z::T
end
julia> σ1 = CylindricalStress1(1.1, 1.2, 1.3)
CylindricalStress1(1.1, 1.2, 1.3)
julia> σ2 = CylindricalStress2(1.1, 1.2, 1.3)
CylindricalStress2{Float64}(1.1, 1.2, 1.3)
julia> σ3 = CylindricalStress1(1, 2, 3.3)
CylindricalStress1(1, 2, 3.3)
julia> σ4 = CylindricalStress2(1, 2, 3.3)
ERROR: LoadError: MethodError: no method matching CylindricalStress2(::Int64, ::Int64, ::Float64)
It seems like CylindricalStress1
is more flexible at the cost of speed. It might be possible for an Int
to make it through my lame
function, and it would be pretty lame if the user received the
σ4
error because not all the entries converted to Float64
. Should I stick with CylindricalStress1
or force a conversion to Float64
somewhere so I can use the faster CylindricalStress2
? I wouldn’t want to convert Unitful.jl types to Float64 though.
Also, why do I get ERROR: LoadError: syntax: invalid type signature
when I uncomment where T <: Number
? What is the proper way to restrict the type of T? I don’t want to allow vectors.
Lastly, I notice that the types of the fields are the same in either case, so it is just the type of the container that is changing.
julia> typeof(σ1.r)
Float64
julia> typeof(σ2.r)
Float64
julia> typeof(σ3.r)
Int64
julia> typeof(σ4.r)
ERROR: LoadError: UndefVarError: σ4 not defined
You should handle this in the constructor, but you need to use an inner constructor for it
julia> struct CS{T<:Number}
r::T
θ::T
z::T
CS(r, θ, z) = begin
T = mapreduce(typeof, promote_type, (r, θ, z))
new{T}(convert(T, r), convert(T, θ), convert(T, z))
end
end
julia> CS(1, 2.0, 5)
CS{Float64}(1.0, 2.0, 5.0)
julia> CS(1u"kg", 2.0, 5)
CS{Quantity{Float64, D, U} where {D, U}}(1.0 kg, 2.0, 5.0)
I’m actually not 100% on whether or not I’m doing this conversion right. Hopefully someone better informed can chime in
I think an outer constructor of:
CS(x,y,z) = CS(promote(x,y,z)...)
would be fine
No, this leads to a stackoverflow i think. It needs to be in the inner constructor. At least that’s what I had when making that MWE above.
julia> struct CS{T<:Number}
r::T
θ::T
z::T
end
julia> CS(1,2,3)
CS{Int64}(1, 2, 3)
julia> CS(x,y,z) = CS(promote(x,y,z)...)
CS
julia> CS(1,2,3)
CS{Int64}(1, 2, 3)
julia> CS(1,2,3.0)
CS{Float64}(1.0, 2.0, 3.0)
looks fine
Ah, my bad, I just saw the first type definition
"""
abstract type Unitlike end
Represents units or dimensions. Dimensions are unit-like in the sense that they are
not numbers but you can multiply or divide them and exponentiate by rationals.
"""
abstract type Unitlike end
and wrongly decided those are the unitful values.
But I did get burnt with subtypes of Number
which seemed appropriate for the problem but turned out problematic when I tried AD, and thus just accepted that unitfuls may be not numbers without even checking.
To add to @pdeffebach’s and @jling’s suggestions:
struct CS{Tx<:Number, Ta<:Number}
r::Tx
θ::Ta
z::Tx
function CS(rr, θ, zz)
r, z = promote(float(rr), float(zz))
Ta = float(typeof(θ))
Tx = typeof(r)
return new{Tx, Ta}(r, θ, z)
end
end
That handles converting to floating point types and the fact that unitful r and \theta have different units.
Solution to Question #1
norm
and sum
do work natively as I wanted with StaticArrays
.
Alternative:
Solution to Question #2
Thus, I will switch to the following type definitions provided by @jling and @lmiq.
struct CylindricalStress{T<:Number} <: FieldVector{3,T}
r::T # Radial Stress
θ::T # Tangential (Hoop) Stress
z::T # Longitudinal Stress
end
CylindricalStress(x, y, z) = CylindricalStress(promote(x, y, z)...) # Outer constructor
function lame(r::AbstractVector, pᵢ::Number, pₒ::Number=0)
# function definition
end
julia> σ = lame([5, 10, 15], 100)
3-element Vector{CylindricalStress{Float64}}:
[-100.0, 125.0, 12.5]
[-15.625, 40.625, 12.5]
[0.0, 25.0, 12.5]
julia> norm.(σ)
3-element Vector{Float64}:
160.56540723331412
45.285552331842
27.95084971874737
julia> mean(σ)
3-element CylindricalStress{Float64} with indices SOneTo(3):
-38.541666666666664
63.541666666666664
12.5
Solution to Question #3
Although in my case, I don’t have any extra fields that the user wouldn’t care about.