Transpose (β
) is a tool for rearranging the axes of an array π©
. Without a left argument, it moves the first axis to the end, while a left argument can specify an arbitrary rearrangement. Both cases are tweaked relative to APL to align better with the leading axis model and make common operations easier.
The name for the primitive β
comes from the Transpose operation on matrices. Given a matrix as an array of rank 2, β
will transpose it:
β’ mat β 2βΏ3 β₯ β6 ββ β΅ 0 1 2 3 4 5 β β mat ββ β΅ 0 3 1 4 2 5 β
Transpose is named this way because it exchanges the two axes of the matrix. Above you can see that while mat
has shape 2βΏ3
, βmat
has shape 3βΏ2
, and we can also check that the element at index iβΏj
in mat
is the same as the one at jβΏi
in βmat
:
1βΏ0 β mat 3 0βΏ1 β β mat 3
With two axes the only interesting operation of this sort is to swap them (and with one or zero axes there's nothing interesting to do, and β
just returns the argument array). But a BQN programmer may well want to work with higher-rank arraysβalthough such a programmer might call them "tensors"βand this means there are many more ways to rearrange the axes. Transpose extends to high-rank arrays to allow some useful special cases as well as completely general axis rearrangement, as described below.
APL extends matrix transposition to any rank by reversing all axes for its monadic β
, but this generalization isn't very natural and is almost never used. The main reason for it is to maintain the equivalence a MP b ββ b MPβΎβ a
, where MP β +ΛβΓβ1βΏβ
is the generalized matrix product. But even here APL's Transpose is suspect. It does much more work than it needs to, as we'll see.
BQN's transpose takes the first axis of π©
and moves it to the end.
β’ a23456 β β2βΏ3βΏ4βΏ5βΏ6 β¨ 2 3 4 5 6 β© β’ β a23456 β¨ 3 4 5 6 2 β©
In terms of the index-ordered elements as given by Deshape (β₯
), this looks like a simple 2-dimensional transpose: one axis is exchanged with a compound axis made up of the other axes. Here we transpose a rank 3 matrix:
a322 β 3βΏ2βΏ2β₯β12 βββ a322 ββ Β· ββ ββ β 0 1 β 0 4 8 2 3 1 5 9 4 5 2 6 10 6 7 3 7 11 β 8 9 10 11 β β
But, ignoring the whitespace and going in reading order, the argument and result have exactly the same element ordering as for the rank 2 matrix β₯Λ a322
:
βββ β₯Λ a322 ββ Β· ββ ββ β΅ 0 1 2 3 β΅ 0 4 8 4 5 6 7 1 5 9 8 9 10 11 2 6 10 β 3 7 11 β β
To exchange multiple axes, use the Repeat modifier. A negative power moves axes in the other direction, just like how Rotate handles negative left arguments. In particular, to move the last axis to the front, use Undo (as you might expect, this exactly inverts β
).
β’ ββ3 a23456 β¨ 5 6 2 3 4 β© β’ ββΌ a23456 β¨ 6 2 3 4 5 β©
In fact, we have β’ββk a ββ kβ½β’a
for any whole number k
and array a
.
To move axes other than the first, use the Rank modifier in order to leave initial axes untouched. A rank of k>0
transposes only the last k
axes while a rank of k<0
ignores the first |k
axes.
β’ ββ3 a23456 β¨ 2 3 5 6 4 β©
And of course, Rank and Repeat can be combined to do more complicated transpositions: move a set of contiguous axes with any starting point and length to the end.
βοΈβ’ ββΌβΒ―1 a23456 β¨ 2 6 3 4 5 β©
Using these forms (and the Rank function), we can state BQN's generalized matrix product swapping rule:
a MP b ββ ββ(1-=a) (βb) MP (ββΌa)
Certainly not as concise as APL's version, but not a horror either. BQN's rule is actually more parsimonious in that it only performs the axis exchanges necessary for the computation: it moves the two axes that will be paired with the matrix product into place before the product, and directly exchanges all axes afterwards. Each of these steps is equivalent in terms of data movement to a matrix transpose, the simplest nontrivial transpose to perform. Also remember that for two-dimensional matrices both kinds of transposition are the same, so that APL's simpler rule MP β‘ MPβΎβΛ
holds in BQN on rank 2.
Axis permutations of the types we've shown generate the complete permutation group on any number of axes, so you could produce any transposition you want with the right sequence of monadic transpositions with Rank. However, this can be unintuitive and tedious. What if you want to transpose the first three axes, leaving the rest alone? With monadic Transpose you have to send some axes to the end, then bring them back to the beginning. For example [following four or five failed tries]:
βοΈβ’ ββΌβΒ―2 β a23456 # Restrict Transpose to the first three axes β¨ 3 4 2 5 6 β©
In a case like this the dyadic version of β
, called Reorder Axes, is much easier.
Transpose also allows a left argument that specifies a permutation of π©
's axes. For each index pβiβπ¨
in the left argument, axis i
of π©
is used for axis p
of the result. Multiple argument axes can be sent to the same result axis, in which case that axis goes along a diagonal of π©
, and the result will have a lower rank than π©
(see the next section).
β’ 1βΏ3βΏ2βΏ0βΏ4 β a23456 β¨ 5 2 4 3 6 β© β’ 1βΏ2βΏ2βΏ0βΏ0 β a23456 # Don't worry too much about this case though β¨ 5 2 3 β©
Since this kind of rearrangement can be counterintuitive, it's often easier to use ββΌ
when specifying all axes. If pβ‘ββ β’a
, then we have β’pββΌa ββ pββ’a
.
β’ 1βΏ3βΏ2βΏ0βΏ4 ββΌ a23456 β¨ 3 5 4 2 6 β©
BQN makes one further extension, which is to allow only some axes to be specified (this is the only difference in dyadic β
relative to APL). Then π¨
will be matched up with leading axes of π©
. Those axes are moved according to π¨
, and remaining axes are placed in order into the gaps between them.
β’ 0βΏ2βΏ4 β a23456 β¨ 2 5 3 6 4 β©
In particular, the case with only one axis specified is interesting. Here, the first axis ends up at the given location. This gives us a much better solution to the problem at the end of the last section.
βοΈβ’ 2 β a23456 # Restrict Transpose to the first three axes β¨ 3 4 2 5 6 β©
Finally, it's worth noting that, as monadic Transpose moves the first axis to the end, it's equivalent to Reorder Axes with a "default" left argument: (=-1Λ)βΈβ
.
When π¨
contains an axis index more than once, the corresponding axes of π©
will all be sent to that axis of the result. This isn't a special case: it follows the same rule that iβπ¨βπ©
is (π¨βi)βπ©
. Only the result shape has to be adjusted for this case: the length along a result axis is the minimum of all the axes of π©
that go into it, because any indices outside this range will be out of bounds along at least one axis.
A bit abstract. This rule is almost always used simply as 0βΏ0βπ©
to get the main diagonal of a matrix.
β’ a β 3βΏ5β₯'a'+β15 ββ β΅"abcde fghij klmno" β 0βΏ0 β a "agm" β¨2β©β0βΏ0βa # Single index into result 'm' β¨2,2β©βa # is like a doubled index into a 'm'
Here we define the two valences of Transpose more precisely.
An atom right argument to Transpose or Reorder Axes is always enclosed to get an array before doing anything else.
Monadic Transpose is identical to (=-1Λ)βΈβ
, except that if π©
is a unit it's returned unchanged (after enclosing, if it's an atom) rather than giving an error.
In Reorder Axes, π¨
is a number or numeric array of rank 1 or less, and π¨β€ββ β’π©
. Define the result rank rβ(=π©)-+´¬βπ¨
to be the rank of π©
minus the number of duplicate entries in π¨
. We require β§Β΄π¨<r
. Bring π¨
to full length by appending the missing indices: π¨βΎβ©π¨(Β¬ββΛ/β’)βr
. Now the result shape is defined to be β´¨π¨ββ’π©
. Element iβz
of the result z
is element (π¨βi)βπ©
of the argument.