Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change vcat and cat behaviour for vectors #19622

Closed
felixrehren opened this issue Dec 16, 2016 · 12 comments
Closed

Change vcat and cat behaviour for vectors #19622

felixrehren opened this issue Dec 16, 2016 · 12 comments

Comments

@felixrehren
Copy link
Contributor

felixrehren commented Dec 16, 2016

Following this discourse, I suggest

  • Changing the ; operator in array construction to always return a 2D matrix
  • Changing vcat to always return a 2D or higher matrix
  • Changing cat to concatenate vectors (taking over the vector methods currently on vcat)

for the following reasons

  • Currently, the behaviour of vector/matrix instantiation is confusing. The proposed change would make it so that commas give vectors, spaces (hcat) and semicolons (vcat) concatenate and return 2D arrays
  • hcat and vcat, having built-in directions, only make sense in 2D or higher. In particular, it doesn't make sense that hcat(xx::Vector{T}...) returns an 1xn Array{T,2} and vcat(xx::Vector{T}...) returns an n Array{T,1}. By analogy, vcat should return an nx1 Array{T,2}.
  • vectors can be concatenated without direction bias by the direction-less function cat

with the benefits that

  • This is more intuitive for beginners
  • It becomes easier to write nx1 Array as well as 1xn Array and vectors
  • Stops mixing metaphors on vectors/matrices
@andyferris
Copy link
Member

