Basic struct usage questions

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.

1 Like

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 :wink: 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

1 Like

I think an outer constructor of:

CS(x,y,z) = CS(promote(x,y,z)...)

would be fine

2 Likes

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

2 Likes

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.

1 Like

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.

4 Likes