For me a vector is just abstractly three or four numbers together. (Maybe with some defined transformation properties if I'm doing physics.) Whether a matrix is stored row-major or column-major is an implementation detail I try to hide.
In fact I used to make indexing errors all the time. But now when I need to deal with vectors and matrices, I look up or decide on the "correct" way, hide everything behind Vector and Matrix classes, implement dot and cross product once, and never fall back to the raw vector elements if possible.
For the engineers who never seen this, |i> |j> |k> work exactly like unit vectors i,j,k, but you aren't limited to the usual 3 dimensions and 3 letters. Writing |x> in a ket like this just means "I am using x as a label for a degree of freedom in some vector space, not as the literal value x". This lets you do cool shit, like if you got n dimensions you can write a vector as sum v_i |i> ranging from 0 to n. You can also do super useful stuff like merging two vector spaces together to represent a larger system (aka a tensor product). Eg if I have a vector v = sum v_i |i> representing one subsystem and another vector w = sum w_j |j> representing some other subsystem, I can construct the vector u = sum v_i w_j |i, j> representing the whole system (sum is over both i and j).
(I know you already learned all this, I wrote it for everyone else's benefit).
Unrelated, given the webs origin at cern, HTML should have used bras as start tags and kets as end tags. <HTML| <head|head> <body| ... |body> |HTML>.
As a nineteen year old first-year university student, I genuinely typed "latex bra" into google image search, pressed enter, and then had a second or two of shock as my brain was genuinely surprised to not see quantum mechanics in front of me with a nifty little "\langle" involved, but rather lots of women in decidedly unique looking underwear...
I agree about just keeping them the way they are originally facing and nothing will go wrong. The nice thing is that you can mechanically rearrange things almost like regular algebra, i.e. draw out a common factor, and don't have to think what these objects are (a vector? a dual vector? a functional?).
Same with four-vector notation / Einstein summation. As long as you are following the rules for raising and lowering indices, you cannot form "wrong" products or mess up the transformation properties.
Said in another way: as a (former) experimentalist, I don't care if the vector is p^mu or p_mu. All I care about is the product p^mu p_mu.
If you have a hard time remembering whether u should be a column vector, you can simply identify things that look like u with vectors (|ket>), and things that look like u' with linear functionals (<bra|). Then u'u is just a dot product.
There are other notations, too... Sometimes (u, v) and u \cdot v are inner products, sometimes u \otimes v is an outer product.
Generally linear algebra-style notation is terser and has less line noise. IMO, some manipulations are simpler to think about using this notation than with the bra-ket notation, especially when you're doing numerical optimization or numerical linear algebra.
Maybe another reason to prefer it is that modern notation for operator algebra follows this notation and not the bra-ket notation (although I think bra-ket notation was invented for this purpose...). If L is a linear operator, L^* is its adjoint, Lu is the application of L to u, etc...
Of course, what notation you should use is determined by what community you belong to. If you're publishing papers on numerical linear algebra, it would be very strange to decide to use the bra-ket notation instead. The purpose of notation is communication. The goal is to communicate ideas to other researchers. It's good to develop the ability to be flexible with notation and go along with what other people are doing.
In my not-super-extensive experience, if you're programming and dealing with linear algebra problems - and you're past the MATLAB prototyping stage - then I really suggest using the BLAS/LAPACK API. You're not gunna beat decades of nerds programming ballistic missiles on punchcards. They're kinda weird and unwieldy and don't match to textbook math - but they've had a ton of thought put into them. And are made with as few footguns as possible. At least re-implementing an algo with them a few times is really educational. You'll see that it forces you to think about your memory layout and you'll realize that the back of napkin complexity calculations are actually tricky to massage into a good algorithms that use memory well. The final result with something like the Intel MKL will be way way faster than anything your abstraction can achieve
(Unfortunately I haven't found a good primer on using the BLAS well)
There are also many different implementations of BLAS, with varying performance. The API itself is not the reason for its performance. Actually, I would argue that it’s a pretty crufty API which should probably be done away with at this point and replaced with something less awkward. I don’t know how many times I’ve had to remind myself what a “leading dimension” was…
A good example is BLIS, which does provide the BLAS API but has a more modern “object-style” C API which is significantly easier to work with than the BLAS API with no performance hit.
Also, depending on what you’re doing, abstractions can be very helpful indeed to keep around. Even at the BLAS level, groups of bits are thought of as singles, doubles, real or complex… a matrix has a size and shape… etc. Having this information is useful. Linear algebra is full of type information which can be used to dispatch different algorithms.
Lots of other alternatives to BLAS which are a bit higher level but still very useful: Eigen and Armadillo in C++, Julia, etc.
Anyway, I would say (based on my actual, significant experience ;-) ) people are beating the guys with punch cards all the time, no reason to stay in the 70s…
For the sake of completion: A vector is a type-(1,0) tensor. A linear form (or "covector" if you prefer this term) is a type-(0,1) tensor. A bilinear form is a type-(0,2) tensor.
The point of saying the type of a tensor, is to clarify what geometric object an array of numbers represents. A curse (and a blessing) of matrix notation is that it doesn't tell you this. Both a type-(0,2) and type-(1,1) tensor can be represented by matrices, but the first is used for (let's say) dot products, and the second is used for linear transformations.
The only problem with type-(m,n) notation is remembering whether "m" is the contravariant order or the covariant order. It turns out that it's contravariant, which is the opposite to what the naming would suggest. In programming, we would do {cov: m, con: n}, so we wouldn't have to remember.
This doesn't say anything regarding their practical storage, but at least conceptually vectors/covectors are a bit more than just a collection of numbers.
This distinction is pretty useful in practice when encoded in types to avoid implicitly mixing things that are conceptually different.
Horizontal vectors are forms (maps from R^n to R). Vertical vectors are vectors (elements of R^n).
Forms applied to vectors are “rows multiplied by columns”, and so: numbers.
Same thing as bras and kets, by the way.
It's also an example of how "making it simpler" by dropping types (old Python, Bash, typeless vectors and matrices) ends up making it harder to get work done right when the ideas are non-trivial.
Declaring that some objects are forms (duals, covariant vectors, ...) is only neccessary when you want to keep track of transformation properties, for example.
But when you define a matrix in most programming languages you can only really write it like this:
mat = [
[1,2,3],
[4,5,6],
[7,8,9]
]
Note that the inner vectors are laid out as rows, and the outer vector (the matrix) is laid out as a column.Some languages have fancy syntax that omits the need for braces. But for those that don't, this is clearly an array of 3 arrays of 3 ints. It's only intuitive to think of this as an array of rows given how it's laid out visually.
I also think in that context it would make more sense to index it as mat[row][col], or in other words row major indexing.
Because of that, it also makes sense to store the mat row-major, that is, laid out in memory as [1,2,3,4,5,6,7,8,9] so that mat[row][col] becomes mem[row*3 + col].
This is also how C does it. I think it was a mistake for OpenGL to break with that C convention given how close the relationship between C and OpenGL is. Mathematicians be damned.
>It's only intuitive to think of this as an array of rows given how it's laid out visually.
That's often the least important thing when designing a fast library. It's much better to optimize for common operations in your problem to be fast and thus choose the storage layout of a individual matrix instance accordingly (row-major, column-major, triangular, diagonal, block diagonal, band) so your data cache doesn't get tripped up regularily.
The actual API for creating new matrices can still be standardized and always the same. You can have the standardized API accept four column vectors for construction just as well. Just document it as such.
If you want, you can also represent the different kinds of vectors as different types, so you don't get identification problems that shouldn't exist. Also, lists of vectors are just that and there's no reason to represent both "list" and "vector" as "array". There are reasons to make the API for "vector" richer--especially since that's why the term "vector" was defined in the first place.
But for tiny 4x4 matrices, who cares about caching. But 4x4 are a minority use of matrices. Try 4096x4096.
As for OpenGL, they chose to keep the internal storage of matrices the same as it was on IRIS GL. That's a good decision for OpenGL and orthogonal to the question of how column vectors of that matrix can be accessed via an API and to the question of how row vectors of that matrix can be accessed via an API and to the question of how matrices can be created via an API.
From a mathematics standpoint, a matrix represents a linear map (a map is a function that takes a vector in space V to a vector in space W). It makes sense to be able to use matrix and linear map interchangeably as a user (i.e. use them just like you would call functions).
Where should I, if we keep the context of OpenGL?
Apart from that, I consider this an implementation issue. My post was mostly about keeping a consistent "front end" semantics. However the compiler or library transforms the data does not pertain to it.
To be fair, I mentioned memory layout. If I had left that out, would we agree with eachother?
Firstly, almost no-one actually creates their matrices as literals. Instead they are build dynamically an usually incrementally. Nevertheless, people still think back to these very basic examples when trying to understand things, so this probably should be standard.
Secondly, these decisions matter a lot for matrix vector multiplication. The 'C' way is very efficient for computing Ax which presumes vector x to be a Column vector. When you want to compute xA instead which presumes vector x to be a row vector, you want column-major ordering for efficiency. The same works for matrix-matrix multiplication AB where you want A to be row-major and B to be column-major.
To sum up. Because of our writing direction, and the natural way to write 2-d arrays, it makes sense to have row-major ordering. That immediately means it also makes sense to use column vectors, generally speaking. There are also exceptions if you happen to know if a matrix will often be at the 'left' or 'right' side of a multiplication.
Some more interesting notes that are less relevant:
In C the matrix you defined has an incompatible type with how most matrices are stored (most people in C store rows contiguously, then define a matrix as an array of pointers to rows).
int mat[3][3]; vs int *mat[3]
These are meaningfully different. In the first case getting element `mat[1][1]` is just 'base-pointer + 4'. Whereas in the second case, getting element `mat[1][1]` is 'read pointer at base-pointer + 1 and add 1 to the added pointer`.Another common ways to use matrices is element-wise, where you refer to x[i][j]. Mathematicians expect i to be the row and j to be the column. Which matches C. Whilst Fortran does it the other way around.
You have this the wrong way around. The C row-major ordering is better for A^Tx, the Fortran column-major format is better for Ax.
If you do Ax in row-major, you end up multiplying a row of A by x as your simd vector op. This then leaves you needing to horizontally reduce the result, which you avoid by using a column-major layout that instead accumulates multiple different results.
That’s not quite it. The fact that the row index is first is purely a convention. And by convention, i in In X(i,j) in some Fortran code is still the row index.
The difference with C is the layout in memory, not the semantics: the fact that X(i+1,j) is contiguous to X(i,j) in Fortran and not in C, which means that for efficiency reasons inner loops in Fortran are over row indices rather than column indices as in C.
It would have been odd for Fortran, which has been since the beginning a language for numerical calculations, to “not match what mathematicians expect”.
int (*mat)[3] = calloc(3, sizeof(int[3]));
which is a pointer to (or array of) arrays of 3 ints. Laid out flat, and you still get the `mat[i][j]` notation.I’ve written godawful python extensions to feed TF or PyTorch mmapped regions to the pipeline in an efficient way. This is currently the way of things.
But half the trouble in model architecture engineering is keeping the ranks straight and having tools to debug when you messed up.
Einstein struggled with tensor math, read his letters. But he didn’t have modern computers or autodiff either. For that era, thinking in higher dimensions was a job for a genius.
I harbor a vague suspicion that some people get paid more because it’s needlessly hard.
Mathematicians use row-major indexing just like any sensible person.
Oh we can get more abstract than that.
A vector is an element of a vector space, or, if you'd like, an F-module where F is a field. A matrix is a homomorphism between vector spaces.
Mathematicians generally don't care because equations are basically pseudocode and they can make up new syntax and hand-wave edge cases away whenever they want.
Mathematical conventions are really dependent on each other. If you change one — a lot of others become awkward.
I wrote short notes about conventions in mathematics to understand them better: (pdf) https://github.com/dandanua/little-endian-vs-big-endian-in-q...
the matrix is simply the layout and so R^n is column on the other side of the equals. It's arbitrary.
But in an abstract sense, matrix is a presentation of a linear operator in a specific basis. If we choose to write it as a transposed matrix, for example, then the matrix product rule would be different (it should correspond to the composition of linear operators).
For computer graphics, the main advantage is that you can represent translation and projection in the matrix in addition to the other linear transformations like rotation and scaling.
For projection, the 4th vector coordinate, usually named "w", is called the homogeneous coordinate and it is typically not 1 after going through the projection matrix. As a final step, it divides all the other coordinates, so (x,y,z,w) becomes (x/w,y/w,z/w,1), x/w and y/w are the 2D screen coordinates, and z/w goes into the Z-buffer.
A matrix represents a transform of the form:
x' = Ax + By + Cz
y' = Dx + Ey + Fz
z' = Gx + Hy + Iz
...the A...I letters being the elements of the 3x3 matrix. (If you squint you can see the matrix above).Although this can represent scales and rotations, there's no way to represent a simple translation with this.
As proof, imagine you want to move everything 3 units along the x axis. You really just want to add 3 to x (x' = x + 3) but you can't: x' is always defined in terms of x, y and z (x' = Ax + By + Cz). There's no room for a constant.
To represent translations, then, what you really want is (x' = Ax + By + Cz + D), where D isn't multiplied by any component of the input vector, it's just D, your translation.
Well, it turns out you can do this by just adding an extra column to the matrix and using 1 for the fourth component of your vectors.
Now x' = Ax + By + Cz + Dw, where w=1, and D is your translation amount.
The full matrix then becomes
x' = Ax + By + Cz + Dw
y' = Ex + Fy + Gz + Hw
z' = Ix + Jy + Kz + Lw
w' = Mx + Ny + Oz + Pw
You can see how (D, H, L) now functions as a translation vector.Now here's the trick. You can embed your three-dimensional space into a four-dimensional space at a fixed fourth coordinate (but not zero). In this new space, translation in the original three-dimensional space is a linear transform.
You may opt into some other mathematical niceties too, which are handy for applying perspective transformations, but the main takeaway is that you get efficient compositions of affine transformations.
As someone who is studying the subject, I would be greatful if someone can please explain the technical reasons for this. I do not understand why I can't write them as row vectors and do the same Linear Algebra trickery (which we do with column vectors) another way around.
That's not just a mechanical computational consideration. One important interpretation of matrix-matrix multiplication is composition of linear transformations. When multiplying AB, you apply multiply matrix A by each column (vector) of B, and concatenate them to form the columns of the result AB. This corresponds to applying the transformation represented by A to a set of basis vectors. This correspondence is essential to the interpretation of matrix multiplication as a linear transformation, so it's important that it work out without additional manipulation.
Matrix-vector multiplication is more or less the heart of linear algebra, so in general it needs to be an elegant sensible operation.
I understand why the "rules" of matrix multiplication make the distinction -- but what is underneath those rules? What does it mean?
I think it's because column vectors are less of a pain to write on the blackboard.
I'm being serious. There is no reason to say that columns are vectors and rows are linear functionals. The other way around is a perfectly valid convention. But I did find that when I taught linear algebra, column vectors were just easier to write on the board.
That way, you can have sparse vectors, or sparse matrices. It doesn't matter how many dimensions are specified for your object, stored in memory. It gets transformed the same.
Have a point at the origin? [1] Done. It doesn't matter if it's a 2D, 3D, 7D.
Have a 0-vector? [0] Done.
Have a 2D point? [1, x, y]
Have a 3D point? [1, x, y, z]
Have a 1D vector? [0, x]
I honestly don't know why we don't all do it this way.
(g o f)(x) = g(f(x)).
Notice that the function f, which is applied first, appears on the right and g, which is applied second, appears on the left. But if we draw a diagram of the composite, it looks like this: f g
X ----> Y ----> Z
In the diagram, the first applied function is on the left and the second applied function is on the right. Some people have felt that, for this reason, it would be better to write composites with the arguments on the left, like this: (x)f and [x(f)]g . However, most mathematicians follow the right-to-left convention (that's the way it seems to be in most programming languages, right?).Anyway, consider how this works if f and g are linear transformations and X, Y, and Z are finite-dimensional vector spaces, as in linear algebra. So we can take X = F^p, Y = F^n, and Z = F^m, where F is the field of scalars, and vectors are just "arrays of numbers".
f: F^p -> F^n, g: F^n -> F^m
You can represent f and g using matrices: f is represented by an n x p matrix B and g is represented by an m x n matrix A. In that case, function application is done by matrix multiplication. The composite g o f is represented by the product A B, which is an m x p matrix. Notice that the matrices are in the same order in "A B" as their functions are in "g o f". Inputs to g o f are p-dimensional vectors; where should a p-dimensional vector go in order that multiplication by A B should represent application of the function g o f? A B [p x 1 column vector] ---> okay
[1 x p row vector] A B ---> wrong: you can't multiply (1 x p) * (m x p)
In other words, once you commit to the standard convention for writing composites, you're committed to using column vectors as inputs to linear transformations written as matrices, or somewhere you'll probably have an inconsistency in the order of application.Or you can bite the bullet, write arguments to functions on the left, and then defaulting to row vectors would be okay. It's just notation.
for me, the GL style (whatever you decide to call it) seems to make sense from a performance POV. I often want one axis or the translation. It's a single SIMD load since vs if they were stored the other way I'd have to load every 4th float.
Also, while you do type them out in code different from math
x x x x
y y y y
z z z z
t t t t
If you squint you can view them as columns xaxis = [
.
.
.
.
]
yaxis = [
.
.
.
.
]
zaxis = [
.
.
.
.
]
translation = [
.
.
.
.
]
matrix = [xaxis, yaxis, zaxis, translation] // 4 columns side by sideFor most software frameworks, there is anyway not a diff between column vector vs row vector, it's just shape [dim] in any case, but it becomes relevant how you do matmul etc. with some other matrix [dim1, dim2] or some other tensor [dim1, dim2, dim3].
Is the matrix row major or column major?
https://bugfix-66.com/e74dcfb6a10cb71404f495922533d947e39fd7...