Checking strided array storage order

Is

strides(array) == Base.size_to_strides(1, size(array)...)

a sane way to check if a strided array is in column-major (alias co-lexicographic, alias Fortran, alias Julia) order, storage-wise?

Cf. julia/base/abstractarray.jl at v1.11.5 · JuliaLang/julia · GitHub

If so, the following might be useful here and there (and perhaps in Base):

@enum ArrayStorageOrder begin
    ArrayStorageOrderColumn
    ArrayStorageOrderRow
end

size_to_strides_column(sz::Dims) = Base.size_to_strides(1, sz...)

size_to_strides_row(sz::Dims) = reverse(Base.size_to_strides(1, reverse(sz)...))

"""
    storage_order(a::AbstractArray; preferred_order::ArrayStorageOrder = ArrayStorageOrderColumn)

Returns the storage order of `a`, or `preferred_order` if `a` is a scalar or vector.
"""
function storage_order(
    a::AbstractArray{T, N}; preferred_order::ArrayStorageOrder = ArrayStorageOrderColumn
) where {T, N}
    if N < 2
        return preferred_order
    end

    if strides(a) == size_to_strides_column(size(a))
        return ArrayStorageOrderColumn
    elseif strides(a) == size_to_strides_row(size(a))
        return ArrayStorageOrderRow
    else
        throw(
            ArgumentError("Unexpected strides $(strides(a)) for array of size $(size(a))")
        )
    end
end

Related topic: Should `reshape` have an option for row-major order?

For convenience, here’s a row-major strided array for testing purposes:

struct RowMajorArray{T, N} <: AbstractArray{T, N}
    array::Array{T, N}
    function RowMajorArray(array::Array{T, N}) where {T, N}
        a = permutedims(array, ndims(array):-1:1)
        return new{T, N}(a)
    end
end

RowMajorArray(array::AbstractArray{T, N}) where {T, N} = RowMajorArray{T, N}(array)

Base.size(array::RowMajorArray) = reverse(size(array.array))

Base.IndexStyle(::Type{<:RowMajorArray}) = IndexCartesian()

function Base.getindex(array::RowMajorArray, i::Vararg{Int, N}) where {N}
    return getindex(array.array, reverse(i)...)
end

Base.strides(array::RowMajorArray) = reverse(strides(array.array))

function Base.unsafe_convert(::Type{Ptr{T}}, array::RowMajorArray{T, N}) where {T, N}
    return unsafe_convert(Ptr{T}, array.array)
end

Base.elsize(array::RowMajorArray) = Base.elsize(array.array)

Where the Base functions has the following behaviour:

julia> a_column = collect(reshape(1:24, (4,3,2)))
4×3×2 Array{Int64, 3}:
[:, :, 1] =
 1  5   9
 2  6  10
 3  7  11
 4  8  12

[:, :, 2] =
 13  17  21
 14  18  22
 15  19  23
 16  20  24

julia> a_row = RowMajorArray(a_column)
4×3×2 RowMajorArray{Int64, 3}:
[:, :, 1] =
 1  5   9
 2  6  10
 3  7  11
 4  8  12

[:, :, 2] =
 13  17  21
 14  18  22
 15  19  23
 16  20  24

julia> strides(a_column)
(1, 4, 12)

julia> strides(a_row)
(6, 2, 1)

julia> sz = size(a_column)
(4, 3, 2)

julia> Base.size_to_strides(1, sz...)
(1, 4, 12)

julia> reverse(Base.size_to_strides(1, reverse(sz)...))
(6, 2, 1)