Array concatenation results in type inconsistency

Here is an example.

function test_func()
    array1 = rand(Float64,10,10)
    array2 = rand(Float64,10,10)
    out = cat(array1,array2,dims=1)
    return out
end
@code_warntype test_func()
Variables
  #self#::Core.Compiler.Const(test_func, false)
  array1::Array{Float64,2}
  array2::Array{Float64,2}
  out::Any

Body::Any
1 ─      (array1 = Main.rand(Main.Float64, 10, 10))
│        (array2 = Main.rand(Main.Float64, 10, 10))
│   %3 = (:dims,)::Core.Compiler.Const((:dims,), false)
│   %4 = Core.apply_type(Core.NamedTuple, %3)::Core.Compiler.Const(NamedTuple{(:dims,),T} where T<:Tuple, false)
│   %5 = Core.tuple(1)::Core.Compiler.Const((1,), false)
│   %6 = (%4)(%5)::Core.Compiler.Const((dims = 1,), false)
│   %7 = Core.kwfunc(Main.cat)::Core.Compiler.Const(Base.var"#cat##kw"(), false)
│   %8 = array1::Array{Float64,2}
│        (out = (%7)(%6, Main.cat, %8, array2))
└──      return out

Both vcat and hcat work just fine though.

function test_func()
    array1 = rand(Float64,10,10)
    array2 = rand(Float64,10,10)
    out = vcat(array1,array2)
    return out
end
@code_warntype test_func()
Variables
  #self#::Core.Compiler.Const(test_func, false)
  array1::Array{Float64,2}
  array2::Array{Float64,2}
  out::Array{Float64,2}

Body::Array{Float64,2}
1 ─     (array1 = Main.rand(Main.Float64, 10, 10))
│       (array2 = Main.rand(Main.Float64, 10, 10))
│       (out = Main.vcat(array1, array2))
└──     return out

Am I doing something wrong or is it a bug?

The problem is that cat is a type unstable function because if dims=3 then it would return a 10x10x2 array.

Do you have any suggestions on how to concatenate arrays in a type stable manner along dimensions higher than 2?

I checked Julia’s source code for cat and it seems that both vcat and hcat are defined using cat. I suppose that one can define his own type stable functions for concatenating arrays along higher dimensions.
Here is an example for the third dimesion.

cat3(A::AbstractArray) = cat(A; dims=Val(3))
cat3(A::AbstractArray, B::AbstractArray) = cat(A, B; dims=Val(3))
cat3(A::AbstractArray...) = cat(A...; dims=Val(3))
function test_func()
    array1 = rand(Float64,10,10,10)
    array2 = rand(Float64,10,10,10)
    out = cat3(array1,array2)
    return out
end
@code_warntype test_func()
Variables
  #self#::Core.Compiler.Const(test_func, false)
  array1::Array{Float64,3}
  array2::Array{Float64,3}
  out::Array{Float64,3}

Body::Array{Float64,3}
1 ─     (array1 = Main.rand(Main.Float64, 10, 10, 10))
│       (array2 = Main.rand(Main.Float64, 10, 10, 10))
│       (out = Main.cat3(array1, array2))
└──     return out

Is is the preferred way of doing that?

You are “hiding” the issue in that. If you do @code_warntype cat3():

a1 = rand(Float64, 10, 10, 10)
a2 = rand(Float64, 10, 10, 10)
@code_typewarn cat3(a1, a2)
Variables
  #self#::Core.Compiler.Const(cat3, false)
  A::Array{Float64,3}
  B::Array{Float64,3}

Body::Array{Float64,3}
1 ─ %1 = (:dims,)::Core.Compiler.Const((:dims,), false)
│   %2 = Core.apply_type(Core.NamedTuple, %1)::Core.Compiler.Const(NamedTuple{(:dims,),T} where T<:Tuple, false)
│   %3 = Main.Val(3)::Core.Compiler.Const(Val{3}(), true)
│   %4 = Core.tuple(%3)::Core.Compiler.Const((Val{3}(),), false)
│   %5 = (%2)(%4)::Core.Compiler.Const((dims = Val{3}(),), false)
│   %6 = Core.kwfunc(Main.cat)::Core.Compiler.Const(Base.var"#cat##kw"(), false)
│   %7 = (%6)(%5, Main.cat, A, B)::Array{Float64,3}
└──      return %7

I did not exactly understand what is wrong here. Could you elaborate on that? As I understand we aim to have all variable types in either blue or yellow after running @code_typewarn which indicates that the compiler is capable of compiling efficient machine code. Did I miss someting?

Sorry, I didn’t test fully. It appears is the difference between dims=3 and dims=Val(3) if you are just looking to get rid of the red…

function test_func()
    array1 = rand(Float64,10,10)
    array2 = rand(Float64,10,10)
    out = cat(array1,array2,dims=Val(1))
    return out
end

@code_warntype test_func()
5 Likes

Have you tried:

function test_func2()
           array1 = rand(Float64,10,10)
           array2 = rand(Float64,10,10)
           out::Array{Float64,2} = cat(array1,array2,dims=1)
           return out
       end

In that way, type of out is well-know.
Edit: solution from @pixel27 seems better.

vcat and hcat might be defined in terms of cat in the general case, but for certain types there are special typed methods that are type stable:

https://github.com/JuliaLang/julia/blob/549a73b99de47700ffe7b92beec9f84789682d38/stdlib/SparseArrays/src/sparsevector.jl#L1120