It might be worth considering also row vectors here (there might be action on JuliaLang/LinearAlgebra.jl#42 soon)

@benninkrs
Copy link

I'm not in favor of this change. There are two issues here: syntax (how commas and spaces semicolons should be interpreted) and what the functions hcat , vcat, and cat should do.

Regarding syntax: Although I was a little confused by the commas/space/semicolons syntax when I first encountered Julia, that was mostly due to my preconceived notions. I soon came to appreciate that it is (like most design features of Julia) actually quite logical and also convenient.

Regarding the behavior of vcat and hcat: These are currently shorthand for cat(1,...) and cat(2,...) . Thinking of concatenation as an operation on n-d arrays, I would find it strange to have vertical concatenation (and ONLY vertical concatenation) introduce an additional dimension as you proposed. In general I am not in favor of treating 1-d and 2-d arrays specially. Matlab does that and it leads to lots of corner cases that makes writing generic code for n-d arrays unnecessarily complicated.

@andyferris
Copy link
Member

Although I mostly agree with @benninkrs on not changing too much the behavior of these functions, I think there is some scope to refine how the literal syntax is parsed (or lowered).

  • Currently, the behaviour of vector/matrix instantiation is confusing. The proposed change would make it so that commas give vectors, spaces (hcat) and semicolons (vcat) concatenate and return 2D arrays

This should be possible:

  • commas gives vector (as now)
  • spaces only gives row vector (could be done via hcat changes in Taking vector transposes seriously LinearAlgebra.jl#42)
  • anything with at least one semicolon gives a matrix (always lowers to hvcat??)
  • we need to choose if [1;] is a 1x1 matrix or perhaps a 1x1 row vector, or see if we can accommodate both somehow. At the moment it is a vector, which seems to be the least desirable of the 3 options. Similarly, is [1 2 3] a row vector and [1 2 3;] a matrix?
  • hcat and vcat, having built-in directions, only make sense in 2D or higher. In particular, it doesn't make sense that hcat(xx::Vector{T}...) returns an 1xn Array{T,2} and vcat(xx::Vector{T}...) returns an n Array{T,1}. By analogy, vcat should return an nx1 Array{T,2}.

Row vectors will change this as hcat(::Number...) should give a row vector (or transposed vector, dual vector, whatever you want to call it).

vectors can be concatenated without direction bias by the direction-less function cat

To be honest, this makes sense to me - I thought cat was meant to work on "flat" or 1d structures.

@martinholters
Copy link
Member

anything with at least one semicolon gives a matrix

Then with v1::Vector{Float64} and v2::Vector{Float64}, we get [v1; v2]::Matrix{Float64}? That might also turn out to be a nasty surprise, sometimes. Although if vcat(v1,v2)::Vector{Float64}, that could probably be used instead. (N.B. that [v1, v2]::Vector{Vector{Float64}}.)

@felixrehren
Copy link
Contributor Author

felixrehren commented Dec 20, 2016

@benninkrs
The proposal is to make hcat and vcat more symmetrical: so that they both always generate 2D-arrays, one to give a 1xn array and the other one an nx1 array. If one of them introduces an extra dimension to a 1D structure, then in the new proposal the other one does too. (Or neither do. But it's symmetrical! So I don't understand what you mean by "only vcat adding a dimension". Did I misunderstand?) To avoid adding a dimension at all, you have , and cat. The motivation is to eliminate exactly the stumbling block for beginners that you encountered too. Whether it's more or less logical is maybe a matter of taste, but I can explain this proposal in one sentence and I can't explain the current behaviour except by listing every case.

@andyferris
Point 4, "we need to choose ...": as you seem to hint at as well, I think they should all be 2D arrays (i.e. matrices). But you propose that spaces create vectors dual to semicolon-created vectors, and I think that's ok as well.

@martinholters
Agreed, that would be a surprise if the mindset is that ; just means concatenation. But this is the combination of two abtractions: ; means vcat, and vcat is used to concatenate vectors. This proposal is to keep "; means vcat", but make it so cat is used to concatenate vectors. Once that is normal, it would no longer be a surprise. It hopefully eliminates one surprise for new users, at the cost of creating one surprise during the transition.

@andyferris
Copy link
Member

Right. I see the commas here indicate a kind of list (currently, Vector), semicolons means vcat and spaces means hcat. I really see hvcat as existing only as an optimization so that we don't need to allocate many arrays when defining one matrix, but I think that

[a b;
 c d]

should give results entirely consistent with

vcat(hcat(a b), hcat(c,d))

or, if you prefer to change the semantics to this,

hcat(vcat(a,c), vcat(b,d))

that would be OK too. But having hvcat not meaning an optimization of one of the above just adds to confusion.

We could (and I would argue we should) change the lowering to hvcat((1,2), (3,4)) since it makes inferable sizes easier (e.g. for static arrays) but I think we should continue to have it mean the above.

(With regards to the first sentence, Julia will have resizeable lists and non-resizeable vectors sometime soon, so we may even need to disambiguate those, e.g. commas for lists via cat() versus semicolons for vectors via vcat(), or else we need {1, 2, 3} for lists or something).

Then with v1::Vector{Float64} and v2::Vector{Float64}, we get [v1; v2]::Matrix{Float64}? That might also turn out to be a nasty surprise

Agreed, completely. I wasn't thinking quite straight!

I feel that having hcat() create a RowVector will make the syntax feel a little more symmetrical. I do see that commas could move to cat() in a future version of Julia that ships with a List (or whatever that gets called).

@benninkrs
Copy link

benninkrs commented Dec 21, 2016

@andyferris

spaces only gives row vector (could be done via hcat changes in JuliaLang/LinearAlgebra.jl#42)

That makes sense if the arguments are scalars, but if they are (column) vectors, then you would get a row vector whose elements are column vectors, whereas I think the expectation (and preferred behavior) would be to get a matrix.

In the context of concatenation, I think the mindset that function X should always return type Y is too simplistic. The "symmetry" people desire really is there in the current behavior, it is just at the underlying level of how concatenation works (see my response to @felixrehren below).

@benninkrs
Copy link

benninkrs commented Dec 21, 2016

@felixrehren

The proposal is to make hcat and vcat more symmetrical: so that they both always generate 2D-arrays, one to give a 1xn array and the other one an nx1 array

See my response to @andyferris above.

I don't understand what you mean by "only vcat adding a dimension".

Sorry, I was a bit imprecise. What I meant was that only vcat would introduce a dimension that was neither present in the arrays being concatenated, nor the dimension of concatenation.

The motivation is to eliminate exactly the stumbling block for beginners that you encountered too. Whether it's more or less logical is maybe a matter of taste, but I can explain this proposal in one sentence and I can't explain the current behaviour except by listing every case.

I only stumbled because I hadn't taken the few minutes to actually read the parts of the manual that describe concatenation. The current behavior is actually quite logical and straightforward, but I agree it is not presented very clearly in the manual. The rules can be stated quite succinctly:

  • Commas separate individual elements of a vector (or tuple).
  • semicolons = vcat = cat(1,...)
  • spaces = hcat = cat(2,...)

From these rules it is easy to determine what the output type of any array construct is. Of course, this hinges on truly understanding two things: (1) the difference between enumeration and concatenation; and (2) what cat does. The behavioral consistency you seek is in the behavior of cat. Loosely speaking, it takes input arrays and "stacks" their respective elements along a specified dimension to create a new array. As a convenience (and special case), scalar inputs are accepted and treated as 1-element arrays. The output array has all the dimensions of the input arrays, plus the stacking dimension if the inputs do not already have that dimension:

julia> a=Array{Float64}(3,4,5);

julia> size(cat(1,a,a))
(6,4,5)

julia> size(cat(2,a,a))
(3,8,5)

julia> size(cat(3,a,a))
(3,4,10)

julia> size(cat(4,a,a))
(3,4,5,2)

Does that help?

@andyferris
Copy link
Member

Ignoring the OP here, I think this audience might be interested in checking out master of https://github.com/andyferris/TransposedVectors.jl - it now has the hcat semantic I mentioned (and yes it should concatenation vectors and transposed vectors as you'd expect). Let me know if you spot any inconsistencies.

@andyferris
Copy link
Member

(The goal there being to refine what is currently in v0.5 not to implement the proposal in the OP).

@benninkrs
Copy link

@andyferris That looks interesting. I saw JuliaLang/LinearAlgebra.jl#42 a while ago, but it seemed to me like much ado about a couple of fairly minor issues. However, if you have indeed found a way to remove those "minor" issues and recover all the conventions of standard linear algebra without introducing weirdness anywhere else, then kudos to you!

@JeffBezanson
Copy link
Member

This comment from @benninkrs describes the situation well: #19622 (comment)
See also issues JuliaLang/LinearAlgebra.jl#342 and #7128, which cover this adequately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants